Testing Price Feeds On Mainnet With Hardhat

When writing smart contracts that interact with price feed oracles or perform things like flash loans and flash swaps it can be useful to test your code using a mainnet fork. It is great as a final step in integration testing when you want to make sure your contract is calling all of the correct mainnet addresses, and make sure all of the testnet addresses are swapped out correctly. It is also convenient if you don't want to hunt down all the various testnet contract addresses for the feeds and AMMs you want to use.

Running tests on mainnet forks is easy using HardHat, a framework for developing and testing Solidity smart contracts. You can follow this guide to get a standard HardHat set up working.

To test on a fork of mainnet simply change your hardhat.config.ts to point at a mainnet RPC API endpoint using the forking directive:

module.exports = {
   solidity: "0.8.7",
   defaultNetwork: "hardhat", 
   networks: {
      hardhat: {
         forking:{
            url: MAIN_API_URL, 
            // blockNumber: 11095000
         } 
      }
   }
}

Including a block number will let your tests cache and execute faster.

We will test executing a chainlink price feed call for ETH/USD on our fork. here is the code for the consumer smart contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

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

contract PriceConsumer {

    AggregatorV3Interface internal priceFeed;

    /**
     * Network: Main
     * Aggregator: ETH/USD
     * Address: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419
     */
    constructor() public {
        priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
    }

    /**
     * Returns the latest price
     */
    function getLatestPrice() public view returns (int) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        return price;
    }
}

And here is the hardhat test code to deploy,call, and print the results of the oracle query on the mainnet fork:

import { ethers } from "hardhat";
import { beforeEach } from "mocha";
import { Contract,BigNumber } from "ethers";
import { SignerWithAddress, } from "@nomiclabs/hardhat-ethers/signers";


describe("Oracle Consumer", () => {
  let priceCheck: Contract;
  let owner: SignerWithAddress;
  let address1: SignerWithAddress;
  let address2: SignerWithAddress;

  beforeEach(async () => {
    [owner, address1,address2] = await ethers.getSigners();
    const ChainLinkFactory = await ethers.getContractFactory(
      "PriceConsumer"
    );
    priceCheck = await ChainLinkFactory.deploy() 
    
  });

  it('Check last ETH price', async () =>{
    console.log( ((await priceCheck.getLatestPrice())/(10**8)).toString())
  })
});

I will be posting some flash loan / flash swap code that utilizes this approach for real world testing!