QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Invalid

Unchecked Weight Boundaries Allow AMM Price Manipulation and Pool Drainage

Summary

The calculateBlockNormalisedWeight function performs weight calculations without validating that the resulting weights stay within the acceptable bounds for an AMM pool:

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPool.sol#L527

function calculateBlockNormalisedWeight(
int256 weight,
int256 multiplier,
uint256 timeSinceLastUpdate
) internal pure returns (uint256) {
int256 multiplierScaled18 = multiplier * 1e18;
if (multiplier > 0) {
return uint256(weight) + FixedPoint.mulDown(uint256(multiplierScaled18), timeSinceLastUpdate);
} else {
return uint256(weight) - FixedPoint.mulUp(uint256(-multiplierScaled18), timeSinceLastUpdate);
}
}

This unbounded calculation creates a critical vulnerability where pool weights can exceed 100% or fall below 0%. In AMM pools, weights directly determine asset pricing and swap calculations. Invalid weights break core invariants of the AMM design:

A weight exceeding 100% could make an asset artificially expensive, while a negative weight would completely break the pricing model. The exploitation potential stems from how these invalid states interact with the AMM's core pricing mechanics. When weights exceed valid bounds, the spot price calculations produce invalid results, creating arbitrage opportunities.

An attacker could exploit these mispriced assets through carefully sequenced trades, first taking advantage of the artificially inflated or deflated prices to accumulate assets at a discount. These manipulated trades would then permanently distort the pool's balance ratios since the underlying price calculations are no longer operating within valid mathematical bounds. By chaining such trades together, an attacker could progressively drain value from the pool by repeatedly exploiting the price discrepancies, with each trade further destabilizing the pool's state. The compounding nature of this exploitation means that even a single instance of an invalid weight could lead to cascading failures in the pool's pricing model.

The impact is particularly severe because the function is called during every swap operation via _getNormalizedWeight, making this a frequently accessible attack vector.

Recommended Mitigation Steps

Add weight boundary validation:

function calculateBlockNormalisedWeight(
int256 weight,
int256 multiplier,
uint256 timeSinceLastUpdate
) internal pure returns (uint256) {
int256 multiplierScaled18 = multiplier * 1e18;
uint256 newWeight;
if (multiplier > 0) {
newWeight = uint256(weight) + FixedPoint.mulDown(uint256(multiplierScaled18), timeSinceLastUpdate);
} else {
newWeight = uint256(weight) - FixedPoint.mulUp(uint256(-multiplierScaled18), timeSinceLastUpdate);
}
// Ensure weight stays within valid bounds
require(newWeight <= FixedPoint.ONE, "Weight exceeds maximum (100%)");
require(newWeight >= MIN_WEIGHT, "Weight below minimum");
return newWeight;
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

invalid_weights_can_be_negative_or_extreme_values

_clampWeights will check that these weights are positive and in the boundaries before writing them in storage.

Support

FAQs

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