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