QuantAMM

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

calculateBlockNormalisedWeight function does not properly handle negative multipliers, leading to negative weights or weights that deviate from expected bounds

Summary

The QuantAMMWeightedPool contract manages a pool of tokens where each token has a normalized weight. These weights can change over time based on certain multipliers and update intervals. A fundamental invariant of such pools is that the weights of all tokens must:

be greater than zero and the total of all weights must equal one (when normalized).

Maintaining these invariants is crucial for the correct operation of the pool, ensuring fairness in trading and accurate price calculations. Let's see how this is broken below.

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

Vulnerability Details

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

The issue arises because the calculateBlockNormalisedWeight function does not properly handle negative multipliers, leading to negative weights or weights that deviate from expected bounds.

function calculateBlockNormalisedWeight(
int256 weight,
int256 multiplier,
uint256 timeSinceLastUpdate
) internal pure returns (uint256) {
//multiplier is always below 1 which is int128, we multiply by 1e18 for rounding as muldown / 1e18 at the end.
int256 multiplierScaled18 = multiplier * 1e18;
if (multiplier > 0) {
return uint256(weight) + FixedPoint.mulDown(uint256(multiplierScaled18), timeSinceLastUpdate);
} else {
//CYFRIN H02
return uint256(weight) - FixedPoint.mulUp(uint256(-multiplierScaled18), timeSinceLastUpdate);
}
}

When multiplier is negative, the function enters the else block. It attempts to calculate the new weight by subtracting the product of the negative multiplier and timeSinceLastUpdate from the current weight.

newWeight = uint256(weight) - FixedPoint.mulUp(uint256(-multiplierScaled18), timeSinceLastUpdate);

Since multiplier is negative, -multiplierScaled18 becomes positive. When the product of -multiplierScaled18 and timeSinceLastUpdate is greater than weight, the subtraction results in a negative value.

example:

Let's say weight = 0.1e18 (representing 10% weight in 18 decimal fixed-point format).

multiplier = -0.05e18 (negative multiplier).

timeSinceLastUpdate = 3.

Calculating multiplierScaled18:

product = FixedPoint.mulUp(uint256(-multiplierScaled18), timeSinceLastUpdate)
= FixedPoint.mulUp(0.05e36, 3)
= 0.15e36

newWeight = uint256(0.1e18) - 0.15e36

This results in a negative value, which is not valid when casting to uint256.

Casting a negative int256 to uint256 does not throw an error but results in a very large number due to underflow.

The negative value wraps around to a large positive value (2^256 - value). This will lead to unintended and invalid weights.

Impact

Casting a negative int256 to uint256 does not throw an error but results in a very large number due to underflow which is unintended.

Tools Used

Manual Review

Recommendations

After computing the new weight, check if it remains within acceptable bounds (greater than zero and less than or equal to 1e18)

Utilize safe math operations that handle overflows and underflows.

For signed integers, use libraries that provide safe arithmetic operations, such as PRBMathSD59x18.

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.

Give us feedback!