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;
} else if (balanceOf(user, VIP_PASS) > 0) {
return 2;
} else if (balanceOf(user, GENERAL_PASS) > 0) {
return 1;
}
return 0;
}
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:
Impact:
Proof of Concept
In test/FestivalPass.t.sol, add new test as follow:
function test_audit_noBeatTokenForPassesOfLowerTiers() public {
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);
uint256 startTime = block.timestamp + 1 hours;
vm.prank(organizer);
uint256 perfId = festivalPass.createPerformance(startTime, 2 hours, 50e18);
vm.warp(startTime + 30 minutes);
vm.prank(user1);
festivalPass.attendPerformance(perfId);
uint256 userBeatTokenAftAttendPerformance = beatToken.balanceOf(user1);
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.