diff --git a/contracts/adapters/BaseUniswapAdapter.sol b/contracts/adapters/BaseUniswapAdapter.sol index eb70d2e4..26aa60b5 100644 --- a/contracts/adapters/BaseUniswapAdapter.sol +++ b/contracts/adapters/BaseUniswapAdapter.sol @@ -5,15 +5,14 @@ pragma experimental ABIEncoderV2; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeMath} from '../dependencies/openzeppelin/contracts/SafeMath.sol'; import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; +import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; import {ILendingPool} from '../interfaces/ILendingPool.sol'; import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol'; -import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol'; import {IUniswapV2Router02} from '../interfaces/IUniswapV2Router02.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; - /** * @title BaseUniswapAdapter * @notice Implements the logic for performing assets swaps in Uniswap V2 @@ -23,23 +22,24 @@ contract BaseUniswapAdapter { using SafeMath for uint256; using PercentageMath for uint256; using SafeERC20 for IERC20; - using ReserveConfiguration for ReserveConfiguration.Map; + + enum LeftoverAction {DEPOSIT, TRANSFER} // Max slippage percent allow by param uint256 public constant MAX_SLIPPAGE_PERCENT = 3000; // 30% // Min slippage percent allow by param uint256 public constant MIN_SLIPPAGE_PERCENT = 10; // 0,1% - ILendingPoolAddressesProvider public immutable addressesProvider; - IUniswapV2Router02 public immutable uniswapRouter; - ILendingPool public immutable pool; + ILendingPool public immutable POOL; + IPriceOracleGetter public immutable ORACLE; + IUniswapV2Router02 public immutable UNISWAP_ROUTER; event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); - constructor(ILendingPoolAddressesProvider _addressesProvider, IUniswapV2Router02 _uniswapRouter) public { - addressesProvider = _addressesProvider; - pool = ILendingPool(_addressesProvider.getLendingPool()); - uniswapRouter = _uniswapRouter; + constructor(ILendingPoolAddressesProvider addressesProvider, IUniswapV2Router02 uniswapRouter) public { + POOL = ILendingPool(addressesProvider.getLendingPool()); + ORACLE = IPriceOracleGetter(addressesProvider.getPriceOracle()); + UNISWAP_ROUTER = uniswapRouter; } /** @@ -58,7 +58,7 @@ contract BaseUniswapAdapter { path[0] = reserveIn; path[1] = reserveOut; - uint256[] memory amounts = uniswapRouter.getAmountsOut(amountIn, path); + uint256[] memory amounts = UNISWAP_ROUTER.getAmountsOut(amountIn, path); return amounts[1]; } @@ -79,7 +79,7 @@ contract BaseUniswapAdapter { path[0] = reserveIn; path[1] = reserveOut; - uint256[] memory amounts = uniswapRouter.getAmountsIn(amountOut, path); + uint256[] memory amounts = UNISWAP_ROUTER.getAmountsIn(amountOut, path); return amounts[0]; } @@ -101,24 +101,23 @@ contract BaseUniswapAdapter { internal returns (uint256) { - uint256 fromAssetDecimals = getDecimals(assetToSwapFrom); - uint256 toAssetDecimals = getDecimals(assetToSwapTo); + uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); + uint256 toAssetDecimals = _getDecimals(assetToSwapTo); - (uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, assetToSwapTo); + uint256 fromAssetPrice = _getPrice(assetToSwapFrom); + uint256 toAssetPrice = _getPrice(assetToSwapTo); uint256 amountOutMin = amountToSwap .mul(fromAssetPrice.mul(10**toAssetDecimals)) .div(toAssetPrice.mul(10**fromAssetDecimals)) .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(slippage)); - IERC20(assetToSwapFrom).approve(address(uniswapRouter), amountToSwap); + IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), amountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; - uint256[] memory amounts = uniswapRouter.swapExactTokensForTokens(amountToSwap, amountOutMin, path, address(this), block.timestamp); - - require(amounts[1] >= amountOutMin, 'INSUFFICIENT_OUTPUT_AMOUNT'); + uint256[] memory amounts = UNISWAP_ROUTER.swapExactTokensForTokens(amountToSwap, amountOutMin, path, address(this), block.timestamp); emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]); @@ -143,10 +142,11 @@ contract BaseUniswapAdapter { internal returns (uint256) { - uint256 fromAssetDecimals = getDecimals(assetToSwapFrom); - uint256 toAssetDecimals = getDecimals(assetToSwapTo); + uint256 fromAssetDecimals = _getDecimals(assetToSwapFrom); + uint256 toAssetDecimals = _getDecimals(assetToSwapTo); - (uint256 fromAssetPrice, uint256 toAssetPrice) = getPrices(assetToSwapFrom, assetToSwapTo); + uint256 fromAssetPrice = _getPrice(assetToSwapFrom); + uint256 toAssetPrice = _getPrice(assetToSwapTo); uint256 expectedMaxAmountToSwap = amountToReceive .mul(toAssetPrice.mul(10**fromAssetDecimals)) @@ -155,14 +155,12 @@ contract BaseUniswapAdapter { require(maxAmountToSwap < expectedMaxAmountToSwap, 'maxAmountToSwap exceed max slippage'); - IERC20(assetToSwapFrom).approve(address(uniswapRouter), maxAmountToSwap); + IERC20(assetToSwapFrom).approve(address(UNISWAP_ROUTER), maxAmountToSwap); address[] memory path = new address[](2); path[0] = assetToSwapFrom; path[1] = assetToSwapTo; - uint256[] memory amounts = uniswapRouter.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp); - - require(amounts[1] >= amountToReceive, 'INSUFFICIENT_OUTPUT_AMOUNT'); + uint256[] memory amounts = UNISWAP_ROUTER.swapTokensForExactTokens(amountToReceive, maxAmountToSwap, path, address(this), block.timestamp); emit Swapped(assetToSwapFrom, assetToSwapTo, amounts[0], amounts[1]); @@ -170,34 +168,20 @@ contract BaseUniswapAdapter { } /** - * @dev Get assets prices from the oracle denominated in eth - * @param assetToSwapFrom first asset - * @param assetToSwapTo second asset - * @return fromAssetPrice eth price for the first asset - * @return toAssetPrice eth price for the second asset + * @dev Get the price of the asset from the oracle denominated in eth + * @param asset address + * @return eth price for the asset */ - function getPrices( - address assetToSwapFrom, - address assetToSwapTo - ) - internal - view - returns (uint256 fromAssetPrice, uint256 toAssetPrice) - { - IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); - fromAssetPrice = oracle.getAssetPrice(assetToSwapFrom); - toAssetPrice = oracle.getAssetPrice(assetToSwapTo); + function _getPrice(address asset) internal view returns (uint256) { + return ORACLE.getAssetPrice(asset); } /** * @dev Get the decimals of an asset * @return number of decimals of the asset */ - function getDecimals(address asset) internal view returns (uint256) { - ReserveConfiguration.Map memory configuration = pool.getConfiguration(asset); - (, , , uint256 decimals, ) = configuration.getParamsMemory(); - - return decimals; + function _getDecimals(address asset) internal view returns (uint256) { + return IERC20Detailed(asset).decimals(); } /** @@ -205,7 +189,7 @@ contract BaseUniswapAdapter { * @return address of the aToken */ function getAToken(address asset) internal view returns (address) { - ReserveLogic.ReserveData memory reserve = pool.getReserveData(asset); + ReserveLogic.ReserveData memory reserve = POOL.getReserveData(asset); return reserve.aTokenAddress; } @@ -213,19 +197,19 @@ contract BaseUniswapAdapter { * @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 Flag indicating what to do with the left over balance from the swap: + * @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 sendLeftOver(address asset, uint256 reservedAmount, uint256 leftOverAction, address user) internal { + 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 == 0) { - IERC20(asset).approve(address(pool), balance); - pool.deposit(asset, assetLeftOver, user, 0); + if (leftOverAction == LeftoverAction.DEPOSIT) { + IERC20(asset).approve(address(POOL), balance); + POOL.deposit(asset, assetLeftOver, user, 0); } else { IERC20(asset).transfer(user, assetLeftOver); } @@ -249,7 +233,7 @@ contract BaseUniswapAdapter { IERC20(reserveAToken).safeTransferFrom(user, address(this), amount); // withdraw reserve - pool.withdraw(reserve, amount); + POOL.withdraw(reserve, amount, address(this)); } /** @@ -266,6 +250,6 @@ contract BaseUniswapAdapter { pullAToken(reserve, user, flashLoanDebt); // Repay flashloan - IERC20(reserve).approve(address(pool), flashLoanDebt); + IERC20(reserve).approve(address(POOL), flashLoanDebt); } } diff --git a/contracts/adapters/UniswapLiquiditySwapAdapter.sol b/contracts/adapters/UniswapLiquiditySwapAdapter.sol index d44f50fd..2a40e0bc 100644 --- a/contracts/adapters/UniswapLiquiditySwapAdapter.sol +++ b/contracts/adapters/UniswapLiquiditySwapAdapter.sol @@ -16,11 +16,11 @@ import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { constructor( - ILendingPoolAddressesProvider _addressesProvider, - IUniswapV2Router02 _uniswapRouter + ILendingPoolAddressesProvider addressesProvider, + IUniswapV2Router02 uniswapRouter ) public - BaseUniswapAdapter(_addressesProvider, _uniswapRouter) + BaseUniswapAdapter(addressesProvider, uniswapRouter) {} /** @@ -31,32 +31,34 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * @param assets Address to be swapped * @param amounts Amount of the reserve to be swapped * @param premiums Fee of the flash loan + * @param initiator Address of the user * @param params Additional variadic field to include extra params. Expected parameters: - * address assetToSwapTo Address of the reserve to be swapped to and deposited - * address user The address of the user + * address[] assetToSwapToList List of the addresses of the reserve to be swapped to and deposited * uint256 slippage The max slippage percentage allowed for the swap */ function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, + address initiator, bytes calldata params ) external override returns (bool) { - ( - address assetToSwapTo, - address user, - uint256 slippage - ) = abi.decode(params, (address, address, uint256)); + require(msg.sender == address(POOL), "CALLER_MUST_BE_LENDING_POOL"); + + (address[] memory assetToSwapToList, uint256 slippage) = abi.decode(params, (address[], uint256)); require(slippage < MAX_SLIPPAGE_PERCENT && slippage >= MIN_SLIPPAGE_PERCENT, 'SLIPPAGE_OUT_OF_RANGE'); + require(assetToSwapToList.length == assets.length, 'INCONSISTENT_PARAMS'); - uint256 receivedAmount = swapExactTokensForTokens(assets[0], assetToSwapTo, amounts[0], slippage); + for (uint256 i = 0; i < assets.length; i++) { + uint256 receivedAmount = swapExactTokensForTokens(assets[i], assetToSwapToList[i], amounts[i], slippage); - // Deposit new reserve - IERC20(assetToSwapTo).approve(address(pool), receivedAmount); - pool.deposit(assetToSwapTo, receivedAmount, user, 0); + // Deposit new reserve + IERC20(assetToSwapToList[i]).approve(address(POOL), receivedAmount); + POOL.deposit(assetToSwapToList[i], receivedAmount, initiator, 0); - uint256 flashLoanDebt = amounts[0].add(premiums[0]); - pullATokenAndRepayFlashLoan(assets[0], user, flashLoanDebt); + uint256 flashLoanDebt = amounts[i].add(premiums[i]); + pullATokenAndRepayFlashLoan(assets[i], initiator, flashLoanDebt); + } return true; } @@ -66,25 +68,35 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * This method can be used when the user has no debts. * The user should give this contract allowance to pull the ATokens in order to withdraw the underlying asset and * perform the swap. - * @param assetToSwapFrom Address of the underlying asset to be swap from - * @param assetToSwapTo Address of the underlying asset to be swap to and deposited - * @param amountToSwap How much `assetToSwapFrom` needs to be swapped - * @param user Address that will be pulling the swapped funds + * @param assetToSwapFromList List of addresses of the underlying asset to be swap from + * @param assetToSwapToList List of addresses of the underlying asset to be swap to and deposited + * @param amountToSwapList List of amounts to be swapped * @param slippage The max slippage percentage allowed for the swap */ function swapAndDeposit( - address assetToSwapFrom, - address assetToSwapTo, - uint256 amountToSwap, - address user, + address[] calldata assetToSwapFromList, + address[] calldata assetToSwapToList, + uint256[] calldata amountToSwapList, uint256 slippage ) external { - pullAToken(assetToSwapFrom, user, amountToSwap); + require( + assetToSwapFromList.length == assetToSwapToList.length && assetToSwapFromList.length == amountToSwapList.length, + 'INCONSISTENT_PARAMS' + ); - uint256 receivedAmount = swapExactTokensForTokens(assetToSwapFrom, assetToSwapTo, amountToSwap, slippage); + for (uint256 i = 0; i < assetToSwapFromList.length; i++) { + pullAToken(assetToSwapFromList[i], msg.sender, amountToSwapList[i]); - // Deposit new reserve - IERC20(assetToSwapTo).approve(address(pool), receivedAmount); - pool.deposit(assetToSwapTo, receivedAmount, user, 0); + uint256 receivedAmount = swapExactTokensForTokens( + assetToSwapFromList[i], + assetToSwapToList[i], + amountToSwapList[i], + slippage + ); + + // Deposit new reserve + IERC20(assetToSwapToList[i]).approve(address(POOL), receivedAmount); + POOL.deposit(assetToSwapToList[i], receivedAmount, msg.sender, 0); + } } } diff --git a/contracts/adapters/UniswapRepayAdapter.sol b/contracts/adapters/UniswapRepayAdapter.sol index 70490fa7..755bfb13 100644 --- a/contracts/adapters/UniswapRepayAdapter.sol +++ b/contracts/adapters/UniswapRepayAdapter.sol @@ -15,12 +15,19 @@ import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; **/ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { + struct RepayParams { + address[] assetToSwapToList; + LeftoverAction leftOverAction; + uint256[] repayAmounts; + uint256[] rateModes; + } + constructor( - ILendingPoolAddressesProvider _addressesProvider, - IUniswapV2Router02 _uniswapRouter + ILendingPoolAddressesProvider addressesProvider, + IUniswapV2Router02 uniswapRouter ) public - BaseUniswapAdapter(_addressesProvider, _uniswapRouter) + BaseUniswapAdapter(addressesProvider, uniswapRouter) {} /** @@ -31,41 +38,102 @@ contract UniswapRepayAdapter is BaseUniswapAdapter, IFlashLoanReceiver { * @param assets Address to be swapped * @param amounts Amount of the reserve to be swapped * @param premiums Fee of the flash loan + * @param initiator Address of the user * @param params Additional variadic field to include extra params. Expected parameters: - * address assetToSwapTo Address of the reserve to be swapped to and deposited - * address user The address of the user + * address[] assetToSwapToList List of the addresses 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 The rate modes of the debt to be repaid + * uint256[] repayAmounts List of amounts of debt to be repaid + * uint256[] rateModes List of the rate modes of the debt to be repaid */ function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, + address initiator, bytes calldata params ) external override returns (bool) { - ( - address assetToSwapTo, - address user, - uint256 leftOverAction, - uint256 repayAmount, - uint256 rateMode - ) = abi.decode(params, (address, address, uint256, uint256, uint256)); + require(msg.sender == address(POOL), "CALLER_MUST_BE_LENDING_POOL"); - swapTokensForExactTokens(assets[0], assetToSwapTo, amounts[0], repayAmount); + RepayParams memory decodedParams = _decodeParams(params); - // Repay debt - IERC20(assetToSwapTo).approve(address(pool), repayAmount); - pool.repay(assetToSwapTo, repayAmount, rateMode, user); + require( + assets.length == decodedParams.assetToSwapToList.length + && assets.length == decodedParams.repayAmounts.length + && assets.length == decodedParams.rateModes.length, + 'INCONSISTENT_PARAMS'); - uint256 flashLoanDebt = amounts[0].add(premiums[0]); - pullATokenAndRepayFlashLoan(assets[0], user, flashLoanDebt); - - // Take care of reserve leftover from the swap - sendLeftOver(assets[0], flashLoanDebt, leftOverAction, user); + for (uint256 i = 0; i < assets.length; i++) { + _swapAndRepay( + assets[i], + decodedParams.assetToSwapToList[i], + amounts[i], + decodedParams.repayAmounts[i], + decodedParams.rateModes[i], + initiator, + decodedParams.leftOverAction, + premiums[i] + ); + } return true; } + + /** + * @dev Perform the swap, the repay of the debt and send back the left overs + * + * @param assetFrom Address of token to be swapped + * @param assetTo Address of token to be received + * @param amount Amount of the reserve to be swapped + * @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 + */ + function _swapAndRepay( + address assetFrom, + address assetTo, + uint256 amount, + uint256 repayAmount, + uint256 rateMode, + address initiator, + LeftoverAction leftOverAction, + uint256 premium + ) internal { + swapTokensForExactTokens(assetFrom, assetTo, amount, repayAmount); + + // Repay debt + IERC20(assetTo).approve(address(POOL), repayAmount); + POOL.repay(assetTo, repayAmount, rateMode, initiator); + + uint256 flashLoanDebt = amount.add(premium); + pullATokenAndRepayFlashLoan(assetFrom, initiator, 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[] assetToSwapToList List of the addresses 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[] repayAmounts List of amounts of debt to be repaid + * uint256[] rateModes List of the rate modes of the debt to be repaid + * @return RepayParams struct containing decoded params + */ + function _decodeParams(bytes memory params) internal returns (RepayParams memory) { + ( + address[] memory assetToSwapToList, + LeftoverAction leftOverAction, + uint256[] memory repayAmounts, + uint256[] memory rateModes + ) = abi.decode(params, (address[], LeftoverAction, uint256[], uint256[])); + + return RepayParams(assetToSwapToList, leftOverAction, repayAmounts, rateModes); + } } diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index 50007af0..555f0ef0 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -121,8 +121,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { // 0,5% slippage const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256'], - [dai.address, userAddress, 50] + ['address[]', 'uint256'], + [[dai.address], 50] ); await expect( @@ -132,7 +132,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -158,6 +158,90 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); }); + it('should revert if inconsistent params', async () => { + const {users, weth, oracle, dai, aWETH, pool, uniswapLiquiditySwapAdapter} = 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) + ); + + await mockUniswapRouter.setAmountToReturn(expectedDaiAmount); + + // User will swap liquidity 10 aEth to aDai + const liquidityToSwap = parseEther('10'); + await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); + + // Subtract the FL fee from the amount to be swapped 0,09% + const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); + + // 0,5% slippage + const params = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256'], + [[dai.address, weth.address], 50] + ); + + await expect( + pool + .connect(user) + .flashLoan( + uniswapLiquiditySwapAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + }); + + it('should revert if caller not lending pool', async () => { + const {users, weth, oracle, dai, aWETH, uniswapLiquiditySwapAdapter} = 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) + ); + + await mockUniswapRouter.setAmountToReturn(expectedDaiAmount); + + // User will swap liquidity 10 aEth to aDai + const liquidityToSwap = parseEther('10'); + await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); + + // Subtract the FL fee from the amount to be swapped 0,09% + const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); + + // 0,5% slippage + const params = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256'], + [[dai.address, weth.address], 50] + ); + + await expect( + uniswapLiquiditySwapAdapter + .connect(user) + .executeOperation( + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params + ) + ).to.be.revertedWith('CALLER_MUST_BE_LENDING_POOL'); + }); + it('should work correctly with tokens of different decimals', async () => { const { users, @@ -215,8 +299,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { // 0,5% slippage const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256'], - [dai.address, userAddress, 50] + ['address[]', 'uint256'], + [[dai.address], 50] ); await expect( @@ -226,7 +310,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapLiquiditySwapAdapter.address, [usdc.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -275,14 +359,14 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { // 30% slippage const params1 = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256'], - [dai.address, userAddress, 3000] + ['address[]', 'uint256'], + [[dai.address], 3000] ); // 0,05% slippage const params2 = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256'], - [dai.address, userAddress, 5] + ['address[]', 'uint256'], + [[dai.address], 5] ); await expect( @@ -292,7 +376,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params1, 0 @@ -305,62 +389,13 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params2, 0 ) ).to.be.revertedWith('SLIPPAGE_OUT_OF_RANGE'); }); - - it('should revert when swap exceed slippage', async () => { - const {users, weth, oracle, dai, aWETH, pool, uniswapLiquiditySwapAdapter} = testEnv; - const user = users[0].signer; - const userAddress = users[0].address; - - const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); - - await weth.connect(user).mint(amountWETHtoSwap); - await weth.connect(user).transfer(uniswapLiquiditySwapAdapter.address, amountWETHtoSwap); - - const daiPrice = await oracle.getAssetPrice(dai.address); - const expectedDaiAmount = await convertToCurrencyDecimals( - dai.address, - new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) - ); - - // 1,5% slippage - const returnedDaiAmountWithBigSlippage = new BigNumber(expectedDaiAmount.toString()) - .multipliedBy(0.985) - .toFixed(0); - await mockUniswapRouter.connect(user).setAmountToReturn(returnedDaiAmountWithBigSlippage); - - // User will swap liquidity 10 aEth to aDai - const liquidityToSwap = parseEther('10'); - await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); - // Subtract the FL fee from the amount to be swapped 0,09% - const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); - - // 0,5% slippage - const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256'], - [dai.address, userAddress, 50] - ); - - await expect( - pool - .connect(user) - .flashLoan( - uniswapLiquiditySwapAdapter.address, - [weth.address], - [flashloanAmount.toString()], - 0, - userAddress, - params, - 0 - ) - ).to.be.revertedWith('INSUFFICIENT_OUTPUT_AMOUNT'); - }); }); describe('swapAndDeposit', () => { @@ -400,13 +435,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); await expect( - uniswapLiquiditySwapAdapter.swapAndDeposit( - weth.address, - dai.address, - amountWETHtoSwap, - userAddress, - 50 - ) + uniswapLiquiditySwapAdapter + .connect(user) + .swapAndDeposit([weth.address], [dai.address], [amountWETHtoSwap], 50) ) .to.emit(uniswapLiquiditySwapAdapter, 'Swapped') .withArgs(weth.address, dai.address, amountWETHtoSwap.toString(), expectedDaiAmount); @@ -427,6 +458,30 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); }); + it('should revert if inconsistent params', async () => { + const {users, weth, dai, uniswapLiquiditySwapAdapter} = testEnv; + const user = users[0].signer; + + const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); + + await expect( + uniswapLiquiditySwapAdapter + .connect(user) + .swapAndDeposit([weth.address, dai.address], [dai.address], [amountWETHtoSwap], 50) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + + await expect( + uniswapLiquiditySwapAdapter + .connect(user) + .swapAndDeposit([weth.address], [dai.address, weth.address], [amountWETHtoSwap], 50) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + + await expect( + uniswapLiquiditySwapAdapter + .connect(user) + .swapAndDeposit([weth.address], [dai.address], [amountWETHtoSwap, amountWETHtoSwap], 50) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + }); }); }); @@ -506,8 +561,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] ); await expect( @@ -517,7 +572,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -539,6 +594,132 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); }); + it('should revert if inconsistent params', async () => { + const {users, pool, weth, aWETH, oracle, dai, uniswapRepayAdapter} = 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 liquidityToSwap = amountWETHtoSwap; + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + + // 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(flashloanAmount); + await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); + + const params1 = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address, weth.address], 0, [expectedDaiAmount], [1]] + ); + + await expect( + pool + .connect(user) + .flashLoan( + uniswapRepayAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params1, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + + const params2 = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount, expectedDaiAmount], [1]] + ); + + await expect( + pool + .connect(user) + .flashLoan( + uniswapRepayAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params2, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + + const params3 = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1, 1]] + ); + + await expect( + pool + .connect(user) + .flashLoan( + uniswapRepayAdapter.address, + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params3, + 0 + ) + ).to.be.revertedWith('INCONSISTENT_PARAMS'); + }); + + it('should revert if caller not lending pool', async () => { + const {users, pool, weth, aWETH, oracle, dai, uniswapRepayAdapter} = 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 liquidityToSwap = amountWETHtoSwap; + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + + // 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(flashloanAmount); + await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); + + const params = ethers.utils.defaultAbiCoder.encode( + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] + ); + + await expect( + uniswapRepayAdapter + .connect(user) + .executeOperation( + [weth.address], + [flashloanAmount.toString()], + [0], + userAddress, + params + ) + ).to.be.revertedWith('CALLER_MUST_BE_LENDING_POOL'); + }); + it('should revert if there is not debt to repay with the specified rate mode', async () => { const {users, pool, weth, oracle, dai, uniswapRepayAdapter, aWETH} = testEnv; const user = users[0].signer; @@ -568,8 +749,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] ); await expect( @@ -579,7 +760,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -613,8 +794,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] ); await expect( @@ -624,7 +805,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -632,55 +813,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { ).to.be.reverted; }); - it('should revert when the received amount is less than expected', async () => { - const {users, pool, weth, oracle, dai, aWETH, uniswapRepayAdapter} = 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 liquidityToSwap = amountWETHtoSwap; - await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); - - // Subtract the FL fee from the amount to be swapped 0,09% - const flashloanAmount = new BigNumber(liquidityToSwap.toString()).div(1.0009).toFixed(0); - - const insufficientOutput = new BigNumber(expectedDaiAmount.toString()) - .multipliedBy(0.985) - .toFixed(0); - - await mockUniswapRouter.connect(user).setAmountToSwap(flashloanAmount); - await mockUniswapRouter.connect(user).setAmountToReturn(insufficientOutput); - - const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] - ); - - await expect( - pool - .connect(user) - .flashLoan( - uniswapRepayAdapter.address, - [weth.address], - [flashloanAmount.toString()], - 0, - userAddress, - params, - 0 - ) - ).to.be.revertedWith('INSUFFICIENT_OUTPUT_AMOUNT'); - }); - it('should revert when max amount allowed to swap is bigger than max slippage', async () => { const {users, pool, weth, oracle, dai, aWETH, uniswapRepayAdapter} = testEnv; const user = users[0].signer; @@ -707,8 +839,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] ); await expect( @@ -718,7 +850,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -779,8 +911,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await mockUniswapRouter.connect(user).setAmountToReturn(expectedDaiAmount); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 0, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 0, [expectedDaiAmount], [1]] ); await expect( @@ -790,7 +922,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0 @@ -870,8 +1002,8 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { const wethBalanceBefore = await weth.balanceOf(userAddress); const params = ethers.utils.defaultAbiCoder.encode( - ['address', 'address', 'uint256', 'uint256', 'uint256'], - [dai.address, userAddress, 1, expectedDaiAmount, 1] + ['address[]', 'uint256', 'uint256[]', 'uint256[]'], + [[dai.address], 1, [expectedDaiAmount], [1]] ); await expect( @@ -881,7 +1013,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], - 0, + [0], userAddress, params, 0