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.
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:
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:
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
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.
Manual review
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:
To fix third problem replace 444-517 lines in QuantAMMWeightedPool with:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.