Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Missing withdrawal checks

Missing withdrawal safety checks + Unnecessary gas costs and loss of funds

Description

  • Missing safety checks in the withdraw function present in line 146-149 of FestivalPass.sol

  • In the function withdraw(address target), missing safety checks before withdrawal of funds

  • "target" address check for validity is missing

  • Missing check for invalid/zero balance before withdrawal

// @ line 147-149 from FestivalPass.sol
// Missing address check for "target"
// Missing zero balance check before withdrawal
// Organizer withdraws ETH
function withdraw(address target) external onlyOwner {
payable(target).transfer(address(this).balance);
}

Risk

Likelihood: HIGH

  • Reason 1: The function can be called with target = address(0), leading to ETH being irreversibly burned to the zero address — a scenario likely to occur during development, misconfigured frontend interactions, or mistakes in multisig calls.

  • Reason 2: The function allows withdrawals even when the contract balance is 0, which will still consume gas and emit confusing or misleading off-chain signals about fund movement (e.g., on-chain monitoring tools or event triggers).

Impact: HIGH

  • Impact 1: Calling transfer(address(0)) results in a permanent loss of all ETH in the contract — this is economically damaging and can harm trust in the protocol.

  • Impact 2: Allowing withdrawals with a 0 balance may lead to unnecessary gas costs and create a false impression of successful payouts, confusing users or automated systems that track fund flows.

Proof of Concept

Vulnerability Summary

  • Location: FestivalPass.sol, function withdraw(address target)

  • Lines:

    payable(target).transfer(address(this).balance);

  • Issue:

    • No validation that target is a valid (non-zero) address.

    • No check for address(this).balance > 0

  • Type: Funds loss / Misconfigured execution / Missing precondition

PoC Structure (Foundry test)
Setup

  • Deploy a mock BeatToken and FestivalPass contract.

  • Fund the FestivalPass contract with 10 ETH.

  • Set owner and attacker addresses for roles.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import "forge-std/Test.sol";
import "../src/FestivalPass.sol";
import "../src/BeatToken.sol";
contract WithdrawPoC is Test {
FestivalPass public festival;
BeatToken public beat;
address public owner = address(0xABCD);
address public attacker = address(0xDEAD);
function setUp() public {
// Deploy BEAT token and FestivalPass contract
beat = new BeatToken();
vm.prank(owner);
festival = new FestivalPass(address(beat), owner);
// Label for clarity
vm.label(owner, "Owner");
vm.label(attacker, "Attacker");
// Fund the contract with ETH
vm.deal(address(festival), 10 ether);
}
function testWithdrawToZeroAddress() public {
uint256 preContractBalance = address(festival).balance;
assertEq(preContractBalance, 10 ether);
// Owner withdraws to address(0) (should NOT be allowed)
vm.prank(owner);
festival.withdraw(address(0));
// ✅ This call will succeed and burn ETH to address(0)
// 🧨 Let's assert damage:
uint256 postContractBalance = address(festival).balance;
assertEq(postContractBalance, 0, "ETH should be lost");
// No one can recover ETH from address(0)
}
function testWithdrawWithZeroBalance() public {
// Deploy a fresh contract with no ETH
vm.prank(owner);
FestivalPass emptyFestival = new FestivalPass(address(beat), owner);
uint256 contractBalance = address(emptyFestival).balance;
assertEq(contractBalance,0, "Zero balance contract"); // This passes as contract balance is 0
vm.prank(owner);
emptyFestival.withdraw(attacker); // 🧪 executes but sends 0 ETH
// This is wasteful and misleading; should revert or no-op
}
}

Business Impact

  • Silent ETH Loss: Without a check, a single mistake (passing address(0)) can permanently destroy all funds.

  • Wasted Gas + Monitoring Confusion: Triggering withdraw() on empty contracts can trip bots, analytics, or mislead users into thinking payouts were successful.

Recommended Mitigation

This mitigation does :

  • Adds zero address check before withdrawal of funds, ensures funds are not lost due to invalid address.

  • Adds zero balance check before withdrawal to prevent unnecessary gas cost

- function withdraw(address target) external onlyOwner {
- payable(target).transfer(address(this).balance);
- }
+ function withdraw(address target) external onlyOwner {
+ require(target != address(0), "Invalid address");
+ uint256 balance = address(this).balance;
+ require(balance > 0, "No ETH to withdraw");
+ payable(target).transfer(balance);
+ }
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Zero address check

Owner/admin is trusted / Zero address check - Informational

Appeal created

cipherknight666 Submitter
30 days ago
inallhonesty Lead Judge
29 days ago
inallhonesty Lead Judge 27 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Zero address check

Owner/admin is trusted / Zero address check - Informational

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.