QuantAMM

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

Inconsistent indexing in `QuantAMMGradientBasedRule` lead to loss of funds.

Summary

In the QuantAMMGradientBasedRule contract, there is a mismatch between how gradients are stored for multi-asset pools when using per-asset (vector) lambda parameters. The bug stores gradients using token index (i) instead of storage index, causing values to be written in wrong slots, these incorrect gradients directly affect weight calculations, causing the pool to rebalance incorrectly.

Vulnerability Details

When QuantAMM rules compute new gradient values, they store those values in intermediateGradientStates[_pool]. This storage array is “packed” so that two 128-bit values fit in one 256-bit slot, meaning n assets occupy roughly [n/2] slots.

In QuantAMMGradientBasedRule the function _calculateQuantAMMGradient() handles two scenarios:

  1. Scalar lambda path

  2. Vector lambda path

In the scalar path, the code updates storage like so (simplified snippet):

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/base/QuantammGradientBasedRule.sol#L92-L100

// (Inside the scalar-lambda branch)
for (uint i; i < numberOfAssetsMinusOne; ) {
@> intermediateGradientStates[_poolParameters.pool][locals.storageArrayIndex] =
_quantAMMPackTwo128(locals.intermediateGradientState[i], locals.secondIntermediateValue);
i += 2; // Move to the next pair of assets
++locals.storageArrayIndex; // Move to the next storage slot
}

Here, each pair of assets (two 128-bit values) goes into one 256-bit slot. storageArrayIndex correctly increments once per two assets.

In the vector path (multi-asset, per-asset lambda), the code instead uses:

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/base/QuantammGradientBasedRule.sol#L126-L184

// (Inside the vector-lambda branch)
for (uint i; i < numberOfAssetsMinusOne; ) {
...
// @audit-issue writing to [i] instead of [locals.storageArrayIndex]
@> intermediateGradientStates[_poolParameters.pool][i] =
_quantAMMPackTwo128(locals.intermediateGradientState[i], locals.secondIntermediateValue);
i += 2; // Move two assets forward
++locals.storageArrayIndex; // BUT we do NOT use `storageArrayIndex` to write
}

Problem is here code is writing to ...[i] instead of ...[locals.storageArrayIndex].

intermediateGradientStates[_poolParameters.pool][i] = ...

Since i goes up by 2 each loop, the pool is going to exceed the storage array’s length (which is about [n/2]. This causes an out-of-bounds revert.

The storage array is sized for pairwise packing (i.e., 4 slots for 8 assets).

In the vector-lambda path, the loop writes to index = i (e.g. 2, 4, 6) rather than the next available slot.

  • Once i surpasses 3 in this example, it’s out of range and will revert on-chain.

PoC

Check the PoC and output here: https://gist.github.com/h0lydev/8b4a32df665a41a32d4d831cd6c318fd

Impact

  • Pool's asset allocation will not follow the desired strategy

  • LPs receive incorrect token exposure based on faulty weight calculations, leading to loss of funds due to incorrect balance.

Likelihood: High since it affects all pools with > 4 tokens.

PoC

Tools Used

Manual Review

Recommendations

Use the same index strategy as the scalar path.

- intermediateGradientStates[_poolParameters.pool][i] = _quantAMMPackTwo128(...);
+ intermediateGradientStates[_poolParameters.pool][locals.storageArrayIndex] = _quantAMMPackTwo128(...);
Updates

Lead Judging Commences

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

finding_gradient_rules_more_than_3_assets_and_1_lambda_will_DoS_the_update

Likelihood: Medium/High, assets>4, lambdas > 1. Impact: Medium/High, DoS update but pool works fine. Pool with 5 assets will use incorrect weights.

Support

FAQs

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

Give us feedback!