電通国際情報サービス オープンイノベーションラボの比嘉です。
今回のテーマは、スマートコントラクト。スマートコントラクトとはブロックチェーン上にデプロイされるプログラムのことで、今回は、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.taichi (Shodoで執筆されました)