Aave Integration
Mainnet fork showing how Oval can be used in Aave.
Last updated
Mainnet fork showing how Oval can be used in Aave.
Last updated
A number of useful unit tests have been created to show how Oval can integrate into Aave v2, Aave v3, and Compound v2. These tests illustrate the minimal required changes needed to these protocols by simply swapping from Chainlink to Oval within the protocol's oracle flow.
This technical example walks through a mainnet fork test for Aave v3 to show how Oval could be integrated without disrupting Aave operation. It uses the version of Oval described in the docs page.
To demonstrate the integration, we will run a mainnet fork test for Aave v3. We have written a script that works by forking Ethereum mainnet before a ETH/USD Chainlink price feed update that catalyzed a liquidation on mainnet. You can find this liquidation in question here and you can use a useful tool like Eigenpi to see the actions of the liquidation here.
To run the script locally you need two things first:
Then, run the following commands to clone the quickstart and install the dependencies.
Next, we can run the fork tests. Before doing so we need to set an environment variable so that forge can access historic Ethereum state through the fork. To do this run the following. Note that if you'd set this value within your .env file from one of the other Oval tutorials you can skip this step.
Next, we execute the tests with the following command:
The mainnet fork tests illustrate a number of key Oval + Aave function touch points. each are discussed below and walkthrough the key parts of the code.
The first test shows that the sample liquidation in question can be executed within the forked environment. The code below shows the liquidation action:
Breaking down this code we can see the following actions done:
seedLiquidator
funds the liquidator wallet.
vm.rollFork
moves the fork to right before the liquidation action.
lendingPool.liquidationCall
is done right after pranking the liquidator. This is the call to the Aave market that will liquidate the position.
The following assertTrue
calls show:
The liquidator spent USDC to repay the liquidated debt
The liquidator received some amount of margin collateral, WETH, for the liquidation
Lastly, the position has a positive health factor after the liquidation.
The second test shows that Chainlink can be replaced with Oval and the liquidation flow continues to work as expected. Importantly, this test showcases that until the unlockLatestValue
function is called on Oval the integration will receive a stale value. This is important as it shows that only the winner of the auction (the account who calls right after the unlockLatestValue
call) can access the new price. The code for this test is as follows:
Breaking down this code we can see the following actions done:
seedLiquidator
to give them funds to run the liquidation.
createOvalAndUnlock
deploys the Oval instance and update it to contain the latest value from chainlink. This Oval instance is pointed to the associated Chainlink contract Aave is using for this market.
setOvalAsAaveSource
changes the Aave configuration to use the Oval instance as its oracle for this price feed.
updateChainlinkToLatestValue
rolls forward the fork to the point past where Chainlink is updated. Note, though, that Oval will still return an old value at this point as no auction has been run and lockWindow
has not yet passed.
Next, we enter into the liquidation flow. First the test tries to run a liquidation without having run an Oval auction. This is shown by checking that the liquidationCall
reverts with an error code of 45, indicating that the position cant be liquidated due to having a health above 1. This means that the Chainlink update has not yet been applied and the position is not yet liquidatable.
The subsequent assertions show that Chainlink has updated but Oval is still returning old values.
Next, the Oval auction is run. This is done form the permissionedUnlocker
s account by calling unlockLatestValue
on the Oval contract. This action would occur within an auction normally.
The liquidation is now attempted again. This time the liquidation goes through as the Oval contract releases the latest value.
Breaking down this code we can see the following actions done:
Similar to the previous code we start by seeding the liquidator, creating an oval instance, setting it as the source and updating the Chainlink value. We are now ready to show the core logic within this test.
Next, we attempt a liquidation. Like before, it fails as the Oval price is returning a stale value.
After this, we show that if you move just over the Oval lockWindow
by using the vm.warp
function the Oval contract will now gracefully unlock, releasing the latest price.
This is shown by first showing that the latest value has been released and second by running the liquidation with liquidationCall
and showing that the position is now healthy (liquidated).
In the event that the trusted unlocker or MEV-Share breaks for any reason Oval is designed to fail gracefully. This is discussed in . This test shows that in this even the impact on the integration is a small delay in price updates being released to the associated protocol.