Hardhat + Solidityでスマコン動かしてみた_チュートリアル編
「Applibot Advent Calendar 2021」3日目の記事になります。
Applibotでゲームサーバーサイドエンジニアとして働いている、川口です。
本記事では、最近個人的に興味があって勉強してるスマートコントラクトの実装に関する紹介をしたいと思います。
最近世の中でNFT関連のサービスとかニュースがにわかに増えてきたなあ、とか外国でP2E(play to earn)なサービスがはちゃめちゃに売れてるって聞いて、とか普段ゲーム作ってるエンジニアとしてどういう仕組みで動いてるんだろうなあと気になってやってみたってな感じです。
深堀ってアプリケーションの実装の方までやった時は、わからない横文字多過ぎてググってたら頭沸騰したので文字ベースでの理解諦めて、やってみた方がなんとなく雰囲気理解出来た気がしました(雑
ブロックチェーン?トランザクション?Ethereum?仮想通貨?な人はたくさん記事あるので色々見てもらえればと思います。
本当は作ってみたアプリケーションの全容紹介したいんですが、分量すごくなりそうだったので、
今回はほんとに超導入部の紹介だけに留めてます。
コード的には下記ページの日本語訳みたいな感じなので、知ってる人は退屈かもです
Hardhat公式
スマートコントラクト?
この辺の用語無数にあってかつそれとなく解説してる記事もたくさんあるので色々ググってもらえればと思うのですが、
ブロックチェーン上で動いてるプログラムモジュール
くらいに思って触れてました(記事によって表現異なってたりするので沼だった
- ブロックチェーン上のトランザクション(別のスマートコントラクトの処理)からも呼べたり
- デプロイし直せない(アドレスの概念)
- グローバルな開発環境でテスト/デプロイするのにテスト用仮想通貨が必要
- 処理実行するのにもコードに応じたお金かかる
- グローバルで書き込み処理が実行される順番が払う手数料次第
上記はほんの一例でしかないですが色々面白いルールがあるものの、
ライブラリがあったりテストコードかけたり普通にプログラムだなぁって感じだったので普段書いてる人はある程度の理解はすぐできるんじゃないかなって思います。
一旦チュートリアルやってみた記事として書いたので、道中わからない単語出てきたら都度ググってみてもらえたらと思います(周辺説明してると腱鞘炎なりそうで、すみません)
Solidity? Hardhat?
Solidity -- 開発言語
Hardhat -- 開発フレームワーク
SolidityはEthereumの開発者達が開発したプログラミング言語らしいです。
そのためEVM(Ethereum Virtual Machine)という専用のVM上で動作します。
参考にした本が扱ってるのはTruffle Suiteだったんですが、どうせなら本に書いてないやつで動かしてみようと思ってHardhatにしました。
開発フレームワークは他にも色々あるので興味ある人は色々みてみたらいいと思います。
Ethereum公式
目次
今回の内容ですがHardhat公式のチュートリアルに沿って、この辺りを順に紹介できればと思います。
- hardhatのinstall
- プロジェクト作成
- タスク一覧見てみる
- プロジェクトを見てみる
- コントラクトを見てみる
- テスト実行してみる
1. hardhatのinstall
適当に作業ディレクトリを作ります
そしてその中で下記実行
npm install --save-dev hardhat
完了してlsしてみると
- node_modules
- package-lock.json
こんな感じにファイルが配置されてると思います
2. プロジェクト作成
上記installが完了したら、
npx hardhat
これ実行すると「What do you want to do?」って聞かれるので、
Create a sample project
を選びます
↓この辺は適当にEnter連打
✔ Hardhat project root: · /Users/hoge/sample
✔ Do you want to add a .gitignore? (Y/n) · y
✔ Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)? (Y/n) · y
↓support中のnode.js version
Package exports for '/Users/hoge/sample/node_modules/uuid' do not define a valid '.' target
的なエラーはnodeのバージョン問題だったりするのでlocalのnodeは12以上の普通のにしておいてください(自分は13とかいう変なの入ってて最初動きませんでした)
Hardhat supports every currently maintained Node.js version, up to two months after its end-of-life. After that period of time, we will stop testing against it, and print a warning when trying to use it. At that point, we will release a new minor version.
We recommend running Hardhat using the current LTS Node.js version. You can learn about it here
Hardhat Node.js versions support
3. タスク一覧見てみる
終わったらもう一回下記実行
npx hardhat
するとこんな感じで実行可能なタスク一覧が表示されます。
4. プロジェクトを見てみる
まず2.の工程で出来上がったプロジェクトの雛形を見てみます
- contracts/Greeter.sol
コントラクトの処理が書かれてる(Solidity) - test/sample-test.js
テストコードが書かれてる - scripts/sample-scripts.js
デプロイscriptが入ってます(今回は使わない) - hardhat.config.js
configファイル。デプロイするネットワーク設定とかrequireとか書いたりします
Hardhat Configuration
5. コントラクトを見てみる
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Greeter {
string private greeting;
constructor(string memory _greeting) {
console.log("Deploying a Greeter with greeting:", _greeting);
greeting = _greeting;
}
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
greeting = _greeting;
}
}
説明する必要もなさそうなくらいな感じですが、
- constructor
普通のコンストラクタ
デプロイする時とかに処理したいものがあれば記述します。
Token系のクラスを継承してればデフォルトで色々詰めてくれてたりするのでそんな自分で書くことはないかも? - view?
関数修飾詞で、インスタンス変数(storage)の読み込みのみ可能で書き込みができなくなります。
他にはpureとかってのがあります。
関数修飾詞のルールに反してコードを書くとコンパイルエラーになります。 - memory?
変数をどの領域に格納するかを表していてmemoryとstorageがあります。
↓その名の通りの役割
memory: ブロックチェーン上には保存されない(処理実行時のみ保持される/コストかからない)
storage: ブロックチェーン上に保存される(コスト大)
- 関数外変数とsetメソッド
関数外定義した変数はデフォルトでstorage定義になります。
記述されてるsetメソッドを通してブロックチェーン上の値を書き換えているのですが、実際に使用される時は権限のバリデーションを入れてデプロイした人ーとか自分がcreateしたものだけを書き換えれるようにしたりするようです。
6. テスト実行してみる
npx hardhat test
これやるとtest/配下のscriptが実行されます。
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Greeter", function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
expect(await greeter.greet()).to.equal("Hello, world!");
const setGreetingTx = await greeter.setGreeting("Hola, mundo!");
// wait until the transaction is mined
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal("Hola, mundo!");
});
});
hardhatでデフォルトで用意されてるテストコードはこんな感じで、ライブラリimportしてdescribe内でテスト単位の説明とかテストコードの記述してーてな感じでわかりやすいですね。
↓この辺はおまじない(contractをテスト用にデプロイしてます)
const Greeter = await ethers.getContractFactory("Greeter");
const greeter = await Greeter.deploy("Hello, world!");
await greeter.deployed();
テスト内容に関しては前述のGreeterコントラクトを見ながら眺めてもらえればわかると思いますが、
- デプロイした時にconstructor経由でセットしたメッセージがgreet関数で取得されるか
- setGreeting関数でセットしたメッセージがgreet関数で取得されるか
をやってます(シンプル)
一点、setGreetingの時だけ.wait()してるのはトランザクションが発行(書き込み処理のため)されてそれが処理されるのを待つ必要があるためってな理解です。
※.greet()関数はネットワーク外からのアクセスでただのブロックチェーン上の値の読み出し
実行結果はこんな感じで、
$ npx hardhat test
Greeter
Deploying a Greeter with greeting: Hello, world!
Changing greeting from 'Hello, world!' to 'Hola, mundo!'
✓ Should return the new greeting once it's changed (863ms)
1 passing (871ms)
itで囲んでるテスト道中でエラーが出なかったりexpect結果がズレてなかったりしたらチェックマークがついてくれるってな感じです。
itを並べてったりしてテストを拡充していったり、beforeEachを使って各テストで共通の前処理(デプロイらへんの記述とか)をまとめたり、revertedWithを利用してエラーハンドリングのテストもちゃんと書けたりするので思ってたよりもだいぶ使いやすかったです。
まとめ
今回はざっとチュートリアル部分だけの解説でした。
もうちょっと踏み込んで仮想通貨を用いた簡単なWebアプリケーションを作ってみたのですが、
- ERC-20 / ERC-721といった規格の理解やそれに準じた実装
- スマートコントラクト同士の連携
- テストコードの拡充
- アプリケーションの役割の分離設計
などやることになり、これらも面白かったのでそれらはまたまとめる元気出た時にでも書いてみようかなと思います。
この記事を見た界隈の方が自分もやってみよーって気になって、もっと日本語記事が増えたら幸いです。(英語ばっかり久しぶりでめちゃ疲れた)
以上、
「Applibot Advent Calendar 2021」3日目の記事でした!
Discussion