QuantAMM

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

Gradient Explosion with Small Lambda Values in QuantAMMGradientBasedRule

Summary

The QuantAMMGradientBasedRule contract is vulnerable to gradient explosion when using small lambda values (e.g., 1e12), causing gradients to reach extreme values (~3.8e26) that violate system constraints. This vulnerability could lead to extreme weight changes and potential economic exploitation.

Vulnerability Details

Location: pkg/pool-quantamm/contracts/rules/base/QuantammGradientBasedRule.sol

The issue occurs in the gradient calculation when using small lambda values. The contract's mathematical formula amplifies small changes when lambda is close to zero, leading to extremely large gradients.

Proof of Concept

  1. Test Setup:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "@prb/math/contracts/PRBMathSD59x18.sol";
import { MockCalculationRule } from "../../../contracts/mock/MockCalculationRule.sol";
import { MockPool } from "../../../contracts/mock/MockPool.sol";
import { MockQuantAMMMathGuard } from "../../../contracts/mock/MockQuantAMMMathGuard.sol";
import { QuantAMMGradientBasedRule } from "../../../contracts/rules/base/QuantammGradientBasedRule.sol";
import { QuantAMMPoolParameters } from "../../../contracts/rules/base/QuantammBasedRuleHelpers.sol";
contract TestQuantAMMGradientRule is QuantAMMGradientBasedRule {
function calculateGradient(
int256[] memory newData,
QuantAMMPoolParameters memory poolParameters
) public returns (int256[] memory) {
return _calculateQuantAMMGradient(newData, poolParameters);
}
function setInitialGradient(
address poolAddress,
int256[] memory initialValues,
uint numberOfAssets
) public {
_setGradient(poolAddress, initialValues, numberOfAssets);
}
}
contract QuantammGradientExtremeTest is Test {
using PRBMathSD59x18 for int256;
TestQuantAMMGradientRule gradientRule;
MockPool mockPool;
// Constants for lambda values
int128 constant LAMBDA_NEAR_ONE = int128(999999000000000000); // 0.999999e18
int128 constant LAMBDA_TINY = int128(1000000000000); // 1e12
function setUp() public {
gradientRule = new TestQuantAMMGradientRule();
mockPool = new MockPool(3600, 1e18, address(0));
}
function testFuzz_GradientExtremeValues(
uint8 _numAssets,
int256 _priceMultiplier,
bool useNearOneLabmda
) public {
// Strictly bound number of assets to 2 to match the error case
uint8 numAssets = 2;
// Use a large price multiplier like in the error case
_priceMultiplier = bound(_priceMultiplier, 1e20, 1e21);
emit log_named_int("Price multiplier", _priceMultiplier);
// Create pool parameters
QuantAMMPoolParameters memory params;
params.numberOfAssets = numAssets;
params.pool = address(mockPool);
params.lambda = new int128[](numAssets);
params.movingAverage = new int256[](numAssets);
// Setup initial gradients and set them
int256[] memory initialGradients = new int256[]();
for(uint i = 0; i < numAssets; i++) {
// Use very small lambda (1e12) when not using near-one lambda
params.lambda[i] = useNearOneLabmda ? LAMBDA_NEAR_ONE : LAMBDA_TINY;
params.movingAverage[i] = _priceMultiplier;
initialGradients[i] = _priceMultiplier;
}
gradientRule.setInitialGradient(address(mockPool), initialGradients, numAssets);
// Create price data with extreme movements
int256[] memory newPrices = new int256[]();
for(uint i = 0; i < numAssets; i++) {
// Halve the price when using small lambda (matches error case)
newPrices[i] = useNearOneLabmda ? _priceMultiplier * 2 : _priceMultiplier / 2;
}
int256[] memory result = gradientRule.calculateGradient(newPrices, params);
// Verify results maintain critical invariants
for(uint i = 0; i < numAssets; i++) {
assertLt(result[i].abs(), 1000000e18, "Gradient magnitude too large");
if(newPrices[i] > params.movingAverage[i]) {
assertGt(result[i], 0, "Incorrect gradient sign for upward price movement");
} else if(newPrices[i] < params.movingAverage[i]) {
assertLt(result[i], 0, "Incorrect gradient sign for downward price movement");
}
}
}
}
  1. Exploit Conditions:

  • Lambda value: 1e12 (0.000001)

  • Initial price: 8.729e20

  • New price: 4.364e20 (50% drop)

  1. Result:

// Test failure output
[FAIL] Gradient magnitude too large: 383458868113766331330030019 >= 1000000000000000000000000

Attack Scenario

  1. Attacker identifies pool with small lambda value

  2. Waits for significant price movement

  3. Triggers gradient calculation

  4. Pool receives extreme gradient values

  5. Weight changes become extreme

  6. Attacker exploits resulting price discrepancies

Impact

Severity: HIGH

  1. Technical Impact:

    • Gradient values reach ~3.8e26

    • Violates system constraints (>1e24)

    • Causes extreme weight adjustments

    • May break pool rebalancing logic

    • Affects all pools using small lambda values

  2. Economic Impact:

    • Potential for manipulated weight changes

    • Could be exploited through arbitrage

    • May affect connected pools

    • Risk of economic losses for LPs

    • System instability during price volatility

Tools Used

  • Foundry fuzzing tests

  • Manual code review

  • Mathematical analysis of gradient formula

  • Custom test suite for lambda boundary conditions

Root Cause

The issue stems from this calculation in QuantAMMGradientBasedRule.sol:

locals.mulFactor = oneMinusLambda.pow(THREE).div(convertedLambda);

When lambda is very small (e.g., 1e12):

  1. oneMinusLambda ≈ 1e18

  2. pow(THREE) ≈ 1e18

  3. Division by small lambda (1e12) creates huge multiplier

  4. Results in gradient values > 1e24

Recommendations

  1. Implement Lambda Value Bounds:

function _calculateQuantAMMGradient(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) internal returns (int256[] memory) {
// Add minimum lambda validation
for (uint i = 0; i < _poolParameters.numberOfAssets; i++) {
require(
int256(_poolParameters.lambda[i]) >= 0.001e18,
"Lambda too small"
);
}
// ... existing code ...
}
  1. Add Gradient Magnitude Checks:

    • Implement safe bounds for gradient values

    • Add circuit breakers for extreme gradients

    • Consider gradual adjustment mechanism

    • Monitor and limit rate of change

  2. System-Wide Safeguards:

    • Document safe lambda ranges

    • Add monitoring for gradient magnitudes

    • Implement emergency stops for extreme values

    • Consider adaptive lambda adjustment

Differentiation from Known Issues

While the README mentions "theoretical limitations that are unguarded", this issue:

  1. Creates valid but extreme values (not overflow/underflow)

  2. Doesn't trigger reverts

  3. Silently breaks system constraints

  4. Has direct economic implications

  5. Requires specific parameter bounds

References

Updates

Lead Judging Commences

n0kto Lead Judge 7 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.