https://github.com/hackenproof-public/rain-contracts
The RainPool contract contains a critical accounting flaw in the closePool() function that leads to arithmetic underflow when orderbook trading volume reaches approximately 38-40x the AMM pool size. This results in permanent denial of service, with all pool funds (potentially millions of dollars) locked forever with no recovery mechanism.
The closePool() function at lines 681-686 incorrectly assumes that all platformShare fees originated from allFunds:
winningPoolShare = totalBaseTokens - platformShare - liquidityShare - creatorShare - resolverShare;
The flawed assumption:
platformShare ⊆ allFunds
Reality:
platformShare = AMM_fees + Orderbook_fees
allFunds = AMM_deposits_only
Fund Tracking Disparity:
The Critical Path - placeBuyOrder(): When users call placeBuyOrder() (lines 1063-1171):
// Line 1083: External capital enters
IERC20(baseToken).safeTransferFrom(msg.sender, address(this), amount);
// Lines 1100-1107: Executes against sell orders
(usedAmount, sharesReceived) = _executeSellOrder(...);
amount -= usedAmount;
// Line 1155: Remaining goes to escrow, NOT allFunds
userAmountInEscrow[option][msg.sender] += amount;
Inside _executeSellOrder() (lines 1666-1693):
uint256 fee = (userAmount * ORDER_EXECUTION_FEE) / FEE_MAGNIFICATION; // 2.5%
platformShare += fee; // Line 1679 - Adds fee WITHOUT adding to allFunds
IERC20(baseToken).safeTransfer(sellerAddress, userAmount); // Seller paid immediately
The Underflow Condition: In closePool():
uint256 totalBaseTokens = allFunds; // Line 672
liquidityShare = (totalBaseTokens * 1.2%) / 1000;
creatorShare = (totalBaseTokens * 1.2%) / 1000;
resolverShare = (totalBaseTokens * 0.1%) / 1000;
// Line 681-686: UNDERFLOW when platformShare + other fees > allFunds
winningPoolShare = totalBaseTokens - platformShare - liquidityShare - creatorShare - resolverShare;
Underflow when: platformShare + (2.5% of allFunds) > allFunds
Simplifies to: platformShare > 97.5% of allFunds
Required orderbook/AMM ratio: ~38-40x
Save the following test as test/PlatformShareUnderflowFinal.t.sol in your Foundry project.
forge test --match-test test_ProofOfAccountingMismatch \
--fork-url https://arb-mainnet.g.alchemy.com/v2/{api_key} \
-vv
BEFORE placeBuyOrder():
allFunds: $110000
platformShare: $2750
AFTER placeBuyOrder($50000):
allFunds: $110000 ← NO CHANGE
platformShare: $4000 ← INCREASED by $1250
KEY FINDING:
* $50,000 entered contract via placeBuyOrder()
* allFunds did NOT increase
* platformShare DID increase by $1250
CONCLUSION:
platformShare accumulates fees from funds NOT in allFunds!
forge test --match-test test_ProofOfUnderflowAttack \
--fork-url https://arb-mainnet.g.alchemy.com/v2/{api_key} \
-vv
INITIAL STATE:
AMM Pool (allFunds): $10000
platformShare: $250
PHASE 1: Creating Orderbook Liquidity
100 sellers created
allFunds now: $192673
platformShare now: $495683
PHASE 2: Checking Current State
platformShare/allFunds ratio: 257%
[!] WARNING: platformShare ALREADY EXCEEDS allFunds!
platformShare: $495683
allFunds: $192673
Excess: $303009
PHASE 3: Attempting closePool()
closePool() calculation:
allFunds: $192673
platformShare: $495683
Total to subtract: $500499
Available (allFunds): $192673
[X] UNDERFLOW!
Attempting to subtract $500499 from $192673
VULNERABILITY CONFIRMED
closePool() REVERTED with arithmetic underflow
IMPACT:
* Pool permanently bricked
* All funds locked forever
* No recovery mechanism
The POC definitively proves:
uint256 public platformShareAMM;
uint256 public platformShareOrderbook;
// In closePool():
winningPoolShare = totalBaseTokens - platformShareAMM - liquidityShare - creatorShare - resolverShare;
// In placeBuyOrder():
allFunds += amount;
// In _executeSellOrder()/_executeBuyOrder():
// Deduct payments from allFunds when sellers are paid