西安網(wǎng)站建設制作價格百度客戶端登錄
在過去的一段時間里,CertiK團隊對比特幣生態(tài)系統(tǒng)及其發(fā)展進行了深入研究。同時,團隊還審計了多個比特幣項目以及基于不同編程語言的智能合約,包括OKX的BRC-20錢包和MVC DAO的sCrypt智能合約實現(xiàn)。
現(xiàn)在,我們的研究重點轉向了Clarity。CertiK團隊在圓滿完成多個Clarity漏洞賞金項目后,獲得了更多關于其安全問題和常見實踐的洞見。本文將分享這些見解以及經(jīng)驗,以期可以幫助到生態(tài)建設者。
Clarity是一種由Hiro PBC、Algorand和其他利益相關者共同開發(fā)的智能合約語言。目前,它已在Stacks鏈(比特幣側鏈)上得到應用。Clarity的主要目標是提供高度的可預測性和安全性,確保智能合約按預期執(zhí)行,不會產(chǎn)生任何出乎意料的副作用。
接下來,我們將探討Clarity智能合約背后的概念,以及使用Clarity編程的最佳實踐和安全檢查清單。
Clarity語言
Clarity的設計源自對智能合約工程中漏洞的深入分析,特別是對Solidity中觀察到的漏洞的研究。它的關鍵特性包括以下幾點:
-
可解釋語言:確保所見即所得。
-
可判定屬性:保證可預測結果和有限執(zhí)行。
-
安全措施:防范重入攻擊、溢出和下溢,這些對保持合約完整性至關重要。
-
自定義代幣支持:簡化開發(fā)流程,便于創(chuàng)建和管理代幣。
-
事務后置條件:通過驗證執(zhí)行后的狀態(tài)變化來增強安全性。
Clarity的獨特之處在于它從LISP語言中汲取靈感,LISP以其處理符號信息的簡潔性和強大功能而聞名。在Clarity中,一切都以“列表中的列表”或“表達式中的表達式”的形式表示。這種嵌套結構是Clarity的核心特點,使其語言具有高度的表達性和靈活性。函數(shù)定義、變量聲明和函數(shù)參數(shù)都被封裝在括號內(nèi),強調了語言的語法統(tǒng)一性。
以下是定義一個簡單Clarity函數(shù)的示例:
(define-data-var count int 0) //State Variable Declaration
?
(define-public (increase-number (number int)) //Function definition
(let
(
(current-count count)
)
(var-set count (+ 1 number))
(ok (var-get count))
)
)
?
(increase-number?1)?//Function?call
通過理解和利用這些嵌套表達式,開發(fā)者可以創(chuàng)建符合Stacks區(qū)塊鏈功能要求的安全高效的智能合約。這種方法不僅增強了可讀性,還確保合約的確定性和可預測性——這是保持去中心化應用安全性和可信度的關鍵。
Clarity與Solidity的區(qū)別
1. 解釋型與編譯型
-
Clarity:Clarity是一種解釋型語言,意味著源代碼直接發(fā)送到Stacks區(qū)塊鏈并在區(qū)塊鏈上執(zhí)行,而無須編譯成字節(jié)碼。
-
Solidity:Solidity代碼首先需要編譯成字節(jié)碼,然后將字節(jié)碼部署到區(qū)塊鏈上。EVM(以太坊虛擬機)會驗證這些字節(jié)碼,并調度相應的操作碼進行執(zhí)行。
2. 無動態(tài)調度
-
Clarity不支持動態(tài)調度,并且不是圖靈完備的,這意味著要執(zhí)行的函數(shù)是預先確定的。這個特性簡化了執(zhí)行模型,并有助于防止重入攻擊,使Clarity本身更加安全。
Clarity智能合約安全
安全一直是DeFi領域的頭等大事,尤其是在Stacks網(wǎng)絡扮演關鍵角色的比特幣DeFi生態(tài)系統(tǒng)中。截至2024年8月,Stacks生態(tài)系統(tǒng)中的總鎖倉價值(TVL)已達到約為8000萬美元,因此強大的安全措施變得尤為重要。
截至目前,Stacks已經(jīng)發(fā)生了多起安全事件,導致超過200萬美元的損失。這些事件突顯了對Clarity智能合約進行安全審計的必要性。
安全事件案例
2024年4月11日,Stacks網(wǎng)絡上的借貸協(xié)議Zest Protocol(比特幣L2)遭遇了一次重大漏洞攻擊,攻擊目標是協(xié)議的借款池(Borrow Pool),導致?lián)p失約322,000STX(折約100萬美元)。這次黑客攻擊事件迄今為止是比特幣DeFi生態(tài)系統(tǒng)中損失最嚴重的事件。
Zest Protocol的借款功能在合約pool-borrow.clar中定義,允許用戶通過提供抵押物來借入資產(chǎn)。該功能的參數(shù)包括池儲備、價格預言機、借入的資產(chǎn)、流動性提供者代幣、抵押資產(chǎn)列表、借款金額、費用計算器、利率模式和所有者:
(define-public (borrow
(pool-reserve principal)
(oracle <oracle-trait>)
(asset-to-borrow <ft>)
(lp <ft>)
(assets (list 100 { asset: <ft>, lp-token: <ft>, oracle: <oracle-trait> }))
(amount-to-be-borrowed uint)
(fee-calculator principal)
(interest-rate-mode uint)
?(owner?principal))
pool-borrow-v1-1.clar 中的借款函數(shù)
攻擊者利用了assets(資產(chǎn))參數(shù),該參數(shù)是一個最多包含100種資產(chǎn)的列表,用作抵押物。其漏洞源于合約未能成功驗證抵押資產(chǎn)的唯一性。更具體地說,合約在驗證資產(chǎn)存在性時未檢查重復項。這個疏忽使攻擊者能夠通過多次列出同一資產(chǎn)操縱抵押物的價值。
其他協(xié)議也遭遇過類似的安全漏洞攻擊。例如,2021年10月,一名攻擊者從Arkadiko Swap中竊取了約400,000枚STX和740,000枚USDA(總計約150萬美元)。攻擊者利用了Arkadiko Swap智能合約代碼中的一個漏洞,該漏洞未能在創(chuàng)建新的交易對時正確驗證LP代幣。該漏洞使攻擊者能夠零成本鑄造大量LP代幣,隨后從STX/USDA池中提取底層資產(chǎn),影響了該池總價值的25%的資產(chǎn)。
Clarity智能合約的最佳實踐與檢查清單
我們總結了廣泛研究后取得的經(jīng)驗,并為Clarity智能合約開發(fā)者編制了最佳實踐和檢查清單。以下是關鍵點:
1. 避免使用-panic函數(shù)
在Clarity智能合約中解包值時,避免使用unwrap-panic和unwrap-err-panic等函數(shù)。當這些函數(shù)解包失敗時,它們會以運行時錯誤中止調用,卻也沒有為與合約交互的應用程序提供有意義的信息。但如果選擇使用unwrap!和unwrap-err!,并附加明確的錯誤代碼。這個方法不僅能夠提高了錯誤的處理能力,還便于調試,并增強了智能合約的韌性。使用具體的錯誤代碼可以使調用應用程序更高效地處理錯誤,并根據(jù)上下文采取適當?shù)拇胧?/p>
2. 避免使用tx-sender進行驗證
在Clarity智能合約中濫用tx-sender變量進行身份驗證可能導致安全漏洞,類似于Solidity中SWC-115列出的漏洞。tx-sender變量標識調用鏈的發(fā)起者,類似于Solidity中的tx.origin。使用tx-sender進行驗證可能會引發(fā)網(wǎng)絡釣魚攻擊,攻擊者可以欺騙用戶并在易受攻擊的合約上執(zhí)行經(jīng)過身份驗證的操作。
tx-sender與contract-caller的對比
另一方面,contract-caller表示當前調用的發(fā)送者。通過避免使用tx-sender進行身份驗證,并采用更安全的替代方案如contract-caller,開發(fā)者可以降低網(wǎng)絡釣魚攻擊和跨站腳本攻擊的風險。
3. 模塊化合約設計以增強靈活性和未來可升級性
一旦智能合約部署到區(qū)塊鏈上,它就變得不可修改。與傳統(tǒng)應用開發(fā)相比,這種不可變性帶來了挑戰(zhàn),這意味著它不可以隨時進行更新和修復。在智能合約開發(fā)中,確保靈活性和未來可升級性需要采取戰(zhàn)略方法,因為一旦合約部署,就沒有直接的方法可以更新合約代碼。
解決這些挑戰(zhàn),開發(fā)者應考慮以下原則:
保持邏輯分離:避免創(chuàng)建一個處理所有功能的單一合約。而應該,通過將智能合約模塊化,將其拆分為更小、獨立的并且可以相互交互的組件。這種方法不僅使合約更易于管理和理解,還能在不影響整個系統(tǒng)的情況下替換或升級單個組件。
無狀態(tài)合約:該合約可以在區(qū)塊鏈上存儲最少量的數(shù)據(jù),從而減少了未來更改的復雜性和潛在影響。通過將狀態(tài)保留在合約外部并作為輸入?yún)?shù)傳遞,您可以更新邏輯而無須修改合約的狀態(tài)。
避免硬編碼變量:將值直接硬編碼到合約代碼中可能導致其缺乏靈活性,且妨礙未來的更新。相反,若將關鍵變量定義為可配置的參數(shù),這些參數(shù)則可以通過合約函數(shù)進行設置或調整。
4. 避免基于區(qū)塊高度(block-hight)的時間計算
在Clarity智能合約中,避免依賴block-height關鍵字進行時間敏感的計算。Stacks鏈的區(qū)塊時間可能會隨著網(wǎng)絡升級而變化,例如Nakamoto版本的發(fā)布將會減少區(qū)塊時間。而應該使用burn-block-height關鍵字,它反映了底層比特幣區(qū)塊鏈的當前區(qū)塊高度。比特幣的區(qū)塊時間更穩(wěn)定,不太可能發(fā)生變化,從而確保合約操作的更高準確性和可靠性。這種做法有助于保持一致性,并防止由于Stacks區(qū)塊時間波動而引發(fā)的潛在問題。
5. 正確處理函數(shù)中的返回值
在開發(fā)Clarity智能合約時,必須正確處理函數(shù)的布爾返回值,尤其是處理像verify-mined()等函數(shù)時。
該函數(shù)返回三種可能的值:(ok true)、(ok false)或錯誤。如果返回(ok true),表示交易已在指定區(qū)塊成功挖掘。如果返回(ok false),則表示交易未被挖掘,而錯誤則表示Merkle證明存在問題。
在使用try!檢查ok/error,但未能驗證響應類型中封裝的布爾值時,會出現(xiàn)一個常見問題。這種疏忽可能導致即使交易未在區(qū)塊中被挖掘(即返回值為(ok false)),函數(shù)也不會失敗。結果是驗證者可能會合作簽署一個未被挖掘的交易,使其未經(jīng)檢查就通過索引器。這一漏洞使得未經(jīng)驗證的未挖掘和潛在惡意的交易得以處理,從而導致安全漏洞和系統(tǒng)內(nèi)未經(jīng)授權的操作。
為了降低這種風險,請確保您的代碼檢查錯誤并明確驗證函數(shù)返回的布爾值。這種做法有助于維護合約的完整性和安全性,確保只處理有效的挖礦交易。
6. 在Clarity中正確使用contract-call?
在開發(fā)Clarity智能合約時,必須使用contract-call?函數(shù)正確實現(xiàn)合約間調用。該函數(shù)從被調用的智能合約返回一個Response(響應)類型的結果。
contract-call?的兩種類型:
-
靜態(tài)調用(Static Call):被調用的是一個已知的不變合約,在調用者合約部署時可在鏈上使用。第一個參數(shù)是被調用者的本金,然后是方法名稱及其參數(shù)。
(contract-call?
.registrar
register-name
????name-to-register)
-
動態(tài)調用(Dynamic Call):將被調用者作為參數(shù)傳遞,并將其類型化為特征引用(trait reference)。通過引用特征,代碼可以更加靈活和可重用。
(define-public (swap (token-a <can-transfer-tokens>)
(amount-a uint)
(owner-a principal)
(token-b <can-transfer-tokens>)
(amount-b uint)
(owner-b principal)))
(begin
(unwrap! (contract-call? token-a transfer-from? owner-a owner-b amount-a))
????????(unwrap!?(contract-call??token-b?transfer-from??owner-b?owner-a?amount-b))))
在處理合約調用時,請注意以下限制:
-
在靜態(tài)調用中,被調用者智能合約在創(chuàng)建時必須存在。
-
智能合約的調用圖中不得存在循環(huán)。這可以防止遞歸(和重入)。這種結構可以通過對調用圖的靜態(tài)分析檢測出來,并將被網(wǎng)絡拒絕。
-
contract-call?僅用于合約間調用。當調用者同時也是被調用者時,如果嘗試執(zhí)行,則會中止交易。
結語
CertiK已對Clarity智能合約安全進行了廣泛地研究。作為一家在智能合約安全領域擁有豐富經(jīng)驗的Web3.0頭部安全審計公司,CertiK已發(fā)現(xiàn)并報告了多個基于Clarity的漏洞賞金項目中的漏洞。如需了解我們之前的風險分析報告,可以訪問我們的博客。