はじめに
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](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F28899%2F120e0e19-8adb-85f1-c9cf-3aa42a7849eb.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=8628258abadc1b1412f15b39fdfeaccc)
一番オーソドックスな呼びだし方で、C2 側の msg.sender
には C1
のアドレスが入る。
C2 が参照する storage 領域は C2 のものなので、C2 の num
と sender
が更新される。
Call による呼び出し
上記の callSetNum
による呼び出し。
具体的に呼び出しは関数名の sha3 の上位 4 バイトを使って呼び出す。
c2.call(bytes4(sha3("setNum(uint256)")), _num)
![スクリーンショット 2018-07-04 11.16.37.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F28899%2F4d2b2e2b-339a-be7d-9c7b-5bf7abfbd72e.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=b2751fd9a1c12ecbe9cba6aab80695f1)
挙動としては直接呼び出しと同じ。
callcode による呼び出し
上記の callcodeSetNum
による呼び出し。
![スクリーンショット 2018-07-04 11.16.41.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F28899%2F4df44a58-bbae-58c0-acea-1a94e4e3e528.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=4c69081c2ea0d56b0083706bc5e03fd4)
この呼び出し方では、C2 が指す Storage 領域は C1 のものになる。また msg.sender
は直接の呼び出し元である C1 のアドレスになる。
この関数が実行され終わったあとで、C1 の num
と storage
が更新されていて、C2 のnum
と storage
は更新されない。
現在は非推奨の method なので基本的には使わない方が良い。
delegatecall による 呼び出し
上記の delegatecallSetNum
による呼び出し。
![スクリーンショット 2018-07-04 11.16.47.png](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F28899%2F88b5f8b4-233d-8dac-9b78-2df7ac150500.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=c38fdcbb1510d0a3d4de16e4aaf62129)
この呼び出し方では、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 を指す | ライブラリなど |