😺
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.sender
をaddress 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 習得した概念
- スマートコントラクトウォレット: セキュアな資金管理システムの実装
- ガーディアン機能: 多要素認証による緊急時回復システム
- 許可額システム: 第三者による制限付き送金機能
- 低レベルコール: 柔軟な送金と関数呼び出しの組み合わせ
- ABIエンコーディング: 関数シグネチャのバイトコード変換
- Receive関数: ETH受信の自動処理メカニズム
12.2 実装スキル
- 階層的権限管理の設計と実装
- 多要素認証システムの実装
- 低レベルコールの安全な活用
- 包括的なエラーハンドリングの実装
- セキュリティ強化のベストプラクティス
12.3 技術的な理解
- payable型: ETH送金可能なアドレスの管理
- 低レベルコール: 外部コントラクトとの柔軟な相互作用
- ABIエンコーディング: 関数呼び出しのバイトコード変換
- ガーディアンシステム: 分散化された権限管理
- セキュリティ: リエントランシー攻撃への対策
13. 今後の学習への応用
13.1 発展的な機能
- 時間ベースの権限: 期限付きの送金権限
- マルチチェーン対応: 複数ブロックチェーンでの動作
- オラクル連携: 外部データに基づく自動実行
- 高度なセキュリティ: より複雑な攻撃への対策
13.2 セキュリティの向上
- 監査機能: 送金履歴の追跡と分析
- 異常検知: 疑わしい活動の自動検出
- 緊急停止: セキュリティ侵害時の即座な対応
- 復旧機能: 攻撃後の状態回復
13.3 パフォーマンスの最適化
- バッチ処理: 複数送金の効率的な実行
- ガス最適化: 送金処理のコスト削減
- スケーラビリティ: 大量送金の効率的な処理
- メタトランザクション: ガス代不要の送金
参考:
Discussion