Hardhatの特徴と開発環境構築
専門家向けに設計されたEthereum開発フレームワークであるHardhatは、スマートコントラクトのテスト・デプロイ・デバッグを効率化します。ローカル開発ノードの提供や拡張可能なプラグインシステムが特徴です。
開発環境の準備
Node.jsバージョン管理ツールnvmをインストール後、v20系を設定します。
# nvmインストール
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# .zshrcに追加
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Node.js v20設定
nvm install 20
nvm use 20
nvm alias default 20
プロジェクト初期化
新規プロジェクト作成とHardhatツールボックスの導入:
mkdir eth-contract-lab
cd eth-contract-lab
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
プロジェクト初期化コマンド実行後、設定ファイルを以下のように修正:
// hardhat.config.cjs
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: {
version: "0.8.24",
settings: { optimizer: { enabled: true, runs: 200 } }
},
networks: {
localhost: { url: "http://127.0.0.1:8545" }
}
};
ERC20コントラクトの実装とデプロイ
contractsディレクトリにカスタムトークンを実装:
// contracts/ERC20Token.sol
pragma solidity ^0.8.24;
contract ERC20Token {
string public tokenName = "DevelopmentToken";
string public tokenSymbol = "DEV";
uint8 public constant decimals = 18;
uint256 public immutable maxSupply;
address public admin;
mapping(address => uint256) private accountBalances;
event TokensTransferred(address indexed sender, address indexed recipient, uint256 amount);
constructor(uint256 initialSupply) {
maxSupply = initialSupply * 10 ** decimals;
accountBalances[msg.sender] = maxSupply;
admin = msg.sender;
}
function sendTokens(address recipient, uint256 value) external {
require(accountBalances[msg.sender] >= value, "Insufficient balance");
unchecked {
accountBalances[msg.sender] -= value;
accountBalances[recipient] += value;
}
emit TokensTransferred(msg.sender, recipient, value);
}
function getBalance(address account) external view returns (uint256) {
return accountBalances[account] / (10 ** decimals);
}
}
コンパイルとローカルネットワークへのデプロイ:
npx hardhat compile
npx hardhat node --port 8546
デプロイスクリプトの実行(別ターミナル):
// scripts/deploy-token.js
async function deployToken() {
const [deployer] = await hre.ethers.getSigners();
console.log(`Deployer: ${deployer.address}`);
const TokenFactory = await hre.ethers.getContractFactory("ERC20Token");
const tokenInstance = await TokenFactory.deploy(1_000_000);
await tokenInstance.waitForDeployment();
console.log(`Token deployed at: ${await tokenInstance.getAddress()}`);
}
deployToken().catch(console.error);
トランザクショントレースの取得
デプロイ時のトランザクションハッシュを取得後、デバッグAPIでトレースデータを生成:
curl -X POST http://localhost:8546 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "debug_traceTransaction",
"params": ["0x7c8a2b5e9f1d3c4a5b6e7f8d9c0a1b2e3d4c5a6b7e8f9d0c1b2a3e4d5c6b7a"],
"id": 1
}' > tx_trace.json
トレースデータを解析するスクリプト:
// utils/trace-processor.js
import fs from 'fs/promises';
async function processTrace() {
try {
const rawData = await fs.readFile('tx_trace.json', 'utf8');
const traceData = JSON.parse(rawData).result.structLogs;
const formattedLogs = traceData.map(log => ({
pc: log.pc,
opcode: log.op,
gasCost: log.gasCost,
stack: log.stack?.slice(-3) || []
}));
await fs.writeFile('execution_trace.jsonl',
formattedLogs.map(log => JSON.stringify(log)).join('\n')
);
console.log('トレースデータを処理しました');
} catch (err) {
console.error('処理失敗:', err);
}
}
processTrace();
実行コマンド:
node --experimental-json-modules utils/trace-processor.js