一篇让你读完上手的“MEV”防薅指南:你的每一笔交易,都可能被机器人“夹击”
前言:一个价值21.5万美元的教训
2025年3月12日,一位加密货币交易者在Uniswap V3上进行了一笔看似普通的交易——他打算将价值22.08万美元的USDC稳定币兑换成等值的USDT。
几秒钟后,他账户里只剩下5,272 USDT。
21.57万美元,瞬间蒸发。
这不是代码漏洞,不是黑客攻击,也不是私钥泄露。他只是被一个看不见的“夹子”夹了一下——三明治攻击(Sandwich Attack) 。
攻击者通过向区块构建者(bobTheBuilder)打赏了20万美元,从这笔交易中获利约8000美元。
你可能觉得这是大额交易才需要担心的事。但事实是:只要你在去中心化交易所(DEX)上交易,无论金额大小,都可能成为MEV机器人的猎物。
本文将带你从零开始,彻底搞懂:
-
MEV是什么,为什么你的交易会被“夹”
-
三明治攻击、抢先交易是如何运作的
-
如何用代码和策略保护自己
-
在审计合约时如何发现和修复MEV漏洞
第1章:MEV到底是什么?
1.1 一个形象的比喻
想象你在一个菜市场买菜。你走到一个摊位前,问老板:“这个西红柿多少钱一斤?”
就在这时,旁边有个人听到你要买,抢先一步把摊位上所有的西红柿都买走了,然后把价格抬高,再卖给你。
你多付了钱,而那个人赚了差价。
这就是MEV在区块链世界的翻版。
1.2 MEV的定义
MEV全称最大可提取价值(Maximal Extractable Value) ,最初被称为“矿工可提取价值”(Miner Extractable Value)。
在区块链中,交易被打包进区块的顺序,是由验证者(以前的矿工)决定的。拥有这种排序权力的人,可以通过重新排序、插入或排除特定交易来获取额外利润。
简单说:谁有权利决定交易顺序,谁就有机会从中“薅羊毛”。
随着以太坊从PoW转向PoS,控制交易排序的权力从矿工转移到了验证者手中,术语也从“矿工可提取价值”演变为“最大可提取价值”。
1.3 MEV是怎么发生的?
要理解MEV,需要先理解区块链的工作方式:
-
用户发起交易后,交易先进入内存池(Mempool) ——一个公开的“待处理交易池”
-
验证者从内存池中挑选交易,打包进区块
-
验证者决定这些交易在区块中的排列顺序
-
交易按顺序逐一执行,每执行一笔,区块链状态就更新一次
正是这个“排序权”,创造了MEV的机会。
一个区块大约每11秒出一个,也就是说每11秒就有人行使一次这种排序权力。
第2章:MEV的三种常见攻击方式
2.1 三明治攻击(Sandwich Attack)——最常见的“夹击”
什么是三明治攻击?
攻击者在受害者交易的前后各插入一笔自己的交易,形成“攻击者交易 → 受害者交易 → 攻击者交易”的三明治结构。
之所以叫“三明治”,是因为受害者的交易被夹在中间,就像三明治的馅料。
攻击流程:
text
┌─────────────────────────────────────────────────────────────┐ │ 三明治攻击结构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 🥪 第一片面包 → 🥩 受害者交易 → 🥪 第二片面包 │ │ (攻击者抢先买入) (被迫高价买入) (攻击者高价卖出) │ │ │ │ 攻击者获利 = 第二片面包的收入 - 第一片面包的成本 │ │ 受害者损失 = 实际成交价 - 预期成交价 │ │ │ └─────────────────────────────────────────────────────────────┘
一个具体的数字例子(基于Uniswap V2的恒定乘积AMM,公式为 x * y = k):
初始池子状态:
-
USDC储备:100,000
-
X代币储备:100,000
-
常数 k = 100,000 × 100,000 = 10,000,000,000
受害者Alice:准备用1,000 USDC买入X代币。
攻击者:决定对Alice实施三明治攻击。
第一步:攻击者前置买入(第一片面包)
攻击者先用1,000 USDC买入X代币:
-
前置后USDC储备:100,000 + 1,000 = 101,000
-
X代币储备变为:10,000,000,000 / 101,000 ≈ 99,009.9
-
攻击者买入的X代币:100,000 - 99,009.9 ≈ 990.1个X
第二步:受害者交易(中间的馅料)
Alice的1,000 USDC交易在价格已被推高后执行:
-
Alice交易后USDC储备:101,000 + 1,000 = 102,000
-
X代币储备变为:10,000,000,000 / 102,000 ≈ 98,039.2
-
Alice买入的X代币:99,009.9 - 98,039.2 ≈ 970.7个X
对比:如果没有攻击者干预,Alice本可以买到约990.1个X。因为攻击者的前置交易,Alice少买了约19.4个X——这就是她的损失。
第三步:攻击者后置卖出(第二片面包)
攻击者将之前买入的990.1个X卖回:
-
卖出后获得的USDC ≈ 1,019.8 USDC
-
攻击者净收益:1,019.8 - 1,000 ≈ 19.8 USDC
攻击者用1,000 USDC赚了约19.8 USDC(约2%),而Alice承担了所有损失。
💡 在真实网络中,攻击者还需要支付Gas费和给验证者的贿赂,但利润依然可观。
2.2 抢先交易(Front-Running)
什么是抢先交易?
攻击者监控到一笔有利可图的交易后,支付更高的Gas费让自己的交易在受害者之前执行。
典型场景:
-
你在DEX上发现一个套利机会:在A池买入便宜代币,在B池高价卖出
-
你提交了交易
-
MEV机器人监控到你的交易,复制你的策略,用更高的Gas费抢在你前面执行
-
机器人赚走了本该属于你的利润,你的交易可能因为价格变化而失败
2.3 清算套利(Liquidation)
在借贷协议(如Aave、Compound)中,当借款人的抵押品价值低于某个阈值时,任何人都可以发起清算,获得清算奖励。
MEV机器人会监控所有借款人的头寸,一旦发现可清算的机会,立刻发起清算交易,抢在其他人之前获取奖励。
第3章:为什么你的合约会被“夹”——漏洞代码剖析
3.1 存在漏洞的合约示例
下面是一个存在三明治攻击风险的简单Swap合约:
solidity
// ❌ 漏洞合约:存在三明治攻击风险
pragma solidity ^0.8.0;
contract VulnerableSwap {
IERC20 public token;
uint256 public price;
constructor(IERC20 _token, uint256 _initialPrice) {
token = _token;
price = _initialPrice;
}
// 用ETH按当前价格买入代币
function buy(uint256 amount) public payable {
require(msg.value >= amount * price, "Insufficient Ether");
token.transfer(msg.sender, amount);
}
// 按当前价格卖出代币换取ETH
function sell(uint256 amount) public {
token.transferFrom(msg.sender, address(this), amount);
payable(msg.sender).transfer(amount * price);
}
// 更新价格——任何人都可以调用!
function updatePrice(uint256 newPrice) public {
price = newPrice;
}
}
这个合约存在三个核心问题:
| 问题 | 说明 |
|---|---|
| 价格可被任意操纵 | updatePrice 函数没有任何访问控制,任何人都可以修改价格 |
| 无滑点保护 | buy 和 sell 函数没有设置最小输出或最大输入限制 |
| 交易公开可见 | 所有交易都在公开内存池中,攻击者可轻松监控 |
3.2 攻击者如何利用这个合约
text
攻击流程: 1. 监控阶段:攻击者监控内存池,发现 Alice 调用 buy(100) 购买100个代币 2. 前置交易:攻击者调用 updatePrice(200),将价格从100提高到200 → Alice的buy交易现在需要支付 100 × 200 = 20,000 ETH(原本只需10,000 ETH) 3. 受害者交易:Alice的交易执行,被迫多支付10,000 ETH 4. 后置交易:攻击者调用 updatePrice(100),将价格恢复原状 → 攻击者完成了“低买高卖”的套利 攻击者利润:约 10,000 ETH(由Alice承担)
3.3 安全的合约应该怎么写?
加入滑点保护的优化版合约:
solidity
// ✅ 安全合约:带滑点保护
pragma solidity ^0.8.0;
contract SafeSwap {
IERC20 public token;
uint256 public price;
address public owner;
// 自定义错误,节省Gas
error SlippageExceeded();
constructor(IERC20 _token, uint256 _initialPrice) {
token = _token;
price = _initialPrice;
owner = msg.sender;
}
// 只有owner可以更新价格
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 买入:带最小输出量保护(防止滑点过大)
function buy(
uint256 amount, // 想要买入的代币数量
uint256 minAmountOut // 最小可接受的输出(滑点保护)
) public payable {
uint256 expectedCost = amount * price;
require(msg.value >= expectedCost, "Insufficient Ether");
// ⭐ 关键:滑点保护——实际得到的不能少于minAmountOut
uint256 actualAmount = amount; // 简化示例
require(actualAmount >= minAmountOut, SlippageExceeded());
token.transfer(msg.sender, amount);
}
// 卖出:带最小输出量保护
function sell(
uint256 amount,
uint256 minEthOut // 最小可接受的ETH输出
) public {
token.transferFrom(msg.sender, address(this), amount);
uint256 ethAmount = amount * price;
// ⭐ 滑点保护
require(ethAmount >= minEthOut, SlippageExceeded());
payable(msg.sender).transfer(ethAmount);
}
// 只有owner可以更新价格
function updatePrice(uint256 newPrice) public onlyOwner {
price = newPrice;
}
}
关键改进点:
-
访问控制:
updatePrice只有owner可以调用 -
滑点保护:用户可设置
minAmountOut,防止价格被操纵时以极端不利价格成交 -
自定义错误:使用
SlippageExceeded节省Gas
第4章:如何防御MEV攻击——从用户到开发者的完整指南
4.1 作为普通用户,你可以这样做
① 设置合理的滑点容忍度
在Uniswap等DEX交易时,滑点容忍度是你最重要的防线。
Uniswap V3默认将滑点容忍度设置为0.1%,这意味着只有在执行价格不低于你看到价格的99.9%时,交易才会执行。
| 滑点设置 | 优点 | 缺点 |
|---|---|---|
| 0.1%-0.5%(推荐) | 有效防止三明治攻击 | 高波动时交易可能失败 |
| 1%-3% | 交易成功率更高 | 给攻击者留下较大利润空间 |
| >5%(危险) | 几乎不会失败 | 极易被三明治攻击 |
⚠️ 关键认知:滑点容忍度不是你“愿意接受的价格范围”,而是攻击者可以利用的利润空间。设置得越高,被“夹”的风险越大。
② 使用私有交易通道
将交易直接发送给矿工/验证者,绕过公开内存池。
Flashbots Protect是最常用的方案:
-
将交易直接发送给区块构建者,不在公开内存池中广播
-
攻击者无法监控到你的交易,自然无法“夹击”
-
使用方法:将钱包RPC地址改为
https://rpc.flashbots.net
③ 使用抗MEV的DEX
-
CowSwap:通过“需求巧合(CoW)”匹配和统一清算价格,从根本上消除MEV机会
-
1inch:提供部分填充功能和MEV保护
-
这些平台通过批量处理和链下聚合,让所有订单以相同价格执行
④ 将大额交易拆分为多笔小额交易
降低单笔交易对市场的价格冲击,减少被攻击者盯上的概率。
4.2 作为开发者/审计者,你应该这样做
① 在合约中实现滑点保护
solidity
// 用户在调用时指定最小输出量
function swap(
address tokenIn,
uint256 amountIn,
uint256 minAmountOut // ⭐ 滑点保护参数
) external {
// ... swap逻辑 ...
require(amountOut >= minAmountOut, "Slippage too high");
}
② 使用时间锁(Timelock)
延迟执行,减少交易的可预测性。
solidity
uint256 public deadline;
function swap(uint256 amountIn, uint256 minAmountOut, uint256 deadline) external {
require(block.timestamp <= deadline, "Transaction expired");
// ... swap逻辑 ...
}
③ 使用提交-揭示模式(Commit-Reveal)
先提交交易哈希(隐藏意图),再揭示具体内容。
④ 审计时的MEV检查清单
在审计智能合约时,需要重点检查以下MEV相关风险:
| 检查项 | 说明 |
|---|---|
| 滑点保护 | 所有Swap函数是否有 minAmountOut 或 maxAmountIn 参数? |
| 访问控制 | 价格更新、参数调整等敏感函数是否有限制? |
| 交易顺序依赖 | 合约逻辑是否依赖特定的交易执行顺序? |
| 预言机安全 | 价格数据来源是否可被操纵?(TWAP vs 瞬时价格) |
| 批量操作 | 批量处理时是否存在跨用户MEV提取风险? |
| 时间戳依赖 | 是否依赖 block.timestamp 做关键决策? |
| 私有RPC | 项目方是否建议用户使用私有RPC? |
第5章:真实世界案例——损失触目惊心
案例一:21.5万美元瞬间蒸发(2025年3月)
一位交易者在Uniswap V3上将22.08万美元的USDC兑换为USDT,结果仅收到5,272 USDT,损失21.57万美元。
攻击者向区块构建者支付了20万美元贿赂,自己净赚8,000美元。
案例二:最活跃的三明治机器人被反向攻击(2026年6月)
以太坊上最活跃的三明治机器人 Jaredfromsubway.eth 被黑客反向攻击,损失超过750万美元。
讽刺的是,在2024年11月至2025年10月期间,这个机器人负责了约70%的以太坊三明治攻击。
案例三:OKX DEX遭遇MEV攻击(2025年6月)
OKX DEX因配置问题遭遇MEV攻击,损失4.7万美元。这说明即使是大型平台,如果没有做好MEV防护,同样会遭受损失。
案例四:Coinbase因合约配置失误损失30万美元(2025年8月)
Coinbase因与0x协议的“交换合约”配置错误,导致MEV机器人从其企业钱包中抽取资金,损失约30万美元。
第6章:总结——你的防“夹”行动清单
✅ 作为普通用户
- □
将DEX滑点容忍度设置在 0.1%-0.5% 之间
- □
使用 Flashbots Protect 或类似私有RPC服务
- □
大额交易拆分为多笔小额交易
- □
优先使用 CowSwap 等抗MEV的DEX
- □
避免在网络拥堵时进行大额交易
✅ 作为开发者/审计者
- □
所有Swap函数实现 滑点保护(minAmountOut)
- □
敏感函数(价格更新、参数调整)设置 访问控制
- □
使用 TWAP 而非瞬时价格作为价格来源
- □
在合约中设置 交易截止时间(deadline)
- □
审计时模拟MEV攻击场景,验证合约韧性
✅ 作为审计者,在报告中记录
审计MEV相关风险时,审计报告应包含:
-
模拟攻击:在受害者交易前后模拟攻击者交易
-
检查顺序依赖:审查每个因排序改变而影响价格、资格、 payout或清算状态的函数
-
验证保护机制:确认滑点、截止时间、批量执行等保护措施是否到位
最后的话:MEV不是区块链的“bug”,而是其“特性”的副作用——交易排序的权力必然带来寻租空间。但这不意味着我们只能被动挨打。通过合理的滑点设置、私有交易通道、抗MEV的DEX,以及在合约层面实现完善的保护机制,我们可以大大降低被“夹击”的风险。
你的每一笔交易,都可能被机器人盯上。但读完这篇指南,你已经知道如何不让它得逞。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)