電通総研 テックブログ

電通総研が運営する技術ブログ

スマートコントラクト入門

電通国際情報サービス オープンイノベーションラボの比嘉です。

今回のテーマは、スマートコントラクト。スマートコントラクトとはブロックチェーン上にデプロイされるプログラムのことで、今回は、Gethはじめましたの続きになります。 まだ、上記の記事をやってない方は、お手数をおかけしますが先に上記の記事をやってください。GethはEthereumクライアントの中で公式に推奨されているクライアントです。

Gethの再起動

上記記事で、Gethのメインプロセスを終了させた方は、もう一度再起動しましょう。

$ cd private_net
$ geth --networkid 1203 --nodiscover --datadir .

別のターミナルを立ち上げて、メインプロセスにコンソールで接続します。

$ cd private_net
$ geth attach geth.ipc

採掘が止まっている場合は、採掘を開始しましょう。

> eth.mining
false
> miner.start()
null

Solidityのインストール

Ethereumでスマートコントラクトを書くためのプログラミング言語としては、Solidity, Vyper, Fe, lllなどがありますが、よく使われているのはSolidityです。そこで、今回はSolidityをインストールします。 Solidityをインストールするための公式サイトはこちら

macOSの場合

Homebrewを事前にインストールし、次のコマンドを実行しましょう。

$ brew update
$ brew upgrade
$ brew tap ethereum/ethereum
$ brew install solidity
$ brew linkapps solidity

brew updateを実行するときにError: homebrew-core is a shallow clone.のエラーが、起きる場合があります。その場合はメッセージに、書いてあるとおりに、gitコマンドを実行しましょう。 インストールがうまくいっていれば、次のコマンドが成功します。

$ solc --version

Windowsの場合

ダウンロードサイトから、solc-windows.exeを落としてインストールしましょう。

スマートコントラクトのコード

最初のスマートコントラクトとして、数値をスマートコントラクトの状態変数に格納したり、取り出したりするプログラムを書いてみましょう。

NumberRegister.sol

pragma solidity ^0.8.10;

contract NumberRegister {
    uint num;

    function set(uint _num) public {
        num = _num;
    }

    function get() public view returns (uint) {
        return num;
    }
}

Solidityの細かい文法については、今後の記事で取り上げるので、細かいことは気にしなくて大丈夫です。このNumberRegiseter.solをprivate_netのディレクトリに保存しましょう。

solc

SolidityのプログラムをEthereumで実行できるようにするには、solcコンパイルします。コンパイルとは、NumberRegiseter.solのような人間が見て理解しやすいコードをコンピューターが実行できるように変更することです。

もう一つ、ターミナルを立ち上げて、solcを実行しましょう。

$ cd private_net
$ solc --abi --bin NumberRegister.sol

次のような出力が表示されたはずです。

======= NumberRegister.sol:NumberRegister =======
Binary:
608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220348a0bef0ed2915311a28c4b28b351b950a27275e260ed0a915108c3b0257d2c64736f6c634300080a0033
Contract JSON ABI
[{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_num","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]

Binaryのところに書いてある6080ではじまり0033で終わる文字列の先頭に0xを付加した上で、シングルクォートでくくり、Gethのコンソールに貼り付けます。BinaryはEVM(Ethereumの仮想マシン)が理解できるバイトコードです。

> bin = '0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806360fe47b11461003b5780636d4ce63c14610057575b600080fd5b610055600480360381019061005091906100c3565b610075565b005b61005f61007f565b60405161006c91906100ff565b60405180910390f35b8060008190555050565b60008054905090565b600080fd5b6000819050919050565b6100a08161008d565b81146100ab57600080fd5b50565b6000813590506100bd81610097565b92915050565b6000602082840312156100d9576100d8610088565b5b60006100e7848285016100ae565b91505092915050565b6100f98161008d565b82525050565b600060208201905061011460008301846100f0565b9291505056fea2646970667358221220348a0bef0ed2915311a28c4b28b351b950a27275e260ed0a915108c3b0257d2c64736f6c634300080a0033'

同様にContract JSON ABIもコンソールに貼り付けましょう。ABIはスマートコントラクトの入出力の仕様になります。

> abi = [{"inputs":[],"name":"get","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_num","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"}]

ここで定義した bin と abi は、この後で使うので覚えておいてください。binはEVM(Ethereumの仮想マシン)が理解できるバイトコード、abiはスマートコントラクトの入出力の仕様です。

スマートコントラクトのデプロイ

先ほどのスマートコントラクトをEthereumネットワークにデプロイしましょう。

> cnt = eth.contract(abi).new({from: eth.accounts[0], data: bin})
Error: authentication needed: password or unlock
    at web3.js:6347:37(47)
    at web3.js:5081:62(37)
    at web3.js:3021:48(134)
    at <eval>:1:28(16)

Error: authentication needed: password or unlockが発生した場合は、アカウントをunlockAcount()しましょう。トランザクションを実行する場合は、アカウントをunlockAccount()する必要があります。このエラーは何度も見ることになるので、どう対応すれば良いか体で覚えましょう。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x29faad1bb68151278c47df617766bf045c9b2b00
Passphrase: 
true
> cnt = eth.contract(abi).new({from: eth.accounts[0], data: bin})
{
  abi: [{
      inputs: [],
      name: "get",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "set",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: undefined,
  transactionHash: "0x13658235f5bff4afefd57b504a048f5c2bc37241bacfd9ce58177627aa1882b2"
}

cntの中身のaddressがundefinedになっていますね。これは、このスマートコントラクトのデプロイがまだ終わっていないことを表しています。Gethのメインプロセスのログで採掘がきちんと行われていることを確認したら、cnt.addressを確認しましょう。

> cnt.address
"0x102118ad7f8bede0158d2353660f63ea5780f966"

スマートコントラクトに、アクセスするにはABI(スマートコントラクトの入出力の仕様)とaddressが必要です。それでは、NumberRegisterコントラクトを作りましょう。

> cnt2 = eth.contract(abi).at(cnt.address)
{
  abi: [{
      inputs: [],
      name: "get",
      outputs: [{...}],
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [{...}],
      name: "set",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: "0x102118ad7f8bede0158d2353660f63ea5780f966",
  transactionHash: null,
  allEvents: function(),
  get: function(),
  set: function()
}

スマートコントラクトの状態を変更しない場合は、トランザクションなしで実行できます。それでは、NumberRegister.get()メソッドを呼び出してみましょう。 コントラクトオブジェクト.メソッド名.call()で呼びだすことができます。

> cnt2.get.call()
0

スマートコントラクトの状態を変更する場合、コントラクトオブジェクト.メソッド名.sendTransaction()を呼び出します。それでは、NumberRegister.set()を呼び出してみましょう。

> cnt2.set.sendTransaction(1, {from: eth.accounts[0]})
Error: authentication needed: password or unlock
    at web3.js:6347:37(47)
    at web3.js:5081:62(37)
    at web3.js:4137:41(57)
    at <eval>:1:36(10)

エラーが出ていない人は気にしなくて大丈夫です。上記のエラーが出た人は、unlockAccount()を呼び出しましょう。

> personal.unlockAccount(eth.accounts[0])
Unlock account 0x29faad1bb68151278c47df617766bf045c9b2b00
Passphrase: 
true
> txid = cnt2.set.sendTransaction(1, {from: eth.accounts[0]})
"0xf12e076acec31684bade23f3ca12c14396160a86464ce1298bbe10e795824f2e"

Gethのメインプロセスのログで採掘が動いていることを確認し、NumberRegister.get()を呼び出してみましょう。

> cnt2.get.call()
1

cnt2.set.sendTransaction()の第一引数で指定した値が、設定されていることが分かります。

まとめ

スマートコントラクトをコンパイルすると、ABI(入出力の仕様)とバイナリー(EVMが理解できるバイトコード)が出力されます。

バイナリーをEthereumネットワークにデプロイするとaddressが付加されます。

ABIとaddressを使ってコントラクトオブジェクトを作成して、スマートコンタクトにアクセスします。

スマートコントラクトの状態を変更しない場合、コントラクトオブジェクト.メソッド名.call()を呼び出します。

スマートコントラクトの状態を変更する場合、コントラクトオブジェクト.メソッド名.sendTransaction()を呼び出します。

今回はここまで。Gethを止めておきましょう。コンソールはCTL-D。メインプロセスはCTL-Cで止めます。正直、GethのコンソールはNode.jsのモジュールも使えないなど、かなり不便です。次回からは、開発環境としてHardhatを使います。

僕の書いたNFT関連の記事

執筆:@higa、レビュー:@sato.taichiShodoで執筆されました