QuantAMM

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

edge case leads to wrong weights calculation in ChannelFollowing

Summary

the calculation of the channel following is complex having multiple exponents and negative signs giving these equation to calculate the channel following

  • w(t) = w(t-1) + κ[channel + trend - normalizationFactor] where

    • g = normalized price gradient = (1/p)·(dp/dt)

    • envelope = exp(-g²/(2W²))

    • s = pi * g / (3W)

    • channel = -(A/h)·envelope · (s - 1/6 s^3)

    • trend = (1-envelope) * sign(g) * |g/(2S)|^(exponent)

    • normalizationFactor = 1/N * ∑(κ[channel + trend])_i

focusing on normalizationFactor and the trend normalizationFactor is a sum of channels + trend (incase of scalar not vector)

and then (channel[i]+ trend[i]) - normalizationFactor

this leads to negative value arise as normalizationFactor > every (channel[i] + trend][i])

resulting in negative newWeights

issue only happens incase of scaller that the kappa length = 1

incase of a vector kappa there is a guard require to insure no negative values appear in newWeights

File: ChannelFollowingUpdateRule.sol
225: // Calculate final weights
226: if (locals.kappa.length == 1) {
227: locals.normalizationFactor /= int256(locals.prevWeightLength);
228: for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
229: newWeightsConverted[locals.i] =
230: _prevWeights[locals.i] +
231: locals.kappa[0].mul(locals.signal[locals.i] - locals.normalizationFactor);
232:
233: unchecked {
234: ++locals.i;
235: }
236: }
237: } else {
238: for (locals.i = 0; locals.i < locals.kappa.length; ) {
239: locals.sumKappa += locals.kappa[locals.i];
240: unchecked {
241: ++locals.i;
242: }
243: }
244:
245: locals.normalizationFactor = locals.normalizationFactor.div(locals.sumKappa);
246: for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
247: int256 weightUpdate = locals.kappa[locals.i].mul(locals.signal[locals.i] - locals.normalizationFactor);
248: newWeightsConverted[locals.i] = _prevWeights[locals.i] + weightUpdate;
249:@> require(newWeightsConverted[locals.i] >= 0, "Invalid weight");

poc

add this testFunction in ChannelFollowingUpdateRuleTest

function testCorrectWeightsWithHigherPricesNegativeWeights() public {
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = PRBMathSD59x18.fromInt(200); // Parameter 1 Kappa
parameters[1] = new int256[]();
parameters[1][0] = 0.5e18; // Parameter 2 Width
parameters[2] = new int256[]();
parameters[2][0] = 0.1e18; // Parameter 3 Amplitude
parameters[3] = new int256[]();
parameters[3][0] = PRBMathSD59x18.fromInt(3); // Parameter 4 Exponents
parameters[4] = new int256[]();
parameters[4][0] = PRBMathSD59x18.fromInt(1); // Parameter 5 Inverse Scaling
parameters[5] = new int256[]();
parameters[5][0] = 0.5e18; // Parameter 6 Pre-exp Scaling
parameters[6] = new int256[]();
parameters[6][0] = PRBMathSD59x18.fromInt(1); // Parameter 7 Use Raw Price
int256[] memory prevAlphas = new int256[]();
prevAlphas[0] = PRBMathSD59x18.fromInt(1);
prevAlphas[1] = PRBMathSD59x18.fromInt(2);
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = PRBMathSD59x18.fromInt(3);
prevMovingAverages[1] = PRBMathSD59x18.fromInt(4);
int256[] memory movingAverages = new int256[]();
movingAverages[0] = PRBMathSD59x18.fromInt(3);
movingAverages[1] = PRBMathSD59x18.fromInt(4);
// Lambda initialization
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.9e18);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.005e18; // Weight 1
prevWeights[1] = 0.995e18; // Weight 2
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(5);
data[1] = PRBMathSD59x18.fromInt(6);
int256[] memory expectedResults = new int256[]();
expectedResults[0] = -0.007719543735408600e18;
expectedResults[1] = 1.007719543735408600e18;
mockPool.setNumberOfAssets(2);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
prevAlphas,
mockPool.numAssets()
);
rule.CalculateUnguardedWeights(prevWeights, data, address(mockPool), parameters, lambdas, movingAverages);
int256[] memory resultWeights = rule.GetResultWeights();
checkResult(resultWeights, expectedResults);
}

Impact

  • broken calculation of ChannelFollowing rule breaks all logic related to it

  • calculation of weights is a core to the protocol and can't have any small error as it have huge impact on the protocol

Tools Used

manual review

Recommendations

As with vector kappa apply the check for the scaller

++ require(newWeightsConverted[locals.i] >= 0, "Invalid weight");
Updates

Lead Judging Commences

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

invalid_weights_can_be_negative_or_extreme_values

_clampWeights will check that these weights are positive and in the boundaries before writing them in storage.

Support

FAQs

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

Give us feedback!