// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import {DeployRaffle} from "../../script/DeployRaffle.s.sol"; import {Raffle} from "../../src/Raffle.sol"; import {HelperConfig} from "../../script/HelperConfig.s.sol"; import {Test, console2} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {VRFCoordinatorV2_5Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol"; import {LinkToken} from "../../test/mocks/LinkToken.sol"; import {CodeConstants} from "../../script/HelperConfig.s.sol"; contract RaffleTest is Test, CodeConstants { /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ event RequestedRaffleWinner(uint256 indexed requestId); event RaffleEnter(address indexed player); event WinnerPicked(address indexed player); Raffle public raffle; HelperConfig public helperConfig; uint256 subscriptionId; bytes32 gasLane; uint256 automationUpdateInterval; uint256 raffleEntranceFee; uint32 callbackGasLimit; address vrfCoordinatorV2_5; LinkToken link; address public PLAYER = makeAddr("player"); uint256 public constant STARTING_USER_BALANCE = 10 ether; uint256 public constant LINK_BALANCE = 100 ether; function setUp() external { DeployRaffle deployer = new DeployRaffle(); (raffle, helperConfig) = deployer.run(); vm.deal(PLAYER, STARTING_USER_BALANCE); HelperConfig.NetworkConfig memory config = helperConfig.getConfig(); subscriptionId = config.subscriptionId; gasLane = config.gasLane; automationUpdateInterval = config.automationUpdateInterval; raffleEntranceFee = config.raffleEntranceFee; callbackGasLimit = config.callbackGasLimit; vrfCoordinatorV2_5 = config.vrfCoordinatorV2_5; link = LinkToken(config.link); vm.startPrank(msg.sender); if (block.chainid == LOCAL_CHAIN_ID) { link.mint(msg.sender, LINK_BALANCE); VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fundSubscription(subscriptionId, LINK_BALANCE); } link.approve(vrfCoordinatorV2_5, LINK_BALANCE); vm.stopPrank(); } function testRaffleInitializesInOpenState() public view { assert(raffle.getRaffleState() == Raffle.RaffleState.OPEN); } /*////////////////////////////////////////////////////////////// ENTER RAFFLE //////////////////////////////////////////////////////////////*/ function testRaffleRevertsWHenYouDontPayEnought() public { // Arrange vm.prank(PLAYER); // Act / Assert vm.expectRevert(Raffle.Raffle__SendMoreToEnterRaffle.selector); raffle.enterRaffle(); } function testRaffleRecordsPlayerWhenTheyEnter() public { // Arrange vm.prank(PLAYER); // Act raffle.enterRaffle{value: raffleEntranceFee}(); // Assert address playerRecorded = raffle.getPlayer(0); assert(playerRecorded == PLAYER); } function testEmitsEventOnEntrance() public { // Arrange vm.prank(PLAYER); // Act / Assert vm.expectEmit(true, false, false, false, address(raffle)); emit RaffleEnter(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); } function testDontAllowPlayersToEnterWhileRaffleIsCalculating() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); raffle.performUpkeep(""); // Act / Assert vm.expectRevert(Raffle.Raffle__RaffleNotOpen.selector); vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); } /*////////////////////////////////////////////////////////////// CHECKUPKEEP //////////////////////////////////////////////////////////////*/ function testCheckUpkeepReturnsFalseIfItHasNoBalance() public { // Arrange vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); // Act (bool upkeepNeeded,) = raffle.checkUpkeep(""); // Assert assert(!upkeepNeeded); } function testCheckUpkeepReturnsFalseIfRaffleIsntOpen() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); raffle.performUpkeep(""); Raffle.RaffleState raffleState = raffle.getRaffleState(); // Act (bool upkeepNeeded,) = raffle.checkUpkeep(""); // Assert assert(raffleState == Raffle.RaffleState.CALCULATING); assert(upkeepNeeded == false); } // Challenge 1. testCheckUpkeepReturnsFalseIfEnoughTimeHasntPassed function testCheckUpkeepReturnsFalseIfEnoughTimeHasntPassed() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); // Act (bool upkeepNeeded,) = raffle.checkUpkeep(""); // Assert assert(!upkeepNeeded); } // Challenge 2. testCheckUpkeepReturnsTrueWhenParametersGood function testCheckUpkeepReturnsTrueWhenParametersGood() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); // Act (bool upkeepNeeded,) = raffle.checkUpkeep(""); // Assert assert(upkeepNeeded); } /*////////////////////////////////////////////////////////////// PERFORMUPKEEP //////////////////////////////////////////////////////////////*/ function testPerformUpkeepCanOnlyRunIfCheckUpkeepIsTrue() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); // Act / Assert // It doesnt revert raffle.performUpkeep(""); } function testPerformUpkeepRevertsIfCheckUpkeepIsFalse() public { // Arrange uint256 currentBalance = 0; uint256 numPlayers = 0; Raffle.RaffleState rState = raffle.getRaffleState(); // Act / Assert vm.expectRevert( abi.encodeWithSelector(Raffle.Raffle__UpkeepNotNeeded.selector, currentBalance, numPlayers, rState) ); raffle.performUpkeep(""); } function testPerformUpkeepUpdatesRaffleStateAndEmitsRequestId() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); // Act vm.recordLogs(); raffle.performUpkeep(""); // emits requestId Vm.Log[] memory entries = vm.getRecordedLogs(); bytes32 requestId = entries[1].topics[1]; // Assert Raffle.RaffleState raffleState = raffle.getRaffleState(); // requestId = raffle.getLastRequestId(); assert(uint256(requestId) > 0); assert(uint256(raffleState) == 1); // 0 = open, 1 = calculating } /*////////////////////////////////////////////////////////////// FULFILLRANDOMWORDS //////////////////////////////////////////////////////////////*/ modifier raffleEntered() { vm.prank(PLAYER); raffle.enterRaffle{value: raffleEntranceFee}(); vm.warp(block.timestamp + automationUpdateInterval + 1); vm.roll(block.number + 1); _; } modifier skipFork() { if (block.chainid != 31337) { return; } _; } function testFulfillRandomWordsCanOnlyBeCalledAfterPerformUpkeep() public raffleEntered skipFork { // Arrange // Act / Assert vm.expectRevert(VRFCoordinatorV2_5Mock.InvalidRequest.selector); // vm.mockCall could be used here... VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords(0, address(raffle)); vm.expectRevert(VRFCoordinatorV2_5Mock.InvalidRequest.selector); VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords(1, address(raffle)); } function testFulfillRandomWordsPicksAWinnerResetsAndSendsMoney() public raffleEntered skipFork { address expectedWinner = address(1); // Arrange uint256 additionalEntrances = 3; uint256 startingIndex = 1; // We have starting index be 1 so we can start with address(1) and not address(0) for (uint256 i = startingIndex; i < startingIndex + additionalEntrances; i++) { address player = address(uint160(i)); hoax(player, 1 ether); // deal 1 eth to the player raffle.enterRaffle{value: raffleEntranceFee}(); } uint256 startingTimeStamp = raffle.getLastTimeStamp(); uint256 startingBalance = expectedWinner.balance; // Act vm.recordLogs(); raffle.performUpkeep(""); // emits requestId Vm.Log[] memory entries = vm.getRecordedLogs(); console2.logBytes32(entries[1].topics[1]); bytes32 requestId = entries[1].topics[1]; // get the requestId from the logs VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords(uint256(requestId), address(raffle)); // Assert address recentWinner = raffle.getRecentWinner(); Raffle.RaffleState raffleState = raffle.getRaffleState(); uint256 winnerBalance = recentWinner.balance; uint256 endingTimeStamp = raffle.getLastTimeStamp(); uint256 prize = raffleEntranceFee * (additionalEntrances + 1); assert(recentWinner == expectedWinner); assert(uint256(raffleState) == 0); assert(winnerBalance == startingBalance + prize); assert(endingTimeStamp > startingTimeStamp); } }