T24 · MEV
你的交易在进块之前,被谁看过?
- 练习的能力
- BuilderProtocol Literacy
- 动手
- 在 Fork 环境里对自己的交易做一次三明治攻击,量化用户损失了多少。
- AI Lab
- 让 AI 列出用户侧的防护手段,逐项评估它们在你的产品里是否可行。
一个现实问题
你的兑换界面上线两个月,用户开始抱怨同一句话:「成交价总是比你们报的差一点。」
你按 T20 那一章排查了一遍,每一项都是对的:报价用的是当前储备,路由选的确实是最优路径,最低到账数量写进了用户的签名,过期时间也在。最关键的是——交易没有 revert。滑点保护生效了,用户拿到的数量确实不低于他签名时声明的下限。
于是你去区块浏览器上看其中一笔。
它在那个区块里排第 47 位。你顺手看了一眼第 46 位和第 48 位:同一个发起地址,同一个池子,一买一卖,中间正好夹着你的用户。
你把这三笔按顺序在本地重放,算出用户少拿了多少。那个数字非常眼熟——它几乎正好等于你前端默认的滑点容差。
用户没有被骗。合约没有 bug。检查全部通过。他只是被排在了中间。
所以真正的问题是:这三笔交易的先后顺序,是谁定的?他凭什么这么定?
思想实验
把区块链缩小成一个村子。
村口有一块公告栏,谁要办事就把条子贴上去。每隔 12 秒来一个打包员,从公告栏上挑一批条子,按他愿意的顺序一次办掉。
先只看这两条规则,就能推出三件事。
第一,公告栏是公开的,而且是「事情办完之前」公开的。 你贴的条子上写着你要买什么、买多少、最差能接受什么价。在它被办掉之前,全村人都读得到。这不是泄密,这是这块公告栏的工作方式——它必须公开,别人才能验证打包员没有乱来。
第二,顺序由打包员定,而且他可以插自己的条子。 他可以把你的条子放在第 47 位,也可以先放一张自己的。
第三,他甚至不必自己动手。 他可以对外宣布:「明天那一批,谁出钱最多,我就把谁的条子排最前面。」
第三条是整章的转折点。到这里,「排在谁前面」不再是一个技术细节,它变成了一件可以定价、可以拍卖、有人愿意出钱买的东西。
有了这个东西,村里立刻长出三门生意,先不给它们名字:
甲:两个摊位卖同一件货,价格不一样。他在便宜的摊位买、在贵的摊位卖。做完之后两边价格一样了,看起来谁也没受损——但那笔差价原本属于给摊位出货的人。
乙:他在公告栏上看见你要买一大批货。他抢在你前面买,等你把价格推上去之后再卖给你。你的意图本身,就是他的利润来源。
丙:他看见某个抵押品刚好跌破了可以被收走的线(T22 那件事)。他抢在所有人前面去收,拿走那笔奖励。
这三门生意有一个共同点,非常重要:没有一门用到了漏洞。 每一步都是合法操作,每一笔都按规则执行,每一个检查都通过。
它们利用的不是代码里的错误,是这个系统的结构本身。
你来决定
用户已经签好名了。这笔交易接下来要从你的代码里发出去。
你发到哪?
观察结果
四个选项摆在一起,它们回答的是同一个问题:你的意图,在多长时间里、对多少人可见;以及谁有资格从这个可见性里赚钱。
| 做法 | 谁看得到你的意图 | 你在信任谁 | 主要代价 | 失败模式 |
|---|---|---|---|---|
| 公开内存池 + 紧容差 | 所有人 | 不信任任何人 | 失败率高 | 反复失败被逼放宽 |
| 公开内存池 + 宽容差 | 所有人 | 不信任任何人 | 被榨走的部分不可见 | 用户长期损失且毫无察觉 |
| 私有通道 | 一个运营方 | 这个运营方的承诺 | 引入新的对手方 | 承诺被违反,你无法验证 |
| 意图 + 竞价执行 | 一组执行方 | 竞争的激烈程度 | 延迟、执行方集中 | 竞争不足时价格悄悄变差 |
看第三列。这一列是本章最该记住的东西:
MEV 不能被消除,只能被转移。你的每一个防护动作,都是在改变「谁有资格从你的交易顺序里赚钱」,而不是让这个数字变成零。
任何一句「用了某个服务就安全了」的说法,都在这一列上撒了谎——它只是没告诉你新的信任对象是谁。
第二个要记住的是一个可以直接算的门槛。它决定了你这笔交易到底会不会被盯上:
攻击者毛利 ≈ 交易金额 × 他能把你推到的滑点空间
攻击者成本 ≈ 两笔兑换的手续费 + 两笔 Gas + 抢到这个位置的出价
净利大于 0,他就会做;小于等于 0,他连看都不会看你一眼把它反过来读,就是 T20 那句话的量化版本:你设的滑点容差,是你主动挂出的悬赏金额。 容差乘以交易金额,等于你为这笔交易开出的赏金;这个赏金超过攻击成本的那一刻,你就进入了被攻击的范围。
于是这一章的那句话:交易顺序本身就是一种可以卖钱的资源。
它不是一个漏洞,不会被某个版本修掉。它是「有人必须决定顺序」这件事的必然结果。你能做的只有一件:知道这笔钱有多大、现在归谁。
建立模型
一、你的交易被谁看过
- 你的前端
- 钱包
- RPC 提供方
- 公开内存池
- 搜索者
- 区块构建者
- 区块提议者
- 进块
| 环节 | 它看到什么 | 它能做什么 | 你能不能验证 |
|---|---|---|---|
| RPC 提供方 | 完整交易 | 延迟转发,或转给别人 | 不能 |
| 公开内存池 | 完整交易 | 全网可读 | 能(这是它的设计目的) |
| 搜索者 | 完整交易 | 构造夹击、抢跑、套利 | 不能 |
| 区块构建者 | 完整交易与所有出价 | 决定最终顺序 | 事后能,事前不能 |
| 区块提议者 | 打包好的区块 | 接受或换掉 | 事后能 |
第一行经常被完全忽略。 很多人以为「不进公开内存池就安全了」,但交易在到达内存池之前已经过了一个 RPC 提供方。它是链条上第一个看到你交易的人,也是唯一一个你在代码里直接配置、却几乎从不审视的人。
二、四种形态,两类性质
| 形态 | 利润来自谁 | 没有人做会怎样 | 用户是否因此受损 |
|---|---|---|---|
| 套利 | 提供流动性的人 | 两个市场价格长期不一致 | 否(但 LP 受损) |
| 清算 | 被清算的用户 | T22 里的坏账 | 否(他本来就要付) |
| 抢跑 | 发起交易的人 | 什么都不会发生 | 是 |
| 三明治 | 发起交易的人 | 什么都不会发生 | 是 |
这张表最重要的是第三列,它把四种形态分成了截然不同的两类:
前两行是「有人必须做的事」。 没有套利者,两个场所的价格会一直背离;没有清算人,借贷协议会积累坏账。它们提取的价值是真实存在的,只是被某个人拿走了。
后两行是「只因为你发了这笔交易才存在的事」。 如果没有人做,这笔钱根本不会产生——它不是被转移的价值,它是被创造出来的成本,而全部由你承担。
区分这两类,是判断一个「MEV 解决方案」有没有意义的唯一标准。一个方案如果只是把套利收益从 A 转给 B,它没有帮到用户;只有减少了后两行,才是真的改善。
三、三明治的数学:最优攻击规模由你的容差决定
这是本章可以完全算清楚的一段。用一个恒定乘积池(T19 那条曲线),下面这两个函数不依赖任何外部库,可以直接抄进你的测试里:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// 恒定乘积报价:手续费按万分之 feeBps 从输入侧扣,典型值是 30
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut,
uint256 feeBps
) pure returns (uint256) {
uint256 inAfterFee = amountIn * (10_000 - feeBps);
return reserveOut * inAfterFee / (reserveIn * 10_000 + inAfterFee);
}
/// 用户在「没被夹」和「被夹」两种情况下拿到的差额
/// 这一个减法,就是本章要你亲手测出来的那个数字
function sandwichLoss(
uint256 userIn,
uint256 attackerIn, // 攻击者抢跑投入的金额
uint256 reserveIn,
uint256 reserveOut,
uint256 feeBps
) pure returns (uint256) {
uint256 clean = getAmountOut(userIn, reserveIn, reserveOut, feeBps);
// 攻击者先买,把储备推到新的位置
uint256 got = getAmountOut(attackerIn, reserveIn, reserveOut, feeBps);
uint256 newIn = reserveIn + attackerIn;
uint256 newOut = reserveOut - got;
// 用户在被推高之后的曲线上成交
uint256 sandwiched = getAmountOut(userIn, newIn, newOut, feeBps);
return clean - sandwiched;
}现在看攻击者怎么选 attackerIn。他受一个硬约束:用户签名里的最低到账数量。 推过头,用户那笔交易整个回滚,他的两笔操作白做还倒贴手续费。
所以他的最优解非常明确:
最优 attackerIn = 恰好让 sandwiched 等于用户的 amountOutMin 的那个值
也就是说:
用户损失的上限 = 交易金额 × 滑点容差
而攻击者会精确地取到这个上限,一分不多一分不少这句话值得停一下。 攻击者不需要猜、不需要贪心,你的容差直接告诉了他该投多少钱。你把容差从 0.5% 改成 5%,等于把他的目标利润乘以十。
他会不会真的动手,取决于毛利够不够覆盖成本。把两边写全:
他的毛利 ≈ 用户损失 − 他自己两笔兑换被池子抽走的手续费
他的成本 ≈ 两笔 Gas + 为了排在你前后所付出的出价
净利大于 0 才动手。
所以存在一个最小可攻击金额:低于它的交易,没有人会看一眼。Lab 的任务就是把这个最小金额算出来。它比任何一条防护建议都有用,因为它告诉你风险是从哪一档交易规模开始的。
四、三条防护路径,各自把权力交给了谁
| 手段 | 它减少的是什么 | 权力转移给谁 | 你无法验证的那件事 |
|---|---|---|---|
| 收紧滑点容差 | 可榨取价值 | 没有转移 | 无(这是唯一不引入新信任的手段) |
| 私有交易通道 | 暴露面 | 通道运营方 | 他有没有偷看、有没有转手 |
| 意图加竞价执行 | 暴露面,且部分收益返还 | 执行方群体 | 竞争到底够不够激烈 |
第二列区分了两类完全不同的东西:减少可榨取价值是从根上减小那笔钱,减少暴露面是把能看到这笔钱的人变少。前者不需要信任任何人,后者必然引入一个新的对手方。
实践里三者是叠加使用的,不是三选一。 一个合理的默认配置通常长这样:容差按交易金额与池子深度动态计算(而不是一个写死的百分比),大额交易走私有通道或竞价执行,并且在界面上把估算的价格影响明确展示给用户——最后这一条不花任何钱,却是多数产品缺失的一环。
它叫什么
通过决定一个区块里交易的包含、排除与顺序,可以额外提取到的价值。
关键词是顺序,不是漏洞。它不会被某个版本修掉,因为「必须有人决定顺序」是这个系统的结构性事实。你能做的不是消灭它,而是弄清这笔钱有多大、此刻归谁。
利用两个场所之间的价格差获利。
它是四种形态里唯一具有明确正外部性的一种:它让分散市场的价格保持一致。但要看清代价——它的利润来自提供流动性的人。LP 的无常损失,有相当一部分是以这种形式被拿走的。
在用户交易的前后各插一笔,把价格推到用户容差的边缘再兑现价差。
它和套利有本质区别:这笔损失是被创造出来的,不是被转移的。 没有人做这件事,它根本不存在。而它能拿走多少,由用户自己设的滑点容差精确决定。
决定一批交易以什么顺序执行的权力。
它是这一章真正的主角。一旦它可以被出售,围绕它就会长出一条完整的供应链:搜索者发现机会、构建者组装区块、提议者出售位置。交易顺序是一种资源,而任何有价资源都会形成市场。
不经过公开广播、直接送达区块生产方的交易通道。
它把「所有人可见」换成「一个人可见」。这是一次信任的重新分配,不是一次风险的消除。 使用它之前必须回答:这个运营方是谁、他承诺了什么、这个承诺由什么保证、违反了你能不能发现。
在意图模式下,替用户寻找并执行最优实现路径的竞争性参与者。
它把顺序权的收益变成竞价对象,一部分会以更好的成交价回到用户手上。代价是执行方这一层天然趋向集中:它需要资金、基础设施和算法。用它之前先问一句——这些执行方的收入从哪来? 答案永远存在,只是不一定写在文档里。
动手
这个 Lab 的产出是一个你自己测出来的数字,不是一段理解。做完之后你应该能回答:在我的产品的默认设置下,一笔一万块的兑换会被拿走多少。
准备环境,先把报价对齐。
在本地 Fork 一条链,挑一个恒定乘积池子,记录它的两侧储备和手续费率。把上面那个 getAmountOut 抄进测试里,用它算一笔兑换的输出,和链上实际调用的返回值比对。必须完全一致,一个单位都不能差——对不上就是取整方向或手续费位置写错了,T19 那张取整表在这里再看一遍。后面所有的损失测算都建立在这一步之上。
跑基线,再做一次夹击。 先用一个测试地址单独发一笔兑换,金额取 10000(按池子的计价单位),记录他实际收到多少,把它叫做 clean——它是后面所有对比的分母。
然后三笔交易按顺序在同一个区块里执行:
第 1 笔 攻击者买入 a ← 把价格推高
第 2 笔 用户的兑换 ← 在被推高的曲线上成交
第 3 笔 攻击者把第 1 笔买到的全部卖出记录三件事:用户这次收到多少(sandwiched)、攻击者最后拿回多少、他比投入多了多少。用户损失就是 clean 减 sandwiched——把它换算成百分比,和你产品的默认滑点容差放在一起看。
扫描攻击者的最优规模。
固定用户的交易和容差,让 a 从很小扫到很大,每一档记录用户损失和攻击者净利。你会看到两条曲线:用户损失随 a 单调上升,直到用户交易跌破下限而回滚;攻击者净利先升后降——推得太狠,用户那笔就不成交了,他血本无归。
找出净利最高的那个 a,然后验证一件事:在这个 a 下,用户收到的数量是不是几乎正好等于他签名里的最低到账数量。如果是,你就亲手确认了本章那个结论——用户的滑点容差,精确地决定了攻击者的目标利润。
填一张二维表。
横轴是滑点容差,纵轴是交易金额。每格填两个数:用户损失的百分比,以及攻击者的净利。
0.1% 0.5% 1% 5%
1,000 ... ... ... ...
10,000 ... ... ... ...
100,000 ... ... ... ...用一条线把「攻击者净利大于 0」的区域圈出来。 线的左下方是安全区——不是因为你做了什么防护,而是因为你不值得被攻击。
算一次攻击成本,求出最小可攻击金额。这是整章的量化练习。
已知:池子一侧深度 D,手续费率 f,你的滑点容差 s,当前 Gas 价格 g
第 1 步:用上一步扫出的最优 a,算出攻击者毛利
毛利 ≈ 用户损失 − 攻击者自己两笔兑换被抽走的手续费
(注意第二项:他也要付两次 f,很多估算漏掉这一项)
第 2 步:算他的成本
两笔 Gas = 2 × 单笔兑换 Gas × g
抢位出价 = 他为了排在你前后愿意让出的那部分毛利
(保守起见按毛利的一半估)
第 3 步:净利 = 第 1 步 − 第 2 步
第 4 步:令净利等于 0,反解出交易金额 V
这个 V 就是你这个池子、这个容差下的最小可攻击金额把 V 算出来,然后做三件事:
- 把 Gas 价格乘以 10 再算一次——拥堵时 V 会显著变大,小额交易反而更安全。
- 把容差从 5% 改成 0.5% 再算一次,看 V 变成多少倍。
- 回到你自己的产品,统计一下有多少比例的真实交易金额超过了 V。
第 3 条是这个 Lab 真正的产出。它把一个抽象的风险变成了一个你可以写进周报的百分比。
换一条防护路径,重测一次,并写下代价。
在 Fork 里模拟私有通道很简单:不执行第 1 笔和第 3 笔,只执行用户那笔,用户损失归零。这不意外,有价值的是后半段——把它写下来:
| 问题 | 你的答案 |
|---|---|
| 这条通道的运营方是谁 | |
| 他承诺了什么,写在哪里 | |
| 这个承诺由什么保证 | |
| 他违反承诺时,你能不能发现 | |
| 他停止服务时,你的降级路径是什么 |
五个格子填不满,这条防护就不能算数。 它不是「开一个开关」,它是引入了一个新的对手方。
全程本地 Fork,不接任何真实资金,攻击脚本只对你自己的测试地址执行。 真实资金只出现在毕业项目,并且必须先通过第五阶段的安全验收。
AI Lab
分三步问,第二步和第三步才是价值所在:
第一步:
列出一个链上兑换产品可以采取的用户侧 MEV 防护手段。
每一条给出:它减少的是可榨取价值还是暴露面、
实现成本、对用户体验的影响、以及它把信任转移给了谁。
不要给结论,先把清单列全。
第二步:
我的产品情况是(自己写):用户规模、典型交易金额分布、
所在的链、当前默认滑点容差、接入的 RPC 来源。
逐条评估上面每一个手段在我这里是否可行,
给出可以量化的预期效果和可以量化的代价。
第三步:
针对你推荐的前两条,分别写出:
它在什么情况下会失效、失效时我能观察到什么信号、
以及如果它的提供方停止服务,我的降级路径是什么。第一步几乎所有模型都能列得很全,但普遍会把「用某个私有通道」写成一条完整的解决方案。追问它那个通道的运营方是谁、承诺由什么保证——答不上来,这一条就还没有被评估。第二步的常见问题是给出笼统的「显著降低」,要求它给数字:按你提供的金额分布,这条手段能覆盖多少比例的交易、减少多少损失。没有数字的评估在这一章里不算评估。
第三步是这个 Lab 真正的产出,而且它是一个通用的追问模板:任何防护手段,都必须回答「它什么时候失效」和「失效时我怎么知道」。 一个只会在正常情况下工作、而你无法察觉它失效的防护,比没有防护更危险——因为你会据此放松别的地方。
拿到清单之后,把第二步的每一项和你 Lab 里那张二维表对照。它推荐的手段,有没有覆盖你真实交易金额落在的那几格?
AI 说完之后,你必须自己验证
- 每一条手段,它有没有说清「权力转移给了谁」——说不出新的信任对象,这条就没评估完
- 它有没有区分「减少可榨取价值」和「减少暴露面」,这是两类完全不同的手段
- 它给的滑点建议是一个固定百分比,还是随交易金额与池子深度变化的公式
- 它有没有把某个私有通道或某个服务当成终点,而不是一次信任的重新分配
- 讲到意图与竞价执行时,它有没有回答执行方的收入从哪来
- 它有没有提到 RPC 提供方也是一个能看到你交易的环节
- 它给的任何具体服务名、接口、参数,你是否去核对过,而不是采信它的记忆
真实案例
在公开内存池上,夹击不是偶发事件,而是一条完整的产业链:有人专门扫描内存池寻找目标,有人专门优化区块内的位置竞价,有人专门提供资金。
它的规模可以被直接统计——这是这个行业少数几件完全公开可查的事,任何人都可以从链上数据里把每一笔夹击复原出来。
对做产品的人来说,结论很具体:你的默认滑点容差不是一个体验参数,它是一个有人在实时读取、并据此定价的公开变量。
一组区块提议者本应按规则原样打包搜索者提交的内容,却在拿到区块之后把里面的交易替换掉,把搜索者准备好的机会自己拿走。涉及金额是一个很大的数字。
这个案例的价值不在金额,在它证明了一件事:私有通道的保护是一份承诺,不是一个密码学保证。 它依赖某一方按规则行事,而当违反规则的收益足够大时,规则就只是规则。
对照 Lab 最后那张表:第三行「这个承诺由什么保证」,在很多方案里的真实答案是「声誉」。声誉可以在一次足够大的机会面前被一次性兑现掉。
一些二层网络由单一排序器决定交易顺序。没有公开内存池,也就没有公开的夹击机会——暴露面确实小了很多。
但顺序权并没有消失,它完整地集中在一个进程里。这个排序器能看到所有交易、能决定所有顺序,而用户几乎没有办法验证它做了什么。
这是「转移而非消除」最干净的一个例子:用户的风险从「被一群陌生人夹击」变成了「信任一个运营方」。 哪一种更好,取决于你更能接受哪一类失败——而这是一个必须显式做出的选择,不是一个技术细节。
回到 T22 那条链的最后一环。同一个清算机会被十个人同时发现,抢到的只有一个,另外九个白付了手续费。
这些失败成本不会凭空消失,它们会被算进清算人对「这门生意值不值得做」的判断里。要让他们在扣掉预期失败成本之后仍然有利可图,协议必须把清算奖励设得更高——而清算奖励是被清算的用户付的。
所以这条链是完整的:排序权的竞争成本,最终以更高的清算惩罚的形式,落在了普通用户头上。 这是 MEV 影响用户的一条间接路径,比三明治隐蔽得多。
改一个变量
夹击归零,用户的成交价立刻变好,你的支持工单会明显减少。
三件事同时发生了,都要写进你的风险清单。一是你有了一个新的单点:这条通道停服时,你的产品要么停摆,要么静默地回退到公开内存池——后者更危险,因为用户不会知道保护已经没了。二是你的交易对这个运营方完全透明,而你无法验证他做了什么。三是套利者也看不到你的交易了,在某些场景下这会让价格发现变慢。
降级路径必须是显式的、可观测的、对用户可见的。 一个悄悄回退到无保护状态的系统,比一个直接失败的系统更糟。
直觉上暴露窗口缩短了三十倍,应该安全得多。
实际上两件事同时变化。攻击者的反应时间确实被压缩,需要更快的基础设施,这会把参与者数量筛下去一批。但留下来的人竞争更激烈,而且一个区块里能装的交易更少,「排在前面」这个位置变得更稀缺、更贵。
更要紧的是:所有按秒或按区块数写死的假设都要重算。 你的过期时间、你的容差计算、你对「一笔交易多久会被打包」的估计,全都建立在一个具体的出块时间上。这和 T21、T22 给出的是同一条结论——换链时,所有和时间有关的参数都必须重新推导。
这是从机制层面动手,而不是从用户侧防护。同一批次内不存在「谁在前谁在后」,夹击需要的那个前提直接消失了。
代价有三个。一是延迟:用户必须等到批次结束。二是价格发现变慢:市场剧烈波动时,一个批次内的统一价格可能已经过时。三是批次边界本身变成了新的竞争点——谁的订单进入这一批、谁被推到下一批,重新成为一个有价值的决定。
这是机制设计里的常态:一个机制消除了某种可提取价值,通常会在别的地方创造出一个新的稀缺位置。 判断它值不值,要看新位置的价值是不是显著小于旧的。
这是四个手段里唯一不引入任何新信任的一个,而且成本最低——它只是前端的一段计算。
小额交易拿到一个宽松的容差(反正毛利覆盖不了成本,没人会动手),大额交易自动收紧,并在超过某个阈值时主动建议拆单或切换执行路径。
真正的难点不在公式,在你需要实时知道池子深度。这把你带回了 T17 和 T18:这个数字必须来自你自己的索引与实时通道,而不是一个可能已经过期的缓存。一个用陈旧深度算出来的容差,比一个固定值更危险,因为它看起来是智能的。
带走的问题
这一章里最该被问清楚的一笔钱:用户在为「交易顺序」付费,而这笔费用不出现在任何一张账单上。 它表现为一个略差的成交价,没有单据,没有提示,用户通常毫无察觉。
一个可操作的习惯:看到「零手续费」「免 Gas」的产品,先问这笔交易的顺序权归谁。 明面上的费用被去掉时,成本往往只是换了一个位置。
MEV 供应链上的每一层都有收入,而且都可以被具体说出来:搜索者赚价差,构建者赚打包费,提议者卖位置,私有通道赚服务费或分成,执行方赚竞价剩余。
把这条链画出来、在每一环上写下「他靠什么赚钱」,是评估任何一个「MEV 解决方案」的第一步。收入说不清楚的那一环,就是这个方案里你没看懂的那一环。
风险分三处,而且它们的可见度完全不同:用户承担成交价变差(不可见),LP 承担被套利拿走的那部分(部分可见),协议承担声誉与用户流失(滞后可见)。
值得单独记住的是第一处:这类损失的特点是「成功的交易」。交易失败用户会来问你,被榨走 5% 用户什么都不会说。一个只靠用户投诉来发现问题的团队,永远发现不了这一类损失。
激励在这一章异常清晰:只要顺序有价值,就一定有人出钱买它。 这不需要任何人作恶,它是理性行为的必然结果。
所以任何依赖「大家都守规矩」的防护方案都要被追问一句:当违反规则的一次性收益,大于遵守规则的长期收益时,会发生什么? 那个 2023 年的案例给出了答案。
本章自测
区别在于这笔钱是被转移的,还是被创造出来的。
套利利润来自两个市场之间已经存在的价格差,它提取的价值确实存在(由 LP 承担),而且它顺带把价格拉平了。没有套利者,这个价差会一直挂着。
三明治不一样:没有人做这件事,这笔损失根本不存在。 它不是在纠正什么,它完全是因为你发了一笔可见的交易才产生的额外成本。
这个区分是判断任何「MEV 方案」的唯一标准:只把套利收益从 A 转给 B 的方案,对用户没有帮助;只有减少了抢跑与夹击的方案,才是真的改善。
是一个精确的等式关系,不是一个模糊的相关性。
攻击者受一个硬约束:推过头,用户交易跌破最低到账数量整笔回滚,他白付两笔 Gas。所以他的最优策略是推到刚好让用户在容差边缘成交,一分不多。
于是:用户损失的上限等于交易金额乘以滑点容差,而攻击者会精确取到这个上限。 你把容差从 0.5% 改成 5%,等于把他的目标利润乘以十。
他会不会真的动手,取决于这个毛利能不能覆盖两笔手续费、两笔 Gas 和抢位出价——这就是 Lab 里那个最小可攻击金额。
不对的地方在于它把一次信任的重新分配说成了一次风险的消除。
你的交易确实不再被所有人看见了,但它被这条通道的运营方完整看见。他能决定是否打包、什么时候打包,理论上也能把机会转给别人。你从「面对一个公开市场」变成了「信任一个对手方」。
判断一条通道能不能用,五个问题缺一不可:运营方是谁、他承诺了什么、承诺写在哪里、由什么保证、违反时你能不能发现。大多数方案在第四个问题上的真实答案是「声誉」,而 2023 年那个案例说明,声誉可以在一次足够大的机会面前被一次性兑现掉。
还有一个常被忽略的前置环节:你的 RPC 提供方在交易到达任何内存池之前就已经看到它了。
没有。它做的是把顺序权的收益变成一个竞价对象,让其中一部分以更好的成交价返还给用户。
这确实是一个真实的改善,但三件事没有变:执行方仍然在提取价值(只是分了一部分出来);返还的比例取决于竞争激烈程度,而这一点你无法从链上验证;执行这一层需要资金、基础设施和算法,天然趋向集中。
所以评估它的关键问题不是「有没有 MEV」,而是:这些执行方的收入从哪来,竞争够不够激烈,以及当它不够激烈时我能观察到什么信号。
没有标准答案,检查你的清单里有没有这几项:
- 每一条交易路径上,从签名到进块之间都有谁看到过它——包括那个你从来没审视过的 RPC 提供方。
- 你的默认滑点容差是多少,是固定值还是动态计算的,这个值乘以你的典型交易金额,等于多少钱。
- 你的最小可攻击金额是多少,你有多少比例的真实交易超过了它。
- 你用的每一个防护手段,把信任交给了谁,以及它失效时你能观察到什么信号。
- 你的降级路径:私有通道停服、执行方全部退出、深度数据延迟——每一种情况下你的系统会怎么做,用户能不能看见。
- 你有没有在监控里放一条「成交价与报价的偏差分布」。这是你唯一能主动发现这类损失的信号,因为用户不会告诉你。
第 6 条是最少被做的一条,也是整章最有价值的一条落地动作:这类损失的全部特征就是「交易成功了」,它不会触发任何报警,除非你专门去看。
到这里,第四阶段结束了。从 T19 的一条曲线开始,到 T20 的一次兑换要经过多少层,到 T21 的价格从哪来、T22 的清算跑不跑得赢、T23 的账能不能平,最后到这一章的顺序归谁——这五章拼出来的是同一件事:一个 DeFi 系统真正的风险,几乎从不在合约的逻辑里,而在它和外部世界接触的那几个边界上。 第五阶段会把这些边界逐个拿出来,做成可以检查的验收项。
一句话带走
交易顺序本身就是一种可以卖钱的资源。