封面信息图

在以太坊与 EVM 兼容链的去中心化金融(DeFi)生态中,跨池套利(Cross-DEX Arbitrage) 始终是维系全网流动性价格高效收敛的核心无形之手。

当同一个代币对在 Uniswap V2 与 Uniswap V3(或者在 Sushiswap 与 Curve)之间由于瞬时大额订单产生价格价差时,自主交易 Agent 会在单笔交易内,从低价池买入目标代币,并在高价池迅速卖出,无风险收回本金并捕获套利利润。

然而,在智能合约安全审计与 MEV 攻防的暗黑森林中,无数自信满满的套利机器人,却在上线的第一天就遭遇了资产血洗。

很多工程师在编写套利执行合约时,自以为“这笔买卖是在同一个交易内原子完成的,只要最终检查本金没有减少就行了”,因而在调用底层路由的 swapExactTokensForTokens 或 exactInputSingle 时,图省事将最低可接受输出参数 amountOutMin 直接硬编码为 0!

这种低级而致命的疏忽,被称为滑点归零陷阱(Zero-Slippage Vulnerability)。

一旦包含 amountOutMin = 0 的套利交易被广播进入公共未确认交易池(Mempool),徘徊在底层的 MEV 夹子机器人(Sandwich Bots)会在纳秒级内发起两头包夹——在目标池中制造极端滑点,让套利者的资金在合法且无报错的情况下,被无情地“按 0 元购”洗劫一空。

要让套利协议在黑暗森林中全身而退,必须彻底消灭零滑点漏洞——基于 AMM 恒定乘积代数方程,动态推演链上理论无损输出,并结合预言机锚定与终极净值原子断言构筑抗夹防线。

本文将为你深度拆解这一攻防细节并给出工业级加固实现。


一、滑点归零陷阱中的三明治猎杀推演

┌─────────────────────────────────────────────────────────────────────────┐
│                 滑点归零漏洞 (amountOutMin = 0) 被夹全景                 │
│                                                             │
│  [正常套利预期]:                                                        │
│  池 A (买入): 花费 10 ETH ──> 买入 30,000 USDC                          │
│  池 B (卖出): 卖出 30,000 USDC ──> 换回 10.5 ETH (净赚 0.5 ETH)         │
│                                                             │
│  ───────────────────【MEV 夹子机器人介入收割】─────────────────         │
│                                                             │
│  1. [前置抢跑 Front-run]:                                               │
│     夹子机器人支付更高 Gas,抢在受害者前向池 B 砸入海量 USDC             │
│     => 导致池 B 的现货兑换率瞬间暴跌: 30,000 USDC 仅值 0.01 ETH!        │
│                                                             │
│  2. [受害者套利合约被动执行]:                                           │
│     受害者在池 A 买入 30,000 USDC;                                      │
│     受害者在池 B 卖出 30,000 USDC;                                      │
│     【由于代码写死了 amountOutMin = 0,合约认为收到 0.01 ETH 完全合法!】 │
│     => 受害者 30,000 USDC 换回 0.01 ETH! 本金瞬间蒸发 99.9%!            │
│                                                             │
│  3. [后置回踩 Back-run]:                                                │
│     夹子机器人随后在池 B 抄底买回全部资产,净赚 10 ETH 扬长而去!         │
└─────────────────────────────────────────────────────────────────────────┘

攻击者没有利用任何复杂的逻辑漏洞,仅仅利用了开发者主动放弃的滑点保护,在合法的 AMM 数学曲线上完成了对套利者的反向屠杀。


二、基于 AMM 恒定乘积方程的动态链上滑点推演

为了在调用外部兑换池时给出最严苛的 amountOutMin,智能合约不能盲目听信外部入参,必须在链上根据当前储备量进行第一性原理推导。

2.1 Uniswap V2 恒定乘积无损输出公式

对于一个遵循 $(x \cdot y = k)$ 的标准 AMM 池子,假设输入代币数量为 $\Delta x$,扣除千分之三手续费(Fee = 0.3%):
$$\Delta x_{\text{net}} = \Delta x \times 997$$
根据新平衡方程 $(x + \Delta x_{\text{net}})(y - \Delta y) = x \cdot y$,推导出精确的理论输出 $\Delta y$:
$$\Delta y = \frac{y \times \Delta x_{\text{net}}}{x \times 1000 + \Delta x_{\text{net}}}$$

在链上,套利合约应该允许至多 5 到 10 个基点(0.05% 到 0.1%) 的正常网络微观波动摩擦,绝不允许出现超过 1% 的滑点滑落:
$$\text{minAmountOut} = \Delta y \times \frac{10000 - \text{maxSlippageBps}}{10000}$$


三、工业级抗三明治跨池套利合约实战

以下是基于 Solidity 0.8.26 构建的高安全性跨池套利执行器,集成了动态数学滑点计算与全局资金底线断言:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}

contract SecureCrossDexArbitrageur {
    using SafeERC20 for IERC20;

    address public immutable owner;

    // 允许的最大滑点波动容忍度: 10 基点 = 0.1%
    uint256 public constant MAX_SLIPPAGE_BPS = 10; 

    error NegativeProfitDetected(uint256 finalBalance, uint256 expectedBalance);
    error SlippageExceeded(uint256 minAmountOut, uint256 actualAmountOut);
    error Unauthorized();

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    /// @notice 安全执行两池之间的原子套利
    function executeArbitrage(
        address pairA,
        address pairB,
        address baseToken,
        address intermediateToken,
        uint256 amountIn,
        uint256 minExpectedProfit
    ) external onlyOwner {
        IERC20 base = IERC20(baseToken);
        uint256 balanceBefore = base.balanceOf(address(this));

        // 1. 在池 A 计算输入 amountIn 应该得到的中间代币数量 (理论无损值)
        uint256 expectedIntermediate = _calculateAmountOut(pairA, baseToken, intermediateToken, amountIn);
        uint256 minIntermediateOut = (expectedIntermediate * (10000 - MAX_SLIPPAGE_BPS)) / 10000;

        // 2. 将 baseToken 转入池 A 并触发兑换
        base.safeTransfer(pairA, amountIn);
        uint256 intermediateReceived = _performSwap(pairA, baseToken, intermediateToken, minIntermediateOut);

        // 3. 在池 B 计算将 intermediateToken 卖回 baseToken 的理论无损值
        uint256 expectedFinalBase = _calculateAmountOut(pairB, intermediateToken, baseToken, intermediateReceived);
        uint256 minFinalBaseOut = (expectedFinalBase * (10000 - MAX_SLIPPAGE_BPS)) / 10000;

        // 4. 将 intermediateToken 转入池 B 并触发兑换
        IERC20(intermediateToken).safeTransfer(pairB, intermediateReceived);
        _performSwap(pairB, intermediateToken, baseToken, minFinalBaseOut);

        // 5. 核心终极防线:全局资产强一致性断言!
        // 最终余额必须严格大于等于:初始本金 + 最低预期净利润
        uint256 balanceAfter = base.balanceOf(address(this));
        uint256 requiredBalance = balanceBefore + minExpectedProfit;

        if (balanceAfter < requiredBalance) {
            revert NegativeProfitDetected(balanceAfter, requiredBalance);
        }
    }

    function _calculateAmountOut(
        address pairAddress,
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) internal view returns (uint256 amountOut) {
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
        (uint112 reserve0, uint112 reserve1, ) = pair.getReserves();

        // 识别 tokenIn 在交易对中的顺序
        (uint256 reserveIn, uint256 reserveOut) = tokenIn < tokenOut 
            ? (uint256(reserve0), uint256(reserve1)) 
            : (uint256(reserve1), uint256(reserve0));

        // 严密遵循恒定乘积扣除 0.3% 手续费的数学方程
        uint256 amountInWithFee = amountIn * 997;
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 1000) + amountInWithFee;

        amountOut = numerator / denominator;
    }

    function _performSwap(
        address pairAddress,
        address tokenIn,
        address tokenOut,
        uint256 minOut
    ) internal returns (uint256 actualOut) {
        IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
        IERC20 outToken = IERC20(tokenOut);
        
        uint256 balanceBefore = outToken.balanceOf(address(this));

        // 根据代币地址大小端设置输出槽
        (uint256 amount0Out, uint256 amount1Out) = tokenIn < tokenOut 
            ? (uint256(0), minOut) 
            : (minOut, uint256(0));

        // 触发低级 swap (若池子被夹,滑点过大时 AMM 内部会自动因恒定乘积不足而 Revert)
        pair.swap(amount0Out, amount1Out, address(this), new bytes(0));

        uint256 balanceAfter = outToken.balanceOf(address(this));
        actualOut = balanceAfter - balanceBefore;

        if (actualOut < minOut) {
            revert SlippageExceeded(minOut, actualOut);
        }
    }
}

四、三重防御矩阵与攻防实测复盘

上述加固合约构建了牢不可破的三重防御矩阵:

  1. 第一重:单池入参级滑点(minIntermediateOut):
    在向池 A 发起买入时,滑点被锁死在 0.1% 以内。攻击者如果试图在前置交易中抬高池 A 价格,池 A 的储备量发生位移,兑换操作会在第一阶段直接因为无法吐出足够的代币而立即回滚;
  2. 第二重:出口级滑点(minFinalBaseOut):
    在向池 B 卖出时,同样锁死 0.1% 的滑点,彻底粉碎了夹子机器人在池 B 实施断崖砸盘的可能;
  3. 第三重:全局净值强断言(NegativeProfitDetected):
    无论中间经过了多少层复杂的路由与跨币种转换,交易结束时的底层本金必须满足 $\text{Balance}{\text{after}} \ge \text{Balance}{\text{before}} + \text{Profit}$。哪怕出现极端闪电贷价格操纵,这道终极保险丝也会在瞬间熔断并无损撤回交易。

五、工程师实战避坑军规

  1. 绝对禁止前端或客户端直接传入静态的 0:
    在所有生产代码的 CI/CD 自动化检测门禁中,必须使用静态规则直接阻断任何包含 amountOutMin = 0、amountOutMinimum = 0 或 maxSlippage = 100% 的提交;
  2. 警惕非标转账税代币(Tax Tokens)的虚假滑点:
    如果套利代币对包含具有燃烧税(Deflationary Token)特性的代币,转账时会被自动扣减 2% 的税费。计算 minOut 时必须将该代币的销毁费率显式纳入分母推算,严防正常交易误触滑点断言;
  3. 结合私有 RPC(Flashbots Protect)实现双重保险:
    链上的代码级滑点保护是“防弹衣”,而私有中继广播是“隐形衣”。套利 Agent 应当默认将交易走私有 Bundle 推送给矿工,不给黑暗森林中的猎杀者留下任何在 Mempool 窥探的机会。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐