QuantAMM

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

Improper handling of multipliers resulting in incorrect calculations of pool asset allocation

Summary

The setWeights() function is called with three parameters: int256[] calldata _weights, address _poolAddress and uint40 _lastInterpolationTimePossible. If there are more than 4 tokens, the weight and multiplier arrays are split into two smaller arrays. For example, if there are 6 tokens, the first array contains the first 4 tokens and their multipliers ([w1, w2, w3, w4, m1, m2, m3, m4]), and the second array contains the remaining tokens ([w5, w6, 0, 0, m5, m6, 0, 0]). This happens because the _splitWeightAndMultipliers function returns two arrays, each of length 8.

The function packs an array of 32-bit integers into a single 256-bit integer. The packed result is then stored in the variables _normalizedFirstFourWeights and _normalizedSecondFourWeights.

The root cause of the vulnerability lies in the incorrect logic used to retrieve the multiplier values, which in certain cases returns 0 instead of the correct value.

Vulnerability Details

The problem arises because in some cases, the handling of multipliers does not correctly account for the split arrays. When unpacking a 256-bit integer into 8 32-bit integers, and there are fewer than 8 tokens, the second array (_normalizedSecondFourWeights) will contain values like [w5, w6, 0, 0, m5, m6, 0, 0]. The same situation arises when tokens are less than 4. For example [w1, w2, 0, 0, m1, m2, 0, 0]

Problem 1: Incorrect Multiplier Retrieval in _calculateCurrentBlockWeight
The function _calculateCurrentBlockWeight has a critical issue when retrieving multipliers for tokens. The problem arises from an incorrect indexing mechanism, causing invalid values (zeros) to be retrieved instead of the correct multipliers. This issue occurs when the number of tokens is less than 4 or 8, depending on the array being processed. This problem affects the function _getNormalizedWeightPair(), which relies on _calculateCurrentBlockWeight to compute normalized weights for token pairs.


Example Scenario:
Suppose we have 6 tokens, and they are split into two arrays:

First array: [w1, w2, w3, w4, m1, m2, m3, m4]
Second array: [w5, w6, 0, 0, m5, m6, 0, 0]
The _getNormalizedWeightPair function attempts to calculate normalized weights for the 5th and 6th tokens using _calculateCurrentBlockWeight.

Token indices and context:

firstTokenIndex = 0 (5th token)
secondTokenIndex = 1 (6th token)
totalTokensInPacked = 2 (the number of tokens in the second split array)
Problematic Code in _calculateCurrentBlockWeight:
int256 blockMultiplier = tokenWeights[tokenIndex + tokensInTokenWeights];
For the 5th token (firstTokenIndex = 0):
The calculated multiplier index is tokenIndex + tokensInTokenWeights = 0 + 2 = 2
At index 2 in [w5, w6, 0, 0, m5, m6, 0, 0], the value is 0 instead of the correct multiplier m5.
For the 6th token (secondTokenIndex = 1):
The calculated multiplier index is tokenIndex + tokensInTokenWeights = 1 + 2 = 3.
At index 3, the value is again 0 instead of the correct multiplier m6.

Problem 2: Incorrect Multiplier Retrieval in getQuantAMMWeightedPoolDynamicData()
In the function getQuantAMMWeightedPoolDynamicData(), the same issue occurs when accessing the weights and multipliers:

for (uint i; i < tokenCount; i++) {
if (i < 4) {
data.weightsAtLastUpdateInterval[i] = firstFourWeights[i];
data.weightBlockMultipliers[i] = firstFourWeights[i + firstTokenOffset];
} else {
data.weightsAtLastUpdateInterval[i] = secondFourWeights[i - 4];
data.weightBlockMultipliers[i] = secondFourWeights[i - 4 + moreThan4Tokens];
}
}

If tokenCount = 6, the firstFourWeights will be [w1, w2, w3, w4, m1, m2, m3, m4], and the secondFourWeights will be [w5, w6, 0, 0, m5, m6, 0, 0]. At the 4th iteration, the code tries to assign the weights and multipliers incorrectly:

uint256 moreThan4Tokens = tokenCount < 4 ? 0 : tokenCount - 4; // = 2
data.weightsAtLastUpdateInterval[i] = secondFourWeights[i - 4]; // secondFourWeights[0] = w5
data.weightBlockMultipliers[i] = secondFourWeights[i - 4 + moreThan4Tokens]; // secondFourWeights[2] = 0 (wrong), should be m5.

Problem 3: Incorrect Multiplier Index in _getNormalizedWeights() In the _getNormalizedWeights() function, when there are for example only 2 tokens, the unpacked firstFourWeights array is [w1, w2, 0, 0, m1, m2, 0, 0]. When calling calculateBlockNormalisedWeight(), the multiplier is incorrectly retrieved due to the use of tokenIndex = totalTokens, which points to index 2 (value 0) instead of m1.

Note: These functions work correctly only when the number of pool tokens is explicitly 4 or 8

Impact

Incorrect multiplier calculations when the number of tokens is less than 4 or 8. Significant deviation from the intended pool asset allocation ratios, which can cause liquidity imbalances and unexpected losses for users.

Tools Used

Manual review

Recommendations

To fix the first issue in _calculateCurrentBlockWeight(), always add 4 instead of the count of tokens when retrieving the multiplier. For example:
replace 377 line with:
int256 blockMultiplier = tokenWeights[tokenIndex + 4];
This ensures that we are correctly referencing the multipliers for tokens.

To fix the second issue in getQuantAMMWeightedPoolDynamicData() replace 582-588 lines in QuantAMMWeightedPool with:

if (i < 4) {
data.weightsAtLastUpdateInterval[i] = firstFourWeights[i];
data.weightBlockMultipliers[i] = firstFourWeights[i + 4];
} else {
data.weightsAtLastUpdateInterval[i] = secondFourWeights[i - 4];
data.weightBlockMultipliers[i] = secondFourWeights[i];
}

To fix third problem replace 444-517 lines in QuantAMMWeightedPool with:

normalizedWeights[0] = calculateBlockNormalisedWeight(
firstFourWeights[0],
firstFourWeights[4],
timeSinceLastUpdate
);
normalizedWeights[1] = calculateBlockNormalisedWeight(
firstFourWeights[1],
firstFourWeights[4 + 1],
timeSinceLastUpdate
);
if (totalTokens > 2) {
normalizedWeights[2] = calculateBlockNormalisedWeight(
firstFourWeights[2],
firstFourWeights[4 + 2],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
if (totalTokens > 3) {
normalizedWeights[3] = calculateBlockNormalisedWeight(
firstFourWeights[3],
firstFourWeights[4 + 3],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
//avoid unneccessary SLOAD
if (totalTokens == 4) {
return normalizedWeights;
}
int256[] memory secondFourWeights = quantAMMUnpack32(_normalizedSecondFourWeights);
uint256 moreThan4Tokens = totalTokens - 4;
if (totalTokens > 4) {
normalizedWeights[4] = calculateBlockNormalisedWeight(
secondFourWeights[0],
secondFourWeights[4],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
if (totalTokens > 5) {
normalizedWeights[5] = calculateBlockNormalisedWeight(
secondFourWeights[1],
secondFourWeights[4 + 1],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
if (totalTokens > 6) {
normalizedWeights[6] = calculateBlockNormalisedWeight(
secondFourWeights[2],
secondFourWeights[4 + 2],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
if (totalTokens > 7) {
normalizedWeights[7] = calculateBlockNormalisedWeight(
secondFourWeights[3],
secondFourWeights[4 + 3],
timeSinceLastUpdate
);
} else {
return normalizedWeights;
}
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.

Give us feedback!