🦁

Solidity基礎学習26日目(Hardhat開発環境の構築)

に公開

Hardhat開発環境の構築とプロジェクト構造の理解

日付: 2025年9月26日
学習内容: Hardhatの使い方、プロジェクト構造、各フォルダの役割について

1. Hardhatとは

1.1 Hardhatの概要

Hardhatは、Ethereumスマートコントラクトの開発、テスト、デプロイを行うための包括的な開発環境です。TypeScriptとJavaScriptの両方をサポートし、豊富なプラグインエコシステムを提供します。

主要な機能:

  • コンパイル: Solidityコードのコンパイル
  • テスト: 単体テストと統合テストの実行
  • デプロイ: スマートコントラクトのデプロイメント
  • デバッグ: 詳細なエラーメッセージとスタックトレース
  • ネットワーク管理: ローカル・テストネット・メインネットの管理

1.2 プロジェクトの初期化

# プロジェクトの初期化
npm init -y
npm install --save-dev hardhat
npx hardhat --init

# TypeScript Hardhat project using Node Test Runner and Viem

2. Hardhatプロジェクトの構造

2.1 プロジェクト全体の構造

hardhat/
├── artifacts/          # コンパイル済みコントラクト
├── cache/             # ビルドキャッシュ
├── contracts/         # Solidityソースコード
├── ignition/modules/  # デプロイメントモジュール
├── node_modules/      # 依存関係
├── scripts/           # 実行スクリプト
├── test/              # テストファイル
├── hardhat.config.ts  # 設定ファイル
├── package.json       # プロジェクト設定
└── tsconfig.json      # TypeScript設定

2.2 各フォルダの詳細な役割

artifacts/ フォルダ

役割: コンパイル済みのスマートコントラクト情報を格納

格納されるファイル:

  • artifacts.d.ts: TypeScript型定義ファイル
  • build-info/: ソースコードのコピーとビルド情報
  • contracts/: 各コントラクトのコンパイル結果

重要な情報:

// artifacts/contracts/Counter.sol/Counter.json
{
  "abi": [...],           // コントラクトのABI
  "bytecode": "0x...",    // バイトコード
  "deployedBytecode": "0x...", // デプロイ済みバイトコード
  "linkReferences": {...}, // ライブラリ参照
  "deployedLinkReferences": {...}
}

使用例:

// コンパイル後のコントラクト情報を取得
import Counter from "./artifacts/contracts/Counter.sol/Counter.json";
const abi = Counter.abi;
const bytecode = Counter.bytecode;

cache/ フォルダ

役割: ビルドプロセスの最適化とキャッシュ管理

格納されるファイル:

  • compile-cache.json: コンパイルキャッシュ情報

機能:

  • 変更されていないファイルの再コンパイルをスキップ
  • ビルド時間の短縮
  • 依存関係の追跡

contracts/ フォルダ

役割: Solidityソースコードの格納場所

格納されるファイル:

  • Counter.sol: メインのスマートコントラクト
  • Counter.t.sol: テスト用コントラクト(オプション)

例:

// contracts/Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Counter {
    uint256 public x;

    event Increment(uint256 by);

    function inc() public {
        x += 1;
        emit Increment(1);
    }

    function incBy(uint256 by) public {
        x += by;
        emit Increment(by);
    }
}

ignition/modules/ フォルダ

役割: スマートコントラクトのデプロイメントモジュール

格納されるファイル:

  • Counter.ts: デプロイメントスクリプト

例:

// ignition/modules/Counter.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

export default buildModule("CounterModule", (m) => {
  const counter = m.contract("Counter");

  m.call(counter, "incBy", [5n]);

  return { counter };
});

使用方法:

# デプロイメントの実行
npx hardhat ignition deploy ./ignition/modules/Counter.ts --network localhost

scripts/ フォルダ

役割: カスタムスクリプトの実行

格納されるファイル:

  • send-op-tx.ts: Optimismスタイルのトランザクション送信

例:

// scripts/send-op-tx.ts
import { network } from "hardhat";

const { viem } = await network.connect({
  network: "hardhatOp",
  chainType: "op",
});

console.log("Sending transaction using the OP chain type");

const publicClient = await viem.getPublicClient();
const [senderClient] = await viem.getWalletClients();

console.log("Sending 1 wei from", senderClient.account.address, "to itself");

const l1Gas = await publicClient.estimateL1Gas({
  account: senderClient.account.address,
  to: senderClient.account.address,
  value: 1n,
});

console.log("Estimated L1 gas:", l1Gas);

console.log("Sending L2 transaction");
const tx = await senderClient.sendTransaction({
  to: senderClient.account.address,
  value: 1n,
});

await publicClient.waitForTransactionReceipt({ hash: tx });

console.log("Transaction sent successfully");

使用方法:

# スクリプトの実行
npx hardhat run --network localhost scripts/send-op-tx.ts

test/ フォルダ

役割: 単体テストと統合テストの格納

格納されるファイル:

  • Counter.ts: Counterコントラクトのテスト

例:

// test/Counter.ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { network } from "hardhat";

describe("Counter", async function () {
  const { viem } = await network.connect();
  const publicClient = await viem.getPublicClient();

  it("Should emit the Increment event when calling the inc() function", async function () {
    const counter = await viem.deployContract("Counter");

    await viem.assertions.emitWithArgs(
      counter.write.inc(),
      counter,
      "Increment",
      [1n],
    );
  });

  it("The sum of the Increment events should match the current value", async function () {
    const counter = await viem.deployContract("Counter");
    const deploymentBlockNumber = await publicClient.getBlockNumber();

    // run a series of increments
    for (let i = 1n; i <= 10n; i++) {
      await counter.write.incBy([i]);
    }

    const events = await publicClient.getContractEvents({
      address: counter.address,
      abi: counter.abi,
      eventName: "Increment",
      fromBlock: deploymentBlockNumber,
      strict: true,
    });

    // check that the aggregated events match the current value
    let total = 0n;
    for (const event of events) {
      total += event.args.by;
    }

    assert.equal(total, await counter.read.x());
  });
});

使用方法:

# テストの実行
npx hardhat test

3. Hardhatの主要コマンド

3.1 基本的なコマンド

# プロジェクトの初期化
npx hardhat --init

# コンパイル
npx hardhat compile

# テスト実行
npx hardhat test

# ローカルネットワーク起動
npx hardhat node

# スクリプト実行
npx hardhat run scripts/send-op-tx.ts --network localhost

# デプロイメント
npx hardhat ignition deploy ./ignition/modules/Counter.ts --network localhost

3.2 ネットワーク設定

// hardhat.config.ts
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable } from "hardhat/config";

const config: HardhatUserConfig = {
  plugins: [hardhatToolboxViemPlugin],
  solidity: {
    profiles: {
      default: {
        version: "0.8.28",
      },
      production: {
        version: "0.8.28",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
    },
  },
  networks: {
    hardhatMainnet: {
      type: "edr-simulated",
      chainType: "l1",
    },
    hardhatOp: {
      type: "edr-simulated",
      chainType: "op",
    },
    sepolia: {
      type: "http",
      chainType: "l1",
      url: configVariable("SEPOLIA_RPC_URL"),
      accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
    },
  },
};

export default config;

4. 開発ワークフロー

4.1 基本的な開発フロー

# 1. プロジェクトの初期化
npm init -y
npm install --save-dev hardhat
npx hardhat --init

# 2. コントラクトの作成
# contracts/Counter.sol にコントラクトを記述

# 3. コンパイル
npx hardhat compile

# 4. テストの作成と実行
# test/Counter.ts にテストを記述
npx hardhat test

# 5. ローカルネットワークの起動
npx hardhat node

# 6. デプロイメント
npx hardhat ignition deploy ./ignition/modules/Counter.ts --network localhost

# 7. カスタムスクリプトの実行
npx hardhat run --network localhost scripts/send-op-tx.ts

4.2 コンパイルプロセス

# コンパイル実行
npx hardhat compile

# 生成されるファイル
# artifacts/contracts/Counter.sol/Counter.json
# artifacts/build-info/... (ソースコードのコピー)
# cache/compile-cache.json

コンパイル結果の確認:

  • ABI: コントラクトのインターフェース定義
  • Bytecode: デプロイ用のバイトコード
  • Deployed Bytecode: 実行時のバイトコード
  • Source Maps: デバッグ用のソースマッピング

4.3 テストプロセス

# テスト実行
npx hardhat test

# 特定のテストファイルの実行
npx hardhat test test/Counter.ts

# 詳細な出力でテスト実行
npx hardhat test --verbose

テストの種類:

  • 単体テスト: 個別の関数のテスト
  • 統合テスト: 複数のコントラクト間の相互作用テスト
  • イベントテスト: イベントの発火確認
  • ガス使用量テスト: ガス効率の測定

5. 高度な機能

5.1 プラグインシステム

// hardhat.config.ts
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";

const config: HardhatUserConfig = {
  plugins: [hardhatToolboxViemPlugin],
  // その他の設定...
};

主要なプラグイン:

  • @nomicfoundation/hardhat-toolbox-viem: Viem統合
  • @nomicfoundation/hardhat-ignition: デプロイメント管理
  • @nomicfoundation/hardhat-verify: コントラクト検証

5.2 ネットワーク管理

// 複数のネットワーク設定
networks: {
  localhost: {
    url: "http://127.0.0.1:8545"
  },
  hardhat: {
    // デフォルトのローカルネットワーク
  },
  sepolia: {
    url: configVariable("SEPOLIA_RPC_URL"),
    accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
  },
  mainnet: {
    url: configVariable("MAINNET_RPC_URL"),
    accounts: [configVariable("MAINNET_PRIVATE_KEY")],
  }
}

5.3 環境変数の管理

# .env ファイル
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_PROJECT_ID
SEPOLIA_PRIVATE_KEY=0x...
MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_PROJECT_ID
MAINNET_PRIVATE_KEY=0x...

6. トラブルシューティング

6.1 よくある問題と解決方法

コンパイルエラー

# キャッシュのクリア
npx hardhat clean

# 再コンパイル
npx hardhat compile

テストエラー

# 詳細なエラー情報の表示
npx hardhat test --verbose

# 特定のテストの実行
npx hardhat test --grep "Should emit"

ネットワーク接続エラー

# ローカルネットワークの確認
npx hardhat node

# ネットワーク設定の確認
npx hardhat console --network localhost

6.2 デバッグ機能

// デバッグ用のコンソール出力
console.log("Current value:", await counter.read.x());

// イベントの監視
const events = await publicClient.getContractEvents({
  address: counter.address,
  abi: counter.abi,
  eventName: "Increment",
  fromBlock: deploymentBlockNumber,
  strict: true,
});

7. 学習の成果

7.1 習得した概念

  1. Hardhatの基本構造: プロジェクトの構成と各フォルダの役割
  2. コンパイルプロセス: Solidityコードのコンパイルと成果物の理解
  3. テスト環境: 単体テストと統合テストの実行方法
  4. デプロイメント: スマートコントラクトのデプロイメント管理
  5. ネットワーク管理: ローカル・テストネット・メインネットの設定
  6. スクリプト実行: カスタムスクリプトの作成と実行

7.2 実装スキル

  • プロジェクトの初期化: 新しいHardhatプロジェクトの作成
  • コントラクトの開発: Solidityコードの記述とコンパイル
  • テストの作成: 包括的なテストスイートの構築
  • デプロイメント: 複数ネットワークへのデプロイメント
  • スクリプト作成: カスタム機能の実装
  • 設定管理: 環境変数とネットワーク設定

7.3 技術的な理解

  • ビルドプロセス: コンパイルからデプロイメントまでの流れ
  • キャッシュシステム: ビルド効率の最適化
  • プラグインシステム: 機能拡張の仕組み
  • ネットワーク抽象化: 異なるブロックチェーンネットワークの統合
  • 型安全性: TypeScriptによる型安全な開発
  • デバッグ機能: 問題の特定と解決方法

8. 今後の学習への応用

8.1 発展的な機能

  • カスタムプラグイン: 独自の機能拡張
  • ガス最適化: コントラクトの効率化
  • セキュリティ監査: 脆弱性の検出と修正
  • CI/CD統合: 自動化された開発フロー

8.2 実用的な応用

  • DeFiプロトコル: 複雑な金融アプリケーションの開発
  • NFTプロジェクト: デジタル資産の管理システム
  • ゲーム開発: ブロックチェーンゲームの実装
  • 企業向けソリューション: ビジネスプロセスの自動化

8.3 セキュリティの向上

  • テストカバレッジ: 包括的なテストスイートの構築
  • 静的解析: コード品質の自動チェック
  • 監査プロセス: セキュリティレビューの実施
  • ベストプラクティス: 安全なコーディング規約の遵守

参考:

Discussion