From cf9c8855c33e72ceba2442c22dcf9130061a4616 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 10 Nov 2020 10:38:24 -0300 Subject: [PATCH] Pull the correct amount of atokens from user to avoid leftovers in repay adapter --- contracts/adapters/BaseUniswapAdapter.sol | 25 ----- contracts/adapters/UniswapRepayAdapter.sol | 25 +---- helpers/contracts-helpers.ts | 27 +---- test/uniswapAdapters.spec.ts | 114 ++------------------- 4 files changed, 14 insertions(+), 177 deletions(-) diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol index cb7d32cb..dab04acd 100644 --- a/contracts/adapters/BaseUniswapAdapter.sol +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -24,8 +24,6 @@ contract BaseUniswapAdapter { using PercentageMath for uint256; using SafeERC20 for IERC20; - enum LeftoverAction {DEPOSIT, TRANSFER} - struct PermitParams { uint256[] amount; uint256[] deadline; @@ -228,29 +226,6 @@ contract BaseUniswapAdapter { return POOL.getReserveData(asset); } - /** - * @dev Take action with the swap left overs as configured in the parameters - * @param asset address of the asset - * @param reservedAmount Amount reserved to be used by the contract to repay the flash loan - * @param leftOverAction enum indicating what to do with the left over balance from the swap: - * (0) Deposit back - * (1) Direct transfer to user - * @param user address - */ - function _sendLeftovers(address asset, uint256 reservedAmount, LeftoverAction leftOverAction, address user) internal { - uint256 balance = IERC20(asset).balanceOf(address(this)); - uint256 assetLeftOver = balance.sub(reservedAmount); - - if (assetLeftOver > 0) { - if (leftOverAction == LeftoverAction.DEPOSIT) { - IERC20(asset).approve(address(POOL), balance); - POOL.deposit(asset, assetLeftOver, user, 0); - } else { - IERC20(asset).transfer(user, assetLeftOver); - } - } - } - /** * @dev Pull the ATokens from the user * @param reserve address of the asset diff --git a/contracts/adapters/UniswapRepayAdapter.sol b/contracts/adapters/UniswapRepayAdapter.sol index f616765b..fb821db3 100644 --- a/contracts/adapters/UniswapRepayAdapter.sol +++ b/contracts/adapters/UniswapRepayAdapter.sol @@ -18,7 +18,6 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { struct RepayParams { address assetToSwapTo; - LeftoverAction leftOverAction; uint256 repayAmount; uint256 rateMode; bool repayAllDebt; @@ -44,9 +43,6 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * @param initiator Address of the user * @param params Additional variadic field to include extra params. Expected parameters: * address Address of the reserve to be swapped to and repay - * uint256 leftOverAction Flag indicating what to do with the left over balance from the swap: - * (0) Deposit back - * (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 @@ -74,7 +70,6 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { decodedParams.repayAmount, decodedParams.rateMode, initiator, - decodedParams.leftOverAction, decodedParams.repayAllDebt, premiums[0], decodedParams.permitSignature @@ -84,7 +79,7 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { } /** - * @dev Perform the swap, the repay of the debt and send back the left overs + * @dev Perform the swap and the repay of the debt * * @param assetFrom Address of token to be swapped * @param assetTo Address of token to be received @@ -92,7 +87,6 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * @param repayAmount Amount of the debt to be repaid * @param rateMode Rate mode of the debt to be repaid * @param initiator Address of the user - * @param leftOverAction enum indicating what to do with the left over balance from the swap * @param premium Fee of the flash loan * @param permitSignature struct containing the permit signature */ @@ -103,12 +97,10 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { uint256 repayAmount, uint256 rateMode, address initiator, - LeftoverAction leftOverAction, bool repayAllDebt, uint256 premium, PermitSignature memory permitSignature ) internal { - if (repayAllDebt) { ReserveLogic.ReserveData memory reserveDebtData = _getReserveData(assetTo); @@ -119,31 +111,26 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { repayAmount = IERC20(debtToken).balanceOf(initiator); } - _swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount); + uint256 amountSwapped = _swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount); // Repay debt IERC20(assetTo).approve(address(POOL), repayAmount); POOL.repay(assetTo, repayAmount, rateMode, initiator); uint256 flashLoanDebt = amount.add(premium); + uint256 amountToPull = flashLoanDebt.sub(amount.sub(amountSwapped)); ReserveLogic.ReserveData memory reserveData = _getReserveData(assetFrom); - _pullAToken(assetFrom, reserveData.aTokenAddress, initiator, flashLoanDebt, permitSignature); + _pullAToken(assetFrom, reserveData.aTokenAddress, initiator, amountToPull, permitSignature); // Repay flashloan IERC20(assetFrom).approve(address(POOL), flashLoanDebt); - - // Take care of reserve leftover from the swap - _sendLeftovers(assetFrom, flashLoanDebt, leftOverAction, initiator); } /** * @dev Decodes debt information encoded in flashloan params * @param params Additional variadic field to include extra params. Expected parameters: * address Address of the reserve to be swapped to and repay - * uint256 leftOverAction Flag indicating what to do with the left over balance from the swap: - * (0) Deposit back - * (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 @@ -157,7 +144,6 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { function _decodeParams(bytes memory params) internal pure returns (RepayParams memory) { ( address assetToSwapTo, - LeftoverAction leftOverAction, uint256 repayAmount, uint256 rateMode, bool repayAllDebt, @@ -166,11 +152,10 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { uint8 v, bytes32 r, bytes32 s - ) = abi.decode(params, (address, LeftoverAction, uint256, uint256, bool, uint256, uint256, uint8, bytes32, bytes32)); + ) = abi.decode(params, (address, uint256, uint256, bool, uint256, uint256, uint8, bytes32, bytes32)); return RepayParams( assetToSwapTo, - leftOverAction, repayAmount, rateMode, repayAllDebt, diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 6eba519e..1b968922 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -241,7 +241,6 @@ export const buildLiquiditySwapParams = ( export const buildRepayAdapterParams = ( assetToSwapTo: tEthereumAddress, - leftoverAction: BigNumberish, repayAmount: BigNumberish, rateMode: BigNumberish, repayAllDebt: BigNumberish, @@ -252,29 +251,7 @@ export const buildRepayAdapterParams = ( s: string | Buffer ) => { return ethers.utils.defaultAbiCoder.encode( - [ - 'address', - 'uint256', - 'uint256', - 'uint256', - 'bool', - 'uint256', - 'uint256', - 'uint8', - 'bytes32', - 'bytes32', - ], - [ - assetToSwapTo, - leftoverAction, - repayAmount, - rateMode, - repayAllDebt, - permitAmount, - deadline, - v, - r, - s, - ] + ['address', 'uint256', 'uint256', 'bool', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32'], + [assetToSwapTo, repayAmount, rateMode, repayAllDebt, permitAmount, deadline, v, r, s] ); }; diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index fcb9d81d..2a086fb8 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -2056,7 +2056,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2164,7 +2163,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2230,7 +2228,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2283,7 +2280,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2335,7 +2331,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2387,7 +2382,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2413,7 +2407,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { ).to.be.revertedWith('maxAmountToSwap exceed max slippage'); }); - it('should swap tokens, repay debt and deposit in pool the left over', async () => { + it('should swap, repay debt and pull the needed ATokens leaving no leftovers', async () => { const { users, pool, @@ -2466,7 +2460,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, expectedDaiAmount, 1, 0, @@ -2497,7 +2490,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address); const userDaiStableDebtAmount = await daiStableDebtContract.balanceOf(userAddress); const userAEthBalance = await aWETH.balanceOf(userAddress); + const adapterAEthBalance = await aWETH.balanceOf(uniswapRepayAdapter.address); + expect(adapterAEthBalance).to.be.eq(Zero); expect(adapterWethBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount); @@ -2509,103 +2504,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { ); }); - it('should swap tokens, repay debt and transfer to user the left over', 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); - - const actualWEthSwapped = new BigNumber(flashloanAmount.toString()) - .multipliedBy(0.995) - .toFixed(0); - - const leftOverWeth = new BigNumber(flashloanAmount).minus(actualWEthSwapped); - - await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, actualWEthSwapped); - - const wethBalanceBefore = await weth.balanceOf(userAddress); - - const params = buildRepayAdapterParams( - dai.address, - 1, - expectedDaiAmount, - 1, - 0, - 0, - 0, - 0, - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000' - ); - - await expect( - pool - .connect(user) - .flashLoan( - uniswapRepayAdapter.address, - [weth.address], - [flashloanAmount.toString()], - [0], - userAddress, - params, - 0 - ) - ) - .to.emit(uniswapRepayAdapter, 'Swapped') - .withArgs(weth.address, dai.address, actualWEthSwapped.toString(), expectedDaiAmount); - - 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); - const wethBalance = await weth.balanceOf(userAddress); - - expect(adapterWethBalance).to.be.eq(Zero); - expect(adapterDaiBalance).to.be.eq(Zero); - expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount); - expect(userDaiStableDebtAmount).to.be.lt(expectedDaiAmount); - expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); - 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, @@ -2657,7 +2555,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, amountToRepay, 1, 1, @@ -2684,7 +2581,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address); const userDaiStableDebtAmount = await daiStableDebtContract.balanceOf(userAddress); const userAEthBalance = await aWETH.balanceOf(userAddress); + const adapterAEthBalance = await aWETH.balanceOf(uniswapRepayAdapter.address); + expect(adapterAEthBalance).to.be.eq(Zero); expect(adapterWethBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmount); @@ -2746,7 +2645,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const params = buildRepayAdapterParams( dai.address, - 0, amountToRepay, 2, 1, @@ -2773,7 +2671,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const adapterDaiBalance = await dai.balanceOf(uniswapRepayAdapter.address); const userDaiVariableDebtAmount = await daiVariableDebtContract.balanceOf(userAddress); const userAEthBalance = await aWETH.balanceOf(userAddress); + const adapterAEthBalance = await aWETH.balanceOf(uniswapRepayAdapter.address); + expect(adapterAEthBalance).to.be.eq(Zero); expect(adapterWethBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(userDaiVariableDebtAmountBefore).to.be.gte(expectedDaiAmount);