defi-amm-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

DeFi AMM Security

DeFi AMM安全

Critical vulnerability patterns and hardened implementations for Solidity AMM contracts, LP vaults, and swap functions.
针对Solidity AMM合约、流动性池金库和兑换函数的关键漏洞模式与加固实现方案。

When to Use

适用场景

  • Writing or auditing a Solidity AMM or liquidity-pool contract
  • Implementing swap, deposit, withdraw, mint, or burn flows that hold token balances
  • Reviewing any contract that uses
    token.balanceOf(address(this))
    in share or reserve math
  • Adding fee setters, pausers, oracle updates, or other admin functions to a DeFi protocol
  • 编写或审计Solidity AMM或流动性池合约时
  • 实现持有代币余额的兑换、存入、取出、铸造、销毁流程时
  • 审查任何在份额或储备金计算中使用
    token.balanceOf(address(this))
    的合约时
  • 为DeFi协议添加费率设置、暂停、预言机更新或其他管理员功能时

How It Works

工作原理

Use this as a checklist-plus-pattern library. Review every user entrypoint against the categories below and prefer the hardened examples over hand-rolled variants.
可作为附带实现模式的检查清单使用。对照以下类别审查所有用户入口点,优先使用经过加固的示例代码,而非自行编写的实现逻辑。

Examples

代码示例

Reentrancy: enforce CEI order

重入攻击:强制执行CEI执行顺序

Vulnerable:
solidity
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    token.transfer(msg.sender, amount);
    balances[msg.sender] -= amount;
}
Safe:
solidity
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, "Insufficient");
    balances[msg.sender] -= amount;
    token.safeTransfer(msg.sender, amount);
}
Do not write your own guard when a hardened library exists.
存在漏洞的实现:
solidity
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    token.transfer(msg.sender, amount);
    balances[msg.sender] -= amount;
}
安全实现:
solidity
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, "Insufficient");
    balances[msg.sender] -= amount;
    token.safeTransfer(msg.sender, amount);
}
已有成熟的加固库时,请勿自行编写防护逻辑。

Donation or inflation attacks

捐赠/通胀攻击

Using
token.balanceOf(address(this))
directly for share math lets attackers manipulate the denominator by sending tokens to the contract outside the intended path.
solidity
// Vulnerable
function deposit(uint256 assets) external returns (uint256 shares) {
    shares = (assets * totalShares) / token.balanceOf(address(this));
}
solidity
// Safe
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;
}
Track internal accounting and measure actual tokens received.
直接使用
token.balanceOf(address(this))
进行份额计算时,攻击者可以通过预期外的路径向合约转入代币,操纵计算分母。
solidity
// 存在漏洞的实现
function deposit(uint256 assets) external returns (uint256 shares) {
    shares = (assets * totalShares) / token.balanceOf(address(this));
}
solidity
// 安全实现
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;
}
追踪内部账户记录,统计实际收到的代币数量。

Oracle manipulation

预言机操纵

Spot prices are flash-loan manipulable. Prefer TWAP.
solidity
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);
现货价格可被闪电贷操纵,优先使用TWAP(时间加权平均价格)。
solidity
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);

Slippage protection

滑点防护

Every swap path needs caller-provided slippage and a deadline.
solidity
function swap(
    uint256 amountIn,
    uint256 amountOutMin,
    uint256 deadline
) external returns (uint256 amountOut) {
    require(block.timestamp <= deadline, "Expired");
    amountOut = _calculateOut(amountIn);
    require(amountOut >= amountOutMin, "Slippage exceeded");
    _executeSwap(amountIn, amountOut);
}
所有兑换路径都需要调用方提供滑点限制和交易有效期。
solidity
function swap(
    uint256 amountIn,
    uint256 amountOutMin,
    uint256 deadline
) external returns (uint256 amountOut) {
    require(block.timestamp <= deadline, "Expired");
    amountOut = _calculateOut(amountIn);
    require(amountOut >= amountOutMin, "Slippage exceeded");
    _executeSwap(amountIn, amountOut);
}

Safe reserve math

安全储备金运算

solidity
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";

uint256 result = FullMath.mulDiv(a, b, c);
For large reserve math, avoid naive
a * b / c
when overflow risk exists.
solidity
import {FullMath} from "@uniswap/v3-core/contracts/libraries/FullMath.sol";

uint256 result = FullMath.mulDiv(a, b, c);
针对大额储备金运算,存在溢出风险时避免使用简单的
a * b / c
写法。

Admin controls

管理员权限控制

solidity
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";

contract MyAMM is Ownable2Step {
    function setFee(uint256 fee) external onlyOwner { ... }
    function pause() external onlyOwner { ... }
}
Prefer explicit acceptance for ownership transfer and gate every privileged path.
solidity
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";

contract MyAMM is Ownable2Step {
    function setFee(uint256 fee) external onlyOwner { ... }
    function pause() external onlyOwner { ... }
}
所有权转让优先使用显式确认逻辑,所有特权操作路径都需要进行权限校验。

Security Checklist

安全检查清单

  • Reentrancy-exposed entrypoints use
    nonReentrant
  • CEI ordering is respected
  • Share math does not depend on raw
    balanceOf(address(this))
  • ERC-20 transfers use
    SafeERC20
  • Deposits measure actual tokens received
  • Oracle reads use TWAP or another manipulation-resistant source
  • Swaps require
    amountOutMin
    and
    deadline
  • Overflow-sensitive reserve math uses safe primitives like
    mulDiv
  • Admin functions are access-controlled
  • Emergency pause exists and is tested
  • Static analysis and fuzzing are run before production
  • 存在重入风险的入口点使用
    nonReentrant
    修饰
  • 遵循CEI执行顺序
  • 份额计算不依赖原生
    balanceOf(address(this))
    返回值
  • ERC-20转账使用
    SafeERC20
  • 存款操作统计实际收到的代币数量
  • 预言机读取使用TWAP或其他抗操纵数据源
  • 兑换操作要求传入
    amountOutMin
    (最小到账金额)和
    deadline
    (交易有效期)
  • 对溢出敏感的储备金运算使用
    mulDiv
    等安全运算原语
  • 管理员功能都配置了访问控制
  • 存在紧急暂停功能且经过测试
  • 上线前执行过静态分析和模糊测试

Audit Tools

审计工具

bash
pip install slither-analyzer
slither . --exclude-dependencies

echidna-test . --contract YourAMM --config echidna.yaml

forge test --fuzz-runs 10000
bash
pip install slither-analyzer
slither . --exclude-dependencies

echidna-test . --contract YourAMM --config echidna.yaml

forge test --fuzz-runs 10000