QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: medium
Valid

Incorrect Weight Tracking in QuantAMM Guard Rails Breaks Pool Weight Invariant

Description

The _clampWeights function in QuantAMMMathGuard.sol contains a critical accounting error in its weight tracking logic. The function fails to properly maintain the remaining weight sum when clamping weights to maximum bounds:

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

int256 sumRemainerWeight = ONE; // Initialized to 1
int256 sumOtherWeights;
for (uint i; i < weightLength; ++i) {
if (_weights[i] < absoluteMin) {
_weights[i] = absoluteMin;
sumRemainerWeight -= absoluteMin; // Subtracts min-clamped weights
} else if (_weights[i] > absoluteMax) {
_weights[i] = absoluteMax;
sumOtherWeights += absoluteMax; // Tracks max-clamped weights
// @audit sumRemainerWeight is never decremented for max-clamped weights
}
}

The issue here is that while the function properly subtracts minimum-clamped weights from sumRemainerWeight, it fails to subtract maximum-clamped weights. This creates an accounting discrepancy where sumRemainerWeight becomes artificially inflated, leading to incorrect proportionalRemainder calculations in the subsequent normalization step.

Impact

This accounting error fundamentally breaks the core invariant of weighted pools - that weights must sum to exactly 1 (ONE). The error in sumRemainerWeight directly affects the calculation of proportionalRemainder:

proportionalRemainder = sumRemainerWeight.div(sumOtherWeights)

When sumRemainerWeight is artificially high due to not accounting for maximum-clamped weights, the resulting proportionalRemainder will be larger than intended. This inflation creates a systemic error in the pool's weight calculations that propagates through multiple layers of the protocol. The incorrect scaling factor leads to weights being adjusted to values that violate the fundamental invariant that all weights must sum to exactly 1 (ONE). This mathematical inconsistency distorts the pool's pricing calculations since prices are derived directly from weight ratios in weighted pools.

The impact affects the protocol's internal accounting system. As weights deviate from their intended values, the relationship between reserves and weights becomes misaligned, creating discrepancies in the pool's core trading function. These pricing inefficiencies open up arbitrage vectors where traders can exploit the mathematical inconsistency between the pool's actual state and its intended state.

What makes this issue particularly severe is its persistent and compounding nature. Every rebalancing operation that involves maximum weight clamping introduces additional error into the system. Since the protocol lacks any mechanism to detect or correct these accumulating discrepancies, the mathematical integrity of the pool's state can deteriorate over time, potentially leading to increasingly severe pricing distortions with each subsequent rebalancing.

Recommended Mitigation Steps

function _clampWeights(
int256[] memory _weights,
int256 _absoluteWeightGuardRail
) internal pure returns (int256[] memory) {
uint weightLength = _weights.length;
if (weightLength == 1) return _weights;
int256 absoluteMin = _absoluteWeightGuardRail;
int256 absoluteMax = ONE - (PRBMathSD59x18.fromInt(int256(weightLength - 1)).mul(_absoluteWeightGuardRail));
int256 sumRemainerWeight = ONE;
int256 sumOtherWeights;
for (uint i; i < weightLength; ++i) {
if (_weights[i] < absoluteMin) {
_weights[i] = absoluteMin;
sumRemainerWeight -= absoluteMin;
} else if (_weights[i] > absoluteMax) {
_weights[i] = absoluteMax;
sumRemainerWeight -= absoluteMax; // Subtract max-clamped weights
sumOtherWeights += absoluteMax;
}
}
if (sumOtherWeights != 0) {
int256 proportionalRemainder = sumRemainerWeight.div(sumOtherWeights);
for (uint i; i < weightLength; ++i) {
if (_weights[i] != absoluteMin) {
_weights[i] = _weights[i].mul(proportionalRemainder);
}
}
}
return _weights;
}

The fix properly decrements sumRemainerWeight for maximum-clamped weights while maintaining the existing logic flow. This ensures accurate weight accounting and preserves the pool's weight sum invariant.

Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_clampWeights_normalizeWeightUpdates_incorrect_calculation_of_sumOtherWeights_proportionalRemainder

Likelihood: Medium/High, when a weight is above absoluteMax. Impact: Low/Medium, weights deviate much faster, and sum of weights also.

Support

FAQs

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