This commit is contained in:
parent
10c4f306f6
commit
12a67132f2
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
@ -64,3 +64,6 @@ $ forge --help
|
||||
$ anvil --help
|
||||
$ cast --help
|
||||
```
|
||||
|
||||
|
||||
cast --to-base 0x714c2 dec
|
26
contracts-dbg/Test1/Test1.t.sol
Normal file
26
contracts-dbg/Test1/Test1.t.sol
Normal 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");
|
||||
}
|
||||
}
|
13
contracts-dbg/Test1/dbg.contract.json
Normal file
13
contracts-dbg/Test1/dbg.contract.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"entryPoint": "DbgEntry",
|
||||
"solc": "0.8.26",
|
||||
"sourceDirs": [
|
||||
"."
|
||||
],
|
||||
"breakOnEntry": false,
|
||||
"fork": {
|
||||
"enable": false,
|
||||
"url": "",
|
||||
"blockNumber": 0
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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
13
dbg.project.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"contractsDir": "contracts-dbg",
|
||||
"selectedContract": "Test1",
|
||||
"autoOpen": true,
|
||||
"breakOnEntry": false,
|
||||
"symbols": {
|
||||
"hardhat": {
|
||||
"projectPaths": [
|
||||
"/home/hoelee/hardhat/hardhat-smartcontract-lottery"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
15
package.json
15
package.json
@ -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"
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
316
test/unit/RaffleTest.t.sol
Normal 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);
|
||||
}
|
||||
}
|
28
yarn.lock
28
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user