QuantAMM

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

Overflow/Underflow Risk in CalculateNewWeights with Odd Assets and `requiresPrevAverage = false`

Summary

In the CalculateNewWeights function, when requiresPrevAverage == false and the numberOfAssets is odd, the last entry in the calculationMovingAverage array is stored without performing 128-bit Min and Max checks.

Later, when the items are unpacked in the _quantAMMUnpack128Array function, this may lead to an overflow/underflow, resulting in incorrect values being returned.

Vulnerability Details

When calling the CalculateNewWeights function, the new moving average is first calculated and then stored in an int128 packed array with overflow/underflow checks applied. There are two scenarios to consider:

  1. Previous Moving Average Required:
    In this case, all array data, including the previous moving average, is packed into the int128 array with the necessary overflow/underflow checks.

  2. Previous Moving Average Not Required:
    If the previous average is not required and the length of the moving average array is odd, the last index is stored without performing any overflow/underflow checks, potentially leading to incorrect values.

/contracts/rules/UpdateRule.sol
174: if (locals.numberOfAssets % 2 != 0) {
175: locals.lastAssetIndex = locals.numberOfAssets - 1;
176: unchecked {
177: locals.nMinusOne = ((locals.lastAssetIndex) / 2);
178: }
179: if (locals.requiresPrevAverage) {
180: locals.calculationMovingAverage[locals.lastAssetIndex + locals.numberOfAssets] = locals
181: .currMovingAverage[locals.lastAssetIndex];
182: }
183: @> locals.calculationMovingAverage[locals.lastAssetIndex] = locals.updatedMovingAverage[locals.lastAssetIndex]; // @audit : where is the check for Max and Min for this value ??
184: if (!locals.requiresPrevAverage) {
185: @> movingAverages[_pool][locals.nMinusOne] = locals.updatedMovingAverage[locals.lastAssetIndex];// @audit : How this last index data will be fetched/unpack
186: }
187: }
198: if (locals.requiresPrevAverage) {
199: movingAverages[_pool] = _quantAMMPack128Array(locals.calculationMovingAverage); // [0,1,2,3,4,5,6,7,8,9]
200: }
201:

When unpacking the movingAverages array, there is a risk of downcasting. If the value stored at the last index exceeds the int128 max/min limits, an overflow or underflow may occur, leading to incorrect values being returned.

/contracts/rules/UpdateRule.sol:116
116: locals.currMovingAverage = _quantAMMUnpack128Array(movingAverages[_pool], locals.numberOfAssets);

The code at line 377 poses a risk of silent overflow/underflow. If the value being handled does not fit within the int128 range, it will overflow/underflow without any warnings, potentially leading to incorrect results.

/contracts/QuantAMMStorage.sol
350: function _quantAMMUnpack128Array(
351: int256[] memory _sourceArray,
352: uint _targetArrayLength
353: ) internal pure returns (int256[] memory targetArray) {
355: require(_sourceArray.length * 2 >= _targetArrayLength, "SRC!=TGT");
356: targetArray = new int256[](_targetArrayLength);
357: uint targetIndex;
358: uint sourceArrayLengthMinusOne = _sourceArray.length - 1;
359: bool divisibleByTwo = _targetArrayLength % 2 == 0;
360: for (uint i; i < _sourceArray.length; ) {
361: targetArray[targetIndex] = _sourceArray[i] >> 128;
362: unchecked {
363: ++targetIndex;
364: }
365: if ((!divisibleByTwo && i < sourceArrayLengthMinusOne) || divisibleByTwo) {
366: targetArray[targetIndex] = int256(int128(_sourceArray[i]));
367: }
368: unchecked {
369: ++i;
370: ++targetIndex;
371: }
373: }
374:
375: if (!divisibleByTwo) {
376: // @audit-issue : while unpacking movingAverage value the result could be downcast if value store is greater than type(int128).max ?
377: @> targetArray[_targetArrayLength - 1] = int256(int128(_sourceArray[sourceArrayLengthMinusOne]));
378: }
379: }

POC :

To demonstrate the overflow scenario, consider the following test case:

function test_overflow_underflow() public view {
int256 overflow = int256(type(int128).max);
overflow += 1;
int256 afterDownCast = int256(int128(overflow));
assertEq(type(int128).min , afterDownCast);
int256 underFlow = int256(type(int128).min);
underFlow -= 1;
int256 afterUpCast = int256(int128(underFlow));
assertEq(type(int128).max , afterUpCast);
}

Run with command : forge test --mt test_overflow_underflow -vvv

Impact

While unpacking the movingAverages and calculationMovingAverage arrays, there is a risk of overflow/underflow due to downcasting. If the value stored at the last index exceeds int128 range, it will result in an overflow/underflow, leading to incorrect values being returned.

Tools Used

Manual Review, Unit Testing

Recommendations

The best approach to mitigate overflow/underflow risks is to impose explicit checks when storing data at the lastIndex. This ensures that values do not exceed the limits of int128, preventing incorrect results during unpacking.

diff --git a/pkg/pool-quantamm/contracts/rules/UpdateRule.sol b/pkg/pool-quantamm/contracts/rules/UpdateRule.sol
index b9dbff2..329e248 100644
--- a/pkg/pool-quantamm/contracts/rules/UpdateRule.sol
+++ b/pkg/pool-quantamm/contracts/rules/UpdateRule.sol
@@ -186,6 +186,9 @@ abstract contract UpdateRule is QuantAMMMathGuard, QuantAMMMathMovingAverage, IU
locals.calculationMovingAverage[locals.lastAssetIndex + locals.numberOfAssets] = locals
.currMovingAverage[locals.lastAssetIndex];
}
+ require(
+ (locals.updatedMovingAverage[locals.lastAssetIndex] >= int256(type(int128).min)) && (locals.updatedMovingAverage[locals.lastAssetIndex] <= int256(type(int128).max)),
+ "Last array element overflow");
Updates

Lead Judging Commences

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

Informational or Gas / Admin is trusted / Pool creation is trusted / User mistake / Suppositions

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelyhood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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

Give us feedback!