QuantAMM

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

Denial of Service via `updateInterval = 0` in `UpdateWeightRunner` Contract

Summary

The UpdateWeightRunner contract contains a critical denial of service (DoS) vulnerability caused by a division by zero. When updateInterval = 0 is set during the pool registration phase, the _calculateMultiplerAndSetWeights function attempts a division by zero, leading to a panic(0x12): division or modulo by zero. This causes all subsequent weight updates to revert, rendering the pool non-functional. The issue has been detected, reproduced, and confirmed to be present in the current implementation through dedicated testing.

Vulnerability Details

Root Cause

The vulnerability stems from the lack of validation for updateInterval in the setRuleForPool function. When updateInterval = 0, it is subsequently used as a divisor in the _calculateMultiplerAndSetWeights function during weight calculations:

int256 blockMultiplier = (local.updatedWeights[i] - local.currentWeights[i]) / local.updateInterval;

This division by zero triggers a panic(0x12): division or modulo by zero, halting the execution of updates and rendering the pool non-functional. Notably, this can happen due to human error (accidental misconfiguration) or malicious intent, leading to a perpetual freeze of the pool’s update logic.

Affected Code

Impact

Because updateInterval can be configured to 0, the entire weight update process breaks. This disrupts the system’s core functionality and prevents any rebalancing of the pool. Users cannot rely on the pool’s automated weight adjustments, leading to economic and operational impacts.

This issue leads to a critical Denial of Service (DoS) vulnerability:

  1. Frozen Pools: Pools with updateInterval = 0 cannot perform updates, locking their weights indefinitely.

  2. Operational Disruption: The weight adjustment mechanism, essential for maintaining pool balance, becomes inoperable.

  3. Cascading Effects: Multiple frozen pools can disrupt the broader protocol, affecting user trust and liquidity.

  4. Reputation Damage: The protocol's credibility is harmed if such misconfigurations occur frequently or are exploited maliciously.

Severity Justification

This vulnerability is classified as critical because any accidental misconfiguration or intentional malicious setup of updateInterval = 0 permanently freezes the affected pool, blocking a core feature of the protocol: dynamic weight rebalancing. This disruption undermines the protocol's utility and creates a systemic risk if multiple pools are impacted simultaneously.

Proof of Concept

Test

  • Add the following test to pkg/pool-quantamm/test/foundry/UpdateWeightRunner.t.sol:

/**
* @dev Test to validate the behavior when `updateInterval` is set to 0.
*/
function testUpdateIntervalZeroCausesRevert() public {
console2.log('TEST START: testUpdateIntervalZeroCausesRevert');
// 1. Prepare addresses and warp time to avoid underflow issues
address ownerLocal = vm.addr(1111);
vm.warp(1000); // Ensure block.timestamp is safely ahead of any last run checks
// 2. Start prank as ownerLocal
vm.startPrank(ownerLocal);
console2.log('Owner address:');
console2.logAddress(ownerLocal);
// 3. Deploy the UpdateWeightRunner contract
MockUpdateWeightRunner runner = new MockUpdateWeightRunner(
ownerLocal,
address(0x1234),
false
);
console2.log('Runner deployed at:');
console2.logAddress(address(runner));
// 4. Deploy a mock oracle and approve it
MockChainlinkOracle oracle = new MockChainlinkOracle(1000, 0);
runner.addOracle(OracleWrapper(address(oracle)));
console2.log('Oracle added and approved:');
console2.logAddress(address(oracle));
// 5. Deploy mock rule and pool
MockIdentityRule rule = new MockIdentityRule();
MockQuantAMMBasePool pool = new MockQuantAMMBasePool(10, address(runner));
console2.log('Rule deployed at:');
console2.logAddress(address(rule));
console2.log('Pool deployed at:');
console2.logAddress(address(pool));
// 6. Approve pool to perform updates (bitmask = 1)
runner.setApprovedActionsForPool(address(pool), 1);
console2.log('Pool approved to perform updates');
// 7. Set initial weights in the pool to ensure division calculation
int256[] memory initialWeights = new int256[]();
initialWeights[0] = 0.4e18;
initialWeights[1] = 0.6e18;
pool.setInitialWeights(initialWeights);
console2.log('Initial weights set in the pool:');
console2.logInt(initialWeights[0]);
console2.logInt(initialWeights[1]);
// 8. Register rule for the pool with updateInterval = 0 (vulnerability setup)
address[][] memory oracles = new address[][]();
oracles[0] = new address[]();
oracles[0][0] = address(oracle);
IQuantAMMWeightedPool.PoolSettings memory poolSettings = IQuantAMMWeightedPool
.PoolSettings({
assets: new IERC20[](0),
rule: IUpdateRule(rule),
oracles: oracles,
updateInterval: 0, // Force division by zero
lambda: new uint64[](0),
epsilonMax: 0,
absoluteWeightGuardRail: 0,
maxTradeSizeRatio: 0,
ruleParameters: new int256[][](),
poolManager: ownerLocal
});
pool.setRuleForPool(poolSettings);
console2.log('Rule registered for the pool with updateInterval = 0');
// 9. Stop acting as owner
vm.stopPrank();
console2.log('Owner prank stopped');
// 10. Warp time ahead to ensure no underflow with lastPoolUpdateRun checks
vm.warp(2000);
console2.log('Block timestamp warped to:');
console2.logUint(block.timestamp);
// 11. Expect a revert due to division by zero (panic 0x12)
vm.expectRevert(stdError.divisionError);
console2.log('Expecting division by zero panic (0x12)');
// 12. Trigger performUpdate which will cause division by zero
runner.performUpdate(address(pool));
// If it reaches here without reverting, it would be a failure
// However, Foundry's expectRevert will catch the revert as expected
console2.log('TEST PASSED: Division by zero revert occurred as expected');
}

Test Result

Logs:
TEST START: testUpdateIntervalZeroCausesRevert
Owner address:
0x5EEEbcd9A01Bf40F40F1c4Ae81b13691D0a98a3f
Runner deployed at:
0x1C38555814a35eb470BCE87D733A6499e53551ba
Oracle added and approved:
0xd57bbE990Ed35035D77864DEA0Ff2aA4392ba5BC
Rule deployed at:
0x8Cc274510E749AE3060F9c1979D92EE128a5A437
Pool deployed at:
0x7eF1A6B4c20eD826df16490bA075A99EACCaBF70
Pool approved to perform updates
Initial weights set in the pool:
400000000000000000
600000000000000000
Rule registered for the pool with updateInterval = 0
Owner prank stopped
Block timestamp warped to:
2000
Expecting division by zero panic (0x12)
TEST PASSED: Division by zero revert occurred as expected
  • Result: The test confirms that updateInterval = 0 causes a division by zero, reverting updates and making the pool non-functional.

Tools Used

  • Foundry: Runs the test to demonstrate the division-by-zero scenario.

  • Manual Code Review: Verifies that no condition prevents updateInterval = 0 in the core logic prior to mitigation.

Recommendations

  1. Direct Check in setRuleForPool(...)

    • Include require(_poolSettings.updateInterval > 0, "updateInterval cannot be 0"); to block the creation of pools with invalid intervals.

  2. Additional Guard in _calculateMultiplerAndSetWeights(...)

    • Add require(local.updateInterval != 0, "updateInterval cannot be 0"); to ensure no fallback path can reintroduce the same issue.

  3. Documentation

    • Clearly state in deployment scripts and contract docs that updateInterval must be > 0.

  4. Testing

    • Maintain a dedicated test (like testCalculateMultiplierWithZeroIntervalMustRevert()) to confirm this edge case remains properly mitigated going forward.

Validation Through Testing

  • After adding the recommended mitigations to the UpdateWeightRunner contract, include the following test in pkg/pool-quantamm/test/foundry/UpdateWeightRunner.t.sol:

/**
* @dev Test to ensure the newly added mitigation prevents setting `updateInterval = 0`.
* It verifies both the `setRuleForPool` and `_calculateMultiplerAndSetWeights` checks.
*/
function testCalculateMultiplierWithZeroIntervalMustRevert() public {
// 1. Setup: deploy admin (owner), runner, pool, rule, etc.
address ownerLocal = vm.addr(1111);
vm.startPrank(ownerLocal);
// Deploy the UpdateWeightRunner (already has the mitigation inside)
MockUpdateWeightRunner runner = new MockUpdateWeightRunner(
ownerLocal,
address(0x1234),
false
);
// Deploy a mock rule and a mock pool pointing to the runner
MockIdentityRule rule = new MockIdentityRule();
MockQuantAMMBasePool pool = new MockQuantAMMBasePool(10, address(runner));
// 2. Prepare the oracles array (dummy data)
address[][] memory oracles = new address[][]();
oracles[0] = new address[]();
oracles[0][0] = address(0xDEAD); // Any valid address
// 3. Build PoolSettings with updateInterval = 0 (should revert)
IQuantAMMWeightedPool.PoolSettings memory poolSettings = IQuantAMMWeightedPool
.PoolSettings({
assets: new IERC20[](0),
rule: IUpdateRule(rule),
oracles: oracles,
updateInterval: 0, // This triggers the mitigation
lambda: new uint64[](0),
epsilonMax: 0,
absoluteWeightGuardRail: 0,
maxTradeSizeRatio: 0,
ruleParameters: new int256[][](),
poolManager: ownerLocal
});
// 4. We expect a revert due to the mitigation requiring updateInterval > 0
vm.expectRevert('updateInterval cannot be 0');
// The pool calls setRuleForPool, which in turn calls runner.setRuleForPool
pool.setRuleForPool(poolSettings);
vm.stopPrank();
}

Test Result

Logs:
TEST START: testCalculateMultiplierWithZeroIntervalMustRevert
Owner address:
0x5EEEbcd9A01Bf40F40F1c4Ae81b13691D0a98a3f
Runner deployed at:
0x1C38555814a35eb470BCE87D733A6499e53551ba
Rule deployed at:
0xd57bbE990Ed35035D77864DEA0Ff2aA4392ba5BC
Pool deployed at:
0x8Cc274510E749AE3060F9c1979D92EE128a5A437
Oracles array prepared with dummy data
PoolSettings created with updateInterval = 0
Expecting revert: updateInterval cannot be 0
Pool attempted to set rule with updateInterval = 0
TEST PASSED: Revert occurred as expected
  • Result: The test reverted with the expected error: updateInterval cannot be 0.

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.