Beatland Festival

AI First Flight #4
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

FundsWithdrawn event defined in IFestivalPass but never emitted

Summary

The IFestivalPass interface declares event FundsWithdrawn(address indexed organizer, uint256 amount), but FestivalPass.withdraw() never emits it. Every withdrawal therefore goes unrecorded on-chain, leaving off-chain accounting and monitoring with a blind spot.

Root Cause

  • Contract: src/FestivalPass.sol — function withdraw(address target) (the event itself is declared in src/interfaces/IFestivalPass.sol)

// src/interfaces/IFestivalPass.sol
event FundsWithdrawn(address indexed organizer, uint256 amount); // @> declared ...
​
// src/FestivalPass.sol
function withdraw(address target) external onlyOwner {
uint256 amount = address(this).balance;
payable(target).transfer(amount);
// @> ... but never emitted here
}

Source inspection confirms FundsWithdrawn is referenced nowhere in FestivalPass.sol besides the interface import.

Impact

  • Silent accounting gaps: indexers, dashboards, and alerting that track organizer withdrawals (the natural consumer of this event) observe nothing. Reconciling pass-sale revenue against withdrawals requires manual chain forensics instead of a simple event feed.

  • Low / informational severity: no funds are at risk and protocol behavior is unchanged; the cost is operational (monitoring, bookkeeping, incident response).

Description

The interface IFestivalPass defines FundsWithdrawn(address indexed organizer, uint256 amount) — clearly intended to be emitted whenever the owner withdraws accumulated pass-sale proceeds via withdraw(). The implementation, however, performs the ETH transfer and returns without emitting the event. This is a classic "dead event" inconsistency between interface and implementation: integrators writing subgraphs or monitors against the interface will silently miss every withdrawal.

Proof of Concept

No executable PoC test — evident by source inspection: FundsWithdrawn is declared in src/interfaces/IFestivalPass.sol and is never emitted anywhere in FestivalPass.withdraw() (or anywhere else in the codebase). A forge test asserting the absence of the event would be tautological; the one-line fix below is self-evidently correct.

Recommended Mitigation

Emit the event in withdraw() (one-line fix):

function withdraw(address target) external onlyOwner {
uint256 amount = address(this).balance;
payable(target).transfer(amount);
+ emit FundsWithdrawn(msg.sender, amount);
}

(Adjust the organizer argument to match the intended semantics if the withdrawer role differs.)

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!