QuantAMM

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

Double Increment of targetIndex in _quantAMMUnpack128Array Corrupts Pool Weight Calculations and Memory

Summary

A vulnerability exists in the _quantAMMUnpack128Array function's core logic within the ScalarRuleQuantAMMStorage contract. The function incorrectly unpacks 128-bit integers that represent pool weights, variances, and gradients by double-incrementing array indices, leading to data corruption.

The vulnerability specifically appears in this code section:

https://github.com/Cyfrin/2024-12-quantamm/blob/main/pkg/pool-quantamm/contracts/QuantAMMStorage.sol#L335

// In ScalarRuleQuantAMMStorage.sol
function _quantAMMUnpack128Array(int256[] memory _sourceArray, uint _targetArrayLength) internal pure returns (int256[] memory targetArray) {
require(_sourceArray.length * 2 >= _targetArrayLength, "SRC!=TGT");
targetArray = new int256[](_targetArrayLength);
uint targetIndex;
uint sourceArrayLengthMinusOne = _sourceArray.length - 1;
bool divisibleByTwo = _targetArrayLength % 2 == 0;
for (uint i; i < _sourceArray.length; ) {
targetArray[targetIndex] = _sourceArray[i] >> 128; // Unpack high bits
unchecked {
++targetIndex; // First incorrect increment
}
if ((!divisibleByTwo && i < sourceArrayLengthMinusOne) || divisibleByTwo) {
targetArray[targetIndex] = int256(int128(_sourceArray[i])); // Unpack low bits
}
unchecked {
++i;
++targetIndex; // Second incorrect increment
}
}
}

This function is critical because it's used throughout the QuantAMM system for unpacking:

  • Pool weights in gradient calculations (used in QuantAMMGradientBasedRule.sol)

  • Variance values (used in QuantAMMVarianceBasedRule.sol)

  • Intermediate states for mathematical calculations

The double increment causes a cascading failure across the system's core mathematical functions. In the gradient calculations, the corrupted unpacking leads to incorrect weight positions:

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/base/QuantammGradientBasedRule.sol#L47

// In QuantAMMGradientBasedRule.sol
locals.intermediateGradientState = _quantAMMUnpack128Array(
intermediateGradientStates[_poolParameters.pool],
_poolParameters.numberOfAssets
);

Similarly, in variance calculations, the unpacking error propagates through the variance state management:

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/base/QuantammVarianceBasedRule.sol#L58

// In QuantAMMVarianceBasedRule.sol
locals.intermediateVarianceState = _quantAMMUnpack128Array(
intermediateVarianceStates[_poolParameters.pool],
locals.n
);

The corruption manifests in two critical ways: First, pool weights end up in incorrect positions, which directly corrupts price calculations and swap amounts in the AMM's core pricing functions. Second, since variance calculations rely on properly ordered values, the risk assessment mechanism receives corrupted data sequences. Eventually, as the index skipping continues, array bounds are exceeded, leading to memory corruption that can crash transactions entirely.

Impact

When pool weights are unpacked into incorrect positions, it directly corrupts the gradient-based price calculations and variance assessments. This corruption propagates through every swap operation, causing severe miscalculation of token amounts and prices. Since the function also handles unpacking of intermediate states for variance calculations, risk assessment mechanisms receive corrupted data sequences, leading to incorrect pool behavior under varying market conditions. The severity is amplified by memory corruption when skipped indices eventually exceed array bounds, potentially crashing transactions and freezing pool operations. This represents a critical failure point that compromises both the financial accuracy and operational stability of the entire AMM system.

Recommended mitigation steps

Remove the second increment of targetIndex and only increment after successfully writing a value:

function _quantAMMUnpack128Array(
int256[] memory _sourceArray,
uint _targetArrayLength
) internal pure returns (int256[] memory targetArray) {
require(_sourceArray.length * 2 >= _targetArrayLength, "SRC!=TGT");
targetArray = new int256[](_targetArrayLength);
uint targetIndex;
uint sourceArrayLengthMinusOne = _sourceArray.length - 1;
bool divisibleByTwo = _targetArrayLength % 2 == 0;
for (uint i; i < _sourceArray.length; ) {
// Unpack and store high bits
targetArray[targetIndex] = _sourceArray[i] >> 128;
unchecked {
++targetIndex;
}
// Unpack and store low bits if needed
if ((!divisibleByTwo && i < sourceArrayLengthMinusOne) || divisibleByTwo) {
targetArray[targetIndex] = int256(int128(_sourceArray[i]));
unchecked {
++targetIndex;
}
}
unchecked {
++i;
}
}
}
Updates

Lead Judging Commences

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

Support

FAQs

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