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