本文深入解释了区块链中的抢跑交易(Frontrunning)和最大可提取价值(MEV)现象。通过公开内存池(mempool)机制,分析了恶意机器人如何利用用户交易信息进行三明治攻击,导致用户损失。文章提供了易受攻击的智能合约代码示例及修复方法(强制滑点检查),并介绍了更高级的MEV策略如尾随交易和即时流动性(JIT Liquidity),后者虽可改善交易执行但损害被动流动性提供者收益。结论强调开发者应在智能合约中实施滑点保护,以应对公共内存池带来的风险。

想象一位交易者在市场中发现了有利可图的交易机会。他们签署交易,点击确认,然后等待。但当交易最终确认时,他们发现买入代币的价格高于预期。在去中心化交易所(DEX)交易时,这并不罕见;他们可能被 MEV 机器人盯上了。
区块链交易的公开性带来了一些挑战。虽然透明度确保了可信度,但用户的意图在最终执行前就暴露了。从签署交易到交易被纳入区块之间的时间差,正是抢先交易可能发生的窗口。
作为智能合约安全研究人员,我们亲身见证了抢先交易的影响,尤其是在自动做市商(AMM)中。抢先其他用户的交易是一种复杂的经济操作,每年从用户和协议中榨取数百万美元。
要理解抢先交易的含义,首先必须了解内存池。当一笔交易被发送到网络(例如,在 Uniswap 这样的 DEX 上进行一笔兑换交易),该交易并不会立即执行。它进入一个称为内存池的"等待区",直到被验证节点拾取并确认。
验证者受到经济激励,会优先处理支付最高 gas 费用的交易。
抢先交易是指发现内存池中待处理的交易(例如代币兑换交易),并提交自己的交易,支付更高的 gas 费用,以确保它在原始交易之前被处理。
由于交易数据是可见的,复杂的机器人可以针对 AMM 的当前状态模拟交易。如果它们发现盈利机会,就会复制交易逻辑,支付更高的"贿赂"(gas 费用),并在原始交易之前让它们的交易被处理。
抢先交易只是更大现象——MEV——的一个方面。最初,这个缩写代表矿工可提取价值(Miners Extractable Value),指矿工通过处理区块链交易可以提取的利润。
加密货币中的 MEV 已经发展成为一种工业规模的运作,涉及"搜索者"(运行机器人的实体)和"构建者"(打包交易的实体)。虽然有些 MEV 是无害的,但"有毒"的 MEV,例如三明治攻击,会直接损害用户利益。
在 AMM 中最臭名昭著的抢先交易形式是三明治攻击。当 MEV 机器人检测到内存池中有一笔待处理的大额兑换订单时,就会发生这种情况。
在一个依赖恒定乘积公式 x * y = k 的 AMM 中,一笔大额买入订单必然会导致价格上涨。为了演示攻击如何展开,让我们看看一些有漏洞的代码。
抢先交易经常利用设计不良的滑点检查。考虑这个忽略了最小输出金额的简单兑换函数:
// 有漏洞的代码示例
function naiveSwap(address tokenIn, uint256 amountIn) external {
// 假设在实际兑换前进行了一些检查
// 与 AMM 的交互
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
// 没有指定最小输出金额 -> 没有滑点保护
AMM.swap(tokenIn, amountIn, 0);
}
在上述代码中,swap 函数中的 0 代表 100% 的滑点容忍度。抢先交易机器人看到这一点后就知道,它们可以在该交易执行前操纵价格,而交易仍然会成功,迫使受害者在极其不利的价格下进行兑换。
假设 Alice 调用上述 naiveSwap 函数,用 10,000 USDC 兑换 ETH。

图 1: 三明治攻击通过三笔有序交易执行。机器人的抢跑交易抬高价格;受害者的交易在该抬高价格上以无滑点底线完成结算;尾随交易让机器人获利。
攻击在内存池中是这样展开的:
0,合约接受任何价格作为有效价格。naiveSwap 函数缺少滑点检查。为了防止这种情况,智能合约必须强制执行严格的滑点参数。
// 安全的代码示例
function secureSwap(
address tokenIn,
uint256 amountIn,
uint256 minAmountOut, // 滑点保护
) external {
// 假设在实际兑换前进行了一些检查
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
// 如果抢跑交易将返回金额推低至 minAmountOut 以下,该交易将回滚
uint256 actualAmountOut = AMM.swap(tokenIn, amountIn, minAmountOut);
require(actualAmountOut >= minAmountOut, "输出金额不足");
}
通过添加 minAmountOut 参数并强制执行 actualAmountOut >= minAmountOut,用户定义了其愿意接受的"最差"价格。如果三明治机器人试图抢先该交易以改变价格,最后的 require 语句会导致受害者的交易回滚,从而保护其资金。攻击者的策略失败,MEV 机器人会浪费 gas 费用。
并非所有 MEV 策略都涉及抢先交易。有些发生在目标交易之后。
尾随交易是指提交一笔旨在目标交易之后立即执行的交易。常见的例子是套利。
在集中流动性 AMM(如 Uniswap V3)中,更复杂的 MEV 形式是即时流动性。
虽然 JIT 流动性实际上为交易者提供了更好的执行价格(由于流动性更深,价格影响更小),但它通过窃取收益来打击被动流动性提供者(LP),因为该笔特定兑换的大部分费用将流向 MEV 机器人,而不是长期 LP 头寸持有者。
假设一个 USDC/ETH 池,兑换费率为 0.3%。
初始状态:
如果此时发生一笔交易,Bob 和 Charlie 将按 50/50 平分兑换费,因为他们各拥有 50% 的活跃流动性。

图 2: 一笔 100 万美元的兑换交易即将执行前,JIT 机器人存入 990 万美元流动性,从而捕获该笔交易产生的 3,000 美元费用中的 99%。池中各有 5 万美元的 Bob 和 Charlie 仅各获得 15 美元,而非在没有干预时本应获得的 1,500 美元。
主要交易:
最终结果如何?
如果没有 Alice,Bob 和 Charlie 本应平分 3,000 美元的费用。由于 JIT 流动性"攻击",他们每人只赚了 15 美元。Alice 获得了该笔兑换 99% 的费用,影响了其他流动性提供者的收益。
只要验证者有权选择哪些交易被包含以及以何种顺序包含,MEV 就会存在。其范围从良性到有害:套利机器人在市场间平衡价格,JIT 流动性在侵蚀被动 LP 收益的同时改善了交易者的执行价格,而三明治攻击则直接从不设防的用户那里窃取资金。区分这些结果的不是 MEV 基础设施本身,而是目标智能合约是否给了攻击者可乘之机。
内存池是公开的,每一笔待处理的交易都是意图的声明,任何运行机器人的人都可以读取。这种不对称性不会消失,但开发者可以控制的是,这种可见性对用户造成的成本有多大。一个简单的 minAmountOut 检查,就能区分一笔安全回滚的交易和一笔将用户 80% 价值拱手让给机器人的交易。假设每一笔交易都在被监视,并据此进行构建。
需要进行智能合约代码安全审查? 立即联系我们的安全团队,进行全面审计。
作者

Andrei Popa
专注于 EVM、StarkNet 和 Solana 生态系统的智能合约安全研究员,拥有审计高价值去中心化金融和基础设施协议的经验。
- 原文链接: nethermind.io/blog/front...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!
作者暂未设置收款二维码