QuantAMM

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

Missing Non-Negative Weight Validation in `AntiMomentumUpdateRule.sol` with Scalar Kappa

Summary

The Anti-Momentum Rule's _getWeights function lacks validation for negative weights when using scalar kappa parameters, while this validation exists for vector kappa. This inconsistency allows negative pool weights to be set, which could lead to invalid pool states and potential economic exploits.

Affected code: https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/AntimomentumUpdateRule.sol#L94-L105

Vulnerability Details

In the _getWeights function, two different paths exist for weight calculation based on whether kappa is scalar (single value) or vector (multiple values):

For vector kappa:

int256 res = int256(_prevWeights[locals.i]) +
int256(locals.kappa[locals.i]).mul(locals.normalizationFactor - locals.newWeights[locals.i]);
require(res >= 0, "Invalid weight");

For scalar kappa:

int256 res = int256(_prevWeights[locals.i]) +
int256(locals.kappa[0]).mul(locals.normalizationFactor - locals.newWeights[locals.i]);
newWeightsConverted[locals.i] = res; // No validation

The vulnerability can be triggered under the following conditions:

  1. Using scalar kappa configuration

  2. Price movements creating large gradients

  3. Kappa value sufficient to amplify the negative adjustment

  4. Previous weights small enough to be overwhelmed by the negative adjustment

The issue was reproduced using the following parameters:

  • Kappa: 0.8e18

  • Previous weights: [0.5e18, 0.5e18]

  • Price data: [1e18, 3e18]

  • Moving averages: [2e18, 2e18]

  • Lambda: 0.2e18

POC

Paste the code below into the QuantAMMAntiMomentum.t.sol file.

function testNegativeWeightsAllowedForScalarKappa() public {
// Define parameters for scalar kappa
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = int256(0.8e18); // Using 0.8 as kappa
// Define previous alphas
int256[] memory previousAlphas = new int256[]();
previousAlphas[0] = PRBMathSD59x18.fromInt(1);
previousAlphas[1] = PRBMathSD59x18.fromInt(1);
// Define previous moving averages
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = PRBMathSD59x18.fromInt(2);
prevMovingAverages[1] = PRBMathSD59x18.fromInt(2);
// Define current moving averages
int256[] memory movingAverages = new int256[]();
movingAverages[0] = PRBMathSD59x18.fromInt(2);
movingAverages[1] = PRBMathSD59x18.fromInt(2);
// Define lambda values
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.2e18);
// Previous weights
int256[] memory prevWeights = new int256[]();
prevWeights[0] = int256(0.5e18);
prevWeights[1] = int256(0.5e18);
// Data array
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(1);
data[1] = PRBMathSD59x18.fromInt(3);
// Initialize the pool
mockPool.setNumberOfAssets(2);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
previousAlphas,
2
);
// Calculate weights
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
movingAverages
);
// Get the calculated weights
int256[] memory resultWeights = rule.GetResultWeights();
// Log the weights for inspection
emit log_named_int("Weight 0", resultWeights[0]);
emit log_named_int("Weight 1", resultWeights[1]);
// Assert that at least one weight is negative
bool hasNegativeWeight = false;
for (uint256 i = 0; i < resultWeights.length; i++) {
if (resultWeights[i] < 0) {
hasNegativeWeight = true;
break;
}
}
assertTrue(hasNegativeWeight, "Expected to find at least one negative weight");
}

Impact

The ability to produce negative weights in an AMM pool can have severe consequences:

  1. Pool operations like swaps and liquidity provisions may revert or behave unpredictably

  2. Economic calculations based on pool weights could produce invalid results

  3. Integration with other DeFi protocols may fail

  4. Potential for economic exploits by manipulating prices to force negative weights

  5. Loss of funds for liquidity providers if negative weights cause incorrect distribution of pool assets

Tools Used

  • Foundry for testing and reproduction

  • Manual code review

Recommendations

  1. Add non-negative weight validation for scalar kappa:

if (locals.kappa.length == 1) {
locals.normalizationFactor /= int256(_prevWeights.length);
for (locals.i = 0; locals.i < _prevWeights.length; ) {
int256 res = int256(_prevWeights[locals.i]) +
int256(locals.kappa[0]).mul(locals.normalizationFactor - locals.newWeights[locals.i]);
+ require(res >= 0, "Invalid weight"); // Add this line
newWeightsConverted[locals.i] = res;
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.