QuantAMM

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

Unvalidated Scalar Weight Calculations in `DifferenceMomentumUpdateRule.sol` Leading to Negative Pool Weights

Summary

A vulnerability in the AMM weight adjustment logic (_getWeights) within the DifferenceMomentumUpdateRule.sol file allows weights to become negative under certain conditions when scalar logic is used, even though the function is designed to maintain non-negative weights. The issue arises due to insufficient bounds checking in the momentum-based weight adjustment calculation. This vulnerability is triggered when handling significant price divergences and unbalanced initial weights. Notably, this issue is effectively mitigated in the vector-based logic is employed.

Affected code: https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/DifferenceMomentumUpdateRule.sol#L118-L131

Vulnerability Details

The vulnerability exists in the weight calculation logic within the _getWeights function. The core issue lies in this calculation:

locals.res = int256(_prevWeights[locals.i]) +
locals.kappaStore[locals.i].mul(locals.newWeights[locals.i] - locals.normalizationFactor);

The function fails to properly validate that the resulting weight remains positive after applying the momentum adjustment. This becomes particularly problematic when:

  1. Initial weights are significantly unbalanced (e.g., 0.1e18 vs 0.8e18)

  2. There are large price divergences between assets

  3. The kappa parameter amplifies the momentum effect

The vulnerability was confirmed through the following test scenario:

  • weights: [0.1e18, 0.8e18]

  • Price data: [2, 7] (3.5x difference)

  • Moving averages: All initialized to [1, 1] for short and [2, 2] for long

  • Kappa: 1.0 (scalar)

  • Lambda short: 0.9

POC

Paste the following code into QuantAMMDifferenceMomentum.t.sol file

function testNegativeWeightsIsAllowedForScalarKappa() public {
// Initialize parameters array (needs only 2 parameters for this contract)
int256[][] memory parameters = new int256[][]();
// Parameter 0: Kappa (scalar)
parameters[0] = new int256[]();
parameters[0][0] = PRBMathSD59x18.fromInt(1);
parameters[1] = new int256[]();
parameters[1][0] = 0.9e18;
// Initialize previous short moving averages
int256[] memory prevShortMovingAverage = new int256[]();
prevShortMovingAverage[0] = PRBMathSD59x18.fromInt(1);
prevShortMovingAverage[1] = PRBMathSD59x18.fromInt(1);
// Initialize moving averages (previous)
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = PRBMathSD59x18.fromInt(2);
prevMovingAverages[1] = PRBMathSD59x18.fromInt(2);
// Current moving averages
int256[] memory movingAverages = new int256[]();
movingAverages[0] = PRBMathSD59x18.fromInt(2);
movingAverages[1] = PRBMathSD59x18.fromInt(2);
// Lambda initialization
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.9e18);
// Previous weights with small initial weight for first asset
int256[] memory prevWeights = new int256[]();
prevWeights[0] = int256(0.1e18);
prevWeights[1] = int256(0.8e18);
// Price data with significant difference
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(2);
data[1] = PRBMathSD59x18.fromInt(7);
// Initialize pool
mockPool.setNumberOfAssets(2);
// Initialize the rule
vm.startPrank(owner);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
prevShortMovingAverage,
2
);
// Calculate weights
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
movingAverages
);
// Get results
int256[] memory resultWeights = rule.GetResultWeights();
// Log weights for inspection
emit log_named_int("Weight 0", resultWeights[0]);
emit log_named_int("Weight 1", resultWeights[1]);
// Check for negative weights
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 generate negative weights in an AMM system can lead to:

  1. Pool Instability: Negative weights can cause incorrect price calculations and pool imbalances

  2. Potential Economic Exploits: Traders could potentially exploit these negative weights for arbitrage opportunities

  3. System Malfunction: Many AMM functions assume weights are positive, potentially leading to system-wide failures

  4. Loss of Funds: In extreme cases, negative weights could lead to incorrect swap calculations and loss of user funds

Tools Used

  • Foundry Framework for testing

  • Manual code review

Recommendations

  1. Implement Strict Weight Validation:

function _getWeights(
int256[] calldata _prevWeights,
int256[] memory _data,
int256[][] calldata _parameters,
QuantAMMPoolParameters memory _poolParameters
) internal override returns (int256[] memory newWeightsConverted) {
// ... existing code ...
for (locals.i = 0; locals.i < _prevWeights.length; ) {
locals.res = int256(_prevWeights[locals.i]) +
locals.kappaStore[locals.i].mul(locals.newWeights[locals.i] - locals.normalizationFactor);
+ require(newWeightsConverted[locals.i] >= 0, "Invalid weight");
newWeightsConverted[locals.i] = locals.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.