Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Vault Yield Implementation with Aave #6

Open
wants to merge 15 commits into
base: feat/aave_vault
Choose a base branch
from

Conversation

banasa44
Copy link

@banasa44 banasa44 commented Dec 20, 2024

Feat: Vault Yield Implementation

Summary

This work was done for OpenFort during a testing week, therefor this PR will include an extensive report of all the work done, and all the decision-making will be described.

In this pull request, you will find an enhanced functionality for OpenFort Vaults, particularly, the support for Aave yield protocols to generate interest for the users within the vaults.

The work has been done trying to adapt to existing functionalities as much as possible. However other implementations will be considered in this report.

How to Install and Test

Follow these steps to install and test the changes in this branch:

Prerequisites

Install Foundry (if not already installed):

curl -L https://foundry.paradigm.xyz | bash
foundryup

Installation and build

  1. Clone the repository
git clone https://github.com/banasa44/fork-openfort-chain-abstraction.git
cd openfort-chain-abstraction
git checkout feat/carles-vault-yield
  1. Install dependencies and remappings:
forge install
  1. Build the contracts:
forge build

Testing

For this test, you will need to fork a testnet to recreate the interaction with Aave protocol Smart Contracts deployed on an Aave-supported testnet.
Sepolia may be a nice choice.

Run all tests with the Testnet fork enabled:

forge test --fork-url <your-ethereum-rpc-url> -vv

To run individual tests, use the --match-contract or --match-test flags:

forge test --fork-url <your-ethereum-rpc-url> --match-contract DeployAndTestAaveVaults

Notes for testing

I provided you an example.env file with the necessary environment variables to make the test work.
I left the Aave Sepolia addresses so you don't need to change them, however, if you prefer to test on another testnet or use another token you can check on the Aave address-book repository:
https://github.com/bgd-labs/aave-address-book/tree/main/src

For the test to work the address doing the deposit (owner), has to have token balance.

Notes for reviewers

Files Changed/Added

  • Modified deployChainAbstractionSetup, added functionality to deploy and register AaveVaults.
  • Added CheckAaveTokenStatus auxiliary script to check if some token is supported by Aave protocol.
  • Added aaveVault vault supplying tokens to Aave protocol.
  • Added AaveVault.t.sol E2E test for AaveVault.

deployChainAbstractionSetup

Added the deployment and register in the VaultManager of AaveVaults. To do so, an auxiliary script was used, to check if the the underlying token is supported by the Aave Protocol. Additionally, I added a function to retrieve the aToken associated with the underlying token.

AaveVault

The core piece of the PR, this AaveVault inherits from BaseVault and overrides/adds:

  1. Custom initialize function
  2. _afterDeposit
  3. _previewDeposit
  4. _afterWithdraw
  5. _totalAssets

1. Custom initialize Function

  • Since the contract inherits from an upgradeable contract, the constructor is disabled. Instead, a custom initialize function is implemented to handle additional parameters during smart contract deployment.
  • This initializer:
    • Sets the associated aToken for the underlying token (automated via the CheckAaveTokenStatus script).
    • Configures the Aave Pool contract. The Aave Pool address must be manually added to the .env file for each specific chain.
    • Calls super.initialize(_vaultManager, _underlyingToken); to leverage the parent contract's initialization logic.

2. _afterDeposit

  • This function supplies tokens to the Aave protocol, enabling the generation of interest for users.
  • Following Ethereum best practices:
    • State is updated before any transfers or external calls to minimize the risk of reentrancy attacks.
    • Although using _beforeDeposit could arguably suffice in this specific case, _afterDeposit was chosen for clarity and consistency in handling post-deposit logic.

3. _previewDeposit

  • Overriding _afterDeposit necessitated changes to _previewDeposit:
    • This function calculates newShares using totalAssets.
    • Since totalAssets fetches the aToken.balanceOf(address(this)) (which updates only after supply), subtracting amount from virtualPriorTokenBalance became incorrect.
    • The logic was adjusted to account for this discrepancy and ensure accurate share calculations.

4. _afterWithdraw

  • This function interacts with the Aave Pool to burn aTokens and retrieve the underlying tokens (plus proportional interest) for the user.
  • Low-level Ethereum calls are used to:
    • Maintain finer control when interacting with external contracts like Aave.
    • Clearly distinguish between transaction reverts caused by our contract logic versus external contract issues.

5. _totalAssets

  • This function is overridden to fetch the balance of the aToken associated with the Vault instead of the underlying token.
  • Ensures share calculations are done for the aToken.

Considerations and Decision Making

IYieldVault vs IVault

To keep things simple and fit with the existing protocol, I decided not to use the IYieldVault interface. The flexibility of the before and after hooks was enough to enhance the deposit and withdrawal functionalities.
The overridden hooks only added the necessary functionality to work with Aave while keeping the input arguments the same. This was enough for this specific use case.

That said, Aave has specific methods for L2 pools:
Aave L2 Pool Documentation
These methods require function arguments to be passed as byte-encoded inputs. This reduces transaction costs on L2s. If we were to use these L2-specific methods, we would need to add specific deposit and withdrawal functions in the VaultManager and AaveL2Vault. An L2Encoder could be used by OpenFort's backend to properly call these functions on the VaultManager. In this case, using the IYieldVault interface would make more sense.

Additionally, if we plan to add features like calculating the interest earned by each user or similar functions, using IYieldVault would become more valuable. It would provide a clearer and more structured way to manage these advanced features.

Having a variety of Vaults

Managing different types of vaults can create organizational challenges. Currently, all vaults are registered like this:

    /// @notice Mapping: Underlying => Vault[] to store the list of Vaults for the underlying token.
    mapping(IERC20 => IVault[]) public underlyingToVaultList;

I believe this could be improved by updating the mapping to store an array of structs for each ERC20 token. Or adding an additional mapping form VaultAddress to VaultType (maybe this is a cheaper and easier approach).

Why Chose Aave for OpenFort Vaults

I've chosen Aave for the yielding vaults because it offers simplicity, safety, and instant access to funds. It works with over 30 assets, including key stablecoins like USDC, DAI, and USDT, and supports most Layer 2s such as Polygon, Arbitrum, Optimism, and Avalanche.

Compared to alternatives like Compound, which supports fewer chains, or MakerDAO, which is limited to DAI, Aave stands out for its broader coverage and multi-chain flexibility.

Summary Table: Aave Alternatives

Protocol Assets Supported Chains Yield Source Risk Profile Liquidity
Aave ~30 assets (USDC, DAI, USDT, WBTC, ETH, etc.) Ethereum, Polygon, Arbitrum, Optimism, Avalanche, and more Borrower interest Low Instant
Compound ~10 assets (USDC, DAI, USDT, ETH) Ethereum, Polygon Borrower interest Low Instant
MakerDAO (DSR) DAI only Ethereum DAI Savings Rate Very Low Instant
Morpho ~10 assets (via Aave/Compound) Same as Aave and Compound Optimized borrower interest Low (inherits Aave/Compound) Instant (fallback)
Spark Protocol DAI, USDC, ETH Ethereum Borrower interest + DSR Low Instant

ToDo

I would have loved to make tests more robust.
The current test just checks E2E, if a user deposits, and time passes, the user will get back more money.
I added another test to see the distribution of shares.
However, more robust and precise tests ensuring precise workflow of deposits, yielding and withdrawals should be done (properly calculating the yield generated for each user (using shares), and the amount withdrawn any time even if the user doesn't withdraw all the amount he processes in the vault).
Sometimes there's a 1wei unit discrepancy while running the tests, it doesn't happen every time.

Sorry for writing such a loooong book 🙈🙈
Have a nice holiday!!!

@banasa44 banasa44 changed the title Feat/carles vault yield Feat: Vault Yield Implementation with Aave Dec 20, 2024
@banasa44 banasa44 marked this pull request as ready for review December 20, 2024 16:53
@Haypierre Haypierre changed the base branch from main to feat/aave_vault December 28, 2024 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant