Overview
As a passionate DeFi contributor, Chaos Labs is always excited to partner with top DeFi protocols. Uniswap is the leading decentralized exchange that is pushing AMM innovation forward across chains and is an unquestioned leader in the space. As part of our commitment and mission to drive DeFi adoption, Chaos Labs builds and publishes open-source tools for the larger developer ecosystem. Today, we are releasing the Uniswap V3 Oracle Hardhat plugin. This project was proudly developed with grants from the Uniswap Grants Program (UGP).
This repository hosts a hardhat plugin for configuring Uniswap V3 Oracle prices in a local hardhat mainnet fork testing environment. These posts provide sufficient literature on Uniswap v3 TWAP Architecture and our previously released CLI tool:
Why is Mocking Oracle values useful in testing?
Oracle return values trigger internal state changes in web3 applications, but TWAP oracles are static by default when forking mainnet as no trades are executed in an isolated forked environment. If your application consumes price data and initiates control flows based on these values, being able to test a range of prices is critical.
Unfortunately, manipulating prices to bring an application to a specific state requires many swaps in a pool. This is because TWAP oracle prices are determined by the pair ratio of liquidity in the pools at the time that the observations recorded. When we have a myriad of liquidity pools, configuring prices can become a tedious process that involves a lot of custom scripting and hacks.
Chaos Labs aims to streamline developer productivity while also making it easier to test applications. This new hardhat plugin gives developers the ability to mock return values easily. Now we can test how our contracts / applications react to different types of external data.
Below, we provide some specific use cases for mocking oracle return values.
Use Cases
DeFi protocols and applications are at high risk due to volatile market conditions and a myriad of security vectors. Mocking Uniswap V3 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. Because of this, controlling oracle return values in development is extremely powerful. To further illustrate this let's use an example.
Imagine a lending protocol (Maker, AAVE, Benqi, 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 volatility 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 behaviors such as modified permissions, transaction execution, emergency pausing / shutdown and more.
With the ability to manipulate Uniswap V3 Oracle return values, simulating such scenarios in your local development environment is possible.
Usage
For a full installation guide, please follow the steps in the README.
Once everything is installed the plugin interface is quite straight forward. Here's an example hardhat.config.js
task that utilizes the plugin to configure pricing:
const { task } = require("hardhat/config");
require("@chaos-labs/uniswap-v3-oracle-hardhat-plugin");
task("demo1", async () => {
// Create new pool USDT\LUNA
const usdtTokenAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const lunaTokenAddress = "0xd2877702675e6cEb975b4A1dFf9fb7BAF4C91ea9";
const newPoolAdress = await uniswapV3OracleConfig.CreatePool(usdtTokenAddress, lunaTokenAddress);
const currentObservationTimestamp = 100;
await uniswapV3OracleConfig.OverrideCurrentObservationTimestamp(newPoolAdress, currentObservationTimestamp);
const twapInterval = 5;
const mockedPrice = 20;
// Show price
const originalPrices = await uniswapV3OracleConfig.ShowPrices(newPoolAdress, twapInterval);
console.log("original prices - " + originalPrices);
// Mock price
await uniswapV3OracleConfig.MockPoolPrice(newPoolAdress, twapInterval, mockedPrice);
// Show price after mock
const updatedPrices = await uniswapV3OracleConfig.ShowPrices(newPoolAdress, twapInterval);
console.log("updated prices - " + updatedPrices);
});
task("demo2", async () => {
const poolAdress = "0x4e68ccd3e89f51c3074ca5072bbac773960dfa36";
const twapInterval = 5;
const mockedPrice = 20;
// Show price
const originalPrices = await uniswapV3OracleConfig.ShowPrices(poolAdress, twapInterval);
console.log("original prices - " + originalPrices);
// Mock price
await uniswapV3OracleConfig.MockPoolPrice(poolAdress, twapInterval, mockedPrice);
// Show price after mock
const updatedPrices = await uniswapV3OracleConfig.ShowPrices(poolAdress, twapInterval);
console.log("updated prices - " + updatedPrices);
});
What's next? ⏭️
Let us know what you think or if you think we missed anything, but we're building a LOT more for the Uniswap community! Be on the lookout as we release new tools and products for the ecosystem 😎
About the Uniswap Grant Program
If you want to learn more about the Uniswap Grants Program, check out their blog.