QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: high
Valid

Index out-of-bounds error in QuantAMMVarianceBasedRule::_calculateQuantAMMVariance

Summary

The QuantAMMVarianceBasedRule::_calculateQuantAMMVariance function will revert in case _poolParameters.lambda.length != 1 and locals.notDivisibleByTwo == true due to an array index out-of-bounds access.

Vulnerability Details

In the QuantAMMVarianceBasedRule::_calculateQuantAMMVariance function, there is a lack of logic to decrease locals.nMinusOne by 1 when _poolParameters.lambda.length != 1 and locals.notDivisibleByTwo == true. This causes the function to attempt accessing array elements at an index that is out of bounds.

QuantAMMVarianceBasedRule::_calculateQuantAMMVariance function:

function _calculateQuantAMMVariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) internal returns (int256[] memory) {
QuantAMMVarianceLocals memory locals;
locals.n = _poolParameters.numberOfAssets;
locals.finalState = new int256[](locals.n);
locals.intermediateVarianceState = _quantAMMUnpack128Array(
intermediateVarianceStates[_poolParameters.pool],
locals.n
);
locals.nMinusOne = locals.n - 1;
locals.notDivisibleByTwo = locals.n % 2 != 0;
locals.convertedLambda = int256(_poolParameters.lambda[0]);
locals.oneMinusLambda = ONE - locals.convertedLambda;
if (_poolParameters.lambda.length == 1) {
...
} else {
=> // @audit Lack of logic: `locals.nMinusOne -= 1` when `locals.notDivisibleByTwo == true`
for (uint i; i < locals.nMinusOne; ) {
...
}
if (locals.notDivisibleByTwo) {
unchecked {
=> ++locals.nMinusOne; // locals.nMinusOne == locals.n
locals.convertedLambda = int256(_poolParameters.lambda[locals.nMinusOne]);
locals.oneMinusLambda = ONE - locals.convertedLambda;
}
locals.intermediateState =
locals.convertedLambda.mul(locals.intermediateVarianceState[locals.nMinusOne]) +
(_newData[locals.nMinusOne] - _poolParameters.movingAverage[locals.n + locals.nMinusOne])
.mul(_newData[locals.nMinusOne] - _poolParameters.movingAverage[locals.nMinusOne])
.div(TENPOWEIGHTEEN);
=> locals.intermediateVarianceState[locals.nMinusOne] = locals.intermediateState; // Array index out of bounds
=> locals.finalState[locals.nMinusOne] = locals.oneMinusLambda.mul(locals.intermediateState); // Array index out of bounds
intermediateVarianceStates[_poolParameters.pool][locals.storageIndex] = locals
.intermediateVarianceState[locals.nMinusOne];
}
}
return locals.finalState;
}

Key issues:

  1. After ++locals.nMinusOne, locals.nMinusOne becomes equal to locals.n.

  2. Arrays like locals.finalState and locals.intermediateVarianceState have a length of locals.n.

  3. Accessing elements such as locals.intermediateVarianceState[locals.nMinusOne] and locals.finalState[locals.nMinusOne] results in an index-out-of-bounds error.

Impact

The QuantAMMVarianceBasedRule::_calculateQuantAMMVariance function reverts in cases where _poolParameters.lambda.length != 1 and locals.notDivisibleByTwo == true.

Recommendations

Update the QuantAMMVarianceBasedRule::_calculateQuantAMMVariance function:

function _calculateQuantAMMVariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) internal returns (int256[] memory) {
...
if (_poolParameters.lambda.length == 1) {
...
} else {
+ if (locals.notDivisibleByTwo) {
+ unchecked {
+ --locals.nMinusOne;
+ }
+ }
for (uint i; i < locals.nMinusOne; ) {
...
}
if (locals.notDivisibleByTwo) {
...
}
}
return locals.finalState;
}
Updates

Lead Judging Commences

n0kto Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_calculateQuantAMMVariance_revert_when_vector_lambda_and_odd_asset_number

Likelihood: Medium/High, odd asset number + lambda is a vector. Impact: Medium/High, DoS the update.

Support

FAQs

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

Give us feedback!