From 6e9defe14c111a02abc5056d9c2f1c8fddb6b412 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Mon, 9 Nov 2020 16:53:03 -0300 Subject: [PATCH] Add repayAllDebt flag to repay the whole debt with collateral --- contracts/adapters/BaseUniswapAdapter.sol | 5 +- .../adapters/UniswapLiquiditySwapAdapter.sol | 4 +- contracts/adapters/UniswapRepayAdapter.sol | 48 ++--- helpers/contracts-helpers.ts | 15 +- test/uniswapAdapters.spec.ts | 186 +++++++++++++++++- 5 files changed, 229 insertions(+), 29 deletions(-) diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol index c69fa9a0..cb7d32cb 100644 --- a/contracts/adapters/BaseUniswapAdapter.sol +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -224,9 +224,8 @@ contract BaseUniswapAdapter { * @dev Get the aToken associated to the asset * @return address of the aToken */ - function _getAToken(address asset) internal view returns (address) { - ReserveLogic.ReserveData memory reserve = POOL.getReserveData(asset); - return reserve.aTokenAddress; + function _getReserveData(address asset) internal view returns (ReserveLogic.ReserveData memory) { + return POOL.getReserveData(asset); } /** diff --git a/contracts/adapters/UniswapLiquiditySwapAdapter.sol b/contracts/adapters/UniswapLiquiditySwapAdapter.sol index 83b334d1..a4150ef1 100644 --- a/contracts/adapters/UniswapLiquiditySwapAdapter.sol +++ b/contracts/adapters/UniswapLiquiditySwapAdapter.sol @@ -126,7 +126,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { ); for (uint256 i = 0; i < assetToSwapFromList.length; i++) { - address aToken = _getAToken(assetToSwapFromList[i]); + address aToken = _getReserveData(assetToSwapFromList[i]).aTokenAddress; uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(msg.sender); uint256 amountToSwap = amountToSwapList[i] > aTokenInitiatorBalance ? aTokenInitiatorBalance : amountToSwapList[i]; @@ -172,7 +172,7 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { bool swapAllBalance, PermitSignature memory permitSignature ) internal { - address aToken = _getAToken(assetFrom); + address aToken = _getReserveData(assetFrom).aTokenAddress; uint256 aTokenInitiatorBalance = IERC20(aToken).balanceOf(initiator); uint256 amountToSwap = swapAllBalance ? aTokenInitiatorBalance.sub(premium) : amount; diff --git a/contracts/adapters/UniswapRepayAdapter.sol b/contracts/adapters/UniswapRepayAdapter.sol index dba64afd..f616765b 100644 --- a/contracts/adapters/UniswapRepayAdapter.sol +++ b/contracts/adapters/UniswapRepayAdapter.sol @@ -7,6 +7,7 @@ import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddresses import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; +import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; /** * @title UniswapRepayAdapter @@ -20,6 +21,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { LeftoverAction leftOverAction; uint256 repayAmount; uint256 rateMode; + bool repayAllDebt; PermitSignature permitSignature; } @@ -47,6 +49,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * (1) Direct transfer to user * uint256 repayAmount Amount of debt to be repaid * uint256 rateMode Rate modes of the debt to be repaid + * bool repayAllDebt Flag indicating if all the debt should be repaid * uint256 permitAmount Amount for the permit signature * uint256 deadline Deadline for the permit signature * uint8 v V param for the permit signature @@ -72,6 +75,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { decodedParams.rateMode, initiator, decodedParams.leftOverAction, + decodedParams.repayAllDebt, premiums[0], decodedParams.permitSignature ); @@ -100,9 +104,21 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { uint256 rateMode, address initiator, LeftoverAction leftOverAction, + bool repayAllDebt, uint256 premium, PermitSignature memory permitSignature ) internal { + + if (repayAllDebt) { + ReserveLogic.ReserveData memory reserveDebtData = _getReserveData(assetTo); + + address debtToken = ReserveLogic.InterestRateMode(rateMode) == ReserveLogic.InterestRateMode.STABLE + ? reserveDebtData.stableDebtTokenAddress + : reserveDebtData.variableDebtTokenAddress; + + repayAmount = IERC20(debtToken).balanceOf(initiator); + } + _swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount); // Repay debt @@ -110,7 +126,12 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { POOL.repay(assetTo, repayAmount, rateMode, initiator); uint256 flashLoanDebt = amount.add(premium); - _pullATokenAndRepayFlashLoan(assetFrom, initiator, flashLoanDebt, permitSignature); + + ReserveLogic.ReserveData memory reserveData = _getReserveData(assetFrom); + _pullAToken(assetFrom, reserveData.aTokenAddress, initiator, flashLoanDebt, permitSignature); + + // Repay flashloan + IERC20(assetFrom).approve(address(POOL), flashLoanDebt); // Take care of reserve leftover from the swap _sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator); @@ -125,6 +146,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * (1) Direct transfer to user * uint256 repayAmount Amount of debt to be repaid * uint256 rateMode Rate modes of the debt to be repaid + * bool repayAllDebt Flag indicating if all the debt should be repaid * uint256 permitAmount Amount for the permit signature * uint256 deadline Deadline for the permit signature * uint8 v V param for the permit signature @@ -138,18 +160,20 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { LeftoverAction leftOverAction, uint256 repayAmount, uint256 rateMode, + bool repayAllDebt, uint256 permitAmount, uint256 deadline, uint8 v, bytes32 r, bytes32 s - ) = abi.decode(params, (address, LeftoverAction, uint256, uint256, uint256, uint256, uint8, bytes32, bytes32)); + ) = abi.decode(params, (address, LeftoverAction, uint256, uint256, bool, uint256, uint256, uint8, bytes32, bytes32)); return RepayParams( assetToSwapTo, leftOverAction, repayAmount, rateMode, + repayAllDebt, PermitSignature( permitAmount, deadline, @@ -159,24 +183,4 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { ) ); } - - /** - * @dev Pull the ATokens from the user and use them to repay the flashloan - * @param reserve address of the asset - * @param user address - * @param flashLoanDebt need to be repaid - * @param permitSignature struct containing the permit signature - */ - function _pullATokenAndRepayFlashLoan( - address reserve, - address user, - uint256 flashLoanDebt, - PermitSignature memory permitSignature - ) internal { - address reserveAToken = _getAToken(reserve); - _pullAToken(reserve, reserveAToken, user, flashLoanDebt, permitSignature); - - // Repay flashloan - IERC20(reserve).approve(address(POOL), flashLoanDebt); - } } diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index e2ccf3c8..6eba519e 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -244,6 +244,7 @@ export const buildRepayAdapterParams = ( leftoverAction: BigNumberish, repayAmount: BigNumberish, rateMode: BigNumberish, + repayAllDebt: BigNumberish, permitAmount: BigNumberish, deadline: BigNumberish, v: BigNumberish, @@ -256,12 +257,24 @@ export const buildRepayAdapterParams = ( 'uint256', 'uint256', 'uint256', + 'bool', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32', ], - [assetToSwapTo, leftoverAction, repayAmount, rateMode, permitAmount, deadline, v, r, s] + [ + assetToSwapTo, + leftoverAction, + repayAmount, + rateMode, + repayAllDebt, + permitAmount, + deadline, + v, + r, + s, + ] ); }; diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index a3ea4dbe..fcb9d81d 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -1959,7 +1959,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { }); }); - describe.only('UniswapRepayAdapter', () => { + describe('UniswapRepayAdapter', () => { describe('constructor', () => { it('should deploy with correct parameters', async () => { const {addressesProvider} = testEnv; @@ -2062,6 +2062,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2166,6 +2167,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, expectedDaiAmount, 1, + 0, liquidityToSwap, deadline, v, @@ -2234,6 +2236,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2286,6 +2289,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2337,6 +2341,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2388,6 +2393,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2466,6 +2472,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2563,6 +2570,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { 0, 0, 0, + 0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000' ); @@ -2597,6 +2605,182 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.gt(userAEthBalanceBefore.sub(liquidityToSwap)); expect(wethBalance).to.be.eq(wethBalanceBefore.add(leftOverWeth.toString())); }); + + it('should correctly swap tokens and repay the whole stable debt', async () => { + const { + users, + pool, + weth, + aWETH, + oracle, + dai, + uniswapRepayAdapter, + helpersContract, + } = testEnv; + const user = users[0].signer; + const userAddress = users[0].address; + + const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); + + const daiPrice = await oracle.getAssetPrice(dai.address); + const expectedDaiAmount = await convertToCurrencyDecimals( + dai.address, + new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) + ); + + // Open user Debt + await pool.connect(user).borrow(dai.address, expectedDaiAmount, 1, 0, userAddress); + + const daiStableDebtTokenAddress = ( + await helpersContract.getReserveTokensAddresses(dai.address) + ).stableDebtTokenAddress; + + const daiStableDebtContract = await getContract( + eContractid.StableDebtToken, + daiStableDebtTokenAddress + ); + + const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress); + + const liquidityToSwap = amountWETHtoSwap; + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); + + // Subtract the FL fee from the amount to be swapped 0,09% + const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount); + + // Passed amount to repay is smaller than debt, + // but repayAllDebt flag is enabled so the whole debt should be paid + const amountToRepay = expectedDaiAmount.div(2); + + const params = buildRepayAdapterParams( + dai.address, + 0, + amountToRepay, + 1, + 1, + 0, + 0, + 0, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000' + ); + + await pool + .connect(user) + .flashLoan( + uniswapRepayAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params, + 0 + ); + + const adapterWethBalance = await weth.balanceOf(uniswapRepayAdapter.address); + const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address); + const userDaiStableDebtAmount = await daiStableDebtContract.balanceOf(userAddress); + const userAEthBalance = await aWETH.balanceOf(userAddress); + + expect(adapterWethBalance).to.be.eq(Zero); + expect(adapterDaiBalance).to.be.eq(Zero); + expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount); + expect(userDaiStableDebtAmount).to.be.eq(Zero); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); + + it('should correctly swap tokens and repay the whole variable debt', async () => { + const { + users, + pool, + weth, + aWETH, + oracle, + dai, + uniswapRepayAdapter, + helpersContract, + } = testEnv; + const user = users[0].signer; + const userAddress = users[0].address; + + const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); + + const daiPrice = await oracle.getAssetPrice(dai.address); + const expectedDaiAmount = await convertToCurrencyDecimals( + dai.address, + new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) + ); + + // Open user Debt + await pool.connect(user).borrow(dai.address, expectedDaiAmount, 2, 0, userAddress); + + const daiStableVariableTokenAddress = ( + await helpersContract.getReserveTokensAddresses(dai.address) + ).variableDebtTokenAddress; + + const daiVariableDebtContract = await getContract( + eContractid.VariableDebtToken, + daiStableVariableTokenAddress + ); + + const userDaiVariableDebtAmountBefore = await daiVariableDebtContract.balanceOf( + userAddress + ); + + const liquidityToSwap = amountWETHtoSwap; + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); + + // Subtract the FL fee from the amount to be swapped 0,09% + const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount); + + // Passed amount to repay is smaller than debt, + // but repayAllDebt flag is enabled so the whole debt should be paid + const amountToRepay = expectedDaiAmount.div(2); + + const params = buildRepayAdapterParams( + dai.address, + 0, + amountToRepay, + 2, + 1, + 0, + 0, + 0, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000' + ); + + await pool + .connect(user) + .flashLoan( + uniswapRepayAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params, + 0 + ); + + const adapterWethBalance = await weth.balanceOf(uniswapRepayAdapter.address); + const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address); + const userDaiVariableDebtAmount = await daiVariableDebtContract.balanceOf(userAddress); + const userAEthBalance = await aWETH.balanceOf(userAddress); + + expect(adapterWethBalance).to.be.eq(Zero); + expect(adapterDaiBalance).to.be.eq(Zero); + expect(userDaiVariableDebtAmountBefore).to.be.gte(expectedDaiAmount); + expect(userDaiVariableDebtAmount).to.be.eq(Zero); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); }); }); });