前言:一个价值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,需要先理解区块链的工作方式:

  1. 用户发起交易后,交易先进入内存池(Mempool) ——一个公开的“待处理交易池”

  2. 验证者从内存池中挑选交易,打包进区块

  3. 验证者决定这些交易在区块中的排列顺序

  4. 交易按顺序逐一执行,每执行一笔,区块链状态就更新一次

正是这个“排序权”,创造了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费让自己的交易在受害者之前执行。

典型场景

  1. 你在DEX上发现一个套利机会:在A池买入便宜代币,在B池高价卖出

  2. 你提交了交易

  3. MEV机器人监控到你的交易,复制你的策略,用更高的Gas费抢在你前面执行

  4. 机器人赚走了本该属于你的利润,你的交易可能因为价格变化而失败

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;
    }
}

关键改进点:

  1. 访问控制updatePrice 只有 owner 可以调用

  2. 滑点保护:用户可设置 minAmountOut,防止价格被操纵时以极端不利价格成交

  3. 自定义错误:使用 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相关风险时,审计报告应包含:

  1. 模拟攻击:在受害者交易前后模拟攻击者交易

  2. 检查顺序依赖:审查每个因排序改变而影响价格、资格、 payout或清算状态的函数

  3. 验证保护机制:确认滑点、截止时间、批量执行等保护措施是否到位

最后的话:MEV不是区块链的“bug”,而是其“特性”的副作用——交易排序的权力必然带来寻租空间。但这不意味着我们只能被动挨打。通过合理的滑点设置、私有交易通道、抗MEV的DEX,以及在合约层面实现完善的保护机制,我们可以大大降低被“夹击”的风险。

你的每一笔交易,都可能被机器人盯上。但读完这篇指南,你已经知道如何不让它得逞。

Logo

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

更多推荐