DeFi AMM 安全性 是做什麼的?
Solidity AMM 合約、LP 金庫與交換函數的關鍵漏洞模式與強化實作。
使用時機
- 編寫或審計 Solidity AMM 或流動性池合約
- 實作持有代幣餘額的 swap、deposit、withdraw、mint 或 burn 流程
- 審查任何在份額或儲備計算中使用
token.balanceOf(address(this))的合約 - 在 DeFi 協定中新增費用設定者、暫停器、預言機更新或其他管理員函數
運作方式
將此作為檢查清單加模式庫使用。針對以下類別審查每個使用者入口點,並優先使用強化範例而非手寫變體。
執行安全
此技能中的 shell 指令為本機審計範例。請僅在受信任的檢出目錄或一次性沙盒環境中執行,且不要將不受信任的合約名稱、路徑、RPC URL、私鑰或用戶提供的旗標拼接至 shell 指令中。在安裝工具或執行可能消耗大量本機或付費資源的長時間模糊測試/靜態分析作業前,請先取得使用者同意。
切勿在指令範例、日誌或報告中包含機密資訊、私鑰、助記詞、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