Beatland Festival

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

createPerformance() returns off-by-one value of performaceCount

Description

The createPerformance() function in the FestivalPass contract creates a new performance and returns performanceCount which counts the total no. of performances that have been created but the return value is 1 less than the performanceCount actually is. Due to the use of post-increment on performanceCount (initialized to 0), the first performance created returns 0 instead of 1, the second returns 1, and so forth.

This can lead to confusion in frontends, off-chain indexing, event emission parsing, and user-facing displays.

Impact/Risk

  • Low / No direct security risk: No loss of funds, no exploitation vector, no incorrect reward distribution.

  • User experience issue: Frontends may display "Performance #0" or require special handling for ID 0.

  • Potential for off-by-one errors in integrations or future extensions assuming 1-based IDs.

  • Inconsistency with other parts of the contract (e.g., memorabilia currentItemId starts at 1).

Proof of Concept

The following Foundry test demonstrates the behavior:

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import "forge-std/Test.sol";
import "../src/FestivalPass.sol";
import "../src/BeatToken.sol";
contract FestivalPassTest is Test {
FestivalPass festival;
BeatToken beatToken;
address organizer = address(0x1111);
address owner = address(this);
function setUp() public {
beatToken = new BeatToken();
festival = new FestivalPass(address(beatToken), organizer);
beatToken.setFestivalContract(address(festival));
// Make organizer able to call functions
vm.prank(owner);
festival.setOrganizer(organizer);
}
function test_FirstPerformanceReturnsId1() public {
// this is the test to prove that the returned value from the createperformance functino is not correct
vm.prank(organizer);
uint256 performanceId = festival.createPerformance(
block.timestamp + 1 hours,
2 hours,
10 ether
);
assertEq(performanceId, 0, "First performance created and returned value is 0 instead of 1");
}
}


[PASS] test_FirstPerformanceReturnsId1() (gas: 105986)
Traces:
[105986] FestivalPassTest::test_FirstPerformanceReturnsId1()
├─ [0] VM::prank(0x0000000000000000000000000000000000001111)
│ └─ ← [Return]
├─ [94535] FestivalPass::createPerformance(3601, 7200, 10000000000000000000 [1e19])
│ ├─ emit PerformanceCreated(performanceId: 0, startTime: 3601, endTime: 10801 [1.08e4])
│ └─ ← [Return] 0
└─ ← [Stop]

Running this test confirms the returned ID is 0 for the first created performance.

Relevant code snippet:

solidity

uint256 public performanceCount; // defaults to 0
// Inside createPerformance()
performances[performanceCount] = Performance({...});
emit PerformanceCreated(performanceCount, startTime, startTime + duration);
return performanceCount++; // post-increment returns current value before incrementing

Recommended Mitigation

unction createPerformance(
uint256 startTime,
uint256 duration,
uint256 reward
) external onlyOrganizer returns (uint256) {
require(startTime > block.timestamp, "Start time must be in the future");// @audit - low - it can also start in the current block
require(duration > 0, "Duration must be greater than 0");
// Set start/end times
performances[performanceCount] = Performance({
startTime: startTime,
endTime: startTime + duration,
baseReward: reward
});
emit PerformanceCreated(performanceCount, startTime, startTime + duration);
- return performanceCount++;
+ return performanceCount++;
}

This change:

  • Makes the first performance return ID 1

  • Maintains sequential IDs (1, 2, 3...)

  • Requires no other contract modifications

  • Improves UX without affecting gas or security

Alternative: Explicitly return performanceCount + 1 if keeping zero-based storage is preferred.

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!