Solidityで作成するコントラクトの関数から例外を投げる方法について。
http://solidity.readthedocs.io/en/develop/control-structures.html#exceptions
コントラクトの関数内で手動で例外を投げたい場合throw
を使う。例外が投げられると現在実行中の処理が停止し、状態や残高に関する全ての変更は取り消され元に戻る。
ちなみに発生した例外はキャッチすることはまだできない。
throw
の例↓
pragma solidity ^0.4.0; contract Sharer { function sendHalf(address addr) payable returns (uint balance) { if (!addr.send(msg.value / 2)) throw; // Sharerへの転送を元に戻す return this.balance; } }
Solidityで自動的に例外が発生するケース
以下のケースでは、自動的に例外が発生する
- 配列にアクセスする際のインデックの値が大きすぎるかマイナスの値の場合
- 固定長のbytesNに大きすぎるもしくはマイナスのインデックスでアクセスした場合
- メッセージ呼び出しを使って関数を呼び出したが(gas不足や合致する関数が無い、その関数自体が例外を投げるなど)その関数が正常に終了しない場合。ただし、
call
やsend
、delegatecall
といった低レベルの操作の場合は例外は発生せず、falseを返すことにより障害が発生したことを示す。 - 新しいキーワードを使ってコントラクトを作成したがそのコントラクトが正しく作成されない場合(前述のような理由で)。
- 0を使った除算、剰余を行った場合(
5 / 0
とか23 % 0
とか) - 何のコードも含まれていないをターゲットにした外部関数呼び出しを実行した場合
payable
修飾子が付いていない関数を介してetherを受け取った場合- コントラクトがpublicなアクセッサ関数を介してetherを受け取った場合
内部的には、Solidityは例外が発生するとinvalid jump
を実行することで、EVMが全ての変更を元に戻している。
実際に例外を発生させたトランザクションのデバッグトレースを見ると
... { depth: 1, error: "invalid jump destination (PUSH1) 2", gas: 2977807, gasCost: 8, memory: ["0000000000000000000000000bcfa0dd51b7be1081cd1b82b2e3cb17f7fa1d56", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb51", "0000000000000000000000000000000000000000000000000000000000000060"], op: "JUMP", pc: 970, stack: ["000000000000000000000000000000000000000000000000000000004b5f4b1d", "00000000000000000000000000000000000000000000000000000000000000f1", "0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000000000000000000000000000000001f4", "a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "61f469589cff8db864e34dcbe0959d5c4658d7fc34600773b9ffb8ae1321930e"], storage: {} }] }
とinvalid jump
で終わってるのが分かる。