引き続き、Solidity v0.5.0 Breaking Changes から気になったものをピックアップしていくシリーズです。
今回は、外部コントラクトの function を呼び出すときに使う .delegatecall() について。前回の keccak256() in Solidity v0.5.0 の内容も関係してきます。
サンプルコード
v0.4.25
複数の引数を渡せました。また、返り値は bool 1 つでした。
<address>.delegatecall(...) returns (bool)
まず、呼び出される側のコントラクトのサンプルコード。
pragma solidity ^0.4.25;
contract Callee {
uint256 public v;
function sum(uint256 _x, uint256 _y) public {
v = _x + _y;
}
}
ただの足し算をして、結果を v に保存します。桁が大きいと overflow するのでよろしくないですが、今回は無視します。
次に、呼び出す側のコントラクトのサンプルコード。
pragma solidity ^0.4.25;
contract Caller {
uint256 public v;
address public callee;
function setCallee(address _callee) public {
callee = _callee;
}
function sum(uint256 _x, uint256 _y) public {
require(callee.delegatecall(bytes4(keccak256("sum(uint256,uint256)")), _x, _y));
}
}
setCallee() で適切な Callee のアドレスを設定した後で sum() を実行すると、足し算の結果が Caller の v に保存されます。また、前述した通り .delegatecall() の返り値は bool 1 つなので、そのまま require() に渡せています。
v0.5.0
前回の keccak256() in Solidity v0.5.0 でも言及した通り、引数が bytes 1 つになりました。
<address>.delegatecall(bytes memory) returns (bool, bytes memory)
また、返り値として、bool だけでなく bytes が返ってくるようになりました。すなわち、.delegatecall() で呼び出した function の返り値にアクセスできるようになったわけです。今回は、この返り値を使って、上記 v0.4.25 のコードを少し違うパターンで実装してみようと思います。
まず、呼び出される側のサンプルコード。
pragma solidity >0.4.99 <0.6.0;
contract Callee {
function sum(uint256 _x, uint256 _y) public pure returns(uint256) {
return _x + _y;
}
}
今回は足し算の結果を返すようにします。上記 v0.4.25 のサンプルコードでは v に保存するところまで行いましたが、返すだけです。
次に、呼び出す側のサンプルコード。
pragma solidity >0.4.99 <0.6.0;
contract Caller {
uint256 public v;
address public callee;
function setCallee(address _callee) public {
callee = _callee;
}
function sum(uint256 _x, uint256 _y) public {
(bool success, bytes memory data) = callee.delegatecall(abi.encodeWithSignature("sum(uint256,uint256)", _x, _y));
require(success);
v = bytesToUint256(data);
}
function bytesToUint256(bytes memory _b) internal pure returns(uint256) {
uint256 res;
assembly {
res := mload(add(_b, 32))
}
return res;
}
}
まず、.delegatecall() の引数が bytes 1 つになったため、abi.encodeWithSignature() を使って、引数として渡すデータをまとめる必要があります。また、今回は Callee が足し算の結果を保存せずに返してくるので、これを受け取って Caller 自身が v に保存しています。
まあ、足し算程度でこんなことする必要もないのですが、.delegatecall() で呼び出した function の返り値を受け取れるようなったことで、外部ライブラリで実現できることの幅は広がりそうです。
補足
v0.5.0 では、上記に関連した以下のような変更も入っていますので、ご注意ください。
.delegatecall()だけでなく、.call()と.staticcall()も、呼び出した function の返り値を受け取れるようになりました.callcode()は使えなくなりました