譯者注:
智能合約代碼的審計耿眉,目前還不是技術(shù)社區(qū)內(nèi)經(jīng)常會討論的主題筐骇。今年3月6日,發(fā)表在博客網(wǎng)站【Schneier on Security】上的一篇博客(原文鏈接:【https://www.schneier.com/blog/archives/2018/03/security_vulner_13.html】茄螃,原文中附有一篇專業(yè)的研究報告【Finding The Greedy, Prodigal, and Suicidal Contracts at Scale】)指出吻氧,目前在以太坊中,有89%的智能合約代碼都或多或少存在安全漏洞/隱患,這顯然是一個非常驚人的調(diào)查結(jié)果,對社區(qū)而言也是一個巨大的風險因素识窿。而隨著智能合約的增多乃至未來可能的大規(guī)模發(fā)展,相信對各種合約代碼的審計也將會變成一個專門的匣沼、專業(yè)的領(lǐng)域窖认,并且是不能夠、也不應該被忽視的。
本文譯自Merunas Grincalaitis(一位以太坊開發(fā)者)于2017年9月18日發(fā)表在Medium上的文章桥言,原文鏈接:【https://medium.com/@merunasgrincalaitis/how-to-audit-a-smart-contract-most-dangerous-attacks-in-solidity-ae402a7e7868】枯夜。本文是作者結(jié)合自己所寫的一份智能合約代碼來講述智能合約審計要點的技術(shù)文章,并包含了對Solidity語言可能遇到的幾種危險攻擊的介紹。對于以太坊智能合約開發(fā)者而言有一定的參考和學習價值棘劣。
你有沒有考慮過如何審計一個智能合約來找出安全漏洞赊颠?
你可以自己學習远寸,或者你可以使用這份便利的一步步的指南來準確地知道在什么時候該做什么夜涕,并對合約進行審計丧诺。
我已經(jīng)研究過很多智能合約的審計,并且我已經(jīng)找到了從任何合約中提取所有重要信息的最常規(guī)步驟志于。
在本文中杖挣,你將會學到以下內(nèi)容:
- 生成對一個智能合約的完整審計報告所需的所有步驟。
- 作為以太坊智能合約審計人員需要了解的最重要的攻擊類型。
- 應該在合約中尋找什么抡医,和一些你不會在其他任何地方找到的有用的提示。
讓我們直接開始審計合約吧:
如何審計一個智能合約
為了教會你如何進行審計,我會審計我自己寫的一份合約衙猪。這樣扭屁,你可以看到可以由你自行完成的真實世界的審計鲸匿。
現(xiàn)在你也許會問:智能合約的審計到底是指什么?
智能合約審計就是仔細研究代碼的過程阻肩,在這里就是指在把Solidity合約部署到以太坊主網(wǎng)絡(luò)中并使用之前發(fā)現(xiàn)錯誤带欢、漏洞和風險运授;因為一旦發(fā)布,這些代碼將無法再被修改乔煞。這個定義僅僅是為了討論目的吁朦。
請注意,審計不是驗證代碼安全的法律文件渡贾。沒有人能100%確保代碼不會在未來發(fā)生錯誤或產(chǎn)生漏洞空骚。這僅僅是保證你的代碼已被專家校訂過刻诊,基本上是安全的。
討論可能的改進冲簿,主要是為了找出那些可能會危害到用戶的以太幣的風險和漏洞峦剔。
好了吝沫,現(xiàn)在我們來看看一份智能合約審計報告的結(jié)構(gòu):
- 免責聲明: 在這里你會說審計不是一個具有法律約束力的文件,它不保證任何東西栅受。這只是一個討論性質(zhì)的文件。
- 審計概覽和優(yōu)良特性: 快速查看將被審計的智能合約并找到良好的實踐痰腮。
- 對合約的攻擊: 在本節(jié)中棍丐,你將討論對合約的攻擊以及會產(chǎn)生的結(jié)果。這只是為了驗證它實際上是安全的踏烙。
- 合約中發(fā)現(xiàn)的嚴重漏洞: 可能嚴重損害合約完整性的關(guān)鍵問題荐捻。那些會允許攻擊者竊取以太幣的嚴重問題。
- 合約中發(fā)現(xiàn)的中等漏洞: 那些可能損害合約但危害有限的漏洞。比如一個允許人們修改隨機變量的錯誤瞧栗。
- 低嚴重性的漏洞: 這些問題并不會真正損害合約,并且可能已經(jīng)存在于合約的已部署版本中。
- 逐行評注: 在這部分中能耻,你將分析那些具有潛在改進可能的最重要的語句行赏枚。
- 審計總結(jié): 你對合約的看法和關(guān)于審計的最終結(jié)論亡驰。
將這份結(jié)構(gòu)說明保存在一個安全的地方,這是你安全地審計智能合約時需要做的全部內(nèi)容饿幅。它將確實地幫助你找到那些難以發(fā)現(xiàn)的漏洞凡辱。
我建議你從第7點“逐行評注”開始,因為當逐行分析合約時栗恩,你會發(fā)現(xiàn)最重要的問題透乾,你會看到缺少了什么,以及哪些地方應該修改或改進磕秤。
在后文中乳乌,我會給你展示一個免責聲明,你可以把它作為審計的第一步市咆。你可以從第1點開始看下去汉操,直到結(jié)束。
接下來蒙兰,我將向你展示使用這樣的結(jié)構(gòu)完成的審計結(jié)果磷瘤,這是我針對我自己寫的一個合約來做的。你還將在第3點中看到對于智能合約可能受到的最重要的攻擊的介紹癞己。
賭場合約審計
你可以在我的Github上看到審計的代碼:https://github.com/merlox/casino-ethereum/blob/master/contracts/Casino.sol
以下就是我的合約Casino.sol的審計報告:
序言
在這份智能合約審計報告中將包含以下內(nèi)容:
- 免責聲明
- 審計概覽和優(yōu)良特性
- 對合約的攻擊
- 合約中發(fā)現(xiàn)的嚴重漏洞
- 合約中發(fā)現(xiàn)的中等漏洞
- 低嚴重性的漏洞
- 逐行評注
- 審計總結(jié)
1膀斋、免責聲明
審計不會對代碼的實用性、代碼的安全性痹雅、商業(yè)模式的適用性仰担、商業(yè)模式的監(jiān)管制度或任何其他有關(guān)合約適用性的說明以及合約在無錯狀態(tài)的行為作出聲明或擔保。審計文檔僅用于討論目的绩社。
2摔蓝、概述
該項目只有一個包含142行Solidity代碼的文件 Casino.sol
。所有的函數(shù)和狀態(tài)變量的注釋都按照標準說明格式(即Ethereum Nature Specification Format愉耙,縮寫為natspec贮尉,它是以太坊社區(qū)官方的代碼注釋格式說明,原文參考github:【https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format】朴沿,譯者注)進行編寫猜谚,這可以幫助我們快速地理解程序是如何工作。
該項目使用了一個中心化的服務實現(xiàn)了Oraclize API赌渣,來在區(qū)塊鏈上生成真正的隨機數(shù)字魏铅。
譯者注:
Oraclize是一種為智能合約和區(qū)塊鏈應用提供數(shù)據(jù)的獨立服務,官網(wǎng):【http://www.oraclize.it】坚芜。因為類似于比特幣腳本或者以太坊智能合約這樣的區(qū)塊鏈應用無法直接獲取鏈外的數(shù)據(jù)览芳,所以就需要一種可以提供鏈外數(shù)據(jù)并可以與區(qū)塊鏈進行數(shù)據(jù)交互的服務。Oraclize可以提供類似于資產(chǎn)/財務應用程序中的價格信息鸿竖、可用于點對點保險的天氣信息或者對賭合約所需要的隨機數(shù)信息沧竟。
這里是指在這個項目的源代碼中引入了一個實現(xiàn)了Oraclize API的開源的Solidity代碼庫铸敏。
在區(qū)塊鏈上生成隨機數(shù)字是一個相當困難的課題,因為以太坊的核心價值之一就是可預測性悟泵,其目標是確保沒有未定義的值杈笔。
譯者注:
這里之所以說在區(qū)塊鏈上生成隨機數(shù)很困難,是因為糕非,無論采用何種算法桩撮,都需要使用時間戳作為生成隨機數(shù)的“種子”(因為時間戳是計算機領(lǐng)域內(nèi)唯一可以理論上保證“不會重復”的數(shù)值);而在智能合約中取得時間戳只能依賴某個節(jié)點(礦工)來做到峰弹。這就是說,合約中取得的時間戳是由運行其代碼的節(jié)點(礦工)的計算機本地時間決定的芜果;所以這個節(jié)點(礦工)的可信度就成了最大的問題鞠呈。理論上,這個本地時間是可以由惡意程序偽造的右钾,所以這種方法被認為是“不安全的”蚁吝。通行的做法是采用一個鏈外(off-chain)的第三方服務,比如這里使用的Oraclize舀射,來獲取隨機數(shù)窘茁。因為Oraclize是一種公共基礎(chǔ)服務,不會針對特定的合約“作假”脆烟,所以這可以認為是“相對安全的”山林。
因為使用Oraclize可以在鏈外生成隨機數(shù)字,所以使用它來產(chǎn)生可信的數(shù)字被認為是一種很好的做法邢羔。 它實現(xiàn)了修飾符和一個回調(diào)函數(shù)驼抹,用于驗證信息是否來自可信實體。
此智能合約的目的是參與隨機抽獎拜鹤,人們在1到9之間下注框冀。當有10個人下注時,獎金會自動分配給贏家敏簿。每個用戶都有一個最低下注金額明也。
每個玩家在每局游戲中只能下一次注,并且只有在參與者數(shù)量達到要求時才會產(chǎn)生贏家號碼惯裕。
優(yōu)秀特性
這個合約提供了一系列很好的功能性代碼:
- 使用Oraclize生成安全的隨機數(shù)并在回調(diào)中進行驗證温数。
- 修改器檢查游戲結(jié)束條件,阻止關(guān)鍵功能轻猖,直到獎勵得以分配帆吻。
- 做了較多的檢查來驗證bet函數(shù)的使用是合適的。
- 只有在下注數(shù)達到最大條件時才安全地生成贏家號碼咙边。
3猜煮、對合約進行的攻擊
為了檢查合約的安全性次员,我們測試了多種攻擊,以確保合約是安全的并遵循了最佳實踐王带。
重入攻擊(Reentrancy attack)
此攻擊通過遞歸地調(diào)用ERC20代幣中的
call.value()
方法來提取合約中的以太幣淑蔚,如果用戶在發(fā)送以太幣之后才更新發(fā)送者的balance
(即賬戶余額,譯者注)的話愕撰,攻擊就會生效刹衫。
當你調(diào)用一個函數(shù)將以太幣發(fā)送給合約時,你可以使用fallback函數(shù)再次執(zhí)行該函數(shù)搞挣,直到以太幣被從合約中提取出來带迟。
由于該合約使用了 transfer()
而不是 call.value()
,因此不存在重入攻擊的風險囱桨;因為transfer函數(shù)只允許使用2300 gas仓犬,這只夠用來產(chǎn)生事件日志數(shù)據(jù)并在失敗時拋出異常。這樣就無法遞歸調(diào)用發(fā)送者函數(shù)舍肠,從而避免了重入攻擊搀继。
因為transfer函數(shù)只會在每局游戲結(jié)束,向贏家分發(fā)獎勵時才會被調(diào)用一次翠语,所以重入式攻擊在這里不會導致任何問題叽躯。
請注意,調(diào)用此函數(shù)的條件是投注次數(shù)大于或等于10次肌括,但這個投注次數(shù)只有在 distributePrizes()
函數(shù)結(jié)束時才會被重置為0点骑,這是有風險的;因為理論上是可以在投注次數(shù)被清零之前調(diào)用該函數(shù)并執(zhí)行所有邏輯的谍夭。
所以我的建議是在函數(shù)開始時就更新條件畔况、將投注次數(shù)設(shè)置為0,以確保 distributePrizes()
在被超出預期地多次調(diào)用時不會產(chǎn)生實際效果慧库。
數(shù)值溢出(Over and under flows)
當一個
uint256
類型的變量值超出上限2**256(即2的256次方跷跪,譯者注)時會發(fā)生溢出。其結(jié)果是變量值變?yōu)?齐板,而不是更大吵瞻。
例如丈氓,如果你想把一個unit類型的變量賦予大于2**256的值是目,它會簡單地變?yōu)?俗孝,這是危險的沙峻。
另一方面,當你從0值中減去一個大于0的數(shù)字時涩嚣,則會發(fā)生下溢出(underflow)奋救。例如紧憾,如果你用0減去1,結(jié)果將是2**256签夭,而不是-1齐邦。
在處理以太幣的時候,這非常危險第租;然而在這個合約中并不存在減法操作措拇,所以也不會有下溢出的風險。
唯一可能發(fā)生溢出的情況是當你調(diào)用 bet()
向某個數(shù)字下注時慎宾, totalBet
變量的值會相應增加:
totalBet += msg.value;
有人可能會發(fā)送大量的以太幣而導致累加結(jié)果超過2**256丐吓,這會使totalBet變?yōu)?。這當然是不大可能發(fā)生的趟据,但風險是有的券犁。
所以我推薦使用類似于[OpenZeppelin’s SafeMath.sol]這樣的庫。它可以使你的計算處理更安全汹碱,免去發(fā)生溢出(overflow或者underflow)的風險族操。
可以將其導入來使用,對uint256類型激活它比被,然后使用 .mul()
、 .add()
泼舱、 .sub()
和 .div()
這些函數(shù)等缀。例如:
import './SafeMath.sol';
contract Casino {
using SafeMath for uint256;
function example(uint256 _value) {
uint number = msg.value.add(_value);
}
}
重放攻擊(Replay attack)
重放攻擊是指在像以太坊這樣的區(qū)塊鏈上發(fā)起一筆交易,而后在像以太坊經(jīng)典這樣的另一個鏈上重復這筆交易的攻擊娇昙。(就是說在主鏈上創(chuàng)建一個交易之后尺迂,在分岔鏈上重復同樣的交易。譯者注冒掌。)
以太幣會像普通的交易那樣噪裕,從一個鏈轉(zhuǎn)移到另一個鏈。
基于由Vitalik Buterin提出的EIP 155【https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md】股毫,從Geth的1.5.3版本和Parity的1.4.4版本開始膳音,已經(jīng)增加了對這個攻擊的防護。
譯者注:
EIP铃诬,即Ethereum Improvement Proposal(以太坊改進建議)祭陷,官方地址【https://github.com/ethereum/EIPs】是由以太坊社區(qū)所共同維護的以太坊平臺標準規(guī)范文檔,涵蓋了基礎(chǔ)協(xié)議規(guī)格說明趣席、客戶端API以及合約標準規(guī)范等等內(nèi)容兵志。
所以使用合約的用戶們需要自己升級客戶端程序來保證針對這個攻擊的安全性。
重排攻擊(Reordering attack)
這種攻擊是指礦工或其他方試圖通過將自己的信息插入列表(list)或映射(mapping)中來與智能合約參與者進行“競爭”宣肚,從而使攻擊者有機會將自己的信息存儲到合約中想罕。
當一個用戶使用 bet()
函數(shù)下注以后,因為實際的數(shù)據(jù)是存儲在鏈上的霉涨,所以任何人都可以簡單地通過調(diào)用公有狀態(tài)變量 playerBetsNumber
這個mapping看到所下注的數(shù)字按价。
這個mapping是用來表示每個人所選擇的數(shù)字的惭适,所以,結(jié)合交易數(shù)據(jù)俘枫,你就可以很容易地看到他們各自下注了多少以太幣腥沽。這可能會發(fā)生在 distributePrizes()
函數(shù)中,因為它是在隨機數(shù)生成處理的回調(diào)中被調(diào)用的鸠蚪。
因為這個函數(shù)起作用的條件在其結(jié)束之前才會被重置今阳,所以這就有了重排攻擊(reordering attack)的風險。
因此茅信,我的建議就像我之前談的那樣:在 distributePrizes()
函數(shù)開始時就重置下注人數(shù)來避免其產(chǎn)生非預期的行為盾舌。
短地址攻擊(Short address attack)
這種攻擊是由Golem團隊發(fā)現(xiàn)的針對ERC20代幣的攻擊:
- 一個用戶創(chuàng)建一個空錢包,這并不難蘸鲸,它只是一串字符妖谴,例如:【0xiofa8d97756as7df5sd8f75g8675ds8gsdg0】
- 然后他使用把地址中的最后一個0去掉的地址來購買代幣:也就是用【0xiofa8d97756as7df5sd8f75g8675ds8gsdg】作為收款地址來購買1000代幣。
- 如果代幣合約中有足夠的余額酌摇,且購買代幣的函數(shù)沒有檢查發(fā)送者地址的長度膝舅,以太坊虛擬機會在交易數(shù)據(jù)中補0,直到數(shù)據(jù)包長度滿足要求
- 以太坊虛擬機會為每個1000代幣的購買返回256000代幣窑多。這是一個虛擬機的bug仍稀,并且仍未被修復。所以如果你是一個代幣合約的開發(fā)者埂息,請確保對地址長度進行了檢查技潘。
但我們這個合約因為并不是ERC20代幣合約,所以這種攻擊并不能適用千康。
你可以參考這篇文章【http://vessenes.com/the-erc20-short-address-attack-explained/】來獲得更多關(guān)于這種攻擊的信息享幽。
4、合約中發(fā)現(xiàn)的嚴重漏洞
審計中并未發(fā)現(xiàn)嚴重漏洞拾弃。
5值桩、合約中發(fā)現(xiàn)的中等漏洞
checkPlayerExists()
應該是一個常態(tài)(constant)函數(shù),然而實際上它并不是豪椿。因此這增加了調(diào)用這個函數(shù)的gas消耗颠毙,當有大量對此函數(shù)的調(diào)用發(fā)生時會產(chǎn)生很大的問題。
應該把它改為常態(tài)函數(shù)來避免昂貴的消耗gas的執(zhí)行砂碉。
譯者注:
Solidity語言中的常態(tài)(constant)函數(shù)蛀蜜,指的是在運行時不會改變合約狀態(tài)的函數(shù),也就是不會改變合約級別的狀態(tài)變量(state variable)的值的函數(shù)增蹭。因為狀態(tài)變量的更改是會保存到鏈上的滴某,所以對狀態(tài)變量的更改都要消耗gas(來支付給礦工),這是非常昂貴的。在本例中霎奢,因為checkPlayerExists()
函數(shù)中訪問了狀態(tài)變量playerBetsNumber
來判斷是否已經(jīng)有人下過注了户誓,雖然這是個合約級別的變量,但這個函數(shù)并沒有改變它的值幕侠,所以這個函數(shù)應該聲明為constant
以節(jié)省其對gas的消耗帝美。
6、低嚴重性的漏洞
- 你在
__callback()
函數(shù)和pay()
函數(shù)的開始位置使用了assert()
而不是require()
晤硕。
assert()
和 require()
大體上是相同的悼潭,但assert函數(shù)一般用來在更改合約狀態(tài)之后做校驗,而require通常在函數(shù)的開頭用做輸入?yún)?shù)的檢查舞箍。
- 你定義了一個合約級別的變量players舰褪,但沒有任何地方使用它。如果你不打算使用它疏橄,就把它刪除占拍。
7、逐行評注
- 第1行:你在版本雜注(pragma version)中使用了脫字符號(^)來指定使用高于
0.4.11
版本的編譯器捎迫。
這不是一個好實踐晃酒。因為大版本的變化可能會使你的代碼不穩(wěn)定,所以我推薦使用一個固定的版本窄绒,比如‘0.4.11’贝次。
- 第14行:你定義了一個
uint
類型的變量totalBet
,這個變量名是不合適的颗祝,因為它保存的是所有下注的合計值。我推薦使用totalBets
作為變量名恼布,而不是totalBet
螺戳。 - 第24行:你用大寫字母定義了一個常量(constant variable),這是一個好實踐折汞,可以使人知道這是個固定的倔幼、不可變的變量。
- 第30行:就像我之前提到的爽待,你定義了一個未使用的數(shù)組
player
损同。如果你不打算使用它,就把它刪除鸟款。 - 第60行:函數(shù)
checkPlayerExists()
應該被聲明為constant
膏燃。因為它并沒有更改合約狀態(tài),把它聲明為constant
可以節(jié)省下每次運行它所要消耗的gas何什。
即使函數(shù)默認是public類型组哩,但顯式地給函數(shù)指定類型仍然是一個好實踐,它可以避免任何困惑。這里可以在這個函數(shù)聲明的末尾確切地加上public聲明伶贰。
- 第61行:你沒有檢查輸入?yún)?shù)
player
被正常傳入且格式正確蛛砰。請確保在函數(shù)開頭使用require(player != address(0));
語句來檢查傳入地址是否為0。為了以防萬一黍衙,最好也要檢查地址的長度是否符合要求來應對短地址攻擊泥畅。 - 第69行:同樣建議給
bet()
函數(shù)加上可見度(visibilty)關(guān)鍵字public
來避免任何困惑,以明確應該如何使用此函數(shù)琅翻。 - 第72行:使用
require()
來檢查函數(shù)輸入?yún)?shù)位仁,而不是assert()
。
同樣的望迎,在函數(shù)開頭障癌,一般更經(jīng)常使用 require()
。請把所有在函數(shù)開頭使用的 assert()
改為 require()
辩尊。
- 第90行:你使用了一個對
msg.value
的簡單合計涛浙,在value值很大時這會導致溢出。所以我建議你每次對數(shù)值進行運算時都要檢查是否會溢出摄欲。 - 第98行:
generateNumberWinner()
應該是internal
函數(shù)轿亮,因為你肯定不希望任何人都可以從合約以外執(zhí)行它。
譯者注:
在Solidity語言中胸墙,internal
關(guān)鍵字的效果我注,與面向?qū)ο笳Z言比如C++、Java中的protected類型基本一致迟隅,此關(guān)鍵字限定的函數(shù)或者狀態(tài)變量但骨,僅在當前合約及當前合約的子合約(contacts deriving from this contract)中可以訪問。private
關(guān)鍵字則與其他語言中的此關(guān)鍵字相同智袭,由其限定的函數(shù)或者狀態(tài)變量僅在當前合約中可以訪問奔缠。
- 第103行:你把
oraclize_newRandomDSQuery()
函數(shù)的結(jié)果保存在了一個bytes32類型的變量中。調(diào)用callback函數(shù)并不需要這么做吼野,而且你也沒有在其他地方再用到這個變量校哎,所以我建議不要用變量保存這個函數(shù)的返回值。 - 第110行:
__callback()
函數(shù)應該聲明為external
瞳步,因為你只希望它從外部被調(diào)用闷哆。
譯者注:
在Solidity中,函數(shù)關(guān)鍵字public
和external
在gas的消耗上是有區(qū)別的单起。因為public
的函數(shù)既可以在合約外調(diào)用抱怔,又可以在合約內(nèi)調(diào)用,所以虛擬機會在運行時為其分配內(nèi)存嘀倒,拷貝其所用到的所有變量野蝇。而external
的函數(shù)只允許從合約外部進行調(diào)用讼稚,其調(diào)用會直接從calldata(即函數(shù)調(diào)用的二進制字節(jié)碼數(shù)據(jù))中獲取參數(shù),虛擬機不會為其分配內(nèi)存并拷貝變量值绕沈,所以其gas消耗比public
的函數(shù)要低很多锐想。
- 第117行:這里的
assert()
應該使用require()
,就像我先前解釋的那樣乍狐。 - 第119行:你使用了
sha3()
函數(shù)赠摇,但這并不是一個好的實踐。實際的算法使用的是keccak256浅蚪,并不是sha3藕帜。所以我建議這里更明確地改為使用keccak256()
。 - 第125行:
distributePrizes()
函數(shù)應該被聲明為internal
惜傲。
譯者注:
此函數(shù)與第98行的generateNumberWinner()
函數(shù)一樣洽故,聲明為internal
或者private
都是可以的。區(qū)別僅在于你希不希望子合約中可以使用它們盗誊。
- 第129行:盡管你在這里用了一個變長數(shù)組的大小來控制循環(huán)次數(shù)时甚,但其實也沒有多糟糕,因為獲勝者的數(shù)量被限制為小于100哈踱。
8荒适、審計總結(jié)
總體上講,這個合約的代碼有很好的注釋开镣,清晰地解釋了每個函數(shù)的目的刀诬。
下注和分發(fā)獎勵的機制非常簡單,不會帶來什么大問題邪财。
我最終的建議是需要更加注意函數(shù)的可見性聲明陕壹,因為這對于明確函數(shù)應該供誰來執(zhí)行的問題非常重要。然后就是需要在編碼中考慮 assert
树埠、 require
和 keccak
的使用上的最佳實踐糠馆。
這是一個安全的合約,可以在其運行期間保證資金安全弥奸。
結(jié)論
以上就是我使用我在開篇介紹過的結(jié)構(gòu)所進行的審計榨惠。希望你確實學到了一些東西并且可以對其他智能合約進行安全審計了奋早。
請繼續(xù)學習合約安全知識盛霎、編碼最佳實踐以及其他實用知識,并努力提高耽装。