🦁
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 習得した概念
- Hardhatの基本構造: プロジェクトの構成と各フォルダの役割
- コンパイルプロセス: Solidityコードのコンパイルと成果物の理解
- テスト環境: 単体テストと統合テストの実行方法
- デプロイメント: スマートコントラクトのデプロイメント管理
- ネットワーク管理: ローカル・テストネット・メインネットの設定
- スクリプト実行: カスタムスクリプトの作成と実行
7.2 実装スキル
- プロジェクトの初期化: 新しいHardhatプロジェクトの作成
- コントラクトの開発: Solidityコードの記述とコンパイル
- テストの作成: 包括的なテストスイートの構築
- デプロイメント: 複数ネットワークへのデプロイメント
- スクリプト作成: カスタム機能の実装
- 設定管理: 環境変数とネットワーク設定
7.3 技術的な理解
- ビルドプロセス: コンパイルからデプロイメントまでの流れ
- キャッシュシステム: ビルド効率の最適化
- プラグインシステム: 機能拡張の仕組み
- ネットワーク抽象化: 異なるブロックチェーンネットワークの統合
- 型安全性: TypeScriptによる型安全な開発
- デバッグ機能: 問題の特定と解決方法
8. 今後の学習への応用
8.1 発展的な機能
- カスタムプラグイン: 独自の機能拡張
- ガス最適化: コントラクトの効率化
- セキュリティ監査: 脆弱性の検出と修正
- CI/CD統合: 自動化された開発フロー
8.2 実用的な応用
- DeFiプロトコル: 複雑な金融アプリケーションの開発
- NFTプロジェクト: デジタル資産の管理システム
- ゲーム開発: ブロックチェーンゲームの実装
- 企業向けソリューション: ビジネスプロセスの自動化
8.3 セキュリティの向上
- テストカバレッジ: 包括的なテストスイートの構築
- 静的解析: コード品質の自動チェック
- 監査プロセス: セキュリティレビューの実施
- ベストプラクティス: 安全なコーディング規約の遵守
参考:
Discussion