Description
createMemorabiliaCollection accepts an activateNow parameter that sets the collection's isActive flag. This is intended to allow the organizer to prepare a collection ahead of time and activate it later.
There is no function to change isActive after creation. A collection created with activateNow = false is permanently locked — redeemMemorabilia will always revert with "Collection not active", and the collection can never be used.
Risk
Likelihood:
The organizer creates a collection with activateNow = false intending to activate it before a specific event or performance — there is no function to do so
The activateNow parameter's existence implies deferred activation is a supported workflow, making this a likely usage pattern
Impact:
The collection is permanently unusable — all redemption attempts revert, and the organizer must deploy a new collection, wasting a collection ID slot
There is also no function to deactivate an active collection, so the organizer cannot pause redemptions during an incident or after a collection has served its purpose
Proof of Concept
Recommended Mitigation
Add an organizer function to toggle collection activation:
# Inactive Collections — Indefinite BEAT Lock-up ## Description * Normal behaviour: Organizer creates memorabilia collections with `activateNow = true` so users can immediately redeem BEAT for NFTs. * Issue: Collections can be created with `activateNow = false` and there is **no mechanism** to activate them later, nor any timeout. Users may acquire BEAT expecting to redeem specific memorabilia, but the organizer can indefinitely prevent access. ```solidity function createMemorabiliaCollection(..., bool activateNow) external onlyOrganizer { // ... validation ... @> collections[collectionId] = MemorabiliaCollection({ // ... isActive: activateNow // Can be false forever }); } function redeemMemorabilia(uint256 collectionId) external { @> require(collection.isActive, "Collection not active"); // Permanent block // ... } ``` ## Risk **Likelihood**: * Organizer may create collections in advance but forget to activate. * Intentional strategy to create hype then indefinitely delay launch. **Impact**: * Users hold BEAT tokens anticipating memorabilia that never becomes available. * Economic utility of BEAT reduced if major collections remain locked. ## Proof of Concept ```solidity function test_CollectionNeverActivated() public { // Alice gets BEAT tokens vm.prank(alice); festivalPass.buyPass{value: 0.1 ether}(VIP_PASS); // gets 5 BEAT bonus // Organizer creates inactive collection vm.prank(organizer); uint256 collectionId = festivalPass.createMemorabiliaCollection( "Limited Edition", "ipfs://limited", 3e18, 100, false // NOT activated ); // Alice tries to redeem but can't vm.prank(alice); vm.expectRevert("Collection not active"); festivalPass.redeemMemorabilia(collectionId); // Time passes, organizer chooses not to activate vm.warp(block.timestamp + 365 days); // Alice still can't redeem - funds effectively locked vm.prank(alice); vm.expectRevert("Collection not active"); festivalPass.redeemMemorabilia(collectionId); assertEq(beatToken.balanceOf(alice), 5e18, "Alice holds 'useless' BEAT"); } ``` ## Recommended Mitigation ```diff + mapping(uint256 => bool) public collectionActivated; + function activateCollection(uint256 collectionId) external onlyOrganizer { + require(collections[collectionId].priceInBeat > 0, "Collection does not exist"); + collections[collectionId].isActive = true; + collectionActivated[collectionId] = true; + } // Or add automatic timeout: + uint256 constant ACTIVATION_DEADLINE = 30 days; + mapping(uint256 => uint256) public collectionCreatedAt; function createMemorabiliaCollection(...) external onlyOrganizer { // ... + collectionCreatedAt[collectionId] = block.timestamp; } function redeemMemorabilia(uint256 collectionId) external { MemorabiliaCollection storage collection = collections[collectionId]; + bool autoActive = block.timestamp >= collectionCreatedAt[collectionId] + ACTIVATION_DEADLINE; + require(collection.isActive || autoActive, "Collection not active"); // ... } ```
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.