1. 引子
2018年4月24日,又一件突發(fā)性事件引爆了幣圈!剛剛發(fā)行了才兩個月的“美鏈 Beauty Chain” (簡稱BEC)在受到黑客的攻擊的影響下直接歸零了!黑客使用的是以太坊ERC-20智能合約BatchOverFlow數(shù)據(jù)溢出的漏洞缴挖,向兩個地址轉(zhuǎn)出了數(shù)量巨大的BEC代幣懊缺!黑客先是試探性地往Okex中轉(zhuǎn)100萬的BEC欧宜,發(fā)現(xiàn)成功轉(zhuǎn)入賣出后燎悍,又分兩次轉(zhuǎn)入了一千萬的BEC嗡呼。發(fā)現(xiàn)兩次都成功惠啄,黑客變得更加大膽壁榕,便轉(zhuǎn)入了一億枚BEC矛紫。但這1億枚 BEC轉(zhuǎn)入后,OKEx已經(jīng)發(fā)現(xiàn)問題并停止了BEC的交易牌里。按照轉(zhuǎn)入記錄颊咬,預計黑客已經(jīng)賣出了最少 1100萬枚BEC,折合昨日售價約一千八百多萬人民幣牡辽。
BEC官方團隊已經(jīng)暫停了一切交易和轉(zhuǎn)賬麸澜。BEC上線的交易所有兩家,Okex和LBank奏黑。暫停交易之后炊邦,官方團隊將對Okex交易所的交易回滾到黑客轉(zhuǎn)賬之前。
2. 智能合約安全攻擊實際案例分析
下面我們就來分析分析發(fā)生在區(qū)塊鏈世界的幾個血淋淋的真實案例熟史。
2.1 美鏈BEC遭遇黑客攻擊
上例中馁害,黑客使用的是一個叫“batchTransfer“攻擊方法。因為BEC的開發(fā)人員在寫代碼時犯了一個錯誤蹂匹,使得出現(xiàn)一個簡單的溢出漏洞碘菜。就這么一個簡單的漏洞,讓黑客有機可乘限寞,讓BEC的60億市值頃刻間歸零忍啸,讓手中擁有BEC的韭菜們血本無歸!
碼農(nóng)沒有想到履植,自己的手藝活現(xiàn)在是那么值錢计雌!一行代碼就職60億元,這是什么樣的價值體現(xiàn)呢静尼?
2.1.1 問題分析
美鏈發(fā)生問題的智能合約地址(點擊查看) ,完整代碼我們就不引用了白粉,直接看問題代碼。
從上述源碼中我們摘出batchTransfer這個函數(shù)鼠渺,如下:
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value; // <==== 敗在這個乘法運算
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount); // 下面知道用庫函數(shù),卻粗心在上一步?jīng)]用
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
這個函數(shù)的作用就是批量轉(zhuǎn)賬眷细,其中我們可以看到有這么一句“ uint256 amount = uint256(cnt) * _value; ”拦盹,問題就出在這里,復盤一下:
1溪椎、給·_receivers參數(shù)傳遞兩個或更多元素的數(shù)組普舆,即_receivers.length>=2了恬口,這里我們假定_receivers.length=2。
2沼侣、給_value傳一個什么樣的值呢祖能,要讓_value * 2 = 2 ** 256 + 1剛好溢出,得到這個10 進制數(shù)57896044618658097711785492504343953926634992332820282019728792003956564819968蛾洛,但筆者在Remix測試了一下养铸,直接傳這個數(shù)是不行的,要轉(zhuǎn)換成16進制轧膘,即0x8000000000000000000000000000000000000000000000000000000000000000(8后面63個0)钞螟,可以用web3.fromDecimal('數(shù)字')做一下轉(zhuǎn)換。
兩個參數(shù)的值如下:
_value = 0x8000000000000000000000000000000000000000000000000000000000000000
_receivers.length=2
這樣就超出了amount的值范圍谎碍,從而導致溢出歸0鳞滨,即amount=0,進而繞過了異常檢查語句:
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
3蟆淀、再往下看for循環(huán)的加法運算拯啦,突破了前面的檢查,到達for循環(huán)這里就如進入了無人看管的銀庫熔任,想往自己賬號上加多少錢就加多少提岔。我們來一起見證一下這瘋狂的一幕,點擊查看BEC加錢現(xiàn)場笋敞。
2.1.2 解決方案
數(shù)值溢出是最容易出問題的智能合約常見錯誤碱蒙。為了代碼安全,一定要使用“檢查-生效-交互”(Checks-Effects-Interactions)模式來編寫代碼夯巷。
以下引用了哥哥的修改代碼赛惩,即可防止64億的價值損失。
方案說明:
采用SafeMath的庫函數(shù)趁餐,改為" uint256 amount = _value.mul(uint256(cnt)); "喷兼,如果發(fā)生溢出,會產(chǎn)生assert中斷后雷,異常退出季惯,不會執(zhí)行非法轉(zhuǎn)賬。
對程序員的忠告:
智能合約中算術(shù)一律采用SafeMath的庫函數(shù)臀突,不以一行而不為勉抓!
代碼如下:
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = _value.mul(uint256(cnt)); // <===修改成這樣即可
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
2.2 EDU 智能合約出現(xiàn)重大漏洞,可轉(zhuǎn)走任意賬戶的 EDU Token
2018年5月24日凌晨01:33 時分候学,火幣 Pro 發(fā)布公告稱 EduCoin(EDU) 官方智能合約升級藕筋,暫停提幣和交易業(yè)務(wù)。
2.2.1 問題分析
據(jù)慢霧區(qū)最新消息梳码,EDU 智能合約出現(xiàn)重大漏洞隐圾,可轉(zhuǎn)走任意賬戶的 EDU Token伍掀。目前已經(jīng)發(fā)現(xiàn)有黑客的大量洗劫行為,攻擊者不需要私鑰即可轉(zhuǎn)走你賬戶里所有的 EDU暇藏,并且由于合約沒有 Pause 設(shè)計蜜笤,導致無法止損。
1盐碱,EDU的老智能合約地址把兔,可點擊查看代碼
2,EDU修正后的智能合約地址甸各,可點擊查看合約代碼
以下為存在問題的智能合約代碼垛贤,輝哥增加備注說明:
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
/// same as above
require(_to != 0x0);
require(balances[_from] >= _value);
require(balances[_to] + _value > balances[_to]);
uint previousBalances = balances[_from] + balances[_to];
balances[_from] -= _value;
balances[_to] += _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);/*智能合約事件LOG記錄,無實際代幣操作*/
assert(balances[_from] + balances[_to] == previousBalances);
return true;
}
在 EDU 智能合約 transferFrom 函數(shù)中趣倾,未校驗 allowed[_from][msg.sender] >= _value 并且函數(shù)內(nèi) allowed[_from][msg.sender] -= _value也沒有使用 SafeMath聘惦,導致無法拋出異常并回滾交易。
通過這個漏洞儒恋,攻擊者通過調(diào)用智能合約中的allowance函數(shù)善绎,即可擴大賬戶余額,不需要私鑰即可轉(zhuǎn)走指定賬戶里所有的 EDU诫尽,并且由于合約沒有 Pause 設(shè)計禀酱,導致無法止損。
2.2.2 解決方案
EDU團隊請知停止交易后牧嫉,又發(fā)布了一個新的智能合約剂跟,并把原來的賬戶分配遷移到這個代幣合約來。
說明下酣藻,上鏈了曹洽,智能通過中心化的交易所行為讓之前這個漏洞代幣的交易停止,幣值歸零了辽剧。
新的智能合約中送淆,問題代碼修改成如下:
乖乖的采用SafeMath的庫函數(shù),就不會出問題了怕轿。
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
Transfer(_from, _to, _value);
return true;
}
2.3 價值兩百萬的以太坊錢包陷阱
2018年5月份網(wǎng)絡(luò)某些論壇和交流群里偷崩,出現(xiàn)了一個以太坊錢包地址。
帳號私鑰: 668a369e87c01da5bfca9851e6ee86d760e17ee7912d77b7dffe8e0cdf63bcb5
地址: 0xA8015DF1F65E1f53D491dC1ED35013031AD25034
里面有交易價值的代幣撞羽。有些人在躍躍欲試阐斜,要不要轉(zhuǎn)到自己的錢包地址來呢?2.3.1 問題分析
錢包中有價值 320382 美金的 ICX放吩。我們立即將私鑰導入錢包看看智听,價值7萬多的ICX代幣哦:
我們來轉(zhuǎn)出點 ICX 試試:
轉(zhuǎn)帳時會提示你手續(xù)費余額不足,意料之中的渡紫,那往里面充點 ETH 吧到推!等我們充完錢之后會發(fā)現(xiàn)里面的 ETH 會立馬被轉(zhuǎn)走,已經(jīng)有小伙伴做過實驗了惕澎,這里就不測試了莉测。
第一點不難解釋,所有操作都是通過腳本完成唧喉,通過監(jiān)測以太坊主網(wǎng)新生成區(qū)塊中是否有該地址的轉(zhuǎn)入交易捣卤,若有則立即轉(zhuǎn)出錢包中的 ETH;
第二點八孝,所有的轉(zhuǎn)出金額都非常小董朝,為什么要這樣做?通過觀察交易列表干跛,我們會發(fā)現(xiàn)轉(zhuǎn)出地址不止一個子姜,就是說有人也想從這里面分一杯羹,把小白轉(zhuǎn)過來的 ETH 搶走楼入,所以把手續(xù)費設(shè)置得很高哥捕,有的到了總金額的 99% 左右,在以太坊系統(tǒng)中嘉熊,礦工會優(yōu)先打包手續(xù)高的交易遥赚。
OK!到這里為止可能有人問阐肤,我們設(shè)置更高的手續(xù)費也用腳本去轉(zhuǎn)走里面的 ICX Token 不就行了嗎凫佛?現(xiàn)在我們來看一看 ICX 的智能合約代碼。點擊查看合約代碼孕惜。
來看看合約中的兩個轉(zhuǎn)帳函數(shù):
function transfer( address to, uint value)
isTokenTransfer
checkLock
returns (bool success) {
require( _balances[msg.sender] >= value );
_balances[msg.sender] = _balances[msg.sender].sub(value);
_balances[to] = _balances[to].add(value);
Transfer( msg.sender, to, value );
return true;
}
function transferFrom( address from, address to, uint value)
isTokenTransfer
checkLock
returns (bool success) {
// if you don't have enough balance, throw
require( _balances[from] >= value );
// if you don't have approval, throw
require( _approvals[from][msg.sender] >= value );
// transfer and return true
_approvals[from][msg.sender] = _approvals[from][msg.sender].sub(value);
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
Transfer( from, to, value );
return true;
}
發(fā)現(xiàn)這兩個函數(shù)都帶了 “checkLock” modifier愧薛,我們再來看看“checkLock”有什么用
// This modifier check whether the contract should be in a locked
// or unlocked state, then acts and updates accordingly if
// necessary
modifier checkLock {
if (lockaddress[msg.sender]) {
throw;
}
_;
}
這幾行代碼就是判斷該地址是否處于 lockaddrss 中,如果在 lockaddrss 中則 throw诊赊,不執(zhí)行轉(zhuǎn)賬操作厚满,我們來看看這個釣魚錢包地址是否在 lockaddress 中(因為 lockaddress 為 public 類型,所以可以直接查詢)
毫無疑問碧磅,我們先前的錢包地址處于鎖定狀態(tài)碘箍,所以釣魚的人先將錢包中的 ICX Token 鎖定,再故意流出私鑰鲸郊,以大量的 ERC20 Token 去誘惑大家往錢包地址中轉(zhuǎn)手續(xù)費丰榴,再立馬轉(zhuǎn)走 ETH。
2.3.2 解決方案
收起貪婪的心秆撮,你貪人家的小利四濒,人家要你的本金!
2.3.3 延伸閱讀
"2.3.1"章節(jié)講了查看該錢包地址的狀態(tài)是否是lockaddrss的截圖,但是沒有講清楚如何查詢的方法盗蟆,本節(jié)以此為案例戈二,做一下延伸閱讀。
1. 登錄myetherwallet合同查看頁面
登錄myetherwallet合同查看頁面,輸入合約地址'0xb5a5f22694352c15b00323844ad545abb2b11028'喳资,在右邊合同列表中沒有找到對應(yīng)的ICX合約觉吭。
2. 更換方法,采用ABI的方式查找合約
點擊查看ICX合約代碼仆邓, 鲜滩,獲取其ABI信息。
登錄myetherwallet合同查看頁面, 右上角的網(wǎng)絡(luò)選擇為“Network ETH(etherscan.io)”【很重要】
然后輸入復制的ABI信息节值,然后找到lockaddress映射函數(shù)徙硅,輸入地址'0xA8015DF1F65E1f53D491dC1ED35013031AD25034',點擊READ按鈕搞疗,得到結(jié)果為TRUE嗓蘑。
地址鎖住了。取幣贴汪,逗你玩脐往!
4. 智能合約常見安全問題
4.1 私有信息和隨機性
在智能合約中你所用的一切都是公開可見的,即便是局部變量和被標記成 private
的狀態(tài)變量也是如此扳埂。
如果不想讓礦工作弊的話业簿,在智能合約中使用隨機數(shù)會很棘手 (譯者注:在智能合約中使用隨機數(shù)很難保證節(jié)點不作弊, 這是因為智能合約中的隨機數(shù)一般要依賴計算節(jié)點的本地時間得到阳懂, 而本地時間是可以被惡意節(jié)點偽造的梅尤,因此這種方法并不安全。 通行的做法是采用鏈外的第三方服務(wù)岩调,比如 Oraclize 來獲取隨機數(shù))巷燥。
4.2 重入
任何從合約 A 到合約 B 的交互以及任何從合約 A 到合約 B 的以太幣 的轉(zhuǎn)移,都會將控制權(quán)交給合約 B号枕。 這使得合約 B 能夠在交互結(jié)束前回調(diào) A 中的代碼缰揪。 舉個例子,下面的代碼中有一個 bug(這只是一個代碼段葱淳,不是完整的合約):
pragma solidity ^0.4.0;
// 不要使用這個合約钝腺,其中包含一個 bug。
contract Fund {
/// 合約中 |ether| 分成的映射赞厕。
mapping(address => uint) shares;
/// 提取你的分成艳狐。
function withdraw() public {
if (msg.sender.send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
這里的問題不是很嚴重,因為有限的 gas 也作為 send
的一部分皿桑,但仍然暴露了一個缺陷: 以太幣Ether的傳輸過程中總是可以包含代碼執(zhí)行毫目,所以接收者可以是一個回調(diào)進入 withdraw
的合約蔬啡。 這就會使其多次得到退款,從而將合約中的全部 以太幣Ether 提取镀虐。 特別地箱蟆,下面的合約將允許一個攻擊者多次得到退款,因為它使用了 call
粉私,默認發(fā)送所有剩余的 gas顽腾。
pragma solidity ^0.4.0;
// 不要使用這個合約近零,其中包含一個 bug诺核。
contract Fund {
/// 合約中 |ether| 分成的映射。
mapping(address => uint) shares;
/// 提取你的分成久信。
function withdraw() public {
if (msg.sender.call.value(shares[msg.sender])())
shares[msg.sender] = 0;
}
}
為了避免重入窖杀,你可以使用下面撰寫的“檢查-生效-交互”(Checks-Effects-Interactions)模式:
pragma solidity ^0.4.11;
contract Fund {
/// 合約中 |ether| 分成的映射。
mapping(address => uint) shares;
/// 提取你的分成裙士。
function withdraw() public {
var share = shares[msg.sender];
shares[msg.sender] = 0;
msg.sender.transfer(share);
}
}
請注意重入不僅是 以太幣ETHer 傳輸?shù)钠渲幸粋€影響入客,還包括任何對另一個合約的函數(shù)調(diào)用。 更進一步說腿椎,你也不得不考慮多合約的情況桌硫。 一個被調(diào)用的合約可以修改你所依賴的另一個合約的狀態(tài)。
4.3 gas 限制和循環(huán)
必須謹慎使用沒有固定迭代次數(shù)的循環(huán)啃炸,例如依賴于存儲值的循環(huán): 由于區(qū)塊 gas 有限铆隘,交易只能消耗一定數(shù)量的 gas。 無論是明確指出的還是正常運行過程中的南用,循環(huán)中的數(shù)次迭代操作所消耗的 gas 都有可能超出區(qū)塊的 gas 限制膀钠,從而導致整個合約在某個時刻驟然停止。 這可能不適用于只被用來從區(qū)塊鏈中讀取數(shù)據(jù)的 constant
函數(shù)裹虫。 盡管如此肿嘲,這些函數(shù)仍然可能會被其它合約當作 鏈上on-chain操作的一部分來調(diào)用,并使那些操作驟然停止筑公。 請在合約代碼的說明文檔中明確說明這些情況雳窟。
4.4 發(fā)送和接收以太幣Ether
- 目前無論是合約還是“外部賬戶”都不能阻止有人給它們發(fā)送 以太幣Ether。 合約可以對一個正常的轉(zhuǎn)賬做出反應(yīng)并拒絕它匣屡,但還有些方法可以不通過創(chuàng)建消息來發(fā)送 以太幣Ether封救。 其中一種方法就是單純地向合約地址“挖礦”,另一種方法就是使用
selfdestruct(x)
耸采。 - 如果一個合約收到了 以太幣Ether(且沒有函數(shù)被調(diào)用)兴泥,就會執(zhí)行 fallback 函數(shù)。 如果沒有 fallback 函數(shù)虾宇,那么 以太幣Ether 會被拒收(同時會拋出異常)搓彻。 在 fallback 函數(shù)執(zhí)行過程中,合約只能依靠此時可用的“gas 津貼”(2300 gas)來執(zhí)行。 這筆津貼并不足以用來完成任何方式的 以太幣Ether 訪問旭贬。 為了確保你的合約可以通過這種方式收到 以太幣Ether怔接,請你核對 fallback 函數(shù)所需的 gas 數(shù)量 (在 Remix 的“詳細”章節(jié)會舉例說明)。
- 有一種方法可以通過使用
addr.call.value(x)()
向接收合約發(fā)送更多的 gas稀轨。 這本質(zhì)上跟addr.transfer(x)
是一樣的扼脐, 只不過前者發(fā)送所有剩余的 gas,并且使得接收者有能力執(zhí)行更加昂貴的操作 (它只會返回一個錯誤代碼奋刽,而且也不會自動傳播這個錯誤)瓦侮。 這可能包括回調(diào)發(fā)送合約或者你想不到的其它狀態(tài)改變的情況。 因此這種方法無論是給誠實用戶還是惡意行為者都提供了極大的靈活性佣谐。 - 如果你想要使用
address.transfer
發(fā)送 以太幣Ether 肚吏,你需要注意以下幾個細節(jié):- 如果接收者是一個合約,它會執(zhí)行自己的 fallback 函數(shù)狭魂,從而可以回調(diào)發(fā)送 以太幣Ether的合約罚攀。
- 如果調(diào)用的深度超過 1024,發(fā)送 以太幣Ether也會失敗雌澄。由于調(diào)用者對調(diào)用深度有完全的控制權(quán)斋泄,他們可以強制使這次發(fā)送失敗镐牺; 請考慮這種可能性炫掐,或者使用
send
并且確保每次都核對它的返回值。 更好的方法是使用一種接收者可以取回 以太幣Ether 的方式編寫你的合約任柜。 - 發(fā)送 以太幣Ether也可能因為接收方合約的執(zhí)行所需的 gas 多于分配的 gas 數(shù)量而失敗 (確切地說卒废,是使用了
require
,assert
宙地,revert
摔认,throw
或者因為這個操作過于昂貴) - “gas 不夠用了”。 如果你使用transfer
或者send
的同時帶有返回值檢查宅粥,這就為接收者提供了在發(fā)送合約中阻斷進程的方法参袱。 再次說明,最佳實踐是使用 “取回”模式而不是“發(fā)送”模式秽梅。
4.5 調(diào)用棧深度
外部函數(shù)調(diào)用隨時會失敗抹蚀,因為它們超過了調(diào)用棧的上限 1024。 在這種情況下企垦,Solidity 會拋出一個異常环壤。 惡意行為者也許能夠在與你的合約交互之前強制將調(diào)用棧設(shè)置成一個比較高的值。
請注意钞诡,使用 .send()
時如果超出調(diào)用棧 并不會 拋出異常郑现,而是會返回 false
湃崩。 低級的函數(shù)比如 .call()
,.callcode()
和 .delegatecall()
也都是這樣的接箫。
4.6 tx.origin
永遠不要使用 tx.origin 做身份認證攒读。假設(shè)你有一個如下的錢包合約:
// 不要使用這個合約,其中包含一個 bug辛友。
contract TxUserWallet {
address owner;
function TxUserWallet() public {
owner = msg.sender;
}
function transferTo(address dest, uint amount) public {
require(tx.origin == owner);
dest.transfer(amount);
}
}
現(xiàn)在有人欺騙你薄扁,將 以太幣Ether發(fā)送到了這個惡意錢包的地址:
pragma solidity ^0.4.11;
interface TxUserWallet {
function transferTo(address dest, uint amount) public;
}
contract TxAttackWallet {
address owner;
function TxAttackWallet() public {
owner = msg.sender;
}
function() public {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
如果你的錢包通過核查 msg.sender
來驗證發(fā)送方身份,你就會得到惡意錢包的地址废累,而不是所有者的地址邓梅。 但是通過核查 tx.origin
,得到的就會是啟動交易的原始地址九默,它仍然會是所有者的地址震放。 惡意錢包會立即將你的資金抽出。
4.7 細枝末節(jié)
- 在
for (var i = 0; i < arrayName.length; i++) { ... }
中驼修,i
的類型會變?yōu)?uint8
, 因為這是保存0
值所需的最小類型诈铛。如果數(shù)組超過 255 個元素乙各,則循環(huán)不會終止。 -
constant
關(guān)鍵字并不是編譯器強制的幢竹,另外也不是以太坊虛擬機Ethereum Virtual Machine(EVM)強制的耳峦, 因此一個“聲明”為constant
的函數(shù)可能仍然會發(fā)生狀態(tài)發(fā)生變化。 - 不占用完整 32 字節(jié)的類型可能包含“臟高位”焕毫。這在當你訪問
msg.data
的時候尤為重要 —— 它帶來了延展性風險: 你既可以用原始字節(jié)0xff000001
也可以用0x00000001
作為參數(shù)來調(diào)用函數(shù)f(uint8 x)
以構(gòu)造交易蹲坷。 這兩個參數(shù)都會被正常提供給合約,并且x
的值看起來都像是數(shù)字1
邑飒, 但msg.data
會不一樣循签,所以如果你無論怎么使用keccak256(msg.data)
,你都會得到不同的結(jié)果疙咸。
5 推薦做法
5.1 限定以太幣Ether的數(shù)量
限定 存儲storage在一個智能合約中 以太幣Ether(或者其它通證)的數(shù)量县匠。 如果你的源代碼、編譯器或者平臺出現(xiàn)了 bug撒轮,可能會導致這些資產(chǎn)丟失乞旦。 如果你想控制你的損失,就要限定 以太幣Ether的數(shù)量题山。
5.2 保持合約簡練且模塊化
保持你的合約短小精煉且易于理解兰粉。 找出無關(guān)于其它合約或庫的功能。 有關(guān)源碼質(zhì)量可以采用的一般建議: 限制局部變量的數(shù)量以及函數(shù)的長度等等顶瞳。 將實現(xiàn)的函數(shù)文檔化玖姑,這樣別人看到代碼的時候就可以理解你的意圖崖蜜,并判斷代碼是否按照正確的意圖實現(xiàn)。
d
5.3 使用“檢查-生效-交互”(Checks-Effects-Interactions)模式
大多數(shù)函數(shù)會首先做一些檢查工作(例如誰調(diào)用了函數(shù)客峭,參數(shù)是否在取值范圍之內(nèi)豫领,它們是否發(fā)送了足夠的以太幣Ether用戶是否具有通證等等)。 這些檢查工作應(yīng)該首先被完成舔琅。
第二步等恐,如果所有檢查都通過了,應(yīng)該接著進行會影響當前合約狀態(tài)變量的那些處理备蚓。 與其它合約的交互應(yīng)該是任何函數(shù)的最后一步课蔬。
早期合約延遲了一些效果的產(chǎn)生,為了等待外部函數(shù)調(diào)用以非錯誤狀態(tài)返回郊尝。 由于上文所述的重入問題二跋,這通常會導致嚴重的后果。
請注意流昏,對已知合約的調(diào)用反過來也可能導致對未知合約的調(diào)用扎即,所以最好是一直保持使用這個模式編寫代碼。
5.4 包含故障-安全(Fail-Safe)模式
盡管將系統(tǒng)完全去中心化可以省去許多中間環(huán)節(jié)况凉,但包含某種故障-安全模式仍然是好的做法谚鄙,尤其是對于新的代碼來說:
你可以在你的智能合約中增加一個函數(shù)實現(xiàn)某種程度上的自檢查,比如 以太幣Ether 是否會泄露刁绒?”闷营, “通證的總和是否與合約的余額相等?”等等知市。 請記住傻盟,你不能使用太多的 gas,所以可能需要通過鏈外off-chain計算來輔助嫂丙。
如果自檢查沒有通過娘赴,合約就會自動切換到某種“故障安全”模式, 例如奢入,關(guān)閉大部分功能筝闹,將控制權(quán)交給某個固定的可信第三方,或者將合約轉(zhuǎn)換成一個簡單的“退回我的錢”合約腥光。
5.5 形式化驗證
使用形式化驗證可以執(zhí)行自動化的數(shù)學證明关顷,保證源代碼符合特定的正式規(guī)范。 規(guī)范仍然是正式的(就像源代碼一樣)武福,但通常要簡單得多议双。
請注意形式化驗證本身只能幫助你理解你做的(規(guī)范)和你怎么做(實際的實現(xiàn))的之間的差別。 你仍然需要檢查這個規(guī)范是否是想要的捉片,而且沒有漏掉由它產(chǎn)生的任何非計劃內(nèi)的效果平痰。
6. 業(yè)務(wù)對接
不管怎么小心閉坑汞舱,對于一行代碼可以摧毀整個區(qū)塊鏈通證生態(tài)的風險,很多基金會還是會傾向于找專業(yè)團隊做驗收測試宗雇。
國內(nèi)有幾家專業(yè)做智能合約測試的公司昂芜,慢霧科技-廈門,鏈安科技-成都赔蒲,Testin云測泌神,知道創(chuàng)宇,輝哥跟這些創(chuàng)始團隊都建立了聯(lián)系舞虱,他們可以提供火幣/OK等大的交易所認同的測試報告欢际,有需要可幫忙對接介紹。
7. 參考文檔
1) EduCoin(EDU) 智能合約漏洞分析及修復方法
1> 已更新的智能合約地址
2> 老的問題智能合約地址
2)BEC 智能合約無限轉(zhuǎn)幣漏洞分析及預警
3)BCH 挖礦程序 Bitcoin-ABC 分叉漏洞剖析
4)智能合約 transferFrom 權(quán)限控制不當導致的任意盜幣攻擊簡述
5) 價值兩百萬的以太坊錢包陷阱 - OK 陷阱
6) 以太坊黑色情人節(jié)專題之惡意掃描 IP 披露
7) 【以太坊開發(fā)】BeautyChain (BEC) 溢出漏洞分析
8)以太坊生態(tài)缺陷導致的一起億級代幣盜竊大案
9)安全考量