🔖

Solidity基礎学習21日目(Truffle, Ganache)

に公開

NFTプロジェクト開発 - Truffle、Hardhat、Foundryの比較とTruffleでのデプロイ実習

日付: 2025年9月17日
学習内容: NFTプロジェクトの開発フレームワーク比較とTruffleを使用したNFTコントラクトのデプロイ、Ganacheでの対話、NFTのミントについて

1. 開発フレームワークの比較

1.1 Truffle、Hardhat、Foundryの特徴比較

Truffle

// Truffleの特徴
- 最も古い開発フレームワーク(2015年から)
- 豊富なエコシステムとコミュニティ
- 直感的な設定と使いやすさ
- 内蔵のテストフレームワーク
- マイグレーション機能
- Ganacheとの統合

Truffleの利点:

  • 学習コストが低い: 初心者に優しい設計
  • 豊富なドキュメント: 充実したチュートリアルとサンプル
  • 安定性: 長期間の開発実績
  • Ganache統合: ローカル開発環境との連携

Truffleの欠点:

  • パフォーマンス: 他のフレームワークより遅い
  • 設定の複雑さ: 大規模プロジェクトでの設定が複雑
  • 依存関係: Node.jsに依存

Hardhat

// Hardhatの特徴
- モダンな開発フレームワーク
- 高速なコンパイルとテスト
- 豊富なプラグインエコシステム
- TypeScriptサポート
- デバッグ機能
- タスクシステム

Hardhatの利点:

  • 高速: インクリメンタルコンパイル
  • 柔軟性: プラグインによる機能拡張
  • デバッグ: 優れたデバッグツール
  • TypeScript: 型安全性のサポート

Hardhatの欠点:

  • 学習コスト: 設定が複雑
  • 新しさ: 比較的新しいフレームワーク
  • 依存関係: Node.jsに依存

Foundry

// Foundryの特徴
- Rustで書かれた高速フレームワーク
- ネイティブのSolidityテスト
- 低レベルな制御
- ガス最適化
- フォージ(コンパイル・テスト)
- キャスト(ブロックチェーンとの対話)

Foundryの利点:

  • 最高速度: Rustによる高速実行
  • ガス最適化: 詳細なガス分析
  • 低レベル制御: 細かい設定が可能
  • 依存関係なし: 単体で動作

Foundryの欠点:

  • 学習コスト: 高難易度
  • 新しさ: 比較的新しい
  • コミュニティ: まだ小さい

1.2 フレームワーク選択の指針

プロジェクト規模による選択

// 小規模プロジェクト(学習・プロトタイプ)
推奨: Truffle
理由: 学習コストが低く、迅速な開発が可能

// 中規模プロジェクト(本格的なDApp)
推奨: Hardhat
理由: バランスの取れた機能とパフォーマンス

// 大規模プロジェクト(エンタープライズ)
推奨: Foundry
理由: 最高のパフォーマンスとガス最適化

開発チームの経験による選択

// 初心者チーム
推奨: Truffle
理由: 豊富なドキュメントとコミュニティサポート

// 中級者チーム
推奨: Hardhat
理由: モダンな機能と柔軟性

// 上級者チーム
推奨: Foundry
理由: 最高のパフォーマンスと制御性

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

2.1 プロジェクトディレクトリ構成

truffle/
├── contracts/                    # スマートコントラクト
│   └── SpaceTiger.sol           # NFTコントラクト
├── migrations/                   # デプロイスクリプト
│   └── 01-spacetiger-deployment.js
├── test/                        # テストファイル
├── build/                       # コンパイル済みコントラクト
├── truffle-config.js           # Truffle設定ファイル
├── package.json                # NPM設定
└── README.md                   # プロジェクト説明

2.2 重要なファイルの役割

truffle-config.js

module.exports = {
  networks: {
    ganache: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*",
    }
  },
  compilers: {
    solc: {
      version: "0.8.24",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        },
        evmVersion: "shanghai"
      }
    }
  }
};

設定の説明:

  • networks: 接続先ブロックチェーンネットワークの設定
  • compilers: Solidityコンパイラの設定
  • optimizer: ガス最適化の設定
  • evmVersion: EVMバージョンの指定

package.json

{
  "name": "spacetiger-nft",
  "version": "1.0.0",
  "dependencies": {
    "@openzeppelin/contracts": "^5.4.0"
  },
  "scripts": {
    "compile": "truffle compile",
    "migrate": "truffle migrate --network ganache",
    "test": "truffle test"
  }
}

3. SpaceTiger NFTコントラクトの実装

3.1 コントラクトの基本構造

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SpaceTiger is ERC721, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    constructor(address initialOwner)
        ERC721("SpaceTiger", "STG")
        Ownable(initialOwner)
    {}

    function _baseURI() internal pure override returns (string memory) {
        return "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/";
    }

    function safeMint(address to, string memory uri)
        public
        onlyOwner
        returns (uint256)
    {
        uint256 tokenId = _nextTokenId++;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        return tokenId;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

3.2 継承関係の詳細

ERC721の継承

// ERC721の基本機能
contract SpaceTiger is ERC721 {
    // 基本NFT機能
    // - transferFrom()
    // - approve()
    // - ownerOf()
    // - balanceOf()
}

ERC721URIStorageの継承

// 個別URI管理機能
contract SpaceTiger is ERC721, ERC721URIStorage {
    // 個別URI機能
    // - _setTokenURI()
    // - tokenURI()
}

Ownableの継承

// 所有者権限管理
contract SpaceTiger is ERC721, ERC721URIStorage, Ownable {
    // 所有者機能
    // - onlyOwner修飾子
    // - owner()
    // - transferOwnership()
}

3.3 重要な関数の詳細

safeMint関数

function safeMint(address to, string memory uri)
    public
    onlyOwner
    returns (uint256)
{
    uint256 tokenId = _nextTokenId++;
    _safeMint(to, tokenId);
    _setTokenURI(tokenId, uri);
    return tokenId;
}

機能の説明:

  • onlyOwner: 所有者のみが実行可能
  • _nextTokenId++: 後置インクリメントでユニークID生成
  • _safeMint: 安全なNFT発行
  • _setTokenURI: メタデータURIの設定

_baseURI関数

function _baseURI() internal pure override returns (string memory) {
    return "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/";
}

機能の説明:

  • ベースURI: 全NFTの共通ベースURL
  • 内部関数: コントラクト内部でのみ使用
  • pure: 状態を変更しない
  • override: 親クラスの関数をオーバーライド

4. デプロイスクリプトの実装

4.1 マイグレーションファイル

const SpaceTiger = artifacts.require("SpaceTiger");

module.exports = function(deployer, network, accounts) {
    // デプロイ時にinitialOwnerを指定
    // accounts[0]はデプロイに使用されるアカウント
    console.log("Network:", network);
    console.log("Deployer:", accounts[0]);
    deployer.deploy(SpaceTiger, accounts[0]);
};

4.2 デプロイプロセスの詳細

デプロイの流れ

// 1. コントラクトのコンパイル
npx truffle compile

// 2. Ganacheネットワークへのデプロイ
npx truffle migrate --network ganache

// 3. デプロイ結果の確認
npx truffle console --network ganache

デプロイ成功時の出力

// デプロイ成功時の出力例
Deploying 'SpaceTiger'
----------------------
> transaction hash:    0x1234...
> contract address:    0x5678...
> block number:        123
> block timestamp:     1640995200
> account:            0x9abc...
> balance:            99.9 ETH
> gas used:           1234567
> gas price:          20 gwei
> value sent:         0 ETH
> total cost:         0.02469134 ETH

5. Ganacheでの対話

5.1 Truffle Consoleの起動

# Ganacheネットワークでコンソールを起動
npx truffle console --network ganache

5.2 コントラクトインスタンスの取得

// コンソール内でのコマンド
// コントラクトのインスタンスを取得
let spaceTiger = await SpaceTiger.deployed()

// コントラクトの基本情報を確認
await spaceTiger.name()        // "SpaceTiger"
await spaceTiger.symbol()      // "STG"
await spaceTiger.owner()       // デプロイ者のアドレス
spaceTiger.address             // コントラクトアドレス

5.3 アカウント情報の確認

// 利用可能なアカウントを取得
const accounts = await web3.eth.getAccounts()

// アカウント情報の表示
console.log("Account 0:", accounts[0])  // デプロイ者
console.log("Account 1:", accounts[1])  // 受信者
console.log("Account 2:", accounts[2])  // 受信者

6. NFTのミント(Mint)実習

6.1 基本的なミント操作

// コントラクトインスタンスとアカウントを取得
let spaceTiger = await SpaceTiger.deployed()
const accounts = await web3.eth.getAccounts()

// NFTをミント(accounts[1]にspacetiger_1.jsonをミント)
// 注意: safeMintはオーナー(accounts[0])のみが実行可能
await spaceTiger.safeMint(accounts[1], "spacetiger_1.json")

// 返り値にはトランザクション情報が含まれる
// トランザクションハッシュやイベントログを確認可能

6.2 ミントされたNFTの詳細確認

// NFTの所有者を確認(tokenId: 0の所有者を取得)
await spaceTiger.ownerOf(0)
// → accounts[1]のアドレスが返される

// NFTのメタデータURIを確認
await spaceTiger.tokenURI(0)
// → "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.json"が返される

// 複数のNFTをミントした場合の例
await spaceTiger.safeMint(accounts[2], "spacetiger_2.json")  // tokenId: 1
await spaceTiger.safeMint(accounts[3], "spacetiger_3.json")  // tokenId: 2

// それぞれのNFTの所有者を確認
await spaceTiger.ownerOf(1)  // → accounts[2]
await spaceTiger.ownerOf(2)  // → accounts[3]

// それぞれのトークンURIを確認
await spaceTiger.tokenURI(1)  // → ベースURI + "spacetiger_2.json"
await spaceTiger.tokenURI(2)  // → ベースURI + "spacetiger_3.json"

6.3 高度なNFT操作

// 特定アドレスが所有するNFTの数を確認
await spaceTiger.balanceOf(accounts[1])

// NFTの転送(accounts[1]からaccounts[2]へtokenId:0を転送)
// 注意: 転送はトークンの所有者のみが実行可能
await spaceTiger.transferFrom(accounts[1], accounts[2], 0, {from: accounts[1]})

// 転送後の所有者を確認
await spaceTiger.ownerOf(0)  // → accounts[2]

// 承認の設定
await spaceTiger.approve(accounts[3], 0, {from: accounts[2]})

// 承認されたアドレスからの転送
await spaceTiger.transferFrom(accounts[2], accounts[3], 0, {from: accounts[3]})

7. スクリプトを使用した対話

7.1 対話スクリプトの作成

// interact.jsファイルを作成
const SpaceTiger = artifacts.require("SpaceTiger");

module.exports = async function(callback) {
    try {
        const spaceTiger = await SpaceTiger.deployed();
        
        console.log("=== SpaceTiger Contract Information ===");
        console.log("Contract address:", spaceTiger.address);
        console.log("Contract name:", await spaceTiger.name());
        console.log("Contract symbol:", await spaceTiger.symbol());
        console.log("Contract owner:", await spaceTiger.owner());
        
        // アカウント情報の取得
        const accounts = await web3.eth.getAccounts();
        console.log("\n=== Available Accounts ===");
        for(let i = 0; i < 3; i++) {
            console.log(`Account ${i}:`, accounts[i]);
        }
        
        // NFTのミント
        console.log("\n=== Minting NFT ===");
        const mintResult = await spaceTiger.safeMint(accounts[1], "spacetiger_1.json");
        console.log("Mint transaction:", mintResult.tx);
        
        // ミントされたNFTの確認
        console.log("\n=== NFT Details ===");
        console.log("Owner of token 0:", await spaceTiger.ownerOf(0));
        console.log("Token URI:", await spaceTiger.tokenURI(0));
        console.log("Balance of account 1:", await spaceTiger.balanceOf(accounts[1]));
        
        callback();
    } catch (error) {
        console.error("Error:", error);
        callback(error);
    }
};

7.2 スクリプトの実行

# スクリプトを実行
npx truffle exec interact.js --network ganache

7.3 スクリプト実行結果

// スクリプト実行時の出力例
=== SpaceTiger Contract Information ===
Contract address: 0x1234567890abcdef...
Contract name: SpaceTiger
Contract symbol: STG
Contract owner: 0x9abcdef123456789...

=== Available Accounts ===
Account 0: 0x9abcdef123456789...
Account 1: 0x1234567890abcdef...
Account 2: 0x567890abcdef1234...

=== Minting NFT ===
Mint transaction: 0xabcdef1234567890...

=== NFT Details ===
Owner of token 0: 0x1234567890abcdef...
Token URI: https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.json
Balance of account 1: 1

8. メタデータの構造

8.1 NFTメタデータの標準

{
  "name": "SpaceTiger #1",
  "description": "A majestic space tiger exploring the cosmos",
  "image": "https://ethereum-blockchain-developer.com/2022-06-nft-truffle-hardhat-foundry/nftdata/spacetiger_1.png",
  "attributes": [
    {
      "trait_type": "Background",
      "value": "Nebula"
    },
    {
      "trait_type": "Fur Color",
      "value": "Silver"
    },
    {
      "trait_type": "Eyes",
      "value": "Glowing Blue"
    },
    {
      "trait_type": "Rarity",
      "value": "Legendary"
    }
  ]
}

8.2 メタデータの重要性

NFTの価値決定要因

// メタデータの要素
{
  "name": "NFTの名前",           // 識別性
  "description": "NFTの説明",     // ストーリー性
  "image": "画像URL",           // 視覚的価値
  "attributes": [               // 希少性
    {
      "trait_type": "特性名",
      "value": "特性値"
    }
  ]
}

メタデータの活用

// メタデータに基づく機能
function getRarityScore(uint256 tokenId) public view returns (uint256) {
    // メタデータのattributesから希少性スコアを計算
    // 例:Legendary = 100, Rare = 50, Common = 10
}

function getTraitValue(uint256 tokenId, string memory traitType) public view returns (string memory) {
    // 特定の特性値を取得
    // 例:traitType = "Fur Color" → "Silver"
}

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

9.1 よくあるエラーと解決方法

コンパイルエラー

# エラー例
Error: Compilation failed. See above.

# 解決方法
1. Solidityバージョンの確認
2. OpenZeppelinバージョンの確認
3. インポートパスの確認

デプロイエラー

# エラー例
Error: insufficient funds for gas * price + value

# 解決方法
1. Ganacheの起動確認
2. アカウントの残高確認
3. ガス制限の調整

ミントエラー

# エラー例
Error: VM Exception while processing transaction: revert

# 解決方法
1. onlyOwner権限の確認
2. 受信者アドレスの確認
3. URI形式の確認

9.2 デバッグのベストプラクティス

ログの活用

// デバッグ用のログ出力
console.log("Current account:", accounts[0]);
console.log("Contract address:", spaceTiger.address);
console.log("Owner:", await spaceTiger.owner());

段階的なテスト

// 1. 基本情報の確認
await spaceTiger.name()
await spaceTiger.symbol()

// 2. 権限の確認
await spaceTiger.owner()

// 3. ミントの実行
await spaceTiger.safeMint(accounts[1], "test.json")

// 4. 結果の確認
await spaceTiger.ownerOf(0)

10. 実用的な応用

10.1 バッチミント機能

// 複数NFTの一括ミント
function batchMint(address[] memory recipients, string[] memory uris) public onlyOwner {
    require(recipients.length == uris.length, "Array length mismatch");
    
    for(uint i = 0; i < recipients.length; i++) {
        safeMint(recipients[i], uris[i]);
    }
}

10.2 条件付きミント

// 条件付きミント
mapping(address => bool) public whitelist;
uint256 public constant MAX_SUPPLY = 1000;

function whitelistMint(string memory uri) public {
    require(whitelist[msg.sender], "Not whitelisted");
    require(_nextTokenId < MAX_SUPPLY, "Max supply reached");
    
    safeMint(msg.sender, uri);
}

10.3 ロイヤリティ機能

// ロイヤリティの実装
uint256 public constant ROYALTY_PERCENTAGE = 250; // 2.5%

function royaltyInfo(uint256 tokenId, uint256 salePrice) 
    public view returns (address, uint256) {
    return (owner(), (salePrice * ROYALTY_PERCENTAGE) / 10000);
}

11. セキュリティの考慮事項

11.1 アクセス制御

// 適切なアクセス制御
modifier onlyOwner() {
    require(msg.sender == owner(), "Not the owner");
    _;
}

// 緊急停止機能
bool public paused;

modifier whenNotPaused() {
    require(!paused, "Contract is paused");
    _;
}

function emergencyPause() public onlyOwner {
    paused = true;
}

11.2 リエントランシー攻撃への対策

// リエントランシー攻撃の防止
bool private _locked;

modifier nonReentrant() {
    require(!_locked, "ReentrancyGuard: reentrant call");
    _locked = true;
    _;
    _locked = false;
}

function safeMint(address to, string memory uri) 
    public onlyOwner nonReentrant returns (uint256) {
    // ミント処理
}

12. 学習の成果

12.1 習得した概念

  1. 開発フレームワーク比較: Truffle、Hardhat、Foundryの特徴と選択指針
  2. Truffleプロジェクト構造: ディレクトリ構成とファイルの役割
  3. NFTコントラクト実装: ERC721標準の実装と継承関係
  4. デプロイプロセス: マイグレーションとGanacheでのデプロイ
  5. コントラクト対話: Truffle Consoleとスクリプトでの対話
  6. NFTミント: 安全なNFT発行とメタデータ管理

12.2 実装スキル

  • Truffleプロジェクトの設定と管理
  • ERC721コントラクトの実装とカスタマイズ
  • デプロイスクリプトの作成と実行
  • Ganacheでの開発環境の構築
  • NFTのミントと管理の実装
  • メタデータの構造と標準化

12.3 技術的な理解

  • 開発フレームワーク: 各フレームワークの特徴と使い分け
  • NFT標準: ERC721の実装と拡張機能
  • デプロイメント: ブロックチェーンへのコントラクト展開
  • ローカル開発: Ganacheでの開発環境構築
  • メタデータ: NFTの価値決定要因
  • セキュリティ: スマートコントラクトのセキュリティ考慮事項

13. 今後の学習への応用

13.1 Hardhatでの実装

  • Hardhatプロジェクトの設定と管理
  • TypeScriptサポートの活用
  • プラグインエコシステムの利用
  • デバッグ機能の活用

13.2 Foundryでの実装

  • Rustベースの高速開発環境
  • ガス最適化の詳細分析
  • 低レベル制御の活用
  • フォージとキャストの使用

13.3 高度なNFT機能

  • 動的メタデータの実装
  • ロイヤリティシステムの構築
  • マーケットプレイスとの連携
  • クロスチェーン対応の実装

参考:

Discussion