385 lines
13 KiB
Solidity
385 lines
13 KiB
Solidity
// 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);
|
|
}
|
|
}
|