Aave Ape 🦍 with 🏗️ scaffold-eth 😱

Adam Fuller
8 min readFeb 22, 2021

--

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:

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 on getAvailableBorrowInAsset, 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 the apeAsset 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 and UniswapRouter 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 apeAssetvia Uniswap V2 for the borrowAsset 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:

  1. Verify the LendingPool is the caller, and that the Ape initiated the flashloan (to protect against attacks!)
  2. Calculate the amount owed to the LendingPool
  3. Decode the params — we can use abi.encode() and abi.decode() to pass parameters from our initial unwindApe function to executeOperation — this gives us the information we then need in order to…
  4. 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:

Slow mainnet fork response time!

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 out deploy.js for the details.

  1. Send yourself some ETH with the faucet
  2. Use the Uniswap <Swap/> to swap it to the asset you want to deposit
  3. 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.

Your local ape!

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!

Will you be king of the jungle?

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:

Better luck next time

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-appto include a REACT_APP_PROVIDER pointing at the Kovan Infura endpoint, and add REACT_APP_NETWORK=kovan

If you restart the app (CTRL-C then yarn start), you should see a wonderful purple banner.

Vibrant.

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!

--

--