Ethereum
solidity

[図解] delegatecall callcode call の違い


はじめに

Solidity には contract が別の contract を呼び出す仕組みがあるが、関数がいくつかあるためどれを使えば良いのか多少混乱しました。Remix で動かしつつ挙動を確認したのでそれぞれの特徴を整理してみました。

実際に動かして挙動を試したい方は、こちらの gist を remix に貼り付けて動きを見てみる事をオススメします。

https://gist.github.com/critesjosh/68593429fd2f84f8f55d4ff7b74f0323


関数と特徴

上記 gist のコントラクトを例として各関数の挙動を解説します。

pragma solidity ^0.4.15;

contract C1 {
uint public num;
address public sender;

// 直接呼び出し
function c2setNum(address _c2, uint _num) public{
C2 c2 = C2(_c2);
c2.setNum(_num);
}

// call 呼び出し
function callSetNum(address c2, uint _num) public {
if(!c2.call(bytes4(sha3("setNum(uint256)")), _num)) revert(); // C2's num is set
}

// callcode 呼び出し
function callcodeSetNum(address c2, uint _num) public {
if(!c2.callcode(bytes4(sha3("setNum(uint256)")), _num)) revert(); // C1's num is set
}

// delegatecall 呼び出し
function delegatecallSetNum(address c2, uint _num) public {
if(!c2.delegatecall(bytes4(sha3("setNum(uint256)")), _num)) revert(); // C1's num is set
}
}

contract C2 {
uint public num;
address public sender;

function setNum(uint _num) public {
num = _num;
sender = msg.sender;
// msg.sender is C1 if invoked by C1.callcodeSetNum
// msg.sender is C3 if invoked by C3.foo()

}
}


関数の直接呼び出し

上記の c2setNum による呼び出し。

スクリーンショット 2018-07-04 11.16.33.png

一番オーソドックスな呼びだし方で、C2 側の msg.sender には C1 のアドレスが入る。

C2 が参照する storage 領域は C2 のものなので、C2 の numsender が更新される。


Call による呼び出し

上記の callSetNum による呼び出し。

具体的に呼び出しは関数名の sha3 の上位 4 バイトを使って呼び出す。

c2.call(bytes4(sha3("setNum(uint256)")), _num)

スクリーンショット 2018-07-04 11.16.37.png

挙動としては直接呼び出しと同じ。


callcode による呼び出し

上記の callcodeSetNum による呼び出し。

スクリーンショット 2018-07-04 11.16.41.png

この呼び出し方では、C2 が指す Storage 領域は C1 のものになる。また msg.sender は直接の呼び出し元である C1 のアドレスになる。

この関数が実行され終わったあとで、C1 の numstorage が更新されていて、C2 のnumstorage は更新されない。

現在は非推奨の method なので基本的には使わない方が良い。


delegatecall による 呼び出し

上記の delegatecallSetNum による呼び出し。

スクリーンショット 2018-07-04 11.16.47.png

この呼び出し方では、C2 が指す Storage 領域は C1 のものになる。また msg.sender は元々の呼び出し元である EOA のアドレスになる。

EOA からみるとあたかも C1 コントラクトが全ての処理を行い結果を格納しているように見えるので、ライブラリなどを利用する際にはこの呼び出し方を使うと良い。


まとめ

それぞれの関数はの特徴をまとめるよ以下のようになります。

関数の呼び出し方
msg.sender
storage
用途

直接呼び出し
直接の呼び出し元
呼び出された contract の storage を指す
Crowdsale contract が token contract の関数を呼び出すなど

call
直接の呼び出し元
呼び出された contract の storage を指す
Crowdsale contract が token contract の関数を呼び出すなど

callcode
直接の呼び出し元
直接の呼び出し元 contract の storage を指す
非推奨

delegatecall
EOA
直接の呼び出し元 contract の storage を指す
ライブラリなど