Skip to content

bgd-labs/aave-debt-swap

Repository files navigation

BGD labs <> Aave Debt Swap Adapter

This repository contains the ParaSwapDebtSwapAdapter, which aims to allow users to arbitrage borrow APY and exit illiquid debt positions. Therefore, this contract is able to swap one debt position to another debt position - either partially or completely.

You could for example swap your 1000 BUSD debt to max(1010 USDC) debt. In order to perform this task, swapDebt:

  1. Creates a flashLoan with variable debt mode with the target debt(1010 USDC) on behalf of the user
    • On aave v2 you need to approve the debtSwapAdapter for credit delegation
    • On aave v3 you can also pass a credit delegation permit
  2. It then swaps the flashed assets to the underlying of the current debt(1000 BUSD) via exact out swap (meaning it will receive 1000 BUSD, but might only need 1000.1 USDC for the swap)
  3. Repays the current debt (1000 BUSD)
  4. Uses potential (9.9 USDC) to repay parts of the newly created target debt

The user has now payed off his 1000 BUSD debt position, and created a new 1000.1 USDC debt position.

In situations where a user's real loan-to-value (LTV) is higher than their maximum LTV but lower than their liquidation threshold (LT), extra collateral is needed to "wrap" around the flashloan-and-swap outlined above. The flow would then look like this:

  1. Create a standard, repayable flashloan with the specified extra collateral asset and amount
  2. Supply the flashed collateral on behalf of the user
  3. Create the variable debt flashloan with the target debt(1010 USDC) on behalf of the user
  4. Swap the flashloaned target debt asset to the underlying of the current debt(1000 BUSD), needing only 1000.1 USDC
  5. Repay the current debt (1000 BUSD)
  6. Repay the flashloaned collateral asset and premium if needed (requires aToken approval)
  7. Use the remaining new debt asset (9.9 USDC) to repay parts of the newly created target debt

Notice how steps 3, 4, 5, and 7 are the same four steps from the collateral-less flow.

The guidelines for selecting a proper extra collateral asset are as follows:

For Aave V3:

  1. Ensure that the potential asset's LTV is nonzero.
  2. Ensure that the potential asset's LT is nonzero.
  3. Ensure that the potential asset's Supply Cap has sufficient capacity.
  4. If the user is in isolation mode, ensure the asset is the same as the isolated collateral asset.

For Aave V2:

  1. Ensure that the potential asset's LTV is nonzero.
  2. Ensure that the potential asset's LT is nonzero.
  3. Ensure that the extra collateral asset is the same as the new debt asset.
  4. Ensure that the collateral flashloan premium is added to the newDebtAmount.

When possible, for both V2 and V3 deployments, use the from/to debt asset in order to reduce cold storage access costs and save gas.

The recommended formula to determine the minimum amount of extra collateral is derived below:

USER_TOTAL_BORROW / (USER_OLD_COLLATERAL * OLD_COLLATERAL_LTV + EXTRA_COLLATERAL * EXTRA_COLLATERAL_ltv) = 1

USER_OLD_COLLATERAL * OLD_COLLATERAL_LTV + EXTRA_COLLATERAL * EXTRA_COLLATERAL_LTV = USER_TOTAL_BORROW

Therefore:

EXTRA_COLLATERAL = USER_TOTAL_BORROW * EXTRA_COLLATERAL_LTV / (USER_OLD_COLLATERAL * OLD_COLLATERAL_LTV)

We recommend a margin to account for interest accrual and health factor fluctuation until execution.

The function swapDebt(DebtSwapParams memory debtSwapParams, CreditDelegationInput memory creditDelegationPermit, PermitInput memory collateralATokenPermit) expects three parameters.

The first one describes the swap:

struct DebtSwapParams {
  address debtAsset; // the asset you want to swap away from
  uint256 debtRepayAmount; // the amount of debt you want to eliminate
  uint256 debtRateMode; // the type of debt (1 for stable, 2 for variable)
  address newDebtAsset; // the asset you want to swap to
  uint256 maxNewDebtAmount; // the max amount of debt your're willing to receive in excahnge for repaying debtRepayAmount
  address extraCollateralAsset; // The asset to flash and add as collateral if needed
  uint256 extraCollateralAmount; // The amount of `extraCollateralAsset` to flash and supply momentarily
  bytes paraswapData; // encoded exactOut swap
}

The second one describes the (optional) creditDelegation permit:

struct CreditDelegationInput {
  ICreditDelegationToken debtToken;
  uint256 value;
  uint256 deadline;
  uint8 v;
  bytes32 r;
  bytes32 s;
}

The third one describes the (optional) collateral aToken permit:

struct PermitInput {
  IERC20WithPermit aToken;
  uint256 value;
  uint256 deadline;
  uint8 v;
  bytes32 r;
  bytes32 s;
}

For usage examples please check the tests.

Security

  • This contract is a extra layer on top of BaseParaswapBuyAdapter which is used in production for ParaSwapRepayAdapter. It uses the exact same mechanism for exact out swap.

  • In contrast to ParaSwapRepayAdapter the ParaSwapDebtSwapAdapter will always repay on the pool on behalf of the user. So instead of having approvals per transaction the adapter will approve type(uint256).max once to reduce gas consumption.

  • The Aave POOL is considered a trustable entity for allowance purposes.

  • The contract only interact with msg.sender and therefore ensures isolation between users.

  • The contract is not upgradable.

  • The contract is ownable and will be owned by governance, so the governance will be the only entity able to call tokenRescue.

  • The approach with credit delegation and borrow-mode flashLoans is very similar to what is done on V2-V3 Migration helper

  • The contract inherits the security and limitations of Aave v2/v3. The contract itself does not validate for frozen/inactive reserves and also does not consider isolation/eMode or borrowCaps. It is the responsibility of the interface integrating this contract to correctly handle all user position compositions and pool configurations.

  • The contract implements an upper bound of 30% price impact, which would revert any swap. The slippage has to be properly configured in incorporated into the DebtSwapParams.maxNewDebt parameter.

Install

This repo has forge and npm dependencies, so you will need to install foundry then run:

forge install

and also run:

yarn

Tests

To run the tests just run:

forge test

References

This code is based on the existing aave paraswap adapters for v3.

The BaseParaSwapAdapter.sol was slightly adjusted to receive the POOL via constructor instead of fetching it.

This makes the code agnostic for v2 and v3, as the only methods used are unchanged between the two versions.