Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Missing Access Control on Critical Festival Operations

Description

  • The normal behavior should prevent modification of active festival parameters that could disadvantage existing participants

  • The current implementation allows organizers to change pass prices, performance rewards, and collection settings without restrictions or user protection

function configurePass(
uint256 passId,
uint256 price,
uint256 maxSupply
) external onlyOrganizer {
require(passId == GENERAL_PASS || passId == VIP_PASS || passId == BACKSTAGE_PASS, "Invalid pass ID");
require(price > 0, "Price must be greater than 0");
require(maxSupply > 0, "Max supply must be greater than 0");
@> passPrice[passId] = price; // Can change price anytime
@> passMaxSupply[passId] = maxSupply; // Can reduce supply below current sales
@> passSupply[passId] = 0; // Resets current supply unconditionally
}
function createPerformance(
uint256 startTime,
uint256 duration,
uint256 reward
) external onlyOrganizer returns (uint256) {
@> // No validation on reward amounts or limits
@> // No protection against unrealistic values
performances[performanceCount] = Performance({
startTime: startTime,
endTime: startTime + duration,
baseReward: reward
});
}

Risk

Likelihood:

  • Organizers have financial incentives to modify parameters in their favor

  • Users cannot verify configuration changes until after they occur

Impact:

  • Price manipulation leading to unfair advantage for connected buyers

  • Supply reduction below already sold amounts causing logical inconsistencies

  • Excessive reward creation leading to token inflation

  • Loss of user trust and potential legal issues

Proof of Concept

function testOrganiserManipulation() public {
// Initial fair configuration
vm.startPrank(organizer);
festival.configurePass(VIP_PASS, 1 ether, 100);
vm.stopPrank();
// Users buy passes at fair price
vm.startPrank(user1);
festival.buyPass{value: 1 ether}(VIP_PASS);
vm.stopPrank();
// Organizer manipulates after sales begin
vm.startPrank(organizer);
// Increase price dramatically for remaining passes
festival.configurePass(VIP_PASS, 10 ether, 100);
// Create performance with excessive rewards
festival.createPerformance(
block.timestamp + 1 hours,
1 hours,
1000000 ether // Unrealistic reward amount
);
vm.stopPrank();
// New users face 10x price increase
vm.startPrank(user2);
vm.expectRevert(); // Cannot afford new price
festival.buyPass{value: 1 ether}(VIP_PASS);
vm.stopPrank();
}

Recommended Mitigation

+ mapping(uint256 => bool) public passConfigurationLocked;
+ uint256 public constant MAX_REWARD_PER_PERFORMANCE = 1000 ether;
function configurePass(
uint256 passId,
uint256 price,
uint256 maxSupply
) external onlyOrganizer {
require(passId == GENERAL_PASS || passId == VIP_PASS || passId == BACKSTAGE_PASS, "Invalid pass ID");
require(price > 0, "Price must be greater than 0");
require(maxSupply > 0, "Max supply must be greater than 0");
+ require(!passConfigurationLocked[passId], "Pass configuration is locked");
+ require(maxSupply >= passSupply[passId], "Cannot reduce supply below current sales");
- passPrice[passId] = price;
+ // Only allow price changes if no passes sold yet
+ if (passSupply[passId] == 0) {
+ passPrice[passId] = price;
+ }
passMaxSupply[passId] = maxSupply;
- passSupply[passId] = 0;
+ // Don't reset supply unless explicitly needed
}
+ function lockPassConfiguration(uint256 passId) external onlyOrganizer {
+ passConfigurationLocked[passId] = true;
+ emit PassConfigurationLocked(passId);
+ }
function createPerformance(
uint256 startTime,
uint256 duration,
uint256 reward
) external onlyOrganizer returns (uint256) {
require(startTime > block.timestamp, "Start time must be in the future");
require(duration > 0, "Duration must be greater than 0");
+ require(reward <= MAX_REWARD_PER_PERFORMANCE, "Reward exceeds maximum allowed");
+ require(duration <= 24 hours, "Performance duration too long");
performances[performanceCount] = Performance({
startTime: startTime,
endTime: startTime + duration,
baseReward: reward
});
emit PerformanceCreated(performanceCount, startTime, startTime + duration);
return performanceCount++;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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