call()とdelegatecall()(およびcallcode())の違い
いずれも別のコントラクトの関数の処理を実行するための関数
- <コントラクトアドレス>.call(): 呼び出し先コントラクトのコンテキストで関数の処理を実行する。関数内の状態参照や状態変更は呼び出し先コントラクトの状態を参照したり更新したりする。
- <コントラクトアドレス>.delegatecall()および<コントラクトアドレス>.callcode(): 呼び出し元コントラクトのコンテキストで関数の処理を実行する。関数内の状態参照や状態変更は呼び出し元コントラクトの状態を参照したり更新したりする。
delegatecall()とcallcode()の違い
関数内のmsg.senderとmsg.valueが違う。
delegatecall()で実行される関数内のmsg.senderはdelegatecall()が含まれる関数の呼び出し元コントラクトであるのに対し、callcode()で実行される関数内のmsg.senderは、callcode()が含まれる関数のコントラクトである。
わかりづらいので例示
以下のようにCallerCaller -> Caller -> Calleeと呼び出すパターンで、CallerからCalleeの呼び出しでcall()とdelegatecall()とcallcode()を使い分けてみる。
contract CallerCaller {
Caller caller;
function CallerCaller(address _caller) public {
caller = Caller(_caller);
}
function setByCall(uint256 _v) public {
caller.setByCall(_v);
}
function setByCallcode(uint256 _v) public {
caller.setByCallcode(_v);
}
function setByDelegateCall(uint256 _v) public {
caller.setByDelegateCall(_v);
}
}
contract Caller {
uint256 public v;
address callee;
function Caller(address _callee) public {
callee = _callee;
}
function setByCall(uint256 _v) public {
require(callee.call(bytes4(keccak256("set(uint256)")), _v));
}
function setByCallcode(uint256 _v) public {
require(callee.callcode(bytes4(keccak256("set(uint256)")), _v));
}
function setByDelegateCall(uint256 _v) public {
require(callee.delegatecall(bytes4(keccak256("set(uint256)")), _v));
}
}
contract Callee {
uint256 public v;
event SenderIs(address);
function set(uint256 _v) public {
v = _v;
SenderIs(msg.sender);
}
}
- setByCall(42) -> Calleeのvが42にセットされ、SenderIsイベントに含まれるアドレスはCallerのもの。
- setByDelegateCall(43) -> Callerのvが43にセットされ、SendierIsイベントに含まれるアドレスはCallerCallerのもの
- setByCallCode(44) -> Callerのvが44にセットされ、SendierIsイベントに含まれるアドレスはCallerのもの
delegatecallはなんのためにあるのか
delegatecallはライブラリを実現するためにある。コントラクトとしてインストールされたライブラリを使う際にライブラリ利用側の状態を参照・変更するため。
callcode
msg.senderの問題があり非推奨。