本文將詳細分析在 TON 區塊鏈上的一些與智能合約有關的特性,以及 TON 上智能合約容易被忽略的漏洞點。
作者: Beosin
封面: Photo by ilgmyzin on Unsplash
在區塊鏈技術快速發展的今天,TON (The Open Network) 作為一款高效且靈活的區塊鏈平台,正受到越來越多開發者的關注。 TON 的獨特架構和特性為去中心化應用的開發提供了強大的工具和豐富的可能性。
然而,随着功能和复杂性的增加,智能合约的安全性也变得越来越重要。FunC 作为 TON 上的智能合约编程语言,以其灵活性和高效性著称,但同时也带来了许多潜在的风险和挑战。编写安全可靠的智能合约,需要开发者深刻理解 FunC 语言的特性以及可能存在的风险。
本文將詳細分析在 TON 區塊鏈上的一些與智能合約有關的特性,以及 TON 上智能合約容易被忽略的漏洞點。
Ton 非同步特性與帳戶機制解析
智能合約異步調用
網路分片與非同步通信
TON 區塊鏈在設計上分為三種鏈:主鏈 (Masterchain),工作鏈 (Workingchains)和分片鏈 (Shardchains)。
主鍊是整個網路的核心,負責儲存全網的元資料和共識機制。它記錄所有工作鏈和分片鏈的狀態,並確保全網的一致性和安全性。工作鏈是獨立的區塊鏈,最多有 2^32 條,負責處理特定類型的交易和智慧合約。每個工作鏈可以有自己的規則和特性,以滿足不同的應用需求。分片鍊是工作鏈的子鏈,用來進一步分割工作鏈的負載,提升處理能力和擴展性。每個工作鏈最多拆分為 2^60 個 shard chain,分片鏈獨立處理部分交易,從而實現高效的並行處理。
理論上每個帳戶都可以獨佔一個 shard chain,每個帳戶獨立維護自己的 COIN/TOKEN 餘額,每個帳戶間的交易都可以完全並行。帳戶與帳戶間透過非同步訊息傳遞,訊息在 shard chain 間傳遞的路徑為 log_16(N) - 1,其中 N 為 shard chain 的數量。
在 Ton 中,智能合約透過發送和接收訊息進行互動。這些訊息可以是內部訊息(一般來說是智慧合約互相互動所發送的訊息)或外部訊息(由外部來源發送的訊息)。訊息的傳遞過程不需要等待目標合約的立即回應,發送者可以繼續執行其餘的邏輯代碼。這種非同步訊息傳遞機制相較於以太坊的同步調用,提供了更高的靈活性和擴展性,減少了因等待響應而導致的效能瓶頸,同時也帶來了處理並發和競爭條件的挑戰。
訊息格式與結構
在 Ton 中,訊息通常包含寄件者、收件者、金額、訊息體等資訊。訊息體可以是函數呼叫、資料傳輸或其他自訂內容。 Ton 使用的訊息格式可以靈活定義和擴展,使得不同合約之間能夠有效率地傳遞各種類型的信息。
cellmsg = begin_cell().store_uint(0x18,6).store_slice(addr).store_coins(amount).store_uint(0,1 + 4 + 4 + 64 + 32 + 1 + 1).store_slice(message_body).end_cell();
訊息佇列與狀態處理
每個合約都維護一個訊息佇列,儲存尚未處理的訊息。合約在執行過程中,會根據佇列中的消息逐一處理。由於訊息處理是異步的,合約的狀態在收到訊息之前不會立即更新。
非同步訊息傳遞的優勢
•高效的分片機制:Ton 的非同步機制與其分片設計高度契合。每個分片獨立處理合約的訊息和狀態變化,避免了跨分片同步通訊帶來的延遲問題。這種設計提升了整個網路的吞吐量和可擴充性。
•降低資源消耗:由於非同步訊息不要求即時回應,Ton 的合約執行可以分散在多個區塊內完成,避免了單一區塊內資源的過度消耗。這使得 Ton 能夠支持更複雜和資源密集的智慧合約。
•容錯性與可靠性:非同步訊息的傳遞機制使得系統更具容錯性。例如,如果某個合約因資源限製或其他原因無法及時回應訊息,發送者仍然可以繼續處理其他邏輯,系統不會因為單一合約的延遲而停滯。
非同步合約設計的挑戰
•狀態一致性問題:由於訊息傳遞是非同步的,合約的狀態在不同時刻可能會接收到不同的訊息,這需要開發者特別注意狀態一致性問題。在設計合約時,必須考慮到不同訊息順序可能帶來的狀態變化,確保系統在任何情況下都能保持一致性。
•競爭條件與防護:非同步訊息處理帶來了潛在的競爭條件問題,多個訊息可能同時嘗試修改合約狀態。開發者需要引入適當的鎖定機製或使用事務性操作來防止狀態衝突。
•安全性考量:非同步合約在處理跨合約通訊時,容易受到中間人攻擊或重播攻擊。因此,在設計非同步合約時,必須考慮到這些潛在的安全風險,並採取措施防止它們發生,例如使用時間戳記、隨機數或多重簽章等手段。
帳本模型
Ton(The Open Network)在設計其區塊鏈基礎架構時,採用了獨特的帳戶抽象化和帳本模型。這個模型的靈活性體現在它如何處理帳戶的狀態、訊息傳遞以及合約的執行。
帳戶抽象
Ton 的帳戶模型採用了基於合約的抽象,每個帳戶都可以視為一個合約,這與以太坊的帳戶抽像模型有一些相似之處,但更靈活和通用。在 Ton 中,帳戶不僅僅是持有資產的容器,它們還包含了合約代碼和狀態資料。每個帳戶都由其代碼(Code)、資料(Data)和訊息處理(Message Handling)邏輯組成。
帳戶結構:每個 Ton 帳戶都有一個唯一的位址,該位址是由帳戶代碼的雜湊值、部署時的初始資料以及一些其他參數組合而成的。這意味著同樣的程式碼和初始資料部署在不同的環境下(例如,不同的區塊鏈或分片)可能會產生不同的位址。
靈活性:由於每個帳戶都可以運行自己的合約代碼,因此 Ton 的帳戶可以實現非常複雜的邏輯。帳戶不僅僅是簡單的餘額持有者,還可以處理複雜的狀態轉移、跨帳戶的訊息通訊、甚至是基於特定條件的自動化操作。這使得 Ton 的帳戶模型比傳統區塊鏈上的帳戶模型更具擴展性和靈活性。
帳本結構
Ton 的帳本結構設計為高效處理大規模並發交易,支援非同步訊息傳遞和多分片操作。每個帳戶的狀態保存在 Merkle 樹結構中,這使得 Ton 的帳本具有高效的狀態驗證能力。
狀態儲存
帳戶的狀態資訊儲存在持久化儲存中,並透過 Merkle 樹進行組織,以確保狀態的完整性和安全性。這種設計還支援狀態的高效查詢和驗證,尤其是在跨分片交易的場景中。
帳戶或智能合約狀態通常包含以下內容:
1. 基礎貨幣的餘額
2. 其他貨幣的餘額
3. 智能合約程式碼(或其哈希)
4. 智能合約的持久化資料(或其 Merkle 雜湊)
5. 有關持久化儲存單元數和使用的原始位元組數的統計信息
6. 智能合約持久儲存的付款的最近時間(實際上是主鏈區塊號)
7. 轉移貨幣並從此帳戶發送訊息所需的公鑰(可選; 預設等於 account_id 本身)。在某些情況下,類似於比特幣交易輸出所做的,可以在此處找到更複雜的簽名檢查代碼; 然後 account_id 將等於此代碼的哈希值。
並非所有的資訊都是每個帳戶必須需要的。例如,智慧合約程式碼僅適用於智慧合約,但不適用於「簡單」帳戶。此外,雖然任何帳戶必須具有主要貨幣的非零餘額(例如,基本工作鏈的主鍊和分片鏈的 Gram),但其它貨幣的餘額可能為零。為了避免保留未使用的數據,在工作鏈的創建期間定義了一個 sum-product 類型,它使用不同的標記位元組來區分不同的「夠造函數」。最終,帳戶狀態本身被保存為 TVM 持久化儲存的單元集合。
訊息傳遞與處理
Ton 的帳本結構內建了對非同步訊息傳遞的支持,每個帳戶可以獨立處理接收到的訊息並更新其狀態。這種非同步訊息機制允許帳戶之間進行複雜的交互,而不會因為某個操作的延遲而影響其他帳戶的正常運作。
Gas 模型
Ton(The Open Network)區塊鏈透過其獨特的 Gas 費模型大幅優化了智慧合約的執行效率。 Gas 費模型在區塊鏈中用於衡量和限制智慧合約執行過程中消耗的資源。與傳統區塊鏈(如以太坊)的 Gas 模型相比,Ton 的模型設計更為複雜且高效,能夠更精確地管理合約執行過程中的資源消耗。
細化的 Gas 消耗量測量
Ton 的 Gas 模型能夠精確測量智慧合約在執行過程中消耗的運算資源、儲存操作以及訊息傳遞成本。透過對計算、儲存和訊息傳遞等資源的細化測量,Ton 的 Gas 模型能夠防止某些複雜度過高的操作佔用過多的資源。透過限制 Gas 消耗,Ton 確保了網路的每個節點都能公平地分配運算資源,避免單一合約或操作對網路資源的過度消耗。
並行處理與 Gas 優化
Ton 支持智能合約的平行處理,這使得多個合約能夠同時在不同的分片上運行,而不會相互阻塞。在這種設計下,其 Gas 模型與其並行執行和分片機制緊密結合,透過在多個分片上並行處理合約,Ton 可以將 Gas 的計算和支付分散到不同的節點和鏈上,避免了網路擁堵,同時最大化了資源利用率。
動態 Gas 調整機制
Ton 的 Gas 模型中包含了動態調整機制,允許根據網路的即時負載情況對 Gas 費進行調整。這意味著在網路負載較低時,使用者可以以較低的 Gas 費用執行合約,從而鼓勵在低負載時段進行操作,平衡網路的資源使用。這種機制不僅提升了使用者體驗,也透過市場化的方式控制了資源的使用高峰。
Ton 智能合約易忽略漏洞
在我們上一篇 TON 的安全分析文章中已經詳細介紹了 Ton 生態的常規安全漏洞,也可參考下表:
本文將重點放在我們團隊總結的 TON 合約中容易被忽略的漏洞點:
(1) 程式碼可讀性優化
在 TON 的智能合約中,會使用數字來儲存訊息發送的相關數據,例如下面的程式碼中,多次使用數字來表示對應的標識和數據儲存長度,這大大降低降低程式碼的可讀性和可維護性。其他開發者在閱讀這些程式碼時,很難理解這些數字的意義和用途。為了提高程式碼的可讀性和可維護性,建議將關鍵的數字值定義為具名常數,例如:0x18 定義為 NON_BOUNCEABLE。
check_std_addr(address);var msg = begin_cell() store_uint(0x18, 6) ;; nobounce store_slice(address) store_coins(amount) store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) end_cell();send_raw_message(msg, 1);
另外,在合約判斷條件中的錯誤提示訊息,同樣建議定義對應的變數替換錯誤碼。
throw_unless(705, equal_slices(owner_address, sender_address));
(2) 使用 end_parse() 確保資料完整性
在 TON 合約中,資料解析遵循固定的順序,從原始資料逐步載入指定類型的資料。這種解析方式確保了資料的一致性和準確性。如下圖所示:
()load_data() impure {sliceds = get_data().begin_parse();storage::owner = ds~load_msg_addr();storage::amount = ds~load_uint(256);storage::data = ds~load_ref();storage::api_data = ds~load_ref();ds.end_parse();}
注意這裡的 end_parse() 用來檢查資料切片(slice)是否為空,如果切片不為空,函數會拋出一個異常。這樣可確保資料的格式和內容都是符合預期的。如果 end_parse() 函數發現資料切片中仍然有剩餘的數據,這可能表示資料解析沒有完全按照預期進行,或者資料的格式有問題。因此,透過呼叫 end_parse(),可以檢查是否解析過程中資料有遺漏或異常。
(3) 資料記載和儲存類型不符引發的異常
這裡主要需要說明的是 int 和 uint 的存取類型匹配,如下所示的程式碼中,資料儲存時使用了 store_int() 來儲存 int 類型的值為-42,但卻使用了 load_uint() 來載入這個值,這裡就可能出現異常。
()Test_Fuction() {varcell = begin_cell();cell = cell.store_int(-42, 32);varmy_cell = cell.end_cell();slices = my_cell.begin_parse();varresult = s.load_uint(32);}
(4)inline_ref 和 inline 修飾符的合理使用
首先,需要闡述 inline_ref 和 inline 修飾符的區別:
lInline:使用 inline 修飾符的函數,其程式碼會在每次呼叫時直接插入到呼叫位置。也就是說,每次呼叫函數時,函數的實際程式碼會被複製到呼叫的位置,而不是像普通函數那樣透過跳到函數體執行。
linline_ref:使用 inline_ref 修飾符的函數,其程式碼儲存在一個獨立的 cell 中。每次呼叫函數時,TVM 透過 CALLREF 指令來執行儲存在 cell 中的程式碼,而不是在呼叫位置插入函數程式碼。
所以,inline 修飾符適用於簡單函數,減少函數呼叫開銷,但可能導致合約程式碼重複;而 inline_ref 修飾符適用於較複雜或被多次呼叫的函數,透過將函數程式碼儲存在單獨的 cell 中來提高效率,避免了程式碼重複。那麼可以總結為:當函數較大或被多個地方呼叫時,建議使用 inline_ref;反之,則建議使用 inline。
(5) 確定正確的工作鏈
TON 允許創建多達 2^32 條工作鏈,每條工作鏈則可細分為多達 2^60 個分片,前只有 2 個工作鏈:主鏈 (-1) 和基本鏈 (0)。合約中計算目標位址時,必須明確指定目標位址所屬的鏈 ID,以確保產生的錢包位址位於正確的工作鏈上。為了避免產生錯誤位址,建議使用 force_chain() 強制指定鏈 ID。
(6) 避免錯誤碼衝突
在合約設計中,為了確保規範性和避免混淆,錯誤碼的管理非常關鍵。對於 TON 智能合約,首先應確保每個錯誤碼在合約中是唯一的,避免在同一個合約中定義重複的錯誤碼,以防止錯誤碼的混淆和信息的不明確;其次 TON 平台或底層系統已經定義了一些標準的錯誤碼,應避免與這些系統錯誤碼衝突,例如 333 錯誤碼表示的鏈 ID 不符。所以建議合約的錯誤碼最好在 400 到 1000 之間。
(7) 操作完成後需要儲存資料和呼叫 return()
在 TON 智能合約中,訊息處理會根據 op-code 選擇不同的邏輯。完成對應業邏輯後,還需完成兩項操作:首先,如果涉及資料更改,必須呼叫 save_data() 以確保資料被存儲,否則更改將無效;其次,必須呼叫 return() 以表示該操作完成,否則將觸發 throw(0xffff) 異常。
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {int flags = cs~load_uint(4);if (flags & 1) { ;; ignore all bounced messagesreturn(); }slice sender_address = cs~load_msg_addr();load_data();int op = in_msg_body~load_op();if ((op == op::op_1())) { handle_op1(); save_data();return (); }if ((op == op::op_2())) { handle_op2(); save_data();return (); }if ((op == op::op_3())) { handle_op3(); save_data();return (); }throw(0xffff);}
綜上所述,TON 區塊鏈憑藉其創新的架構和靈活的開發環境,正逐漸成為去中心化應用開發者的理想平台。然而,隨著智能合約在 TON 生態系統中扮演越來越重要的角色,合約安全性問題也不容忽視。開發者應深入了解 TON 生態的特性,嚴格遵循最佳實踐,強化安全審計流程,確保合約的穩健性與安全性。只有這樣,才能充分發揮 TON 平台的優勢,建構更安全可靠的去中心化應用,為整個生態系統的健康發展保駕護航。
免責聲明:作為區塊鏈資訊平台,本站所發布文章僅代表作者及來賓個人觀點,與 Web3Caff 立場無關。文章內的資訊僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。