Vanguard

First Flight #56
Beginner FriendlyDeFiFoundry
0 EXP
Submission Details
Impact: medium
Likelihood: high

[MEDIUM-01] Liquidity value and token quantity units mismatch, quota calculation error

Author Revealed upon completion

Root + Impact

  • Root Cause:maxSwapAmount is calculated using Uniswap's liquidity unit (L), but swapAmount being compared to it is in token quantity; the two use different units.

  • Impact: The quota calculation may be severely off from expectations, causing users to be wrongly penalized or to bypass limits.

Description

  • Expected behavior: Limits should be calculated based on token amounts to ensure a user's single or cumulative trades do not exceed a certain proportion of the pool's token reserves

  • Specific issue: the code uses the liquidity L value directly as the token amount:

// initialLiquidity It's a liquidity unit (L)
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
// swapAmount is the amount of token
uint256 swapAmount = params.amountSpecified < 0
? uint256(-params.amountSpecified)
: uint256(params.amountSpecified);
// @audit the unit is not match!L vs tokenamount
uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
if (addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true;
}

Risk

Likelihood: High

  • This calculation is performed for every transaction

  • Unit discrepancies cause the limit value to be unrelated to the actual token reserves

Impact: Medium

  • If L >> token quantity, the cap is too high and the protection fails

  • If L << token supply, the cap is too low and legitimate users are penalized

Proof of Concept

function test_UnitMismatch() public {
// Suppose pool state
// liquidity L = 1e18
// actual token0 reserve = 1000e18
// phase1LimitBps = 500 (5%)
// Current calculation method
uint256 maxSwapAmount = (1e18 * 500) / 10000; // = 5e16
// The user attempts to trade 1e18 tokens (0.1% of the actual reserves)
uint256 swapAmount = 1e18;
// 1e18 > 5e16, penalty triggered!
// But the user actually only traded 0.1% of the reserves, well below the 5% limit
}

Recommended Mitigation

Calculate limits using actual token reserve amounts:

- uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
+ uint256 public initialReserve0;
+ uint256 public initialReserve1;
+function _afterInitialize(...) internal override returns (bytes4) {
+ // ...
+ // Obtain actual token reserves
+ (uint256 reserve0, uint256 reserve1) = getPoolReserves(poolManager, key);
+ initialReserve0 = reserve0;
+ initialReserve1 = reserve1;
+ // ...
+}
+function _beforeSwap(...) internal override returns (...) {
+ // Choose the correct reserve based on the trade direction
+ uint256 relevantReserve = params.zeroForOne ? initialReserve0 : initialReserve1;
+ uint256 maxSwapAmount = (relevantReserve * phaseLimitBps) / 10000;
+ // ...
+}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!