QuantAMM

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

Double-Write Logic Flaw in _quantAMMUnpack128Matrix() Corrupts Last Element of Odd-Sized Covariance Matrices Causing Incorrect Price Calculations

Summary

In the VectorRuleQuantAMMStorage contract, the _quantAMMUnpack128Matrix function contains a critical flaw that could lead to price calculation errors in the AMM. The function double-writes the last element when unpacking odd-sized covariance matrices:

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

// First write in the main unpacking loop
if (targetIndex < _numberOfAssets) {
targetArray[targetRow][targetIndex] = int256(int128(_sourceArray[i]));
unchecked {
++targetIndex;
}
} else {
unchecked {
++targetRow;
targetIndex = 0;
}
if (targetRow < _numberOfAssets) {
targetArray[targetRow] = new int256[]();
targetArray[targetRow][targetIndex] = int256(int128(_sourceArray[i]));
unchecked {
++targetIndex;
}
}
}
// Second write at the end of the function
if ((_numberOfAssets * _numberOfAssets) % 2 != 0) {
targetArray[_numberOfAssets - 1][_numberOfAssets - 1] = int256(
int128(_sourceArray[_sourceArray.length - 1])
);
}

This double-write is particularly dangerous because this function is called during covariance matrix updates in QuantAMMCovarianceBasedRule:

int256[][] memory intermediateCovarianceState = _quantAMMUnpack128Matrix(
intermediateCovarianceStates[_poolParameters.pool],
locals.n
);

The second write could overwrite the correct value with corrupted data, leading to incorrect covariance calculations. Since these matrices directly influence price calculations through:

locals.intermediateState =
locals.convertedLambda.mul(intermediateCovarianceState[i][j]) +
locals.u[i].mul(locals.v[j]).div(TENPOWEIGHTEEN);

A corrupted matrix element could cause the AMM to calculate incorrect prices or enter an invalid state, potentially leading to financial losses.

The implementation attempts to handle odd-sized matrices in both the main unpacking loop and with a special case at the end, leading to double-writing of the last element.

Scenario:

A pool is using a 3x3 covariance matrix for price calculations. During normal operation, the matrix data needs to be unpacked from storage. The unpacking function first processes all elements through its main loop, correctly writing the last element (position [2][2]) with value X.

However, because 3x3 = 9 elements (odd number), the special case handling at the end of the function triggers. It writes to position [2][2] again, but due to differences in how the int128 conversions handle the packed data in these two different code paths, the final value becomes Y instead of X.

This corrupted matrix element then causes the next price update's covariance calculations to use the wrong value, leading to incorrect prices being quoted by the AMM.

Recommended mitigation steps

Remove the redundant last element assignment and handle odd-sized matrices in the main loop:

function _quantAMMUnpack128Matrix(
int256[] memory _sourceArray,
uint _numberOfAssets
) internal pure returns (int256[][] memory targetArray) {
require(_sourceArray.length * 2 >= numberOfAssets *numberOfAssets, "Source cannot provide target");
targetArray = new int256[][]();
for (uint i; i < _numberOfAssets; i++) {
targetArray[i] = new int256[]();
}
uint totalElements = *numberOfAssets * *numberOfAssets;
uint currentElement = 0;
for (uint i = 0; i < _sourceArray.length; i++) {
// Handle first value
uint row = currentElement / _numberOfAssets;
uint col = currentElement % _numberOfAssets;
targetArray[row][col] = _sourceArray[i] >> 128;
currentElement++;
// Handle second value only if we're not at the last element of an odd-sized matrix
if (currentElement < totalElements &&
!(currentElement == totalElements - 1 && totalElements % 2 != 0)) {
row = currentElement / _numberOfAssets;
col = currentElement % _numberOfAssets;
targetArray[row][col] = int256(int128(_sourceArray[i]));
currentElement++;
}
}
return targetArray;
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
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.