QuantAMM

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

Negative Weight Calculation in Momentum Update Rule

Summary

This critical vulnerability has been identified in the momentumUpdateRule.sol contract where the scalar computation of weights does not enforce non-negative constraints but the vector computation does. The implementation allows weight calculations to result in negative values, which is fundamentally incompatible with AMM (Automated Market Maker) operations.

Affected segment of code: https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/MomentumUpdateRule.sol#L97-L136

Vulnerability Details

The issue is demonstrated in the provided proof of concept where weight calculations can produce negative values due to insufficient bounds checking. The test case shows that under certain market conditions and parameter configurations, specifically:

  • Using a scalar kappa of 0.8e18

  • With lambda set to 0.2e18

  • When there is a significant price divergence between assets

  • Initial weights of 0.5e18 for both assets

The calculation results in a negative weight of approximately -0.78e18 for one asset and 1.78e18 for the other, which violates fundamental AMM invariants.

Paste the following code into the QuantAMMMomentum.t.sol file

function testNegativeWeightsScalar() public {
// Define parameters - using scalar kappa (length 1)
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.8e18;
int256[] memory previousAlphas = new int256[]();
previousAlphas[0] = 1e18;
previousAlphas[1] = 1e18;
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = 2e18;
prevMovingAverages[1] = 2e18;
int256[] memory movingAverages = new int256[]();
movingAverages[0] = 2e18;
movingAverages[1] = 2e18;
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.2e18);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.5e18;
prevWeights[1] = 0.5e18;
// Create significant price difference to generate momentum
int256[] memory data = new int256[]();
data[0] = 1e18; // Lower price for first asset
data[1] = 3e18; // Higher price for second asset
// Expect first weight to go negative due to price decline and negative kappa
int256[] memory expectedResults = new int256[]();
expectedResults[0] = -780000000000000035; // Negative weight
expectedResults[1] = 1780000000000000035; // Compensating positive weight
//11.42857142857142850
//0.564285714285714285
runInitialUpdate(
2,
parameters,
previousAlphas,
prevMovingAverages,
movingAverages,
lambdas,
prevWeights,
data,
expectedResults
);
// Retrieve the actual results
int256[] memory actualResults = rule.GetResultWeights();
// Ensure the array is not empty and check the negative weight
require(actualResults.length > 0, "Results array is empty.");
require(actualResults[0] < 0, "Expected first weight to be negative.");
}

Impact

The presence of negative weights in an AMM system can have severe consequences:

  1. Pool Invariant Violation: AMMs rely on constant product or similar invariants that assume positive weights. Negative weights break these fundamental assumptions.

  2. Incorrect Price Formation: Negative weights lead to invalid price calculations, potentially causing:

    • Erroneous trade executions

    • Incorrect slippage calculations

    • Manipulation opportunities for attackers

  3. Economic Implications:

    • Destabilized pool economics

  4. System Instability:

    • Undefined behavior in core AMM functions

    • Potential for cascading failures in dependent protocols

    • Risk of permanent pool imbalance

Tools Used

  • Manual code review

Recommendations

  1. Implement Weight Bounds:

if (locals.kappaStore.length == 1) {
//scalar logic separate to vector for efficiency
locals.normalizationFactor /= int256(locals.prevWeightLength);
// To avoid intermediate overflows (because of normalization), we only downcast in the end to an uint6
// κ · ( 1/p(t) * ∂p(t)/∂t − ℓp(t))
for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
int256 res = int256(_prevWeights[locals.i]) +
locals.kappaStore[0].mul(locals.newWeights[locals.i] - locals.normalizationFactor);
newWeightsConverted[locals.i] = res;
+ require(locals.res >= 0, "Invalid weight");
unchecked {
++locals.i;
}
}
}
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!