ビジネスロジックと経済的セキュリティのテスト (Testing Business Logic and Economic Security)
説明
スマートコントラクトのビジネスロジックと経済的セキュリティは、コントラクトの運用ルールと関連する経済的インセンティブが適切に設計され、悪用や非効率性を避けていることを確保します。これは、インセンティブ構造の定義、トークン価値の安定性の確保、再入攻撃の防止、トークノミクスがシステムの期待される動作と整合していることの確認を含みます。コントラクトは、論理的な欠陥を回避し、トークンの報酬とペナルティが正しく実装されるように確保し、コントラクトの経済モデルを損なう可能性のある攻撃を防ぐように設計される必要があります。
例: 不適切なインセンティブ構造
// Push-based withdrawal process (vulnerable)
function withdraw(uint256 amount) public {
require(balance[msg.sender] >= amount, "Insufficient balance");
payable(msg.sender).transfer(amount); // Push-based transfer
balance[msg.sender] -= amount;
}
上記の例では、プッシュベースの引き落としシステムが、再入攻撃や、制御されていない資金移動による残高の適切な追跡の失敗など、予期しない動作を許す可能性があります。より良いアプローチはプルベースのシステムを使用することです。
影響
インセンティブの不整合: 不正確な経済モデルはインセンティブの不均衡につながり、ユーザーがエコシステムに損害を与えたりコントラクトの目的を損なうような行動をとるように促される可能性があります。
ボラティリティと損失: cbETH や ETH などの資産間の変換レートなど、トークン価値の変動は、適切に処理されない場合、ユーザーに損失をもたらす可能性があります。同様に、rETH などのステーキング報酬は、予測不可能な価値変動を引き起こし、ユーザーに影響を及ぼす可能性があります。
再入攻撃: トランザクションフローと状態変更の処理が不十分だと、攻撃者がトランザクション中にコントラクトに再入でき、二重引き落としなどの意図しない結果を引き起こす可能性があります。
トークンの脆弱性: 不適切な料金適用や欠陥のある報酬システムなど、間違いのあるトークノミクスは、ユーザーが資金を失ったり、間違えた報酬を受け取る可能性があります。
二重支払いと詐欺: 重複した Merkle 証明などの弱いメカニズムは、システムを二重支払い攻撃やその他の詐欺メカニズムにさらず可能性があります。
対策
インセンティブ構造: プッシュベースの送金ではなく、プルベースの引き落としシステムを実行して、適切な追跡を可能にし、再入攻撃のリスクを軽減します。すべての経済的インセンティブが明確に定義され、望ましい行動と整合していることを確保します。
トークノミクスと報酬システム: リベースメカニズムや報酬請求を含む、すべてのトークンの動作が適切に処理されることを確保します。トークンの予期しない動作を防止し、不正な請求を防止するために、請求可能なすべてのアドレスがハッシュ処理に含まれていることを検証します。
変動管理: 資産価値の変動 (cbETH から ETH、rETH ステーキング報酬など) を考慮し、それに応じて調整してユーザーのリスクを緩和できるシステムを開発します。
トランザクションフローセキュリティ: 再入攻撃を防ぐために check-effect-interaction などのパターンを使用し、外部呼び出しの前に状態変更が発生するようにします。意図しない再起呼び出しを防ぐため、該当する場合は NonReentrant 修飾子を適用します。
関数の完全性: 関数が同じ送信者/受信者のシナリオなど、エッジケースを処理するように設計されていること、また潜在的な不一致や論理上の欠陥を避けるために関数が不必要に複数回呼び出されないことを確保します。
テスト 1: 経済モデルの完全性を確認してロジックの欠陥を防御する
脆弱なコード:
pragma solidity ^0.8.0;
contract IncentiveModel {
uint256 public rewardPool;
function distributeRewards(address[] memory users) public {
uint256 reward = rewardPool / users.length; // Dividing the total reward pool among users
for (uint256 i = 0; i < users.length; i++) {
payable(users[i]).transfer(reward);
}
}
}
なぜ脆弱なのか
users
配列が空の場合、ゼロ除算が発生し、ランタイム例外につながります。さらに、報酬プールが小さすぎる場合やユーザー数が多すぎる場合、予期しない動作を引き起こす可能性があり、攻撃者が過剰に多くのアドレスでコントラクトをフラッディングしてシステムを悪用することにつながります。
修正されたコード:
pragma solidity ^0.8.0;
contract SecureIncentiveModel {
uint256 public rewardPool;
modifier nonZeroUsers(address[] memory users) {
require(users.length > 0, "No users provided");
_;
}
function distributeRewards(address[] memory users) public nonZeroUsers(users) {
uint256 reward = rewardPool / users.length;
require(reward > 0, "Reward amount is too low");
for (uint256 i = 0; i < users.length; i++) {
payable(users[i]).transfer(reward);
}
}
}
チェック方法
コードレビュー: 除算や算術演算が、ゼロ除算などのエラーや、極端な入力値による意図しない動作となる可能性のあるシナリオを探します。
動的テスト: 空のユーザーリスト、非常に大きなユーザー配列、報酬プールのサイズなどのエッジケースで関数をテストします。システムがこれらのケースを適切に処理し、予期しないエラーやエクスプロイトを許容しないことを確認します。
Last updated