QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: high
Valid

in `onAfterRemoveLiquidity()`, `maxAmountsIn` during admin fee distribution can cause txn reverts

Summary

in onAfterRemoveLiquidity() during _vault.addLiquidity() made to the admin, maxAmountsIn will cause reverts in multiple circumstances

Vulnerability Details

In onAfterRemoveLiquidity(), amountsOutRaw can round down when calculating exitFee here

File: UpliftOnlyExample.sol
522: for (uint256 i = 0; i < localData.amountsOutRaw.length; i++) {
523: uint256 exitFee = localData.amountsOutRaw[i].mulDown(localData.feePercentage);

That exitFee is then used to get accruedQuantAMMFees (used as maxAmountIn during _vault.addLiquidity())

  • Both of the above variables are the actual tokens of the Pool that will be used during liquidity addition
    amountsOutRaw array can have low decimal tokens like USDC for example that will most likely have slight truncation during fee calculation

Now during minBptAmountOut calculations in _vault.addLiquidity() to the admin

File: UpliftOnlyExample.sol
542: minBptAmountOut: localData.feeAmount.mulDown(localData.adminFeePercent) / 1e18,
  • There can be cases where BPT amounts are perfectly divisible and doesn't round down

    • This is likely to happen since during Liquidity addition BPT is calculated and then stay unchanged after that, but Pool reserves change by swapping and weight changes

      • So during swapping balances corresponding to those BPT change by wei through the invariant swap formula

    • BPT tokens are 18 decimals and less likely to round down in that calculation

The above condition will create a situation where during _vault.addLiquidity() the minBptAmountOut was accurate but the call will provide less than needed maxAmountsIn causing a revert of the txn

The above temporary DOS can be mitigated by user repeatedly trying to adjust bptAmountIn that rounds down (to have consistent double rounding down in onAfterRemoveLiquidity()) during his call to removeLiquidityProportional()

Pseudo Exampled of numbered scenario

1- Token A (USDC, 6 decimals):

  • amountsOutRaw[0] = 100.000123 USDC (100_000_123)

2- Token B (WETH, 18 decimals):

  • amountsOutRaw[1] = 0.100000000000000123 WETH (100_000_000_000_000_123)

Parameters:

  • feePercentage = 0.02e18 (2%)

  • adminFeePercent = 0.5e18 (50%)

  • feeAmount = 20 (perfect number, no decimals)

Calculation flow with rounding:

1- For USDC:

  • exitFee = 100_000_123 × 0.02e18 / 1e18 = 2_000_002 (rounded down)

  • accruedQuantAMMFees[0] = 2_000_002 × 0.5e18 / 1e18 = 1_000_001

2- For WETH:

  • exitFee = 100_000_000_000_000_123 × 0.02e18 / 1e18 = 2_000_000_000_000_002

  • accruedQuantAMMFees[1] = 2_000_000_000_000_002 × 0.5e18 / 1e18 = 1_000_000_000_000_001

3- BPT calculation:

  • feeAmount = 2e18 (perfect number)

  • minBptAmountOut = 20 × 0.5e18 / 1e18 = 1e18 (no rounding needed)
    Txn reverting due to minBptAmountOut not reached with the provided accruedQuantAMMFees as maxAmountsIn

Please notice that simply rounding Up won't work for the same reason (a variable will be perfect % and will not benefit from mulUp while other variable gets benefited from mulUp cause it would have been truncated otherwise) and this issue sequence will always happen and is different from issues simply stating that adminFees should rounUp

Impact

Multiple txn reverts in multiple circumstances affecting the availability of removeLiquidityProportional() (temporary DOS)

Tools Used

Manual review

Recommendations

Build a logic that

  • Doesn't pass maxAmountsIn or pass a very high amount, doesn't matter

  • uses returned amountsIn used from addLiquidity() and then use those to remove it from hookAdjustedAmountsOutRaw

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_onAfterRemoveLiquidity_vault.addLiquidity_rounding_precision_DoS

Likelihood: High, multiple rounding down and little value can trigger the bug Impact: High, DoS the removal.

Support

FAQs

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

Give us feedback!