Normal behaviour: Each festival pass (ERC-1155 IDs 1-3) should allow its holder to earn BEAT tokens once per performance.
Issue: Attendance is tracked per caller address via hasAttended[performanceId][msg.sender]. After attending, a user can transfer their pass to another address; the new holder passes the hasPass() check and the per-address guard is still false, so they can attend again. The cycle can be repeated indefinitely during the same performance window.
Likelihood:
Passes are freely transferable and trades often occur; moving a pass between wallets is common.
Performances last hours; plenty of time for an automated attacker to rotate a pass among dozens of wallets.
Impact:
Unlimited inflation of BEAT tokens; a single VIP pass can mint hundreds of times its intended reward.
Honest users’ relative share diminishes; project tokenomics and reputation suffer.
The Forge test test_PoC_PassReuse_MultipleAttendance() demonstrates the flow:
Alice buys a VIP pass and, during the concert, claims her attendance reward (2× multiplier).
She transfers that very pass to Bob while the performance is still ongoing.
Bob calls attendPerformance() for the same performanceId and receives another full reward because the contract only checked hasAttended[perfId][Bob] (which was false).
Both wallets now hold the expected BEAT balances, confirming the pass was reused.
Two viable defences:
A. Track usage per token
This assigns the NFT itself as the key, so even after transfers the pass cannot be recycled.
B. Temporarily freeze transfers
Override _beforeTokenTransfer to reject moves of IDs 1-3 while any performance is active (block timestamp between startTime and endTime). This solution is simpler but imposes UX restrictions on secondary-market trading during events.
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.