Aave Ape 🦍 with 🏗️ scaffold-eth 😱
This is the second post about building on Aave, see the first here (in which we develop a minimal React component).
Building ain’t easy! There comes a time when an enterprising Ethereum builder needs something that existing smart contracts can’t offer. In this article we walk through the Aave Ape 🦍 , a simple contract that lets you create and unwind leveraged positions on Aave 👻 by integrating with Uniswap 🦄, plus some testing courtesy of Hardhat👷, and of course a little 🏗️ scaffold-eth frontend.
Let’s get to it — check the code here!
🚨 This is experimental, use at your own risk! 🚨
The base: Aave + Uniswap 🧱
The Aave Ape combines two Defi protocols, Aave (for lending) and Uniswap (for swapping). Interacting with them from a smart contract requires knowing where they are (their deployed addresses) and what they look like (their interface) — see solidity-by-example. We created an AaveUniswapBase.sol
to import all the relevant interfaces, and set the addresses that we need.
Interacting with Aave starts with the Lending Pool Addresses Provider — this is the source of truth for Aave V2’s latest contract addresses:
- LendingPool: where the depositing & borrowing happens 🏦
- ProtocolDataProvider: provides easy-to-access information about reserves & users 📊
- PriceOracle: provides secure price feeds 💱
Interacting with Uniswap V2 all happens through the Uniswap Router02.
We can create a constructor
that lets us set the two addresses we need (the AddressesProvider and the Router). Once we then have the addresses and the interfaces, we can create helper functions to instantiate contracts so that we can interact with them, and fetch Aave reserve data from the ProtocolDataProvider
. We’re ready to Ape!
The Aave Ape
AaveApe.sol
inherits AaveUniswapBase.sol
, so it needs to pass the same two addresses as constructor arguments, and is ready to interact with both protocols:
contract AaveApe is AaveUniswapBase {constructor(address addressesProviderAddress, address uniswapRouterAddress) AaveUniswapBase(addressesProviderAddress, uniswapRouterAddress) public {}
...
There are then two key actions of interest — ape
and unwindApe
.
ape 🙈
The ape function assumes that you have collateral deposited on Aave that you are able to borrow against (not all assets can act as collateral!) Assuming you do, and you have an apeAsset
you want to go long and a borrowAsset
you want to go short, this function lets a user carry out the following steps in one transaction, which they would otherwise have to do one at a time:
- Calculate the maximum amount the user is able to borrow in the
borrowAsset
, based on their collateral (this relies ongetAvailableBorrowInAsset
, which make the corresponding calculation) - Borrow that amount of
borrowAsset
from Aave V2 on behalf of the user
This requires the user to have delegated credit to the Aave Ape contract, so that it can borrow from Aave on the user’s behalf — see more about Credit Delegation here.
- Approve the
borrowAsset
for trading on Uniswap - Swap the
borrowAsset
for the maximum available amount of theapeAsset
via Uniswap V2 - Deposit the
apeAsset
back into Aave on behalf of the user
The user now has long exposure to the apeAsset
, while they are short the borrowAsset
, so they run the risk of liquidation if the relative value of the apeAsset
drops.
Some gas costs could perhaps be mitigated by pre-approving the
LendingPool
andUniswapRouter
for all the Aave assets
The raw programmable nature of Defi is demonstrated by the additional functionsuperApe
, which effectively lets a user re-run the ape
function several times for the same asset pair, increasing their leverage:
// Call "ape" for the number of levers specified
for (uint i = 0; i < levers; i++) {
ape(apeAsset, borrowAsset, interestRateMode);
}
Quite a reckless for loop
to be sure!
unwindApe
⏪
Check the code! This function effectively reverses the ape
, in order to close out an Ape position. The implementation is a little more complicated, as a simple reversal might not be possible (particularly if a position has been ape
-d more than once, or if the trade has gone the wrong way!) We therefore leverage Aave’s flashloan functionality which, given an apeAsset
and a borrowAsset
lets a user:
- Borrow the amount needed to repay their
borrowAsset
debt via a flashloan from Aave V2
🧮 To repay the full debt, it is best to calculate the amount as part of the function, rather than passing an amount as a parameter, due to the constant accrual of interest
- Repay their original debt
- Withdraw the maximum amount of the
apeAsset
collateral available to the borrower
🙏 The Aave Ape needs to have an allowance to transfer the borrower’s aTokens or this step will fail!
- Swap that
apeAsset
via Uniswap V2 for theborrowAsset
amount owed to Aave for the flashloan (the borrow amount, plus the premium)
💵 There is a key requirement here, which is that we have enough of the
apeAsset
deposited as collateral to repay our debt (if we don’t the transaction will revert)
- Approve the lendingPool to reclaim the requisite amount of
borrowAsset
- Deposit any leftover collateral back into Aave
All in one transaction! This isn’t all happening in one place however— the unwindApe
function itself ends with a call to getLendingPool().flashLoan()
. Any call to flashLoan()
transfers the requested assets to the specified address, then the LendingPool will make a subsequent call to the specified receiverAddress
, in our case calling executeOperation()
on the Aave Ape with predefined parameters. The Ape’s executeOperation()
does a couple of things:
- Verify the LendingPool is the caller, and that the Ape initiated the flashloan (to protect against attacks!)
- Calculate the amount owed to the LendingPool
- Decode the params — we can use
abi.encode()
andabi.decode()
to pass parameters from our initialunwindApe
function toexecuteOperation
— this gives us the information we then need in order to… - Call the Ape’s
closePosition()
function with the relevant parameters, which then does the heavy lifting described above.
And we’re out!
Hardhat testing
With 🏗️ scaffold-eth we can obviously go straight to the front-end, but while developing the Ape we also used 👷 Hardhat & Waffle to quickly check that things were working as intended, in packages/hardhat/test/test.js
.
Tests run on a local hardhat network, so we needed to make sure that our hardhat
network in hardhat.config.js
included forking:
networks: {
hardhat: {
forking: {
url: "https://eth-mainnet.alchemyapi.io/v2/<yourApiKey>"
}
},
}
We also extended the mocha
timeout in our config, as mainnet forks response can sometimes take longer than the 20 second default.
mocha: {
timeout: 80000
}
Running requires a simpleyarn test
:
Testing demonstrates the actions required for the Ape to work — depositing on Aave, delegating credit, approving aTokens, and is therefore good preparation ahead of building a user interface (as well as being good practice!)
Simple frontend
It wouldn’t be a 🏗️ scaffold-eth walk-through without a simple frontend to play with, so fire up yarn fork
yarn deploy
and yarn start
and check it out!
yarn deploy
deploys the Aave Ape to our local mainnet fork, passing the constructor the mainnet addresses for the Lending Pool Addresses Provider and Uniswap V2 Router— check outdeploy.js
for the details.
- Send yourself some ETH with the faucet
- Use the Uniswap
<Swap/>
to swap it to the asset you want to deposit - Deposit to Aave using
<Lend/>
And we’re ready to go!
The top of the Ape shows your overall position on Aave (recycling the <AccountSummary/>
component), and the settings widget ⚙️ slides out to show more account meta-data. Then you get into the Aave Ape stuff — selecting the apeAsset
to go long, and the borrowAsset
to go short.
Once you have selected the asset pair you are interested in, it only remains to Delegate credit to the Aave Ape so they can borrow on your behalf, then you are ready to Go Ape!
Unwinding is similarly a two-step process — give the Aave Ape an allowance to move your aTokens, then you can unwind your position.
Note that you may not be able to Unwind if you went long an asset you hadn’t already deposited as collateral and it went against you, as you won’t have enough collateral to repay your flashloan!
The Ape
events are shown in a table below:
So pretty simple stuff, but the Ape is now an end-to-end dApp!
There is lots that could be added and improved here — a better summary of your real time performance, better logic for selecting assets, including estimations of gas prices 😱 — that’s what forking is for!
Beyond localhost
While the Ape could technically go to mainnet, the price of gas⛽ and his uncouth (unaudited) nature mean that a spell on a testnet is in order. This branch has been set up to be relatively easy to deploy the Ape to Kovan, Aave’s testnet of choice…
Quick gotcha: We need to change both the Lending Pool Address Provider, and the Uniswap Router in the deployment configuration, as Aave on Kovan works with a purpose-deployed
MockUniswapV2Router02
, rather than Uniswap’s own Kovan deployment address
1. Run yarn generate
👼 and yarn account
, and send your new deployment account some Kovan ETH
2. Deploy with the process.env.HARDHAT_NETWORK
set to Kovan and the Ape will be on his way! (Deployed here!)
yarn workspace @scaffold-eth/hardhat hardhat --network kovan run scripts/deploy.js
yarn workspace @scaffold-eth/hardhat hardhat run scripts/publish.js
The app is also ready to go to Kovan, as described previously:
Update the .env file in
packages/react-app
to include aREACT_APP_PROVIDER
pointing at the Kovan Infura endpoint, and addREACT_APP_NETWORK=kovan
If you restart the app (CTRL-C then yarn start
), you should see a wonderful purple banner.
A quick yarn build
and yarn surge
later, and you can have an app for all to see (example here!)
Finishing up
In this post we ran through the process of creating & testing a contract leveraging Aave and Uniswap (pun intended), plus a minimal UI & the route to a testnet, all in 🏗️ scaffold-eth.
The functionality is pretty simple, and it exists elsewhere, but the goal was to illustrate how anyone can build on these Defi legos 🧱 to create interesting new experiences 🎉.
If you have feedback, questions, or if you want to collaborate on future investigatory building, please do get in touch 🐦!
And if you fancy forking & iterating, there is no shortage of possibilities…
- Extend the ape function so that it executes the initial deposit as well as the borrow & swap
- Let users specify the amount they want to ape or unwind
- Update the Ape to handle multiple positions (i.e. not just single pairs)
- Rebuild with Uniswap Flash Swaps instead of Flashloans
- Integrate with other venues to get better pricing (1Inch, Sushiswap)
- Estimate gas usage & better illustrate transaction costs
- Optimise for lesser gas usage
- Provide a better illustration of your P&L or Account Health
Or something completely different… Happy building!
References
Hat tip as always to Austin Griffith and the rest of the Buidl Guidl!