From 16a28d6223896998ae444ab0fc795d0587704842 Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Tue, 27 Oct 2020 16:33:07 -0300 Subject: [PATCH] Add swapAndDeposit method to use without flashloan --- .../adapters/UniswapLiquiditySwapAdapter.sol | 27 ++++++ test/uniswapAdapters.spec.ts | 96 +++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/contracts/adapters/UniswapLiquiditySwapAdapter.sol b/contracts/adapters/UniswapLiquiditySwapAdapter.sol index 4cfd156a..d44f50fd 100644 --- a/contracts/adapters/UniswapLiquiditySwapAdapter.sol +++ b/contracts/adapters/UniswapLiquiditySwapAdapter.sol @@ -60,4 +60,31 @@ contract UniswapLiquiditySwapAdapter is BaseUniswapAdapter, IFlashLoanReceiver { return true; } + + /** + * @dev Swaps an `amountToSwap` of an asset to another and deposits the funds on behalf of the user without using a flashloan. + * 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 slippage The max slippage percentage allowed for the swap + */ + function swapAndDeposit( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + address user, + uint256 slippage + ) external { + pullAToken(assetToSwapFrom, user, amountToSwap); + + uint256 receivedAmount = swapExactTokensForTokens(assetToSwapFrom, assetToSwapTo, amountToSwap, slippage); + + // Deposit new reserve + IERC20(assetToSwapTo).approve(address(pool), receivedAmount); + pool.deposit(assetToSwapTo, receivedAmount, user, 0); + } } diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index 18b78be9..6fe92b8a 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -34,6 +34,36 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { await evmRevert(evmSnapshotId); }); + describe('BaseUniswapAdapter', () => { + describe('getAmountOut', () => { + it('should return the estimated amountOut for the asset swap', async () => { + const {weth, dai, uniswapLiquiditySwapAdapter} = testEnv; + const amountIn = parseEther('1'); + const amountOut = parseEther('2'); + + await mockUniswapRouter.setAmountOut(amountIn, weth.address, dai.address, amountOut); + + expect( + await uniswapLiquiditySwapAdapter.getAmountOut(amountIn, weth.address, dai.address) + ).to.be.eq(amountOut); + }); + }); + + describe('getAmountIn', () => { + it('should return the estimated required amountIn for the asset swap', async () => { + const {weth, dai, uniswapLiquiditySwapAdapter} = testEnv; + const amountIn = parseEther('1'); + const amountOut = parseEther('2'); + + await mockUniswapRouter.setAmountIn(amountOut, weth.address, dai.address, amountIn); + + expect( + await uniswapLiquiditySwapAdapter.getAmountIn(amountOut, weth.address, dai.address) + ).to.be.eq(amountIn); + }); + }); + }); + describe('UniswapLiquiditySwapAdapter', () => { describe('constructor', () => { it('should deploy with correct parameters', async () => { @@ -328,6 +358,72 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { ).to.be.revertedWith('INSUFFICIENT_OUTPUT_AMOUNT'); }); }); + + describe('swapAndDeposit', () => { + beforeEach(async () => { + const {users, weth, dai, pool, deployer} = testEnv; + const userAddress = users[0].address; + + // Provide liquidity + await dai.mint(parseEther('20000')); + await dai.approve(pool.address, parseEther('20000')); + await pool.deposit(dai.address, parseEther('20000'), deployer.address, 0); + + // Make a deposit for user + await weth.mint(parseEther('100')); + await weth.approve(pool.address, parseEther('100')); + await pool.deposit(weth.address, parseEther('100'), userAddress, 0); + }); + + it('should correctly swap tokens and deposit the out tokens in the pool', async () => { + const {users, weth, oracle, dai, aDai, aEth, 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 aEth.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); + const userAEthBalanceBefore = await aEth.balanceOf(userAddress); + + await expect( + uniswapLiquiditySwapAdapter.swapAndDeposit( + weth.address, + dai.address, + amountWETHtoSwap, + userAddress, + 50 + ) + ) + .to.emit(uniswapLiquiditySwapAdapter, 'Swapped') + .withArgs(weth.address, dai.address, amountWETHtoSwap.toString(), expectedDaiAmount); + + const adapterWethBalance = await weth.balanceOf(uniswapLiquiditySwapAdapter.address); + const adapterDaiBalance = await dai.balanceOf(uniswapLiquiditySwapAdapter.address); + const adapterDaiAllowance = await dai.allowance( + uniswapLiquiditySwapAdapter.address, + userAddress + ); + const userADaiBalance = await aDai.balanceOf(userAddress); + const userAEthBalance = await aEth.balanceOf(userAddress); + + expect(adapterWethBalance).to.be.eq(Zero); + expect(adapterDaiBalance).to.be.eq(Zero); + expect(adapterDaiAllowance).to.be.eq(Zero); + expect(userADaiBalance).to.be.eq(expectedDaiAmount); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); + }); }); describe('UniswapRepayAdapter', () => {