QuantAMM

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

Upon pool creation there might be a revert due to improper array handling

Summary

The _splitWeightAndMultipliers function assumes that the input array weights has an even length and can be evenly divided into two halves. When weights.length is odd (e.g., weights.length = 9), the function exhibits logical inconsistencies, leaving splitWeights[1] uninitialized (filled with zeros) and potentially causing issues downstream. This is not explicitly validated or handled in the code.

Vulnerability Details

The main issue with weights.length = 9 is that the function does not handle cases where weights.length is not evenly divisible by 2. It assumes weights can always be split into two equal halves, which is not true for odd-length arrays.

function _setInitialWeights(int256[] memory _weights) internal {
require(_normalizedFirstFourWeights == 0, "init");
require(_normalizedSecondFourWeights == 0, "init");
InputHelpers.ensureInputLengthMatch(_totalTokens, _weights.length);
int256 normalizedSum;
int256[] memory _weightsAndBlockMultiplier = new int256[]();
for (uint i; i < _weights.length; ) {
if (_weights[i] < int256(uint256(absoluteWeightGuardRail))) {
revert MinWeight();
}
_weightsAndBlockMultiplier[i] = _weights[i];
normalizedSum += _weights[i];
//Initially register pool with no movement, first update will come and set block multiplier.
_weightsAndBlockMultiplier[i + _weights.length] = int256(0);
unchecked {
++i;
}
}
// Ensure that the normalized weights sum to ONE
if (uint256(normalizedSum) != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
if (_weightsAndBlockMultiplier.length > 8) {
int256[][] memory splitWeights = _splitWeightAndMultipliers(_weightsAndBlockMultiplier);
_normalizedFirstFourWeights = quantAMMPack32Array(splitWeights[0])[0];
_normalizedSecondFourWeights = quantAMMPack32Array(splitWeights[1])[0];
} else {
_normalizedFirstFourWeights = quantAMMPack32Array(_weightsAndBlockMultiplier)[0];
}
//struct allows one SSTORE
poolSettings.quantAMMBaseInterpolationDetails = QuantAMMBaseInterpolationVariables({
lastPossibleInterpolationTime: uint40(block.timestamp), //given muliplier is 0 on start
lastUpdateIntervalTime: uint40(block.timestamp)
});
emit WeightsUpdated(address(this), _weights);
}
/// @notice Initialize the pool
/// @param _initialWeights Initial weights to set
/// @param _poolSettings Settings for the pool
/// @param _initialMovingAverages Initial moving averages to set
/// @param _initialIntermediateValues Initial intermediate values to set for a given rule
/// @param _oracleStalenessThreshold Maximum amount of seconds that can pass between two oracle updates
function initialize(
int256[] memory _initialWeights,
PoolSettings memory _poolSettings,
int256[] memory _initialMovingAverages,
int256[] memory _initialIntermediateValues,
uint _oracleStalenessThreshold
) public initializer {
require(_poolSettings.assets.length > 0 && _poolSettings.assets.length == _initialWeights.length, "INVASSWEIG"); //Invalid assets / weights array
assets = _poolSettings.assets;
poolSettings.assets = new address[](_poolSettings.assets.length);
for (uint i; i < _poolSettings.assets.length; ) {
poolSettings.assets[i] = address(_poolSettings.assets[i]);
unchecked {
++i;
}
}
oracleStalenessThreshold = _oracleStalenessThreshold;
updateInterval = _poolSettings.updateInterval;
_setRule(_initialWeights, _initialIntermediateValues, _initialMovingAverages, _poolSettings);
@> _setInitialWeights(_initialWeights);
}
function _setInitialWeights(int256[] memory _weights) internal {
require(_normalizedFirstFourWeights == 0, "init");
require(_normalizedSecondFourWeights == 0, "init");
InputHelpers.ensureInputLengthMatch(_totalTokens, _weights.length);
int256 normalizedSum;
int256[] memory _weightsAndBlockMultiplier = new int256[]();
for (uint i; i < _weights.length; ) {
if (_weights[i] < int256(uint256(absoluteWeightGuardRail))) {
revert MinWeight();
}
_weightsAndBlockMultiplier[i] = _weights[i];
normalizedSum += _weights[i];
//Initially register pool with no movement, first update will come and set block multiplier.
_weightsAndBlockMultiplier[i + _weights.length] = int256(0);
unchecked {
++i;
}
}
// Ensure that the normalized weights sum to ONE
if (uint256(normalizedSum) != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
if (_weightsAndBlockMultiplier.length > 8) {
@> int256[][] memory splitWeights = _splitWeightAndMultipliers(_weightsAndBlockMultiplier);
_normalizedFirstFourWeights = quantAMMPack32Array(splitWeights[0])[0];
_normalizedSecondFourWeights = quantAMMPack32Array(splitWeights[1])[0];
} else {
_normalizedFirstFourWeights = quantAMMPack32Array(_weightsAndBlockMultiplier)[0];
}
//struct allows one SSTORE
poolSettings.quantAMMBaseInterpolationDetails = QuantAMMBaseInterpolationVariables({
lastPossibleInterpolationTime: uint40(block.timestamp), //given muliplier is 0 on start
lastUpdateIntervalTime: uint40(block.timestamp)
});
emit WeightsUpdated(address(this), _weights);
}
function _splitWeightAndMultipliers(
int256[] memory weights
) internal pure returns (int256[][] memory splitWeights) {
uint256 tokenLength = weights.length / 2;
splitWeights = new int256[][]();
splitWeights[0] = new int256[]();
for (uint i; i < 4; ) {
splitWeights[0][i] = weights[i];
splitWeights[0][i + 4] = weights[i + tokenLength];
unchecked {
i++;
}
}
splitWeights[1] = new int256[]();
uint256 moreThan4Tokens = tokenLength - 4;
for (uint i = 0; i < moreThan4Tokens; ) {
uint256 i4 = i + 4;
splitWeights[1][i] = weights[i4];
splitWeights[1][i + moreThan4Tokens] = weights[i4 + tokenLength];
unchecked {
i++;
}
}
}

When weights.length = 9, the function assumes weights are evenly divisible into two halves of size tokenLength. However:

  • The last index of weights is weights[8].

  • tokenLength = 9 / 2 = 4 (integer division truncates the decimal).
    Calculate moreThan4Tokens:

uint256 moreThan4Tokens = tokenLength - 4;

moreThan4Tokens = 4 - 4 = 0.
Second Loop:

for (uint i = 0; i < moreThan4Tokens; ) {
// This loop does not execute because moreThan4Tokens = 0.
}

Impact

Users might be able to create pool and updateRunner might be able to update weight

Tools Used

Manual review

Recommendations

Handle odd-length explicitly and refactor the second loop

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
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.