aave/aave-debt-swap

Failure of flashloan repayment

Closed this issue · 2 comments

In the implementation of ParaSwapLiquiditySwapAdapter, ParaSwapRepayAdapter and ParaSwapDebtSwapAdapter(ParaSwapDebtSwapAdapter written by bgd-labs while other two written by AAVE team), flashloan with mode 0 can be taken if there is need of extra collateral asset. If flashloan with mode 0 is taken, it needs to be paid in full.

There can be issue when flashloan is taken with mode 0 as follows(it shows the issue in ParaSwapLiquiditySwapAdapter but same can happen in above mentioned adapters):

  • Flashloan is taken by adapter. Received amount is supplied as collateral on behalf of user.
  • User receives corresponding aTokens for flashloaned asset.
  • The collateral is swapped using paraswap adapters and new collateral is supplied which updates the index.
  • During repayment of flashloan, the aTokens for flashloaned asset may not correspond to exact flashloan amount due to rounding issue. This results in failure of transaction.

Testcase highlighting this behaviour in ParaSwapLiquiditySwapAdapter:

function test_liquiditySwap_with_extra_collateral() public {
    uint256 supplyAmount = 12000e18;
    uint256 borrowAmount = 5000e18;
    uint256 flashLoanAmount = 367242556761;
    address collateralAssetAToken = AaveV2EthereumAssets.DAI_A_TOKEN;
    address collateralAsset = AaveV2EthereumAssets.DAI_UNDERLYING;
    address newCollateralAsset = AaveV2EthereumAssets.LUSD_UNDERLYING;
    address newCollateralAssetAToken = AaveV2EthereumAssets.LUSD_A_TOKEN;
    address flashLoanAsset = AaveV2EthereumAssets.USDC_UNDERLYING;
    address flashLoanAssetAToken = AaveV2EthereumAssets.USDC_A_TOKEN;
    uint256 flashLoanAmount = bound(flashLoanAmount1, 2000e6, 1000_000e6);
    vm.startPrank(user);

    _supply(AaveV2Ethereum.POOL, supplyAmount, collateralAsset);
    _borrow(AaveV2Ethereum.POOL, borrowAmount, collateralAsset);

    uint256 collateralAssetATokenBalanceBefore = IERC20Detailed(collateralAssetAToken).balanceOf(
      user
    );

    // Swap liquidity(collateral)
    uint256 collateralAmountToSwap = 2000e18;
    PsPResponse memory psp = _fetchPSPRoute(
      collateralAsset,
      newCollateralAsset,
      collateralAmountToSwap,
      user,
      true,
      false
    );

    IERC20Detailed(collateralAssetAToken).approve(
      address(liquiditySwapAdapter),
      collateralAmountToSwap
    );

    IERC20Detailed(flashLoanAssetAToken).approve(address(liquiditySwapAdapter), flashLoanAmount);


    IParaSwapLiquiditySwapAdapter.LiquiditySwapParams
      memory liquiditySwapParams = IParaSwapLiquiditySwapAdapter.LiquiditySwapParams({
        collateralAsset: collateralAsset,
        collateralAmountToSwap: collateralAmountToSwap,
        newCollateralAsset: newCollateralAsset,
        minNewCollateralAmount: 2000e18,
        offset: psp.offset,
        paraswapData: abi.encode(psp.swapCalldata, psp.augustus)
      });

    IParaSwapLiquiditySwapAdapter.PermitInput memory collateralATokenPermit;
    IParaSwapLiquiditySwapAdapter.PermitInput memory flashLoanATokenPermit;
    IParaSwapLiquiditySwapAdapter.FlashParams memory flashParams = IParaSwapLiquiditySwapAdapter.FlashParams({
      pool: address(AaveV3Ethereum.POOL),
      flashLoanAsset: flashLoanAsset,
      flashLoanAmount: flashLoanAmount,
      user: user,
      flashLoanAssetPermit: collateralATokenPermit
    });

    liquiditySwapAdapter.swapLiquidity(liquiditySwapParams, flashParams, collateralATokenPermit);
    {
    uint256 collateralAssetATokenBalanceAfter = IERC20Detailed(collateralAssetAToken).balanceOf(
      user
    );
    assertTrue(
      _withinRange(
        collateralAssetATokenBalanceBefore - collateralAssetATokenBalanceAfter,
        collateralAmountToSwap,
        2
      )
    );
    }
    _invariant(address(liquiditySwapAdapter), collateralAsset, newCollateralAsset);
    _invariant(address(liquiditySwapAdapter), collateralAssetAToken, newCollateralAssetAToken);
    _invariant(address(liquiditySwapAdapter), flashLoanAsset, flashLoanAssetAToken);
  }

The above test fails due to substraction overflow because user's aToken is not equal to flashLoanAmount.

Potential solution:

  • Not to fix and keep it as it is because it is very less likely to occur.
  • Change the logic of flashloan to always take debt bearing flashloan.