見出し画像

Hardhatのチュートリアルを試してみた

hardhatのチュートリアルを試してみましたので、まとめます。

開発ツールをHardhatかfoundryで悩んだのですが、ドキュメントを読むとHardhatが触りやすそうなのとv3がリリース予定だったので事前に触ってみたいという理由で選びました。

違いについては、以下の記事が参考になりました!

早速、チュートリアルを読みながら試していきます。

Hardhatは、ローカルでスマートコントラクトやDapps(ブロックチェーンを実装したアプリの名称)を開発するための環境構築が出来ます。

この記事では、環境構築、コントラクトの作成&テスト、テストネットワークへのデプロイまでをやっていきます。

環境設定

HardhatはJavaScriptで書かれており、実行するにはNode.jsをインストールする必要があります。

はじめにNodeのバージョンを管理するnvmをインストールしていきます。

この記事では、Macのzshにインストールしていきます。
※WindowsやLinuxはドキュメントに記載されています。

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
nvm install 22
nvm use 22
nvm alias default 22
npm install npm --global # npmを最新バージョンにアップグレード


プロジェクトの作成

作業用のフォルダーを作成します。

mkdir hardhat-tutorial
cd hardhat-tutorial

Hardhatをインストールします。

npm init
npm install --save-dev hardhat

Hardhatのプロジェクトを作成していきます。

npx hardhat init

「Create an empty hardhat.config.js」でHardhatプロジェクトを作成します。

$ npx hardhat init
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.22.9 👷‍

? What do you want to do? …
  Create a JavaScript project
  Create a TypeScript project
  Create a TypeScript project (with Viem)
❯ Create an empty hardhat.config.js
  Quit

Hardhatのプロジェクトが作成されました。

Hardhatは、プラグインを導入して開発を効率化できます。
今回は、「hardhat-toolbox」というスマートコントラクトの開発に必要なものが揃っているプラグインを使用します。

ドキュメントの推奨プラグなので、Hardhatとセットで導入するのがいいと思います。

npm install --save-dev @nomicfoundation/hardhat-toolbox

hardhat.config.jsにインポートします。

require("@nomicfoundation/hardhat-toolbox");

ここまでがHardhatの環境構築でした。

コントラクトの作成

ここからERC20のトークンを作成していきます。
「contracts」フォルダーの中に、「Token.sol」ファイルを作成します。

コードをToken.solに貼り付けます。
コメントにコードの説明を記載しています。

せっかくなので、nameとsymbolをドキュメントと違うオリジナルに変えます。
name: Note Write Token
symbol: NWT

発行量は、1万枚とします。
totalSupply: 10000

//SPDX-License-Identifier: UNLICENSED
// SPDXライセンス識別子を指定します。

// Solidityコンパイラのバージョンを指定する。
// これによってバージョン0.8.0以上で、0.9.0未満だと動作することを保証する。
pragma solidity ^0.8.0;


// スマートコントラクトの構成要素
contract Token {
    // トークン名とシンボルを設定する。
    // 任意で変更してOK
    string public name = "Note Write Token";
    string public symbol = "NWT";

    // トークンの発行量を指定する。
    uint256 public totalSupply = 10000;

    // アドレスタイプの変数はEthereumアカウントを格納するために使用します。
    address public owner;

    // マッピング型の変数で、アドレスとアドレスに紐づいたトークン量を関連づけるために使います。
    mapping(address => uint256) balances;

    // Transferイベントはフロントエンドにコントラクトを実行したことを知らせる。
    event Transfer(address indexed _from, address indexed _to, uint256 _value);


    // コントラクトがデプロイされるときに最初の1回のみ実行される。
    constructor() {
        // トークンの発行量をコントラクトをデプロイするアカウントに割り当てる。
    //  デプロイしたアカウントにNWTトークンが10000割り当てている。
        balances[msg.sender] = totalSupply;

        // デプロイしたアドレスを変数に格納する。
        owner = msg.sender;
    }


    // 指定した量のトークンを送信する関数
    function transfer(address to, uint256 amount) external {
        // トランザクションの送信者が十分なトークンを持っているか確認します。
        // `持っていなければ、「amount, "Not enough tokens」を返す。
        require(balances[msg.sender] >= amount, "Not enough tokens");

        // 指定したトークン数を転送します。
        balances[msg.sender] -= amount;
        balances[to] += amount;

        // Transferイベントを実行して、フロントエンドに実行されたことを知らせる
        emit Transfer(msg.sender, to, amount);
    }

    // 実行したアドレスのトークン量を返す
    function balanceOf(address account) external view returns (uint256) {
        return balances[account];
    }
}

コントラクトをコンパイルします。

$ npx hardhat compile
Compiled 1 Solidity file successfully (evm target: paris).

コントラクトが正常にコンパイルされ、使用できる状態になりました。

コントラクトをテストする

スマートコントラクトを構築する際には、ユーザーの資産を守るために自動テストを作成することが非常に重要です。

Hardhatに用意されているローカルでテストするためのHardhat Networkを使っていきます。

「test」フォルダーを作成しフォルダー内に、「Token.js」を作成します。
Hardhatのテストスクリプトは、Javascriptで書いていきます。

以下のコードを貼り付けます。
コメントに説明を書いています。

const { expect } = require("chai");

// テストの構成要素
describe("Token contract", function () {

  // トークンを正常に発行できるかをテストする。
  it("Deployment should assign the total supply of tokens to the owner", async function () {

    // Hardhatのテスト用アカウントを取得し、変数に格納する。
    const [owner] = await ethers.getSigners();

    // Tokenコントラクトをデプロイする。
    const hardhatToken = await ethers.deployContract("Token");

    // デプロイしたアカウントのトークン残高を取得し、変数に格納する。
    const ownerBalance = await hardhatToken.balanceOf(owner.address);

    // 発行したトークン量とアカウントに付与されたトークン量が一致しているか確認する。
    expect(await hardhatToken.totalSupply()).to.equal(ownerBalance);
  });


  // Hardhatに用意された二つのアカウントにトークンを送信し正常に届くかをテストする。
  it("Should transfer tokens between accounts", async function() {
    // addr1とaddr2のアカウントを用意する。
    const [owner, addr1, addr2] = await ethers.getSigners();

    const hardhatToken = await ethers.deployContract("Token");

    // addr1に50トークンを転送する。
    await hardhatToken.transfer(addr1.address, 50);
    expect(await hardhatToken.balanceOf(addr1.address)).to.equal(50);

    // addr1のトークンをaddr2に50トークン転送する。
    await hardhatToken.connect(addr1).transfer(addr2.address, 50);

    // addr1のトークンをaddr2にトークン量が一致しているか確認する。
    expect(await hardhatToken.balanceOf(addr2.address)).to.equal(50);
  });
});

テストコマンドを実行し、テストが正常に完了することを確認します。

$ npx hardhat test

  Token contract
    ✓ Deployment should assign the total supply of tokens to the owner (654ms)


  1 passing (663ms)

テストの共通化や複数のアドレスを使ったテスト方法については、ドキュメントを参照してください。

テスト中にログを出力する場合は、「contracts/Token.sol」に下記をインポートします。

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract Token {
  //...
}

次に出力するコードを同じファイル内に追加します。

function transfer(address to, uint256 amount) external {
    require(balances[msg.sender] >= amount, "Not enough tokens");

    // ログを出力するコードを追加
    console.log(
        "Transferring from %s to %s %s tokens",
        msg.sender,
        to,
        amount
    );

    balances[msg.sender] -= amount;
    balances[to] += amount;

    emit Transfer(msg.sender, to, amount);
}

再度、テストコマンドを実行するとログが出力されます。

$ npx hardhat test

  Token contract
    ✔ Deployment should assign the total supply of tokens to the owner (1244ms)
Transferring from 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 50 tokens
Transferring from 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 to 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc 50 tokens
    ✔ Should transfer tokens between accounts


  2 passing (1s)


テストネットワークにデプロイする

ローカル環境でコントラクトが動くことを確認できたら、テストネットワークにデプロイしてみます。

イーサリアムのテストネットワークの「Sepolia」を使っていきます。
デプロイするスクリプトを作成していきます。

プロジェクトのルートディレクトリ内にignitionという新しいディレクトリを作成し、その中にmodulesという名前のディレクトリを作成します。
そのディレクトリ内にToken.jsファイルを作成します。

次のコードを貼り付けます。

const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");

const TokenModule = buildModule("TokenModule", (m) => {
  const token = m.contract("Token");

  return { token };
});

module.exports = TokenModule;

まずはローカル環境で、動作するかを確認するため以下のコマンドを実行します。

$ npx hardhat ignition deploy ./ignition/modules/Token.js

Compiled 1 Solidity file successfully (evm target: paris).
You are running Hardhat Ignition against an in-process instance of Hardhat Network.
This will execute the deployment, but the results will be lost.
You can use --network <network-name> to deploy to a different network.

Hardhat Ignition 🚀

Deploying [ TokenModule ]

Batch #1 
  Executed TokenModule#Token

[ TokenModule ] successfully deployed 🚀

Deployed Addresses

TokenModule#Token - 0x5FbDB2315678afecb367f032d93F642f64180aa3

デプロイが成功しました。

次はイーサリアムのテストネットワークであるSepoliaにデプロイしていきます。

Sepoliaネットにデプロイするためには、alchemyに登録しApi keyを発行する必要があります。

ログインしたら、Create new appを押します。

Nameを入力します。
 Descriptionは、任意なので空白にします。
Use caseは、利用目的に近そうな「Wallet」を選択します。

Nextで次に進めます。

ここでは、Ethereumを選択します。

デフォルトで、いくつか選択されていますがそのまま進めます。

NetworksのチェーンをSepoliaに変更してAPI Keyを取得します。
後ほど使うので、適当なメモに残します。

次にメタマスクでSepoliaを選択した状態で、アカウントの詳細から秘密鍵を取得します。
※秘密鍵が外部に漏れると悪用される危険性があります。
  秘密鍵が含まれたソースコードをGitなどに公開しないでください。

先ほど取得したAPI keyをALCHEMY_API_KEYに設定します。

npx hardhat vars set ALCHEMY_API_KEY
✔ Enter value: · ********************************
The configuration variable has been stored in /hardhat-nodejs/vars.json

続いてメタマスクの秘密鍵を設定します。

npx hardhat vars set ACCOUNT_PRIVATE_KEY
✔ Enter value: · ********************************
The configuration variable has been stored in /hardhat-nodejs/vars.json


hardhat.confic.jsにコードを貼り付けます。

require("@nomicfoundation/hardhat-toolbox");

// hardhatの設定変数を読み込みます。
const { vars } = require("hardhat/config");

// Alchemyのapi keyを取得。
const ALCHEMY_API_KEY = vars.get("ALCHEMY_API_KEY");

// メタマスクの秘密鍵を取得。
const SEPOLIA_PRIVATE_KEY = vars.get("SEPOLIA_PRIVATE_KEY");

module.exports = {
  solidity: "0.8.24",
  networks: {
    sepolia: {
      url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
      accounts: [SEPOLIA_PRIVATE_KEY]
    }
  }
};

デプロイするためには、Sepoliaが必要になります。
Chainlinkから取得します。

ここまでできたら、Sepoliaを指定してデプロイします。

npx hardhat ignition deploy ./ignition/modules/Token.js --network sepolia

✔ Confirm deploy to network sepolia (11155111)? … yes
Hardhat Ignition 🚀

Deploying [ TokenModule ]

Batch #1 
  Executed TokenModule#Token

[ TokenModule ] successfully deployed 🚀

Deployed Addresses

TokenModule#Token - 0x6df9434aCF56F434cc7E4465676Da2B071d5b43F

デプロイが成功したら、コントラクトアドレスを確認します。

https://sepolia.etherscan.io/

デプロイされていることを確認できました。
最後にトークンをメタマスクに表示させてみます。

コントラクトアドレスをコピーします。

メタマスクのトークンをインポートにアドレスを貼り付けます。
トークンの少数桁数を設定して、進めます。

インポートします。

メタマスクにも、作成したトークンが表示されました!

アプリとの連結までやりたかったのですが、長くなったので次の記事で紹介していきたいと思います!

この記事が気に入ったらサポートをしてみませんか?