The attendance cooldown mechanism is enforced per address rather than per pass, allowing users with multiple passes to bypass intended rate limiting and farm excessive rewards.
mapping(address => uint256) public lastCheckIn;
uint256 constant COOLDOWN = 1 hours;
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);
}
function testCooldownBypass() public {
address user = makeAddr("user");
vm.startPrank(user);
festival.buyPass{value: generalPrice}(GENERAL_PASS);
festival.buyPass{value: vipPrice}(VIP_PASS);
festival.buyPass{value: backstagePrice}(BACKSTAGE_PASS);
festival.attendPerformance(0);
uint256 firstBalance = beatToken.balanceOf(user);
vm.warp(block.timestamp + 30 minutes);
vm.expectRevert("Cooldown period not met");
festival.attendPerformance(1);
assert(firstBalance == baseReward * 3);
}
- mapping(address => uint256) public lastCheckIn;
+ mapping(address => mapping(uint256 => uint256)) public lastCheckInByPass; // per pass type
+
+ function attendPerformance(uint256 performanceId) external {
+ require(isPerformanceActive(performanceId), "Performance is not active");
+
+ // Determine which pass type to use for this attendance
+ uint256 passTypeUsed;
+ if (balanceOf(msg.sender, BACKSTAGE_PASS) > 0) {
+ passTypeUsed = BACKSTAGE_PASS;
+ } else if (balanceOf(msg.sender, VIP_PASS) > 0) {
+ passTypeUsed = VIP_PASS;
+ } else if (balanceOf(msg.sender, GENERAL_PASS) > 0) {
+ passTypeUsed = GENERAL_PASS;
+ } else {
+ revert("Must own a pass");
+ }
+
+ require(!hasAttended[performanceId][msg.sender], "Already attended this performance");
- require(block.timestamp >= lastCheckIn[msg.sender] + COOLDOWN, "Cooldown period not met");
+ require(block.timestamp >= lastCheckInByPass[msg.sender][passTypeUsed] + COOLDOWN, "Cooldown period not met");
hasAttended[performanceId][msg.sender] = true;
- lastCheckIn[msg.sender] = block.timestamp;
+ lastCheckInByPass[msg.sender][passTypeUsed] = block.timestamp;
- uint256 multiplier = getMultiplier(msg.sender);
+ uint256 multiplier = getPassMultiplier(passTypeUsed);
BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
}
+ function getPassMultiplier(uint256 passType) public pure returns (uint256) {
+ if (passType == BACKSTAGE_PASS) return 3;
+ if (passType == VIP_PASS) return 2;
+ if (passType == GENERAL_PASS) return 1;
+ return 0;
+ }