hardhat について
hardhat は、Hardhat 3 を使う Solidity 開発者向けのスキルで、ビルド設定、テスト構成、型チェックを扱います。開発時に slang-solx を使う方法も説明し、ツールボックス固有の内容は連携スキルに委ねます。
主な機能
- 01Solidity と TypeScript のテストを使い分け
- 02分離されたネットワーク接続の利用
- 03networkHelpers による EVM 状態操作
- 04開発ビルド向け slang-solx の設定
- 05コントラクトのコンパイル後に型チェック
活用シーン
- 01コントラクトに適したテスト層の選択
- 02テスト中の時間やブロック状態の制御
- 03遅い via-IR 開発ビルド向けコンパイラー設定
- 04テスト後の生成済みコントラクト型の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nomicfoundation/ hardhat-skills --skill hardhatインストール後、Claude Code で/hardhatを入力して読み込まれたか確認します。
詳細
Hardhat 3
このスキルでは、Hardhat 3 自体を扱います。ツールボックス層(クライアント、コントラクトとのやり取り、エコシステム固有のアサーション)は関連スキルで扱います。プロジェクトに合ったものを読み込んでください。
@nomicfoundation/hardhat-toolbox-viem→hardhat-toolbox-viemスキルも読み込んでください。@nomicfoundation/hardhat-toolbox-mocha-ethers→hardhat-toolbox-mocha-ethersスキルも読み込んでください。
ビルドが遅いですか?
hardhat build/hardhat testの大半の時間をコンパイルに費やしている場合、通常はビルドプロファイルでviaIR: trueや重いオプティマイザー設定を使っていることが原因です。開発用ビルドを@nomicfoundation/hardhat-slang-solx経由で slang-solx コンパイラーに切り替えてください。slang-solx による高速ビルドを参照してください。
slang-solx による高速ビルド(開発専用)
slang-solx は、solc と LLVM を基盤とする Solidity コンパイラーです(元は Matter Labs の solx で、現在は Nomic Foundation が保守しています)。solc と比べてコンパイルが大幅に高速で、常に LLVM で最適化されたバイトコードを出力し、「stack too deep」エラーも解消します。現在も活発に開発中のため、ローカルでの反復作業やテストにのみ使用し、本番ビルドやデプロイには決して使用しないでください。ドキュメント:https://hardhat.org/docs/cookbook/compiling-with-slang-solx。
導入を検討するタイミング — 次の場合は、積極的に提案してください。
- 開発用または
hardhat testに使うプロファイルでviaIR: trueやoptimizer.enabled: trueを使用していて、編集とテストのサイクルでコンパイル時間が大きな割合を占める場合。特に効果が大きいのはこのケースです。via-IR パイプラインの Yul オプティマイザーがsolcを遅くしているため、slang-solx はこれを完全に回避します。 - コントラクトで「stack too deep」が発生し、via-IR だけが回避策だった場合。slang-solx は代わりにメモリへ退避することで解決します。
- デフォルトプロファイルですでに最適化なしのレガシー
solcを使っている場合。所要時間はほぼ同じと見込まれますが、slang-solx なら最適化されたバイトコードをテストに使用できます。
参考値(The Graph horizon パッケージ、137 ファイル、12 コア arm64、hardhat build --force):
| プロファイル | コンパイラー | 実時間 |
|---|---|---|
default(オプティマイザーなし、レガシーパイプライン) | solc 0.8.35 | ~15 s |
production(optimizer.runs: 100、viaIR) | solc 0.8.35 | ~160 s |
slang-solx(デフォルトの optimizer.mode: "1") | slang-solx 0.1.8 | ~20 s |
セットアップ。 hardhat >= 3.15 が必要です。プロジェクトのパッケージマネージャーで @nomicfoundation/hardhat-slang-solx をインストールし、プラグインを登録して、slang-solx という名前の Build Profile を追加してください。既存の default / production プロファイルでは、引き続き solc がそのまま使用されます。
import hardhatSlangSolx from "@nomicfoundation/hardhat-slang-solx";
export default defineConfig({
plugins: [/* ...other plugins... */ hardhatSlangSolx],
solidity: {
profiles: {
default: {
/* existing solc config */
},
production: {
/* existing solc config */
},
"slang-solx": {
type: "slang-solx",
version: "0.8.34", // must be a Solidity version slang-solx supports
settings: { evmVersion: "cancun" }, // cancun | prague | osaka
},
},
},
});
npx hardhat build --build-profile slang-solx
npx hardhat test --build-profile slang-solx
Hardhat は Compiled N Solidity files with slang-solx X (Solidity 0.8.34) と表示します。この行を確認して、プロファイルが適用されたことを確かめてください。
ルールと注意点(以下はすべて実際に遭遇したものです):
- リリースごとに Solidity のバージョンは 1 つです。 slang-solx 0.1.8 は Solidity 0.8.34 を対象としています。
type: "slang-solx"を指定したすべてのコンパイラーで、サポート対象のバージョンを使用しないとビルドに失敗します。pragma ではそのバージョンを許可する必要があります。^0.8.27は有効ですが、0.8.28や^0.8.35は無効です。サポート対象外のバージョンが必要なファイルがある場合は、同じプロファイル内でコンパイラーを組み合わせます。compilers: [{ type: "slang-solx", version: "0.8.34" }, { version: "0.8.20" }]の場合、新しいコンパイラーを使用するのはslang-solx型のエントリーのみです。slang-solx に合わせるために、コントラクトのpragmaを編集しないでください。バージョンの範囲を広げる以外に方法がないように見える場合は、まずユーザーに確認してください。監査や検証を受けている可能性のあるコードへのソース変更となるためです。 - インラインアセンブリには
("memory-safe")を付けます。 slang-solx は、マークのないすべてのassembly {}ブロックについて警告します。また、マークのないブロックとスタックの深さが上限を超える問題が同時にあるコントラクトは、runtime code LLVM IR generator: This contract cannot be compiled due to a combination of a memory-unsafe assembly block and a stack-too-deep errorというエラーで失敗します。警告されたブロックが Solidity のメモリー安全性ルール(スクラッチ領域0x00–0x3f、確保済みメモリーのみを操作するか、メモリーに一切触れないこと)に準拠しているか確認し、準拠している場合はassembly ("memory-safe") { ... }に変更します。それ以外の領域に書き込むブロックには、決してこのタグを付けないでください。 - ここでは
viaIRは何もしません。 slang-solx のプロファイルでviaIR: trueを設定しても、メタデータハッシュ以外は変わりません。solc プロファイルからコピーしないでください。最適化レベルはsettings.optimizer.modeで指定します。"1"(デフォルト、コンパイルが最速)、"2"、"3"(実行時性能が最良)、"s"、"z"(サイズが最小)です。settings.optimizer.enabledが切り替えるのは、組み込みの solc フロントエンドにあるレガシーなアセンブリオプティマイザーのみです。 - プロファイルの制約。
type: "slang-solx"を使用できるのは、slang-solxという名前のプロファイル内のみです。トップレベルで"slang-solx": { dangerouslyAllowSlangSolxInProduction: true }を設定しない限り、defaultまたはproductionで使用すると検証エラーになります。リリース対象には、この設定を絶対に指定しないでください。 - バイトコードは solc と異なります。 ABI と TypeChain/viem の型は同一のため、型チェックや動作テスト(状態、イベント、リバート、戻り値)への影響はありません。slang-solx では、ガスレポートや、正確なガス量、ガス制限、コントラクトサイズ、バイトコードハッシュ(CREATE2 アドレスを含む)をチェックするテストの結果が異なります。これらは
solcプロファイルでのみ実行または更新してください。 - via-IR を使った大規模な solc ビルドでは Hardhat が OOM になることがあります。
solcと via-IR を組み合わせたビルドがJavaScript heap out of memoryで失敗する場合は、ヒープを増やす(NODE_OPTIONS=--max-old-space-size=8192)か、開発ビルドを slang-solx に切り替えてください。
テストの構成
Hardhat 3 は、次の 2 つの異なるテスト層をサポートしています。
Solidity テスト(contracts/ 内の .t.sol ファイル、または test/ 内の任意の .sol ファイル)は、個々のコントラクトのユニットテストにおける標準的な選択肢です。EVM 上で直接実行され、実際の ABI に対してコンパイル時チェックを行い、EVM 状態を操作するチートコードを利用できます。名前が test で始まる公開関数は、すべてテストケースとして実行されます。
TypeScript テスト(test/ 内の .ts ファイル)は、オフチェーンでのオーケストレーションを必要とするテストに適しています。たとえば、複数コントラクトの連携、大規模なテストスイートでのフィクスチャの再利用、外部から確認するガス量や ETH 残高の検証、外部状態を元にした統合シナリオなどです。
Solidity だけでは不十分な場合に限り、TypeScript を使ってください。コントラクトのロジックはすべて Solidity テストでカバーし、ユーザーやスクリプトが体験するエンドツーエンドのフローは TypeScript テストでカバーします。
hardhat test # run all tests (Solidity + TypeScript)
hardhat test solidity # Solidity tests only
TypeScript テストは、使用しているツールボックスまたはプラグインに応じて、hardhat test nodejs または hardhat test mocha で実行します。viem ベースのツールボックスでは nodejs を、ethers と mocha を組み合わせたツールボックスでは mocha を使用します。
テストの実行中に Solidity のカバレッジを収集するには、--coverage を渡します。これはメインの hardhat test タスクに加え、solidity および TypeScript のサブタスク(nodejs/mocha)でも使用できます。そのため、必要に応じてカバレッジの対象を単一のテスト層に絞れます。
Solidity のテスト
test* 関数を1つ以上含むコントラクトは、テストコントラクトとして扱われます。アサーションとチートコードには、package.json に含まれている場合は forge-std を使用します。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import { Test } from "forge-std/Test.sol";
import { Counter } from "./Counter.sol";
contract CounterTest is Test {
Counter counter;
function setUp() public {
counter = new Counter();
}
function test_InitialValueIsZero() public view {
assertEq(counter.x(), 0);
}
function test_IncByIncreasesByAmount() public {
counter.incBy(3);
assertEq(counter.x(), 3);
}
// Fuzz test: Hardhat runs this with many random inputs automatically
function testFuzz_Inc(uint8 x) public {
for (uint8 i = 0; i < x; i++) {
counter.inc();
}
require(counter.x() == x, "Value after calling inc x times should be x");
}
}
vm オブジェクト(forge-std/Test.sol 由来)には、EVM の状態を操作するチートコードが用意されています。よく使われるものは次のとおりです。
| チートコード | 効果 |
|---|---|
vm.prank(addr) | 次の呼び出しに限り、msg.sender を設定します |
vm.startPrank(addr) / vm.stopPrank() | 複数の呼び出しにわたって msg.sender を設定します |
vm.deal(addr, amount) | アカウントの ETH 残高を設定します |
vm.warp(timestamp) | block.timestamp を設定します |
vm.roll(blockNum) | block.number を設定します |
vm.expectRevert(...) | 次の呼び出しがリバートすることをアサートします |
vm.expectEmit(...) | 次の呼び出しで特定のイベントが発生することをアサートします |
TypeScript のテストとネットワーク接続
TypeScript のテストの中心となるのは、network.create() で作成するネットワーク接続です。ツールボックスプラグインによって、ヘルパーオブジェクト(viem や ethers、さらに networkHelpers など)が追加されます。
import { network } from "hardhat";
const { networkHelpers } = await network.create();
// Toolbox plugins also expose viem / ethers on this object;
// see the matching hardhat-toolbox-* skill.
Hardhat 3 は ESM のみをサポートするため、トップレベルで await を使用できます。network.create() を呼び出すたびに、分離されたブロックチェーンの状態が作成されます。同じ接続と関連する状態をファイル内のすべてのテストで共有できる場合は、トップレベルのネットワーク接続を使用できます。さらに分離するには、テストスイートごと、またはテストケースごとに新しい接続を作成します。
networkHelpers:EVM の状態操作
networkHelpers は、Hardhat の開発専用ヘルパーをすべて、型付きで使いやすい API の背後にまとめています。生の JSON-RPC 呼び出しよりもこちらを優先してください。
// Time
await networkHelpers.time.increase(60); // mine a block 60 s ahead
await networkHelpers.time.increaseTo(target); // mine to a specific timestamp
const now = await networkHelpers.time.latest(); // latest block timestamp
await networkHelpers.time.setNextBlockTimestamp(ts); // set without mining
// Blocks
await networkHelpers.mine(); // mine one block
await networkHelpers.mine(5); // mine several blocks
// Accounts
await networkHelpers.impersonateAccount(addr);
await networkHelpers.setBalance(addr, 10n ** 18n);
// Block parameters
await networkHelpers.setPrevRandao(value); // set block.prevrandao for the next block
await networkHelpers.setNextBlockBaseFeePerGas(baseFee);
// Fixtures
//
// loadFixture runs the setup once and snapshots the state. Subsequent calls
// restore the snapshot instead of re-deploying, much faster for large suites.
// The deploy step inside the fixture is toolbox-specific (e.g.
// viem.deployContract, an ethers ContractFactory) — see the matching
// hardhat-toolbox-* skill.
async function deployCounter() {
// ... toolbox-specific deploy ...
return { counter };
}
const { counter } = await networkHelpers.loadFixture(deployCounter);
// loadFixture requires a named function for caching to work (not an
// arrow/anonymous function)
// Snapshots (manual)
const snap = await networkHelpers.takeSnapshot();
// ... do things ...
await snap.restore();
等価性、配列、型などの通常の TypeScript アサーションには、node:assert/strict を使用してください。リバート、イベント、残高の変化など、エコシステム固有のアサーションについては、対応する hardhat-toolbox-* スキルを参照してください。
型チェック
生成されるコントラクトの型は、コンパイル済み ABI から導出されます(viem ツールボックスでは viem、ethers ツールボックスでは TypeChain)。そのため、まず再コンパイルしてから型チェックを行ってください。以下の例では npm を使用しています。プロジェクトで使っているパッケージマネージャーの実行コマンドに合わせて、npx を置き換えてください。
npx hardhat build && npx tsc --noEmit
プロジェクトに slang-solx プロファイル(上記を参照)がある場合は、npx hardhat build --build-profile slang-solx && npx tsc --noEmit を実行すると、via-IR プロジェクトで同じ型をはるかに高速に取得できます。ABI、ひいては生成される型はコンパイラーに依存しないためです。
テストランナーを起動する前に、コンパイラーが誤った引数の型、引数の不足、無効なオプション(例:非 payable 関数での value)を検出します。これを習慣にしましょう。テストスイート全体を実行するよりも早く、多くのミスを発見できます。