Added Full Test
Some checks are pending
CI / Foundry project (push) Waiting to run

This commit is contained in:
hoelee 2024-08-15 16:45:28 +08:00
parent 10c4f306f6
commit 12a67132f2
16 changed files with 539 additions and 113 deletions

2
.gitmodules vendored
View File

@ -9,4 +9,4 @@
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
url = https://github.com/transmissions11/solmate

View File

@ -64,3 +64,6 @@ $ forge --help
$ anvil --help
$ cast --help
```
cast --to-base 0x714c2 dec

View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity >= 0.4.21 < 0.9.0;
contract DbgEntry {
event EvmPrint(string);
constructor() {
emit EvmPrint("DbgEntry.constructor");
// Here you can either deploy your contracts via `new`, eg:
// Counter counter = new Counter();
// counter.increment();
// or interact with an existing deployment by specifying a `fork` url in `dbg.project.json`
// eg:
// ICounter counter = ICounter(0x12345678.....)
// counter.increment();
//
// If you have correct symbols (`artifacts`) for the deployed contract, you can step-into calls.
uint256 abc = 123;
uint256 def = abc + 5;
emit EvmPrint("DbgEntry return");
}
}

View File

@ -0,0 +1,13 @@
{
"entryPoint": "DbgEntry",
"solc": "0.8.26",
"sourceDirs": [
"."
],
"breakOnEntry": false,
"fork": {
"enable": false,
"url": "",
"blockNumber": 0
}
}

View File

@ -28,25 +28,26 @@ import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFCo
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
import "@chainlink/contracts/src/v0.8/vrf/dev/interfaces/IVRFCoordinatorV2Plus.sol";
import "@chainlink/contracts/src/v0.8/automation/interfaces/AutomationCompatibleInterface.sol";
//import "hardhat/console.sol";
error Raffle__NotEnoughETHEntered();
error Raffle__TransferFailed();
error Raffle__RaffleNotOpen();
error Raffle__IntervalNotPassed();
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);
//import "hardhat/console.sol";
/**
* @title Automatic Raffle Contract
* @author Hoelee
* @notice This contract is a lottery contract that allows users to enter the lottery by paying a fee.
* @dev Raffle Contract
Enter the lottery (paying some amount)
Pick a random winner (verifiably random)
Winner to be selected every X minutes -> completely automated
Chainlink Oracle -> Randomness, Automated Execution (Chainlink Keeper)
* Enter the lottery (paying some amount)
* Pick a random winner (verifiably random)
* Winner to be selected every X minutes -> completely automated
* Chainlink Oracle -> Randomness, Automated Execution (Chainlink Keeper)
*/
contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
error Raffle__NotEnoughETHEntered();
error Raffle__TransferFailed();
error Raffle__RaffleNotOpen();
error Raffle__IntervalNotPassed();
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);
/* Type declarations */
enum RaffleState {
OPEN,
@ -71,8 +72,8 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
uint256 private immutable i_entranceFee;
/* Event */
event RequestedRaffleWinner(uint256 indexed requestId);
event RaffleEnter(address indexed player);
event RequestedRaffleWinner(uint256 indexed requestId);
event WinnerPicked(address indexed player);
/* Function */
@ -166,7 +167,7 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
extraArgs: extraArgs
})
);
emit RequestedRaffleWinner(requestId);
emit RequestedRaffleWinner(requestId); // Redundant, because i_vrfCoordinator do it same
}
/**
@ -177,6 +178,7 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
* 2. The lottery is open.
* 3. The contract has ETH.
* 4. Implicity, your subscription is funded with LINK.
* @return upkeepNeeded - true will call performUpKeep automatically
*/
function checkUpkeep(
bytes memory /* checkData */
@ -230,8 +232,15 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
emit RequestedRaffleWinner(requestId);
}
/*
* CEI: Check, Effects, Interactions Pattern
Check : Conditional - eg: Ensure is the raffle state is open
Effects : Internal Contract State
Interactions : External Contract Interactions
* @dev chainlink VRF callback after run vrfCoordinator.requestRandomWords
*/
function fulfillRandomWords(
uint256 /* requestId */,
uint256, // requestId
uint256[] calldata randomWords
) internal override {
// s_players size 10
@ -244,14 +253,15 @@ contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
address payable recentWinner = s_players[indexOfWinner];
s_recentWinner = recentWinner;
s_players = new address payable[](0);
s_raffleState = RaffleState.OPEN;
s_lastTimeStamp = block.timestamp;
s_raffleState = RaffleState.OPEN;
emit WinnerPicked(recentWinner);
(bool success, ) = recentWinner.call{value: address(this).balance}("");
// require(success, "Transfer failed");
if (!success) {
revert Raffle__TransferFailed();
}
emit WinnerPicked(recentWinner);
}
function getEntranceFee() public view returns (uint256) {

View File

@ -14,6 +14,7 @@ contract SimpleStorage {
string name;
}
// uint256[] public anArray;
Person[] public listOfPeople;
mapping(string => uint256) public nameToFavoriteNumber;

13
dbg.project.json Normal file
View File

@ -0,0 +1,13 @@
{
"contractsDir": "contracts-dbg",
"selectedContract": "Test1",
"autoOpen": true,
"breakOnEntry": false,
"symbols": {
"hardhat": {
"projectPaths": [
"/home/hoelee/hardhat/hardhat-smartcontract-lottery"
]
}
}
}

View File

@ -3,6 +3,10 @@ src = "contracts"
out = "out"
libs = ["lib"]
#evm_version = "berlin" # for zksync only
fs_permissions = [
{access = "read", path="./broadcast"},
{access = "read", path="./reports"},
]
remappings = [
"foundry-devops/=lib/foundry-devops/src/",
@ -17,4 +21,8 @@ sepolia = {key = "${ETHERSCAN_API_KEY}"}
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
[fuzz]
runs = 256 # Foundry will run 256 fuzz tests
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

@ -1 +1 @@
Subproject commit 97bdb2003b70382996a79a406813f76417b1cf90
Subproject commit a9e3ea26a2dc73bfa87f0cb189687d029028e0c5

View File

@ -24,15 +24,16 @@
"@chainlink/token": "^1.1.0",
"@openzeppelin/contracts": "^5.0.2",
"babel-eslint": "^10.1.0",
"dotenv": "^16.4.5"
"dotenv": "^16.4.5",
"dotnet": "^1.1.4"
},
"scripts": {
"test": "yarn hardhat test",
"test-staging": "yarn hardhat test --network sepolia",
"lint": "yarn solhint 'contracts/*.sol'",
"lint:fix": "yarn solhint 'contracts/**/*.sol' --fix",
"format": "yarn prettier --write .",
"coverage": "yarn hardhat coverage"
"test": "yarn hardhat test",
"test-staging": "yarn hardhat test --network sepolia",
"lint": "yarn solhint 'contracts/*.sol'",
"lint:fix": "yarn solhint 'contracts/**/*.sol' --fix",
"format": "yarn prettier --write .",
"coverage": "yarn hardhat coverage"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -3,7 +3,7 @@ pragma solidity ^0.8.19;
import {Script} from "forge-std/Script.sol";
import {HelperConfig} from "./HelperConfig.s.sol";
import {Raffle} from "../contracts/Raffle.sol";
import {Raffle} from "contracts/Raffle.sol";
import {AddConsumer, CreateSubscription, FundSubscription} from "./Interactions.s.sol";
contract DeployRaffle is Script {
@ -15,20 +15,20 @@ contract DeployRaffle is Script {
if (config.subscriptionId == 0) {
CreateSubscription createSubscription = new CreateSubscription();
(config.subscriptionId, config.vrfCoordinatorV2_5) = createSubscription
.createSubscription(config.vrfCoordinatorV2_5, config.account);
.createSubscription(config.vrfCoordinatorV2_5, config.myAccount);
FundSubscription fundSubscription = new FundSubscription();
fundSubscription.fundSubscription(
config.vrfCoordinatorV2_5,
config.subscriptionId,
config.link,
config.account
config.myAccount
);
helperConfig.setConfig(block.chainid, config);
}
vm.startBroadcast(config.account);
vm.startBroadcast(config.myAccount);
Raffle raffle = new Raffle(
config.vrfCoordinatorV2_5,
config.subscriptionId,
@ -44,7 +44,7 @@ contract DeployRaffle is Script {
address(raffle),
config.vrfCoordinatorV2_5,
config.subscriptionId,
config.account
config.myAccount
);
return (raffle, helperConfig);
}

View File

@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {LinkToken} from "../test/mocks/LinkToken.sol";
import {Script, console2} from "forge-std/Script.sol";
import {VRFCoordinatorV2_5Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
import {LinkToken} from "../test/mocks/LinkToken.sol";
abstract contract CodeConstants {
uint96 public MOCK_BASE_FEE = 0.25 ether;
@ -35,7 +35,7 @@ contract HelperConfig is CodeConstants, Script {
uint32 callbackGasLimit;
address vrfCoordinatorV2_5;
address link;
address account;
address myAccount;
}
/*//////////////////////////////////////////////////////////////
@ -81,20 +81,21 @@ contract HelperConfig is CodeConstants, Script {
callbackGasLimit: 500000, // 500,000 gas
vrfCoordinatorV2_5: 0x271682DEB8C4E0901D1a1550aD2e64D568E69909,
link: 0x514910771AF9Ca656af840dff83E8264EcF986CA,
account: 0x643315C9Be056cDEA171F4e7b2222a4ddaB9F88D
myAccount: 0x643315C9Be056cDEA171F4e7b2222a4ddaB9F88D
});
}
function getSepoliaEthConfig() public pure returns (NetworkConfig memory sepoliaNetworkConfig) {
sepoliaNetworkConfig = NetworkConfig({
subscriptionId: 0, // If left as 0, our scripts will create one!
// If left as 0, our scripts will create one!
subscriptionId: 54852953177758767717007928774683925681326589777506065303357242036079980899870,
gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae,
automationUpdateInterval: 30, // 30 seconds
raffleEntranceFee: 0.01 ether,
raffleEntranceFee: 0.01 ether, // 1e16
callbackGasLimit: 500000, // 500,000 gas
vrfCoordinatorV2_5: 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B,
link: 0x779877A7B0D9E8603169DdbD7836e478b4624789,
account: 0x643315C9Be056cDEA171F4e7b2222a4ddaB9F88D
link: 0x779877A7B0D9E8603169DdbD7836e478b4624789, // This is fixed LINK token address
myAccount: 0xc9445E993dAeA4bA3F1FE1080F0F6f8c46b4d967 // my address with supplied private key
});
}
@ -112,6 +113,7 @@ contract HelperConfig is CodeConstants, Script {
MOCK_GAS_PRICE_LINK,
MOCK_WEI_PER_UINT_LINK
);
LinkToken link = new LinkToken();
uint256 subscriptionId = vrfCoordinatorV2_5Mock.createSubscription();
vm.stopBroadcast();
@ -124,9 +126,9 @@ contract HelperConfig is CodeConstants, Script {
callbackGasLimit: 500000, // 500,000 gas
vrfCoordinatorV2_5: address(vrfCoordinatorV2_5Mock),
link: address(link),
account: FOUNDRY_DEFAULT_SENDER
myAccount: FOUNDRY_DEFAULT_SENDER
});
vm.deal(localNetworkConfig.account, 100 ether);
vm.deal(localNetworkConfig.myAccount, 100 ether);
return localNetworkConfig;
}
}

View File

@ -16,16 +16,16 @@ contract CreateSubscription is Script {
address vrfCoordinatorV2_5 = helperConfig
.getConfigByChainId(block.chainid)
.vrfCoordinatorV2_5;
address account = helperConfig.getConfigByChainId(block.chainid).account;
return createSubscription(vrfCoordinatorV2_5, account);
address myAccount = helperConfig.getConfigByChainId(block.chainid).myAccount;
return createSubscription(vrfCoordinatorV2_5, myAccount);
}
function createSubscription(
address vrfCoordinatorV2_5,
address account
address myAccount
) public returns (uint256, address) {
console.log("Creating subscription on chainId: ", block.chainid);
vm.startBroadcast(account);
vm.startBroadcast(myAccount);
uint256 subId = VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).createSubscription();
vm.stopBroadcast();
console.log("Your subscription Id is: ", subId);
@ -38,28 +38,89 @@ contract CreateSubscription is Script {
}
}
contract AddConsumer is Script {
function addConsumer(
address contractToAddToVrf,
address vrfCoordinator,
uint256 subId,
address account
) public {
console.log("Adding consumer contract: ", contractToAddToVrf);
console.log("Using vrfCoordinator: ", vrfCoordinator);
console.log("On ChainID: ", block.chainid);
vm.startBroadcast(account);
VRFCoordinatorV2_5Mock(vrfCoordinator).addConsumer(subId, contractToAddToVrf);
vm.stopBroadcast();
/*
Fund Subscription Script:
forge script script/Interactions.s.sol:FundSubscription --rpc-url $SEPOLIA_RPC_URL --broadcast --private-key $SEPOLIA_PRIVATE_KEY
forge script script/Interactions.s.sol:FundSubscription --rpc-url $SEPOLIA_RPC_URL --broadcast --myAccount hoelee
*/
contract FundSubscription is CodeConstants, Script {
uint96 public constant FUND_AMOUNT = 0.5 ether;
function fundSubscriptionUsingConfig() public {
HelperConfig helperConfig = new HelperConfig();
uint256 subId = helperConfig.getConfig().subscriptionId;
address vrfCoordinatorV2_5 = helperConfig.getConfig().vrfCoordinatorV2_5;
address link = helperConfig.getConfig().link;
address myAccount = helperConfig.getConfig().myAccount;
if (subId == 0) {
CreateSubscription createSub = new CreateSubscription();
(uint256 updatedSubId, address updatedVRFv2) = createSub.run();
subId = updatedSubId;
vrfCoordinatorV2_5 = updatedVRFv2;
console.log("New SubId Created! ", subId, "VRF Address: ", vrfCoordinatorV2_5);
}
fundSubscription(vrfCoordinatorV2_5, subId, link, myAccount);
}
function fundSubscription(
address vrfCoordinatorV2_5,
uint256 subId,
address linkToken,
address myAccount
) public {
console.log("Funding subscription:", subId);
console.log("Using vrfCoordinator:", vrfCoordinatorV2_5);
console.log("On ChainID:", block.chainid);
if (block.chainid == LOCAL_CHAIN_ID) {
vm.startBroadcast(myAccount);
VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fundSubscription(subId, FUND_AMOUNT);
vm.stopBroadcast();
} else {
console.log("Sender balance:", LinkToken(linkToken).balanceOf(msg.sender));
console.log("Sender address:", msg.sender);
console.log("LINK: ", linkToken);
console.log("myAccount: ", myAccount);
console.log("Subscription myAccount:", LinkToken(linkToken).balanceOf(address(this)));
console.log("Subscription address", address(this));
vm.startBroadcast(myAccount);
LinkToken(linkToken).transferAndCall(
vrfCoordinatorV2_5,
FUND_AMOUNT,
abi.encode(subId)
);
vm.stopBroadcast();
}
}
function run() external {
fundSubscriptionUsingConfig();
}
}
contract AddConsumer is Script {
function addConsumerUsingConfig(address mostRecentlyDeployed) public {
HelperConfig helperConfig = new HelperConfig();
uint256 subId = helperConfig.getConfig().subscriptionId;
address vrfCoordinatorV2_5 = helperConfig.getConfig().vrfCoordinatorV2_5;
address account = helperConfig.getConfig().account;
address myAccount = helperConfig.getConfig().myAccount;
addConsumer(mostRecentlyDeployed, vrfCoordinatorV2_5, subId, account);
addConsumer(mostRecentlyDeployed, vrfCoordinatorV2_5, subId, myAccount);
}
function addConsumer(
address contractToAddToVrf,
address vrfCoordinator,
uint256 subId,
address myAccount
) public {
console.log("Adding consumer contract: ", contractToAddToVrf);
console.log("Using vrfCoordinator: ", vrfCoordinator);
console.log("On ChainID: ", block.chainid);
vm.startBroadcast(myAccount);
VRFCoordinatorV2_5Mock(vrfCoordinator).addConsumer(subId, contractToAddToVrf);
vm.stopBroadcast();
}
function run() external {
@ -70,53 +131,3 @@ contract AddConsumer is Script {
addConsumerUsingConfig(mostRecentlyDeployed);
}
}
contract FundSubscription is CodeConstants, Script {
uint96 public constant FUND_AMOUNT = 3 ether;
function fundSubscriptionUsingConfig() public {
HelperConfig helperConfig = new HelperConfig();
uint256 subId = helperConfig.getConfig().subscriptionId;
address vrfCoordinatorV2_5 = helperConfig.getConfig().vrfCoordinatorV2_5;
address link = helperConfig.getConfig().link;
address account = helperConfig.getConfig().account;
if (subId == 0) {
CreateSubscription createSub = new CreateSubscription();
(uint256 updatedSubId, address updatedVRFv2) = createSub.run();
subId = updatedSubId;
vrfCoordinatorV2_5 = updatedVRFv2;
console.log("New SubId Created! ", subId, "VRF Address: ", vrfCoordinatorV2_5);
}
fundSubscription(vrfCoordinatorV2_5, subId, link, account);
}
function fundSubscription(
address vrfCoordinatorV2_5,
uint256 subId,
address link,
address account
) public {
console.log("Funding subscription: ", subId);
console.log("Using vrfCoordinator: ", vrfCoordinatorV2_5);
console.log("On ChainID: ", block.chainid);
if (block.chainid == LOCAL_CHAIN_ID) {
vm.startBroadcast(account);
VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fundSubscription(subId, FUND_AMOUNT);
vm.stopBroadcast();
} else {
console.log(LinkToken(link).balanceOf(msg.sender));
console.log(msg.sender);
console.log(LinkToken(link).balanceOf(address(this)));
console.log(address(this));
vm.startBroadcast(account);
LinkToken(link).transferAndCall(vrfCoordinatorV2_5, FUND_AMOUNT, abi.encode(subId));
vm.stopBroadcast();
}
}
function run() external {
fundSubscriptionUsingConfig();
}
}

View File

@ -28,11 +28,7 @@ contract LinkToken is ERC20 {
* @param _value The amount to be transferred.
* @param _data The extra data to be passed to the receiving contract.
*/
function transferAndCall(
address _to,
uint256 _value,
bytes memory _data
) public virtual returns (bool success) {
function transferAndCall(address _to, uint256 _value, bytes memory _data) public virtual returns (bool success) {
super.transfer(_to, _value);
// emit Transfer(msg.sender, _to, _value, _data);
emit Transfer(msg.sender, _to, _value, _data);

316
test/unit/RaffleTest.t.sol Normal file
View File

@ -0,0 +1,316 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {Test, console2} 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 = 100 ether;
uint256 public constant STARTING_PLAYER_BALANCE = 10 ether;
function setUp() public {
DeployRaffle deployer = new DeployRaffle();
(raffle, helperConfig) = deployer.run();
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;
vm.deal(PLAYER, STARTING_USER_BALANCE);
}
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
//////////////////////////////////////////////////////////////*/
modifier raffleEntered() {
vm.prank(PLAYER);
raffle.enterRaffle{value: entranceFee}();
vm.warp(block.timestamp + interval + 1);
vm.roll(block.number + 1);
_;
}
modifier skipFork() {
if (block.chainid != 31337) {
return;
}
_;
}
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);
}
}

View File

@ -2937,6 +2937,11 @@ arraybuffer.prototype.slice@^1.0.3:
is-array-buffer "^3.0.4"
is-shared-array-buffer "^1.0.2"
asap@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
asn1.js@^4.10.1:
version "4.10.1"
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
@ -4538,6 +4543,11 @@ command-line-usage@^6.1.0:
table-layout "^1.0.2"
typical "^5.2.0"
commander@*:
version "12.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
commander@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
@ -5203,6 +5213,15 @@ dotignore@~0.1.2:
dependencies:
minimatch "^3.0.4"
dotnet@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/dotnet/-/dotnet-1.1.4.tgz#7acc945452734c1ae7111002a4287aa30075cf8b"
integrity sha512-JxRiwvNLTpIkjpphFNovyCARQbrQCMZY4OP7Q61JPGCDpvE3aJnm6Fo6x0LmroGE3L7yo7unKNRZ6aViDLR/0g==
dependencies:
commander "*"
minimist "*"
promise "*"
drbg.js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b"
@ -8983,7 +9002,7 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.8:
minimist@*, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@ -10046,6 +10065,13 @@ promise-to-callback@^1.0.0:
is-fn "^1.0.0"
set-immediate-shim "^1.0.1"
promise@*:
version "8.3.0"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a"
integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==
dependencies:
asap "~2.0.6"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"