├── .env.example
├── .github
└── workflows
│ └── pr-tests.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── foundry.toml
├── img
└── chainlink-foundry.png
├── remappings.txt
├── script
├── HelperConfig.sol
├── KeepersCounter.s.sol
├── PriceFeedConsumer.s.sol
└── VRFConsumerV2.s.sol
├── slither.config.json
├── src
├── KeepersCounter.sol
├── PriceFeedConsumer.sol
└── VRFConsumerV2.sol
└── test
├── KeepersCounter.t.sol
├── PriceFeedConsumer.t.sol
├── VRFConsumerV2.t.sol
├── mocks
├── LinkToken.sol
├── MockOracle.sol
├── MockV3Aggregator.sol
└── MockVRFCoordinatorV2.sol
└── utils
└── Cheats.sol
/.env.example:
--------------------------------------------------------------------------------
1 | SEPOLIA_RPC_URL=asdfasdf
2 | ETHERSCAN_API_KEY=asdfasfs
3 |
4 | # Foundry-Chainlink Toolkit
5 | FCT_PLUGIN_PATH=lib/foundry-chainlink-toolkit
6 |
7 | # Postgres Properties
8 | POSTGRES_USER=chainlink
9 | POSTGRES_PASSWORD=password123456789
10 | PG_MAX_CONNECTIONS=500
11 |
12 | # Deploy Properties
13 | PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
14 | RPC_URL=http://localhost:8545
15 | CHAINLINK_CONTAINER_NAME=foundry-chainlink-node
16 | COMPOSE_PROJECT_NAME=foundry-chainlink-plugin
17 | # Chainlink Common Properties
18 | ROOT=/chainlink
19 | ETH_CHAIN_ID=1337
20 | ETH_URL=ws://host.docker.internal:8545
21 | ETH_HTTP_URL=http://host.docker.internal:8545
22 | CHAINLINK_TLS_PORT=0
23 | LOG_LEVEL=debug
24 | SECURE_COOKIES=false
25 | ALLOW_ORIGINS=*
26 |
--------------------------------------------------------------------------------
/.github/workflows/pr-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests On PR
2 |
3 | on:
4 | # Run tests for pull requests to the main or develop branch
5 | pull_request:
6 | branches: [main, develop]
7 | # Run tests on pushes to all branches
8 | push:
9 | branches: ["**"]
10 | workflow_dispatch:
11 |
12 |
13 | jobs:
14 | test:
15 | name: run forge test
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v3
19 | with:
20 | submodules: recursive
21 |
22 | - name: Install Foundry
23 | uses: foundry-rs/foundry-toolchain@v1
24 | with:
25 | version: nightly
26 |
27 | - name: Run tests
28 | run: forge test -vvv
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cache/
2 | out/
3 | .gas-snapshot
4 | .vscode/
5 | .env
6 | .encryptedKey
7 | broadcast/
8 |
9 | node_modules
10 | package-lock.json
11 |
12 | .DS_Store
13 | src/.DS_Store
14 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/solmate"]
2 | path = lib/solmate
3 | url = https://github.com/transmissions11/solmate
4 | [submodule "lib/forge-std"]
5 | path = lib/forge-std
6 | url = https://github.com/foundry-rs/forge-std
7 | [submodule "lib/foundry-chainlink-toolkit"]
8 | path = lib/foundry-chainlink-toolkit
9 | url = https://github.com/smartcontractkit/foundry-chainlink-toolkit
10 | branch = feature/integration-automation
11 | [submodule "lib/chainlink-evm"]
12 | path = lib/chainlink-evm
13 | url = https://github.com/smartcontractkit/chainlink-evm
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 2018 SmartContract ChainLink, Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | _Note: This repo has been recently updated for Sepolia_
2 |
3 | # Foundry Starter Kit
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | [](https://gitpod.io/#https://github.com/smartcontractkit/foundry-starter-kit)
14 |
15 | Foundry Starter Kit is a repo that shows developers how to quickly build, test, and deploy smart contracts with one of the fastest frameworks out there, [foundry](https://github.com/gakonst/foundry)!
16 |
17 | - [Foundry Starter Kit](#foundry-starter-kit)
18 | - [Getting Started](#getting-started)
19 | - [Requirements](#requirements)
20 | - [Quickstart](#quickstart)
21 | - [Testing](#testing)
22 | - [Deploying to a network](#deploying-to-a-network)
23 | - [Setup](#setup)
24 | - [Deploying](#deploying)
25 | - [Working with a local network](#working-with-a-local-network)
26 | - [Working with other chains](#working-with-other-chains)
27 | - [Security](#security)
28 | - [Contributing](#contributing)
29 | - [Thank You!](#thank-you)
30 | - [Resources](#resources)
31 | - [TODO](#todo)
32 |
33 | # Getting Started
34 |
35 | ## Requirements
36 |
37 | Please install the following:
38 |
39 | - [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
40 | - You'll know you've done it right if you can run `git --version`
41 | - [Foundry / Foundryup](https://github.com/gakonst/foundry)
42 | - This will install `forge`, `cast`, and `anvil`
43 | - You can test you've installed them right by running `forge --version` and get an output like: `forge 0.2.0 (f016135 2022-07-04T00:15:02.930499Z)`
44 | - To get the latest of each, just run `foundryup`
45 |
46 | ## Quickstart
47 |
48 | ```sh
49 | git clone https://github.com/smartcontractkit/foundry-starter-kit
50 | cd foundry-starter-kit
51 | ```
52 |
53 | ## Install dependencies as follows:
54 |
55 | Run `forge install` to install dependencies. [Foundry uses git submodules](https://book.getfoundry.sh/projects/dependencies) as its dependency management system.
56 |
57 | > ⚠️ when running forge install, you may see an error message if you have uncomitted changes in your repo. Read the message carefully - it may inform you that you can add the `--no-commit` flag to each of these `install` commands if your workspace has uncommitted changes.
58 |
59 | You can update dependencies by running `forge update`
60 |
61 | ## Testing
62 | To check that everything is compiling and working as intended after cloning and installing dependencies, run
63 | ```
64 | forge test
65 | ```
66 |
67 | All tests should pass.
68 |
69 | # Chainlink Foundry Starter Kit
70 |
71 | Implementation of the following 4 Chainlink services using the [Foundry] (https://book.getfoundry.sh/) smart contract development tooling:
72 |
73 | - [Chainlink Price Feeds](https://docs.chain.link/docs/using-chainlink-reference-contracts)
74 | - [Chainlink VRF V2](https://docs.chain.link/docs/chainlink-vrf)
75 | - [Chainlink Automation](https://docs.chain.link/chainlink-automation/introduction)
76 |
77 | For [Chainlink Functions](https://docs.chain.link/chainlink-functions) please go to these starter kits: [Hardhat](https://github.com/smartcontractkit/functions-hardhat-starter-kit) | [Foundry (coming soon)](https://github.com/smartcontractkit/functions-foundry-starter-kit)
78 |
79 | For [Chainlink CCIP (Cross Chain Interoperability Prototocol)](https://docs.chain.link/ccip) please go to these starter kits: [Hardhat](https://github.com/smartcontractkit/ccip-starter-kit-hardhat) | [Foundry](https://github.com/smartcontractkit/ccip-starter-kit-foundry)
80 |
81 | # Deploying to a network
82 |
83 | Deploying to a network uses the [foundry scripting system](https://book.getfoundry.sh/tutorials/solidity-scripting.html), where you write your deploy scripts in solidity!
84 |
85 | ## Setup
86 |
87 | We'll demo using the Sepolia testnet. (Go here for [testnet sepolia ETH](https://faucets.chain.link/).)
88 |
89 | You'll need to add the following variables to a `.env` file:
90 |
91 | - `SEPOLIA_RPC_URL`: A URL to connect to the blockchain. You can get one for free from [Infura](https://www.infura.io/) account
92 | - `PRIVATE_KEY`: A private key from your wallet. You can get a private key from a new [Metamask](https://metamask.io/) account
93 | - Additionally, if you want to deploy to a testnet, you'll need test ETH and/or LINK. You can get them from [faucets.chain.link](https://faucets.chain.link/).
94 | - Optional `ETHERSCAN_API_KEY`: If you want to verify on etherscan
95 |
96 | When you've added your environment variables to the `.env` file, run `source .env` in your terminal (and for each new terminal session) to load the environment variables into your terminal.
97 |
98 | ## Deploying
99 |
100 | Deploy scripts are in `./script`. The relevant Chainlink Service can be determined from the name of the Contract script. `HelperConfig` is not meant to be deployed.
101 |
102 | To deploy one of the Chainlink Service consumer contracts run the script as follows:
103 |
104 | ```
105 | forge script script/${CONTRACT_NAME}.s.sol:Deploy${CONTRACT_NAME} --rpc-url $SEPOLIA_RPC_URL --private-key PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvv
106 | make deploy-sepolia contract=
107 | ```
108 |
109 | For example, to deploy the `PriceFeedConsumer` contract:
110 |
111 | ```
112 | forge script script/PriceFeedConsumer.s.sol:DeployPriceFeedConsumer --rpc-url $SEPOLIA_RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY -vvvv
113 | ```
114 |
115 | If you don't have an `ETHERSCAN_API_KEY`, you can omit `--verify --etherscan-api-key $ETHERSCAN_API_KEY`
116 |
117 |
118 | ### Working with Anvil local development network
119 |
120 | Foundry comes with local network [anvil](https://book.getfoundry.sh/anvil/index.html) baked in, and allows us to deploy to our local network for quick testing locally.
121 |
122 | To start a local network run the following in a new terminal window or tab:
123 |
124 | ```
125 | anvil
126 | ```
127 |
128 | This will spin up a local blockchain on `http://localhost:8545` : (see console output for the mnemonic used, and 10 private keys and their associated wallet address), so you can use the same private key each time.
129 |
130 | Then, you can deploy to it with one of those private keys; in this example we use the first one:
131 |
132 | ```
133 | forge script script/${contract}.s.sol:Deploy${contract} --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast
134 | ```
135 |
136 |
137 | ### Working with other chains
138 |
139 | To add a chain, you'd just need to pass in the RPC URL for the relevant chain to the `--rpc-url` flag.
140 |
141 | ```
142 | forge script script/${contract}.s.sol:Deploy${contract} --rpc-url ${_RPC_URL} --private-key ${PRIVATE_KEY} --broadcast -vvvv
143 |
144 | ```
145 |
146 | # Security
147 |
148 | This framework comes with slither parameters, a popular security framework from [Trail of Bits](https://www.trailofbits.com/). To use slither, you'll first need to [install python](https://www.python.org/downloads/) and [install slither](https://github.com/crytic/slither#how-to-install).
149 |
150 | Then, you can run:
151 |
152 | ```
153 | make slither
154 | ```
155 |
156 | And get your slither output.
157 |
158 | # Contributing
159 |
160 | Contributions are always welcome! Open a PR or an issue!
161 | If you do contribute please add `solidity.formatter": "forge` to your VSCode Settings, or run `forge fmt` before you commit and push.
162 |
163 | # Thank You!
164 |
165 | ## Resources
166 |
167 | - [Chainlink Documentation](https://docs.chain.link/)
168 | - [Foundry Documentation](https://book.getfoundry.sh/)
169 |
170 | ### TODO
171 |
172 | [ ] Add bash scripts to interact with contracts using `cast`
173 |
174 | [ ] Make deploying contracts to `anvil` simpler
175 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = 'src'
3 | out = 'out'
4 | libs = ['lib']
5 | gas_reports = ["*"]
6 | optimizer = true
7 | optimizer_runs = 20000
8 | fs_permissions = [{ access = "read", path = "lib/foundry-chainlink-toolkit/out"}]
9 |
10 | # Remappings in remappings.txt
11 |
12 | # See more config options https://github.com/gakonst/foundry/tree/master/config
--------------------------------------------------------------------------------
/img/chainlink-foundry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smartcontractkit/foundry-starter-kit/fd4ab9df89f1fbaf4fb04f189e9ffa2842605ef8/img/chainlink-foundry.png
--------------------------------------------------------------------------------
/remappings.txt:
--------------------------------------------------------------------------------
1 | @solmate=lib/solmate/src/
2 | @std=lib/forge-std/src/
3 | @clones=lib/clones-with-immutable-args/src/
4 | @chainlink/=lib/chainlink-evm/
5 | forge-std/=lib/forge-std/src/
6 |
--------------------------------------------------------------------------------
/script/HelperConfig.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.7;
3 |
4 | contract HelperConfig {
5 | NetworkConfig public activeNetworkConfig;
6 |
7 | struct NetworkConfig {
8 | address oracle;
9 | bytes32 jobId;
10 | uint256 chainlinkFee;
11 | address link;
12 | uint256 updateInterval;
13 | address priceFeed;
14 | uint64 subscriptionId;
15 | address vrfCoordinator;
16 | bytes32 keyHash;
17 | }
18 |
19 | mapping(uint256 => NetworkConfig) public chainIdToNetworkConfig;
20 |
21 | constructor() {
22 | chainIdToNetworkConfig[11155111] = getSepoliaEthConfig();
23 | chainIdToNetworkConfig[31337] = getAnvilEthConfig();
24 |
25 | activeNetworkConfig = chainIdToNetworkConfig[block.chainid];
26 | }
27 |
28 | function getSepoliaEthConfig() internal pure returns (NetworkConfig memory sepoliaNetworkConfig) {
29 | sepoliaNetworkConfig = NetworkConfig({
30 | oracle: 0x6090149792dAAeE9D1D568c9f9a6F6B46AA29eFD,
31 | jobId: "ca98366cc7314957b8c012c72f05aeeb",
32 | chainlinkFee: 1e17,
33 | link: 0x779877A7B0D9E8603169DdbD7836e478b4624789,
34 | updateInterval: 60, // every minute
35 | priceFeed: 0x694AA1769357215DE4FAC081bf1f309aDC325306, // ETH / USD
36 | subscriptionId: 0, // UPDATE ME!
37 | vrfCoordinator: 0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625,
38 | keyHash: 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c
39 | });
40 | }
41 |
42 | function getAnvilEthConfig() internal pure returns (NetworkConfig memory anvilNetworkConfig) {
43 | anvilNetworkConfig = NetworkConfig({
44 | oracle: address(0), // This is a mock
45 | jobId: "6b88e0402e5d415eb946e528b8e0c7ba",
46 | chainlinkFee: 1e17,
47 | link: address(0), // This is a mock
48 | updateInterval: 60, // every minute
49 | priceFeed: address(0), // This is a mock
50 | subscriptionId: 0,
51 | vrfCoordinator: address(0), // This is a mock
52 | keyHash: 0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/script/KeepersCounter.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.7;
3 |
4 | import "forge-std/Script.sol";
5 | import "../src/KeepersCounter.sol";
6 | import "./HelperConfig.sol";
7 |
8 | contract DeployKeepersCounter is Script, HelperConfig {
9 | function run() external {
10 | HelperConfig helperConfig = new HelperConfig();
11 |
12 | (,,,, uint256 updateInterval,,,,) = helperConfig.activeNetworkConfig();
13 |
14 | vm.startBroadcast();
15 |
16 | new KeepersCounter(updateInterval);
17 |
18 | vm.stopBroadcast();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/script/PriceFeedConsumer.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.7;
3 |
4 | import "forge-std/Script.sol";
5 | import "../src/PriceFeedConsumer.sol";
6 | import "./HelperConfig.sol";
7 | import "../test/mocks/MockV3Aggregator.sol";
8 |
9 | contract DeployPriceFeedConsumer is Script, HelperConfig {
10 | uint8 constant DECIMALS = 18;
11 | int256 constant INITIAL_ANSWER = 2000e18;
12 |
13 | function run() external {
14 | HelperConfig helperConfig = new HelperConfig();
15 |
16 | (,,,,, address priceFeed,,,) = helperConfig.activeNetworkConfig();
17 |
18 | if (priceFeed == address(0)) {
19 | priceFeed = address(new MockV3Aggregator(DECIMALS, INITIAL_ANSWER));
20 | }
21 |
22 | vm.startBroadcast();
23 |
24 | new PriceFeedConsumer(priceFeed);
25 |
26 | vm.stopBroadcast();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/script/VRFConsumerV2.s.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.7;
3 |
4 | import "forge-std/Script.sol";
5 | import "../src/VRFConsumerV2.sol";
6 | import "./HelperConfig.sol";
7 | import "../test/mocks/LinkToken.sol";
8 | import "../test/mocks/MockVRFCoordinatorV2.sol";
9 |
10 | contract DeployVRFConsumerV2 is Script, HelperConfig {
11 | function run() external {
12 | HelperConfig helperConfig = new HelperConfig();
13 |
14 | (,,, address link,,, uint64 subscriptionId, address vrfCoordinator, bytes32 keyHash) =
15 | helperConfig.activeNetworkConfig();
16 |
17 | if (link == address(0)) {
18 | link = address(new LinkToken());
19 | }
20 |
21 | if (vrfCoordinator == address(0)) {
22 | vrfCoordinator = address(new MockVRFCoordinatorV2());
23 | }
24 |
25 | vm.startBroadcast();
26 |
27 | new VRFConsumerV2(subscriptionId, vrfCoordinator, link, keyHash);
28 |
29 | vm.stopBroadcast();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/slither.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "filter_paths": "lib",
3 | "solc_remaps": [
4 | "ds-test/=lib/ds-test/src/",
5 | "forge-std/=lib/forge-std/src/",
6 | "@chainlink/=lib/chainlink-brownie-contracts/"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/src/KeepersCounter.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.7;
3 |
4 | import "@chainlink/contracts/src/v0.8/automation/interfaces/KeeperCompatibleInterface.sol";
5 |
6 | contract KeepersCounter is KeeperCompatibleInterface {
7 | uint256 public counter;
8 | uint256 public immutable interval;
9 | uint256 public lastTimeStamp;
10 |
11 | constructor(uint256 updateInterval) {
12 | interval = updateInterval;
13 | lastTimeStamp = block.timestamp;
14 | counter = 0;
15 | }
16 |
17 | function checkUpkeep(bytes memory /* checkData */ )
18 | public
19 | view
20 | override
21 | returns (bool upkeepNeeded, bytes memory performData)
22 | {
23 | upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
24 | performData = bytes("");
25 | }
26 |
27 | function performUpkeep(bytes calldata /* performData */ ) external override {
28 | // add some verification
29 | (bool upkeepNeeded,) = checkUpkeep("");
30 | require(upkeepNeeded, "Time interval not met");
31 |
32 | lastTimeStamp = block.timestamp;
33 | counter = counter + 1;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/PriceFeedConsumer.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.7;
3 |
4 | import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
5 |
6 | /**
7 | * @title The PriceConsumerV3 contract
8 | * @notice Acontract that returns latest price from Chainlink Price Feeds
9 | */
10 | contract PriceFeedConsumer {
11 | AggregatorV3Interface internal immutable priceFeed;
12 |
13 | constructor(address _priceFeed) {
14 | priceFeed = AggregatorV3Interface(_priceFeed);
15 | }
16 |
17 | /**
18 | * @notice Returns the latest price
19 | *
20 | * @return latest price
21 | */
22 | function getLatestPrice() public view returns (int256) {
23 | (
24 | ,
25 | /* uint80 roundID */
26 | int256 price,
27 | ,
28 | ,
29 | ) = /* uint256 startedAt */
30 | /* uint256 timeStamp */
31 | /* uint80 answeredInRound */
32 | priceFeed.latestRoundData();
33 | return price;
34 | }
35 |
36 | /**
37 | * @notice Returns the Price Feed address
38 | *
39 | * @return Price Feed address
40 | */
41 | function getPriceFeed() public view returns (AggregatorV3Interface) {
42 | return priceFeed;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/VRFConsumerV2.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | // An example of a consumer contract that relies on a subscription for funding.
3 | pragma solidity ^0.8.7;
4 |
5 | import "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
6 | import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
7 | import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
8 |
9 | /**
10 | * @title The VRFConsumerV2 contract
11 | * @notice A contract that gets random values from Chainlink VRF V2
12 | */
13 | contract VRFConsumerV2 is VRFConsumerBaseV2 {
14 | VRFCoordinatorV2Interface immutable COORDINATOR;
15 | LinkTokenInterface immutable LINKTOKEN;
16 |
17 | // Your subscription ID.
18 | uint64 immutable s_subscriptionId;
19 |
20 | // The gas lane to use, which specifies the maximum gas price to bump to.
21 | // For a list of available gas lanes on each network,
22 | // see https://docs.chain.link/docs/vrf-contracts/#configurations
23 | bytes32 immutable s_keyHash;
24 |
25 | // Depends on the number of requested values that you want sent to the
26 | // fulfillRandomWords() function. Storing each word costs about 20,000 gas,
27 | // so 100,000 is a safe default for this example contract. Test and adjust
28 | // this limit based on the network that you select, the size of the request,
29 | // and the processing of the callback request in the fulfillRandomWords()
30 | // function.
31 | uint32 immutable s_callbackGasLimit = 100000;
32 |
33 | // The default is 3, but you can set this higher.
34 | uint16 immutable s_requestConfirmations = 3;
35 |
36 | // For this example, retrieve 2 random values in one request.
37 | // Cannot exceed VRFCoordinatorV2.MAX_NUM_WORDS.
38 | uint32 public immutable s_numWords = 2;
39 |
40 | uint256[] public s_randomWords;
41 | uint256 public s_requestId;
42 | address s_owner;
43 |
44 | event ReturnedRandomness(uint256[] randomWords);
45 |
46 | /**
47 | * @notice Constructor inherits VRFConsumerBaseV2
48 | *
49 | * @param subscriptionId - the subscription ID that this contract uses for funding requests
50 | * @param vrfCoordinator - coordinator, check https://docs.chain.link/docs/vrf-contracts/#configurations
51 | * @param keyHash - the gas lane to use, which specifies the maximum gas price to bump to
52 | */
53 | constructor(uint64 subscriptionId, address vrfCoordinator, address link, bytes32 keyHash)
54 | VRFConsumerBaseV2(vrfCoordinator)
55 | {
56 | COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
57 | LINKTOKEN = LinkTokenInterface(link);
58 | s_keyHash = keyHash;
59 | s_owner = msg.sender;
60 | s_subscriptionId = subscriptionId;
61 | }
62 |
63 | /**
64 | * @notice Requests randomness
65 | * Assumes the subscription is funded sufficiently; "Words" refers to unit of data in Computer Science
66 | */
67 | function requestRandomWords() external onlyOwner {
68 | // Will revert if subscription is not set and funded.
69 | s_requestId = COORDINATOR.requestRandomWords(
70 | s_keyHash, s_subscriptionId, s_requestConfirmations, s_callbackGasLimit, s_numWords
71 | );
72 | }
73 |
74 | /**
75 | * @notice Callback function used by VRF Coordinator
76 | *
77 | * @param requestId - id of the request
78 | * @param randomWords - array of random results from VRF Coordinator
79 | */
80 | function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
81 | s_randomWords = randomWords;
82 | emit ReturnedRandomness(randomWords);
83 | }
84 |
85 | modifier onlyOwner() {
86 | require(msg.sender == s_owner);
87 | _;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/test/KeepersCounter.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import "../src/KeepersCounter.sol";
6 | import "forge-std/Test.sol";
7 | import "./utils/Cheats.sol";
8 |
9 | contract KeepersCounterTest is Test {
10 | KeepersCounter public counter;
11 | uint256 public staticTime;
12 | uint256 public INTERVAL;
13 | Cheats internal constant cheats = Cheats(VM_ADDRESS);
14 |
15 | function setUp() public {
16 | staticTime = block.timestamp;
17 | counter = new KeepersCounter(INTERVAL);
18 | cheats.warp(staticTime);
19 | }
20 |
21 | function testCheckupReturnsFalseBeforeTime() public {
22 | (bool upkeepNeeded,) = counter.checkUpkeep("0x");
23 | assertTrue(!upkeepNeeded);
24 | }
25 |
26 | function testCheckupReturnsTrueAfterTime() public {
27 | cheats.warp(staticTime + INTERVAL + 1); // Needs to be more than the interval
28 | (bool upkeepNeeded,) = counter.checkUpkeep("0x");
29 | assertTrue(upkeepNeeded);
30 | }
31 |
32 | function testPerformUpkeepUpdatesTime() public {
33 | // Arrange
34 | uint256 currentCounter = counter.counter();
35 | cheats.warp(staticTime + INTERVAL + 1); // Needs to be more than the interval
36 |
37 | // Act
38 | counter.performUpkeep("0x");
39 |
40 | // Assert
41 | assertTrue(counter.lastTimeStamp() == block.timestamp);
42 | assertTrue(currentCounter + 1 == counter.counter());
43 | }
44 |
45 | function testFuzzingExample(bytes memory variant) public {
46 | // We expect this to fail, no matter how different the input is!
47 | cheats.expectRevert(bytes("Time interval not met"));
48 | counter.performUpkeep(variant);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/test/PriceFeedConsumer.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import "../src/PriceFeedConsumer.sol";
6 | import "./mocks/MockV3Aggregator.sol";
7 | import "forge-std/Test.sol";
8 |
9 | contract PriceFeedConsumerTest is Test {
10 | uint8 public constant DECIMALS = 18;
11 | int256 public constant INITIAL_ANSWER = 1 * 10 ** 18;
12 | PriceFeedConsumer public priceFeedConsumer;
13 | MockV3Aggregator public mockV3Aggregator;
14 |
15 | function setUp() public {
16 | mockV3Aggregator = new MockV3Aggregator(DECIMALS, INITIAL_ANSWER);
17 | priceFeedConsumer = new PriceFeedConsumer(address(mockV3Aggregator));
18 | }
19 |
20 | function testConsumerReturnsStartingValue() public {
21 | int256 price = priceFeedConsumer.getLatestPrice();
22 | assertTrue(price == INITIAL_ANSWER);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/VRFConsumerV2.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "../src/VRFConsumerV2.sol";
5 | import "./mocks/MockVRFCoordinatorV2.sol";
6 | import "./mocks/LinkToken.sol";
7 | import "./utils/Cheats.sol";
8 | import "forge-std/Test.sol";
9 |
10 | contract VRFConsumerV2Test is Test {
11 | LinkToken public linkToken;
12 | MockVRFCoordinatorV2 public vrfCoordinator;
13 | VRFConsumerV2 public vrfConsumer;
14 | Cheats internal constant cheats = Cheats(VM_ADDRESS);
15 |
16 | uint96 constant FUND_AMOUNT = 1 * 10 ** 18;
17 |
18 | // Initialized as blank, fine for testing
19 | uint64 subId;
20 | bytes32 keyHash; // gasLane
21 |
22 | event ReturnedRandomness(uint256[] randomWords);
23 |
24 | function setUp() public {
25 | linkToken = new LinkToken();
26 | vrfCoordinator = new MockVRFCoordinatorV2();
27 | subId = vrfCoordinator.createSubscription();
28 | vrfCoordinator.fundSubscription(subId, FUND_AMOUNT);
29 | vrfConsumer = new VRFConsumerV2(subId, address(vrfCoordinator), address(linkToken), keyHash);
30 | vrfCoordinator.addConsumer(subId, address(vrfConsumer));
31 | }
32 |
33 | function testCanRequestRandomness() public {
34 | uint256 startingRequestId = vrfConsumer.s_requestId();
35 | vrfConsumer.requestRandomWords();
36 | assertTrue(vrfConsumer.s_requestId() != startingRequestId);
37 | }
38 |
39 | function testCanGetRandomResponse() public {
40 | vrfConsumer.requestRandomWords();
41 | uint256 requestId = vrfConsumer.s_requestId();
42 |
43 | uint256[] memory words = getWords(requestId);
44 |
45 | // When testing locally you MUST call fulfillRandomness youself to get the
46 | // randomness to the consumer contract, since there isn't a chainlink node on your local network
47 | vrfCoordinator.fulfillRandomWords(requestId, address(vrfConsumer));
48 | assertTrue(vrfConsumer.s_randomWords(0) == words[0]);
49 | assertTrue(vrfConsumer.s_randomWords(1) == words[1]);
50 | }
51 |
52 | function testEmitsEventOnFulfillment() public {
53 | vrfConsumer.requestRandomWords();
54 | uint256 requestId = vrfConsumer.s_requestId();
55 | uint256[] memory words = getWords(requestId);
56 |
57 | cheats.expectEmit(false, false, false, true);
58 | emit ReturnedRandomness(words);
59 | // When testing locally you MUST call fulfillRandomness youself to get the
60 | // randomness to the consumer contract, since there isn't a chainlink node on your local network
61 | vrfCoordinator.fulfillRandomWords(requestId, address(vrfConsumer));
62 | }
63 |
64 | function getWords(uint256 requestId) public view returns (uint256[] memory) {
65 | uint256[] memory words = new uint256[](vrfConsumer.s_numWords());
66 | for (uint256 i = 0; i < vrfConsumer.s_numWords(); i++) {
67 | words[i] = uint256(keccak256(abi.encode(requestId, i)));
68 | }
69 | return words;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/mocks/LinkToken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | // @dev This contract has been adapted to fit with dappTools
4 | pragma solidity ^0.8.0;
5 |
6 | import "@solmate/tokens/ERC20.sol";
7 |
8 | interface ERC677Receiver {
9 | function onTokenTransfer(address _sender, uint256 _value, bytes memory _data) external;
10 | }
11 |
12 | contract LinkToken is ERC20 {
13 | uint256 constant INITIAL_SUPPLY = 1000000000000000000000000;
14 | uint8 constant DECIMALS = 18;
15 |
16 | constructor() ERC20("LinkToken", "LINK", DECIMALS) {
17 | _mint(msg.sender, INITIAL_SUPPLY);
18 | }
19 |
20 | event Transfer(address indexed from, address indexed to, uint256 value, bytes data);
21 |
22 | /**
23 | * @dev transfer token to a contract address with additional data if the recipient is a contact.
24 | * @param _to The address to transfer to.
25 | * @param _value The amount to be transferred.
26 | * @param _data The extra data to be passed to the receiving contract.
27 | */
28 | function transferAndCall(address _to, uint256 _value, bytes memory _data) public virtual returns (bool success) {
29 | super.transfer(_to, _value);
30 | // emit Transfer(msg.sender, _to, _value, _data);
31 | emit Transfer(msg.sender, _to, _value, _data);
32 | if (isContract(_to)) {
33 | contractFallback(_to, _value, _data);
34 | }
35 | return true;
36 | }
37 |
38 | // PRIVATE
39 |
40 | function contractFallback(address _to, uint256 _value, bytes memory _data) private {
41 | ERC677Receiver receiver = ERC677Receiver(_to);
42 | receiver.onTokenTransfer(msg.sender, _value, _data);
43 | }
44 |
45 | function isContract(address _addr) private view returns (bool hasCode) {
46 | uint256 length;
47 | assembly {
48 | length := extcodesize(_addr)
49 | }
50 | return length > 0;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/mocks/MockOracle.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "@chainlink/contracts/src/v0.8/operatorforwarder/interfaces/ChainlinkRequestInterface.sol";
5 | import "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";
6 |
7 | /**
8 | * @title The LinkTokenReceiver contract - used for the MockOracle below
9 | */
10 | abstract contract LinkTokenReceiver {
11 | bytes4 private constant ORACLE_REQUEST_SELECTOR = 0x40429946;
12 | uint256 private constant SELECTOR_LENGTH = 4;
13 | uint256 private constant EXPECTED_REQUEST_WORDS = 2;
14 | uint256 private constant MINIMUM_REQUEST_LENGTH = SELECTOR_LENGTH + (32 * EXPECTED_REQUEST_WORDS);
15 |
16 | /**
17 | * @notice Called when LINK is sent to the contract via `transferAndCall`
18 | * @dev The data payload's first 2 words will be overwritten by the `_sender` and `_amount`
19 | * values to ensure correctness. Calls oracleRequest.
20 | * @param _sender Address of the sender
21 | * @param _amount Amount of LINK sent (specified in wei)
22 | * @param _data Payload of the transaction
23 | */
24 | function onTokenTransfer(address _sender, uint256 _amount, bytes memory _data)
25 | public
26 | onlyLINK
27 | validRequestLength(_data)
28 | permittedFunctionsForLINK(_data)
29 | {
30 | assembly {
31 | // solhint-disable-next-line avoid-low-level-calls
32 | mstore(add(_data, 36), _sender) // ensure correct sender is passed
33 | // solhint-disable-next-line avoid-low-level-calls
34 | mstore(add(_data, 68), _amount) // ensure correct amount is passed
35 | }
36 | // solhint-disable-next-line avoid-low-level-calls
37 | (bool success,) = address(this).delegatecall(_data); // calls oracleRequest
38 | require(success, "Unable to create request");
39 | }
40 |
41 | function getChainlinkToken() public view virtual returns (address);
42 |
43 | /**
44 | * @dev Reverts if not sent from the LINK token
45 | */
46 | modifier onlyLINK() {
47 | require(msg.sender == getChainlinkToken(), "Must use LINK token");
48 | _;
49 | }
50 |
51 | /**
52 | * @dev Reverts if the given data does not begin with the `oracleRequest` function selector
53 | * @param _data The data payload of the request
54 | */
55 | modifier permittedFunctionsForLINK(bytes memory _data) {
56 | bytes4 funcSelector;
57 | assembly {
58 | // solhint-disable-next-line avoid-low-level-calls
59 | funcSelector := mload(add(_data, 32))
60 | }
61 | require(funcSelector == ORACLE_REQUEST_SELECTOR, "Must use whitelisted functions");
62 | _;
63 | }
64 |
65 | /**
66 | * @dev Reverts if the given payload is less than needed to create a request
67 | * @param _data The request payload
68 | */
69 | modifier validRequestLength(bytes memory _data) {
70 | require(_data.length >= MINIMUM_REQUEST_LENGTH, "Invalid request length");
71 | _;
72 | }
73 | }
74 |
75 | /**
76 | * @title The Chainlink Mock Oracle contract
77 | * @notice Chainlink smart contract developers can use this to test their contracts
78 | */
79 | contract MockOracle is ChainlinkRequestInterface, LinkTokenReceiver {
80 | uint256 public constant EXPIRY_TIME = 5 minutes;
81 | uint256 private constant MINIMUM_CONSUMER_GAS_LIMIT = 400000;
82 |
83 | struct Request {
84 | address callbackAddr;
85 | bytes4 callbackFunctionId;
86 | }
87 |
88 | LinkTokenInterface internal LinkToken;
89 | mapping(bytes32 => Request) private commitments;
90 |
91 | event OracleRequest(
92 | bytes32 indexed specId,
93 | address requester,
94 | bytes32 requestId,
95 | uint256 payment,
96 | address callbackAddr,
97 | bytes4 callbackFunctionId,
98 | uint256 cancelExpiration,
99 | uint256 dataVersion,
100 | bytes data
101 | );
102 |
103 | event CancelOracleRequest(bytes32 indexed requestId);
104 |
105 | /**
106 | * @notice Deploy with the address of the LINK token
107 | * @dev Sets the LinkToken address for the imported LinkTokenInterface
108 | * @param _link The address of the LINK token
109 | */
110 | constructor(address _link) {
111 | LinkToken = LinkTokenInterface(_link); // external but already deployed and unalterable
112 | }
113 |
114 | /**
115 | * @notice Creates the Chainlink request
116 | * @dev Stores the hash of the params as the on-chain commitment for the request.
117 | * Emits OracleRequest event for the Chainlink node to detect.
118 | * @param _sender The sender of the request
119 | * @param _payment The amount of payment given (specified in wei)
120 | * @param _specId The Job Specification ID
121 | * @param _callbackAddress The callback address for the response
122 | * @param _callbackFunctionId The callback function ID for the response
123 | * @param _nonce The nonce sent by the requester
124 | * @param _dataVersion The specified data version
125 | * @param _data The CBOR payload of the request
126 | */
127 | function oracleRequest(
128 | address _sender,
129 | uint256 _payment,
130 | bytes32 _specId,
131 | address _callbackAddress,
132 | bytes4 _callbackFunctionId,
133 | uint256 _nonce,
134 | uint256 _dataVersion,
135 | bytes calldata _data
136 | ) external override onlyLINK checkCallbackAddress(_callbackAddress) {
137 | bytes32 requestId = keccak256(abi.encodePacked(_sender, _nonce));
138 | require(commitments[requestId].callbackAddr == address(0), "Must use a unique ID");
139 | // solhint-disable-next-line not-rely-on-time
140 | uint256 expiration = block.timestamp + EXPIRY_TIME;
141 |
142 | commitments[requestId] = Request(_callbackAddress, _callbackFunctionId);
143 |
144 | emit OracleRequest(
145 | _specId,
146 | _sender,
147 | requestId,
148 | _payment,
149 | _callbackAddress,
150 | _callbackFunctionId,
151 | expiration,
152 | _dataVersion,
153 | _data
154 | );
155 | }
156 |
157 | /**
158 | * @notice Called by the Chainlink node to fulfill requests
159 | * @dev Given params must hash back to the commitment stored from `oracleRequest`.
160 | * Will call the callback address' callback function without bubbling up error
161 | * checking in a `require` so that the node can get paid.
162 | * @param _requestId The fulfillment request ID that must match the requester's
163 | * @param _data The data to return to the consuming contract
164 | * @return Status if the external call was successful
165 | */
166 | function fulfillOracleRequest(bytes32 _requestId, bytes32 _data)
167 | external
168 | isValidRequest(_requestId)
169 | returns (bool)
170 | {
171 | Request memory req = commitments[_requestId];
172 | delete commitments[_requestId];
173 | require(gasleft() >= MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas");
174 | // All updates to the oracle's fulfillment should come before calling the
175 | // callback(addr+functionId) as it is untrusted.
176 | // See: https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern
177 | (bool success,) = req.callbackAddr.call(abi.encodeWithSelector(req.callbackFunctionId, _requestId, _data)); // solhint-disable-line avoid-low-level-calls
178 | return success;
179 | }
180 |
181 | /**
182 | * @notice Allows requesters to cancel requests sent to this oracle contract. Will transfer the LINK
183 | * sent for the request back to the requester's address.
184 | * @dev Given params must hash to a commitment stored on the contract in order for the request to be valid
185 | * Emits CancelOracleRequest event.
186 | * @param _requestId The request ID
187 | * @param _payment The amount of payment given (specified in wei)
188 | * @param _expiration The time of the expiration for the request
189 | */
190 | function cancelOracleRequest(bytes32 _requestId, uint256 _payment, bytes4, uint256 _expiration) external override {
191 | require(commitments[_requestId].callbackAddr != address(0), "Must use a unique ID");
192 | // solhint-disable-next-line not-rely-on-time
193 | require(_expiration <= block.timestamp, "Request is not expired");
194 |
195 | delete commitments[_requestId];
196 | emit CancelOracleRequest(_requestId);
197 |
198 | assert(LinkToken.transfer(msg.sender, _payment));
199 | }
200 |
201 | /**
202 | * @notice Returns the address of the LINK token
203 | * @dev This is the public implementation for chainlinkTokenAddress, which is
204 | * an internal method of the ChainlinkClient contract
205 | */
206 | function getChainlinkToken() public view override returns (address) {
207 | return address(LinkToken);
208 | }
209 |
210 | // MODIFIERS
211 |
212 | /**
213 | * @dev Reverts if request ID does not exist
214 | * @param _requestId The given request ID to check in stored `commitments`
215 | */
216 | modifier isValidRequest(bytes32 _requestId) {
217 | require(commitments[_requestId].callbackAddr != address(0), "Must have a valid requestId");
218 | _;
219 | }
220 |
221 | /**
222 | * @dev Reverts if the callback address is the LINK token
223 | * @param _to The callback address
224 | */
225 | modifier checkCallbackAddress(address _to) {
226 | require(_to != address(LinkToken), "Cannot callback to LINK");
227 | _;
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/test/mocks/MockV3Aggregator.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | /**
5 | * @title MockV3Aggregator
6 | * @notice Based on the FluxAggregator contract
7 | * @notice Use this contract when you need to test
8 | * other contract's ability to read data from an
9 | * aggregator contract, but how the aggregator got
10 | * its answer is unimportant
11 | */
12 | contract MockV3Aggregator {
13 | uint256 public constant version = 0;
14 |
15 | uint8 public decimals;
16 | int256 public latestAnswer;
17 | uint256 public latestTimestamp;
18 | uint256 public latestRound;
19 |
20 | mapping(uint256 => int256) public getAnswer;
21 | mapping(uint256 => uint256) public getTimestamp;
22 | mapping(uint256 => uint256) private getStartedAt;
23 |
24 | constructor(uint8 _decimals, int256 _initialAnswer) {
25 | decimals = _decimals;
26 | updateAnswer(_initialAnswer);
27 | }
28 |
29 | function updateAnswer(int256 _answer) public {
30 | latestAnswer = _answer;
31 | latestTimestamp = block.timestamp;
32 | latestRound++;
33 | getAnswer[latestRound] = _answer;
34 | getTimestamp[latestRound] = block.timestamp;
35 | getStartedAt[latestRound] = block.timestamp;
36 | }
37 |
38 | function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public {
39 | latestRound = _roundId;
40 | latestAnswer = _answer;
41 | latestTimestamp = _timestamp;
42 | getAnswer[latestRound] = _answer;
43 | getTimestamp[latestRound] = _timestamp;
44 | getStartedAt[latestRound] = _startedAt;
45 | }
46 |
47 | function getRoundData(uint80 _roundId)
48 | external
49 | view
50 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
51 | {
52 | return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId);
53 | }
54 |
55 | function latestRoundData()
56 | external
57 | view
58 | returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
59 | {
60 | return (
61 | uint80(latestRound),
62 | getAnswer[latestRound],
63 | getStartedAt[latestRound],
64 | getTimestamp[latestRound],
65 | uint80(latestRound)
66 | );
67 | }
68 |
69 | function description() external pure returns (string memory) {
70 | return "v0.6/tests/MockV3Aggregator.sol";
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/test/mocks/MockVRFCoordinatorV2.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.0;
3 |
4 | import "@chainlink/contracts/src/v0.8/vrf/mocks/VRFCoordinatorV2Mock.sol";
5 |
6 | contract MockVRFCoordinatorV2 is VRFCoordinatorV2Mock {
7 | uint96 constant MOCK_BASE_FEE = 100000000000000000;
8 | uint96 constant MOCK_GAS_PRICE_LINK = 1e9;
9 |
10 | constructor() VRFCoordinatorV2Mock(MOCK_BASE_FEE, MOCK_GAS_PRICE_LINK) {}
11 | }
12 |
--------------------------------------------------------------------------------
/test/utils/Cheats.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity ^0.8.0;
3 |
4 | abstract contract Cheats {
5 | // sets the block timestamp to x
6 | function warp(uint256 x) public virtual;
7 |
8 | // sets the block number to x
9 | function roll(uint256 x) public virtual;
10 |
11 | // sets the slot loc of contract c to val
12 | function store(address c, bytes32 loc, bytes32 val) public virtual;
13 |
14 | function ffi(string[] calldata) external virtual returns (bytes memory);
15 |
16 | function expectEmit(bool, bool, bool, bool) external virtual;
17 |
18 | function expectRevert(bytes calldata msg) external virtual;
19 | }
20 |
--------------------------------------------------------------------------------