Assert, Require, Revert 和 Exceptions
Solidity使用state-reverting異常來處理錯誤。 這種異常将復原目前調用(及其所有子調用)狀态的所有變化,并将錯誤标志給調用者。
函數
assert
和
require
可以用于檢查條件,如果條件不滿足則抛出異常。
assert
函數隻能用于測試内部錯誤,并檢查不變量。
應該使用
require
函數來确認input或合約狀态變量滿足條件,或者驗證調用外部合約的傳回值。
如果正确使用,分析工具可以評估合約和函數調用是否
assert
失敗。 正常運作的代碼不應該
assert
失敗;如果發生這種情況,則需要修複合約中的bug。
還有另外兩種方法可以觸發異常:
-
函數可用于标記錯誤并復原目前的調用。revert
-
關鍵字也可以用作throw
的替代方法。revert()
注解 |
---|
從0.4.13版本, 關鍵字已被棄用,将來會被淘汰。 |
當異常發生在子調用中時,它們會自動“冒泡”(即異常被重新抛出)。
但也有例外:
send
和底層函數
call
,
delegatecall
和
callcode
——在異常情況下傳回
false
,而不是“冒泡”。
警告 |
---|
如果調用的帳戶不存在, , 和 的傳回值會是 ,EVM就是這麼設計的。如果需要,必須在調用之前檢查賬戶是否存在。 |
捕捉異常目前還做不到。
在下面的示例中,您可以看到如何使用
require
來輕松檢查輸入條件,以及
assert
如何用于内部錯誤檢查:
pragma solidity ^;
contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % == ); // 隻允許偶數
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / );
//因為轉賬失敗而引發異常
//不能在這裡回調,我們不可能
//仍然有一半的錢。
assert(this.balance == balanceBeforeTransfer - msg.value / );
return this.balance;
}
}
在以下情況下會生成
assert
風格異常:
- 以過大或負數索引通路數組(比如
,x[i]
或i >= x.length
)i < 0
- 以過大或負數索引通路固定長度的
。bytesN
- 除數或模數為零(例如
或5/0
)。23%0
- 對負數進行位移
- 将過大或負數轉化為枚舉類型。
- 調用内部函數類型的0初始化(zero-initialized)變量。
-
的條件為assert
false
在以下情況下會生成
require
風格異常:
- 調用
throw
- 調用
并且條件為require
false
- 通過消息調用來調用函數,但是它沒有正确完成(即,用盡了gas,沒有比對的函數,或者抛出了異常),除非使用底層别的操作
,call
,send
或delegatecall
。 底層别的操作不會抛出異常,而是通過傳回callcode
來表示失敗。false
- 使用
關鍵字建立合約但是失敗。new
- 執行一個外部函數調用,其指向不包含代碼的合約。
- 合約通過
函數接收Ether,但沒有public
修飾符。包括構造函數和回退函數。payable
- 合約通過一個
的public
函數接收Ether。getter
- 如果
失敗。.transfer()
在内部,Solidity對
require
-style異常執行一個revert操作(
0xfd
指令),并執行一個無效操作(指令
0xfe
)來抛出一個
assert
-style異常。 在這兩種情況下,這将導緻EVM revert對狀态所做的所有更改。 revert的原因是沒有安全的方式來繼續執行,因為沒有發生預期的效果。 因為我們要保留交易的原子性,是以最安全的做法是恢複所有的變化,并使整個事務(或至少調用)無效。 請注意,
assert
風格的異常消耗調用中可用的所有gas,而
require
風格的異常将不會消耗從Metropolis版本開始的任何gas。
上一篇:深入了解Solidity——作用域和聲明
下一篇:深入了解Solidity——建立合約