Failure of flashloan repayment
Closed this issue · 2 comments
parth-15 commented
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.
parth-15 commented
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
.
parth-15 commented
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.