😺

Solidity基礎学習13日目(スマートコントコントラクトウォレットの実装)

に公開

Solidity基礎学習 - スマートコントラクトウォレットの実装

日付: 2025年9月6日
学習内容: スマートコントラクトウォレットの実装とガーディアン機能、低レベルコールの活用について

1. スマートコントラクトウォレットの基本概念

022_SmartContractWallet.solの概要

スマートコントラクトウォレットの実装について学習します。所有者権限管理、ガーディアン機能、allowance(許可額)システム、低レベルコールによる柔軟な送金機能について理解し、セキュアなウォレットシステムを実装できるようになります。

重要なポイント

スマートコントラクトウォレットの基本構造

contract SecureWallet {
    // ウォレットの所有者(payable型で宣言)
    address payable public walletOwner;

    // 許可額管理システム
    mapping(address => uint) public userAllowance;
    mapping(address => bool) public isAuthorizedToSend;

    // ガーディアン機能
    mapping(address => bool) public guardians;
    address payable candidateOwner;
    mapping(address => mapping(address => bool)) guardianVoteRecord;
    uint guardianVoteCount;
    uint public constant requiredGuardianVotes = 3;

    constructor() {
        walletOwner = payable(msg.sender);
    }
}

コントラクトの特徴:

  • 所有者権限管理: ウォレットの所有者による完全な制御
  • ガーディアン機能: 緊急時の所有者回復システム
  • 許可額システム: 第三者による制限付き送金機能
  • 低レベルコール: 柔軟な送金と関数呼び出しの組み合わせ

2. 所有者権限管理の詳細分析

2.1 コンストラクタの実装

実装コード

constructor() {
    walletOwner = payable(msg.sender);
}

機能の説明:

  • 初期所有者設定: コントラクトデプロイ者が自動的に所有者になる
  • payable型キャスト: msg.senderaddress payable型に変換
  • 権限の確立: 所有者のみが特定の関数を実行可能

payable型の重要性:

// payable型で宣言することで利用可能になるメソッド
address payable owner = payable(msg.sender);
owner.transfer(amount);           // ETH送金
owner.send(amount);              // ETH送金(戻り値で成功確認)
owner.call{value: amount}("");   // 低レベルコール

2.2 所有者権限の活用

将来の機能拡張への対応:

// 緊急時の資金回収機能(将来追加される可能性)
function emergencyWithdraw(uint amount) public {
    require(msg.sender == walletOwner, "Only owner");
    require(amount <= address(this).balance, "Insufficient balance");
    walletOwner.transfer(amount);  // payable型だから可能
}

// 手数料の支払い機能
function payMaintenanceFee(uint feeAmount) public {
    require(msg.sender == walletOwner, "Only owner");
    walletOwner.transfer(feeAmount);
}

3. ガーディアン機能の実装

3.1 ガーディアン設定機能

実装コード

function setGuardian(address _guardian, bool _isGuardian) public {
    require(msg.sender == walletOwner, "you are not the owner, aborting");
    guardians[_guardian] = _isGuardian;
}

機能の説明:

  • ガーディアン管理: 所有者のみがガーディアンを設定可能
  • 権限委譲: 信頼できる第三者に緊急時の権限を委譲
  • セキュリティ: 所有者の秘密鍵紛失時の回復手段

3.2 新しい所有者の提案機能

実装コード

function proposeNewOwner(address payable _newOwner) public {
    require(guardians[msg.sender], "You are no guardian of this wallet, aborting");
    require(guardianVoteRecord[_newOwner][msg.sender] == false, "You already voted, aborting");
    
    if(_newOwner != candidateOwner) {
        candidateOwner = _newOwner;
        guardianVoteCount = 0;
    }

    guardianVoteCount++;

    if(guardianVoteCount >= requiredGuardianVotes) {
        walletOwner = candidateOwner;
        candidateOwner = payable(address(0));  // 候補者をリセット
    }
}

機能の説明:

  • 多要素認証: 3人のガーディアンによる承認が必要
  • 重複投票防止: 同じガーディアンが複数回投票できない
  • 候補者リセット: 新しい候補者が提案されると投票数をリセット
  • 所有者変更: 3票集まると自動的に所有者が変更される

セキュリティの特徴:

// 投票プロセスの安全性
require(guardians[msg.sender], "You are no guardian");           // ガーディアン認証
require(guardianVoteRecord[_newOwner][msg.sender] == false);     // 重複投票防止
require(guardianVoteCount >= requiredGuardianVotes);             // 必要票数の確認

4. 許可額システムの実装

4.1 許可額設定機能

実装コード

function setAllowance(address _for, uint _amount) public {
    require(msg.sender == walletOwner, "You are not the owner, aborting!");
    userAllowance[_for] = _amount;
    
    if(_amount > 0){
        isAuthorizedToSend[_for] = true;
    } else {
        isAuthorizedToSend[_for] = false;
    }
}

機能の説明:

  • 許可額管理: 第三者に送金可能な金額を設定
  • 自動権限付与: 許可額が0より大きい場合、送金権限を自動付与
  • 権限取り消し: 許可額を0に設定すると送金権限を自動取り消し

4.2 送金権限の管理

実装コード

function denySending(address _from) public {
    require(msg.sender == walletOwner, "You are not the owner, aborting!");
    isAuthorizedToSend[_from] = false;
}

機能の説明:

  • 緊急停止: 所有者が任意のアドレスの送金権限を即座に停止
  • セキュリティ: 疑わしい活動を検出した際の緊急対応
  • 制御性: 所有者による完全な送金制御

5. 低レベルコールによる送金機能

5.1 柔軟な送金システム

実装コード

function transfer(address payable _to, uint _amount, bytes memory payload) public returns (bytes memory) {
    require(_amount <= address(this).balance, "Can't send more than the contract owns, aborting.");
    
    if(msg.sender != walletOwner) {
        require(isAuthorizedToSend[msg.sender], "You are not allowed to send any transactions, aborting");
        require(userAllowance[msg.sender] >= _amount, "You are trying to send more than you are allowed to, aborting");
        userAllowance[msg.sender] -= _amount;
    }

    (bool success, bytes memory returnData) = _to.call{value: _amount}(payload);
    require(success, "Transaction failed, aborting");
    return returnData;
}

機能の説明:

  • 柔軟な送金: ETH送金と関数呼び出しを同時に実行
  • 権限チェック: 所有者以外は許可額内での送金のみ可能
  • 低レベルコール: 任意のコントラクト関数を呼び出し可能
  • エラーハンドリング: 送金失敗時の適切な処理

5.2 低レベルコールの仕組み

_to.call{value: _amount}(payload)の動作:

// 1. _toに_amount分のETHを送金
// 2. 同時にpayloadで指定された関数を呼び出し
// 3. 関数の戻り値をreturnDataに格納
// 4. 成功/失敗をsuccessに格納
(bool success, bytes memory returnData) = _to.call{value: _amount}(payload);

payloadの使用例:

// 単純なETH送金
transfer(recipientAddress, amount, "");

// コントラクト関数の呼び出し
bytes memory payload = abi.encodeWithSignature("deposit()");
transfer(contractAddress, amount, payload);

// パラメータ付き関数の呼び出し
bytes memory payload = abi.encodeWithSignature("transfer(address,uint256)", recipient, amount);
transfer(tokenContract, 0, payload);

6. Receive関数の実装

6.1 ETH受信機能

実装コード

receive() external payable {}

機能の説明:

  • ETH受信: 外部からのETH送金を自動的に受け取る
  • payable修飾子: ETHを受け取るために必須
  • 自動実行: ETH送金時に自動的に呼び出される

payable修飾子の重要性:

// payableなし(エラーになる)
receive() external {}  // ETH送信時にエラー

// payableあり(正常動作)
receive() external payable {}  // ETH送信時に正常に受け取れる

6.2 Receive関数の動作

自動実行の条件:

  • transfer()による送金
  • send()による送金
  • call()による送金
  • 直接送金

実際の動作例:

// 外部からの送金
contractAddress.call{value: 1000000000000000000}("");  // 1 ETH送金
// → receive()関数が自動実行される
// → コントラクトの残高が1 ETH増加

7. 関数シグネチャとABIエンコーディング

7.1 関数シグネチャの仕組み

関数シグネチャの計算:

// 関数シグネチャ: "deposit()"
// keccak256ハッシュ: 0xd0e30db0...
// 最初の4バイト: 0xd0e30db0
bytes4 functionSelector = bytes4(keccak256("deposit()"));

ABIエンコーディングの使用:

// 関数シグネチャのエンコード
bytes memory payload = abi.encodeWithSignature("deposit()");
// 結果: 0xd0e30db0

// パラメータ付き関数のエンコード
bytes memory payload = abi.encodeWithSignature("transfer(address,uint256)", recipient, amount);
// 結果: 0xa9059cbb + パラメータデータ

7.2 低レベルコールでの関数呼び出し

コントラクト関数の呼び出し:

// 外部コントラクトのdeposit()関数を呼び出し
bytes memory payload = abi.encodeWithSignature("deposit()");
(bool success, bytes memory data) = contractAddress.call{value: 1000}(payload);

// 成功時: success = true, dataに関数の戻り値
// 失敗時: success = false, dataにエラー情報

EOAへの送金:

// EOAに送金(関数呼び出しは無視される)
bytes memory payload = abi.encodeWithSignature("deposit()");
(bool success, bytes memory data) = eoaAddress.call{value: 1000}(payload);

// 成功時: success = true, dataは空
// ETH送金のみ実行され、関数は実行されない

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

8.1 権限管理の階層

権限レベルの設計:

// レベル1: 所有者(完全な権限)
address payable public walletOwner;

// レベル2: ガーディアン(緊急時権限)
mapping(address => bool) public guardians;

// レベル3: 許可されたユーザー(制限付き権限)
mapping(address => bool) public isAuthorizedToSend;
mapping(address => uint) public userAllowance;

権限チェックの実装:

// 所有者権限のチェック
require(msg.sender == walletOwner, "You are not the owner, aborting!");

// ガーディアン権限のチェック
require(guardians[msg.sender], "You are no guardian of this wallet, aborting");

// 送金権限のチェック
require(isAuthorizedToSend[msg.sender], "You are not allowed to send any transactions, aborting");

8.2 多要素認証システム

ガーディアン投票の安全性:

// 重複投票の防止
require(guardianVoteRecord[_newOwner][msg.sender] == false, "You already voted, aborting");

// 必要票数の確認
require(guardianVoteCount >= requiredGuardianVotes);

// 候補者のリセット
if(_newOwner != candidateOwner) {
    candidateOwner = _newOwner;
    guardianVoteCount = 0;  // 新しい候補者で投票数をリセット
}

9. 実用的な応用

9.1 DeFiプロトコルとの連携

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

// Uniswapでの流動性提供
function provideLiquidity(uint amount) public {
    bytes memory payload = abi.encodeWithSignature("addLiquidityETH()");
    transfer(uniswapRouter, amount, payload);
}

// Compoundでの預金
function depositToCompound(uint amount) public {
    bytes memory payload = abi.encodeWithSignature("mint()");
    transfer(compoundContract, amount, payload);
}

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

高度な権限管理:

contract AdvancedWallet {
    // 時間ベースの権限
    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;
    }
}

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

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

送金失敗の処理:

function safeTransfer(address payable _to, uint _amount, bytes memory payload) public returns (bytes memory) {
    require(_amount <= address(this).balance, "Insufficient balance");
    
    (bool success, bytes memory returnData) = _to.call{value: _amount}(payload);
    
    if(!success) {
        // 失敗時の詳細なエラー情報を取得
        if(returnData.length > 0) {
            assembly {
                let returndata_size := mload(returnData)
                revert(add(32, returnData), returndata_size)
            }
        } else {
            revert("Transaction failed without error message");
        }
    }
    
    return returnData;
}

10.2 ガス最適化

効率的な送金処理:

contract GasOptimizedWallet {
    // バッチ送金機能
    function batchTransfer(
        address payable[] memory recipients,
        uint[] memory amounts,
        bytes[] memory payloads
    ) public {
        require(recipients.length == amounts.length, "Array length mismatch");
        require(recipients.length == payloads.length, "Array length mismatch");
        
        for(uint i = 0; i < recipients.length; i++) {
            transfer(recipients[i], amounts[i], payloads[i]);
        }
    }
}

11. 実装のポイント

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

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

contract SecureWallet {
    bool private locked;
    
    modifier noReentrancy() {
        require(!locked, "Reentrancy detected");
        locked = true;
        _;
        locked = false;
    }
    
    function secureTransfer(address payable _to, uint _amount, bytes memory payload) 
        public noReentrancy returns (bytes memory) {
        return transfer(_to, _amount, payload);
    }
}

11.2 イベントログの実装

透明性の確保:

contract TransparentWallet {
    event OwnerChanged(address indexed oldOwner, address indexed newOwner);
    event GuardianAdded(address indexed guardian);
    event GuardianRemoved(address indexed guardian);
    event AllowanceSet(address indexed user, uint amount);
    event TransferExecuted(address indexed from, address indexed to, uint amount);
    
    function proposeNewOwner(address payable _newOwner) public {
        // ... 既存のロジック ...
        
        if(guardianVoteCount >= requiredGuardianVotes) {
            emit OwnerChanged(walletOwner, candidateOwner);
            walletOwner = candidateOwner;
            candidateOwner = payable(address(0));
        }
    }
}

12. 学習の成果

12.1 習得した概念

  1. スマートコントラクトウォレット: セキュアな資金管理システムの実装
  2. ガーディアン機能: 多要素認証による緊急時回復システム
  3. 許可額システム: 第三者による制限付き送金機能
  4. 低レベルコール: 柔軟な送金と関数呼び出しの組み合わせ
  5. ABIエンコーディング: 関数シグネチャのバイトコード変換
  6. Receive関数: ETH受信の自動処理メカニズム

12.2 実装スキル

  • 階層的権限管理の設計と実装
  • 多要素認証システムの実装
  • 低レベルコールの安全な活用
  • 包括的なエラーハンドリングの実装
  • セキュリティ強化のベストプラクティス

12.3 技術的な理解

  • payable型: ETH送金可能なアドレスの管理
  • 低レベルコール: 外部コントラクトとの柔軟な相互作用
  • ABIエンコーディング: 関数呼び出しのバイトコード変換
  • ガーディアンシステム: 分散化された権限管理
  • セキュリティ: リエントランシー攻撃への対策

13. 今後の学習への応用

13.1 発展的な機能

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

13.2 セキュリティの向上

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

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

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

参考:

Discussion