The TokenManager::withdraw external function allows users to withdraw their tokens. It checks the user's claimable amount using a map and proceeds with the withdrawal. However, the claimable balance is not zeroed out at the end, allowing any user to repeatedly call withdraw until the pool is empty.
Users gain a claimable balance through various interactions with the protocol, the simplest of them being creating an offer with PreMarktes::createOffer and then closing said offer with PreMarktes::closeOffer.
With that claimable balance registered in the contract, users can call TokenManager::withdraw to withdraw funds.
TokenManager::withdraw checks a mapping for msg.sender's balance and performs a withdrawal, but does not zero the balance afterwards.
The forge test below showcases the vulnerability as described above:
Ran 1 test for test/PreMarkets.t.sol:PreMarketsTest
[PASS] test_anyone_can_drain_capital_pool() (gas: 817482)
Logs:
Initial user balance: 100000000000000000000000000
Initial capital pool balance: 100000000000000000000000000
Final user balance: 110800000000000000000000000
Final capital pool balance: 89200000000000000000000000
The example above uses a naive multiple call approach for simplicity's sake.
A real life attacker would most likely use a carefully crafted smart contract to withdraw all tokens from the bridge in a single transaction, giving no chance for the administrator to react by pausing or upgrading the protocol.
All tokens held by the protocol could be drained, leaving users with devastating losses.
Forge.
Zero out the claimable balance in TokenManager::withdraw, insert code at line 148:
userTokenBalanceMap[_msgSender()][_tokenAddress][_tokenBalanceType] = 0
This will stop users from withdrawing their claimable balance multiple times, even reentrancy attacks will not work because the amount is zeroed out before the external call.
Valid critical severity finding, the lack of clearance of the `userTokenBalanceMap` mapping allows complete draining of the CapitalPool contract. Note: This would require the approval issues highlighted in other issues to be fixed first (i.e. wrong approval address within `_transfer` and lack of approvals within `_safe_transfer_from` during ERC20 withdrawals)
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.