solidity勉強 (2)

前の続き

可視レベル

可視レベルとして、external, public, internal privateが指定できる。 ステート変数と関数でデフォルト値は違う。 ステート変数の場合internal(javaでいうprotected)がデフォルト値。 関数はpublicがデフォルト値。

詳細は以下を参照。

複数の値の返却

solidityはpythonのtupleのように複数の値を返却できる。

pragma solidity ^0.4.11;

contract test {
    function tuple() private returns (int, string, address) {
        return (1, "hello", msg.sender);
    }

    function vars() returns (int, string, address) {
        var (_i, _message, _sender) = tuple();
        return (_i + 1, _message, _sender);
    }

}

// vars
// Transaction cost: 22238 gas.
// Execution cost: 966 gas.
// Decoded:
//   int256: 2
//   string: hello
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

abstract contract

javaのabstractと同じ。 実装が欠けてるとデプロイできない。

pragma solidity ^0.4.11;

contract abstractContract {
    string name = "hoge";

    function getName() returns (string);
}


contract test is abstractContract {
    function getName() returns (string) {
        return name;
    }
}

// getName
// Transaction cost: 22736 gas.
// Execution cost: 1464 gas.
// Decoded:
//   string: hoge

interface

javaのインターフェイスと同じ。 インターフェイスを満たしていないとdeployができない。 abstrct contractとの違いは以下。

  • 他のコントラクトやインターフェイスを継承できない
  • コンストラクタを定義できない
  • 変数、struct、enumを定義できない
pragma solidity ^0.4.11;

contract testInterface {
    function getName() returns (string);
    function getMessage() returns (string);
}

contract test is testInterface {
    string name;
    string message;

    function test() {
        name = "alice";
        message = "bob";
    }

    function getName() returns (string) {
        return name;
    }

    function getMessage() returns (string) {
        return message;
    }

}

arrayの返却

arrayを返却することもできる。ただし、多次元配列やstructのarrayは返却できない。 stringは2次元配列扱いなのでできない。

pragma solidity ^0.4.11;

contract testInt {
    function returnIntArray() returns (int[]) {
        int[] memory a = new int[](3);
        a[0] = 1;
        a[1] = 10;
        a[2] = 100;
        return a;
    }
}

// returnIntArray
// Transaction cost: 22269 gas.
// Execution cost: 997 gas.
// Decoded:
//   int256[]: 1, 10, 100

contract testByte {
    function returnByteArray() returns (byte[]) {
        byte[] memory a = new byte[](3);
        a[0] = byte(1);
        a[1] = byte(10);
        a[2] = byte(100);
        return a;
    }
}

// returnByteArray
// Transaction cost: 22365 gas.
// Execution cost: 1093 gas.
// Decoded:
//   bytes1[]: 0x01, 0x0a, 0x64


// contract testString {
//     // compile error
//     // 2次元配列扱い
//     function returnStringArray() returns (string[]) {
//         string[] memory a = new string[](3);
//         a[0] = 1;
//         a[1] = 10;
//         a[2] = 100;
//         return a;
//     }
// }

// contract testStruct {
//     struct value {
//         int v;
//     }
//     // compile error
//     // structはだめ
//     function returnStructArray() returns (value[]) {
//         value[] memory a = new value[](3);
//         a[0].v = 1;
//         a[1].v = 10;
//         a[2].v = 100;
//         return a;
//     }
// }

constant

状態が変更しないこと明示的に指定する。変数や関数につけられる。 関数では主に状態変更をしないgetterにつけておく。 constantな関数はweb3.js等でmethods.myMethod.callを使うことで、ローカルノードで処理を完結できるためgasコスト無しで処理ができる。ただし、ローカルノードの状態に依存する。

現状、状態を変更する関数にconstantをつけてもコンパイラが怒ってくれないので注意。この状態で実行すると謎の挙動になる。

pragma solidity ^0.4.11;

contract test {
    string name;
    string message;

    function test() {
        name = "alice";
        message = "hello";
    }

    function getName() constant returns (string) {
        return name;
    }

    function getMessage() constant returns (string) {
        return message;
    }

    function getAndUpdateMessage() constant returns (string) {
        var m = message;
        name = "bob";
        message = "zzz";
        return m;
    }
}

// getName
// Transaction cost: 22758 gas. (caveat)
// Execution cost: 1486 gas.
// Decoded:
//   string: alice
// getMessage
// Transaction cost: 22780 gas. (caveat)
// Execution cost: 1508 gas.
// Decoded:
//   string: hello
// getAndUpdateMessage
// Transaction cost: 44069 gas. (caveat)
// Execution cost: 22797 gas.
// Decoded:
//   string: zzz

アドレスからコントラクトへの変換

<コントラクト名>(<コントラクトのアドレス>)でアドレスをコントラクトに変換することができる。 変換したコントラクトの関数を呼び出した場合、address.call同様にコンテキストや呼び出し元が変更されているので注意。

pragma solidity ^0.4.11;

contract A {
    int value = 100;

    function getValue() returns (int, address, address) {
        return (value, this, msg.sender);
    }
}

contract B {
    string message = "zzz";
    address contractA;

    function B(address _contractA) {
        contractA = _contractA;
    }

    function getValue() returns (int, string, address, address, address, address){
        message = "hello";
        var (_v, _this, _sender) = A(contractA).getValue();
        return (_v, message, this, msg.sender, _this, _sender);
    }
}

// 1. Aのコントラクトを作ってアドレスを取得する
//    output: 0x8609a0806279c94bcc5432e36b57281b3d524b9b
// 2. 1で取得したアドレスを指定してBのコントラクトを作る
//    input: "0x8609a0806279c94bcc5432e36b57281b3d524b9b"
//
// getValue
// Transaction cost: 35962 gas.
// Execution cost: 14690 gas.
// Decoded:
// int256: 100
// string: hello
// address: 0x9876e235a87f520c827317a8987c9e1fde804485
// address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
// address: 0x8609a0806279c94bcc5432e36b57281b3d524b9b
// address: 0x9876e235a87f520c827317a8987c9e1fde804485

thisやsenderが変わっていることが確認できる。次に、呼び出した関数でrevert()を読んでみる。

pragma solidity ^0.4.11;

contract A {
    int value = 100;

    function getValue() returns (int, address, address) {
        revert();
        return (value, this, msg.sender);
    }
}

contract B {
    string message = "zzz";
    address contractA;

    function B(address _contractA) {
        contractA = _contractA;
    }

    function getValue() returns (int, string, address, address, address, address){
        message = "hello";
        var (_v, _this, _sender) = A(contractA).getValue();
        return (_v, message, this, msg.sender, _this, _sender);
    }
}

// getValue
// Exception during execution.

この場合は、例外になる。address.callのように自前で例外の処理をする必要はない。

contractの変更

一度deployしたcontractを変更することはできない。ベースがブロックチェーンなので、記録はできても消したり変更したりはできない。そこで、変更が必要になった時のことを考えてコントラクトを作る必要がある。

  • 変更可能なコントラクトを作るポイント
    • ストレージ用とロジック用のコントラクトを分ける
    • ストレージコントラクトを汎用的に作っておく
    • インターフェイス定義をしておいて、変更があってもインターフェイスは保障されるようにする

これまでのことを踏まえつつ、contractを作ると以下のような感じになる。

pragma solidity ^0.4.11;

contract Ownable {
  address public owner;

  function Ownable() {
    owner = msg.sender;
  }

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
}

contract storageContract is Ownable {
    address _this;
    address _sender;

    mapping (address => bool) allowContracts;

    function setAllowContracts(address _allowContract) onlyOwner {
        allowContracts[_allowContract] = true;
    }

    function checkAllowContract() private returns (bool) {
        return (allowContracts[msg.sender] == true);
    }

    mapping(bytes32 => uint) idMap;

    function getAndIncrementId(bytes32 key) returns (uint, bool) {
        if (!checkAllowContract()) {
            return (0, false);
        }
        var _v = idMap[key];
        idMap[key] = _v + 1;
        return (_v, true);
    }

    function getMaxId(bytes32 key) constant returns (uint) {
        return idMap[key];
    }

    function getIdArray(bytes32 key) constant returns (uint[]) {
        var _maxId = idMap[key];
        uint[] memory ids = new uint[](_maxId);
        for (uint i = 0; i < _maxId; i++) {
            ids[i] = i;
        }
        return ids;
    }

    mapping(bytes32 => string) stringMap;

    function setString(bytes32 key, string value) returns (bool) {
        if (!checkAllowContract()) {
            return false;
        }
        stringMap[key] = value;
        // thisはstorageContract
        _this = this;
        // msg.senderはmasterContract
        _sender = msg.sender;
        return true;
    }

    function getString(bytes32 key) constant returns (string) {
        return stringMap[key];
    }

    // this, sender確認用。不要。
    function getThisSender() returns (address, address) {
        return (_this, _sender);
    }
}

library logicLibrary {
    int constant NG = 0;
    int constant OK = 1;

    function checkName(string _name) returns (bool) {
        if (bytes(_name).length == 0) {
            return false;
        }
        return true;
    }

    function setUser(address _storage, string _name, string _email) internal returns (uint, int) {
        if (!checkName(_name)) {
            return (0, NG);
        }
        var (_userId, _ok) = storageContract(_storage).getAndIncrementId(sha3("userId"));
        if (!_ok) {
            return (0, NG);
        }
        _ok = storageContract(_storage).setString(sha3("user.name", _userId), _name);
        if (!_ok) {
            return (0, NG);
        }
        _ok = storageContract(_storage).setString(sha3("user.email", _userId), _email);
        if (!_ok) {
            return (0, NG);
        }
        return (_userId, OK);
    }
}

contract masterContractInterface {
    function setUser(string _name, string _email) returns (uint, int);
}

contract mainContract is masterContractInterface {
    using logicLibrary for address;
    address externalStorage;
    address _this;
    address _sender;

    function mainContract(address _externalStorage) {
        require(_externalStorage != address(0));
        externalStorage = _externalStorage;
    }

    function setUser(string _name, string _email) returns (uint, int) {
        // thisはmasterContract
        _this = this;
        // msg.senderはこの関数を呼び出したコントラクトのアドレス
        _sender = msg.sender;
        return externalStorage.setUser(_name, _email);
    }

    // sha3の値が知りたいだけ、クライアント側で計算すれば不要
    function getIdKey(string _key) returns (bytes32) {
        return sha3(_key);
    }
    function getValueKey(string _key, uint _userId) returns (bytes32) {
        return sha3(_key, _userId);
    }

    // this, sender確認用。不要。
    function getThisSender() returns (address, address) {
        return (_this, _sender);
    }
}

// 1. storageContractをdeployしてアドレスを取得
//      output: "0x11f0c46dc617619d51aa60e02daca7608dc714e7"
// 2. mainContractをdeplayしてアドレスを取得
//      input:  "0x11f0c46dc617619d51aa60e02daca7608dc714e7"
//      output: "0x91e3e0cd9afa1a0ced2456214a2e544a4f5a1db2"
// 3. storageContractのsetAllowContractでmainContractのアドレスを設定
//      input:  "0x91e3e0cd9afa1a0ced2456214a2e544a4f5a1db2"
// 4. mainContractのsetUserで一人目のユーザーを登録
//      input:  "user1", "email1"
//      output  0, 1
// 5. mainContractのsetUserで二人目のユーザーを登録
//      input:  "user2", "email2"
//      output  1, 1
// 6. mainContractのgetIdKeyで"userId"のsha3の値を取得
//      input:  "userId"
//      output: 0x27c962f3b25d487c0ce42b202affbfd86a5cc45d430047bd3644438ed354eaaa
// 7. storageContractのgetIdArrayでuserIdリストを確認
//      input:  "0x27c962f3b25d487c0ce42b202affbfd86a5cc45d430047bd3644438ed354eaaa"
//      output: 0, 1
// 8. mainContractのgetValueKeyで"user.name", 0のsha3の値を取得
//      input: "user.name", 0
//      output: 0x68582ccb44bf56e47bc7080f480a1889bc73a0c06b5be9bcca501755eb67fc91
// 9. mainContractのgetValueKeyで"user.email", 1のsha3の値を取得
//      input: "user.email", 1
//      output: 0xf8c4454c0d4b8625ee2090e89210f762544f806f2ca8d8c9c5b43b12acf64b50
// 10. storageContractのgetStringでuserId 0のユーザ名を取得
//      input: "0x68582ccb44bf56e47bc7080f480a1889bc73a0c06b5be9bcca501755eb67fc91"
//      output: user1
// 11. storageContractのgetStringでuserId 1のメールアドレスを取得
//      input: "0xf8c4454c0d4b8625ee2090e89210f762544f806f2ca8d8c9c5b43b12acf64b50"
//      output: email2

ここで問題なのは、ライブラリのビジネスロジックだけを更新したい場合どうすればいいのかという問題が出てくるのだけど、どうしたらいいのだろう。。。 基本的に、DB操作とビジネスロジックは近くにあって欲しいし、delegatecallとか使い倒すとそれはそれでgasコストのオーバヘッドが出てくる。

単純に思いつく方法としては、機能単位にコントラクトとライブラリのセットを細分化しておけば、変更が必要になった場合の影響が小さくなることが考えられるぐらい。

以下のサイトでもライブラリのアップグレードをどうするか考えているみたい。

solidity勉強

細かい話はドキュメントを参照してもらうのが一番正確で早い。

メッセージ

ethreumにはトランザクションとメッセージがある。詳細は以下。

ちなみに、solidityのドキュメントで言うトランザクションはethreumのホワイトペーパのtransactionを含むメッセージ全般のことを言っている。 solidityのコード上msgという特別な変数が出てくるがこれは、このメッセージ全般を指す。

gas

contractのdeploy(トランザクション)やcontract内の関数の実行(メッセージ)にはノードのリソースを消費するためgas料金が発生する。つまり、リアルにお金がかかるということ。ちなみにgas料金は変動していて、今のgas料金がどれぐらいかは、以下からわかる。

gas limit

どれだけgasを使うかを指定する。gasが足りない場合は処理が中断される。トランザクションは巻き戻る。 無限ループ防止のために必要。

単位

一般的にethの単位がよく知られているが、一番最小の単位はwei。solidityのmsg.valueもweiを基準にしている。 そのほかにもbabbageとかlovelaceとかshannonとかszaboとかfinneyとかある。

var unitMap = {
    'noether':      '0',
    'wei':          '1',
    'kwei':         '1000',
    'Kwei':         '1000',
    'babbage':      '1000',
    'femtoether':   '1000',
    'mwei':         '1000000',
    'Mwei':         '1000000',
    'lovelace':     '1000000',
    'picoether':    '1000000',
    'gwei':         '1000000000',
    'Gwei':         '1000000000',
    'shannon':      '1000000000',
    'nanoether':    '1000000000',
    'nano':         '1000000000',
    'szabo':        '1000000000000',
    'microether':   '1000000000000',
    'micro':        '1000000000000',
    'finney':       '1000000000000000',
    'milliether':   '1000000000000000',
    'milli':        '1000000000000000',
    'ether':        '1000000000000000000',
    'kether':       '1000000000000000000000',
    'grand':        '1000000000000000000000',
    'mether':       '1000000000000000000000000',
    'gether':       '1000000000000000000000000000',
    'tether':       '1000000000000000000000000000000'
};

変数の格納場所

変数の格納場所はstorage, memory stackの3種類がある。

  • storage
    • 永続化される高価な領域
  • memory
    • storageより安い揮発性領域
  • stack
    • memoryより安い
    • 1関数に16変数しか定義できないほど小さな領域

  • address
    • ethreumのアドレスを入れる型
  • int, uint
    • int256のalias
    • uint256のalias
  • int8,..(8bit増やし)..,int256
  • byte
    • bytes1のalias
  • bytes1,..(1バイト増やし)..,bytes32
  • string
  • bytes
    • byte[]と同じようなもの
  • array
    • <型>[]で表す
    • storage領域であればpushを使って動的にサイズを拡張できる
  • map
    • mapping(<型> => <型>) で表す

msg

メッセージの情報が格納された変数。

  • msg.sender
    • メッセージ送信者のアドレス
  • msg.value
    • メッセージと一緒に送ったwei数

より詳細は、以下参照

block

ブロックチェーンのブロックにまつわる情報が格納された変数。

  • block.coinbase
    • コインベースを返す
  • block.number
    • 現在のブロック番号
  • block.timestamp
    • 現在のブロックのタイムスタンプ

より詳細は、以下参照

sha3()

keccak-256ダイジェストを取る関数。bytes32が返ってくる。

revert()

状態変更を巻き戻して実行を止める。

require()/assert()

どちらも条件がfalseの場合にrevert()を行うものだが、requireは入力が不正な場合、assertは内部的にあり得ない場合に使う。

Contract

classのようなもの。deployすると、deployしたcontractに対してアドレスが割り当てられる。 継承可能。contract名と同じ関数がコンストラクタ。

pragma solidity ^0.4.11;

contract test {
    string message;
    string name;

    function test() {
        message = "hello";
        name = "bob";
    }

    function getMessage() returns (string) {
        return message;
    }

    function getName() returns (string) {
        return name;
    }
}

contract test2 is test {
    string test;

    function test2() {
        test = "test";
        name = "alice";
    }

    function getAll() returns (string, string, string) {
        return (test, message, name);
    }
}

// getAll
//   Transaction cost: 25430 gas.
//   Execution cost: 4158 gas.
//   Decoded:
//     string: test
//     string: hello
//     string: alice
// getMessage
//   Transaction cost: 22780 gas.
//   Execution cost: 1508 gas.
//   Decoded:
//     string: hello
// getName
//   Transaction cost: 22736 gas.
//   Execution cost: 1464 gas.
//   Decoded:
//     string: alice

Modifier

pythonのデコレータのようなもの。”_”にラップした関数が挿入されているイメージ。 主に事前の条件チェックに使われる。継承で上書することもできる。

pragma solidity ^0.4.11;

contract test {
    string message;
    string name;
    bool ok = true;

    function test() {
        message = "hello";
        name = "bob";
    }

    modifier isOK() {
        assert(ok);
        _; // ここに関数本体が挿入されている
        // _ の後ろに何か書いてもOK
    }

    function getMessage() isOK returns (string) {
        return message;
    }

    function getName() isOK returns (string) {
        return name;
    }
}

contract test2 is test {
    string t;

    function test2() {
        t = "test";
        name = "alice";
        ok = false;
    }

    function getAll() isOK returns (string, string, string) {
        return (t, message, name);
    }
}

// getAll
//   Exception during execution.
// getMessage
//   Exception during execution.
// getName
//   Exception during execution.

address.call()

低レベルな呼び出し。別のコントラクトの関数を呼び出したりできる。 特に使う必要がないなら使わないのが無難。

  • 危険ポイント

    • msg.senderが変わる
    • reentrantが発生する可能性がある
      • 同じ契約の関数を呼び出した場合等
    • 呼び出された側でrevert()しても呼び出し側は自動でrevert処理をしない
      • 例外を伝播できなため
pragma solidity ^0.4.11;

contract test {
    string attr;
    string message;
    string name;
    address sender;

    function test() {
        attr = "hehehe";
        message = "hello";
        name = "alice";
    }

    function first() returns (string, string, string, string, address, address) {
        attr = "hahaha";
        if (this.call(bytes4(sha3("second()")))) {
            return ("success", attr, message, name, msg.sender, sender);
        } else {
            return ("error", attr, message, name, msg.sender, sender);
        }
    }

    function second()  {
        name = "bob";
        message = "zzz";
        sender = msg.sender;
    }

}

// first
// Transaction cost: 79543 gas.
// Execution cost: 58271 gas.
// Decoded:
//   string: success
//   string: hahaha
//   string: zzz
//   string: bob
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0xdc544654fefd1a458eb24064a6c958b14e579154

返却された値のaddressが変わっていることが確認できる。 次にsecond()内でrevert()をしてみる。

pragma solidity ^0.4.11;

contract test {
    string attr;
    string message;
    string name;
    address sender;

    function test() {
        attr = "hehehe";
        message = "hello";
        name = "alice";
    }

    function first() returns (string, string, string, string, address, address) {
        attr = "hahaha";
        if (this.call(bytes4(sha3("second()")))) {
            return ("success", attr, message, name, msg.sender, sender);
        } else {
            return ("error", attr, message, name, msg.sender, sender);
        }
    }

    function second()  {
        name = "bob";
        message = "zzz";
        sender = msg.sender;
        revert();
    }

}

// first
// Transaction cost: 2958284 gas.
// Execution cost: 2937012 gas.
// Decoded:
//   string: error
//   string: hahaha
//   string: hello
//   string: alice
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0x0

firstで変更したattrの値は変わっていないことが確認できる。

address.delegatecall()

address.call()との違いはコンテキストの違いにある。 delegatecallは呼び出し元の契約がコンテキストとなる。 callは呼び出された契約がコンテキストとなる。 つまり、別のコントラクトの関数を借りてきて使うことができる。

  • call()の場合
pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
    }

    function getA() returns (string, address, address) {
        return (name, self, sender);
    }
}

contract b {
    string name;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address) {
        name = "smith";
        if (contractA.call(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender);
        } else {
            return ("error", name, this, contractA, msg.sender);
        }
    }
}

// call
// Transaction cost: 86749 gas.
// Execution cost: 65477 gas.
// Decoded:
//   string: success
//   string: smith
//   address: 0x375eae23b65feb1833072328647902f1fe9afa61
//   address: 0xa36c60652b04ec2c04221eab0401fb91132f7a06
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
// getA
// Transaction cost: 23266 gas.
// Execution cost: 2058 gas.
// Decoded:
//   string: alice
//   address: 0xa36c60652b04ec2c04221eab0401fb91132f7a06
//   address: 0x375eae23b65feb1833072328647902f1fe9afa61

selfがcontractAを指していて、msg.senderが呼び出し元コントラクトになっていることがわかる。

  • delegatecallの場合
pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
    }
}

contract b {
    string name;
    address self;
    address sender;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address, address, address) {
        name = "smith";
        if (contractA.delegatecall(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender, self, sender);
        } else {
            return ("error", name, this, contractA, msg.sender, self, sender);
        }
    }
}

// call
// Transaction cost: 87368 gas.
// Execution cost: 66096 gas.
// Decoded:
//   string: success
//   string: alice
//   address: 0xe37538be3aee714cef0e8a11c0227d2240fec16f
//   address: 0x22e37c29ad8303c6b58d3cea5a3f86160278af01
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
//   address: 0xe37538be3aee714cef0e8a11c0227d2240fec16f
//   address: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

thisやmsg.senderが変わっていないことがわかる。 次に、delegatecallする関数にrevert()を仕込んでみる。

pragma solidity ^0.4.11;

contract a {
    string name;
    address self;
    address sender;

    function a() {
        name = "bob";
    }

    function update() {
        name = "alice";
        self = this;
        sender = msg.sender;
        revert();
    }
}

contract b {
    string name;
    address self;
    address sender;
    address contractA;

    function b(address _contractA) {
        contractA = _contractA;
        name = "jon";
    }

    function call() returns (string, string, address, address, address, address, address) {
        name = "smith";
        if (contractA.delegatecall(bytes4(sha3("update()")))) {
            return ("success", name, this, contractA, msg.sender, self, sender);
        } else {
            return ("error", name, this, contractA, msg.sender, self, sender);
        }
    }
}

// call
// Exception during execution.

関数を借りてきているだけなので、ちゃんとexceptionで中止される。

Library

前に説明したdelegatecallやJUMP命令が内部的に使われている。 主にロジックを外に出すときに使う。 internalがついていればJUMP命令が実行され、ついてなければDELEGATECALL命令が実行される。

usingで型にライブラリ関数をバインドできる。バインドするとpythonのselfのように扱えるようになる。 <型の変数>.<関数>()で呼び出しができるようになり、ライブラリ関数の一つ目の引数が”型の変数”になる。

pragma solidity ^0.4.11;

library who {
    struct person {
        string name;
    }

    function init (string _name) internal returns (person) {
        person memory _person;
        _person.name = _name;
        return _person;
    }

    function getName (person _person) internal returns (string) {
        return _person.name;
    }

    function echoBool (bool b) returns (bool) {
        return b;
    }

}

contract test {
    using who for who.person;

    who.person person;

    function test() {
        person = who.init("hoge");
    }

    function getName() returns (string) {
        // JUMP命令
        return person.getName();
    }

    function echoBool () returns (bool) {
        // DELEGATECALL命令
        return who.echoBool(true);
    }
}

// echoBool
// Transaction cost: 23267 gas.
// Execution cost: 1995 gas.
// Decoded:
//   bool: true
// getName
// Transaction cost: 22928 gas.
// Execution cost: 1656 gas.
// Decoded:
//  string: hoge

gas代検証

  • まとめ
    • ストレージの使用は必要最低限にする
    • データ量は必要最低限にする
    • structやarrayを使うよりもmappingをうまく使った方がリーズナブル
      • 割り切って mapping(byte32 => string) を一つ用意してsha3()でkeyを作って入れていく方がリーズナブル
    • loopは重いし、高価だから極力使わない
    • a[x][y]みたいな呼び出しを何度もやるより、var v = a[x][y]で変数化してvを使い回す方がリーズナブル
  • データ量
pragma solidity ^0.4.11;

contract gasTestInputChange {
    string value;

    function setString(string _value){
        value = _value;
    }

    function getString() returns (string) {
        return value;
    }
}

// input "aaa"
// setString
//   Transaction cost: 42677 gas.
//   Execution cost: 20701 gas.
// getString
//   Transaction cost: 22268 gas.
//   Execution cost: 996 gas.

// input "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
// setString
//   Transaction cost: 710010 gas.
//   Execution cost: 623010 gas.
// getString
//   Transaction cost: 27568 gas.
//   Execution cost: 6296 gas.
  • ストレージとストラクとマッピング
pragma solidity ^0.4.11;

contract gasTestMultistorage {
    string value1;
    string value2;
    string value3;

    function setString(string _value1, string _value2, string _value3){
        value1 = _value1;
        value2 = _value2;
        value3 = _value3;
    }

    function getString() returns (string, string, string) {
        return (value1, value2, value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85273 gas.
//   Execution cost: 61889 gas.
// getString
//   Transaction cost: 23938 gas.
//   Execution cost: 2666 gas.

contract gasTestStruct1 {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    value v;

    function setString(string _value1, string _value2, string _value3){
        v = value({
            value1: _value1,
            value2: _value2,
            value3: _value3
        });
    }

    function getString() returns (string, string, string) {
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85405 gas.
//   Execution cost: 62021 gas.
// getString
//   Transaction cost: 23956 gas.
//   Execution cost: 2684 gas.

contract gasTestStruct2 {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    value v;

    function setString(string _value1, string _value2, string _value3){
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
    }

    function getString() returns (string, string, string) {
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString
//   Transaction cost: 85291 gas.
//   Execution cost: 61907 gas.
// getString
//   Transaction cost: 23956 gas.
//   Execution cost: 2684 gas.

contract getTestStatuctMapping {
    struct value {
        string value1;
        string value2;
        string value3;
    }
    value sv;
    mapping (string => string) mv;

    function setStruct(string _value1, string _value2, string _value3) {
        sv.value1 = _value1;
        sv.value2 = _value2;
        sv.value3 = _value3;
    }

    function getStruct() returns (string _value1, string _value2, string _value3) {
        return (sv.value1, sv.value2 = _value2, sv.value3);
    }

    function setMapping(string _value1, string _value2, string _value3) {
        mv["value1"] = _value1;
        mv["value2"] = _value2;
        mv["value3"] = _value3;
    }

    function getMapping() returns (string _value1, string _value2, string _value3) {
        return (mv["value1"], mv["value2"], mv["value2"]);
    }

}

// input "aaa", "bbb", "ccc"
// setStruct
//    Transaction cost: 85291 gas.
//    Execution cost: 61907 gas.
// setMapping
//    Transaction cost: 85628 gas.
//    Execution cost: 62244 gas.
// getStruct
//    Transaction cost: 29070 gas.
//    Execution cost: 7798 gas.
// getMapping
//    Transaction cost: 24331 gas.
//    Execution cost: 3059 gas.
  • もう少し難しい構造
pragma solidity ^0.4.11;

contract gasTestMapping {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(address => value) values;

    function setString1(string _value1, string _value2, string _value3){
        values[msg.sender].value1 = _value1;
        values[msg.sender].value2 = _value2;
        values[msg.sender].value3 = _value3;
    }

    function setString2(string _value1, string _value2, string _value3){
        var v = values[msg.sender];
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
    }

    function setString3(string _value1, string _value2, string _value3){
        values[msg.sender] = value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        });
    }

    function getString1() returns (string, string, string) {
        return (values[msg.sender].value1,  values[msg.sender].value2, values[msg.sender].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[msg.sender];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//     Transaction cost: 85558 gas.
//     Execution cost: 62174 gas.
// setString2
//     Transaction cost: 55608 gas.
//     Execution cost: 32224 gas.
// setString3
//     Transaction cost: 55731 gas.
//     Execution cost: 32347 gas.
// getString1
//     Transaction cost: 24267 gas.
//     Execution cost: 2995 gas.
// getString2
//     Transaction cost: 24058 gas.
//     Execution cost: 2786 gas.

contract gasTestArray {
    struct value {
        string value1;
        string value2;
        string value3;
    }
    value[] values;
    uint valuesIndex;

    function getInitLength() returns (uint) {
        return values.length;
    }

    function setString1(string _value1, string _value2, string _value3){
        values.push(value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        }));
    }

    function getString1() returns (string, string, string) {
        return (values[0].value1,  values[0].value2, values[0].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[0];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 105844 gas.
//    Execution cost: 82460 gas.
// getString1
//    Transaction cost: 24459 gas.
//    Execution cost: 3187 gas.
// getString2
//    Transaction cost: 24122 gas.
//    Execution cost: 2850 gas.

contract gasTestMappingIndex {
    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(uint => value) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[valuesIndex].value1 = _value1;
        values[valuesIndex].value2 = _value2;
        values[valuesIndex].value3 = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[valuesIndex].value1 = _value1;
        values[valuesIndex].value2 = _value2;
        values[valuesIndex].value3 = _value3;
        valuesIndex++;
    }

    function setString2(string _value1, string _value2, string _value3){
        var v = values[valuesIndex];
        v.value1 = _value1;
        v.value2 = _value2;
        v.value3 = _value3;
        valuesIndex++;
    }

    function setString3(string _value1, string _value2, string _value3){
        values[valuesIndex] = value({
           value1: _value1,
           value2: _value2,
           value3: _value3
        });
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[valuesIndex].value1,  values[valuesIndex].value2, values[valuesIndex].value3);
    }

    function getString2() returns (string, string, string) {
        var v = values[valuesIndex];
        return (v.value1, v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 105762 gas.
//    Execution cost: 82378 gas.
// setString2
//    Transaction cost: 90563 gas.
//    Execution cost: 67179 gas.
// setString3
//    Transaction cost: 90686 gas.
//    Execution cost: 67302 gas.
// getString1
//    Transaction cost: 24424 gas.
//    Execution cost: 3152 gas.
// getString2
//    Transaction cost: 24115 gas.
//    Execution cost: 2843 gas.


contract gasTestMappingSha3 {

    mapping(bytes32 => string) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex, "value1")] = _value1;
        values[sha3(valuesIndex, "value2")] = _value2;
        values[sha3(valuesIndex, "value3")] = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex, "value1")] = _value1;
        values[sha3(valuesIndex, "value2")] = _value2;
        values[sha3(valuesIndex, "value3")] = _value3;
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[sha3(valuesIndex, "value1")],  values[sha3(valuesIndex, "value2")] , values[sha3(valuesIndex, "value3")] );
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 76308 gas.
//    Execution cost: 52924 gas.
// getString1
//    Transaction cost: 24753 gas.
//    Execution cost: 3481 gas.

contract gasTestMappingSha3Struct {

    struct value {
        string value1;
        string value2;
        string value3;
    }

    mapping(bytes32 => value) values;
    uint valuesIndex;

    function preSetString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)].value1 = _value1;
        values[sha3(valuesIndex)].value2 = _value2;
        values[sha3(valuesIndex)].value3 = _value3;
    }

    function setString1(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)].value1 = _value1;
        values[sha3(valuesIndex)].value2 = _value2;
        values[sha3(valuesIndex)].value3 = _value3;
        valuesIndex++;
    }

    function setString2(string _value1, string _value2, string _value3){
        values[sha3(valuesIndex)] = value({
            value1 : _value1,
            value2 : _value2,
            value3 : _value3
        });
        valuesIndex++;
    }

    function getString1() returns (string, string, string) {
        return (values[sha3(valuesIndex)].value1,  values[sha3(valuesIndex)].value2 , values[sha3(valuesIndex)].value3 );
    }

    function getString2() returns (string, string, string) {
        var v = values[sha3(valuesIndex)];
        return (v.value1,  v.value2, v.value3);
    }
}

// input "aaa", "bbb", "ccc"
// setString1
//    Transaction cost: 106096 gas.
//    Execution cost: 82712 gas.
// setString2
//    Transaction cost: 105792 gas.
//    Execution cost: 82408 gas.
// getString1
//     Transaction cost: 24715 gas.
//     Execution cost: 3443 gas.
// getString2
//     Transaction cost: 24200 gas.
//     Execution cost: 2928 gas.
  • storage/memory/stack
pragma solidity ^0.4.11;

contract gasTestLocation {

    byte strg;

    function writeStorage(uint8 _value) {
        strg = byte(_value);
    }

    function writeMem(uint8 _value) {
        bytes memory mmry = new bytes(1);
        mmry[0] = byte(_value);
    }

    function writeStack(uint8 _value) {
        byte stck;
        stck = byte(_value);
    }

    function ReadStorage() returns (uint8) {
        return uint8(strg);
    }

    function writeReadStorage(uint8 _value) returns (uint8) {
        strg = byte(_value);
        return uint8(strg);
    }

    function writeReadMem(uint8 _value) returns (uint8) {
        bytes memory mmry = new bytes(1);
        mmry[0] = byte(_value);
        return uint8(mmry[0]);
    }

    function writeReadStack(uint8 _value) returns (uint8) {
        byte stck;
        stck = byte(_value);
        return uint8(stck);
    }

}

// input 1
// writeStorage
//    Transaction cost: 41788 gas.
//    Execution cost: 20324 gas.
// writeMem
//    Transaction cost: 21867 gas.
//    Execution cost: 403 gas.
// writeStack
//    Transaction cost: 21657 gas.
//    Execution cost: 193 gas.
// readStorage
//    Transaction cost: 21685 gas.
//    Execution cost: 413 gas.
// writeReadStorage
//    Transaction cost: 42008 gas.
//    Execution cost: 20544 gas.
// writeReadMem
//    Transaction cost: 22090 gas.
//    Execution cost: 626 gas.
// writeReadStack
//    Transaction cost: 21767 gas.
//    Execution cost: 303 gas.
  • ループ検証
pragma solidity ^0.4.11;

contract gasTestMemory {

    function mem10() {
        bytes memory v = new bytes(10);
    }

    function mem100() {
        bytes memory v = new bytes(100);
    }

    function mem1000() {
        bytes memory v = new bytes(1000);
    }

    function mem10000() {
        bytes memory v = new bytes(10000);
    }

    function mem100000() {
        bytes memory v = new bytes(100000);
    }

    function mem1000000() {
        bytes memory v = new bytes(1000000);
    }

    function mem10000000() {
        bytes memory v = new bytes(10000000);
    }

    function mem100000000() {
        bytes memory v = new bytes(100000000);
    }

    function init(bytes b, uint size) private {
        for (uint i = 0; i < size; i++) {
            b[i] = byte(0);
        }
    }

    function memInit10() {
        bytes memory v = new bytes(10);
        init(v, 10);
    }

    function memInit100() {
        bytes memory v = new bytes(100);
        init(v, 100);
    }

    function memInit1000() {
        bytes memory v = new bytes(1000);
        init(v, 1000);
    }

}

// mem10
//    Transaction cost: 21665 gas.
//    Execution cost: 393 gas.
// mem100
//    Transaction cost: 21555 gas.
//    Execution cost: 283 gas.
// mem1000
//    Transaction cost: 21599 gas.
//    Execution cost: 327 gas.
// mem10000
//    Transaction cost: 21687 gas.
//    Execution cost: 415 gas.
// mem100000
//    Transaction cost: 21577 gas.
//    Execution cost: 305 gas.
// mem1000000
//    Transaction cost: 21643 gas.
//    Execution cost: 371 gas.
// mem10000000
//    Transaction cost: 21621 gas.
//    Execution cost: 349 gas.
// mem100000000
//    Transaction cost: 21709 gas.
//    Execution cost: 437 gas.
// memInit10
//    Transaction cost: 23331 gas.
//    Execution cost: 2059 gas.
// memInit100
//    Transaction cost: 36796 gas.
//    Execution cost: 15524 gas.
// memInit1000
//    Transaction cost: 171970 gas.
//    Execution cost: 150698 gas.

stack検証

  • まとめ
    • スタックサイズで怒られた場合は関数を小分けにする。
    • modifierもスタックを消費する場合がある
      • この仕様が良くわからない。
pragma solidity ^0.4.11;

contract gasTestStack {

    function args(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8,
        int _a9,
        int _a10,
        int _a11,
        int _a12,
        int _a13,
        int _a14,
        int _a15,
        int _a16,
        int _a17) {
        // error
    }

    function vars() {
        int _a1;
        int _a2;
        int _a3;
        int _a4;
        int _a5;
        int _a6;
        int _a7;
        int _a8;
        int _a9;
        int _a10;
        int _a11;
        int _a12;
        int _a13;
        int _a14;
        int _a15;
        int _a16;
        int _a17;
        // error
    }

    function rets() returns(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8,
        int _a9,
        int _a10,
        int _a11,
        int _a12,
        int _a13,
        int _a14,
        int _a15,
        int _a16,
        int _a17) {
        // error
    }


    function argsRets(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            var i = 1;
            // error
    }

    modifier m1(int _ma1) {
        int _l = 0;
        _;
    }

    function mod1(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m1(_a1) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // OK
    }

    modifier m2(int _ma1) {
        int _v = 1;
        _;
    }

    function mod2(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m1(_a1) m2(_a2) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // error
    }

    modifier m3(int _ma1) {
        int _i = 1;
        int _j = 1;
        _;
    }

    function mod3(
        int _a1,
        int _a2,
        int _a3,
        int _a4,
        int _a5,
        int _a6,
        int _a7,
        int _a8
        ) m3(_a1) returns(
        int,
        int,
        int,
        int,
        int,
        int,
        int,
        int) {
            // ok
    }
}