エラーハンドリングであるrevert、require、assert関数について記述しておきます。
以下のコードは条件分岐としての流れは全て同じとなりますが、利用シーンやGas消費の仕様が変わってきます。
1 2 3 4 5 6 7 |
if( !x ) { throw; } if( !x ) { revert(); } require( x ); assert( x ); |
例外処理について
例外処理としてのthrowとrevertはコントラクトに対する処理を全て取り消します。つまりトランザクション処理の前の状態に戻します(ロールバック)。そして、その処理までのGasのみを消費し、残りのGasは戻されます(Solidity 0.4.10以降)。
※throwはSolidity 0.4.13以降は非推奨となっているので以下のようなコードはrequireを利用するようにします。
1 2 3 4 5 |
//非推奨 modifier onlyOwner () { if( msg.sender !== owner ) throw; _; } |
1 2 3 4 |
modifier onlyOwner () { require( msg.sender == owner ); _; } |
requireとassertについて
require
仕様として「REVERT(戻す)」opcodeである0xfdにコンパイルされます。
falseを返した場合、処理実行前のコントラクトの状態へと戻し、その処理までのGasは消費しますが、残りのGasは返されます。
assert
仕様として「INVALID(無効とする)」opcodeである0xfeにコンパイルされます。
falseを返した場合、処理実行前のコントラクトの状態へと戻しますが、残りすべてのGasも消費されます。
requireとassertのイメージの違いは以下のコードを参照しておくと良いでしょう。
1 2 3 4 5 6 7 8 9 10 11 |
pragma solidity ^0.4.0; contract Sharer { function sendHalf(address addr) public payable returns (uint balance) { require(msg.value % 2 == 0); uint balanceBeforeTransfer = this.balance; addr.transfer(msg.value / 2); assert(this.balance == balanceBeforeTransfer - msg.value / 2); return this.balance; } } |
transferは失敗時に例外を返しますが、実行前のコントラクトの状態へ戻せません。そのためにassertでコントラクトの内部状態を検証し(上記コードではthis.balanceの量)、もしtransferによる送金が失敗していたら、実行前のコントラクトの状態に戻すようにしています。
一般的にrequireは関数のはじめに記述し、主に外部からの入力および呼び出した外部コントラクトから返ってきた値の検証等で利用します。
またassertはコントラクトの内部状態を検証するために処理の後に記述し、予期せぬ事態への対処を行います。
※上記コードについては以下サイトを引用し、書籍『ブロックチェーンアプリケーション開発の教科書』(下記参照書籍)の説明を参照した。
Solidity 0.4.21 documentation
Expressions and Control Structures
参照ページ
yellow paper (PDF)