DeFi AMM セキュリティ は何をしますか?
Solidity AMMコントラクト、LPボールト、スワップ関数における重要な脆弱性パターンと堅牢な実装例です。
使用タイミング
- Solidity AMMまたは流動性プールコントラクトの作成または監査中
- トークン残高を保持するスワップ、預入、引出、ミント、バーンフローを実装する場合
- シェア計算や準備金計算で
token.balanceOf(address(this))を使用するコントラクトのレビュー時 - 手数料設定者、一時停止機能、オラクル更新、その他の管理者機能をDeFiプロトコルに追加する場合
動作の仕組み
このスキルは「チェックリスト兼パターンライブラリ」として使用します。すべてのユーザーエントリポイントを以下のカテゴリに照らしてレビューし、手書きのバリアントよりも堅牢な実装例を優先してください。
実行時の安全
このスキル内のシェルコマンドはローカル監査の例です。信頼できるチェックアウトまたは使い捨てのサンドボックス内でのみ実行し、信頼できないコントラクト名、パス、RPC URL、秘密鍵、ユーザー指定フラグをシェルコマンドに組み込まないでください。ツールのインストールや、ローカルまたは有料リソースを大量に消費する可能性のある長時間のファジング/静的解析ジョブの実行前に確認を行ってください。
コマンド例、ログ、レポートに秘密情報、秘密鍵、シードフレーズ、APIトークン、メインネット署名資格情報を含めないでください。
例
リエントランシー:CEI順序の強制
脆弱な実装:
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
token.transfer(msg.sender, amount);
balances[msg.sender] -= amount;
}
安全な実装:
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "残高不足");
balances[msg.sender] -= amount;
token.safeTransfer(msg.sender, amount);
}
堅牢化されたライブラリが存在する場合は、独自のガードを作成しないでください。
ドネーション/インフレーション攻撃
シェア計算で token.balanceOf(address(this)) を直接使用すると、攻撃者が意図しない経路でトークンをコントラクトに送信することで分母を操作できます。
// 脆弱な実装
function deposit(uint256 assets) external returns (uint256 shares) {
shares = (assets * totalShares) / token.balanceOf(address(this));
}
// 安全な実装
uint256 private _totalAssets;
function deposit(uint256 assets) external nonReentrant returns (uint256 shares) {
uint256 balBefore = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), assets);
uint256 received = token.balanceOf(address(this)) - balBefore;
shares = totalShares == 0 ? received : (received * totalShares) / _totalAssets;
_totalAssets += received;
totalShares += shares;
}
内部会計を追跡し、実際に受け取ったトークン量を計測します。
オラクル操作
スポット価格はフラッシュローンで操作可能です。TWAPを優先してください。
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 1800;
secondsAgos[1] = 0;
(int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);
int24 twapTick = int24(
(tickCumulatives[1] - tickCumulatives[0]) / int56(uint56(30 minutes))
);
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(twapTick);
スリッページ保護
すべてのスワップパスには、呼び出し元指定のスリッページとデッドラインが必要です。
function swap(
uint256 amountIn,
uint256 amountOutMin,
uint256 deadline
) external returns (uint256 amountOut) {
require(block.timestamp <= deadline, "期限切れ");
amountOut = _calculateOut(amountIn);
require(amountOut >= amountOutMin, "スリッページ超過");
_executeSwap(amountIn, amountOut);
}
安全な準備金計算
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";
uint256 result = FullMath.mulDiv(a, b, c);
大きな準備金計算では、オーバーフローリスクがある場合に単純な a * b / c を避けてください。
管理者コントロール
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
contract MyAMM is Ownable2Step {
function setFee(uint256 fee) external onlyOwner { ... }
function pause() external onlyOwner { ... }
}
所有権の移転には明示的な承認を優先し、すべての特権パスをゲートしてください。
セキュリティチェックリスト
- リエントランシーが露出するエントリポイントは
nonReentrantを使用している - CEI順序が守られている
- シェア計算が生の
balanceOf(address(this))に依存していない - ERC-20転送は
SafeERC20を使用している - 預入は実際に受け取ったトークン量を計測している
- オラクル読み取りはTWAPまたはその他の操作耐性のあるソースを使用している
- スワップは
amountOutMinとdeadlineを必須としている - オーバーフローが発生しやすい準備金計算は
mulDivのような安全なプリミティブを使用している - 管理者関数はアクセス制御されている
- 緊急一時停止機能が存在し、テストされている
- 静的解析とファジングが本番前に実行されている
監査ツール
pip install slither-analyzer
slither . --exclude-dependencies
echidna-test . --contract YourAMM --config echidna.yaml
forge test --fuzz-runs 10000