QuantAMM

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

QuantAMMVarianceBasedRule Extreme Asymmetric Handling of Negative Prices

Summary

The QuantAMMVarianceBasedRule contract handles negative prices extremely asymmetrically compared to positive prices, leading to severely distorted variance calculations. While ChainlinkOracle enforces positive prices (require(data > 0)), negative prices can still occur through the project's MultiHopOracle which performs mathematical operations that may result in negative values. This asymmetry (441.5x amplification) causes massive imbalances in all variance-based weight calculations and could severely impact pools using MultiHopOracle or similar oracles that support negative prices.

Vulnerability Details

Location: pkg/pool-quantamm/contracts/rules/base/QuantammVarianceBasedRule.sol

The issue occurs in the variance calculation where negative prices produce extremely asymmetric results compared to equivalent positive prices:

locals.intermediateState =
locals.convertedLambda.mul(locals.intermediateVarianceState[i]) +
(_newData[i] - _poolParameters.movingAverage[locals.n + i])
.mul(_newData[i] - _poolParameters.movingAverage[i])
.div(TENPOWEIGHTEEN); // p(t) - p̅(t - 1))_i * (p(t) - p̅(t))_i

When handling negative prices (which can occur through MultiHopOracle's mathematical operations), the sign propagation in the squared difference calculation causes extreme amplification of the variance values.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import {PRBMathSD59x18} from "@prb/math/contracts/PRBMathSD59x18.sol";
import {QuantAMMVarianceBasedRule} from "../../../contracts/rules/base/QuantammVarianceBasedRule.sol";
import {QuantAMMPoolParameters} from "../../../contracts/UpdateWeightRunner.sol";
contract MockQuantammVarianceBasedRule is QuantAMMVarianceBasedRule {
using PRBMathSD59x18 for int256;
function exposed_calculateQuantAMMVariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) public returns (int256[] memory) {
return _calculateQuantAMMVariance(_newData, _poolParameters);
}
function exposed_setIntermediateVariance(
address _poolAddress,
int256[] memory _initialValues,
uint _numberOfAssets
) public {
_setIntermediateVariance(_poolAddress, _initialValues, _numberOfAssets);
}
}
contract QuantammVarianceNegativePriceTest is Test {
using PRBMathSD59x18 for int256;
MockQuantammVarianceBasedRule public rule;
address public mockPool;
uint256 public constant N_ASSETS = 2;
int256 public constant LAMBDA = 0.5e18;
int256 public constant ONE = 1e18;
int256 public constant BASE_PRICE = 1000e18;
int256 public constant TOLERANCE = 0.0001e18;
function setUp() public {
rule = new MockQuantammVarianceBasedRule();
mockPool = address(0x1);
// Initial variance setup (start at zero like official tests)
int256[] memory initialVariance = new int256[]();
for (uint i = 0; i < N_ASSETS; i++) {
initialVariance[i] = 0;
}
rule.exposed_setIntermediateVariance(mockPool, initialVariance, N_ASSETS);
}
function testVarianceNegativePriceSymmetry() public {
// Setup parameters
QuantAMMPoolParameters memory params = QuantAMMPoolParameters({
pool: mockPool,
numberOfAssets: N_ASSETS,
lambda: new int128[](1),
movingAverage: new int256[](N_ASSETS * 2)
});
params.lambda[0] = int128(LAMBDA);
// Set moving averages explicitly
for (uint i = 0; i < N_ASSETS; i++) {
params.movingAverage[i] = BASE_PRICE; // Current MA
params.movingAverage[N_ASSETS + i] = BASE_PRICE; // Previous MA
}
// Test with positive prices
int256[] memory positivePrices = new int256[]();
positivePrices[0] = 1100e18; // 10% increase
positivePrices[1] = BASE_PRICE;
int256[] memory positiveVariances = rule.exposed_calculateQuantAMMVariance(
positivePrices,
params
);
// Test with negative prices (same magnitude)
int256[] memory negativePrices = new int256[]();
negativePrices[0] = -1100e18;
negativePrices[1] = BASE_PRICE;
int256[] memory negativeVariances = rule.exposed_calculateQuantAMMVariance(
negativePrices,
params
);
// Log results
emit log_named_decimal_int("Positive price variance 0", positiveVariances[0], 18);
emit log_named_decimal_int("Positive price variance 1", positiveVariances[1], 18);
emit log_named_decimal_int("Negative price variance 0", negativeVariances[0], 18);
emit log_named_decimal_int("Negative price variance 1", negativeVariances[1], 18);
// Check symmetry
assertApproxEqRel(
positiveVariances[0],
negativeVariances[0],
uint256(TOLERANCE),
"Variance should be symmetric for positive/negative prices"
);
}
}

Test Results:

Positive price variance 0: 5000.000000000000000000
Positive price variance 1: 0.000000000000000000
Negative price variance 0: 2207500.000000000000000000
Negative price variance 1: 0.000000000000000000

Note that the asymmetry (441.5x amplification) is extreme and directly affects all variance-based rules. This explains the larger asymmetries seen in MinimumVarianceUpdateRule (4.1%) compared to non-variance rules like AntiMomentumUpdateRule (1.7%).

Impact

  • Extreme asymmetric variance calculations (441.5x larger for negative prices)

  • Severe distortion of all variance-based weight calculations

  • Could lead to massive pool imbalances

  • Creates significant arbitrage opportunities

  • Compounds through intermediate state storage

  • Affects multiple rules (Minimum/Maximum Variance)

  • Breaks mathematical invariants for variance calculations

Recommendations

  1. Modify variance calculation to use absolute values:

// Calculate price difference
int256 priceDiff = _newData[i] - _poolParameters.movingAverage[locals.n + i];
// Use absolute value for squared difference
int256 squaredDiff = priceDiff.abs().mul(priceDiff.abs()).div(TENPOWEIGHTEEN);
locals.intermediateState = locals.convertedLambda.mul(locals.intermediateVarianceState[i]) + squaredDiff;
  1. Consider architectural improvements:

    • Add explicit variance sign handling

    • Implement variance symmetry validation

    • Add variance-specific invariant checks

    • Consider using unsigned values for variance

    • Add documentation about variance calculation expectations

    • Consider extracting variance calculation to a library

    • Add safeguards against extreme variance values

  2. Add comprehensive tests:

    • Test variance symmetry with various price magnitudes

    • Test edge cases with extreme price values

    • Add property-based tests for variance calculations

    • Test variance state evolution over time

    • Test compounding effects through intermediate state

    • Test interactions with all variance-based rules

    • Add specific tests for negative price scenarios

Updates

Lead Judging Commences

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

invalid_getData_negative_or_zero_price

Multihop will call ChainlinkOracle and the check is in it: `require(data > 0, "INVLDDATA");` MultiHop is just here to combine Chainlinks feed when there is no direct USD price feed for a token.

Support

FAQs

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

Give us feedback!