💭

Solidity基礎学習17日目(単位指定とコントラクト破棄機能)

に公開

Solidity基礎学習 - 単位指定とコントラクト破棄の実装

日付: 2025年9月11日
学習内容: 単位指定(ether、gwei、wei、時間単位)の活用とコントラクト破棄機能の実装について

1. 単位指定システムの基本概念

028_Units.solの概要

Solidityにおける単位指定システムについて学習します。Ether単位(ether、gwei、wei)、時間単位(seconds、minutes、hours、days、weeks)の正しい使用方法、数値の可読性向上、計算の簡素化について理解し、より保守性の高いコードを書けるようになります。

重要なポイント

単位指定システムの基本構造

contract CurrencyUnits {
    modifier validAmount() {
        // 数値での指定(読みにくく、間違いやすい)
        // require(msg.value >= 1000000000000000000 && msg.value <= 2000000000000000000);
        
        // 指数表記での指定(読みやすいが計算が必要)
        // require(msg.value >= 1e18 && msg.value <= 2e18);
        
        // 単位指定での記述(最も読みやすく安全)
        require(msg.value >= 1 ether && msg.value <= 2 ether);
        _;
    }
}

コントラクトの特徴:

  • 可読性の向上: 数値の意味が明確になる
  • 保守性の向上: 計算ミスを防止
  • セキュリティ: 単位変換エラーを回避
  • 効率性: 開発時間の短縮

2. Ether単位の詳細分析

2.1 Ether単位の階層構造

基本単位の関係

// Ether単位の階層構造
1 ether = 1,000,000,000 gwei = 1,000,000,000,000,000,000 wei
1 gwei = 1,000,000,000 wei

単位の使用例:

contract EtherUnits {
    function demonstrateUnits() public pure returns (uint) {
        uint oneEther = 1 ether;           // 1 ETH
        uint halfEther = 0.5 ether;        // 0.5 ETH
        uint tenGwei = 10 gwei;            // 10 Gwei
        uint oneWei = 1 wei;               // 1 Wei
        
        return oneEther + halfEther + tenGwei + oneWei;
    }
}

2.2 実用的な使用例

送金制限の実装

contract PaymentLimits {
    uint public constant MIN_PAYMENT = 0.01 ether;  // 最小送金額
    uint public constant MAX_PAYMENT = 10 ether;    // 最大送金額
    
    modifier validPayment() {
        require(msg.value >= MIN_PAYMENT, "Payment too small");
        require(msg.value <= MAX_PAYMENT, "Payment too large");
        _;
    }
    
    function processPayment() public payable validPayment {
        // 送金処理
    }
}

単位指定の利点:

// 悪い例:数値で直接指定
require(msg.value >= 10000000000000000);  // 0.01 ETH(読みにくい)

// 良い例:単位指定を使用
require(msg.value >= 0.01 ether);         // 0.01 ETH(読みやすい)

3. 時間単位の詳細分析

3.1 時間単位の階層構造

基本時間単位の関係

// 時間単位の階層構造
1 weeks = 7 days
1 days = 24 hours  
1 hours = 60 minutes
1 minutes = 60 seconds
1 seconds = 1 seconds

時間単位の使用例:

contract TimeUnits {
    function demonstrateTimeUnits() public pure returns (uint) {
        uint oneWeek = 1 weeks;            // 1週間
        uint threeDays = 3 days;           // 3日間
        uint twoHours = 2 hours;           // 2時間
        uint thirtyMinutes = 30 minutes;   // 30分
        uint fortyFiveSeconds = 45 seconds; // 45秒
        
        return oneWeek + threeDays + twoHours + thirtyMinutes + fortyFiveSeconds;
    }
}

3.2 トークンセール期間の実装

実装コード

contract TokenSale {
    uint public saleStartTime;
    uint public saleEndTime;
    uint public constant SALE_DURATION = 7 days;
    
    constructor(uint startDelayInDays) {
        // 従来の方法(計算が複雑で間違いやすい)
        /*
        saleStartTime = block.timestamp + (startDelayInDays * 24 * 60 * 60);
        saleEndTime = saleStartTime + (7 * 24 * 60 * 60);
        */
        
        // 単位指定を使用(読みやすく安全)
        saleStartTime = block.timestamp + (startDelayInDays * 1 days);
        saleEndTime = saleStartTime + SALE_DURATION;
    }
    
    modifier duringSale() {
        require(block.timestamp >= saleStartTime, "Sale not started");
        require(block.timestamp <= saleEndTime, "Sale ended");
        _;
    }
}

時間単位の利点:

// 悪い例:手動計算
uint duration = 7 * 24 * 60 * 60;  // 7日間(計算ミスのリスク)

// 良い例:単位指定
uint duration = 7 days;            // 7日間(明確で安全)

4. コントラクト破棄機能の実装

4.1 従来のselfdestruct関数の問題

非推奨化の背景

// 従来の方法(非推奨)
function destroyContract() public {
    selfdestruct(payable(msg.sender));  // 警告が発生
}

selfdestructの問題点:

  • EIP-6780: Cancunハードフォーク以降の動作変更
  • セキュリティリスク: 予期しない動作の可能性
  • 将来の互換性: さらなる機能制限の可能性
  • 推奨されない: 新しいコントラクトでの使用は非推奨

4.2 モダンなコントラクト破棄の実装

実装コード

contract ModernDestruction {
    bool public isContractDestroyed = false;
    
    modifier contractActive() {
        require(!isContractDestroyed, "Contract has been terminated");
        _;
    }
    
    receive() external payable contractActive {}
    
    function terminateContract() public {
        isContractDestroyed = true;
        // 残りのEtherを安全に送信者に送信
        payable(msg.sender).transfer(address(this).balance);
    }
}

新実装の特徴:

  • 状態管理: boolフラグによる破棄状態の管理
  • 安全な資金回収: transferによるEther送信
  • 関数の無効化: モディファイアによる実行制御
  • 将来対応: 新しいSolidityバージョンに対応

5. モディファイアによる状態管理

5.1 状態チェックモディファイア

実装コード

contract StateManagement {
    bool public contractTerminated = false;
    
    modifier onlyActive() {
        require(!contractTerminated, "Contract is no longer active");
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can execute");
        _;
    }
    
    modifier validAmount(uint amount) {
        require(amount > 0, "Amount must be positive");
        require(amount <= address(this).balance, "Insufficient balance");
        _;
    }
}

モディファイアの利点:

  • 再利用性: 複数の関数で同じチェックを適用
  • 保守性: ロジックの変更が一箇所で可能
  • 可読性: 関数の意図が明確になる
  • セキュリティ: 一貫した権限チェック

5.2 複合モディファイアの活用

実装コード

contract AdvancedStateManagement {
    bool public isActive = true;
    address public owner;
    mapping(address => bool) public authorizedUsers;
    
    modifier activeAndAuthorized() {
        require(isActive, "Contract is inactive");
        require(msg.sender == owner || authorizedUsers[msg.sender], "Not authorized");
        _;
    }
    
    modifier activeOwnerOnly() {
        require(isActive, "Contract is inactive");
        require(msg.sender == owner, "Owner only");
        _;
    }
    
    function executeAuthorizedFunction() public activeAndAuthorized {
        // 認証されたユーザーまたは所有者のみ実行可能
    }
    
    function executeOwnerFunction() public activeOwnerOnly {
        // アクティブな所有者のみ実行可能
    }
}

6. 安全な資金回収システム

6.1 Ether回収の実装

実装コード

contract SafeWithdrawal {
    bool public isTerminated = false;
    address public owner;
    
    modifier notTerminated() {
        require(!isTerminated, "Contract is terminated");
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner");
        _;
    }
    
    receive() external payable notTerminated {}
    
    function emergencyWithdraw() public onlyOwner {
        uint balance = address(this).balance;
        require(balance > 0, "No funds to withdraw");
        
        isTerminated = true;
        payable(owner).transfer(balance);
    }
}

安全な回収のポイント:

  • 残高チェック: 送金前に残高の確認
  • 状態更新: 送金前に破棄状態を設定
  • 所有者限定: 権限のある者のみ実行可能
  • 一度のみ: 破棄後の再実行を防止

6.2 複数通貨対応の回収

実装コード

contract MultiCurrencyWithdrawal {
    bool public isTerminated = false;
    address public owner;
    mapping(address => uint) public tokenBalances;
    
    modifier notTerminated() {
        require(!isTerminated, "Contract is terminated");
        _;
    }
    
    function withdrawEther() public {
        require(msg.sender == owner, "Only owner");
        require(!isTerminated, "Contract terminated");
        
        isTerminated = true;
        uint ethBalance = address(this).balance;
        if(ethBalance > 0) {
            payable(owner).transfer(ethBalance);
        }
    }
    
    function withdrawToken(address tokenAddress) public {
        require(msg.sender == owner, "Only owner");
        require(!isTerminated, "Contract terminated");
        
        // ERC20トークンの回収(簡略化)
        // IERC20(tokenAddress).transfer(owner, tokenBalances[tokenAddress]);
    }
}

7. 実用的な応用

7.1 期間限定コントラクト

実装コード

contract TimeLimitedContract {
    uint public contractStartTime;
    uint public contractEndTime;
    bool public isTerminated = false;
    
    constructor(uint durationInDays) {
        contractStartTime = block.timestamp;
        contractEndTime = contractStartTime + (durationInDays * 1 days);
    }
    
    modifier duringContractPeriod() {
        require(block.timestamp >= contractStartTime, "Contract not started");
        require(block.timestamp <= contractEndTime, "Contract period ended");
        require(!isTerminated, "Contract terminated");
        _;
    }
    
    function executeFunction() public duringContractPeriod {
        // 期間内のみ実行可能な処理
    }
    
    function terminateEarly() public {
        require(block.timestamp < contractEndTime, "Already expired");
        isTerminated = true;
        // 資金回収処理
    }
}

7.2 段階的破棄システム

実装コード

contract PhasedDestruction {
    enum ContractState { Active, Warning, Terminating, Terminated }
    
    ContractState public currentState = ContractState.Active;
    uint public warningPeriod = 7 days;
    uint public warningStartTime;
    
    modifier onlyInState(ContractState state) {
        require(currentState == state, "Invalid contract state");
        _;
    }
    
    function startWarningPeriod() public {
        require(currentState == ContractState.Active, "Not in active state");
        currentState = ContractState.Warning;
        warningStartTime = block.timestamp;
    }
    
    function terminate() public {
        require(currentState == ContractState.Warning, "Not in warning state");
        require(block.timestamp >= warningStartTime + warningPeriod, "Warning period not ended");
        
        currentState = ContractState.Terminated;
        payable(msg.sender).transfer(address(this).balance);
    }
}

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

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

実装コード

contract RobustContract {
    bool public isActive = true;
    address public owner;
    
    event ContractTerminated(address indexed terminatedBy, uint timestamp);
    event EmergencyWithdrawal(address indexed to, uint amount);
    
    modifier onlyWhenActive() {
        require(isActive, "Contract is not active");
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can execute");
        _;
    }
    
    function terminateContract() public onlyOwner onlyWhenActive {
        isActive = false;
        
        uint balance = address(this).balance;
        if(balance > 0) {
            (bool success, ) = payable(owner).call{value: balance}("");
            require(success, "Transfer failed");
            emit EmergencyWithdrawal(owner, balance);
        }
        
        emit ContractTerminated(msg.sender, block.timestamp);
    }
}

8.2 ガス最適化

実装コード

contract GasOptimizedContract {
    bool private _isTerminated;
    address private _owner;
    
    modifier notTerminated() {
        require(!_isTerminated, "Contract terminated");
        _;
    }
    
    // パックされたストレージを使用
    struct ContractState {
        bool isTerminated;
        address owner;
        uint96 balance; // 残高の追跡(96ビットで十分)
    }
    
    ContractState private contractState;
    
    function terminate() public {
        require(msg.sender == contractState.owner, "Not owner");
        require(!contractState.isTerminated, "Already terminated");
        
        contractState.isTerminated = true;
        
        if(contractState.balance > 0) {
            payable(contractState.owner).transfer(contractState.balance);
        }
    }
}

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

9.1 破棄状態の検証

実装コード

contract SecureDestruction {
    bool public isDestroyed = false;
    address public owner;
    uint public destructionTime;
    
    modifier notDestroyed() {
        require(!isDestroyed, "Contract has been destroyed");
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner");
        _;
    }
    
    function destroy() public onlyOwner notDestroyed {
        isDestroyed = true;
        destructionTime = block.timestamp;
        
        // 安全な資金回収
        uint balance = address(this).balance;
        if(balance > 0) {
            (bool success, ) = payable(owner).call{value: balance}("");
            require(success, "Transfer failed");
        }
    }
    
    // 破棄後の状態確認
    function getContractInfo() public view returns (bool, uint, uint) {
        return (isDestroyed, destructionTime, address(this).balance);
    }
}

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

実装コード

contract ReentrancyProtected {
    bool private _locked;
    bool public isTerminated = false;
    address public owner;
    
    modifier noReentrancy() {
        require(!_locked, "Reentrancy detected");
        _locked = true;
        _;
        _locked = false;
    }
    
    modifier notTerminated() {
        require(!isTerminated, "Contract terminated");
        _;
    }
    
    function safeTerminate() public noReentrancy notTerminated {
        require(msg.sender == owner, "Only owner");
        
        isTerminated = true;
        
        uint balance = address(this).balance;
        if(balance > 0) {
            payable(owner).transfer(balance);
        }
    }
}

10. 実装のポイント

10.1 単位指定のベストプラクティス

推奨される記述方法:

// Ether単位
uint public constant MIN_DEPOSIT = 0.01 ether;
uint public constant MAX_DEPOSIT = 100 ether;
uint public constant FEE_AMOUNT = 0.001 ether;

// 時間単位
uint public constant AUCTION_DURATION = 3 days;
uint public constant WITHDRAWAL_DELAY = 24 hours;
uint public constant GRACE_PERIOD = 7 days;

避けるべき記述方法:

// 数値での直接指定(読みにくく、間違いやすい)
uint public constant MIN_DEPOSIT = 10000000000000000;  // 0.01 ETH
uint public constant AUCTION_DURATION = 259200;        // 3日間

10.2 コントラクト破棄のベストプラクティス

推奨される実装パターン:

contract BestPracticeDestruction {
    bool public isTerminated = false;
    address public immutable owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier notTerminated() {
        require(!isTerminated, "Contract terminated");
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    receive() external payable notTerminated {}
    
    function terminate() external onlyOwner notTerminated {
        isTerminated = true;
        
        uint balance = address(this).balance;
        if(balance > 0) {
            payable(owner).transfer(balance);
        }
    }
}

11. 学習の成果

11.1 習得した概念

  1. 単位指定システム: ether、gwei、wei、時間単位の正しい使用方法
  2. 可読性の向上: 数値の意味を明確にする記述方法
  3. selfdestructの非推奨化: モダンなSolidityでの代替手法
  4. 状態管理: ブール値による破棄状態の管理
  5. 安全な資金回収: transfer関数によるEther送信
  6. モディファイアの活用: 状態チェックの効率的な実装

11.2 実装スキル

  • 単位指定の適切な使用方法
  • 時間計算の簡素化と可読性向上
  • コントラクト破棄のモダンな実装方法
  • 状態管理による機能制御
  • セキュリティ強化のベストプラクティス

11.3 技術的な理解

  • Ether単位: wei、gwei、etherの階層関係
  • 時間単位: seconds、minutes、hours、days、weeksの活用
  • selfdestructの制限: EIP-6780による動作変更
  • 状態フラグ: ブール値による制御フロー
  • 安全な送金: transfer関数の適切な使用方法

12. 今後の学習への応用

12.1 発展的な機能

  • 複数通貨対応: ERC20トークンとの連携
  • 段階的破棄: 警告期間を設けた安全な破棄
  • 自動破棄: 条件達成時の自動終了
  • 復旧機能: 誤操作時の復旧メカニズム

12.2 セキュリティの向上

  • 多要素認証: 複数の承認が必要な破棄
  • 時間制限: 破棄実行の時間制限
  • 監査機能: 破棄履歴の追跡
  • 緊急停止: 異常時の即座な対応

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

  • ガス最適化: 効率的な状態管理
  • ストレージ最適化: パックされたデータ構造
  • バッチ処理: 複数操作の効率的な実行
  • メモリ効率: 一時的なデータの適切な管理

参考:

Discussion