QuantAMM

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

The `QuantAMMMathGuard::_normalizeWeightUpdates` function does not check whether the final sum of the normalized weights is equal to ONE.

Summary

The QuantAMMMathGuard::_normalizeWeightUpdateslack a check for the final sum of weights equal to ONEor not.

Vulnerability Details

The normalizeWeightUpdates calculates the normalized weights using absolute changes from the previous weights and _epsilonMax.

The NatSpec of the function states that the weights are normalized to ensure their sum is equal to ONE (1e18). However, the function does not include any check to verify that the final sum of the weights equals ONE.

If maxAbsChange is less than _epsilonMax, then no rescaling calculation will be done. Additionally, there is no check to ensure that the sum of the weights is equal to ONE, which could result in incorrect weight values being used in further calculations.

https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/rules/base/QuantammMathGuard.sol#L72

@>> ///@notice Normalizes the weights to ensure that the sum of the weights is equal to 1
///@param _prevWeights Previous weights
///@param _newWeights New weights
///@param _epsilonMax Maximum allowed change in weights per update step (epsilon) in the QuantAMM whitepaper
function _normalizeWeightUpdates(
int256[] memory _prevWeights,
int256[] memory _newWeights,
int256 _epsilonMax
) internal pure returns (int256[] memory) {
unchecked {
int256 maxAbsChange = _epsilonMax;
for (uint i; i < _prevWeights.length; ++i) {
int256 absChange;
if (_prevWeights[i] > _newWeights[i]) {
absChange = _prevWeights[i] - _newWeights[i];
} else {
absChange = _newWeights[i] - _prevWeights[i];
}
if (absChange > maxAbsChange) {
maxAbsChange = absChange;
}
}
int256 newWeightsSum;
if (maxAbsChange > _epsilonMax) {
int256 rescaleFactor = _epsilonMax.div(maxAbsChange);
for (uint i; i < _newWeights.length; ++i) {
int256 newDelta = (_newWeights[i] - _prevWeights[i]).mul(rescaleFactor);
_newWeights[i] = _prevWeights[i] + newDelta;
newWeightsSum += _newWeights[i];
}
} else {
for (uint i; i < _newWeights.length; ++i) {
newWeightsSum += _newWeights[i];
}
}
// There might a very small (1e-18) rounding error, add this to the first element.
// In some edge cases, this might break a guard rail, but only by 1e-18, which is modelled to be acceptable.
_newWeights[0] = _newWeights[0] + (ONE - newWeightsSum);
}
return _newWeights;
}

Impact

Without the check, the incorrectly calculated weights will be passed, and further calculations will be based on those incorrect values, leading to errors and unintended behavior in the contract.

Tools Used

Manual review

Recommendations

Following check can be placed :-

///@notice Normalizes the weights to ensure that the sum of the weights is equal to 1
///@param _prevWeights Previous weights
///@param _newWeights New weights
///@param _epsilonMax Maximum allowed change in weights per update step (epsilon) in the QuantAMM whitepaper
function _normalizeWeightUpdates(
int256[] memory _prevWeights,
int256[] memory _newWeights,
int256 _epsilonMax
) internal pure returns (int256[] memory) {
unchecked {
int256 maxAbsChange = _epsilonMax;
for (uint i; i < _prevWeights.length; ++i) {
int256 absChange;
if (_prevWeights[i] > _newWeights[i]) {
absChange = _prevWeights[i] - _newWeights[i];
} else {
absChange = _newWeights[i] - _prevWeights[i];
}
if (absChange > maxAbsChange) {
maxAbsChange = absChange;
}
}
int256 newWeightsSum;
if (maxAbsChange > _epsilonMax) {
int256 rescaleFactor = _epsilonMax.div(maxAbsChange);
for (uint i; i < _newWeights.length; ++i) {
int256 newDelta = (_newWeights[i] - _prevWeights[i]).mul(rescaleFactor);
_newWeights[i] = _prevWeights[i] + newDelta;
newWeightsSum += _newWeights[i];
}
} else {
for (uint i; i < _newWeights.length; ++i) {
newWeightsSum += _newWeights[i];
}
}
+ require(newWeightsSum == ONE, "Incorrect calculation of normalized weights.");
// There might a very small (1e-18) rounding error, add this to the first element.
// In some edge cases, this might break a guard rail, but only by 1e-18, which is modelled to be acceptable.
_newWeights[0] = _newWeights[0] + (ONE - newWeightsSum);
}
return _newWeights;
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

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.

Give us feedback!