アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

Solidityの外部ライブラリの呼び出し方いろいろ

Solidityで用意されている外部のcontractのメソッドを呼び出すdelegatecallのtipsみたいなものをまとめてみました。
思いついた使い方は以下の4通り。

A. library定義を利用して呼び出す
B. 外部Contractのaddressをあらかじめ準備してdelegatecallメソッドを利用して呼び出す
C. Bの派生というか王道?可変長引数のヘッダパートを補完するのがめんどくさいので、同名、同引数の関数を用意して、そのまま引数にmsg.dataを渡してdelegatecallを呼び出す
D. C.の派生で何らかの事情でメソッド名を合わせられない時にメソッドのsignature部分だけを書き換えてdelegatecallを呼び出す


まぁよっぽどの理由がない限りA.のLibraryを定義する方法が間違いも少なく安定ですね。ちなみに、libraryを使った方が圧倒的にGas代も安くすみます。browser-solidityのデバッグで見る限り、引数のデータをメモリに展開してからlibraryのメソッドをdelegatecallしてるみたいなのになんでfoo(uint, uint[])よりも安く済むんだろうか・・・

それぞれのメソッド実行で消費されたGas代の一覧
A. 23700 gas
B. 90059 gas
C. 53550 gas
D. 53570 gas

今回実験したソースを載せておきます。

pragma solidity ^0.4.18;

contract Callee 
{
    uint public result_;
    bytes public _calleedata;
    bytes public _callerdata;
    address constant public callee_ = 0x00;
    
    function foo (uint _offset, uint[] _nums) public returns (uint)
    {    
        _calleedata = msg.data;
        result_ = _offset;
        for ( uint i = 0; i < _nums.length; ++i)
             result_ += _nums[i];
        return result_;
    }
}

library CalleeLib
{
    struct Sum {
        uint _result;
    }
    
    function foo (Sum storage self, uint _offset, uint[] _nums) public returns(uint)
    {    
        self._result = _offset;
        for ( uint i = 0; i < _nums.length; ++i)
             self._result += _nums[i];
        
        return self._result;
    }
}

contract Caller
{
    uint public result_;
    bytes public _calleedata;
    bytes public _callerdata;
    address constant public callee_ = 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a;

    using CalleeLib for CalleeLib.Sum;
    CalleeLib.Sum sumlib;
    
    // A.Libraryとしてcall
    function callLib(uint _offset, uint[] _nums) public {
        result_ = sumlib.foo(_offset, _nums);    
    }
    
    // B.delegatecallメソッドを利用
    function Call (uint _offset, uint[] _nums) public returns (bool)
    {    
        uint data_address = 2 * 32; // header partが2つなので
        bytes4 sig = bytes4(keccak256("foo(uint256,uint256[])"));
        return callee_.delegatecall(sig, _offset, data_address, _nums.length, _nums);
    }
    
    // C.メソッド名と引数を合わせてそのままmsg.dataを渡してcall
    function foo(uint _offset, uint[] _nums) public returns (bool result)
    {    
        return callee_.delegatecall(msg.data);
    }

    // D.msg.dataを利用してメソッド名だけ変えてcall
    function callAssembly(uint _offset, uint[] _nums) public returns (bool result)
    {    
        bytes4 sig = bytes4(keccak256("foo(uint256,uint256[])"));
        address target = callee_;
        
        assembly {
            let freep := mload(0x40)
            mstore(freep, sig)
            calldatacopy(add(freep, 4), 4, sub(calldatasize,4))
            result := delegatecall(sub(gas, 10000), target, freep, calldatasize, freep, 0)
        }
    }
}