The NativeMinterWithdrawal and ERC20MinterWithdrawal contracts have a critical issue in the interaction between the withdraw() and claimWithdrawal() functions. These contracts are designed to handle user withdrawals and owner withdrawals separately, but a logical flaw allows the owner to withdraw funds that should be reserved for user withdrawals.
Relevant Components:
balanceAvailable() Function:
Calculates the available balance for withdrawal by subtracting totalUnclaimedWithdrawals from the contract's balance.
The withdraw() function can drain all the funds from the contract, leaving nothing for users to claim, even though their withdrawals have been processed and are marked as claimable. This violates the principle that processed withdrawals should always be claimable.
The owner processes these withdrawals, marking them as claimable.
New deposits are made, increasing the contract's balance.
The owner calls withdraw(), which transfers all funds exceeding totalUnclaimedWithdrawals.
Users try to claim their withdrawals, but the transaction fails due to insufficient funds in the contract.
Impact
The owner can withdraw funds that should be reserved for user withdrawals, leading to a situation where users cannot claim their processed withdrawals. This undermines the integrity of the withdrawal system and can result in loss of user funds.
Tokens are reserved for withdrawal only when withdrawal is being processed.
When withdrawal is NOT processed, tokens can be withdrawn by multisig admin.
Github username: @4gontuk Twitter username: 4gontuk Submission hash (on-chain): 0x6f44bcb01bd213530d45974dc2123ad3b45dc109fd0ec15296c7f3e5233c2f95 Severity: medium
Description:
Description:
The
NativeMinterWithdrawal
andERC20MinterWithdrawal
contracts have a critical issue in the interaction between thewithdraw()
andclaimWithdrawal()
functions. These contracts are designed to handle user withdrawals and owner withdrawals separately, but a logical flaw allows the owner to withdraw funds that should be reserved for user withdrawals.Relevant Components:
balanceAvailable()
Function:totalUnclaimedWithdrawals
from the contract's balance.Code from
NativeMinterWithdrawal
:withdraw()
Function:NativeMinterWithdrawal
:claimWithdrawal()
Function:NativeMinterWithdrawal
:Issue:
The
withdraw()
function can drain all the funds from the contract, leaving nothing for users to claim, even though their withdrawals have been processed and are marked as claimable. This violates the principle that processed withdrawals should always be claimable.Highest Impact Scenario:
totalUnclaimedWithdrawals
.withdraw()
, which transfers all funds exceedingtotalUnclaimedWithdrawals
.Impact
The owner can withdraw funds that should be reserved for user withdrawals, leading to a situation where users cannot claim their processed withdrawals. This undermines the integrity of the withdrawal system and can result in loss of user funds.
Attack Scenario:
totalUnclaimedWithdrawals
.withdraw()
, which transfers all funds exceedingtotalUnclaimedWithdrawals
.Proof of Concept
User Requests Withdrawal:
requestWithdrawal(100, userAddress)
.totalUnclaimedWithdrawals
increases by 100.Owner Processes Withdrawal:
processWithdrawals([withdrawalId])
.New Deposits:
Owner Withdraws Funds:
withdraw(ownerAddress)
.balanceAvailable()
returns 100 (200 - 100).User Claims Withdrawal:
claimWithdrawal(withdrawalId, userAddress)
.This scenario demonstrates how the owner can drain funds reserved for user withdrawals, leading to failed user withdrawal claims.