In Uniswap v3, transaction fees are distributed based on the liquidity provided within specific price ranges. I observed an issue where the fees generated by a swap are divided by the total liquidity across all ranges, but when liquidity providers (LPs) claim their fees, the calculation only considers the liquidity within their specific price range. This results in a discrepancy where some fees appear to be "lost" or unclaimable.
Example:
There are three non-overlapping liquidity ranges, a, b, and c, each with 10 liquidity.
A swap occurs within the b range, generating 90 in fees.
The global fee growth feeGrowthGlobalX128 is updated based on the total liquidity (30 in this case):
state.feeGrowthGlobalX128 += PRBMath.mulDiv(
step.feeAmount,
FixedPoint128.Q128,
state.liquidity
);
Here, the feeAmount is divided by 30 (the total liquidity across ranges).
However, when LPs claim fees in their respective ranges, the fees are calculated based on their individual liquidity within that range, not the total liquidity:
uint128 tokensOwed0 = uint128(
PRBMath.mulDiv(
feeGrowthInside0X128 - self.feeGrowthInside0LastX128,
self.liquidity,
FixedPoint128.Q128
)
);
In this case, the LP in range b will claim fees using their 10 liquidity, but LPs in ranges a and c receive nothing because no trades occurred in their ranges.
The Problem:
When updating the global fee growth, the fees are divided by the total liquidity across all ranges, but when LPs claim fees, they only receive a portion based on their liquidity in the active range. This leads to a situation where 2/3 of the fees (those corresponding to ranges a and c) seem unclaimable, even though they are accounted for in the global fee growth.
In other words, fees from inactive ranges are recorded in the global fee variable but are never distributed, as no liquidity in those ranges participated in the swap.
Please, I am a beginner in the web3 field. Please help me analyze whether I have missed something or the mechanism itself is like this?
I asked gpt, and read the source again and again, but still confusing.