
從風險到防護:TON 智能合約的安全隱患與優化建議
TechFlow Selected深潮精選

從風險到防護:TON 智能合約的安全隱患與優化建議
本文將詳細分析在 TON 區塊鏈上的一些與智能合約有關的特性,以及 TON 上智能合約容易被忽略的漏洞點。
撰文:Beosin
在區塊鏈技術快速發展的今天,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 的數量。

圖源:https://frontierlabzh.medium.com/ton-web3 世界的 weixin-e1d3ae3b3574
在 Ton 中,智能合約通過發送和接收消息進行交互。這些消息可以是內部消息(一般來說是智能合約互相交互所發送的消息)或外部消息(由外部來源發送的消息)。消息的傳遞過程不需要等待目標合約的立即響應,發送方可以繼續執行其餘的邏輯代碼。這種異步消息傳遞機制相較於以太坊的同步調用,提供了更高的靈活性和擴展性,減少了因等待響應導致的性能瓶頸,同時也帶來了處理併發和競爭條件的挑戰。
消息格式與結構
在 Ton 中,消息通常包含發件人、收件人、金額、消息體等信息。消息體可以是函數調用、數據傳輸或其他自定義內容。Ton 使用的消息格式可以靈活定義和擴展,使得不同合約之間能夠高效傳遞各種類型的信息。
cell msg = 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 樹進行組織,以確保狀態的完整性和安全性。這種設計還支持狀態的高效查詢和驗證,尤其是在跨分片交易的場景中。
帳戶或智能合約狀態通常包含以下內容:
-
基礎貨幣的餘額
-
其他貨幣的餘額
-
智能合約代碼(或其哈希)
-
智能合約的持久化數據(或其 Merkle 哈希)
-
有關持久化存儲單元數和使用的原始字節數的統計信息
-
智能合約持久存儲的付款的最近時間(實際上是主鏈塊號)
-
轉移貨幣並從此帳戶發送消息所需的公鑰(可選; 默認情況下等於 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 {
slice ds = 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() {
var cell = begin_cell();
cell = cell.store_int(-42, 32);
var my_cell = cell.end_cell();
slice s = my_cell.begin_parse();
var result = 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 messages
return ();
}
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 平臺的優勢,構建更加安全可靠的去中心化應用,為整個生態系統的健康發展保駕護航。
目前 TON 生態正在快速發展,吸引了大量的資金與活躍用戶。然而,隨之而來的安全問題也不容忽視。
歡迎加入深潮 TechFlow 官方社群
Telegram 訂閱群:https://t.me/TechFlowDaily
Twitter 官方帳號:https://x.com/TechFlowPost
Twitter 英文帳號:https://x.com/BlockFlow_News














