├── .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 | Chainlink Foundry logo 9 | 10 |

11 |
12 | 13 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](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 | --------------------------------------------------------------------------------