QuantAMM

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

Numerical Instability in PowerChannelUpdateRule Weight Calculations

Summary

The PowerChannelUpdateRule contract exhibits severe numerical instability in its weight calculations when processing certain combinations of parameters. This can result in astronomically large weights (>1e77) while still maintaining a sum of 1e18. Despite existing guard rails in the system, none effectively prevent this instability from causing economic damage to users of QuantAMM-approved pools.

Vulnerability Details

Location: pkg/pool-quantamm/contracts/rules/PowerChannelUpdateRule.sol

The issue can occur in QuantAMM-approved pools when certain parameter combinations are approved that trigger numerical instability in the power channel calculations:

  • Small prices (1e6)

  • Small kappa values (1e6)

  • Large Q values (>3e18)

  • Extreme initial weights (0.001e18/0.999e18)

While existing guard rails check for:

  1. Parameter validation (kappa > 0, Q > 1e18)

  2. Weight interpolation bounds

  3. Manual weight setting limits (0 < weight < 1e18)

None of these prevent the PowerChannelUpdateRule from calculating extreme weights that, while mathematically valid, could destabilize pool pricing and harm users.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../../../contracts/mock/MockRuleInvoker.sol";
import "../../../contracts/mock/mockRules/MockPowerChannelRule.sol";
import "../../../contracts/mock/MockPool.sol";
import "../utils.t.sol";
import {console} from "forge-std/console.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract QuantammPowerChannelUpdateRuleTest is Test, QuantAMMTestUtils {
MockPowerChannelRule rule;
MockPool mockPool;
// Constants
int256 private constant ONE = 1e18; // Same as PowerChannelUpdateRule.ONE
function setUp() public {
rule = new MockPowerChannelRule(address(this));
mockPool = new MockPool(3600, PRBMathSD59x18.fromInt(1), address(rule));
}
// Helper function for absolute value
function abs(int256 x) internal pure returns (int256) {
return x >= 0 ? x : -x;
}
function testPowerChannelNumericalInstability() public {
// Setup values that trigger numerical instability
int256 smallPrice = 1e6; // Keep small price
int256 smallKappa = 1e6; // Small kappa
int256 largeQ = 3e18; // Q > 1 will amplify the instability
// Setup parameters
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = smallKappa; // Small kappa
parameters[1] = new int256[]();
parameters[1][0] = largeQ; // Large Q
// Initial weights close to 0/1 boundary to maximize instability
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.001e18; // 0.1%
prevWeights[1] = 0.999e18; // 99.9%
// Setup price data with small difference
int256[] memory data = new int256[]();
data[0] = smallPrice;
data[1] = smallPrice * 2; // 2x difference to clearly show the effect
// Initialize pool
mockPool.setNumberOfAssets(2);
mockPool.setLambda(ONE);
// Setup moving averages slightly different from current prices
int256[] memory movingAverages = new int256[]();
movingAverages[0] = smallPrice * 99 / 100; // 1% lower
movingAverages[1] = smallPrice * 2 * 99 / 100;
int256[] memory alphas = new int256[]();
alphas[0] = 1e18;
alphas[1] = 1e18;
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
movingAverages,
alphas,
2
);
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(900000000000000000); // lambda = 0.9
// Calculate weights
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
movingAverages
);
int256[] memory resultWeights = rule.GetResultWeights();
// Log values to demonstrate instability
console.log("\nNumerical Instability Test Results:");
console.log("Parameters:");
console.log(" Kappa:", uint256(smallKappa));
console.log(" Q:", uint256(largeQ));
console.log(" Initial Price 1:", uint256(data[0]));
console.log(" Initial Price 2:", uint256(data[1]));
console.log("\nWeights:");
console.log(" Initial weights:", uint256(prevWeights[0]), uint256(prevWeights[1]));
console.log(" Final weights:", uint256(resultWeights[0]), uint256(resultWeights[1]));
console.log(" Weight sum:", uint256(resultWeights[0] + resultWeights[1]));
// Check for numerical instability indicators:
// 1. Extreme weight changes
bool hasExtremeChange = false;
for(uint i = 0; i < 2; i++) {
int256 weightChange = abs(resultWeights[i] - prevWeights[i]);
if (weightChange > 1e20) { // Weight changed by more than 100
hasExtremeChange = true;
break;
}
}
// 2. Unreasonable weight values
bool hasUnreasonableValues = false;
for(uint i = 0; i < 2; i++) {
if (resultWeights[i] > 1e20 || resultWeights[i] < -1e20) {
hasUnreasonableValues = true;
break;
}
}
assertTrue(
hasExtremeChange || hasUnreasonableValues,
"Expected numerical instability with extreme weight changes or unreasonable values"
);
}
}

Test Results:

Numerical Instability Test Results:
Parameters:
Kappa: 1000000
Q: 3000000000000000000
Initial Price 1: 1000000
Initial Price 2: 2000000
Weights:
Initial weights: 1000000000000000 999000000000000000
Final weights: 450891941556288179585948406141932 115792089237316195423570985008687907853269984214748622483170404421964723498004
Weight sum: 1000000000000000000

Risk Scenario

  1. A pool is created with parameters that can trigger numerical instability

  2. The QuantAMM team approves the pool and its parameters for the update weight runner process

  3. When certain price conditions occur, the pool enters a numerically unstable state

  4. Price calculations become unreliable

  5. Users of the pool suffer economic damage

Impact

Severity: HIGH

  1. Technical Impact:

    • Generates astronomically large weights (>1e77)

    • Breaks pool mathematics while maintaining superficial invariants

    • Creates unreliable price calculations

    • Affects approved pools using PowerChannelUpdateRule

    • System becomes unstable and unpredictable

  2. Economic Impact:

    • Unreliable price calculations leading to significant economic damage

    • High risk of substantial loss of user funds

    • Systemic risk to connected pools or protocols

    • Potential for complete pool failure

    • Undermines fundamental trust in the protocol

  3. Despite Pool Approval Requirements:

    • While pools require QuantAMM team approval, this is insufficient protection because:

      • Parameter combinations that trigger instability are non-obvious

      • Effects may only manifest under specific market conditions

      • Once approved, pools can enter unstable states without further checks

      • Human approval process is prone to oversight

      • Economic damage can be severe before issues are detected

    • The approval process provides a false sense of security rather than meaningful protection

  4. Systemic Risk:

    • Affects core pool mathematics

    • No effective circuit breakers or recovery mechanisms

    • Can impact multiple pools simultaneously

    • May trigger cascading failures in integrated protocols

    • Recovery requires manual intervention

Tools Used

  • Foundry testing framework

  • Manual code review

  • Mathematical analysis of power channel formula

  • Custom test suite for numerical stability

Recommendations

  1. Add Parameter Bounds:

contract PowerChannelUpdateRule {
int256 private constant MAX_Q = 5e18; // Maximum Q value
int256 private constant MIN_KAPPA = 1e12; // Minimum kappa value
int256 private constant MIN_PRICE = 1e12; // Minimum price value
function _calculateWeights(...) internal {
require(q <= MAX_Q, "Q too large");
require(kappa >= MIN_KAPPA, "Kappa too small");
require(price >= MIN_PRICE, "Price too small");
// ... existing code ...
}
}
  1. Implement Intermediate Value Checks:

    • Add bounds checking on intermediate calculations

    • Validate power operation results

    • Ensure weight changes remain within reasonable bounds

    • Add circuit breakers for extreme results

  2. Improve Parameter Validation Process:

    • Add validation checks for parameter combinations during pool approval

    • Document safe parameter ranges

    • Consider automated parameter validation tools

    • Add monitoring for approved pools' weight calculations

References

Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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