Develop with pleasure!

福岡でCloudとかBlockchainとか。

コントラクトの関数で例外を投げる

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不足や合致する関数が無い、その関数自体が例外を投げるなど)その関数が正常に終了しない場合。ただし、callsenddelegatecallといった低レベルの操作の場合は例外は発生せず、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で終わってるのが分かる。