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

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

簡単なコントラクトを作ってみる

今回はEthereumで簡単なコントラクトを作って実行してみます。

qiita.com
とか
inon29.hateblo.jp
を参考にしてます。内容もほとんど一緒です。



今回はbrowser-solidityでコントラクトを作成します。
browser-solidityはブラウザ上でSolidity言語でコントラクトを作成できるツールです。
インストールして使う方法もありますが、今回は手っ取り早く済ませるためにすでにサーバ上に用意されているものを使います。
Remix - Solidity IDE

作成するコントラクトは公式のサンプルにあるものをそのまま使います。
www.ethereum.org
上記ページの一番最初にあるコントラクトのソースをIDE上にコピペします。
ただ、サンプルはバージョンが古いのかこのままだとコンパイルが通らなかったので以下のように修正しました。
#といってもpublicつけただけですが。。。。

pragma solidity ^0.4.0;
contract MyToken {
    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function MyToken(
        uint256 initialSupply
        ) public {
        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
    }
    
    /* Send coins */
    function transfer(address _to, uint256 _value) public {
        require(balanceOf[msg.sender] >= _value);           // Check if the sender has enough
        require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for overflows
        balanceOf[msg.sender] -= _value;                    // Subtract from the sender
        balanceOf[_to] += _value;                           // Add the same to the recipient
    }
}

browser-solidityの画面の左上にあるプラスのアイコンをクリックしてMyToken.solという名前で新しいファイルを追加して
f:id:y_nakajo:20171110163328p:plain


そこに上のソースコードを貼り付けます。
f:id:y_nakajo:20171110163557p:plain

右側にコンパイルエラーとかの表示がなければコントラクトの完成です。
続いて作成したコントラクトを自分のプライベートチェーンにデプロイします。

ノードを起動するときに外部からのrpc呼び出しを受け付けられるようにオプションを指定してします。

geth --datadir="ether-private" --rpc --rpcport 8545 --rpcaddr "0.0.0.0" --rpccorsdomain "*" --maxpeers 0 --nodiscover --networkid=2017  console 2

必要なオプションと説明は以下の通り

  • --rpc rpcを有効にします
  • --rpcport rpcの受付ポート番号。任意の番号でOK。ここではbrowser-solidityがデフォルトで指定してくるポート番号を指定しています。
  • --rpcaddr rpcサーバのアドレス。"0.0.0.0"を指定するとよしなにやってくれるらしい。localhostでもOK
  • --rpccorsdomain rpcの接続元許可アドレス。今回はテストなので "*" にしてるけどこれだと誰でもアクセス可能なので注意。

その他詳しいことは、参考にした記事に乗っていたのでそちらを参照ください。
ノードを起動したら、browser-solidityをノードに接続します。
ちなみに、browser-solidityにhttpsで接続してる場合は、ローカルへの接続がうまくいかないのでhttpで接続しなおしてください。

右のタブから Rn > Environment > Web3 Providerを選択して、先ほど起動したノードへのアドレスを入力します。
#参考にした記事と接続の設定箇所が違っていて探すのに手間取った。。。
f:id:y_nakajo:20171110164630p:plain

接続が完了すると右側のタブの中のAccountのリストがローカルノードのものに変わります。
コントラクトのデプロイにはGasが必要になりますのでetherを持っているアカウントを選択します。
そして選択したアカウントをGeth コンソール上でアンロックします。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x858a07cb1a350064075d728499a037c6d0c2b82e
Passphrase: 
true

アカウントをアンロックできたら、browser-solidityの右側のタブのCreateに適当な数字(ここでは100000000)を入れてCreateボタンを押すと、コントラクトを作成するTransactionが発行されます。
f:id:y_nakajo:20171110170609p:plain

Gethコンソール上にもcontract作成ようトランザクションがsubmitされたという内容のログが出てると思います。
minerが動いてなければeth.pendingTransactionで今発行されたトランザクションの内容が確認できます。

> INFO [11-10|17:01:39] Submitted contract creation              fullhash=0x7ff61438662bb2c35bea55c6221ec41c06735750df8909826077ffd23cfd84c2 contract=0x21Ed0407C40540c2De2187E794D83911B1E1fA9d

> eth.pendingTransactions
[{
    blockHash: null,
    blockNumber: null,
    from: "0x858a07cb1a350064075d728499a037c6d0c2b82e",
    gas: 256240,
    gasPrice: 18000000000,
    hash: "0x7ff61438662bb2c35bea55c6221ec41c06735750df8909826077ffd23cfd84c2",
    input: "0x6060604052341561000f57600080fd5b60405160208061031883398101604052808051906020019091905050806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505061029a8061007e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806370a0823114610051578063a9059cbb1461009e575b600080fd5b341561005c57600080fd5b610088600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100e0565b6040518082815260200191505060405180910390f35b34156100a957600080fd5b6100de600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506100f8565b005b60006020528060005260406000206000915090505481565b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015151561014557600080fd5b6000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205401101515156101d257600080fd5b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254019250508190555050505600a165627a7a723058209ec20d123416b645a9ffed8d10a1a216b3b9a5d8c8679073325aba2d082bce2e002900000000000000000000000000000000000000000000000000000000000f4240",
    nonce: 6,
    r: "0x368e196dba1073afbbe57cba244622913a8c87ca4784e7672a71e733462ae9e5",
    s: "0x27a32017f34d405a38dc38e63c8c52e793c5efeef26059e378503a536a86a3b8",
    to: null,
    transactionIndex: 0,
    v: "0xfe5",
    value: 0
}]

ブロックに格納されるまではこのコントラクトは有効にならないのでminingしてブロックに格納してください。
コントラクトが有効になったら、browser-solidityの右下の部分の表示が変わり、このコントラクトの公開メソッドが表示されます。
f:id:y_nakajo:20171110171253p:plain

browser-solidityからコントラクトを操作することも出来ますが、今回はGethコンソールでこのコントラクトを操作してみます。
コントラクトを操作するためにこのコントラクトのオブジェクト(という言い方が正確なのかわからないけど。。。)を生成する必要があり、
そのためにはコントラクトのABIとaddressが必要です。

コントラクトのaddressはbrowser-solidityからコピーできます。右側のRunタブのなかのコントラクト名の横にあるコピーアイコンをクリックするとクリップボードにコピーされます。
f:id:y_nakajo:20171110172334p:plain

他にも、TransactionReceiptから探すこともできます。Gethコンソールで以下コマンドでレシートを表示できます。

> eth.getTransactionReceipt(eth.getBlock(223).transactions[0])
{
  blockHash: "0x171b3631d27ac5c7cdb3b5a0ab990b2067f6aa946d9448323d31d92d2258ba39",
  blockNumber: 223,
  contractAddress: "0x21ed0407c40540c2de2187e794d83911b1e1fa9d", <- これがaddress
  cumulativeGasUsed: 256240,
  from: "0x858a07cb1a350064075d728499a037c6d0c2b82e",
  gasUsed: 256240,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0x08d3a7a0fcf9d722e96654f0be411cdbde8969446756b2775e81852820aa8049",
  to: null,
  transactionHash: "0x7ff61438662bb2c35bea55c6221ec41c06735750df8909826077ffd23cfd84c2",
  transactionIndex: 0
}

コントラクト作成のtransactionが取り込まれたブロックナンバーは頑張って探してください。

次にコントラクトのABIですがこれはbrowser-solidityからしか取得できません。
browser-solidityの右側にあるCompileタブを開いてDetailsボタンを押します。
f:id:y_nakajo:20171110172939p:plain

表示されたコントラクトの詳細からINTERFACE - ABIのセクションを探してその横にあるコピーアイコンをクリックするとクリップボードにコピーされます。
f:id:y_nakajo:20171110172958p:plain

ABIはメソッド定義情報みたいなものです。コントラクトはコンパイルされるとEthereumのスクリプトに変換され、メソッド名などの情報は失われてしまうので、ABIはソースコードがないと生成できないようです。

これで必要な材料は揃ったのでGethコンソールからMyTokenへアクセスするためのオブジェクトを以下のように生成してメソッドを呼び出したりできます。

> receipt = eth.getTransactionReceipt(eth.getBlock(223).transactions[0])
{
  blockHash: "0x171b3631d27ac5c7cdb3b5a0ab990b2067f6aa946d9448323d31d92d2258ba39",
  blockNumber: 223,
  contractAddress: "0x21ed0407c40540c2de2187e794d83911b1e1fa9d",
  cumulativeGasUsed: 256240,
  from: "0x858a07cb1a350064075d728499a037c6d0c2b82e",
  gasUsed: 256240,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  root: "0x08d3a7a0fcf9d722e96654f0be411cdbde8969446756b2775e81852820aa8049",
  to: null,
  transactionHash: "0x7ff61438662bb2c35bea55c6221ec41c06735750df8909826077ffd23cfd84c2",
  transactionIndex: 0
}
> 
> myToken = eth.contract([{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]).at(receipt.contractAddress)
{
  abi: [{
      constant: true,
      inputs: [{...}],
      name: "balanceOf",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      constant: false,
      inputs: [{...}, {...}],
      name: "transfer",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      inputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "constructor"
  }],
  address: "0x21ed0407c40540c2de2187e794d83911b1e1fa9d",
  transactionHash: null,
  allEvents: function(),
  balanceOf: function(),
  transfer: function()
}

今回のコントラクトではToken残高の表示と送金できます。myToken.までうってTABキーを押すと呼び出し可能なメソッドの候補の表示や補完もしてくれます。
それでは2つのアカウントのToken残高の確認と別のアカウントへのTokenの送金をやってみます。

> myToken.balanceOf(eth.accounts[0])
1000000
> myToken.balanceOf(eth.accounts[1])
0
> myToken.transfer(eth.accounts[1], 100, {from: eth.accounts[0]})
"0xead3fbb0319a46caee4759b7c1e64d45ebbe0a10573e78e5890c08e18ebbbbd2"
>miner.start()
null
>miner.stop()
true
> myToken.balanceOf(eth.accounts[0])
999900
> myToken.balanceOf(eth.accounts[1])
100

注意点としては、MyToken.transferメソッドのようなcontractの状態を変えるメソッドを呼び出すときにはtransactionの発行が必要となります。そのため、どのアカウントからトランザクションを発行するかを指定する必要があるのでそれを第3引数で指定します。

myToken.transfer(eth.accounts[1], 100, {from: eth.accounts[0]})

この部分ですね。

また、トランザクションが発行されるので、miner.start()でトランザクションをブロックに入れるまで結果は反映されません。
どのメソッドがトランザクション発行が必要なのかどうかはabiで決まるようです。

> myToken.abi
[{
    constant: true,
    inputs: [{
        name: "",
        type: "address"
    }],
    name: "balanceOf",
    outputs: [{
        name: "",
        type: "uint256"
    }],
    payable: false,
    stateMutability: "view",
    type: "function"
}, {
    constant: false,
    inputs: [{
        name: "_to",
        type: "address"
    }, {
        name: "_value",
        type: "uint256"
    }],
    name: "transfer",
    outputs: [],
    payable: false,
    stateMutability: "nonpayable",
    type: "function"
}, {
    inputs: [{
        name: "initialSupply",
        type: "uint256"
    }],
    payable: false,
    stateMutability: "nonpayable",
    type: "constructor"
}]

このabiの定義の中でメソッドのstateMutabilitynonpayableとなっているものがメソッド呼び出しにトランザクションの発行が必要なもの、つまりcontractの状態を変更するものになります。