Hardhatで始めるスマートコントラクト開発
Hardhatとは
HardhatはSolidtyの開発環境です。 コントラクトのテストをローカルで実行できたり、果てはコントラクトのデプロイまで、Solidityでスマートコントラクトを開発するのに便利なツールです。 個人的な推しポイントはデバッグの簡単さです。 EVM上のスタックトレースの表示やプリントでバッグなどの機能が便利です。
似たようなものとしては、Truffle Suiteが該当します。
使ってみる
今回はHardhatの環境を立ち上げてみます。 開発が始められるところまでがゴールです。
インストール
$ mkdir hardhat $ cd hardhat $ npm init -y $ npm install --save-dev hardhat
プロジェクトの作成
$ npx hardhat 888 888 888 888 888 888 888 888 888 888 888 888 888 888 888 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888 888 888 "88b 888P" d88" 888 888 "88b "88b 888 888 888 .d888888 888 888 888 888 888 .d888888 888 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b. 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888 ? Welcome to Hardhat v2.9.9 ? ✔ What do you want to do? · Create a basic sample project ✔ Hardhat project root: · /path/to/project/of/hardhat ✔ Do you want to add a .gitignore? (Y/n) · y ✔ Help us improve Hardhat with anonymous crash reports & basic usage data? (Y/n) · false ✔ 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
これで先程作成したhardhat
ディレクトリにファイルが展開されます。
hardhat ├── README.md ├── contracts │ └── Greeter.sol # コントラクト ├── hardhat.config.js # 設定ファイル ├── package-lock.json ├── package.json ├── scripts │ └── sample-script.js # デプロイ用のスクリプト └── test └── sample-test.js # テストコード
今回はサンプルプロジェクトを作成するように選択したので、Greeterというコントラクトが用意されています。
コントラクトの中身
//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); // Hardhatのローカルチェーンで実行した場合にのみ表示される。 greeting = _greeting; } }
Solidityについては今回は深く触れませんが、以下のような機能を持ったコントラクトです。
- greet()を呼び出すとコントラクト内に保存された文字列が取得できる。
- setGreeting()を呼び出すと文字列をセットできる。
テストを回してみる
$ npx hardhat test Downloading compiler 0.8.4 Compiled 2 Solidity files successfully 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 (669ms) 1 passing (671ms)
無事にテストが通ったみたいです。
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!"); // トランザクションが通るまで待機 await setGreetingTx.wait(); // 値が変わっているか検証 expect(await greeter.greet()).to.equal("Hola, mundo!"); }); });
テストコードはJavaScriptで書かれています。 テストフレームワークはMochaとChaiの拡張です。 ethereumクライアントはether.jsです。
テストコードとしては、ブロックチェーン上にデプロイされたコントラクトをether.jsクラアントを使用して呼び出し、その結果を検証するといった形です。
テストはHardhatのローカルで起動可能なブロックチェーンを利用しています。 なのでガス代は無料で使い放題です(お得!)。 ブロックチェーンはテスト起動時に初期化されるので気をつけてください。
デプロイする
ノードの起動
今回はHardhatに付属しているブロックチェーン上にデプロイします。 これは外部のチェーンとは関係ない独立した開発用のチェーンです。
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/ Accounts ======== WARNING: These accounts, and their private keys, are publicly known. Any funds sent to them on Mainnet or any other live network WILL BE LOST. Account #0: 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (10000 ETH) Private Key: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa . . . Account #19: 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB (10000 ETH) Private Key: 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb WARNING: These accounts, and their private keys, are publicly known. Any funds sent to them on Mainnet or any other live network WILL BE LOST.
ノードが起動しました。
127.0.0.1:8545
がノードのアドレスとポートです。
起動時に20個のアドレスが払い出されています。 このPrivateKeyを使用すればHardhatのローカルチェーンで活動ができます。 このアドレス他の人に知られているのでメインネットなどで使い回さないように気をつけてください。
最初は10000 ETH持っています。 これは、ローカルなので意味はないですが、メインネットでこれだけ持っていたら一生ガス代に困りませんね...
デプロイする
$ npx hardhat run --network localhost scripts/sample-script.js Greeter deployed to: 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
無事デプロイされました。
--network
オプションを指定することで、設定ファイルに書かれた任意のネットワークに切り替えて実行できます。
web3_clientVersion (2) eth_accounts eth_chainId eth_blockNumber eth_chainId (2) eth_estimateGas eth_getBlockByNumber eth_feeHistory eth_sendTransaction Contract deployment: Greeter Contract address: 0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC Transaction: 0xDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD From: 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Value: 0 ETH Gas used: 497026 of 497026 Block #1: 0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE console.log: Deploying a Greeter with greeting: Hello, Hardhat! eth_chainId eth_getTransactionByHash eth_chainId eth_getTransactionReceipt
色々表示されていますね。 すべてノードのAPIコールのログです。
eth_sendTransaction
の部分にトランザクションの詳細が表示されています。
デプロイスクリプト
// このスクリプトはhardhatコマンドを通して実行する必要があります。 const hre = require("hardhat"); async function main() { // Hardhatは実行前にコンパイルを行ってくれます。 // 明示的に行いたい場合は以下の用にしてください。 // await hre.run('compile'); const Greeter = await hre.ethers.getContractFactory("Greeter"); const greeter = await Greeter.deploy("Hello, Hardhat!"); // コントラクトのデプロイ await greeter.deployed(); // コントラクトのアドレスを表示 console.log("Greeter deployed to:", greeter.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
デプロイスクリプトは上記のようなものです。 今回はデプロイだけが書かれていますが、コントラクトの初期設定などもここに書くと便利です。
まとめ
Hardhatを使用することで簡単にSolidityの開発環境を用意することができました。 また、機会があれば簡単なコントラクトの開発の記事も書いてみようと思います。