Ethereum開発環境Hardhatの実践的活用とトレース分析

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

タグ: hardhat ethers.js solidity ethereum debug_traceTransaction

6月1日 01:57 投稿