QuantAMM

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

Incorrect Implementation of the Covariance Matrix Update Formula in QuantAMMCovarianceBasedRule

Summary

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

There's an issue in how the covariance matrix is being updated in the code. Specifically, the implementation deviates from the standard Exponential Weighted Moving Average (EWMA) method, leading to incorrect calculations.

The issue in the covariance matrix update arises from:

  • Incorrect Scaling: Multiplying the entire update by ( 1 - \lambda ) instead of only the new data term.

  • Inconsistent Deviations: Using different means for deviations in the outer product.

Vulnerability Details

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

for (uint i; i < locals.n; ) {
unchecked {
locals.convertedLambda = int256(_poolParameters.lambda[i]);
locals.oneMinusLambda = ONE - locals.convertedLambda;
}
newState[i] = new int256[]();
for (uint j; j < locals.n; ) {
//Better to do this item by item saving 2 SSTORES by not changing the length
// locals.u and locals.v are in 18 decimals, need to scale back the result to 18 decimals
locals.intermediateState =
locals.convertedLambda.mul(intermediateCovarianceState[i][j]) +
locals.u[i].mul(locals.v[j]).div(TENPOWEIGHTEEN); // i is the row, j the column -> u_i * v_j the result of the outer product.
newState[i][j] = locals.intermediateState.mul(locals.oneMinusLambda);
intermediateCovarianceState[i][j] = locals.intermediateState;
unchecked {
++j;
}
}
unchecked {
++i;
}
}

The above code is multiplying the entire intermediate result by ( 1 - \lambda ) in newState[i][j] = locals.intermediateState.mul(locals.oneMinusLambda);. This scales down both the recursive term ( \lambda \Sigma_{t-1}[i][j] ) and the new data term, which is incorrect.

The entire intermediate state locals.intermediateState is multiplied by ( 1 - \lambda ):

[ \text{newState}[i][j] = ( \lambda \Sigma_{t-1} + (p_t - \mu_{t-1})(p_t - \mu_t)^\top ) \times (1 - \lambda) ]

This means both the recursive term ( \lambda \Sigma_{t-1} ) and the new information from the outer product are being scaled by ( 1 - \lambda ), which is incorrect according to the standard EWMA formula.

Also typically, the deviations should be calculated using the same mean ( \mu_t ) at time ( t ):

[ \text{Deviations} = p_t - \mu_t ]

However, the code uses ( \mu_{t-1} ) for locals.u and ( \mu_t ) for locals.v. This inconsistency will lead to asymmetry in the covariance matrix and incorrect covariance values.

By scaling the entire intermediate state by ( 1 - \lambda ), you're effectively applying ( \lambda (1 - \lambda) ) to ( \Sigma_{t-1}[i][j] ). This results in the influence of past data diminishing faster than intended.

The covariance matrix loses historical information too quickly. The model becomes over-sensitive to recent data.

Impact

Incorrect covariance values will lead to underestimating or overestimating portfolio risk. Asset weights derived from faulty covariance matrices will negatively affect portfolio performance. The covariance matrix will not accurately capture the relationships between assets, which can adversely affect downstream calculations that rely on it.

Tools Used

Manual Review

Recommendations

  1. Correct the Covariance Update Formula:

    Implement the standard EWMA covariance update formula, ensuring that only the outer product is scaled by ( 1 - \lambda ):

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

    Here, locals.deviation[i] = _newData[i] - _poolParameters.movingAverage[i]; (i.e., deviations computed using ( \mu_t )).

  2. Use Consistent Means for Deviations:

    Ensure that both deviations locals.u and locals.v use the same mean at time ( t ):

    locals.deviation[i] = _newData[i] - _poolParameters.movingAverage[i]; // p(t) - μ(t)
  3. Remove the Extra Multiplication by ( 1 - \lambda ):

    Do not multiply the entire intermediate state by ( 1 - \lambda ). Only the outer product term should be scaled by ( 1 - \lambda ), as per the standard formula.

Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!