Beatland Festival

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

Rewards are only granted for the highest-tier pass, even if the user holds multiple passes of different tiers

Summary

For user who buys multiple passes of different tiers to cater the budget needs of their friends/family members, only the highest tier pass will earn bonus beat tokens when attending performance, other passes are found not given beat tokens due to the function getMultiplier which returns the multiplier value once it detects the matched tier by checking from the high tier BackStage, VIP followed by General tier pass.

Description

The getMultiplier in the attendPerformance function returns the multiplier value once it detects the matched tier starting from the high tier BackStage, VIP followed by General tier pass.

function getMultiplier(address user) public view returns (uint256) {
if (balanceOf(user, BACKSTAGE_PASS) > 0) {
return 3; // 3x for BACKSTAGE
} else if (balanceOf(user, VIP_PASS) > 0) {
return 2; // 2x for VIP
} else if (balanceOf(user, GENERAL_PASS) > 0) {
return 1; // 1x for GENERAL
}
return 0; // No pass
}

For user who buys multiple passes of different tiers, with the checks in getMultiplier, any other passes after the highest tier pass won't earn them extra beat tokens.

Risk

Likelihood:

  • Happen to users who help to buy multiple passes of different tiers for friends or family members with different budget needs

Impact:

  • User who has passes of multiple tiers can only earn bonus beat tokens on 1 highest tier pass. Extra passes of lower tiers won't be able to get them the bonus beat tokens. A reward system that is not fair for multi pass users/supporters.

Proof of Concept

In test/FestivalPass.t.sol, add new test as follow:

function test_audit_noBeatTokenForPassesOfLowerTiers() public {
// user buys multiple passes of different tiers to cater the budget needs of friends
vm.startPrank(user1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
festivalPass.buyPass{value: VIP_PRICE}(2);
festivalPass.buyPass{value: BACKSTAGE_PRICE}(3);
vm.stopPrank();
assertEq(festivalPass.balanceOf(user1, 1), 2);
assertEq(festivalPass.balanceOf(user1, 2), 1);
assertEq(festivalPass.balanceOf(user1, 3), 1);
assertEq(festivalPass.passSupply(1), 2);
assertEq(festivalPass.passSupply(2), 1);
assertEq(festivalPass.passSupply(2), 1);
assertEq(beatToken.balanceOf(user1), 20e18); // earn 5e18 + 15e18 since user buys 1 VIP and 1 backstage
// Organizer creates a performance
uint256 startTime = block.timestamp + 1 hours;
vm.prank(organizer);
uint256 perfId = festivalPass.createPerformance(startTime, 2 hours, 50e18);
// Warp to performance time
vm.warp(startTime + 30 minutes);
// User attends performance with friends
vm.prank(user1);
festivalPass.attendPerformance(perfId);
uint256 userBeatTokenAftAttendPerformance = beatToken.balanceOf(user1);
// user should earn beatToken via his 1, general, 1 VIP and 1 backstage passes upon purchase and attend performance
// reward upon purchase: (5e18 + 15e18)
// reward upon attending preformance: (50e18 * 1 * 1) + (50e18 * 2 * 1) + (50e18 * 3 * 1)
uint256 userBeatTokenExpectedBalance = (50e18 * 1 * 1) + (50e18 * 2 * 1) + (50e18 * 3 * 1) + (5e18 + 15e18);
console.log("User beat token balance after attending performance: %d", userBeatTokenAftAttendPerformance);
console.log("User beat token expected balance: %d", userBeatTokenExpectedBalance);
assertTrue(festivalPass.hasAttended(perfId, user1));
assert(userBeatTokenAftAttendPerformance != userBeatTokenExpectedBalance);
}

Run the test in terminal as follow:

$ forge test --match-test test_audit_noBeatTokenForPassesOfLowerTiers -vvv [13:18:52]
[⠊] Compiling...
[⠑] Compiling 1 files with Solc 0.8.25
[⠘] Solc 0.8.25 finished in 651.10ms
Compiler run successful!
Ran 1 test for test/FestivalPass.t.sol:FestivalPassTest
[PASS] test_audit_noBeatTokenForPassesOfLowerTiers() (gas: 459633)
Logs:
User beat token balance after attending performance:
170000000000000000000
User beat token expected balance:
320000000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.47ms (263.33µs CPU time)
Ran 1 test suite in 119.48ms (1.47ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

The test shows that the beat token balance of the user after attending performance is not tally to the expected balance that the user shall have.

Recommended Mitigation

To revisit reward system by factoring the number of passes a user has and return the multiplier accordingly for the passes of different tiers.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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