QuantAMM

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

Lack of Weight Value Constraints (No Enforced Sum = 1 and No Prevention of Negative/Excessive Weights)

Summary

During pool weight updates, the current logic does not enforce that the sum of weights must equal 1 (sum(weights) = 1), nor does it clearly prevent the scenario of negative weights or weights exceeding 1.0 in the normal update flow (via performUpdate). This can cause the pool to operate incorrectly if any weight values lie outside the expected range.

Vulnerability Details

1, No Check for Sum of Weights = 1

In the initial setup function, _setInitialWeights(...), of the QuantAMMWeightedPool contract, we can see:

function _setInitialWeights(int256[] memory _weights) internal {
...
int256 normalizedSum;
for (uint i; i < _weights.length; ) {
if (_weights[i] < int256(uint256(absoluteWeightGuardRail))) {
revert MinWeight();
}
normalizedSum += _weights[i];
unchecked {
++i;
}
}
// Ensure that the normalized weights sum to ONE
if (uint256(normalizedSum) != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
...
}
  • the contract does check if (uint256(normalizedSum) != FixedPoint.ONE) revert NormalizedWeightInvariant(); to ensure the total weights = 1 (in the form of 1e18).

  • However, this check only occurs once at initialization (within initialize(...)).

After the pool is set up, weights are updated through the function

function setWeights(
int256[] calldata _weights,
address _poolAddress,
uint40 _lastInterpolationTimePossible
) external override {
require(msg.sender == address(updateWeightRunner), "ONLYUPDW");
require(_weights.length == _totalTokens * 2, "WLDL"); // weight length different
if (_weights.length > 8) {
int256[][] memory splitWeights = _splitWeightAndMultipliers(_weights);
_normalizedFirstFourWeights = quantAMMPack32Array(splitWeights[0])[0];
_normalizedSecondFourWeights = quantAMMPack32Array(splitWeights[1])[0];
} else {
_normalizedFirstFourWeights = quantAMMPack32Array(_weights)[0];
}
// Update interpolation times
poolSettings.quantAMMBaseInterpolationDetails = QuantAMMBaseInterpolationVariables({
lastPossibleInterpolationTime: _lastInterpolationTimePossible,
lastUpdateIntervalTime: uint40(block.timestamp)
});
emit WeightsUpdated(_poolAddress, _weights);
}
  • We can see no requirement to ensure that ∑(weights)=1 in this snippet.

  • The function only checks the array length (_weights.length == _totalTokens * 2), then packs/unpacks the data into _normalizedFirstFourWeights and _normalizedSecondFourWeights.

  • There is no require(...) or other logic to verify that the sum of the first half of the _weights array equals 1.

As a result, if external logic (usually UpdateWeightRunner and a “rule” contract) passes in a weight array that does not sum to 1, the pool still accepts and stores those invalid weights.

In the “runner” contract (_UpdateWeightRunner_), the main update flow is:

performUpdate(_pool)
-> _performUpdateAndGetData(_pool, settings)
-> _getUpdatedWeightsAndOracleData(_pool, currentWeights, ruleSettings)
-> rules[_pool].CalculateNewWeights(...)
-> _calculateMuliplierAndSetWeights(...)
-> IQuantAMMWeightedPool(_pool).setWeights(...);

There is no line of code verifying `∑(updatedWeights)=1.`

In UpdateWeightRunner, the “breakglass” function (setWeightsManually) only enforces that weights cannot be negative or exceed 1:

function setWeightsManually(
int256[] calldata _weights,
address _poolAddress,
uint40 _lastInterpolationTimePossible,
uint _numberOfAssets
) external {
...
// Checking weight[i] > 0 and weight[i] < 1e18
for (uint i; i < _weights.length; i++) {
if (i < _numberOfAssets) {
require(_weights[i] > 0, "Negative weight not allowed");
require(_weights[i] < 1e18, "greater than 1 weight not allowed");
}
}
...
IQuantAMMWeightedPool(_poolAddress).setWeights(_weights, _poolAddress, _lastInterpolationTimePossible);
}

There is still no step to calculate “sum of _weights[i]” and require that the total equals 1.

This function prevents negative or >1.0 weights, but does not force “sum(weights) = 1.”

2, No Prevention of Negative or Excessively Large Weights in Normal Updates

  • In the normal update flow (performUpdate -> CalculateNewWeights -> setWeights), there is no check to confirm that “weight >= 0” or “weight <= 1e18.”

  • Only the “breakglass” function setWeightsManually(...) includes require(weight[i] > 0) and require(weight[i] < 1e18).

  • Consequently, regular updates are still vulnerable to inputting negative weights or excessively large weights, which may cause reverts or break interpolation calculations.

Impact

  • If the total weight does not equal 1, the pool’s invariant and asset allocations become inaccurate, leading to value losses or pool downtime.

  • Negative or overly large weights can cause swap or balance/invariant calculations to fail, revert, or produce incorrect results, harming users.

Tools Used

Manual

Recommendations

Enforce Sum(weights) = 1 During the Update Flow

  • Before invoking pool.setWeights(...), add code to:
    - Calculate the sum of the new weights and compare it with 1 (1e18), allowing for a small tolerance.
    - Revert the transaction if the sum is incorrect.

**Apply Checks for Weights ≥0 and ≤1 ** (or ≤ 1e18) in Normal Updates

  • Validate the “updatedWeights” returned by CalculateNewWeights(...) so that each weight is ≥ 0 and ≤ 1e18.

Updates

Lead Judging Commences

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

invalid_sum_of_weights_can_exceeds_one_no_guard

According the sponsor and my understanding, sum of weights does not have to be exactly 1 to work fine. So no real impact here. Please provide a PoC showing a realistic impact if you disagree. This PoC cannot contains negative weights because they will be guarded per clampWeights.

Support

FAQs

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