https://github.com/hackenproof-public/rain-contracts
Description:
The RainPool contract is vulnerable to a permanent Denial of Service (DoS) that prevents the settlement of any pool, permanently locking all user and liquidity provider funds. The vulnerability stems from an accounting error in the enterOption() function when called after the sales period has ended (block.timestamp > endTime) but before the pool is finalized (closePool() has not been called).
Reproduction Steps:
RainPool contract is in its "post-sale, pre-settlement" state. This is a natural window in every pool's lifecycle where block.timestamp > endTime and winner == 0.enterOption(uint256 option, uint256 amount) function. The amount is set to a large value, significantly greater than the total funds (allFunds) currently in the pool.enterOption(), a platform fee (fee) is calculated based on the attacker's amount and is immediately added to the platformShare state variable (L403).L447-496) that only attempts to match orders from the on-chain order book. In this stage, the AMM logic which would increase allFunds is bypassed.amount back to the attacker (L495).platformShare variable has been artificially inflated with a fee that was never backed by real assets (since the principal was fully refunded). However, allFunds remains unchanged. This breaks the core accounting invariant of the pool (allFunds should equal the sum of all shares).closePool() will now fail. The function will revert due to an arithmetic underflow (L681-686) when it tries to calculate winningPoolShare by subtracting the artificially inflated platformShare from totalBaseTokens (allFunds). Since there is no function to decrease platformShare, the pool is permanently bricked, and all funds are locked forever.The attack requires no capital, as the principal is fully refunded, and the only cost is the gas fee for a single transaction.
The vulnerability is fully validated through a Foundry Proof-of-Concept (POC) test suite.
POC File: test/POC_Finding23_PlatformShareInflation.t.sol
Logical Validation Flow:
The POC simulates the exact attack scenario and confirms the outcome. The key test case for validation is test_DoS_ClosePoolRevertAfterAttack().
Setup:
RainPool instance is deployed with standard parameters.enterOption(), establishing a non-zero allFunds value.endTime using vm.warp(endTime + 1) to enter the vulnerable window.Attack Simulation:
enterOption() with a large amount (e.g., 2,400,000 tokens), which is calculated to be sufficient to make platformShare larger than the remaining funds after other fees are accounted for.State Verification:
platformShare has increased, while allFunds and the attacker's token balance remain unchanged. This confirms the accounting imbalance.test_Attack_InflatePlatformShareAfterEndTime():After attack enterOption call:
Attacker balance: 10000000000000 (Unchanged - full refund)
platformShare: 21250250000 (Increased by 20B)
allFunds: 50010000000 (Unchanged)
DoS Confirmation:
closePool().vm.expectRevert() to assert that this call must fail with an arithmetic underflow.test_DoS_ClosePoolRevertAfterAttack():State before closePool attempt:
allFunds: 50010000000
platformShare: 49250250000 (Inflated to 98.5% of allFunds)
Attempting to call closePool...
RESULT: closePool reverted as expected (arithmetic underflow)
poolFinalized: false
DoS CONFIRMED: Pool is permanently locked
Execution:
forge test --match-contract POC_Finding23_PlatformShareInflation results in all 6 tests passing, which comprehensively confirms the vulnerability, its boundary conditions, and the DoS impact.