サルでもわかるEthereum,DAppsの使い方,作り方 その2 ~簡単なスマートコントラクトの実行編~

概要

実際にEthereumを触りながら、EthereumとDAppsに慣れていきましょうという記事です。
※筆者の勉強の備忘録を兼ねているので、間違い等あったらご連絡お願いいたします。

前編はこちら↓

サルでもわかるEthereum,DAppsの使い方,作り方 その1 ~送金をしてみる編~

※ローカルのブロックチェーンの作成や送金トランザクションなどを説明しています。

スマートコントラクトとは

スマートコントラクトの詳細についてはググってください。

簡単にいうと、Ethereumのネットワークで動くプログラムみたいな感じです。

そのプログラムはEthereum仮想マシン(EVM)というものの上で実行されます。そのためEVMがわかるコードに変換してあげる必要があります。

そのため、Solidity(コンパイラ)をインストールします。

Installing the Solidity Compiler — Solidity 0.4.21 documentation

sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc

今回使うSolidityのバージョンは以下の通り

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.23+commit.124ca40d.Linux.g++

Solidityについて詳しくは以下の通り

Ethereumネットワーク上では、このコントラクト・コードは「Ethereum Virtual Machine Code」または略して「EVM Code」と呼ばれる、バイトコードの形式で記述され処理されます。 このようなバイトコードの形式は低水準の機械言語であって、人間にとっては可読性が悪く開発の生産性も悪いものとなっています。そこでEthereumでは、可読性と生産性が高く、コントラクトを記述することに特化した高水準言語と、それを EVM Code に翻訳するためのコンパイラが幾つか開発されています。その代表的なものとして「Solidity」が挙げられます。コントラクト指向言語Solidity詳解 · Ethereum入門

なに言っているんだかさっぱりわからないっていう人は"コンパイラ"とかでググってください。

コントラクトを書いてみる

Solidityで書きます。

SolidityはJavaScriptライクな言語ですが、結構違います。

今回は以下のコードを使います。

HelloWorld.sol
pragma solidity ^0.4.13; // コンパイラのバージョン指定

contract HelloWorld { // クラスみたいな感じ
    string message; // プロパティみたいな感じ

    function setMessage(string _message) { // 関数みたいな感じ
        message = _message;
    }

    function getMessage() returns (string) { // 関数みたいな感じ
        return message;
    }
}

このコントラクトは簡単にいうと「1つのテキストを読み書きするだけのプログラム」です。

setMessage(string _message)は指定の文字列を保存します。

getMessage()は保存された文字列を返します。

説明だけ聞いてもわからないので、コンパイルしてデプロイして実行してみましょう。

コントラクトをコンパイルする

以下のようにコンパイルします。
おそらくWarningがでますが現時点では無視します。

solc HelloWorld.sol --bin --abi -o build

を実行すると

└─...
└─HelloWorld.sol
└─build
  └─HelloWorld.abi
  └─HelloWorld.bin

のようにファイルが出力されます。

--binオプションでバイナリ(EVMが実行できるコード)の出力を
--abiオプションでABI(Application Binary Interface)の出力をすることを指定します。
また-o buildbuildフォルダ内に以上を書き出すようにします。

ABIについてはEthereum Contract ABI · ethereum/wiki Wiki · GitHubを参考に。

solcのコマンドオプションの詳細はUsing the compiler — Solidity 0.4.21 documentationを参考に。

コントラクトをデプロイする

HelloWorld.abiとHelloWorld.binを使って自前のブロックチェーンにコントラクトをデプロイします。

geth --datadir "~/local_bc" consoleでgethを起動します。

gethの起動に関してはサルでもわかるEthereum,DAppsの使い方,作り方 その1 ~送金をしてみる編~ - Qiitaを参考にしてください。(--devオプションでブロックチェーンを作らないと、いろいろめんどうです。)

以下の"{}"内を埋めて実行してください。

※コントラクトをデプロイするアカウントはpersonal.unlockAccount()しておきましょう。

var bin = "0x" + "{HelloWorld.binのテキスト(コード)}";
var abi = {HelloWorld.abiのテキスト};
var contract = eth.contract(abi);
var HelloWorld = contract.new({
    from: {コントラクトをデプロイするアカウントのアドレス},
    data: bin,
    gas: 1000000 });

"{}"を埋めると以下のようになります。

var bin = '0x608060405234801561001057600080fd5b506102d7806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ce6d41de146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a7230582094862207ce9d134907da8e0b8746bdc93875afe5fac755e3e659d46a4b9f97360029';
var abi = [{"constant":false,"inputs":[{"name":"_message","type":"string"}],"name":"setMessage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"getMessage","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}];
var contract = eth.contract(abi);
HelloWorld = contract.new({ from: '0xa2fcc1fcb6e32c26e8d35c921e51ee29b02fd902', data: bin, gas: 1000000 });

gethに打ち込んでみると

> var bin = '0x608060405234801561001057600080fd5b506102d7806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ce6d41de146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a7230582094862207ce9d134907da8e0b8746bdc93875afe5fac755e3e659d46a4b9f97360029';
undefined
> var abi = [{"constant":false,"inputs":[{"name":"_message","type":"string"}],"name":"setMessage","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"getMessage","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"function"}];
undefined
> var contract = eth.contract(abi);
undefined
> HelloWorld = contract.new({ from: '0xa2fcc1fcb6e32c26e8d35c921e51ee29b02fd902', data: bin, gas: 1000000 });
INFO [05-01|09:42:42] Submitted contract creation              fullhash=0xb79036be0e72101a4562cd69fc2b420b4cd913da17a4143f3b18be0839ef83ee contract=0x491d3177a66bf4CB842BF71fB490ac5519bF20A6
{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "setMessage",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [],
      name: "getMessage",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: undefined,
  transactionHash: "0xb79036be0e72101a4562cd69fc2b420b4cd913da17a4143f3b18be0839ef83ee"
}

Submitted contract creationとなります。

現時点ではブロックチェーンに書き込まれていません。(address: undefinedとなっています)

書き込むためにはマイニングをする必要があります。(Ctrl+Cでマイニングを中断できます)

> miner.start()
INFO [05-01|09:45:59] Transaction pool price threshold updated price=18000000000
null
> INFO [05-01|09:45:59] Starting mining operation
INFO [05-01|09:45:59] Commit new mining work                   number=8 txs=1 uncles=0 elapsed=590.278µs
INFO [05-01|09:45:59] Successfully sealed new block            number=8 hash=81db64…d483d5
INFO [05-01|09:45:59] 🔨 mined potential block                  number=8 hash=81db64…d483d5
INFO [05-01|09:45:59] Commit new mining work                   number=9 txs=0 uncles=0 elapsed=486.063µs
WARN [05-01|09:45:59] Block sealing failed                     err="waiting for transactions"

コントラクトにアドレスが割り振られました。(これで実行できるようになりました)

> HelloWorld
{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "setMessage",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: false,
      inputs: [],
      name: "getMessage",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: "0x491d3177a66bf4cb842bf71fb490ac5519bf20a6",
  transactionHash: "0xb79036be0e72101a4562cd69fc2b420b4cd913da17a4143f3b18be0839ef83ee",
  allEvents: function(),
  getMessage: function(),
  setMessage: function()
}

コントラクトを実行する

setMessageで書き込んで、getMessageで取得できます。

まだ値がセットされていないので、getMessageをしても空文字しか返ってきません。

> HelloWorld.getMessage.call()
""

setMessagecallではなくsendTransactionをつかって実行します。これは、書き込みを行う処理のため、GAS(手数料)を払うためです。

> HelloWorld.setMessage.sendTransaction("test message", { from: eth.accounts[0] })
{略}

getMessageをすると、先ほど保存した文字列が返ってきます。

> HelloWorld.getMessage.call()
"test message"

このように

以下の2つの方法で関数を呼び出します。

{Contract}.{Function}.call({Arguments})
{Contract}.{Function}.sendTransaction({Arguments}, { from: {Address}, ... })

callとsendTransactionの違いはcontract design - What is the difference between a transaction and a call? - Ethereum Stack Exchangeがわかりやすいです。

以上の記事を要約すると、以下のようなイメージになります。

call: データの変更無し、読み取り専用、手数料無し
sendTransaction: Contractのデータを変更、ブロックチェーンの状態を変更、手数料必要

次回予告

次回は簡単なトークンの作り方編です。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.