No setter function exists for the activateNow boolean flag
The activateNow boolean flag controls whether the contract's core functionality — likely pass minting or purchasing — is currently active. This flag is set once in the constructor at deployment time and is never subsequently modifiable, because the contract contains no owner-gated or admin-gated setter function to update it.
This design creates a rigid on/off state that cannot adapt to real-world operational needs. If the contract is deployed with activateNow = true, there is no way to pause it in response to a discovered vulnerability, unexpected market conditions, or regulatory concerns — standard operational safeguards that every production contract should have. Conversely, if deployed with activateNow = false, there is no way to ever activate it, rendering the contract permanently non-functional.
While this is not an exploitable vulnerability in the traditional sense — an attacker cannot manipulate activateNow — the absence of an activation toggle removes a critical safety lever. Industry-standard contracts (including OpenZeppelin's Pausable module) treat pausability as a non-negotiable safety feature precisely because emergencies are unpredictable. A contract that cannot be paused is a contract that cannot be protected once deployed.
Likelihood:
This vulnerability is rated Low likelihood as an exploitable attack vector — no external party can change the flag maliciously. However, the operational risk is near-certain to manifest at some point: virtually every production contract eventually needs an emergency pause. The likelihood that the missing setter will become a problem during the contract's lifetime is high, even if the probability of a targeted exploit is low.
Impact:
This vulnerability is rated Medium impact. If the contract is deployed in the wrong activation state, it is either permanently unusable or permanently un-pausable. In the event of a security incident elsewhere in the system, the inability to pause the pass contract could allow continued exploitation while the team scrambles to deploy a replacement. The redeployment cost — including user migration, loss of minted token history, and reputational damage — is significant. This is classified as medium rather than critical because the flag itself does not control fund custody, only access.
The proof of concept is straightforward: after deployment with activateNow = false, there is no transaction the owner can send to enable the contract. Any user interaction gated behind the require(activateNow) check will revert indefinitely. The contract must be redeployed to change this state, which forfeits any existing state such as previously registered buyers or configuration.
At minimum, add an onlyOwner setter that updates activateNow and emits an event for off-chain indexers. The event is important — it allows explorers and monitoring tools to track activation history and alert on unexpected state changes. The strongly recommended approach is to replace the custom flag entirely with OpenZeppelin's Pausable module, which is audited, well-understood, and integrates cleanly with whenNotPaused and whenPaused modifiers on any function that needs to respect the activation state.
# 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.