withdrawFees()
FunctionNormally, when the contract owner calls withdrawFees()
, the function should send the collected USDC protocol fees to a specified address and then reset the internal fee counter (totalFees
) to zero. This ensures fees are only withdrawn once and future calls will have no funds to send unless new fees are collected.
The function sends the USDC before it resets the internal totalFees
value. If the receiving address is a smart contract, it could re-enter the function during the transfer and withdraw the same fees again. This is a reentrancy vulnerability, caused by updating internal state after making an external call — which breaks the Checks-Effects-Interactions pattern.
Likelihood:
The vulnerability will occur when the contract owner designates a smart contract as the recipient (_to
) of withdrawFees()
, and that recipient has a fallback function that re-enters withdrawFees()
during the external call.
It can also happen in DAO or multisig setups where the owner
is itself a contract, or automated scripts interact with the protocol — increasing the chance of unintended reentrant behavior.
Impact:
The same totalFees
amount could be withdrawn multiple times, leading to a loss of protocol funds that should have only been transferred once.
State inconsistency may arise, which can lead to confusion in accounting, false balances, or other parts of the system relying on incorrect fee values.
Inside withdrawFees()
, the contract transfers USDC to address(this)
using safeTransfer
.
Because address(this)
is a smart contract, the fallback()
function is triggered.
Inside the fallback, withdrawFees()
is called again — before totalFees
is set to 0.
This results in double withdrawal of the same fees.
After both calls, totalFees
is finally set to 0 — but the protocol has already been drained.
To fix the vulnerability, the withdrawFees()
function should be updated to follow the Checks-Effects-Interactions pattern, ideally with a reentrancy guard. After patching, a new version of the contract can be deployed. If using a proxy, only the logic needs updating. Otherwise, key state like allowed tokens and fee data should be migrated manually. The old contract should be retired or disabled, and users—especially DAO or multisig owners—should be notified to interact only with the updated version.
`withdrawFees()` function performs an external transfer using `iUSDC.safeTransfer()` before resetting totalFees. This breaks the `Checks-Effects-Interactions (CEI)` pattern and can lead to incorrect internal state if the transfer fails for any reason.
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.