QuantAMM

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

In ChannelFollowingUpdateRule weights can become negative

Summary

In ChannelFollowingUpdateRule.sol rule weights can go below zero under certain circumstances due to insufficient validation during weight computation. (this issue occurs in all rules, but this rule is taken as an example)

Vulnerability Details

The root cause lies in the _getWeights() function of the ChannelFollowingUpdateRule.sol contract. The function checks whether weights are positive only if locals.kappa.length != 1 this can lead to negative weights computation if locals.kappa.length = 1:

function _getWeights(
int256[] calldata _prevWeights,
int256[] memory _data,
int256[][] calldata _parameters,
QuantAMMPoolParameters memory _poolParameters
) internal override returns (int256[] memory newWeightsConverted) {
...
// Calculate final weights
if (locals.kappa.length == 1) {
locals.normalizationFactor /= int256(locals.prevWeightLength);
for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
newWeightsConverted[locals.i] =
_prevWeights[locals.i] +
locals.kappa[0].mul(locals.signal[locals.i] - locals.normalizationFactor);
unchecked {
++locals.i;
}
}
} else {
for (locals.i = 0; locals.i < locals.kappa.length; ) {
locals.sumKappa += locals.kappa[locals.i];
unchecked {
++locals.i;
}
}
locals.normalizationFactor = locals.normalizationFactor.div(locals.sumKappa);
for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
int256 weightUpdate = locals.kappa[locals.i].mul(locals.signal[locals.i] - locals.normalizationFactor);
newWeightsConverted[locals.i] = _prevWeights[locals.i] + weightUpdate;
//CHECK if weights positive ONLY here
require(newWeightsConverted[locals.i] >= 0, "Invalid weight");
unchecked {
++locals.i;
}
}
}
}

The issue appears from the lack of checks for negative weights before the final update.

PoC:

Update QuantAMMChannelUpdateRuleTest.t.sol with this test (codehawks site somehow deletes length of array in initialization so it well be errors like this Wrong argument count for function call: 0 arguments given but expected 1 , and to run this test you have to add arrays length manualy sorry):

function testWeightsBelowZero() 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(0); // Parameter 7 Don't 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.5e18; // Weight 1
prevWeights[1] = 0.5e18; // 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.464719395233870000e18;
expectedResults[1] = 0.535280604766130000e18;
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);
for(uint i = 0; i < 5; i++) {
rule.CalculateUnguardedWeights(resultWeights, data, address(mockPool), parameters, lambdas, movingAverages);
resultWeights = rule.GetResultWeights();
if (i == 4) {
//weights is below 0
assert(resultWeights[0] < 0);
}
}
}

In cmd run following command:

forge test --mt testWeightsBelowZero

Output:

Ran 1 test for test/foundry/rules/QuantAMMChannelFollowing.t.sol:ChannelFollowingUpdateRuleTest
[PASS] testWeightsBelowZero() (gas: 692690)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 16.13ms (12.24ms CPU time)

Impact

Negative weights can break normal allocation of liquidity or capital in the pool, leading to inefficiencies or losses for users.

Tools Used

Manual Review

Recommendations

Add final check for weights without depending on kappa value (also after this check optionally you can set weights by default to MIN amount and MAX amount but just requirement is probably enough):

// Calculate final weights
if (locals.kappa.length == 1) {
locals.normalizationFactor /= int256(locals.prevWeightLength);
for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
newWeightsConverted[locals.i] =
_prevWeights[locals.i] +
locals.kappa[0].mul(locals.signal[locals.i] - locals.normalizationFactor);
unchecked {
++locals.i;
}
}
} else {
for (locals.i = 0; locals.i < locals.kappa.length; ) {
locals.sumKappa += locals.kappa[locals.i];
unchecked {
++locals.i;
}
}
locals.normalizationFactor = locals.normalizationFactor.div(locals.sumKappa);
for (locals.i = 0; locals.i < locals.prevWeightLength; ) {
int256 weightUpdate = locals.kappa[locals.i].mul(locals.signal[locals.i] - locals.normalizationFactor);
newWeightsConverted[locals.i] = _prevWeights[locals.i] + weightUpdate;
unchecked {
++locals.i;
}
}
}
//add this requirements
for (locals.i = 0; locals.i < locals.prevWeightLength; locals.i += 1) {
require(newWeightsConverted[locals.i] >= 0, "Invalid weight");
}
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
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!