BNB Chain上一个Uniswap v4动态费率池通过三层环境判定机制(调用者地址、gas消耗、区块指纹)实现报价0%与执行高费率(如12.8%)的系统性分叉,在29小时内完成2.1万笔交易,收取13.2万美元手续费后注销;该池利用滑点保护宽松、白名单独占流动性等设计规避检测,暴露了DeFi聚合器模拟与执行不一致及用户风控设置过松的安全隐患。
原文标题:《报价 0%,执行 12.8%:Uniswap v4 "毒池"动态费率陷阱复盘》
原文作者:王子豪
原文来源:https://mp.weixin.qq.com/s/Z8rVX1HVyPLHzQXBxW9odQ
转载:火星财经
2026 年 8 月 22 日到 23 日,BNB Chain 上一个 Uniswap v4 USDC/WBNB 动态费率池在聚合器报价里显示 0% 费率,用户实际成交却被收取 12.8% 的 LP fee。约 29 个小时里,这个池子成交 21,086 笔、累计手续费 131,888 美元,随后注销。所有受害交易都成功了:合约未被盗,输出高于用户设定的下限,没有任何异常信号——差额安静地变成了手续费。
本文以其中一笔样本交易为入口:
(0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0:用户投入 5,926.90 USDT,到手比预期少 757.43 USDT),拆解这个池子如何做到「报价 0%、执行 12.8%」:费率函数的三层判定、被架空的滑点保护、把其他 LP 挡在门外的白名单。全部结论基于链上数据与本地受控验证。
用户在聚合器前端发起兑换后,整条链路是这样走的:

这条链路上有两个背景事实,后面每一层判定都建立在这两点之上。
先看 v4 这一侧:它允许 Hook 逐笔覆盖费率。PoolKey.fee = 0x800000 声明动态费率池;Hook 在 beforeSwap 的第三个返回值携带 0x400000 | fee。v4-core 里接收这个返回值的代码(Pool.sol:303-305):

isOverride() 检查覆盖标志,removeOverrideFlagAndValidate() 剥掉 0x400000 并校验不超过 MAX_LP_FEE = 1,000,000(100%),然后按该费率执行本笔 swap 并写入 Swap 事件。也就是说,Hook 返回什么,用户就付什么——只要输出仍高于 amountOutMin。这是正式功能,按波动率、库存调费率都是正当用途。
再看这个 Hook 本身:它只声明了两项权限。v4 把权限编码在合约地址低 14 位(Hooks.sol:29-47),部署时校验、事后不可改。v4-core 侧的标志位定义与地址解码对读:

链上实测 getHookPermissions() 返回与位图一致:仅这两位为真。
Hook 无源码。本文从字节码反编译出费率函数全文——以下是对这份已在链上运行过的逻辑的行为拆解:

接下来逐层看。前两层回答的都是「这是不是一次模拟」,第三层决定真实交易付多少。
费率函数被调用后,先比对 tx.origin:等于三个常量地址中的任何一个,就直接返回回退档。回退档当前配置为 0%。
三个地址不是随便选的。eth_call 未指定 from 时,交易的 origin 落在零地址上——聚合器批量询价通常不指定。本地 fork 中直接验证:

第一层只认这三个地址。下一层看指定了普通地址之后,Hook 还能怎么区分。
把 tx.origin 换成普通地址重试,返回仍是 0%——第一层被绕过了,费率却没变。这说明第一层不是唯一的判定,定位到第二层:gasleft() * 10100 / 10000 >= gate。
聚合器报价习惯给很高的 gas,防止候选路线在模拟中因 gas 不足失败;真实交易进 Hook 前,gas 已被 Router、授权、前置跳消耗。同一份代码,三种 gas:

分界线实测位于 16.85M–16.9M gas 之间。30M 与 2.59M 的差异,就是"报价"与"执行"的差异。
前两层都过的真实交易进入第三层:把区块环境指纹哈希后对 10,000 取模,按结果所在的区间返回对应费率档。当前配置三档 8% / 10% / 10%,落空则回退 0%。
这层有两个值得注意的设计。指纹里混入了 calldata 的三个字段——聚合器报价的 calldata 和真实路由的 calldata 本来就不同,指纹自然也不同。另外,逐笔伪随机让费率带分布,单看几笔很难察觉规律,简单的规则枚举拦不住。
全部 21,086 笔链上事件中出现 19 档历史费率(6.8%–28%),说明费率档一直由管理员函数按需重配;样本交易成交时生效的是 12.8%。

至此可以给「毒池」下一个定义——同时满足三个条件的 v4 池:PoolKey.fee 携带动态费率标志;费率决策读取执行环境信号(gas、origin、区块环境),而不读公开市场状态;报价模拟与链上执行的费率出现系统性分叉,受益方为毒池部署者。分界就在费率读什么:对任何调用者返回同一费率的,是可编程市场设计;按「是谁在问价」返回不同费率的,是对路由系统的欺骗。
到这里,费率分叉的机制已经完整。但费率收走之后,交易为什么还能成功,是下一个问题。
先看这笔交易的完整路径与金额:

该跳的 Swap 事件把生效费率记在 fee 字段里:128000。v4 的费率单位以 1,000,000 为 100%,128000 就是 12.8%。这笔交易的损失可以从两个角度衡量:从池子看,费率对 WBNB 输入收取;从整条路线看,稳定币进出差额 757.43 USDT,折损 12.7794%,其中还含前置跳费用与脱锚偏差。两个数字只差 0.02 个百分点,说明折损的主要来源就是这个池子的 LP fee。
amountOutMin 只校验最终输出下限,不看中间每一跳收了多少费:

12.8% 的费率整个落在 20.32% 的缓冲之内,输出仍高于底线——交易成功,不回退。主流稳定币路线的常规默认是 0.1%–1%,这条路线给了 20 倍以上的空间。用户以为宽缓冲是「稳一点」,实际上是把路径上每一个合约都交给了对端自律。把这笔账画成图:

白名单逻辑在 beforeAddLiquidity 里,反编译结果如下:

加流动性要过三道门:先是任何 v4 Hook 都有的 PoolManager 调用检查,然后必须经由官方 PositionManager,最后核对头寸持有人的白名单。不在名单上的地址在这里被合约回退,其他 LP 进不来,手续费收入不会被稀释,全部归部署者。名单本身由一个管理员函数(选择器 0xc4452e52)按地址维护。
把 21,086 笔交易放在一起看,分布呈现出清晰的模式:0% 档 6,946 笔、成交 12,947,751 美元,笔均 1,864 美元;收费档 14,140 笔、成交 1,120,106 美元,笔均 79 美元。大额成交集中在 0% 档,收费集中在小额交易上。大额 0% 最合理的解释是部署者自己用高 gas 自成交:高额 gas 与聚合器报价命中的是同一个判定,自成交的手续费成本接近零。刷出来的成交量把池子推上行情站排行(快照显示 24h 成交约 1,082 万美元、16,671 笔),在聚合器眼里,这就是一个深度好、费率低的池子。
判定三提到过 19 档历史费率,它们来自反编译出的费率配置函数(选择器 0x4d909a45,未收录公开签名):

四个费率档与阈值整体打包,写入 keccak(poolId, 2) 这个存储槽。链上实测该槽读数为 0x0138800186a00186a0,与函数的打包格式逐段对应。费率档在这里是随时可以调整的参数,并非部署时定死。
这个池子 08-22 06:57 建池,07:13 出现第一笔 swap,21:39 是本文的样本交易;08-23 12:25 最后一笔成交,之后注销。整个活跃期约 29 小时,总成交 14,067,857 美元,手续费收入 131,888 美元(未扣部署者成本)。复核时注册标志已经回到启用——池子还在,随时可以再次启用。
把整条攻击链连起来:

整条链的因果都在上面这张图里,三个条件缺一不可:模拟命中前两层、真实交易落第三层、缓冲大于费率。任何一个不成立,这套手法都会失效。
第一处在聚合器的报价方式。根因是模拟和执行走的不是同一条路:报价用理想化的池级查询,配上零地址和高 gas;真实交易带着真实身份和已被消耗的 gas。Hook 恰好能读出这些差异,费率于是分叉。对治的办法是让模拟贴近执行——用实际要发的 Router calldata、真实的 from/to、接近链上交易的 gas 做模拟;成交之后解码 Swap 事件核对 fee,与报价对不上就降权或下线。
第二处在用户的滑点设置。根因是 min_out 给得太松:20.32% 的缓冲把 12.8% 的费率整个吞掉,交易照常成功。主流稳定币路线 0.1%–1% 足够日常使用,发起交易前看一眼这个数字;钱包和前端把默认值收紧,能替用户挡掉大部分风险。
第三处在路由准入。根因是任何动态费率池都能直接参与报价竞争:没有开源、没有审计记录的 Hook,一样能靠刷出来的成交量进入推荐路线。对没有可信档案的动态费率池,默认不进路由或显著降权,是最稳妥的处理。
·样本交易:
https://bscscan.com/tx/0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0(区块 117497524)
·池初始化交易:
https://bscscan.com/tx/0x8fa72ef72d77b61f715dc9ce90548249ab045c6d78716078cd5ab425bbe69a05
·PoolManager:
0x28e2ea090877bf75740558f6bfb36a5ffee9e9df
·Pool ID:
0x36e5540e9dedc02229fe8a82aa5b10c0bf07d1fa74e4f2ffe0efd00fa1a36aea
·Hook:
0xd111b3ddd92e627f1864520c770e913ec04e0880(无源码,伪代码为本文独立反编译;管理员 0x08b03e1a5444d469f4dc954e74d3f662c94a6b13)
·成交与手续费:该池全部 21,086 笔 Swap 事件逐笔累计(eth_getLogs),WBNB 按同笔执行价折算;收入为毛值,未扣部署者成本