Beatland Festival

First Flight #44
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Users Can Attend Multiple Performances Simultaneously to Farm Rewards

Users Can Attend Multiple Performances Simultaneously to Farm Rewards

Description

  • The protocol reward the user to atteding a performance by minting him/her a specific number of beatTokens based on the pass level (general, VIP or backstage) and the reward set this performance. reward = typeOfPass * performanceReward.

  • This reward is used to provide the user an incentive to attend the performacne.

  • If the system allows a user to attend multiple performances at the same timestamp, they may exploit this to accumulate rewards disproportionately, especially if attendance is rewarded per performance.

  • So even if multiple performances in the festival are taking place at the same time, the user is able to attend them all and get the reward from each one.

function attendPerformance(uint256 performanceId) external {
require(isPerformanceActive(performanceId), "Performance is not active");
require(hasPass(msg.sender), "Must own a pass");
require(!hasAttended[performanceId][msg.sender], "Already attended this performance");
require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");
hasAttended[performanceId][msg.sender] = true;
lastCheckIn[msg.sender] = block.timestamp;
uint256 multiplier = getMultiplier(msg.sender);
@> BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
emit Attended(msg.sender, performanceId, performances[performanceId].baseReward * multiplier);
}

Risk

Likelihood:

  • High; in an festival multiple performances will take place at the same time. The festival is designed to host multiple performances concurrently.

Impact:

  • Users can accumulate multiple rewards by attending overlapping performances, which leads to reward inflation and unfair distribution.

  • Users can accumulate multiple rewards by attending overlapping performances, which leads to reward inflation and unfair distribution.

Proof of Concept

  • Organizer create 3 performances with different duration and rewards

  • the three performances overlap in time

  • user will attend the 3 performances at the same time and claim the reward of the three perfromances in beatToken.

function test_userCanAttendMutiplePerformanceAtSameTime() public {
address user = makeAddr("user");
uint256 startTime = block.timestamp + 1 hours;
uint256 duration1 = 2 hours;
uint256 duration2 = 1 hours;
uint256 duration3 = 3 hours;
uint256 reward1 = 100e18;
uint256 reward2 = 50e18;
uint256 reward3 = 150e18;
// organizer configure the pass then
// organizer create three performances that with same start time and same reward
vm.startPrank(organizer);
festivalPass.configurePass(1, GENERAL_PRICE, GENERAL_MAX_SUPPLY);
festivalPass.configurePass(2, VIP_PRICE, VIP_MAX_SUPPLY);
festivalPass.configurePass(3, BACKSTAGE_PRICE, BACKSTAGE_MAX_SUPPLY);
uint256 perfId1 = festivalPass.createPerformance(startTime, duration1, reward1);
uint256 perfId2 = festivalPass.createPerformance(startTime, duration2, reward2);
uint256 perfId3 = festivalPass.createPerformance(startTime, duration3, reward3);
vm.stopPrank();
// user buy general pass
deal(user, GENERAL_PRICE); // deal user the ether to buy general pass
vm.prank(user);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
// get the user initial beatToken balance
uint256 userBalanceBefore = beatToken.balanceOf(user);
assertEq(userBalanceBefore, 0, "User beatToken balance must be zero");
// warp to time of performace start; keep in mind the 3 performance are overlapping
vm.warp(block.timestamp + 1 hours + 1 minutes);
// user will attend the 3 performances at the same time earning the 3 rewards
vm.startPrank(user);
festivalPass.attendPerformance(perfId1);
festivalPass.attendPerformance(perfId2);
festivalPass.attendPerformance(perfId3);
vm.stopPrank();
uint256 userBalanceAfter = beatToken.balanceOf(user);
assertEq(userBalanceAfter, reward1 + reward2 + reward3);
}

Recommended Mitigation

One check + minting beatToken based on time spent

  • First, the system must verify that the user is not already attending another performance at the time they call attendPerformance.

  • Second, if rewards are issued to incentivize attendance, the minting of beatToken should be proportional to the actual time spent in a specific performance. Because if only the above check is added, the user can enter and exit to claim rewards.

  • An alternative is to mint the user the beatToken on entrace of a specific performance, but set a lock to prevent the user to leave or attend another performance equal to performance duration.


Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unlimited beat farming by transferring passes to other addresses.

Support

FAQs

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