看到很多朋友还是不懂,再加上聚合器是我目前的工作,再详细的解释一下。在我看来,这更接近利用系统盲区进行的恶意攻击,危害不亚于常见的 MEV 夹子,而且更加隐蔽。
简单描述这个攻击方法:交易可以正常成功,但你最后收到的钱变少了。
用户发起交易后,聚合器会在多个协议和流动性池之间寻找路线。交易经过一连串操作,最后有一步进入一个带恶意 Hook 的 Uniswap v4 池。这个池在报价时表现得很便宜,等真实交易执行,再把动态 LP fee 调高到 12.8%,直接从输入资产中收走一大笔费用。
---
用户发起交易
↓
聚合器寻找并模拟最优路线
↓
路线进入 Uniswap v4 动态费池
↓
报价模拟:低费率 / 甚至 0%
真实执行:LP fee 突然变成 12.8%
↓
交易仍然成功,但用户收到的钱明显减少
---
这里有三个疑问需要先讲清楚。
1. 聚合器为什么没有在模拟报价时发现?
聚合器一般会先用 eth_call 模拟交易,比较不同路线能拿到多少输出。为了避免模拟因为 gas 不够而失败,报价系统经常会给 eth_call 一个很高的 gas ceiling。
这个恶意 Hook 恰好会读取执行时的 gas 环境。高 gas 的模拟调用会落入低费率分支,看起来像一个非常有竞争力的池;真实交易的 gas limit 低得多,于是会落入另一个分支,被收取更高的 LP fee。
对完全相同的 beforeSwap calldata,只修改 call gas,可以复现出不同返回值:
---
gas = 1,000,000 → 10%
gas ≈ 16,000,000 → 0.5%
gas = 30,000,000 → 0%
---
这些数值是调查时合约的当前配置。历史真实交易中的 12.8%,由 PoolManager 的 Swap event 直接证明。攻击的核心是让 quote 和 execution 看到不同规则。
2. Uniswap v4 Hook 是什么?
Uniswap v4 允许每个池绑定一个 Hook。Hook 可以在 swap 前后运行自定义逻辑。正常情况下,它可以根据市场波动、流动性深度或其他条件动态调整费用。
Dynamic-fee pool 还允许 beforeSwap Hook 为每笔交易单独覆盖 LP fee。这个能力本身是 Uniswap v4 的正常设计。问题在于恶意 Hook 可以把“当前调用看起来像报价模拟还是真实执行”也放进费率逻辑里。
PoolKey 里的 0x800000 只是 dynamic-fee flag,表示这个池的 LP fee 可以被动态覆盖,并不代表 800% 手续费。真实费率要看每笔 Swap event 里的 fee 字段。本案出现的 fee = 128000,换算后就是 12.8%。
3. amountOutMin 为什么没有拦住?
amountOutMin 是用户愿意接受的最终输出底线。实际输出低于这个数,交易回滚;高于这个数,交易就会成功。
它只检查整条路线最后拿到了多少,不会逐个检查中间池子的 fee 是否和 quote 一致,也不知道某个 Hook 是否临时改变了规则。
如果 slippage 设置得很宽,或者路线里其他步骤带来了一些正向 price improvement,恶意池抽走一部分价值之后,最终输出仍然可能高于 amountOutMin。链上检查会认为交易合格,用户却拿少了。
具体案例:Relay 路线在哪里出了问题
代表交易:
bscscan.com/tx/0x7615dec2ce8…
这笔交易走的是 Relay 顶层流程,source-side swap 由 0x BnbSettler 执行。完整调用层级大致是:
---
ERC-4337 EntryPoint
↓
Relay v3 Router / ApprovalProxy / Depository
↓
0x BnbSettler
├─ FOMO action:USDT → WBNB
└─ Uniswap v4 action:WBNB → USDC
↓
目标动态费池 + 恶意 Hook
---
关键点是 FOMO 只完成 USDT → WBNB 这一步。随后 0x BnbSettler 才单独进入有问题的 Uniswap v4 池,把 WBNB 换成 USDC。
这一步之前大约有 5,926.9 USDT,FOMO 输出约 8.536 WBNB。进入目标 v4 pool 后,最后只得到约 5,169.5 USDC。稳定币价值差约 757.4 美元,约为 12.779%,与 Swap event 记录的 12.8% LP fee 基本吻合。
这笔交易的正确 minAmountOut 是约 4,742.5 USDC,相比费前路线价值低约 20%。实际输出 5,169.5 USDC,虽然被抽走约 12.8%,仍然比 minimum 高约 9%,所以交易正常成功。
amountOutMin 没有失效,它严格执行了用户或上层系统写入的最终底线。问题是这个底线留出的空间足以容纳中间的异常高费。仅凭链上数据,我们还不能确定这约 20% 是用户手动设置,还是 Relay、0x 或上层集成针对 meme、tax、低流动性和多跳路线自动加入的 buffer。
反编译后看到的核心逻辑
这个 Hook 没有 verified source,也没有 Sourcify metadata。调查中取得了约 7,191-byte runtime bytecode,通过 Heimdall 反编译,并配合 live eth_call 验证。下面是按恢复出的控制流整理的高层伪代码,不是项目方公开源码:
---
function beforeSwap(...) returns (..., uint24 lpFeeOverride) {
require(msg.sender == PoolManager);
poolSwapCount[poolId]++;
Config memory config = poolConfig[poolId];
uint24 fee = selectFeeTier(
gasleft(),
block.gaslimit,
config.thresholds,
config.feeTiers
);
return (beforeSwap.selector, 0, OVERRIDE_FEE_FLAG | fee);
}
---
可以确认的链上行为有两个:同一输入在不同 gas envelope 下会返回不同 fee override;历史 Swap event 确实记录了 12.8%。函数名、变量名和部分控制流来自逆向恢复,因此不应该把这段伪代码当成 verified source。
为什么是 12.8%?
128000 不是 Uniswap v4 的特殊常量。v4 的 LP fee 单位是 1 / 1,000,000,所以 128000 / 1,000,000 = 12.8%。
目前没有 verified source 或攻击者说明解释为什么精确选择 128000。下面只能从攻击经济学推断。
报价阶段已经伪装成低费率,真实 fee 从 5% 提高到 12.8%,不一定会降低聚合器选中这个池的概率。真正限制攻击者的是:提高费用后,最终输出是否仍高于 amountOutMin,交易是否会大量回滚,以及异常多久会被监控发现并拉黑。
在 split route 中,这种做法会更隐蔽。底层池即使收取 12.8%,只要进入它的分片很小,对整条路线的影响就会被稀释,也可能被 slippage buffer 或其他分片的正向价格改善覆盖。
所以 12.8% 更像是在单笔抽取、交易成功率和暴露风险之间选择的高收益档位,而不是技术上必须使用的数字。这一点属于合理推断,目前没有直接证据证明攻击者的具体动机。
怎么防?
对普通用户来说,最直接的保护仍然是 amountOutMin。确认钱包或聚合器给出的 minimum 是否能接受,避免为了提高成交率设置过宽的 slippage。它是损失底线,但无法保证中间路线诚实。
对聚合器和 swap 服务来说,只做一次高 gas eth_call 已经不够。至少应该:
对同一 calldata 分别使用高 gas 和接近真实交易 gas limit 的环境模拟。fee 或 output 明显变化时直接拒绝。
识别 dynamic-fee pool 和 unknown/unverified Hook,对实际 LP fee 设置可接受上限。
交易完成后核对 quote 与 receipt 中的 Pool ID、Hook、LP fee 和实际输出。即使交易成功,低费报价、高费执行也应该立即告警。
这件事说明,可编程 AMM 带来了更多定价策略,也打开了识别报价环境、针对路由器行为差异化收费的新攻击面。聚合器以后既要检查最终输出,也要确认报价时看到的规则和真实执行时完全一致。正在增强中。