Beatland Festival

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

BeatToken.burnFrom burns from any address with no allowance check, deviating from ERC20 semantics (safe only due to the single trusted caller)

BeatToken.burnFrom performs an unchecked burn with no allowance, deviating from ERC20 semantics

Description

BeatToken.burnFrom calls _burn(from, amount) (BeatToken.sol:24-27) after only checking that the caller is the festival contract. It never verifies that from approved the burn, so the standard ERC20 burnFrom allowance semantics are silently violated. It is safe only by accident: the sole current caller, FestivalPass.redeemMemorabilia (FestivalPass.sol:197), always passes msg.sender as from.

function burnFrom(address from, uint256 amount) external {
require(msg.sender == festivalContract, "Only_Festival_Burn");
_burn(from, amount); // @> no allowance check on `from`
}

Risk

Likelihood:

Low. Exploitation requires the festival contract to be upgraded or extended with a new code path that passes an arbitrary from. Today's only caller is self-referential, so no live attack exists.

Impact:

Low. As written there is no loss, but the function is a latent footgun: any future FestivalPass function that calls burnFrom(victim, amount) would burn a victim's BEAT with zero consent, since the privileged-caller check is the only gate. A function named burnFrom that ignores allowances will mislead future maintainers into assuming standard, consent-based behavior.

Proof of Concept

If a future festival path forwards a non-caller from, a victim's balance is burned without approval.

// hypothetical future FestivalPass function
BeatToken(beatToken).burnFrom(victim, BeatToken(beatToken).balanceOf(victim));
// succeeds: only msg.sender == festivalContract is checked, victim never approved

Recommended Mitigation

Enforce an allowance (use ERC20 _spendAllowance) or rename to make the privileged, self-burn intent explicit.

- function burnFrom(address from, uint256 amount) external {
- require(msg.sender == festivalContract, "Only_Festival_Burn");
- _burn(from, amount);
+ function burnFrom(address from, uint256 amount) external {
+ require(msg.sender == festivalContract, "Only_Festival_Burn");
+ _spendAllowance(from, msg.sender, amount);
+ _burn(from, amount);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours 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!