add lottery smart contract from updraft
This commit is contained in:
parent
5a3d550691
commit
a9bf1f8fe6
3
foundry-smart-contract-lottery-cu/.env.example
Normal file
3
foundry-smart-contract-lottery-cu/.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PRIVATE_KEY=XXXXXXXXX
|
||||||
|
RPC_URL=http://0.0.0.0:8545
|
||||||
|
ETHERSCAN_API_KEY=XXXX
|
||||||
34
foundry-smart-contract-lottery-cu/.github/workflows/test.yml
vendored
Normal file
34
foundry-smart-contract-lottery-cu/.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on: workflow_dispatch
|
||||||
|
|
||||||
|
env:
|
||||||
|
FOUNDRY_PROFILE: ci
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
name: Foundry project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install Foundry
|
||||||
|
uses: foundry-rs/foundry-toolchain@v1
|
||||||
|
with:
|
||||||
|
version: nightly
|
||||||
|
|
||||||
|
- name: Run Forge build
|
||||||
|
run: |
|
||||||
|
forge --version
|
||||||
|
forge build --sizes
|
||||||
|
id: build
|
||||||
|
|
||||||
|
- name: Run Forge tests
|
||||||
|
run: |
|
||||||
|
forge test -vvv
|
||||||
|
id: test
|
||||||
18
foundry-smart-contract-lottery-cu/.gitignore
vendored
Normal file
18
foundry-smart-contract-lottery-cu/.gitignore
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Compiler files
|
||||||
|
cache/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Ignores development broadcast logs
|
||||||
|
!/broadcast
|
||||||
|
/broadcast/*/31337/
|
||||||
|
/broadcast/**/dry-run/
|
||||||
|
/broadcast/
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# Dotenv file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# zkSync
|
||||||
|
zkout/
|
||||||
12
foundry-smart-contract-lottery-cu/.gitmodules
vendored
Normal file
12
foundry-smart-contract-lottery-cu/.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[submodule "lib/foundry-devops"]
|
||||||
|
path = lib/foundry-devops
|
||||||
|
url = https://github.com/cyfrin/foundry-devops
|
||||||
|
[submodule "lib/chainlink-brownie-contracts"]
|
||||||
|
path = lib/chainlink-brownie-contracts
|
||||||
|
url = https://github.com/smartcontractkit/chainlink-brownie-contracts
|
||||||
|
[submodule "lib/forge-std"]
|
||||||
|
path = lib/forge-std
|
||||||
|
url = https://github.com/foundry-rs/forge-std
|
||||||
|
[submodule "lib/solmate"]
|
||||||
|
path = lib/solmate
|
||||||
|
url = https://github.com/transmissions11/solmate
|
||||||
6
foundry-smart-contract-lottery-cu/.gitpod.yml
Normal file
6
foundry-smart-contract-lottery-cu/.gitpod.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
tasks:
|
||||||
|
- name: Install Foundry
|
||||||
|
init: |
|
||||||
|
curl -L https://foundry.paradigm.xyz | bash
|
||||||
|
source /home/gitpod/.bashrc
|
||||||
|
foundryup
|
||||||
54
foundry-smart-contract-lottery-cu/Makefile
Normal file
54
foundry-smart-contract-lottery-cu/Makefile
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
-include .env
|
||||||
|
|
||||||
|
.PHONY: all test clean deploy fund help install snapshot format anvil
|
||||||
|
|
||||||
|
DEFAULT_ANVIL_KEY := 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Usage:"
|
||||||
|
@echo " make deploy [ARGS=...]\n example: make deploy ARGS=\"--network sepolia\""
|
||||||
|
@echo ""
|
||||||
|
@echo " make fund [ARGS=...]\n example: make deploy ARGS=\"--network sepolia\""
|
||||||
|
|
||||||
|
all: clean remove install update build
|
||||||
|
|
||||||
|
# Clean the repo
|
||||||
|
clean :; forge clean
|
||||||
|
|
||||||
|
# Remove modules
|
||||||
|
remove :; rm -rf .gitmodules && rm -rf .git/modules/* && rm -rf lib && touch .gitmodules && git add . && git commit -m "modules"
|
||||||
|
|
||||||
|
install :; forge install cyfrin/foundry-devops@0.2.2 --no-commit && forge install smartcontractkit/chainlink-brownie-contracts@1.1.1 --no-commit && forge install foundry-rs/forge-std@v1.8.2 --no-commit && forge install transmissions11/solmate@v6 --no-commit
|
||||||
|
|
||||||
|
# Update Dependencies
|
||||||
|
update:; forge update
|
||||||
|
|
||||||
|
build:; forge build
|
||||||
|
|
||||||
|
test :; forge test
|
||||||
|
|
||||||
|
snapshot :; forge snapshot
|
||||||
|
|
||||||
|
format :; forge fmt
|
||||||
|
|
||||||
|
anvil :; anvil -m 'test test test test test test test test test test test junk' --steps-tracing --block-time 1
|
||||||
|
|
||||||
|
NETWORK_ARGS := --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast
|
||||||
|
|
||||||
|
ifeq ($(findstring --network sepolia,$(ARGS)),--network sepolia)
|
||||||
|
NETWORK_ARGS := --rpc-url $(SEPOLIA_RPC_URL) --private-key $(PRIVATE_KEY) --broadcast --verify --etherscan-api-key $(ETHERSCAN_API_KEY) -vvvv
|
||||||
|
endif
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
@forge script script/DeployRaffle.s.sol:DeployRaffle $(NETWORK_ARGS)
|
||||||
|
|
||||||
|
createSubscription:
|
||||||
|
@forge script script/Interactions.s.sol:CreateSubscription $(NETWORK_ARGS)
|
||||||
|
|
||||||
|
addConsumer:
|
||||||
|
@forge script script/Interactions.s.sol:AddConsumer $(NETWORK_ARGS)
|
||||||
|
|
||||||
|
fundSubscription:
|
||||||
|
@forge script script/Interactions.s.sol:FundSubscription $(NETWORK_ARGS)
|
||||||
|
|
||||||
|
|
||||||
204
foundry-smart-contract-lottery-cu/README.md
Normal file
204
foundry-smart-contract-lottery-cu/README.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
|
||||||
|
> ! Updates from Video
|
||||||
|
> 1. V2.5 of Chainlink VRF uses a `uint256` as a subId instead of a `uint64` this repo has a comment to reflect that. We added a mock in case you'd like to work with version 2.5.
|
||||||
|
> 2. We use `0.1.0` of the `foundry-devops` package which doesn't need to have `ffi=true`
|
||||||
|
|
||||||
|
# Foundry Smart Contract Lottery
|
||||||
|
|
||||||
|
This is a section of the Cyfrin Foundry Solidity Course.
|
||||||
|
|
||||||
|
*[⭐️ (3:04:09) | Lesson 9: Foundry Smart Contract Lottery](https://www.youtube.com/watch?v=sas02qSFZ74&t=11049s)*
|
||||||
|
|
||||||
|
- [Foundry Smart Contract Lottery](#foundry-smart-contract-lottery)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Quickstart](#quickstart)
|
||||||
|
- [Optional Gitpod](#optional-gitpod)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Start a local node](#start-a-local-node)
|
||||||
|
- [Library](#library)
|
||||||
|
- [Deploy](#deploy)
|
||||||
|
- [Deploy - Other Network](#deploy---other-network)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Test Coverage](#test-coverage)
|
||||||
|
- [Deployment to a testnet or mainnet](#deployment-to-a-testnet-or-mainnet)
|
||||||
|
- [Scripts](#scripts)
|
||||||
|
- [Estimate gas](#estimate-gas)
|
||||||
|
- [Formatting](#formatting)
|
||||||
|
- [Additional Info:](#additional-info)
|
||||||
|
- [Let's talk about what "Official" means](#lets-talk-about-what-official-means)
|
||||||
|
- [Summary](#summary)
|
||||||
|
- [Thank you!](#thank-you)
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
|
- You'll know you did it right if you can run `git --version` and you see a response like `git version x.x.x`
|
||||||
|
- [foundry](https://getfoundry.sh/)
|
||||||
|
- You'll know you did it right if you can run `forge --version` and you see a response like `forge 0.2.0 (816e00b 2023-03-16T00:05:26.396218Z)`
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/Cyfrin/foundry-smart-contract-lottery-cu
|
||||||
|
cd foundry-smart-contract-lottery-cu
|
||||||
|
forge build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional Gitpod
|
||||||
|
|
||||||
|
If you can't or don't want to run and install locally, you can work with this repo in Gitpod. If you do this, you can skip the `clone this repo` part.
|
||||||
|
|
||||||
|
[](https://gitpod.io/#github.com/Cyfrin/foundry-smart-contract-lottery-cu)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Start a local node
|
||||||
|
|
||||||
|
```
|
||||||
|
make anvil
|
||||||
|
```
|
||||||
|
|
||||||
|
## Library
|
||||||
|
|
||||||
|
If you're having a hard time installing the chainlink library, you can optionally run this command.
|
||||||
|
|
||||||
|
```
|
||||||
|
forge install smartcontractkit/chainlink-brownie-contracts@0.6.1 --no-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
This will default to your local node. You need to have it running in another terminal in order for it to deploy.
|
||||||
|
|
||||||
|
```
|
||||||
|
make deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy - Other Network
|
||||||
|
|
||||||
|
[See below](#deployment-to-a-testnet-or-mainnet)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
We talk about 4 test tiers in the video.
|
||||||
|
|
||||||
|
1. Unit
|
||||||
|
2. Integration
|
||||||
|
3. Forked
|
||||||
|
4. Staging
|
||||||
|
|
||||||
|
This repo we cover #1 and #3.
|
||||||
|
|
||||||
|
```
|
||||||
|
forge test
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
forge test --fork-url $SEPOLIA_RPC_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
```
|
||||||
|
forge coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
# Deployment to a testnet or mainnet
|
||||||
|
|
||||||
|
1. Setup environment variables
|
||||||
|
|
||||||
|
You'll want to set your `SEPOLIA_RPC_URL` and `PRIVATE_KEY` as environment variables. You can add them to a `.env` file, similar to what you see in `.env.example`.
|
||||||
|
|
||||||
|
- `PRIVATE_KEY`: The private key of your account (like from [metamask](https://metamask.io/)). **NOTE:** FOR DEVELOPMENT, PLEASE USE A KEY THAT DOESN'T HAVE ANY REAL FUNDS ASSOCIATED WITH IT.
|
||||||
|
- You can [learn how to export it here](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key).
|
||||||
|
- `SEPOLIA_RPC_URL`: This is url of the sepolia testnet node you're working with. You can get setup with one for free from [Alchemy](https://alchemy.com/?a=673c802981)
|
||||||
|
|
||||||
|
Optionally, add your `ETHERSCAN_API_KEY` if you want to verify your contract on [Etherscan](https://etherscan.io/).
|
||||||
|
|
||||||
|
1. Get testnet ETH
|
||||||
|
|
||||||
|
Head over to [faucets.chain.link](https://faucets.chain.link/) and get some testnet ETH. You should see the ETH show up in your metamask.
|
||||||
|
|
||||||
|
2. Deploy
|
||||||
|
|
||||||
|
```
|
||||||
|
make deploy ARGS="--network sepolia"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will setup a ChainlinkVRF Subscription for you. If you already have one, update it in the `scripts/HelperConfig.s.sol` file. It will also automatically add your contract as a consumer.
|
||||||
|
|
||||||
|
3. Register a Chainlink Automation Upkeep
|
||||||
|
|
||||||
|
[You can follow the documentation if you get lost.](https://docs.chain.link/chainlink-automation/compatible-contracts)
|
||||||
|
|
||||||
|
Go to [automation.chain.link](https://automation.chain.link/new) and register a new upkeep. Choose `Custom logic` as your trigger mechanism for automation. Your UI will look something like this once completed:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
After deploying to a testnet or local net, you can run the scripts.
|
||||||
|
|
||||||
|
Using cast deployed locally example:
|
||||||
|
|
||||||
|
```
|
||||||
|
cast send <RAFFLE_CONTRACT_ADDRESS> "enterRaffle()" --value 0.1ether --private-key <PRIVATE_KEY> --rpc-url $SEPOLIA_RPC_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
or, to create a ChainlinkVRF Subscription:
|
||||||
|
|
||||||
|
```
|
||||||
|
make createSubscription ARGS="--network sepolia"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Estimate gas
|
||||||
|
|
||||||
|
You can estimate how much gas things cost by running:
|
||||||
|
|
||||||
|
```
|
||||||
|
forge snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
And you'll see an output file called `.gas-snapshot`
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
|
||||||
|
To run code formatting:
|
||||||
|
|
||||||
|
```
|
||||||
|
forge fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
# Additional Info:
|
||||||
|
Some users were having a confusion that whether Chainlink-brownie-contracts is an official Chainlink repository or not. Here is the info.
|
||||||
|
Chainlink-brownie-contracts is an official repo. The repository is owned and maintained by the chainlink team for this very purpose, and gets releases from the proper chainlink release process. You can see it's still the `smartcontractkit` org as well.
|
||||||
|
|
||||||
|
https://github.com/smartcontractkit/chainlink-brownie-contracts
|
||||||
|
|
||||||
|
## Let's talk about what "Official" means
|
||||||
|
The "official" release process is that chainlink deploys it's packages to [npm](https://www.npmjs.com/package/@chainlink/contracts). So technically, even downloading directly from `smartcontractkit/chainlink` is wrong, because it could be using unreleased code.
|
||||||
|
|
||||||
|
So, then you have two options:
|
||||||
|
|
||||||
|
1. Download from NPM and have your codebase have dependencies foreign to foundry
|
||||||
|
2. Download from the chainlink-brownie-contracts repo which already downloads from npm and then packages it nicely for you to use in foundry.
|
||||||
|
## Summary
|
||||||
|
1. That is an official repo maintained by the same org
|
||||||
|
2. It downloads from the official release cycle `chainlink/contracts` use (npm) and packages it nicely for digestion from foundry.
|
||||||
|
|
||||||
|
|
||||||
|
# Thank you!
|
||||||
|
|
||||||
|
If you appreciated this, feel free to follow me or donate!
|
||||||
|
|
||||||
|
ETH/Arbitrum/Optimism/Polygon/etc Address: 0x9680201d9c93d65a3603d2088d125e955c73BD65
|
||||||
|
|
||||||
|
[](https://twitter.com/PatrickAlphaC)
|
||||||
|
[](https://www.youtube.com/channel/UCn-3f8tw_E1jZvhuHatROwA)
|
||||||
|
[](https://www.linkedin.com/in/patrickalphac/)
|
||||||
|
[](https://medium.com/@patrick.collins_58673/)
|
||||||
14
foundry-smart-contract-lottery-cu/foundry.toml
Normal file
14
foundry-smart-contract-lottery-cu/foundry.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[profile.default]
|
||||||
|
src = "src"
|
||||||
|
out = "out"
|
||||||
|
libs = ["lib"]
|
||||||
|
remappings = ['@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/', '@solmate=lib/solmate/src/']
|
||||||
|
|
||||||
|
[etherscan]
|
||||||
|
mainnet = { key = "${ETHERSCAN_API_KEY}" }
|
||||||
|
sepolia = {key = "${ETHERSCAN_API_KEY}"}
|
||||||
|
|
||||||
|
[rpc_endpoints]
|
||||||
|
sepolia = "${SEPOLIA_RPC_URL}"
|
||||||
|
|
||||||
|
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
|
||||||
BIN
foundry-smart-contract-lottery-cu/img/automation.png
Normal file
BIN
foundry-smart-contract-lottery-cu/img/automation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 228 KiB |
43
foundry-smart-contract-lottery-cu/script/DeployRaffle.s.sol
Normal file
43
foundry-smart-contract-lottery-cu/script/DeployRaffle.s.sol
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
import {Script} from "forge-std/Script.sol";
|
||||||
|
import {HelperConfig} from "./HelperConfig.s.sol";
|
||||||
|
import {Raffle} from "../src/Raffle.sol";
|
||||||
|
import {AddConsumer, CreateSubscription, FundSubscription} from "./Interactions.s.sol";
|
||||||
|
|
||||||
|
contract DeployRaffle is Script {
|
||||||
|
function run() external returns (Raffle, HelperConfig) {
|
||||||
|
HelperConfig helperConfig = new HelperConfig(); // This comes with our mocks!
|
||||||
|
AddConsumer addConsumer = new AddConsumer();
|
||||||
|
HelperConfig.NetworkConfig memory config = helperConfig.getConfig();
|
||||||
|
|
||||||
|
if (config.subscriptionId == 0) {
|
||||||
|
CreateSubscription createSubscription = new CreateSubscription();
|
||||||
|
(config.subscriptionId, config.vrfCoordinatorV2_5) =
|
||||||
|
createSubscription.createSubscription(config.vrfCoordinatorV2_5, config.account);
|
||||||
|
|
||||||
|
FundSubscription fundSubscription = new FundSubscription();
|
||||||
|
fundSubscription.fundSubscription(
|
||||||
|
config.vrfCoordinatorV2_5, config.subscriptionId, config.link, config.account
|
||||||
|
);
|
||||||
|
|
||||||
|
helperConfig.setConfig(block.chainid, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.startBroadcast(config.account);
|
||||||
|
Raffle raffle = new Raffle(
|
||||||
|
config.subscriptionId,
|
||||||
|
config.gasLane,
|
||||||
|
config.automationUpdateInterval,
|
||||||
|
config.raffleEntranceFee,
|
||||||
|
config.callbackGasLimit,
|
||||||
|
config.vrfCoordinatorV2_5
|
||||||
|
);
|
||||||
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
// We already have a broadcast in here
|
||||||
|
addConsumer.addConsumer(address(raffle), config.vrfCoordinatorV2_5, config.subscriptionId, config.account);
|
||||||
|
return (raffle, helperConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
129
foundry-smart-contract-lottery-cu/script/HelperConfig.s.sol
Normal file
129
foundry-smart-contract-lottery-cu/script/HelperConfig.s.sol
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// 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";
|
||||||
|
|
||||||
|
abstract contract CodeConstants {
|
||||||
|
uint96 public MOCK_BASE_FEE = 0.25 ether;
|
||||||
|
uint96 public MOCK_GAS_PRICE_LINK = 1e9;
|
||||||
|
// LINK / ETH price
|
||||||
|
int256 public MOCK_WEI_PER_UINT_LINK = 4e15;
|
||||||
|
|
||||||
|
address public FOUNDRY_DEFAULT_SENDER = 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38;
|
||||||
|
|
||||||
|
uint256 public constant ETH_SEPOLIA_CHAIN_ID = 11155111;
|
||||||
|
uint256 public constant ETH_MAINNET_CHAIN_ID = 1;
|
||||||
|
uint256 public constant LOCAL_CHAIN_ID = 31337;
|
||||||
|
}
|
||||||
|
|
||||||
|
contract HelperConfig is CodeConstants, Script {
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
ERRORS
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
error HelperConfig__InvalidChainId();
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
TYPES
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
struct NetworkConfig {
|
||||||
|
uint256 subscriptionId;
|
||||||
|
bytes32 gasLane;
|
||||||
|
uint256 automationUpdateInterval;
|
||||||
|
uint256 raffleEntranceFee;
|
||||||
|
uint32 callbackGasLimit;
|
||||||
|
address vrfCoordinatorV2_5;
|
||||||
|
address link;
|
||||||
|
address account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
STATE VARIABLES
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
// Local network state variables
|
||||||
|
NetworkConfig public localNetworkConfig;
|
||||||
|
mapping(uint256 chainId => NetworkConfig) public networkConfigs;
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////
|
||||||
|
FUNCTIONS
|
||||||
|
//////////////////////////////////////////////////////////////*/
|
||||||
|
constructor() {
|
||||||
|
networkConfigs[ETH_SEPOLIA_CHAIN_ID] = getSepoliaEthConfig();
|
||||||
|
networkConfigs[ETH_MAINNET_CHAIN_ID] = getMainnetEthConfig();
|
||||||
|
// Note: We skip doing the local config
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfig() public returns (NetworkConfig memory) {
|
||||||
|
return getConfigByChainId(block.chainid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setConfig(uint256 chainId, NetworkConfig memory networkConfig) public {
|
||||||
|
networkConfigs[chainId] = networkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConfigByChainId(uint256 chainId) public returns (NetworkConfig memory) {
|
||||||
|
if (networkConfigs[chainId].vrfCoordinatorV2_5 != address(0)) {
|
||||||
|
return networkConfigs[chainId];
|
||||||
|
} else if (chainId == LOCAL_CHAIN_ID) {
|
||||||
|
return getOrCreateAnvilEthConfig();
|
||||||
|
} else {
|
||||||
|
revert HelperConfig__InvalidChainId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMainnetEthConfig() public pure returns (NetworkConfig memory mainnetNetworkConfig) {
|
||||||
|
mainnetNetworkConfig = NetworkConfig({
|
||||||
|
subscriptionId: 0, // If left as 0, our scripts will create one!
|
||||||
|
gasLane: 0x9fe0eebf5e446e3c998ec9bb19951541aee00bb90ea201ae456421a2ded86805,
|
||||||
|
automationUpdateInterval: 30, // 30 seconds
|
||||||
|
raffleEntranceFee: 0.01 ether,
|
||||||
|
callbackGasLimit: 500000, // 500,000 gas
|
||||||
|
vrfCoordinatorV2_5: 0x271682DEB8C4E0901D1a1550aD2e64D568E69909,
|
||||||
|
link: 0x514910771AF9Ca656af840dff83E8264EcF986CA,
|
||||||
|
account: 0x643315C9Be056cDEA171F4e7b2222a4ddaB9F88D
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSepoliaEthConfig() public pure returns (NetworkConfig memory sepoliaNetworkConfig) {
|
||||||
|
sepoliaNetworkConfig = NetworkConfig({
|
||||||
|
subscriptionId: 0, // If left as 0, our scripts will create one!
|
||||||
|
gasLane: 0x787d74caea10b2b357790d5b5247c2f63d1d91572a9846f780606e4d953677ae,
|
||||||
|
automationUpdateInterval: 30, // 30 seconds
|
||||||
|
raffleEntranceFee: 0.01 ether,
|
||||||
|
callbackGasLimit: 500000, // 500,000 gas
|
||||||
|
vrfCoordinatorV2_5: 0x9DdfaCa8183c41ad55329BdeeD9F6A8d53168B1B,
|
||||||
|
link: 0x779877A7B0D9E8603169DdbD7836e478b4624789,
|
||||||
|
account: 0x643315C9Be056cDEA171F4e7b2222a4ddaB9F88D
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrCreateAnvilEthConfig() public returns (NetworkConfig memory) {
|
||||||
|
// Check to see if we set an active network config
|
||||||
|
if (localNetworkConfig.vrfCoordinatorV2_5 != address(0)) {
|
||||||
|
return localNetworkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
console2.log(unicode"⚠️ You have deployed a mock conract!");
|
||||||
|
console2.log("Make sure this was intentional");
|
||||||
|
vm.startBroadcast();
|
||||||
|
VRFCoordinatorV2_5Mock vrfCoordinatorV2_5Mock =
|
||||||
|
new VRFCoordinatorV2_5Mock(MOCK_BASE_FEE, MOCK_GAS_PRICE_LINK, MOCK_WEI_PER_UINT_LINK);
|
||||||
|
LinkToken link = new LinkToken();
|
||||||
|
uint256 subscriptionId = vrfCoordinatorV2_5Mock.createSubscription();
|
||||||
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
localNetworkConfig = NetworkConfig({
|
||||||
|
subscriptionId: subscriptionId,
|
||||||
|
gasLane: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c, // doesn't really matter
|
||||||
|
automationUpdateInterval: 30, // 30 seconds
|
||||||
|
raffleEntranceFee: 0.01 ether,
|
||||||
|
callbackGasLimit: 500000, // 500,000 gas
|
||||||
|
vrfCoordinatorV2_5: address(vrfCoordinatorV2_5Mock),
|
||||||
|
link: address(link),
|
||||||
|
account: FOUNDRY_DEFAULT_SENDER
|
||||||
|
});
|
||||||
|
vm.deal(localNetworkConfig.account, 100 ether);
|
||||||
|
return localNetworkConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
foundry-smart-contract-lottery-cu/script/Interactions.s.sol
Normal file
103
foundry-smart-contract-lottery-cu/script/Interactions.s.sol
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
import {Script, console} from "forge-std/Script.sol";
|
||||||
|
import {HelperConfig} from "./HelperConfig.s.sol";
|
||||||
|
import {Raffle} from "../src/Raffle.sol";
|
||||||
|
import {DevOpsTools} from "foundry-devops/src/DevOpsTools.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 "./HelperConfig.s.sol";
|
||||||
|
|
||||||
|
contract CreateSubscription is Script {
|
||||||
|
function createSubscriptionUsingConfig() public returns (uint256, address) {
|
||||||
|
HelperConfig helperConfig = new HelperConfig();
|
||||||
|
address vrfCoordinatorV2_5 = helperConfig.getConfigByChainId(block.chainid).vrfCoordinatorV2_5;
|
||||||
|
address account = helperConfig.getConfigByChainId(block.chainid).account;
|
||||||
|
return createSubscription(vrfCoordinatorV2_5, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubscription(address vrfCoordinatorV2_5, address account) public returns (uint256, address) {
|
||||||
|
console.log("Creating subscription on chainId: ", block.chainid);
|
||||||
|
vm.startBroadcast(account);
|
||||||
|
uint256 subId = VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).createSubscription();
|
||||||
|
vm.stopBroadcast();
|
||||||
|
console.log("Your subscription Id is: ", subId);
|
||||||
|
console.log("Please update the subscriptionId in HelperConfig.s.sol");
|
||||||
|
return (subId, vrfCoordinatorV2_5);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() external returns (uint256, address) {
|
||||||
|
return createSubscriptionUsingConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
addConsumer(mostRecentlyDeployed, vrfCoordinatorV2_5, subId, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() external {
|
||||||
|
address mostRecentlyDeployed = DevOpsTools.get_most_recent_deployment("Raffle", block.chainid);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
224
foundry-smart-contract-lottery-cu/src/Raffle.sol
Normal file
224
foundry-smart-contract-lottery-cu/src/Raffle.sol
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// Layout of Contract:
|
||||||
|
// version
|
||||||
|
// imports
|
||||||
|
// errors
|
||||||
|
// interfaces, libraries, contracts
|
||||||
|
// Type declarations
|
||||||
|
// State variables
|
||||||
|
// Events
|
||||||
|
// Modifiers
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
// Layout of Functions:
|
||||||
|
// constructor
|
||||||
|
// receive function (if exists)
|
||||||
|
// fallback function (if exists)
|
||||||
|
// external
|
||||||
|
// public
|
||||||
|
// internal
|
||||||
|
// private
|
||||||
|
// view & pure functions
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity 0.8.19;
|
||||||
|
|
||||||
|
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
|
||||||
|
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
|
||||||
|
import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/interfaces/AutomationCompatibleInterface.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title A sample Raffle Contract
|
||||||
|
* @author Patrick Collins
|
||||||
|
* @notice This contract is for creating a sample raffle contract
|
||||||
|
* @dev This implements the Chainlink VRF Version 2
|
||||||
|
*/
|
||||||
|
contract Raffle is VRFConsumerBaseV2Plus, AutomationCompatibleInterface {
|
||||||
|
/* Errors */
|
||||||
|
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);
|
||||||
|
error Raffle__TransferFailed();
|
||||||
|
error Raffle__SendMoreToEnterRaffle();
|
||||||
|
error Raffle__RaffleNotOpen();
|
||||||
|
|
||||||
|
/* Type declarations */
|
||||||
|
enum RaffleState {
|
||||||
|
OPEN,
|
||||||
|
CALCULATING
|
||||||
|
}
|
||||||
|
|
||||||
|
/* State variables */
|
||||||
|
// Chainlink VRF Variables
|
||||||
|
uint256 private immutable i_subscriptionId;
|
||||||
|
bytes32 private immutable i_gasLane;
|
||||||
|
uint32 private immutable i_callbackGasLimit;
|
||||||
|
uint16 private constant REQUEST_CONFIRMATIONS = 3;
|
||||||
|
uint32 private constant NUM_WORDS = 1;
|
||||||
|
|
||||||
|
// Lottery Variables
|
||||||
|
uint256 private immutable i_interval;
|
||||||
|
uint256 private immutable i_entranceFee;
|
||||||
|
uint256 private s_lastTimeStamp;
|
||||||
|
address private s_recentWinner;
|
||||||
|
address payable[] private s_players;
|
||||||
|
RaffleState private s_raffleState;
|
||||||
|
|
||||||
|
/* Events */
|
||||||
|
event RequestedRaffleWinner(uint256 indexed requestId);
|
||||||
|
event RaffleEnter(address indexed player);
|
||||||
|
event WinnerPicked(address indexed player);
|
||||||
|
|
||||||
|
/* Functions */
|
||||||
|
constructor(
|
||||||
|
uint256 subscriptionId,
|
||||||
|
bytes32 gasLane, // keyHash
|
||||||
|
uint256 interval,
|
||||||
|
uint256 entranceFee,
|
||||||
|
uint32 callbackGasLimit,
|
||||||
|
address vrfCoordinatorV2
|
||||||
|
) VRFConsumerBaseV2Plus(vrfCoordinatorV2) {
|
||||||
|
i_gasLane = gasLane;
|
||||||
|
i_interval = interval;
|
||||||
|
i_subscriptionId = subscriptionId;
|
||||||
|
i_entranceFee = entranceFee;
|
||||||
|
s_raffleState = RaffleState.OPEN;
|
||||||
|
s_lastTimeStamp = block.timestamp;
|
||||||
|
i_callbackGasLimit = callbackGasLimit;
|
||||||
|
// uint256 balance = address(this).balance;
|
||||||
|
// if (balance > 0) {
|
||||||
|
// payable(msg.sender).transfer(balance);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterRaffle() public payable {
|
||||||
|
// require(msg.value >= i_entranceFee, "Not enough value sent");
|
||||||
|
// require(s_raffleState == RaffleState.OPEN, "Raffle is not open");
|
||||||
|
if (msg.value < i_entranceFee) {
|
||||||
|
revert Raffle__SendMoreToEnterRaffle();
|
||||||
|
}
|
||||||
|
if (s_raffleState != RaffleState.OPEN) {
|
||||||
|
revert Raffle__RaffleNotOpen();
|
||||||
|
}
|
||||||
|
s_players.push(payable(msg.sender));
|
||||||
|
// Emit an event when we update a dynamic array or mapping
|
||||||
|
// Named events with the function name reversed
|
||||||
|
emit RaffleEnter(msg.sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev This is the function that the Chainlink Keeper nodes call
|
||||||
|
* they look for `upkeepNeeded` to return True.
|
||||||
|
* the following should be true for this to return true:
|
||||||
|
* 1. The time interval has passed between raffle runs.
|
||||||
|
* 2. The lottery is open.
|
||||||
|
* 3. The contract has ETH.
|
||||||
|
* 4. Implicity, your subscription is funded with LINK.
|
||||||
|
*/
|
||||||
|
function checkUpkeep(bytes memory /* checkData */ )
|
||||||
|
public
|
||||||
|
view
|
||||||
|
override
|
||||||
|
returns (bool upkeepNeeded, bytes memory /* performData */ )
|
||||||
|
{
|
||||||
|
bool isOpen = RaffleState.OPEN == s_raffleState;
|
||||||
|
bool timePassed = ((block.timestamp - s_lastTimeStamp) > i_interval);
|
||||||
|
bool hasPlayers = s_players.length > 0;
|
||||||
|
bool hasBalance = address(this).balance > 0;
|
||||||
|
upkeepNeeded = (timePassed && isOpen && hasBalance && hasPlayers);
|
||||||
|
return (upkeepNeeded, "0x0"); // can we comment this out?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Once `checkUpkeep` is returning `true`, this function is called
|
||||||
|
* and it kicks off a Chainlink VRF call to get a random winner.
|
||||||
|
*/
|
||||||
|
function performUpkeep(bytes calldata /* performData */ ) external override {
|
||||||
|
(bool upkeepNeeded,) = checkUpkeep("");
|
||||||
|
// require(upkeepNeeded, "Upkeep not needed");
|
||||||
|
if (!upkeepNeeded) {
|
||||||
|
revert Raffle__UpkeepNotNeeded(address(this).balance, s_players.length, uint256(s_raffleState));
|
||||||
|
}
|
||||||
|
|
||||||
|
s_raffleState = RaffleState.CALCULATING;
|
||||||
|
|
||||||
|
// Will revert if subscription is not set and funded.
|
||||||
|
uint256 requestId = s_vrfCoordinator.requestRandomWords(
|
||||||
|
VRFV2PlusClient.RandomWordsRequest({
|
||||||
|
keyHash: i_gasLane,
|
||||||
|
subId: i_subscriptionId,
|
||||||
|
requestConfirmations: REQUEST_CONFIRMATIONS,
|
||||||
|
callbackGasLimit: i_callbackGasLimit,
|
||||||
|
numWords: NUM_WORDS,
|
||||||
|
extraArgs: VRFV2PlusClient._argsToBytes(
|
||||||
|
// Set nativePayment to true to pay for VRF requests with Sepolia ETH instead of LINK
|
||||||
|
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// Quiz... is this redundant?
|
||||||
|
emit RequestedRaffleWinner(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev This is the function that Chainlink VRF node
|
||||||
|
* calls to send the money to the random winner.
|
||||||
|
*/
|
||||||
|
function fulfillRandomWords(uint256, /* requestId */ uint256[] calldata randomWords) internal override {
|
||||||
|
// s_players size 10
|
||||||
|
// randomNumber 202
|
||||||
|
// 202 % 10 ? what's doesn't divide evenly into 202?
|
||||||
|
// 20 * 10 = 200
|
||||||
|
// 2
|
||||||
|
// 202 % 10 = 2
|
||||||
|
uint256 indexOfWinner = randomWords[0] % s_players.length;
|
||||||
|
address payable recentWinner = s_players[indexOfWinner];
|
||||||
|
s_recentWinner = recentWinner;
|
||||||
|
s_players = new address payable[](0);
|
||||||
|
s_raffleState = RaffleState.OPEN;
|
||||||
|
s_lastTimeStamp = block.timestamp;
|
||||||
|
emit WinnerPicked(recentWinner);
|
||||||
|
(bool success,) = recentWinner.call{value: address(this).balance}("");
|
||||||
|
// require(success, "Transfer failed");
|
||||||
|
if (!success) {
|
||||||
|
revert Raffle__TransferFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter Functions
|
||||||
|
*/
|
||||||
|
function getRaffleState() public view returns (RaffleState) {
|
||||||
|
return s_raffleState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNumWords() public pure returns (uint256) {
|
||||||
|
return NUM_WORDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequestConfirmations() public pure returns (uint256) {
|
||||||
|
return REQUEST_CONFIRMATIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRecentWinner() public view returns (address) {
|
||||||
|
return s_recentWinner;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlayer(uint256 index) public view returns (address) {
|
||||||
|
return s_players[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastTimeStamp() public view returns (uint256) {
|
||||||
|
return s_lastTimeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInterval() public view returns (uint256) {
|
||||||
|
return i_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntranceFee() public view returns (uint256) {
|
||||||
|
return i_entranceFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNumberOfPlayers() public view returns (uint256) {
|
||||||
|
return s_players.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
contract ExampleEvents {
|
||||||
|
uint256 favoriteNumber;
|
||||||
|
|
||||||
|
event storedNumber(uint256 indexed oldNumber, uint256 indexed newNumber, uint256 addedNumber, address sender);
|
||||||
|
|
||||||
|
function store(uint256 _favoriteNumber) public {
|
||||||
|
emit storedNumber(favoriteNumber, _favoriteNumber, _favoriteNumber + favoriteNumber, msg.sender);
|
||||||
|
favoriteNumber = _favoriteNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
function retrieve() public view returns (uint256) {
|
||||||
|
return favoriteNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
contract ExampleModulo {
|
||||||
|
function getModTen(uint256 number) external pure returns (uint256) {
|
||||||
|
// 10 % 10 = 0
|
||||||
|
// 10 % 9 = 1 (10 / 9 = 1.??)
|
||||||
|
// 2 % 2 = 0. 3 % 2 = 1. 6 % 2 = 0. 7 % 2 = 1
|
||||||
|
return number % 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModTwo(uint256 number) external pure returns (uint256) {
|
||||||
|
return number % 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.19;
|
||||||
|
|
||||||
|
contract ExampleRevert {
|
||||||
|
error ExampleRevert__Error();
|
||||||
|
|
||||||
|
function revertWithError() public pure {
|
||||||
|
if (false) {
|
||||||
|
revert ExampleRevert__Error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function revertWithRequire() public pure {
|
||||||
|
require(true, "ExampleRevert__Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
55
foundry-smart-contract-lottery-cu/test/mocks/LinkToken.sol
Normal file
55
foundry-smart-contract-lottery-cu/test/mocks/LinkToken.sol
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// @dev This contract has been adapted to fit with foundry
|
||||||
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
|
import {ERC20} from "@solmate/tokens/ERC20.sol";
|
||||||
|
|
||||||
|
interface ERC677Receiver {
|
||||||
|
function onTokenTransfer(address _sender, uint256 _value, bytes memory _data) external;
|
||||||
|
}
|
||||||
|
|
||||||
|
contract LinkToken is ERC20 {
|
||||||
|
uint256 constant INITIAL_SUPPLY = 1000000000000000000000000;
|
||||||
|
uint8 constant DECIMALS = 18;
|
||||||
|
|
||||||
|
constructor() ERC20("LinkToken", "LINK", DECIMALS) {
|
||||||
|
_mint(msg.sender, INITIAL_SUPPLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint(address to, uint256 value) public {
|
||||||
|
_mint(to, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev transfer token to a contract address with additional data if the recipient is a contact.
|
||||||
|
* @param _to The address to transfer to.
|
||||||
|
* @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) {
|
||||||
|
super.transfer(_to, _value);
|
||||||
|
// emit Transfer(msg.sender, _to, _value, _data);
|
||||||
|
emit Transfer(msg.sender, _to, _value, _data);
|
||||||
|
if (isContract(_to)) {
|
||||||
|
contractFallback(_to, _value, _data);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE
|
||||||
|
|
||||||
|
function contractFallback(address _to, uint256 _value, bytes memory _data) private {
|
||||||
|
ERC677Receiver receiver = ERC677Receiver(_to);
|
||||||
|
receiver.onTokenTransfer(msg.sender, _value, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isContract(address _addr) private view returns (bool hasCode) {
|
||||||
|
uint256 length;
|
||||||
|
assembly {
|
||||||
|
length := extcodesize(_addr)
|
||||||
|
}
|
||||||
|
return length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
// 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, console} from "forge-std/Test.sol";
|
||||||
|
import {Vm} from "forge-std/Vm.sol";
|
||||||
|
import {StdCheats} from "forge-std/StdCheats.sol";
|
||||||
|
import {VRFCoordinatorV2_5Mock} from "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2_5Mock.sol";
|
||||||
|
import {CreateSubscription} from "../../script/Interactions.s.sol";
|
||||||
|
|
||||||
|
contract RaffleTest is StdCheats, Test {
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
address public PLAYER = makeAddr("player");
|
||||||
|
uint256 public constant STARTING_USER_BALANCE = 10 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// fulfillRandomWords //
|
||||||
|
////////////////////////
|
||||||
|
|
||||||
|
modifier raffleEntered() {
|
||||||
|
vm.prank(PLAYER);
|
||||||
|
raffle.enterRaffle{value: raffleEntranceFee}();
|
||||||
|
vm.warp(block.timestamp + automationUpdateInterval + 1);
|
||||||
|
vm.roll(block.number + 1);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier onlyOnDeployedContracts() {
|
||||||
|
if (block.chainid == 31337) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try vm.activeFork() returns (uint256) {
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFulfillRandomWordsCanOnlyBeCalledAfterPerformUpkeep() public raffleEntered onlyOnDeployedContracts {
|
||||||
|
// Arrange
|
||||||
|
// Act / Assert
|
||||||
|
vm.expectRevert("nonexistent request");
|
||||||
|
// vm.mockCall could be used here...
|
||||||
|
VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords(0, address(raffle));
|
||||||
|
|
||||||
|
vm.expectRevert("nonexistent request");
|
||||||
|
|
||||||
|
VRFCoordinatorV2_5Mock(vrfCoordinatorV2_5).fulfillRandomWords(1, address(raffle));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFulfillRandomWordsPicksAWinnerResetsAndSendsMoney() public raffleEntered onlyOnDeployedContracts {
|
||||||
|
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();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
280
foundry-smart-contract-lottery-cu/test/unit/RaffleTest.t.sol
Normal file
280
foundry-smart-contract-lottery-cu/test/unit/RaffleTest.t.sol
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user