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