// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {Test, console2, console} from "forge-std/Test.sol"; import {DeployRaffle} from "script/DeployRaffle.s.sol"; import {Raffle} from "contracts/Raffle.sol"; import {HelperConfig, CodeConstants} from "script/HelperConfig.s.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 {FundSubscription} from "script/Interactions.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; address vrfCoordinatorV2_5; uint256 subscriptionId; bytes32 gasLane; // keyHash uint256 interval; uint256 entranceFee; uint32 callbackGasLimit; 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(); vrfCoordinatorV2_5 = config.vrfCoordinatorV2_5; subscriptionId = config.subscriptionId; gasLane = config.gasLane; interval = config.automationUpdateInterval; entranceFee = config.raffleEntranceFee; callbackGasLimit = config.callbackGasLimit; 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(); } modifier raffleEntered() { vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + interval + 1); vm.roll(block.number + 1); _; } modifier onlyOnDeployedContracts() { if (block.chainid == 31337) { return; } try vm.activeFork() returns (uint256) { return; } catch { _; } } modifier skipFork() { if (block.chainid != 31337) { return; } _; } function testRaffleInitializesInOpenState() public view { assert(raffle.getRaffleState() == Raffle.RaffleState.OPEN); } /*////////////////////////////////////////////////////////////// ENTER RAFFLE //////////////////////////////////////////////////////////////*/ function testRaffleRevertsWHenYouDontPayEnough() public { // Arrange vm.prank(PLAYER); // Act / Assert vm.expectRevert(Raffle.Raffle__NotEnoughETHEntered.selector); raffle.enterRaffle(); } function testRaffleRecordsPlayerWhenTheyEnter() public { // Arrange vm.prank(PLAYER); // Act raffle.enterRaffle{value: entranceFee}(); // 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)); // 1st true is the indexed palyer // 2nd & 3rd false because no indexed params for the Emit RaffleEnter Event emit RaffleEnter(PLAYER); raffle.enterRaffle{value: entranceFee}(); } function testDontAllowPlayersToEnterWhileRaffleIsCalculating() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + interval + 1); vm.roll(block.number + 1); raffle.performUpkeep(""); // Act / Assert vm.expectRevert(Raffle.Raffle__RaffleNotOpen.selector); vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); } /*////////////////////////////////////////////////////////////// CHECKUPKEEP //////////////////////////////////////////////////////////////*/ function testCheckUpkeepReturnsFalseIfItHasNoBalance() public { // Arrange vm.warp(block.timestamp + interval + 1); // Change time vm.roll(block.number + 1); // Blockchain added // Act (bool upkeepNeeded, ) = raffle.checkUpkeep(""); // Assert assert(!upkeepNeeded); } function testCheckUpkeepReturnsFalseIfRaffleIsntOpen() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + interval + 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: entranceFee}(); // Act (bool upkeepNeeded, ) = raffle.checkUpkeep(""); // Assert assert(!upkeepNeeded); } // Challenge 2. testCheckUpkeepReturnsTrueWhenParametersGood function testCheckUpkeepReturnsTrueWhenParametersGood() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + interval + 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: entranceFee}(); vm.warp(block.timestamp + interval + 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(); vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); currentBalance = currentBalance + entranceFee; numPlayers = numPlayers + 1; vm.warp(block.timestamp); // Not the time yet vm.roll(block.number + 1); // Act / Assert vm.expectRevert( abi.encodeWithSelector( Raffle.Raffle__UpkeepNotNeeded.selector, currentBalance, numPlayers, rState ) ); raffle.performUpkeep(""); } // Get data from Emit Event function testPerformUpkeepUpdatesRaffleStateAndEmitsRequestId() public { // Arrange vm.prank(PLAYER); raffle.enterRaffle{value: entranceFee}(); vm.warp(block.timestamp + interval + 1); vm.roll(block.number + 1); // Act vm.recordLogs(); raffle.performUpkeep(""); // emits requestId Vm.Log[] memory entries = vm.getRecordedLogs(); /* Vm.Log[] entries: bytes32[] topics; bytes data; address emitter; */ bytes32 requestId = entries[1].topics[1]; /* entriess[0] from VRF Coordinator enteries[1] 2nd log topics[0] is reserved for something else */ // Assert Raffle.RaffleState raffleState = raffle.getRaffleState(); // requestId = raffle.getLastRequestId(); assert(uint256(requestId) > 0); // is not blank assert(uint256(raffleState) == 1); // 0 = open, 1 = calculating } /*////////////////////////////////////////////////////////////// FULFILLRANDOMWORDS //////////////////////////////////////////////////////////////*/ function testFulfillRandomWordsCanOnlyBeCalledAfterPerformUpkeep( uint256 randomRequestId ) public raffleEntered skipFork { // Arrange // Act / Assert vm.expectRevert(VRFCoordinatorV2_5Mock.InvalidRequest.selector); // vm.mockCall could be used here... VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords( randomRequestId, address(raffle) ); // Try out many different requestId... //.. } 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 //vm.deal(player, 1 ether); // same raffle.enterRaffle{value: entranceFee}(); } 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 /* FundSubscription fundSubscription = new FundSubscription(); fundSubscription.fundSubscription( helperConfig.getConfig().vrfCoordinatorV2_5, helperConfig.getConfig().subscriptionId, helperConfig.getConfig().link, helperConfig.getConfig().myAccount ); */ VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords( uint256(requestId), address(raffle) ); // InsuficientBalance() // Assert address recentWinner = raffle.getRecentWinner(); Raffle.RaffleState raffleState = raffle.getRaffleState(); uint256 winnerBalance = recentWinner.balance; uint256 endingTimeStamp = raffle.getLastTimeStamp(); uint256 prize = entranceFee * (additionalEntrances + 1); assert(recentWinner == expectedWinner); assert(uint256(raffleState) == 0); assert(winnerBalance == startingBalance + prize); assert(endingTimeStamp > startingTimeStamp); } function testHoelee() public raffleEntered { 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 //vm.deal(player, 1 ether); // same raffle.enterRaffle{value: entranceFee}(); } 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) ); // InsuficientBalance() // Assert address recentWinner = raffle.getRecentWinner(); Raffle.RaffleState raffleState = raffle.getRaffleState(); uint256 winnerBalance = recentWinner.balance; uint256 endingTimeStamp = raffle.getLastTimeStamp(); uint256 prize = entranceFee * (additionalEntrances + 1); assert(recentWinner == expectedWinner); assert(uint256(raffleState) == 0); assert(winnerBalance == startingBalance + prize); assert(endingTimeStamp > startingTimeStamp); } }