QuantAMM

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

Weight Vector Index Manipulation in QuantAMM Pool

Summary

There is an issue in the QuantAMMWeightedPool contract where malicious manipulation of the weight vector index during weight updates can lead to incorrect weight calculations and pool imbalances.

Vulnerability Details

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPool.sol#L722

The issue lies in the logic of handling weight vectors in the _splitWeightAndMultipliers function in QuantAMMWeightedPool. The function incorrectly processes weight indices when splitting weights and multipliers for pools with more than 4 tokens.

function _splitWeightAndMultipliers(
int256[] memory weights
) internal pure returns (int256[][] memory splitWeights) {
uint256 tokenLength = weights.length / 2;
splitWeights = new int256[][]();
splitWeights[0] = new int256[]();
// incorrect index handling
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;
// incorrect offset calculation
for (uint i = 0; i < moreThan4Tokens; ) {
uint256 i4 = i + 4;
splitWeights[1][i] = weights[i4];
splitWeights[1][i + moreThan4Tokens] = weights[i4 + tokenLength];
unchecked { i++; }
}
}

The root of the problem is no validation on tokenLength that allows index manipulation, inflexible hardcoded array size 8 assumption, no validation that moreThan4Tokens is not negative. This function is used in setWeights which is the main entry point for updating pool weights.

POC

Add this to QuantAMMWeightedPoolGenericFuzzer.t.sol and run it forge test --match-test testSplitWeightsIndexManipulationVulnerability -vvvv.

function testSplitWeightsIndexManipulationVulnerability() public {
// Setup test params
FuzzParams memory params;
params.numTokens = 8; // Test with 8 tokens for edge cases
// Construct momentum rule
RuleFuzzParams memory ruleParams = RuleFuzzParams({
ruleType: 0,
kappa: _KAPPA
});
params.ruleParams = ruleParams;
// Set pool params
params.poolParams.epsilonMax = _EPSILON_MAX;
params.poolParams.lambda = _LAMBDA;
params.poolParams.maxSwapfee = _MAX_SWAP_FEE_PERCENTAGE;
params.poolParams.absoluteWeightGuardRail = _ABSOLUTE_WEIGHT_GUARD_RAIL;
params.poolParams.maxTradeSizeRatio = _MAX_TRADE_SIZE_RATIO;
params.poolParams.updateInterval = _UPDATE_INTERVAL;
// Create pool
VariationTestVariables memory variables;
variables.params = _createPoolParams(params.numTokens, params.poolParams, params.ruleParams);
address quantAMMWeightedPool = quantAMMWeightedPoolFactory.createWithoutArgs(variables.params);
// Set malicious weights that will cause index manipulation
int256[] memory maliciousWeights = new int256[]();
for (uint i = 0; i < 8; i++) {
maliciousWeights[i] = int256(i);
maliciousWeights[i + 8] = -1; // Negative multipliers
}
// Set weights through update weight runner
vm.prank(address(updateWeightRunner));
QuantAMMWeightedPool(quantAMMWeightedPool).setWeights(
maliciousWeights,
quantAMMWeightedPool,
uint40(block.timestamp + 5)
);
}

Trace:

├─ [18798] QuantAMMWeightedPool::setWeights([0, 1, 2, 3, 4, 5, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1], QuantAMMWeightedPool: [0xBAF398386F89157d964dD7AEf40179BA536E1725], 1682899205 [1.682e9])

Here we can see that the weights array that was sent has negative values ​​(-1) at index 8-15. This should not be valid because weights cannot be negative.

├─ emit WeightsUpdated(poolAddress: QuantAMMWeightedPool: [0xBAF398386F89157d964dD7AEf40179BA536E1725], weights: [0, 1, 2, 3, 4, 5, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1])

The WeightsUpdated event successfully diets with the weights array containing negative values.

The transaction was successfully executed (empty return) without any revert, even though this transaction should have failed.

Impact

  • Pool imbalances

  • Loss of funds for liquidity providers due to mispriced assets

Tools Used

  • Manual review

  • Foundry

Recommendations

Implement proper bounds checking.

function _splitWeightAndMultipliers(
int256[] memory weights
) internal pure returns (int256[][] memory splitWeights) {
uint256 tokenLength = weights.length / 2;
require(tokenLength <= 8, "Max 8 tokens supported");
splitWeights = new int256[][]();
splitWeights[0] = new int256[]();
// Safe index handling with explicit bounds
uint256 firstHalfTokens = tokenLength < 4 ? tokenLength : 4;
for (uint i; i < firstHalfTokens;) {
splitWeights[0][i] = weights[i];
splitWeights[0][i + 4] = weights[i + tokenLength];
unchecked { i++; }
}
if (tokenLength > 4) {
splitWeights[1] = new int256[]();
uint256 remainingTokens = tokenLength - 4;
for (uint i = 0; i < remainingTokens;) {
splitWeights[1][i] = weights[i + 4];
splitWeights[1][i + 4] = weights[i + 4 + tokenLength];
unchecked { i++; }
}
}
// Validate total weights
int256 totalWeight = 0;
for (uint i = 0; i < tokenLength;) {
totalWeight += splitWeights[i < 4 ? 0 : 1][i % 4];
unchecked { i++; }
}
require(totalWeight == 1e18, "Invalid total weight");
}
Updates

Lead Judging Commences

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

invalid_weights_can_be_negative_or_extreme_values

_clampWeights will check that these weights are positive and in the boundaries before writing them in storage.

Support

FAQs

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

Give us feedback!