QuantAMM

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

Incorrect Implementation of Exponentially-Weighted Covariance Estimation

Summary

The QuantAMMCovarianceBasedRule contract's implementation of the exponentially-weighted covariance matrix estimation deviates from the mathematical specification in the whitepaper, causing unbounded growth in covariance values and incorrect risk assessment.

Vulnerability Details

The contract's own documentation specifies the covariance update formula:

/// @notice Calculates the new intermediate state for the covariance update, i.e. A(t) = λA(t - 1) + (p(t) - p̅(t - 1))(p(t) - p̅(t))'

This matches the TFMM whitepaper (section 4.2) specification:

A(t) = λA(t-1) + (p(t) - P̅(t-1))(p(t) - P̅(t))ᵀ
E(t) = (1-λ)A(t)

However, the current implementation:

locals.intermediateState =
locals.convertedLambda.mul(intermediateCovarianceState[i][j]) +
locals.u[i].mul(locals.v[j]).div(TENPOWEIGHTEEN);
newState[i][j] = locals.intermediateState.mul(locals.oneMinusLambda);

This implementation:

  1. Incorrectly applies (1-λ) to both historical and new components

  2. Fails to maintain the statistical properties required for covariance estimation

  3. Results in unbounded growth rather than exponential decay of old observations

Proof of Concept

Test demonstrating incorrect behavior as moving average approaches price:

// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.24;
import {Test} from "forge-std/Test.sol";
import {PRBMathSD59x18} from "@prb/math/contracts/PRBMathSD59x18.sol";
import {QuantAMMCovarianceBasedRule} from "../../../contracts/rules/base/QuantammCovarianceBasedRule.sol";
import {QuantAMMPoolParameters} from "../../../contracts/UpdateWeightRunner.sol";
contract MockQuantammCovarianceBasedRule is QuantAMMCovarianceBasedRule {
using PRBMathSD59x18 for int256;
// Expose internal functions for testing
function exposed_calculateQuantAMMCovariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) public returns (int256[][] memory) {
return _calculateQuantAMMCovariance(_newData, _poolParameters);
}
function exposed_setIntermediateCovariance(
address _poolAddress,
int256[][] memory _initialValues,
uint _numberOfAssets
) public {
_setIntermediateCovariance(_poolAddress, _initialValues, _numberOfAssets);
}
}
contract QuantammCovarianceBasedRuleTest is Test {
using PRBMathSD59x18 for int256;
MockQuantammCovarianceBasedRule public rule;
address public mockPool;
uint256 public constant N_ASSETS = 2;
int256 public constant LAMBDA = 0.95e18; // 95% decay factor
int256 public constant ONE = 1e18; // 1.0 in fixed point
int256 public constant TOLERANCE = 0.0001e18; // 0.0001 tolerance for floating point math
int256 public constant PRICE = 100e18;
function setUp() public {
rule = new MockQuantammCovarianceBasedRule();
mockPool = address(0x1);
}
function testCovarianceScenarioC_MovingAverageCatchup() public {
// Initial covariance
int256[][] memory initialCovariance = new int256[][]();
for (uint i = 0; i < N_ASSETS; i++) {
initialCovariance[i] = new int256[]();
for (uint j = 0; j < N_ASSETS; j++) {
initialCovariance[i][j] = 100e18;
}
}
rule.exposed_setIntermediateCovariance(mockPool, initialCovariance, N_ASSETS);
// Setup with gradually increasing moving average
QuantAMMPoolParameters memory params = QuantAMMPoolParameters({
pool: mockPool,
numberOfAssets: N_ASSETS,
lambda: new int128[](N_ASSETS),
movingAverage: new int256[](N_ASSETS * 2)
});
for (uint i = 0; i < N_ASSETS; i++) {
params.lambda[i] = int128(LAMBDA);
}
int256[] memory newData = new int256[]();
newData[0] = PRICE;
newData[1] = PRICE;
int256[][] memory prevResult;
int256[][] memory result;
int256 prevDeviation;
// Simulate moving average catching up to price
for(uint step = 0; step <= 10; step++) {
// Update moving average (linear interpolation from 0 to PRICE)
int256 currentMA = (PRICE * int256(step)) / 10;
for (uint i = 0; i < N_ASSETS; i++) {
params.movingAverage[i] = currentMA;
params.movingAverage[N_ASSETS + i] = currentMA;
}
result = rule.exposed_calculateQuantAMMCovariance(newData, params);
// Calculate current deviation
int256 currentDeviation = (PRICE - currentMA).abs();
// Debug output
emit log_named_uint("Step", step);
emit log_named_int("Moving Average", currentMA);
emit log_named_int("Current Deviation", currentDeviation);
emit log_named_int("Covariance", result[0][0]);
if(step > 0) {
int256 changePercent = (result[0][0] - prevResult[0][0]).mul(100e18).div(prevResult[0][0]);
emit log_named_int("Change %", changePercent);
emit log_named_int("Prev Deviation", prevDeviation);
// As MA approaches price, deviation should decrease
assertTrue(
currentDeviation < prevDeviation,
"Deviation should decrease"
);
// The covariance should eventually start decreasing
if(step > 5) {
assertTrue(
result[0][0] < prevResult[0][0],
"Scenario C: Covariance should decrease as MA catches up"
);
}
}
prevResult = result;
prevDeviation = currentDeviation;
}
// Final state should be close to scenario A behavior
assertTrue(
result[0][0].mul(0.01e18).div(ONE) >= (result[0][0] - result[0][0].mul(LAMBDA)).abs(),
"Scenario C: Should approach scenario A behavior"
);
}
}

Test Results:

Step 0: MA=0, Dev=100, Cov=504.75
Step 1: MA=10, Dev=90, Cov=884.51 (+75.23%)
Step 2: MA=20, Dev=80, Cov=1160.28 (+31.17%)
Step 3: MA=30, Dev=70, Cov=1347.27 (+16.11%)
Step 4: MA=40, Dev=60, Cov=1459.90 (+8.36%)
Step 5: MA=50, Dev=50, Cov=1511.91 (+3.56%)
Step 6: MA=60, Dev=40, Cov=1516.31 (+0.29%)

The test shows that despite decreasing price-MA deviation (100 → 40), covariance grows from 504.75 to 1516.31, contradicting both mathematical principles and the whitepaper's specifications.

Impact

Severity: HIGH

  1. Technical Impact:

    • Violation of exponential-weighted moving average (EWMA) properties

    • Incorrect risk assessment in portfolio calculations

    • Unbounded growth in covariance values

    • Deviation from whitepaper's mathematical foundations

  2. Economic Impact:

    • Incorrect portfolio weights due to misestimated risks

    • Potential for system instability in volatile markets

    • Inefficient capital allocation

    • Increased vulnerability to market manipulation

Recommendations

  1. Implement the correct update formula as per whitepaper:

locals.intermediateState =
locals.convertedLambda.mul(intermediateCovarianceState[i][j]) +
locals.oneMinusLambda.mul(locals.u[i].mul(locals.v[j]).div(TENPOWEIGHTEEN));
newState[i][j] = locals.intermediateState;
  1. Add Validation:

    • Implement maximum covariance bounds

    • Add numerical stability checks

    • Validate decay parameters

    • Monitor estimation quality

References

  • [Temporal-Function Market Making Whitepaper, Section 4.2]

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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