Skip to content

Commit

Permalink
Merge pull request #87 from smartcontractkit/chore/fix-issues
Browse files Browse the repository at this point in the history
Add VRF V2, Fuzz Testing, NatSpec, Coverage
  • Loading branch information
PatrickAlphaC authored Mar 10, 2022
2 parents b0b35e1 + 0f0dbf6 commit 5492cde
Show file tree
Hide file tree
Showing 25 changed files with 760 additions and 323 deletions.
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
KOVAN_RPC_URL='https://kovan.infura.io/v3/1234567890'
RINKEBY_RPC_URL='https://alchemy.infura.io/v3/1234567890'
RINKEBY_RPC_URL='https://rinkeby.infura.io/v3/1234567890'
POLYGON_MAINNET_RPC_URL='https://rpc-mainnet.maticvigil.com'
PRIVATE_KEY='abcdefg'
ALCHEMY_MAINNET_RPC_URL="https://eth-mainnet.alchemyapi.io/v2/your-api-key"
REPORT_GAS=true
COINMARKETCAP_API_KEY="YOUR_KEY"
AUTO_FUND=true
AUTO_FUND=true
VRF_SUBSCRIPTION_ID=YOUR_SUBSCRIPTION_ID
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,6 @@ lint/outputs/
lint/tmp/
# lint/reports/

gas-report.txt
gas-report.txt

contracts/test/fuzzing/crytic-export
3 changes: 3 additions & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
skipFiles: ['test/fuzzing/KeepersCounterEchidnaTest.sol', 'test/LinkToken.sol', 'test/MockOracle.sol', 'test/MockV3Aggregator.sol', 'test/VRFCoordinatorV2Mock.sol'],
};
107 changes: 81 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</p>
<br/>

- [Chainlink Hardhat Box](#chainlink-hardhat-box)
- [Chainlink Hardhat Starter Kit](#chainlink-hardhat-starter-kit)
- [Getting Started](#getting-started)
- [Requirements](#requirements)
- [Quickstart](#quickstart)
Expand All @@ -15,7 +15,7 @@
- [Deploying Contracts](#deploying-contracts)
- [Run a Local Network](#run-a-local-network)
- [Using a Testnet or Live Network (like Mainnet or Polygon)](#using-a-testnet-or-live-network-like-mainnet-or-polygon)
- [Kovan Ethereum Testnet Setup](#kovan-ethereum-testnet-setup)
- [Rinkeby Ethereum Testnet Setup](#rinkeby-ethereum-testnet-setup)
- [Forking](#forking)
- [Auto-Funding](#auto-funding)
- [Test](#test)
Expand All @@ -29,11 +29,13 @@
- [Linting](#linting)
- [Code Formating](#code-formating)
- [Estimaging Gas](#estimaging-gas)
- [Code Coverage](#code-coverage)
- [Fuzzing](#fuzzing)
- [Contributing](#contributing)
- [Thank You!](#thank-you)
- [Resources](#resources)

# Chainlink Hardhat Box
# Chainlink Hardhat Starter Kit
Implementation of the following 4 Chainlink features using the [Hardhat](https://hardhat.org/) development environment:
- [Chainlink Price Feeds](https://docs.chain.link/docs/using-chainlink-reference-contracts)
- [Chainlink VRF](https://docs.chain.link/docs/chainlink-vrf)
Expand Down Expand Up @@ -139,15 +141,15 @@ To interact with a live or test network, you'll need:
2. A Private Key
3. ETH & LINK token (either testnet or real)

Let's look at an example of setting these up using the Kovan testnet.
Let's look at an example of setting these up using the Rinkeby testnet.

### Kovan Ethereum Testnet Setup
### Rinkeby Ethereum Testnet Setup

First, we will need to set environment variables. We can do so by setting them in our `.env` file (create it if it's not there). You can also read more about [environment variables](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html) from the linked twilio blog. You'll find a sample of what this file will look like in `.env.example`

> IMPORTANT: MAKE SURE YOU'D DONT EXPOSE THE KEYS YOU PUT IN THIS `.env` FILE. By that, I mean don't push them to a public repo, and please try to keep them keys you use in development not associated with any real funds.
1. Set your `KOVAN_RPC_URL` [environment variable.](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html)
1. Set your `RINKEBY_RPC_URL` [environment variable.](https://www.twilio.com/blog/2017/01/how-to-set-environment-variables.html)

You can get one for free from [Alchmey](https://www.alchemy.com/), [Infura](https://infura.io/), or [Moralis](https://moralis.io/speedy-nodes/). This is your connection to the blockchain.

Expand All @@ -163,53 +165,60 @@ Don't commit and push any changes to .env files that may contain sensitive infor

`.env` example:
```
KOVAN_RPC_URL='www.infura.io/asdfadsfafdadf'
RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf'
PRIVATE_KEY='abcdef'
```
`bash` example
```
export KOVAN_RPC_URL='www.infura.io/asdfadsfafdadf'
export RINKEBY_RPC_URL='www.infura.io/asdfadsfafdadf'
export PRIVATE_KEY='abcdef'
```

> You can also use a `MNEMONIC` instead of a `PRIVATE_KEY` environment variable by uncommenting the section in the `hardhat.config.js`, and commenting out the `PRIVATE_KEY` line. However this is not recommended.
For other networks like mainnet and polygon, you can use different environment variables for your RPC URL and your private key. See the `hardhat.config.js` to learn more.

3. Get some Kovan Testnet ETH and LINK
3. Get some Rinkeby Testnet ETH and LINK

Head over to the [Chainlink faucets](https://faucets.chain.link/) and get some ETH and LINK. Please follow [the chainlink documentation](https://docs.chain.link/docs/acquire-link/) if unfamiliar.

4. Running commands
4. Create VRF V2 subscription

You should now be all setup! You can run any command and just pass the `--network kovan` now!
Head over to [VRF Subscription Page](https://vrf.chain.link/rinkeby) and create the new subscription. Save your subscription ID and put it in `.env` file as `VRF_SUBSCRIPTION_ID`

5. Running commands

You should now be all setup! You can run any command and just pass the `--network rinkeby` now!

To deploy contracts:

```
yarn hardhat deploy --network kovan
yarn hardhat deploy --network rinkeby
```

To run staging testnet tests
```
yarn hardhat test --network kovan
yarn hardhat test --network rinkeby
```

## Forking

If you'd like to run tests or on a network that is a [forked network](https://hardhat.org/hardhat-network/guides/mainnet-forking.html)
1. Set a `MAINNET_RPC_URL` environment variable that connects to the mainnet.
2. Uncomment the section in your `hardhat.config.js`
2. Choose a block number to select a state of the network you are forking and set it as `FORKING_BLOCK_NUMBER` environment variable. If ignored, it will use the latest block each time which can lead to test inconsistency.
3. Set `enabled` flag to `true`/`false` to enable/disable forking feature
```
// forking: {
// url: MAINNET_RPC_URL
// }
forking: {
url: MAINNET_RPC_URL,
blockNumber: FORKING_BLOCK_NUMBER,
enabled: false,
}
```


## Auto-Funding

This Starter Kit is configured by default to attempt to auto-fund any newly deployed contract that uses Any-API or Chainlink VRF, to save having to manually fund them after each deployment. The amount in LINK to send as part of this process can be modified in the [Starter Kit Config](helper-hardhat-config.js), and are configurable per network.
This Starter Kit is configured by default to attempt to auto-fund any newly deployed contract that uses Any-API, to save having to manually fund them after each deployment. The amount in LINK to send as part of this process can be modified in the [Starter Kit Config](helper-hardhat-config.js), and are configurable per network.

| Parameter | Description | Default Value |
| ---------- | :------------------------------------------------ | :------------ |
Expand All @@ -226,7 +235,7 @@ To run unit tests:
```bash
yarn test
```
Or
or
```
yarn hardhat test
```
Expand All @@ -240,13 +249,25 @@ yarn test-integration
or

```
yarn hardhat test --network kovan
yarn hardhat test --network rinkeby
```

## Performance optimizations

Since all tests are written in a way to be independent from each other, you can save time by running them in parallel. Make sure that `AUTO_FUND=false` inside `.env` file. There are some limitations with parallel testing, read more about them [here](https://hardhat.org/guides/parallel-tests.html)

To run tests in parallel:
```
yarn test --parallel
```
or
```
yarn hardhat test --parallel
```

# Interacting with Deployed Contracts

After deploying your contracts.
The deployment output will give you the contract addresses as they are deployed. You can then use these contract addresses in conjunction with Hardhat tasks to perform operations on each contract.
After deploying your contracts, the deployment output will give you the contract addresses as they are deployed. You can then use these contract addresses in conjunction with Hardhat tasks to perform operations on each contract.


## Chainlink Price Feeds
Expand Down Expand Up @@ -276,13 +297,19 @@ yarn hardhat read-data --contract insert-contract-address-here --network network


## VRF Get a random number
The VRFConsumer contract has two tasks, one to request a random number, and one to read the result of the random number request. This contract needs to be funded with link first:
The VRFConsumer contract has two tasks, one to request a random number, and one to read the result of the random number request. To start, go to [VRF Subscription Page](https://vrf.chain.link/rinkeby) and create the new subscription. Save your subscription ID and put it in `.env` file as `VRF_SUBSCRIPTION_ID`:

```bash
yarn hardhat fund-link --contract insert-contract-address-here --network network
VRF_SUBSCRIPTION_ID=subscription_id
```

Then, deploy your VRF V2 contract consumer to the network of your recent subscription using subscription id as constructor argument.

```bash
yarn hardhat deploy --network network
```

Once it's funded, you can perform a VRF request with the request-random-number task:
Finally, you need to go to your subscription page one more time and add the address of deployed contract as a new consumer. Once that's done, you can perform a VRF request with the request-random-number task:

```bash
yarn hardhat request-random-number --contract insert-contract-address-here --network network
Expand Down Expand Up @@ -312,7 +339,7 @@ yarn hardhat verify --network <NETWORK> <CONTRACT_ADDRESS> <CONSTRUCTOR_PARAMETE
example:

```
yarn hardhat verify --network kovan 0x9279791897f112a41FfDa267ff7DbBC46b96c296 "0x9326BFA02ADD2366b30bacB125260Af641031331"
yarn hardhat verify --network rinkeby 0x9279791897f112a41FfDa267ff7DbBC46b96c296 "0x9326BFA02ADD2366b30bacB125260Af641031331"
```

# View Contracts Size
Expand Down Expand Up @@ -347,6 +374,34 @@ yarn hardhat test

If you'd like to see the gas prices in USD or other currency, add a `COINMARKETCAP_API_KEY` from [Coinmarketcap](https://coinmarketcap.com/api/documentation/v1/).

# Code coverage

To see a measure in percent of the degree to which the smart contract source code is executed when a particular test suite is run, type
```
yarn coverage
```

# Fuzzing

We are going to use Echidna as a Fuzz testing tool. You need to have [Docker](https://www.docker.com/) installed with at least 8GB virtual memory allocated (To update this parameter go to _Settings->Resources->Advanced->Memory_).

To start Echidna instance run

```
yarn fuzzing
```

If you are using it for the first time, you will need to wait for Docker to download [eth-security-toolbox](https://hub.docker.com/r/trailofbits/eth-security-toolbox) image for us.

To start Fuzzing run
```
echidna-test /src/contracts/test/fuzzing/KeepersCounterEchidnaTest.sol --contract KeepersCounterEchidnaTest --config /src/contracts/test/fuzzing/config.yaml
```

To exit Echidna type
```bash
exit
```

# Contributing

Expand Down
43 changes: 29 additions & 14 deletions contracts/APIConsumer.sol
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/ChainlinkClient.sol";

/**
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
* @title The APIConsumer contract
* @notice An API Consumer contract that makes GET requests to obtain 24h trading volume of ETH in USD
*/
contract APIConsumer is ChainlinkClient {
using Chainlink for Chainlink.Request;

uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
address private immutable oracle;
bytes32 private immutable jobId;
uint256 private immutable fee;

event DataFullfilled(uint256 volume);

/**
* Network: Kovan
* Oracle: 0xc57B33452b4F7BB189bB5AfaE9cc4aBa1f7a4FD8 (Chainlink Devrel
* Node)
* Job ID: d5270d1c311941d0b08bead21fea7747
* @notice Executes once when a contract is created to initialize state variables
*
* @param _oracle - address of the specific Chainlink node that a contract makes an API call from
* @param _jobId - specific job for :_oracle: to run; each job is unique and returns different types of data
* @param _fee - node operator price per API call / data request
* @param _link - LINK token address on the corresponding network
*
* Network: Rinkeby
* Oracle: 0xc57b33452b4f7bb189bb5afae9cc4aba1f7a4fd8
* Job ID: 6b88e0402e5d415eb946e528b8e0c7ba
* Fee: 0.1 LINK
*/
constructor(
Expand All @@ -34,17 +41,16 @@ contract APIConsumer is ChainlinkClient {
} else {
setChainlinkToken(_link);
}
// oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
// jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
// fee = 0.1 * 10 ** 18; // 0.1 LINK
oracle = _oracle;
jobId = _jobId;
fee = _fee;
}

/**
* Create a Chainlink request to retrieve API response, find the target
* @notice Creates a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*
* @return requestId - id of the request
*/
function requestVolumeData() public returns (bytes32 requestId) {
Chainlink.Request memory request = buildChainlinkRequest(
Expand Down Expand Up @@ -77,7 +83,10 @@ contract APIConsumer is ChainlinkClient {
}

/**
* Receive the response in the form of uint256
* @notice Receives the response in the form of uint256
*
* @param _requestId - id of the request
* @param _volume - response; requested 24h trading volume of ETH in USD
*/
function fulfill(bytes32 _requestId, uint256 _volume)
public
Expand All @@ -86,4 +95,10 @@ contract APIConsumer is ChainlinkClient {
volume = _volume;
emit DataFullfilled(volume);
}

/**
* @notice Witdraws LINK from the contract
* @dev Implement a withdraw function to avoid locking your LINK in the contract
*/
function withdrawLink() external {}
}
16 changes: 16 additions & 0 deletions contracts/KeepersCounter.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol";

/**
* @title The Counter contract
* @notice A keeper-compatible contract that increments counter variable at fixed time intervals
*/
contract KeepersCounter is KeeperCompatibleInterface {
/**
* Public counter variable
Expand All @@ -14,13 +19,21 @@ contract KeepersCounter is KeeperCompatibleInterface {
uint256 public immutable interval;
uint256 public lastTimeStamp;

/**
* @notice Executes once when a contract is created to initialize state variables
*
* @param updateInterval - Period of time between two counter increments expressed as UNIX timestamp value
*/
constructor(uint256 updateInterval) {
interval = updateInterval;
lastTimeStamp = block.timestamp;

counter = 0;
}

/**
* @notice Checks if the contract requires work to be done
*/
function checkUpkeep(
bytes memory /* checkData */
)
Expand All @@ -35,6 +48,9 @@ contract KeepersCounter is KeeperCompatibleInterface {
// We don't use the checkData in this example. The checkData is defined when the Upkeep was registered.
}

/**
* @notice Performs the work on the contract, if instructed by :checkUpkeep():
*/
function performUpkeep(
bytes calldata /* performData */
) external override {
Expand Down
Loading

0 comments on commit 5492cde

Please sign in to comment.