Uniswap v4 Poison Pool Trap: $131,888 in Fees

BlockbeatsBlockbeats

Original title: "Quoted 0%, Executed 12.8%: Uniswap v4 'Poison Pool' Dynamic Fee Trap Review"

Original author: Wang Zihao, BitsLab

 

From August 22 to 23, 2026, a Uniswap v4 USDC/WBNB dynamic fee pool on BNB Chain displayed a 0% fee in aggregator quotes, but users were actually charged a 12.8% LP fee on executed trades. Over approximately 29 hours, this pool processed 21,086 transactions and accumulated $131,888 in fees before being deregistered. All victim transactions succeeded: the contract was not exploited, outputs exceeded user-specified minimums, and there were no abnormal signals—the difference quietly became fees.

 

This article uses one sample transaction as an entry point:

(0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0: user input 5,926.90 USDT, received 757.43 USDT less than expected), analyzing how this pool achieved "quoted 0%, executed 12.8%": the three-layer fee function determination, the bypassed slippage protection, and the whitelist that kept other LPs out. All conclusions are based on on-chain data and local controlled verification.

 

01 The Path from Quote to Execution

After a user initiates a swap on an aggregator frontend, the entire path is as follows:

 

 

Two background facts underpin the subsequent layers of determination.

First, look at the v4 side: it allows Hooks to override fees on a per-swap basis. PoolKey.fee = 0x800000 declares a dynamic fee pool; the Hook returns 0x400000 | fee as the third return value of beforeSwap. The code in v4-core that receives this return value (Pool.sol:303-305):

isOverride() checks the override flag, removeOverrideFlagAndValidate() strips 0x400000 and validates that it does not exceed MAX_LP_FEE = 1,000,000 (100%), then executes the current swap at that fee rate and writes it to the Swap event. In other words, the user pays whatever fee the Hook returns, provided the output stays above amountOutMin. This is an official feature; adjusting fees based on volatility or inventory are legitimate uses.

Now look at the Hook itself: it only declares two permissions. v4 encodes permissions in the lower 14 bits of the contract address (Hooks.sol:29-47), verified at deployment and immutable thereafter. The flag definitions and address decoding on the v4-core side are as follows:

 

On-chain testing of getHookPermissions() confirms the bitmap: only these two bits are set.

The Hook has no source code. This article decompiles the full fee function from bytecode. The following is a behavioral breakdown of this logic that has already run on-chain:

Next, we examine each layer. The first two layers determine whether a call is a simulation; the third determines the fee for real transactions.

 

02 The Three-Layer Fee Function Determination

Determination 1: Distinguishing Callers

When the fee function is called, it first checks tx.origin. If it matches any of three constant addresses, it returns the fallback tier. The fallback tier is currently configured as 0%.

These three addresses are not arbitrary. When eth_call omits the from address, the transaction origin defaults to the zero address—which is typical for aggregator batch quoting. Direct verification in a local fork:

 

The first layer only recognizes these three addresses. The next layer shows how the Hook can further distinguish calls even when a normal address is specified.

 

Determination 2: Checking Gas

Changing tx.origin to a normal address still returns 0%, bypassing the first layer without changing the fee rate. This indicates that the first layer is not the only determination, leading to the second layer: gasleft() * 10100 / 10000 >= gate.

Aggregator quoting typically provides very high gas to prevent candidate routes from failing during simulation due to insufficient gas. Before a real transaction enters the Hook, gas has already been consumed by the Router, authorization, and preceding hops. The same code, three gas levels:

The boundary was measured at 16.85M–16.9M gas. The gap between 30M and 2.59M is the gap between "quote" and "execution".

 

Determination 3: Per-Transaction Pseudo-Randomness

Real transactions that pass the first two layers enter the third layer: the block environment fingerprint is hashed, taken modulo 10,000, and the result determines the fee tier. The current configuration has three tiers: 8% / 10% / 10%, with a fallback to 0% if none match.

This layer has two notable design aspects. The fingerprint includes three calldata fields—aggregator quoting and real routing use different calldata, so the fingerprints differ. Additionally, per-transaction pseudo-randomness gives the fee rate a distribution, making it difficult to detect patterns from a few transactions, and simple rule enumeration cannot block it.

Across all 21,086 on-chain events, 19 historical fee tiers (6.8%–28%) appeared. This indicates that fee tiers were continuously reconfigured by an admin function as needed; the effective rate at the time of the sample transaction was 12.8%.

 

The Combined Picture of the Three Layers

 

At this point, a definition of a "poison pool" can be given—a v4 pool that simultaneously satisfies three conditions: PoolKey.fee carries the dynamic fee flag; the fee decision reads execution environment signals (gas, origin, block environment) rather than public market state; and the fee rate systematically diverges between quote simulation and on-chain execution, benefiting the poison pool deployer. The dividing line lies in what the fee reads: returning the same fee rate to any caller is programmable market design; returning different fee rates based on "who is asking" is deception of the routing system.

Here, the mechanism of fee divergence is complete. But after the fee is collected, why the transaction still succeeds is the next question.

 

03 Why Slippage Protection Did Not Revert

First, look at the complete path and amounts of this transaction:

 

 

The Swap event for that hop records the effective fee rate in the fee field: 128000. v4's fee unit uses 1,000,000 as 100%, so 128000 is 12.8%. The loss from this transaction can be measured from two perspectives: from the pool's perspective, the fee is charged on WBNB input; from the entire route's perspective, the stablecoin in/out difference is 757.43 USDT, a loss of 12.7794%, which also includes preceding hop fees and depeg deviation. The two figures differ by only 0.02 percentage points, indicating that the main source of loss is this pool's LP fee.

amountOutMin only validates the final output lower bound and does not check how much fee each intermediate hop charges:

 

 

The 12.8% fee falls entirely within the 20.32% buffer, and the output remains above the floor—the transaction succeeds without reverting. The conventional default for mainstream stablecoin routes is 0.1%–1%, and this route provided more than 20 times that room. Users think a wide buffer means "safer", but in reality it hands every contract on the path over to counterparty self-discipline. Visualizing this account:

 

 

04 The Poison Pool Deployer's Operations

Whitelist: Keeping Other LPs Out

The whitelist logic is in beforeAddLiquidity, and the decompiled result is as follows:

 

 

Adding liquidity requires passing three gates: first, the PoolManager call check that any v4 Hook has; then, it must go through the official PositionManager; finally, the whitelist of position holders is checked. Addresses not on the list are reverted by the contract here, so other LPs cannot enter, fee income is not diluted, and it all goes to the deployer. The list itself is maintained by an admin function (selector 0xc4452e52) on a per-address basis.

 

0% Tier and Wash Trading

Looking at all 21,086 transactions together, the distribution shows a clear pattern: 6,946 transactions in the 0% tier with $12,947,751 in volume, averaging $1,864 per transaction; 14,140 transactions in fee tiers with $1,120,106 in volume, averaging $79 per transaction. Large volumes are concentrated in the 0% tier, while fees are concentrated on small transactions. The most reasonable explanation for large 0% transactions is that the deployer self-traded using high gas: high gas hits the same determination as aggregator quoting, and the fee cost of self-trading is near zero. The wash-traded volume pushed the pool onto market ranking boards (snapshot shows 24h volume of approximately $10.82 million, 16,671 transactions), and in the eyes of aggregators, this is a pool with good depth and low fees.

 

Fee Tiers Reconfigured as Needed

Determination 3 mentioned 19 historical fee tiers, which come from the decompiled fee configuration function (selector 0x4d909a45, not in public signature databases):

 

 

The four fee tiers and thresholds are packed together and written to the storage slot keccak(poolId, 2). On-chain testing shows this slot reads 0x0138800186a00186a0, which corresponds segment by segment to the function's packing format. Fee tiers here are parameters that can be adjusted at any time, not fixed at deployment.

 

Lifecycle

This pool was created on 08-22 at 06:57, the first swap occurred at 07:13, and the sample transaction in this article was at 21:39; the last transaction was on 08-23 at 12:25, after which it was deregistered. The entire active period was approximately 29 hours, with total volume of $14,067,857 and fee income of $131,888 (before deducting deployer costs). Upon review, the registration flag has been re-enabled—the pool still exists and can be reactivated at any time.

 

05 Complete Path

Connecting the entire attack chain:

 

 

The causality of the entire chain is in the diagram above, and all three conditions are indispensable: simulation hits the first two layers, real transactions fall into the third layer, and the buffer exceeds the fee rate. If any one fails, this method becomes ineffective.

 

06 Fixes and Recommendations

The first issue lies in the aggregator's quoting method. The root cause is that simulation and execution do not follow the same path: quoting uses idealized pool-level queries with zero address and high gas; real transactions carry real identity and already-consumed gas. The Hook can read these differences, causing the fee rate to diverge. The remedy is to make simulation closer to execution—use the actual Router calldata to be sent, real from/to, and gas close to on-chain transactions for simulation; after execution, decode the Swap event to verify the fee, and if it does not match the quote, downgrade or delist.

The second issue lies in user slippage settings. The root cause is that min_out is set too loosely: a 20.32% buffer swallows the entire 12.8% fee, and the transaction succeeds as usual. Mainstream stablecoin routes at 0.1%–1% are sufficient for daily use; check this number before initiating a transaction; wallets and frontends tightening default values can block most risks for users.

The third issue lies in routing admission. The root cause is that any dynamic fee pool can directly participate in quote competition: Hooks without open source or audit records can still enter recommended routes through wash-traded volume. For dynamic fee pools without a trusted profile, the safest approach is to exclude them from routing by default or significantly downgrade them.

 

Data and Notes

·Sample transaction:

https://bscscan.com/tx/0x7615dec2ce80506ec461a47739eb8533ac2bc7c605ca56c255964481b76363d0 (block 117497524)

·Pool initialization transaction:

https://bscscan.com/tx/0x8fa72ef72d77b61f715dc9ce90548249ab045c6d78716078cd5ab425bbe69a05

·PoolManager:

0x28e2ea090877bf75740558f6bfb36a5ffee9e9df

·Pool ID:

0x36e5540e9dedc02229fe8a82aa5b10c0bf07d1fa74e4f2ffe0efd00fa1a36aea

·Hook:

0xd111b3ddd92e627f1864520c770e913ec04e0880 (no source code, pseudocode independently decompiled for this article; admin 0x08b03e1a5444d469f4dc954e74d3f662c94a6b13)

·Volume and fees: all 21,086 Swap events of this pool accumulated transaction by transaction (eth_getLogs), WBNB converted at the execution price of the same transaction; income is gross, before deducting deployer costs

This content is for informational and educational purposes only and does not constitute investment advice related to BTCC. BTCC makes every effort but cannot guarantee the truthfulness, accuracy, or originality of the content above.

Recommended

BTCC Evening News Highlights (September 10)After Google Open-Sourced a Fruit Fly's Brain, It Learned to Play Games and Trade Crypto...Wang Chun Slams Zcash: Wall Street Hype or a Better Bitcoin?Bitcoin (BTC) Price: BTC Holds Below $80K Resistance Ahead of CPI ReportEthereum targets Oct. 6 for Glamsterdam on Sepolia