読者です 読者をやめる 読者になる 読者になる

Develop with pleasure!

福岡でCloudとかBlockchainとか。

Solidityで記述するEthereumのスマートコントラクト

Ethereum Solidity

Ethereumを使ったスマートコントラクト(Solidity版)の作り方について見てみる。

Introduction to Smart Contracts — Solidity 0.4.3-develop documentation

シンプルなスマートコントラクト

ストレージ

Solidityにおけるコントラクトとは、Ethereumのブロックチェーン上の特定のアドレスに紐付けられたコード(ファンクション)とデータのコレクションを指す。

contract SimpleStorage {
    uint storedData;

    function set(uint x) {
        storedData = x;
    }

    function get() constant returns (uint retVal) {
        return storedData;
    }
}

↑はstoreDataという変数にデータをセット及び取得するコントラクトになり、storeDataに格納されたデータ=Ethereumのブロックチェーン上にストアされるデータとなる。

オレオレ通貨を作るサンプル

↓のコントラクトは一番シンプルな暗号通貨のフォームを実装したもの。 コントラクトを作成したユーザのみが自由にコインを生成することができ、誰もがユーザ名やパスワードを登録することなくEthereumのキーペアだけでお互いにコインを送信できる。

contract Coin {
    // public宣言することで外部から参照可能に
    address public minter; // 通貨を発行できるアドレス
    mapping (address => uint) public balances; // 通貨の残高

    event Sent(address from, address to, uint amount);

    // コントラクトが作成された時にだけ実行されるコンストラクタ
    // コントラクトの作成者のアドレスを通貨発行可能なアドレスとして登録
    function Coin() {
        minter = msg.sender;
    }

    // 通貨のチャージ(minterのアドレス以外でのチャージは不可)
    function mint(address receiver, uint amount) {
        if (msg.sender != minter) return;
        balances[receiver] += amount;
    }

    // 通貨の送信(送信とはいってもbalancesストレージの残高を更新しているだけ)
    function send(address receiver, uint amount) {
        if (balances[msg.sender] < amount) return;
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        Sent(msg.sender, receiver, amount);
    }
}

コンストラクタ

↑のfunction Coin()は特別な関数で、コンストラクタとして動作する。コンストラクタはコントラクトの作成時に一度だけ実行され、その後実行されることはない。そのため、コンストラクタCoin()は、コントラクトを作成したユーザのアドレスを恒久的に保存していることになる。

msgグローバル変数で、ブロックチェーンへのアクセスを許可するいくつかのプロパティをが含まれている。
msg.senderは、この関数を呼び出したアドレスを指す。なので、コンストラクタ内のmsg.senderはこのコントラクトを作成したアドレスを指すことになる。

publicキーワード

publicを宣言したフィールドには、そのフィールドの今の値にアクセスするための↓のようなfunctionが自動的に生成される。

function minter() returns (address) { return minter; }
mapping

mapping (address => uint) public balances; で使われているmappingというデータタイプは、ハッシュテーブルのようなもので、初期状態ではハッシュテーブルに存在しうる全てのキーの値として0がマップされている。この場合アドレスがハッシュテーブルのキーになっている。

明示的に初期化してないのに、いきなり

balances[receiver] += amount;

とできるのは、初期状態として0がセットされてるから成立してる。

こういう思想でできてるので、全てのキーのリストを取得することはできないし、値の一覧を取得するといったこともできない。実際にキー自体がストアされているわけでもないし、どれだけの値がセットされているのかも分からないので、JavaのHashTableでいうところのkeys()やvalues()といった使い方はできない。

またbalancesもpublic宣言されているので、こちらも↓のようなfunctionが自動生成される。
データタイプがmappingなので、balances自体を返すのではなく、アドレスを指定してそのアドレスの残高を返すアクセッサになる。

function balances(address _account) returns (uint balance) {
    return balances[_account];
}

event

event Sent(address from, address to, uint amount);の部分は、function sendで発生するイベントを宣言しており、ブロックチェーン上で発生するこれらのイベントは、予めリスナーとして登録されたアプリケーションでウォッチすることができる。イベントが発生するとリスナーはFrom、To、Amountといった引数を受け取り簡単にトランザクションをトラッキングすることができる。

イベントリスナーは↓のように設定できる。

Coin.Sent().watch({}, '', function(error, result) {
    if (!error) {
        console.log("Coin transfer: " + result.args.amount +
            " coins were sent from " + result.args.from +
            " to " + result.args.to + ".");
        console.log("Balances now:\n" +
            "Sender: " + Coin.balances.call(result.args.from) +
            "Receiver: " + Coin.balances.call(result.args.to));
    }
}

このコントラクトを使ってあるアドレスにコインを送った場合、ブロックチェーンエクスプローラでそのアドレスを参照してもそこには何も表示されていない。これはコインを送って残高を更新しても、それはこのコントラクトのストレージに格納されているため。ただ、eventを利用することで新しいコインの残高をトラッキングするブロックチェーンエクスプローラを実装することはできる。

所感

  • contractがクラスっぽいけど、そのプロパティがブロックチェーン上に永続化されるこの構成がなんかキモい。
    クラスなんだけどプラットフォームとしてみると1つのオブジェクトしか存在しない状態で、一意にアクセス方法があるグローバル変数いじってるようにも思える。
  • contractを書く言語を既存言語のDSLなどでなく、専用言語にしたのはやれることを制限するため?ランタイムを小さくしておきたいから?
  • ワールドコンピュータとか言われるけどEVMというサンドボックス環境で実行されるので、できることは制限されるよね。