transfer()
for Withdrawals + Locked Funds ImpactNormally, a withdrawal function should safely transfer ETH to a specified address, regardless of whether the recipient is an externally owned account (EOA) or a smart contract.
In this implementation, the withdraw
function uses payable(target).transfer(address(this).balance)
, which forwards only 2,300 gas to the recipient. If the recipient is a contract with a fallback or receive function that requires more gas (such as a multi-sig wallet or upgradeable contract), the transfer will fail and the funds will remain locked in the contract.
Likelihood:
This will occur when the withdrawal target is a contract with a fallback/receive function that needs more than 2,300 gas.
Many modern multi-sig wallets and upgradeable contracts require more gas, so this is a realistic scenario in production.
Impact:
The contract's ETH balance can become permanently locked, making it impossible for the owner to withdraw funds.
Users and protocol operators may lose access to significant amounts of ETH, leading to financial loss and loss of trust.
Past the following test to FestivalPass.t.sol::FestivalPassTest
to demonstrate the issue. It deploys a GasHeavyReceiver
contract whose receive function requires more than 2,300 gas. When withdraw
is called with this contract as the target, the transaction reverts and the funds remain locked in the contract.
Explanation:
This test shows that when the withdrawal target is a contract with a gas-heavy receive function, the withdrawal fails and the contract's balance remains unchanged, proving the risk of permanent fund lockup.
Use the recommended check-effects-interactions pattern with .call()
to forward all available gas and handle errors safely.
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.