@chaos-labs/chainlink-hardhat-plugin
v0.0.6
Published
Hardhat plugin for configuring Chainlink price feed return values when forking mainnet.
Downloads
7
Readme
TLDR
- This repository hosts a hardhat plugin for configuring Chainlink Oracle prices in a local hardhat mainnet fork testing environment.
- The plugin fetches all Mainnet Chainlink Oracle addresses when invoked and makes them accessible for price modification.
- The only thing the user should supply is the token pair ticker or a price config. Let's explore the different options.
Set Prices Explicity with the following init config
await chainlinkConfig.initChainlinkPriceFeedConfig("ETH/USD", "Mainnet");
await chainlinkConfig.initChainlinkPriceFeedConfig("AAVE/USD", "Mainnet");
await chainlinkConfig.initChainlinkPriceFeedConfig("ETH/BTC", "Mainnet");
Set Prices trends with the following config
https://user-images.githubusercontent.com/8246234/151700339-b4ca4706-697d-40b9-be7d-f603cd7be6c5.mov
const config = {
priceDelta: 10,
priceFunction: "volatile",
initialPrice: 0,
};
await chainlinkConfig.initChainlinkPriceFeedConfig(ticker, "Mainnet", config);
Grab a config via the Chainlink portal!
Why is Mocking Oracle values useful in testing?
Oracle values trigger internal state changes in web3 applications. Currently, when forking mainnent, oracle returns values are constant. This is because the Chainlink protocol only writes updated values to mainnet or public testnets. We want the ability to mock return values easily, so we can test how our contracts / applications react to different types of external data, hence this tool. Below, we provide some specific use cases for mocking oracle return values.
For a full deep dive to the project architecture please visit the Chaos Labs blog.
Use Cases
DeFi protocols and applications are at high risk due to volatile market conditions and a myriad of security vectors. Mocking Chainlink Oracle return values in a controlled, siloed testing environment allows us to address 2 common vectors.
Volatile Market Conditions
Volatility is a DeFi constant and is something that all protocols and applications should test for thoroughly. Internal application and protocol state is often a direct result of Oracle returns values. To further illustrate this let's use an example.
Imagine a lending protocol (Maker, AAVE, Benqi, Spectral.finance, etc..) that accepts Ethereum as collateral against stablecoin loans. What happens on a day like Black Thursday, when Ethereum prices cascade negatively to the tune of ~70% in a 48 hour time frame? Well, a lot of things happen 🤦.
One critical aspect of responding to market volatiltiy is protocol keepers triggering liquidations and thus ensuring protocol solvency.
With the ability to control Oracle return values, simulating such scenarios in your local development environment is possible.
Oracle Manipulation
Oracle manipulation is an additional attack vector. With this method, malicious actors research data sources that various oracle consume as sources of truth. When actors possess the ability to manipulate the underlying data source they trigger downstream effects, manifesting in altered Oracle return values. As a result of manipulated data, actors and contracts can trigger various unwanted behaviours such as modified permissions, transaction execution, emergency pausing / shutdown and more.
With the ability to manipulate Chainlink Oracle return values, simulating such scenarios in your local development environment is possible.
Prerequisites
- We're going to need an instance of a
hardhat
mainnet fork running in a separate terminal window. For a quickstart, follow the installation steps in our Chaos Labs demo repo. - We assume you have npm installed, if not go to https://nodejs.org/en/download/ and follow the instructions.
Installation
Step 1
npm install ChaosLabsInc/chainlink-hardhat-plugin
Step 2
Import the plugin in your hardhat.config.js
:
require("@chaos-labs/chainlink-hardhat-plugin");
Or if you are using TypeScript, in your hardhat.config.ts
:
import "@chaos-labs/chainlink-hardhat-plugin";
Usage - Set Prices Explicity
In this example we will explicitly set the return value of a Chainlink price feed.
const chainlinkConfig = new hre.ChainlinkPriceFeedConfig(this.hre);
// Here we set the plugin to work with ETH/USD pair on Mainnet:
await chainlinkConfig.initChainlinkPriceFeedConfig("ETH/USD", "Mainnet");
// Now we query the price, this is to allow better decision making in setting up the mocked price:
const prevPrice = await chainlinkConfig.getPrice("ETH/USD"); // original price at time of mainnet fork
// We mock the price of the return value fo the token to 555 instead of the original price:
await chainlinkConfig.setPrice("ETH/USD", 555);
const nextPrice = await chainlinkConfig.getPrice("ETH/USD"); // 555
Usage - Set Prices via Configuration
Grab a config via the Chainlink portal!
Some test cases require testing trends in pricing. For example, we may want to test examples in which TokenA is decreasing in a monotonic fashion. For this use case we can grab a Chainlink Configuration object and pass it to the initChainlinkPriceFeedConfig
initializer.
const chainlinkConfig = new ChainlinkPriceFeedConfig(this.hre);
const config = {
priceDelta: 10,
priceFunction: "volatile",
initialPrice: 0,
};
await chainlinkConfig.initChainlinkPriceFeedConfig(ticker, "Mainnet", config);
let price = await chainlinkConfig.getPrice(ticker); // 0
await chainlinkConfig.nextPrice(ticker);
price = await chainlinkConfig.getPrice(ticker); // -10
await chainlinkConfig.nextPrice(ticker);
price = await chainlinkConfig.getPrice(ticker); // 20
Copy Config To Clipboard
After selecting a math function to describe the direction of oracle change, please click on the Export Button. This will pop up a dialog. Click the "Copy" button to copy the config to your clipboard or download it as a json file with the "Download" button.
Paste Config into Hardhat Script
Now that our config has been copied to our keyboard we can copy it into our hardhat
script. You can set the paste as the value of the config
variable above or paste it directly into the init method:
await chainlinkConfig.initChainlinkPriceFeedConfig(ticker, "Mainnet", {
priceDelta: 10,
priceFunction: "volatile",
initialPrice: 0,
});
as the third parameter - replacing the config
variable with the object literal.
How Do Prices Behave As We Query Next Price?
The following image shows the price behavior between subsequent calls to nextPrice(ticker)
Run Tests
Restart the mainnet fork for a fresh state.
Run tests with the following command:
npm run build && npm run test