🐕

Solidity基礎学習18日目(スマートコントラクトの検証とERC20)

に公開

Solidity基礎学習 - スマートコントラクト検証とERC20トークンの実装

日付: 2025年9月12日
学習内容: スマートコントラクトの検証プロセスとERC20トークンの実装、アクセス制御システムについて

1. スマートコントラクト検証の基本概念

030_Verification.solの概要

スマートコントラクトの検証プロセスについて学習します。EtherScanでの検証手順、バイトコードの確認、ソースコードの公開について理解し、透明性のあるスマートコントラクト開発ができるようになります。

重要なポイント

スマートコントラクト検証の基本構造

contract SimpleContract {
    // 残高管理システム
    mapping(address => uint) public userBalance;

    constructor() {
        userBalance[msg.sender] = 100;
    }

    function transfer(address to, uint amount) public {
        userBalance[msg.sender] -= amount;
        userBalance[to] += amount;
    }

    function getBalance(address _addr) public view returns(uint) {
        return userBalance[_addr];
    }
}

コントラクトの特徴:

  • 残高管理: ユーザーの残高を追跡
  • 送金機能: ユーザー間でのトークン送金
  • 照会機能: 任意のアドレスの残高確認
  • 検証対応: EtherScanでの検証が可能

2. EtherScan検証プロセスの詳細分析

2.1 検証手順の実装

検証の流れ

// 1. Sepoliaテストネットにデプロイ
// 2. トランザクションハッシュでデプロイを確認
// 3. コントラクトアドレスでバイトコードを確認
// 4. Verifyボタンから検証を開始
// 5. 必要な情報を入力し、ソースコードをペースト
// 6. 検証完了後、ソースコードが公開される

検証の重要性:

  • 透明性: ソースコードの公開による信頼性向上
  • セキュリティ: コードの検証による脆弱性の早期発見
  • ユーザビリティ: ユーザーがコントラクトの動作を理解可能

2.2 検証に必要な情報

必須情報:

// コントラクトの基本情報
contract SimpleContract {
    // コントラクト名
    // コンパイラバージョン
    // ライセンス情報
    // ソースコード
}

検証プロセスの詳細:

  • コントラクト名: デプロイ時に使用した名前
  • コンパイラバージョン: 使用したSolidityのバージョン
  • 最適化設定: コンパイル時の最適化オプション
  • コンストラクタ引数: デプロイ時の引数

3. ERC20トークンの実装

3.1 BeerTokenの基本構造

実装コード

contract BeerToken is ERC20, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    event BeerPurchased(address indexed receiver, address indexed buyer);

    constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
        _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
        _grantRole(MINTER_ROLE, minter);
    }
}

機能の説明:

  • ERC20継承: 標準的なトークン機能の実装
  • アクセス制御: ロールベースの権限管理
  • イベントログ: ビール購入の記録
  • 権限管理: 管理者とミント権限者の分離

3.2 継承システムの詳細

多重継承の仕組み:

contract BeerToken is ERC20, AccessControl {
    // ERC20: トークンの基本機能
    // AccessControl: アクセス制御機能
    // 両方の機能を組み合わせて使用
}

継承の優先順位:

  • ERC20: トークンの基本機能(送金、残高管理)
  • AccessControl: 権限管理機能(ロール、修飾子)

4. アクセス制御システムの実装

4.1 ロールベースアクセス制御

実装コード

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
    _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
    _grantRole(MINTER_ROLE, minter);
}

機能の説明:

  • MINTER_ROLE: トークン発行権限の識別子
  • DEFAULT_ADMIN_ROLE: システム管理者権限
  • 権限の分離: 管理者とミント権限者の役割分担

4.2 ロールの管理機能

管理者権限の活用:

// 新しいミント権限者の追加
function addMinter(address newMinter) public onlyRole(DEFAULT_ADMIN_ROLE) {
    _grantRole(MINTER_ROLE, newMinter);
}

// ミント権限者の削除
function removeMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
    _revokeRole(MINTER_ROLE, minter);
}

セキュリティの特徴:

  • 最小権限の原則: 必要最小限の権限のみ付与
  • 権限の分離: 管理者とミント権限者の分離
  • 柔軟な管理: 必要に応じて権限の追加・削除

5. トークン発行機能の実装

5.1 ミント機能の詳細

実装コード

function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
    _mint(to, amount * 10 ** decimals());
}

機能の説明:

  • 権限チェック: MINTER_ROLEを持つアドレスのみ実行可能
  • トークン発行: 指定されたアドレスに新しいトークンを発行
  • 小数点調整: 10 ** decimals()を掛けて小数点以下の問題を回避
  • 総供給量更新: 発行されたトークンが総供給量に反映

小数点調整の重要性:

// ERC20の標準的な小数点設定
function decimals() public view virtual returns (uint8) {
    return 18;  // 通常は18桁の小数点
}

// 実際のトークン量の計算
uint256 actualAmount = amount * 10 ** decimals();
// 例: amount = 1の場合、actualAmount = 1 * 10^18 = 1000000000000000000

5.2 ミント機能のセキュリティ

権限管理の実装:

// 修飾子による権限チェック
modifier onlyRole(bytes32 role) {
    _checkRole(role);
    _;
}

// 権限の確認
function _checkRole(bytes32 role) internal view virtual {
    _checkRole(role, _msgSender());
}

セキュリティの特徴:

  • 自動権限チェック: 関数実行前の自動的な権限確認
  • 権限の継承: AccessControlから継承された機能
  • 柔軟な拡張: 新しいロールの追加が容易

6. ビール購入機能の実装

6.1 自己購入機能

実装コード

function buyOneBeer() public {
    _burn(_msgSender(), 1 * 10 ** decimals());
    emit BeerPurchased(_msgSender(), _msgSender());
}

機能の説明:

  • 自己完結型購入: 自分のトークンでビールを購入
  • トークン消費: 1トークンを消費してビールを購入
  • 小数点調整: 10 ** decimals()を掛けて小数点以下の問題を回避
  • イベント発火: 購入記録をブロックチェーンに記録

小数点調整の実装:

// 実際の消費量の計算
uint256 actualAmount = 1 * 10 ** decimals();
// 例: decimals() = 18の場合、actualAmount = 1 * 10^18 = 1000000000000000000

6.2 代理購入機能

実装コード

function buyOneBeerFrom(address account) public{
    _spendAllowance(account, _msgSender(), 1 * 10 ** decimals());
    _burn(account, 1 * 10 ** decimals());
    emit BeerPurchased(_msgSender(), account);
}

機能の説明:

  • 代理購入: 他人のトークンでビールを購入
  • 許可確認: 事前の許可が必要
  • 小数点調整: 10 ** decimals()を掛けて小数点以下の問題を回避
  • 権限管理: 許可された範囲内でのみ実行可能

小数点調整の一貫性:

// 許可量の確認と消費量の調整
_spendAllowance(account, _msgSender(), 1 * 10 ** decimals());  // 許可量の確認
_burn(account, 1 * 10 ** decimals());                          // 実際の消費

7. 許可システムの詳細

7.1 許可の確認機能

許可確認の実装:

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
    uint256 currentAllowance = allowance(owner, spender);
    if (currentAllowance != type(uint256).max) {
        require(currentAllowance >= amount, "ERC20: insufficient allowance");
        unchecked {
            _approve(owner, spender, currentAllowance - amount);
        }
    }
}

機能の説明:

  • 許可量確認: 現在の許可量をチェック
  • 不足チェック: 必要な量が許可されているか確認
  • 許可量更新: 使用した分だけ許可量を減らす
  • 小数点対応: 調整された量での許可確認

7.2 小数点調整の重要性

ERC20の小数点システム:

// 標準的な小数点設定
function decimals() public view virtual returns (uint8) {
    return 18;  // 18桁の小数点
}

// 実際のトークン量の計算例
uint256 userInput = 1;                    // ユーザーが入力した量
uint256 actualAmount = userInput * 10 ** decimals();  // 実際のトークン量
// 結果: 1000000000000000000 (1 * 10^18)

小数点調整の理由:

  • 整数演算: Solidityは小数点を直接サポートしない
  • 精度保持: 小数点以下の精度を保持
  • 標準準拠: ERC20標準に準拠した実装
  • 互換性: 他のERC20トークンとの互換性

7.3 許可の付与機能

許可付与の実装:

// ERC20から継承された関数
function approve(address spender, uint256 amount) public virtual returns (bool) {
    _approve(_msgSender(), spender, amount);
    return true;
}

使用例:

// ユーザーAがユーザーBに5トークンの使用許可を与える
approve(ユーザーBのアドレス, 5 * 10 ** decimals());

// ユーザーBがユーザーAのトークンを使用
buyOneBeerFrom(ユーザーAのアドレス);

小数点調整の一貫性:

// 許可の付与時も小数点調整が必要
uint256 approvalAmount = 5 * 10 ** decimals();  // 5トークンの許可
approve(spender, approvalAmount);

// 使用時も同じ調整が適用される
_spendAllowance(account, _msgSender(), 1 * 10 ** decimals());

8. イベントシステムの実装

8.1 イベントの定義

実装コード

event BeerPurchased(address indexed receiver, address indexed buyer);

機能の説明:

  • 永続的記録: ブロックチェーン上に永続的に記録
  • 検索可能: indexedパラメータによる効率的な検索
  • 透明性: 誰でも購入履歴を確認可能

8.2 イベントの活用

フロントエンドでの監視:

// 特定のユーザーのビール購入を監視
contract.on("BeerPurchased", (receiver, buyer, event) => {
    console.log(`ビール購入: 受け取り人=${receiver}, 支払い人=${buyer}`);
});

// 特定のアドレスのイベントをフィルタリング
const filter = contract.filters.BeerPurchased(null, userAddress);

統計データの収集:

  • ビールの購入回数
  • 特定ユーザーの購入履歴
  • 売上データの分析

9. 継承システムの詳細分析

9.1 多重継承の仕組み

継承チェーン:

BeerToken
    ├── ERC20
    │   ├── Context ← _msgSender()が定義されている
    │   ├── IERC20
    │   └── IERC20Metadata
    └── AccessControl
        ├── Context ← _msgSender()が定義されている
        ├── IAccessControl
        └── IERC165

継承の優先順位:

  • ERC20: 最初に継承されるため、ERC20の機能が優先
  • AccessControl: 2番目に継承される
  • Context: 両方から継承されるが、実装は同じ

9.2 関数の継承とオーバーライド

継承された関数:

// ERC20から継承
function _mint(address to, uint256 amount) internal virtual;
function _burn(address account, uint256 amount) internal virtual;
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual;

// AccessControlから継承
function _grantRole(bytes32 role, address account) internal virtual;
function _revokeRole(bytes32 role, address account) internal virtual;
modifier onlyRole(bytes32 role);

10. コンストラクタの詳細分析

10.1 親コンストラクタの呼び出し

実装コード

constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
    _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
    _grantRole(MINTER_ROLE, minter);
}

実行順序:

  1. ERC20("BeerToken", "BRK"): 親コンストラクタの実行
  2. BeerTokenのコンストラクタ: 子コンストラクタの実行

10.2 初期化プロセス

ERC20の初期化:

// ERC20コンストラクタ内で実行
_name = "BeerToken";    // トークン名
_symbol = "BRK";        // トークンシンボル

BeerTokenの初期化:

// BeerTokenコンストラクタ内で実行
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(MINTER_ROLE, minter);

11. セキュリティ設計の詳細

11.1 権限管理の階層

権限レベルの設計:

// レベル1: 管理者(完全な権限)
DEFAULT_ADMIN_ROLE

// レベル2: ミント権限者(トークン発行権限)
MINTER_ROLE

// レベル3: 一般ユーザー(制限付き権限)
// トークンの送金、購入のみ可能

権限チェックの実装:

// 管理者権限のチェック
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");

// ミント権限のチェック
require(hasRole(MINTER_ROLE, msg.sender), "Not minter");

11.2 セキュリティのベストプラクティス

最小権限の原則:

// 悪い例:管理者が直接mintする
function adminMint(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) {
    _mint(to, amount);
}

// 良い例:管理者はミント権限者を管理するのみ
function addMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
    _grantRole(MINTER_ROLE, minter);
}

12. 実用的な応用

12.1 DeFiプロトコルとの連携

外部プロトコルとの相互作用:

// Uniswapでの流動性提供
function provideLiquidity(uint amount) public {
    approve(uniswapRouter, amount);
    // Uniswapでの流動性提供
}

// Compoundでの預金
function depositToCompound(uint amount) public {
    approve(compoundContract, amount);
    // Compoundでの預金
}

12.2 マルチシグウォレットの拡張

高度な権限管理:

contract AdvancedBeerToken {
    // 時間ベースの権限
    mapping(address => uint) public permissionExpiry;
    
    // 日次送金制限
    mapping(address => uint) public dailyLimit;
    mapping(address => uint) public dailySpent;
    mapping(address => uint) public lastSpendDate;
    
    function checkDailyLimit(address user, uint amount) internal {
        if(block.timestamp > lastSpendDate[user] + 1 days) {
            dailySpent[user] = 0;
            lastSpendDate[user] = block.timestamp;
        }
        require(dailySpent[user] + amount <= dailyLimit[user], "Daily limit exceeded");
        dailySpent[user] += amount;
    }
}

13. エラーハンドリングと最適化

13.1 包括的なエラーハンドリング

送金失敗の処理:

function safeTransfer(address to, uint256 amount) public returns (bool) {
    require(balanceOf(msg.sender) >= amount, "Insufficient balance");
    require(to != address(0), "Transfer to zero address");
    
    _transfer(msg.sender, to, amount);
    return true;
}

13.2 ガス最適化

効率的な送金処理:

contract GasOptimizedBeerToken {
    // バッチ送金機能
    function batchTransfer(
        address[] memory recipients,
        uint256[] memory amounts
    ) public {
        require(recipients.length == amounts.length, "Array length mismatch");
        
        for(uint i = 0; i < recipients.length; i++) {
            _transfer(msg.sender, recipients[i], amounts[i]);
        }
    }
}

14. 実装のポイント

14.1 セキュリティのベストプラクティス

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

contract SecureBeerToken {
    bool private locked;
    
    modifier noReentrancy() {
        require(!locked, "Reentrancy detected");
        locked = true;
        _;
        locked = false;
    }
    
    function secureBuyBeer() public noReentrancy {
        _burn(msg.sender, 1);
        emit BeerPurchased(msg.sender, msg.sender);
    }
}

14.2 イベントログの実装

透明性の確保:

contract TransparentBeerToken {
    event AdminChanged(address indexed oldAdmin, address indexed newAdmin);
    event MinterAdded(address indexed minter);
    event MinterRemoved(address indexed minter);
    event BeerPurchased(address indexed receiver, address indexed buyer);
    
    function addMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
        _grantRole(MINTER_ROLE, minter);
        emit MinterAdded(minter);
    }
}

15. 学習の成果

15.1 習得した概念

  1. スマートコントラクト検証: EtherScanでの検証プロセス
  2. ERC20トークン: 標準的なトークンの実装
  3. アクセス制御: ロールベースの権限管理
  4. 多重継承: 複数コントラクトの機能統合
  5. イベントシステム: ブロックチェーン上のログ記録
  6. 許可システム: 第三者による制限付きトークン使用
  7. 小数点調整: 10 ** decimals()による精度管理

15.2 実装スキル

  • 継承システムの設計と実装
  • ロールベースアクセス制御の実装
  • ERC20標準の準拠
  • イベントログの活用
  • セキュリティ強化のベストプラクティス

15.3 技術的な理解

  • 多重継承: 複数コントラクトの機能統合
  • アクセス制御: ロールベースの権限管理
  • ERC20標準: トークンの標準的な実装
  • イベントシステム: ブロックチェーン上のログ記録
  • 検証プロセス: EtherScanでのソースコード公開
  • 小数点管理: ERC20の精度システムと整数演算

16. 今後の学習への応用

16.1 発展的な機能

  • 時間ベースの権限: 期限付きのトークン使用権限
  • マルチチェーン対応: 複数ブロックチェーンでの動作
  • オラクル連携: 外部データに基づく自動実行
  • 高度なセキュリティ: より複雑な攻撃への対策

16.2 セキュリティの向上

  • 監査機能: トークン使用履歴の追跡と分析
  • 異常検知: 疑わしい活動の自動検出
  • 緊急停止: セキュリティ侵害時の即座な対応
  • 復旧機能: 攻撃後の状態回復

16.3 パフォーマンスの最適化

  • バッチ処理: 複数送金の効率的な実行
  • ガス最適化: 送金処理のコスト削減
  • スケーラビリティ: 大量送金の効率的な処理
  • メタトランザクション: ガス代不要の送金

参考:

Discussion