QuantAMM

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

The `validParameters` function lacks of sufficient checks on parameters, which may lead to math overflow in `_getWeights`

Summary

The validParameters function currently performs basic validation, such as ensuring that parameters are positive and have the correct array lengths, but it does not enforce upper bounds on the values of the parameters. This omission can lead to situations where the parameters are too large, causing intermediate calculations to overflow.

Vulnerability Details

The ChannelFollowingUpdateRule contract is using PRBMathSD59x18 for int256 arithmetic:

using PRBMathSD59x18 for int256;

That means the bound for int256 is between MIN_SD59x18 and MAX_SD59x18.

/// @dev The maximum value a signed 59.18-decimal fixed-point number can have.
int256 internal MAX_SD59x18 =
57896044618658097711785492504343953926634992332820282019728_792003956564819967;
/// @dev The minimum value a signed 59.18-decimal fixed-point number can have.
int256 internal MIN_SD59x18 =
-57896044618658097711785492504343953926634992332820282019728_792003956564819968;

The validParameters function currently only performs basic validation:

for (uint i = 0; i < baseLength; i++) {
if (_parameters[0][i] <= 0) return false; // kappa must be positive
if (_parameters[1][i] <= 0) return false; // width must be positive
if (_parameters[2][i] <= 0) return false; // amplitude must be positive
if (_parameters[3][i] <= 0) return false; // exponents must be positive
if (_parameters[4][i] <= 0) return false; // inverse scaling must be positive
if (_parameters[5][i] <= 0) return false; // pre-exp scaling must be positive
}

That means all parameters' bounds are between 1 and MAX_SD59x18. Add following test in ChannelFollowingUpdateRuleTest and it will prove this point:

function testParamsBounds(
int256 param1,
int256 param2,
int256 param3,
int256 param4,
int256 param5,
int256 param6
)public{
int256[][] memory parameters = new int256[][]();
// Initialize all parameters as valid positive values
for (uint i = 0; i < 6; i++) {
parameters[i] = new int256[]();
parameters[i][0] = PRBMathSD59x18.fromInt(1);
}
parameters[6] = new int256[]();
parameters[6][0] = PRBMathSD59x18.fromInt(0); // Parameter 7 can be 0
// Set each parameter to the fuzzed value
parameters[0][0] = PRBMathSD59x18.fromInt(bound(param1, 1, maxScaledFixedPoint18()));
parameters[1][0] = PRBMathSD59x18.fromInt(bound(param2, 1, maxScaledFixedPoint18()));
parameters[2][0] = PRBMathSD59x18.fromInt(bound(param3, 1, maxScaledFixedPoint18()));
parameters[3][0] = PRBMathSD59x18.fromInt(bound(param4, 1, maxScaledFixedPoint18()));
parameters[4][0] = PRBMathSD59x18.fromInt(bound(param5, 1, maxScaledFixedPoint18()));
parameters[5][0] = PRBMathSD59x18.fromInt(bound(param6, 1, maxScaledFixedPoint18()));
bool valid = rule.validParameters(parameters);
assertTrue(valid);
}

Run forge test -vvv --match-test testParamsBounds , the test passes:

Ran 1 test for test/foundry/rules/QuantAMMChannelFollowing.t.sol:ChannelFollowingUpdateRuleTest
[PASS] testParamsBounds(int256,int256,int256,int256,int256,int256) (runs: 10002, μ: 43923, ~: 44257)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 779.77ms (776.28ms CPU time)

However, it may lead to overflow in _getWeights function.

The _getWeights function performs complex mathematical operations, including:

  • Exponentiation (exp, _pow),

  • Multiplication and division,

  • Summation of weighted signals.

If the parameters are not properly bounded, these operations can easily produce intermediate results that exceed the range of int256 (which is approximately [-2^255, 2^255 - 1]). For example:

  • A large kappa value could amplify the signal, causing the weight update to overflow.

  • A small width value could make the envelope calculation (exp(-gradientSquared / (2 * width^2))) produce extremely large results, leading to overflow in subsequent multiplications.

Add following test case in ChannelFollowingUpdateRuleTest:

function testCalculateWeightsWithFuzzedValue(
int256 param1,
int256 param2,
int256 param3,
int256 param4,
int256 param5,
int256 param6
) public {
int256[][] memory parameters = new int256[][]();
// Initialize all parameters as valid positive values
for (uint i = 0; i < 6; i++) {
parameters[i] = new int256[]();
parameters[i][0] = PRBMathSD59x18.fromInt(1);
}
parameters[6] = new int256[]();
parameters[6][0] = PRBMathSD59x18.fromInt(0); // Parameter 7 can be 0
// Set each parameter to the fuzzed value
parameters[0][0] = PRBMathSD59x18.fromInt(bound(param1, 1, maxScaledFixedPoint18()));
parameters[1][0] = PRBMathSD59x18.fromInt(bound(param2, 1, maxScaledFixedPoint18()));
parameters[2][0] = PRBMathSD59x18.fromInt(bound(param3, 1, maxScaledFixedPoint18()));
parameters[3][0] = PRBMathSD59x18.fromInt(bound(param4, 1, maxScaledFixedPoint18()));
parameters[4][0] = PRBMathSD59x18.fromInt(bound(param5, 1, maxScaledFixedPoint18()));
parameters[5][0] = PRBMathSD59x18.fromInt(bound(param6, 1, maxScaledFixedPoint18()));
bool valid = rule.validParameters(parameters);
assertTrue(valid);
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);
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();
}

All parameters are fuzzed values which are between 1 and MAX_SD59x18 . Run forge test -vvv --match-test testCalculateWeightsWithFuzzedValue , the result is:

Ran 1 test for test/foundry/rules/QuantAMMChannelFollowing.t.sol:ChannelFollowingUpdateRuleTest
[FAIL: PRBMath__MulDivFixedPointOverflow(28948022309329048855696955016157805541946181829897863632468348167255069033018 [2.894e76]); counterexample: calldata=0x009fae92000000000000000000000000000000000000000000000000000000300d00faa9ffffffffffffffffffffffffffffffff6cb3fac52d23455f3157dad0c79b573200000000000000000000000000000000000004e6976a9dc33059fd28a89f57950000000000000000000000000000000000593a0fbe961337b38ccfff39e45dda7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000030796375954f4cf66038bf96 args=[206376598185 [2.063e11], -195791236014171421371645397194854672590 [-1.957e38], 99398976920519518597419552954261 [9.939e31], 463292047572630661258667552856628698 [4.632e35], 57896044618658097711785492504343953926634992332820282019728792003956564819967 [5.789e76], 15002030178907696803694034838 [1.5e28]]] testCalculateWeightsWithFuzzedValue(int256,int256,int256,int256,int256,int256) (runs: 0, μ: 0, ~: 0)

PRBMath__MulDivFixedPointOverflow error happens in _getWeights function.

Impact

The Impact is HIGH, the likelihood is LOW. So the Severity is MEDIUM.

Tools Used

Manual Review

Recommendations

Consider adding Upper Bounds Checking in validParameters.

Updates

Lead Judging Commences

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

Informational or Gas / Admin is trusted / Pool creation is trusted / User mistake / Suppositions

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelyhood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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

Give us feedback!