T29 · Protocol Security
攻击者不用漏洞,只用钱,能做到什么?
- 练习的能力
- BuilderFinancial LiteracySystem Thinking
- 动手
- 给你的协议算一次攻击成本:需要多少资金、多少 Gas、多少时间,收益是多少。
- AI Lab
- 让 AI 扮演攻击者写出攻击计划,你负责按成本收益把它们排序并给出防御。
一个现实问题
你的协议上线了。合约过了 T28 的全部检查:没有重入、权限边界清楚、取整方向都对、升级有时间锁。审计报告干干净净,一个高危都没有。
一个月后,某天下午,你的协议在十几分钟里损失了一大笔钱。
你把交易拉出来复盘,越看越困惑:
- 没有函数被非预期地调用
- 没有权限被越过
- 没有一行代码的行为和你写的不一样
- 每一笔交易,单独看,都完全合法攻击者做的事,用大白话说是这样的:他借来一大笔钱,用这笔钱在一个交易所里把某个价格推到一个离谱的位置,然后拿着这个离谱的价格来和你的协议做了一笔完全按规则进行的交易,赚走差价,最后把借来的钱还回去。全程在一笔交易里完成。
他没有攻破任何东西。他只是把一笔账算清楚了,发现它划算,然后照着做了。
T28 那一整套防御——CEI、访问控制、不变量——在这里一条都没被触发,因为它们防的是「代码做了它不该做的事」。而这次,代码做的全都是它该做的事。
所以这一章的问题,和上一章是完全不同的一个:
当攻击者不需要任何漏洞,只需要这笔账算得过来,你怎么防?
思想实验
先离开代码,想一个赌场。
赌场里有一张桌子,规则写得清清楚楚,绝对公平,庄家一分钱都不作弊。桌上有个玩法:赔率是根据「最近一小时进场的筹码方向」实时算出来的。 押的人多,赔率就往一边偏。
规则本身没有任何问题。直到有一天,一个人推着一车筹码进来。
他先用一半的筹码,在一分钟内朝一个方向猛押。桌子忠实地执行规则,赔率被他一个人推到了极端。然后他用另一半筹码,按这个被他自己推出来的赔率,反向下一注。
结算。他赢的,远超他推动赔率花掉的。他收拾筹码走人。
赌场没有被骗。 每一条规则都被严格执行了,每一注都是合法的。出问题的是规则的一个隐含假设:
它假设「进场的筹码方向」反映的是很多人的独立判断,而不是一个人的钱。
这个假设在大多数日子里成立,因为平时没有一个人有那么多筹码。它失效,只需要一个条件:有人一次性拿得出足够多的钱。
现在把赌场换成你的协议,把「一车筹码」换成一笔可以凭空借来、用完立刻归还、几乎零成本的钱。你会发现两件事:
第一,你的协议里也藏着一个「平时没人有那么多钱」的隐含假设。它可能是「没人能一次性买空这个池子」,可能是「没人能瞬间拿到多数投票权」,可能是「价格不会在一个区块内被推到那个位置」。
第二,加密世界把「一次性拿得出足够多的钱」这个前提,从「几乎不可能」变成了「一笔交易的事」。
于是这一章的思想实验可以浓缩成一句话:
把你协议里每一个「平时没人做得到」的假设找出来,然后假设有人现在就有无限的钱,重新问一遍:他做得到吗?
你来决定
你的协议要用到一个价格。你从哪里取?这个决定,很大程度上决定了上面那笔账算不算得过来。
观察结果
四种取价方式的对照,但这次对照的核心是攻破它要花多少钱:
| 攻破成本约等于 | 这笔钱能借吗 | 主要副作用 | |
|---|---|---|---|
| 即时池价 | 把池子推到目标价的资金 | 能,闪电贷 | 最便宜,最常被攻破 |
| 时间加权 | 维持偏离一段时间的成本 | 部分能 | 滞后,剧烈行情下失灵 |
| 多源中位数 | 同时操纵多数个独立源 | 看源的独立性 | 冗余可能是假的 |
| 抵押挑战 | 突破挑战机制的成本 | 一般不能 | 慢,依赖有人来挑战 |
第二列和第三列放在一起,就是这一章的第一条结论:
协议的安全性,等于攻破它的成本高于攻破它的收益。而在加密世界里,「攻破成本」里的资金那一项,往往可以借。
这句话改变了整个安全的算法。T28 里,攻击者的能力受限于他掌握的私钥和他能触发的代码路径。这一章里,攻击者的能力受限于一件事:这笔账算不算得过来。 而算账的时候,本金那一栏可以填一个几乎无限大的数——因为闪电贷让「拥有一亿」和「拥有一百块」在一笔交易内没有区别。
第二条结论,是这一章要教给你的核心动作:
判断协议安不安全,不是去找漏洞,而是站在攻击者的位置,把这笔账算一遍。
算账的公式很朴素,难的是把每一项填对:
攻击收益 - 攻击成本 = 攻击者的净利润
其中:
攻击成本 = 资金成本 + Gas 成本 + 时间成本 + 风险成本
资金成本 = 如果能用闪电贷,约等于闪电贷手续费(极小)
= 如果不能,约等于占用这笔资金的机会成本只要这个差是正的,攻击就一定会发生——不是可能,是一定,因为链上是公开的,全世界都在算同一笔账,而你只要算错一次。
建立模型
攻击成本清单:把每一项填对
这是这一章的核心工具。给任何一个攻击构想,逐项填这张表,最后看净利润的符号。
| 成本项 | 怎么估 | 容易填错的地方 |
|---|---|---|
| 资金 | 能用闪电贷就填手续费;不能就填机会成本 | 默认能借,除非你的攻击路径里有一步过不了夜 |
| Gas | 攻击交易的复杂度 乘以 当时的 Gas 价格 | 复杂攻击可能撞区块 Gas 上限,这本身是个约束 |
| 时间 | 需要跨几个区块、几个交易 | 需要「过夜」的攻击,资金成本会从手续费跳成机会成本 |
| 风险 | 攻击中途失败、被抢跑、被反向套利的概率 | 单笔原子攻击风险接近 0,多步攻击风险陡增 |
再填收益那一栏:
| 收益项 | 怎么估 |
|---|---|
| 能直接提走的资产 | 协议里可被这个路径触及的资金上限 |
| 能操纵后套利的差价 | 价格被推动的幅度 乘以 你能建立的头寸大小 |
| 能借机低价买入的抵押品 | 被错误清算或错误定价的资产量 |
最关键的判断是资金那一项能不能借。 这一个判断,往往决定了攻击成本是「几十块手续费」还是「几千万本金的机会成本」——中间差了六七个数量级。
判断标准只有一条:
如果整个攻击能在一笔交易里完成、且结束时借来的钱能原样还回去,它就能用闪电贷。
一旦能用闪电贷,你就必须假设攻击者拥有的资金,等于全链所有借贷协议此刻能借出的总和。这不是一个夸张的假设,这是一个下限。
闪电贷:把「本金」这一项抹掉
闪电贷的机制在这里必须讲清楚,因为它是这一整章的放大器。
在一笔交易里:
1. 借出一大笔钱(无需抵押)
2. 用这笔钱做任何事
3. 在同一笔交易结束前,把钱 + 手续费还回去
如果第 3 步没做到,整笔交易 revert,就像 1、2 从没发生过它对贷方是完全安全的——还不上就当无事发生。它对协议安全的意义是灾难性的:
闪电贷不创造新的攻击,它只是把所有「需要很多钱才能做」的攻击的门槛,从「你得有很多钱」降到了「这笔账得算得过来」。
所以闪电贷本身不是漏洞,被闪电贷放大的那些经济假设才是。防御的正确方向不是「禁止闪电贷用户」(做不到,也没意义),而是让你的协议不依赖任何「攻击者钱不够多」的假设。
一个直接的推论,也是一条可以写进代码的规则:
任何「在同一笔交易里,价格/份额/权重先被改变、再被使用」的地方,都要假设那个改变是攻击者用借来的钱做的。
这句话你应该觉得眼熟——它就是 T28 那条「状态更新晚于使用」的经济版本。上一章那是重入,这一章这是操纵。两者是同一个形状在不同层面的投影。
四类经济攻击,逐一算账
- 借来资金
- 制造一个失真的状态
- 按失真状态和协议交易
- 把失真状态复原
- 还钱,落袋差价
第一类:预言机操纵。 第二步制造的失真是「价格」。
收益 = 用失真价格从协议套走的资产
成本 = 闪电贷手续费 + Gas ≈ 极小
安全条件 = 把价格推到目标位置的成本 > 能套走的收益防御就是「你来决定」里那四种取价方式的组合。核心是让「推动价格的成本」这一项变得极高:深流动性、时间加权、多个真独立的源。
第二类:治理攻击。 第二步制造的失真是「投票权」。
收益 = 通过一个恶意提案能转走的资金
成本 = 借到多数投票权的成本 + 等过时间锁的成本
安全条件 = 借票 + 等待的总成本 > 提案能转走的资金这里的关键变量是投票权能不能被借、以及有没有时间锁。如果治理代币可以闪电贷、投票是即时生效的,那么攻击者可以在一笔交易里借票、投票、执行、还票——这是最危险的组合。防御的两把锁:投票权的快照要早于提案(借来的票投不了),关键提案执行前必须过时间锁(给社区反应时间)。 这正是 第 24 章 那句「一次投票能不能把钱投走」的技术答案。
第三类:流动性攻击。 第二步制造的失真是「池子的构成」。
收益 = 从被扭曲的池子里套走的价值,或让别人的头寸被错误清算后低价接手
成本 = 短暂占用大额资金 + Gas
安全条件 = 恢复池子所需的反向成本 > 套走的价值第一存款人问题(T28 讲过)是它的一个特例。更一般的形态是:在低流动性的池子里,用大额资金临时改变价格或权重,触发协议里某个依赖池子状态的逻辑。
第四类:激励攻击。 第二步制造的失真是「协议以为的贡献」。
收益 = 骗取的补贴、奖励、空投
成本 = 伪造贡献的成本(往往极低)
安全条件 = 伪造一份贡献的成本 > 一份贡献能领的奖励这一类最不像「攻击」,却最普遍。刷交易量领手续费返还、自己和自己交易刷排名、造一万个地址领空投(女巫攻击)。它的账算起来最简单:只要「装成一个有效用户」的成本低于「一个有效用户能领到的钱」,就一定有人批量地装。 T27 里代付方被薅干,就是这一类。
一次完整的算账演示
把开头那个场景,用这套工具从头算一遍。假设你的协议用某个池子的即时价来给一种抵押品定价。
攻击构想:
1. 闪电贷借入大量基础资产
2. 在那个池子里把抵押品价格推高
3. 用被推高的抵押品,在你的协议里借出远超其真实价值的资产
4. 把池子价格推回(部分资金自然回收)
5. 还闪电贷,带走多借出来的部分
逐项算成本:
资金 = 能在一笔交易里完成吗?能。→ 闪电贷手续费,极小
Gas = 一笔复杂交易,中等,但远小于收益
时间 = 单笔原子交易,无过夜成本
风险 = 原子交易,要么成功要么整体回滚,风险≈0
算收益:
= (被推高的抵押价 - 真实抵押价)× 能借出的资产量
= 一个正数,且通常远大于上面的成本
结论:净利润为正 → 这个攻击一定会发生现在改一个防御变量,重新算:把即时价换成一个足够长的时间加权价。
第 2 步变成:要把时间加权价推高,攻击者必须把价格维持在高位足够久,
跨越多个区块。
资金 = 不能再用闪电贷了(要过夜)→ 变成占用大额本金的机会成本
时间 = 多个区块,期间价格偏离会被套利者反向吃掉
风险 = 从≈0 跳升为「维持期间被套利、被抢跑」的真实风险
结论:成本从「手续费」跳成「大额本金 + 时间 + 风险」,
往往超过收益 → 攻击不再划算看清楚发生了什么:你没有修任何漏洞,你只是把攻击成本里的一项从「可借」变成了「不可借」。 这就是协议安全的核心手法——不是堵路,是把路修贵。
系统视角:你的协议依赖谁,就继承了谁的风险
最后一层。你的协议很少独立存在,它站在别的协议肩膀上:用某个池子取价、接受某座桥的资产、把资金存进另一个协议生息。
你继承了它们每一个的经济假设。 而经济攻击最喜欢的就是这些接缝:
- 你接受的抵押品,是某座桥的包装资产吗?那么这个抵押品的真实价值,取决于那座桥没出事(T26)。
- 你的收益来自把用户资金存进另一个协议吗?那么另一个协议的经济假设,现在也是你的。
- 你的清算依赖清算人有利可图吗?行情剧烈时清算人可能集体不划算(T22 讲过清算速度是借贷协议的安全边界)。
所以协议安全的最后一个动作是画一张依赖图,对图上每一个外部依赖问同一句话:
如果它的经济假设被攻破,我这边会连带发生什么?我能不能被隔离?
这张图,就是下一章审计里「威胁模型」的骨架。
它叫什么
不利用任何代码漏洞,只利用协议的经济假设来牟利的攻击。
它的判据不是「代码有没有 bug」,而是「这笔账算不算得过来」。防御它的方式也不是修代码,而是把攻击成本抬到高于攻击收益。
在一笔交易内借出、使用、归还的无抵押贷款,还不上则整笔回滚。
它本身不是攻击,而是放大器:它把所有「需要很多钱」的攻击的门槛,从「你得有钱」降到「这笔账得算得过来」。评估任何攻击,第一个问题永远是——资金那一项能不能借。
用资金临时把协议依赖的价格推到失真位置,再按失真价格与协议交易。
攻破成本约等于把价格源推到目标位置所需的资金。当价格源是一个能被闪电贷推动的即时池价时,这个成本几乎为零。防御是把「推动价格的成本」抬高:深流动性、时间加权、多个真独立的源。
取得足够的投票权,通过一个把资金转给自己的提案。
两个决定性变量:投票权能不能被借、执行前有没有时间锁。快照早于提案能挡住借来的票,时间锁能给社区反应和退出的时间。缺任何一个,一次投票就可能把金库投走。
一个人伪装成大量独立用户,骗取按「人头」或「独立贡献」发放的激励。
它的账最简单:只要伪装成一个有效用户的成本低于一个有效用户能领的奖励,就一定有人批量伪装。它也是「多源独立」「多守护人」这类冗余设计的天敌——冗余只在真独立时成立。
设计协议的奖惩,使得诚实行为对每个参与者都是最优选择。
它的检验方式和安全一样,是算账:站在每一类参与者的位置,问「有没有一种不诚实的行为,收益高于诚实行为」。有,这个激励就有漏洞——哪怕没有一行代码写错。
动手
只针对你自己的协议、在你自己的 Fork 环境里做。 这个 Lab 的产出是一份算账报告,不是一次真实攻击。任何针对真实部署合约的尝试都不在这个练习的范围内,也不被允许。
产出是一张表:你的协议里每一个「平时没人做得到」的假设,各自的攻击成本与收益。
列出你协议的全部经济假设。
逐个函数读过去,把每一个隐含的「平时不会发生」写下来。常见的有:
- 没人能一次性买空/买爆这个池子
- 价格不会在一个区块内被推到某个位置
- 没人能瞬间拿到多数投票权
- 首次存款不会是 1 wei
- 清算永远对清算人有利可图
- 没人会为了领奖励而批量造号每写一条,就在后面补一句「如果有人现在就有无限的钱呢」。 这一步是整个 Lab 的地基,列不全,后面算得再准也没用。
对每一条假设,先判断资金能不能借。
用那条判据:整个攻击能在一笔交易里完成、结束时钱能原样还回去吗?
能——资金成本填「闪电贷手续费」,几乎可以忽略。 不能——找出是哪一步过不了夜,资金成本填「占用这笔本金的机会成本」。
这一个判断是整张表里最重要的判断,因为它决定了成本量级。
填满成本的另外三项。
Gas :估算攻击交易的复杂度,乘以一个偏高的 Gas 价格
时间 :需要几个区块、几笔交易
风险 :单笔原子攻击≈0;多步攻击要估中途失败、被抢跑、被反向套利的概率在 Fork 环境里实际发一笔试试,把 Gas 从「估算」变成「实测」——这一步能纠正很多想当然。
填满收益。
对每一条假设,算出「假设被打破后能拿走多少」:能直接提走的资金、能套利的差价、能低价接手的抵押品。
收益要算上限,也就是「这条路最多能偷走多少」。因为攻击者会把头寸开到你允许的最大。
算净利润,排序。
净利润 = 收益 - 成本把所有假设按净利润从高到低排。排在最上面那一条,就是你的协议最可能出事的地方。 从这一阶段开始,你的每一份作业都要附上这一条——这就是它。
对净利润为正的每一条,在 Fork 上实际验证一次。
只在你自己的 Fork、只针对你自己的合约。 把攻击构想写成一个测试,看它在 Fork 上能不能真的跑出正收益。
这一步的价值和 T28 一样:一个能在 Fork 上跑出利润的测试,是唯一能证明这条攻击路径真实存在的东西。 算得再漂亮,跑不出来就说明你的账里有一项填错了——去找它。
对排在最前面的那条,改一个防御变量,重新算。
参考本章那次演示:把即时价换成时间加权、给治理加时间锁和快照、给份额加虚拟偏移、给激励加成本门槛。
改完之后重新算净利润,目标是把它从正变负。 然后在 Fork 上再跑一次那个攻击测试,确认它现在跑不出利润了。
把「改之前」和「改之后」的两张账并排放,就是这个 Lab 的成果——它证明了你不是靠堵漏洞,而是靠把路修贵。
AI Lab
这个 Lab 让 AI 做它擅长的事——枚举攻击构想,而把它不擅长的事——精确算账和排序——留给你。分三步。
第一步:
这是我的协议(给出合约、它依赖的价格源、治理方式、激励规则、
以及它依赖的外部协议)。
扮演一个只用资金、不用任何代码漏洞的攻击者。
列出至少 6 条攻击构想,每一条说明:
- 你借什么、做什么、按什么顺序
- 你利用的是我哪一个经济假设
第二步:
对每一条攻击,填这张成本表:
资金(能否闪电贷)、Gas、时间、风险;再填收益(按上限算)。
资金那一项必须明确判断:整个攻击能不能在一笔交易里完成并还款。
第三步:
对每一条,给出防御。
但区分清楚:你的防御是「堵住这一条具体路径」,
还是「消灭它利用的那个经济假设」。
如果是前者,说明攻击者换个什么变量就能绕过。第一步模型通常表现很好,因为枚举攻击构想本质上是一个发散任务,正是它的强项。它能列出你没想到的组合,这是它在这个 Lab 里最大的价值。
第二步是它最容易出错的地方,而且错得很隐蔽:它经常把资金那一项判断错。 最常见的两种错误是——把一个需要跨区块维持的攻击当成能用闪电贷(低估成本几个数量级),或者反过来,把一个明明能原子完成的攻击当成需要大额本金(高估成本,让你误以为安全)。这一栏你必须自己复核每一条,判据只有一个:能不能在一笔交易里完成并还款。
第三步是分水岭。模型很容易给出「堵这条具体路径」式的防御——比如「在这个函数里加一个价格偏差检查」。这类防御的问题是它只堵了这一条路,攻击者换个入口就绕过去了。真正的防御是消灭那个经济假设本身(把即时价换成时间加权,让操纵价格这件事整体变贵)。让它自己说明「换个什么变量就能绕过」,能逼出这个区别。
最后必须你自己做两件事:按净利润排序(模型给的排序往往凭感觉,不是凭算出来的数),以及对排第一的那条算出防御前后的两张账。这两件事是这一章要你掌握的核心能力,不能外包给模型——它可以帮你想到攻击,但「哪条最危险、防御有没有真的把账算负」必须由你确认。 这和 T28 那条底线一致:AI 生成线索,你出结论。
AI 说完之后,你必须自己验证
- 它给的每一条攻击,资金那一项判断对了吗:能不能用闪电贷,是这一栏对错的关键
- 它有没有把「需要过夜的攻击」误判成「能用闪电贷」,这会让成本估算差好几个数量级
- 它算的收益是不是上限:攻击者会把头寸开到最大,按「典型情况」估收益会低估风险
- 它提的每一条攻击,你能不能在自己的 Fork 上跑出正收益;跑不出来的,要么它算错了,要么你的协议本来就防住了
- 它给的防御是「堵这条具体的路」还是「消灭那个经济假设」:只堵具体路径的防御,换个变量就会被绕过
- 它有没有考虑你协议的外部依赖:继承自其他协议的经济假设,是它最容易漏的一类
- 最后你自己按净利润排序,并对排第一的那条,算出防御前后的两张账
真实案例
一个借贷协议用某个链上池子的即时价给抵押品定价。有人用闪电贷借入大量资金,在那个池子里把抵押品价格推高,用被推高的抵押品借出远超其真实价值的资产,再把价格推回、还款,一笔交易完成。
复盘时,没有任何一行代码的行为异常,每一笔交易都合法。问题在那个隐含假设:「这个池子的价格反映真实市场」,而它可以被一笔借来的钱推动。
这就是开头那个场景,也是这一章存在的理由。防御不是修代码,是换取价方式——把「推动价格的成本」抬到高于「能套走的收益」。
一个协议的治理代币可以借到,且提案通过后立即执行,没有时间锁。有人借入大量代币,投票通过一个把金库资产转给自己的提案,执行,还币。
整个过程完全符合治理规则。投票的人确实持有(借来的)多数票,提案确实按多数通过。
两把锁能挡住它:投票权快照早于提案(借来的票在快照时还不在攻击者手里),以及执行前的时间锁(给社区看到并反应的时间)。第 24 章「一次投票能不能把钱投走」问的就是这件事。
T28 从代码侧讲过这个问题,从经济侧看,它是一次流动性攻击:利用「首存可以极小」和「总资产直接读余额」这两个假设,用一次直接转账把份额价格抬到让后来者的份额算成 0。
它的成本极低(一笔小额存款加一笔转账),收益是后来者存入的全部。这正是「伪造一个状态、按它交易、落袋差价」的五步结构。
防御是那三样一起上:内部记账、虚拟份额、死份额,外加「算出 0 份额就 revert」的兜底。
一个协议为了拉新,按交易量发返佣。有人写脚本自己和自己交易,制造大量交易量,把返佣池刷空——他付出的只有 Gas 和手续费,而这些又通过返佣拿了回来。
没有任何漏洞被利用,协议做的就是它承诺的事:按交易量发钱。问题是「交易量」这个指标可以被零成本伪造。
账很简单:当「刷一笔量的净成本」低于「一笔量能领的返佣」,就一定有人无限刷。 激励设计的检验方式就是算这笔账,而不是看代码。
改一个变量
攻击成本里的资金那一项,从「闪电贷手续费」跳成「维持价格偏离一段时间的大额本金 + 机会成本」,风险那一项从接近 0 跳成「维持期间被套利、被抢跑」的真实风险。
多数瞬时操纵因此变得不划算。但它没有消灭攻击:如果这个资产本身流动性极低、平时价格就容易被少量资金带动,时间加权也会被慢慢带偏。而且它引入了滞后,剧烈行情下可能该清算的没清算。
结论:它把一类攻击变贵了,同时引入了另一类风险。没有免费的防御,只有把风险挪到你更能承受的地方。
借票攻击这条路基本被封死:攻击者没法临时凑出投票权,快照又让他即使买入也来不及在提案时生效。
代价是灵活性和一些边缘情况:合法的大额持有者转移、委托、流动性质押场景都要重新考虑。而且它防不了长期潜伏——一个愿意长期持有多数代币的攻击者,快照挡不住他,这时你依赖的就只剩时间锁和社区的警觉了。
所以治理安全从来不是单点,是「借票 + 快照 + 时间锁 + 社区监督」的组合,每一层抬高一部分成本。
你自己的代码一个字没改,但你的攻击成本表可能整体下移了一档——因为攻击者能借到的钱变多了,一些原本「借不到那么多所以不划算」的攻击,现在划算了。
这是这一章最反直觉、也最重要的一点:你的安全性会因为别人的参数变化而下降,而你收不到任何通知。
所以攻击成本不是算一次就完的。它依赖的每一个外部变量——池子深度、可借额度、Gas 价格——都在动。这份账要定期重算,尤其是在你依赖的协议发生变化时。 这也是下一章要把它变成持续监控的原因。
带走的问题
谁承担风险?经济攻击里,损失往往由协议的流动性提供者和存款人分摊——他们没做错任何事,只是把钱放进了一个「攻击账算得过来」的系统。所以协议方的责任,是把每一条攻击路径的账都算到负,而不是等审计报告说「没有代码漏洞」就放心。没有代码漏洞不等于没有经济漏洞。
激励是什么?这一章把这个问题变成了一个安全问题:你设计的每一个激励,都要站在攻击者的角度算一遍——有没有一种不诚实的行为,收益高于诚实行为。 有,这个激励就是一个攻击面。刷量、女巫、自我交易,全都是激励算错账的结果。
如果 Token 价格归零,产品还能运行吗?这一章给它加了一个更尖锐的版本:如果你依赖的某个价格、某笔流动性、某个外部协议的经济假设被攻破,你的协议会连带发生什么? 把这个问题对你依赖图上的每一个节点问一遍,答案就是你的系统性风险清单——也是下一章威胁模型的起点。
本章自测
核心判据是一笔账:攻击收益减去攻击成本,是不是正数。 是正数,攻击就一定会发生——不是可能,是一定,因为链上公开,全世界都在算同一笔账,你只要算错一次。
成本里最关键的一项是资金,而资金往往可以借。所以真正的问题不是「攻击者有没有钱」,而是「这个攻击能不能用借来的钱完成」。
防御的方向不是修代码,是把攻击成本抬到高于收益——通常是通过让某一项成本从「可借」变成「不可借」。
因为闪电贷本身对贷方完全安全(还不上就整笔回滚),它没有创造任何新的攻击方式。
它做的是把所有「需要很多钱才能做」的攻击的门槛,从「你得拥有很多钱」降到「这笔账得算得过来」。一旦一个攻击能在一笔交易里完成并还款,你就必须假设攻击者的可用资金等于全链此刻能借出的总和。
所以评估任何攻击,第一个问题永远是:资金那一项能不能借? 这一个判断决定了成本是几十块还是几千万。
即时池价:攻击成本≈把池子推到目标价所需的资金,而这笔钱能闪电贷,所以实际成本≈手续费。最便宜,最常被攻破。
时间加权:攻击者必须把价格维持偏离一段时间,跨越多个区块,用不了闪电贷了,成本变成大额本金的机会成本加维持期间的套利风险。高一大截,但对低流动性资产仍可能被慢慢带偏。
多源中位数:成本变成「同时操纵多数个源」——前提是这些源真的独立。如果几个源共享同一个底层池,冗余是假的,一操纵就中多数。
三者常组合使用,核心都是把「推动价格的成本」这一项抬高。
投票权快照早于提案:防住借来的票。攻击者在提案时才借入代币,但快照时间点在提案之前,那时票还不在他手里,所以借来的票投不了。
执行前的时间锁:防住即时生效。即使攻击者真的凑够了票、提案通过了,执行也要等一段时间,这段时间里社区能看到这个恶意提案并做出反应——撤资、否决、或紧急干预。
缺任何一把:只有快照没时间锁,长期潜伏的大户照样能瞬间执行;只有时间锁没快照,借票攻击能过第一关。两把一起,才把「一次投票投走金库」的成本抬到不划算。
没有标准答案,检查这几件事:
- 你有没有先把协议的经济假设列全?每一条后面有没有补「如果有人现在就有无限的钱」?
- 每一条的资金那一项,你判断能不能闪电贷了吗?判据是「能否一笔交易内完成并还款」。
- 收益是按上限算的吗?攻击者会把头寸开到最大,按典型情况估会低估。
- 你按净利润排序了吗?排第一的那条,就是你要附在作业里的「最可能出事的那一项」。
- 对排第一的那条,你在 Fork 上跑出正收益了吗?跑不出来说明账里有一项填错了。
- 你的防御是消灭了那个经济假设,还是只堵了一条具体路径?后者换个变量就被绕过。
- 你有没有把外部依赖算进去?你继承了它们每一个的经济假设,而它们的参数会变。
第 6 条和第 7 条是区分「算过账」和「真的懂了」的地方:真正的防御是把路修贵,不是把某一个门堵上;而且这笔账要随着你依赖的世界变化而重算。
一句话带走
经济攻击不需要漏洞,只需要这笔账算得过来。