withdraw Uses .transfer() — ETH Permanently Locked When Recipient Is a ContractFestivalPass.sol
The withdraw() function is the only mechanism for the owner to extract ETH from the contract. It should reliably send the full balance to any valid Ethereum address.
The function uses Solidity's .transfer() (line 148), which forwards exactly 2,300 gas to the recipient. If the recipient is a smart contract (Gnosis Safe multisig, governance timelock, DAO treasury, or any contract with a non-trivial receive() function), the call will revert due to insufficient gas. Since withdraw() sends the entire balance in a single call, a failed transfer permanently locks all ETH in the contract.
Likelihood: Medium
Triggers when the owner address is a contract (multisig, DAO, treasury). Professional deployments commonly use multisigs as owners. Also triggers if target is any contract with moderate receive() logic.
Impact: High
All collected ETH (pass sale revenue) becomes permanently irrecoverable. No fallback withdrawal mechanism exists.
Severity: Medium
The contract holds ETH from pass sales. When withdraw() is called with a contract address as the target (such as a Gnosis Safe multisig), the .transfer() call reverts because the Safe's receive() function costs more than 2,300 gas. The ETH remains locked in the FestivalPass contract with no alternative extraction method.
PoC result: test_F5_withdrawTransferDoS() — PASS (gas: 78,727). Note: full DoS demonstration would require deploying a contract with a gas-expensive receive().
Replace .transfer() with .call{value:}() and check the return value.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.