どうも、次世代デジタル基盤開発事業部の土田です。
最近までLINE Blockchainを触ってましたが、Solidity関連のご相談も増えてますので、置いていかれないように頑張ります。 これまではTruffleSuiteで開発環境を構築してきましたが、今回はHardhatという開発環境を使ってみたいと思います。
TruffleSuiteで始めるDapps開発 - テコテック開発者ブログ
TruffleSuiteで始めるDapps開発2 - テコテック開発者ブログ
TruffleSuiteで始めるDapps開発3 - テコテック開発者ブログ
筆者環境
Windows10 WSL2(Windows Subsystem for Linux) Ubuntu 20.04 MetaMask Chrome Hardhat 2.10.1
Hardhatをインストール
公式ドキュメントをご参照下さい。npmもyarnも特に問題なくインストールできました。
早速起動
下記コマンドを実行し、サンプルプロジェクトを作成します。
npx hardhat
タスクとは
A task is a JavaScript async function with some associated metadata.
Hardhatで何かをする時にはタスクとして定義して実行する必要があるそうです。
サンプルプロジェクトで実行できるタスクは下記コマンドを実行すると確認できます。
npx hardhat
JavaScriptのサンプル作成時にはaccountsという独自タスクが定義されていたのですが、TypeScriptでプロジェクトを作成すると無かったので作成してみます。
タスクはルートディレクトリのhardhat.config.ts
に定義します。
import { HardhatUserConfig, task } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; import { HardhatRuntimeEnvironment } from "hardhat/types"; const accounts = async (args: string, hre: HardhatRuntimeEnvironment) => { const accounts = await hre.ethers.getSigners(); for (const account of accounts) { console.log(account.address); } }; task("accounts", "Prints the list of accounts", accounts); const config: HardhatUserConfig = { solidity: "0.8.9", }; export default config;
再度タスクを確認すると・・・
accountsが追加されていますね!
早速実行してみましょう。
npx hardhat accounts
デフォルトで用意されているアカウントを確認することができました。
ノード起動
ノード起動はデフォルトのタスクが用意されていますので、そのまま使いましょう。
npx hardhat node
ついでにMetaMaskで起動したノードに接続し、アカウントもインポートしてみます。
ChainIDはデフォルトで31337
だそうです。
無事、接続&インポート完了しました。
送金も試してみます。
問題なく送金できましたね!
MetaMaskで送金を行った場合、ノードを再起動すると下記エラーが発生することがありますので、MetaMaskのアカウントリセットをお試し下さい。
## Nonce too high. Expected nonce to be 0 but got X. Note that transactions can't be queued when automining.
参考
クリプトゾンビを進める
チャプター1の13まで進めます。
当時、特に出なかったエラーが表示されたので、備忘として残しておきます。
Different number of components on the left hand side (1) than on the right hand side (0).
0.6以降でpushの戻り値が変わったそうです。
• Syntax:
push(element)
for dynamic storage arrays do not return the new length anymore.
該当箇所を下記の通り修正します。
// 以下はエラー // uint id = zombies.push(Zombie(_name, _dna)) - 1; zombies.push(Zombie(_name, _dna)); uint id = zombies.length - 1;
コンパイルしてみる
コンパイルされたソース一式はルートディレクトリ直下のartifacts
フォルダに生成されます。
npx hardhat compile
また、abiからTypeScriptも生成されます。HardhatではデフォルトでTypeChainを採用しているようです。
生成後はルートディレクトリ直下のtypechain-types
に生成されます。
ソースコードはこんな感じ。
これで型安全にコントラクトの開発を行う準備ができました!
テストしてみる
テストコードの配置先のデフォルトはルートディレクトリ直下のtest
フォルダになります。今回はZombieFactory.ts
としてテストコードを作成しました。
import { expect } from "chai"; import { ethers } from "hardhat"; describe("ZombieFactory.sol", function () { async function deployZombieFactory() { const ZombieFactory = await ethers.getContractFactory("ZombieFactory"); const zombieFactory = await ZombieFactory.deploy(); return { zombieFactory }; } describe("createRandomZombie", function () { it("Verify that the name and DNA of the created zombie has 16 digits.", async function () { const { zombieFactory } = await deployZombieFactory(); const testZombieName = "test"; await zombieFactory.createRandomZombie(testZombieName); const zombie = await zombieFactory.zombies(0); expect(zombie.name).to.equal(testZombieName); expect(zombie.dna.toString().length).to.equal(16); }); }); describe("Events", function () { it("Should emit an event on createRandomZombie", async function () { const { zombieFactory } = await deployZombieFactory(); await expect(zombieFactory.createRandomZombie("test")).to.emit( zombieFactory, "NewZombie" ); }); }); });
実行してみます。
npx hardhat test
無事、テストも通りました!
また、未コンパイルの状態ではコンパイルとテストを合わせて実行するようでした。
デプロイしてみる
デプロイするコントラクトを定義します。scripts/deploy.ts
を修正しましょう。
import { ethers } from "hardhat"; async function main() { const ZombieFactory = await ethers.getContractFactory("ZombieFactory"); const zombieFactory = await ZombieFactory.deploy(); await zombieFactory.deployed(); console.log("ZombieFactory deployed to:", zombieFactory.address); } // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch((error) => { console.error(error); process.exitCode = 1; });
デプロイは下記コマンドを実行します。
npx hardhat run scripts/deploy.ts --network localhost
hardhat consoleでコントラクトを実行する
デプロイしたコントラクトをhardhat consoleでも確認してみます。
tsuchida@DESKTOP-PBCJR7M:~/workspace/hardhat-cryptzombie$ npx hardhat console --network localhost Welcome to Node.js v16.15.1. Type ".help" for more information. > const ZombieFactory = await ethers.getContractFactory('ZombieFactory') undefined > const zombiefactory = await ZombieFactory.attach("コントラクトアドレス") undefined > await zombiefactory.createRandomZombie("test") 略
hardhat nodeを実行中のターミナルにて、実行されていることが確認できます。
今回のトラブルまとめ
Different number of components on the left hand side (1) than on the right hand side (0).solidity(7364)View Problem
0.6以降で配列が参照を返すようになったため、要素数を別途取得する必要があります。
参考
error: ProviderError: Error: Transaction reverted without a reason string
hardhat consoleでコントラクトを実行した際に、下記のようなエラーが発生しました。この場合は、hardhat.config.ts
に設定を追加する必要があります。
> await zombiefactory.createRandomZombie('test') Uncaught: Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="Error: Transaction reverted without a reason string", method="estimateGas", transaction={"from":"アドレス","to":"アドレス","data":"データ","accessList":null}, error={"name":"ProviderError","code":-32603,"_isProviderError":true,"data":{"message":"Error: Transaction reverted without a reason string","data":"0x"}}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.6.8) at step (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23) at EthersProviderWrapper.<anonymous> (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:603:20) at checkError (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:78:20) at Logger.throwError (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/logger/src.ts/index.ts:273:20) at Logger.makeError (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/logger/src.ts/index.ts:261:28) { reason: 'Error: Transaction reverted without a reason string', code: 'UNPREDICTABLE_GAS_LIMIT', method: 'estimateGas', transaction: { from: 'アドレス', to: 'アドレス', data: 'データ', accessList: null }, error: ProviderError: Error: Transaction reverted without a reason string at HttpProvider.request (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/hardhat/src/internal/core/providers/http.ts:78:19) at AutomaticSenderProvider.request (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/hardhat/src/internal/core/providers/accounts.ts:351:34) at AutomaticGasProvider.request (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/hardhat/src/internal/core/providers/gas-providers.ts:136:34) at AutomaticGasPriceProvider.request (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/hardhat/src/internal/core/providers/gas-providers.ts:153:36) at BackwardsCompatibilityProviderAdapter.send (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/hardhat/src/internal/core/providers/backwards-compatibility.ts:36:27) at EthersProviderWrapper.send (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:48) at EthersProviderWrapper.<anonymous> (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/src.ts/json-rpc-provider.ts:601:31) at step (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:48:23) at Object.next (/home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:29:53) at /home/tsuchida/workspace/hardhat-cryptzombie/node_modules/@ethersproject/providers/lib/json-rpc-provider.js:23:71 }
対策抜粋
const config: HardhatUserConfig = { solidity: "0.8.9", networks: { localhost: { allowUnlimitedContractSize: true }, hardhat: { allowUnlimitedContractSize: true }, }, };
参考
おわり
これでひと通りHardhatに触れることが出来ました。
Truffle、Ganacheも簡単でしたがHardhatも同じくらい簡単ですし、プラグインも充実していてTypeScriptの開発が捗りそうで良さそうです。動作も軽くていい感じなので、今後はHardhatでやると思います。