From bc969d926e27c6568d1967635529a482b0e5657d Mon Sep 17 00:00:00 2001 From: Gerardo Nardelli Date: Wed, 25 Nov 2020 14:55:46 -0300 Subject: [PATCH] Add swapAndRepay unit tests --- test/uniswapAdapters.spec.ts | 533 ++++++++++++++++++++++++++++++++--- 1 file changed, 495 insertions(+), 38 deletions(-) diff --git a/test/uniswapAdapters.spec.ts b/test/uniswapAdapters.spec.ts index eb3d6cc5..f65f2402 100644 --- a/test/uniswapAdapters.spec.ts +++ b/test/uniswapAdapters.spec.ts @@ -1996,6 +1996,42 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { }); describe('UniswapRepayAdapter', () => { + beforeEach(async () => { + const { users, weth, dai, usdc, aave, 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); + + const usdcLiquidity = await convertToCurrencyDecimals(usdc.address, '2000000'); + await usdc.mint(usdcLiquidity); + await usdc.approve(pool.address, usdcLiquidity); + await pool.deposit(usdc.address, usdcLiquidity, deployer.address, 0); + + await weth.mint(parseEther('100')); + await weth.approve(pool.address, parseEther('100')); + await pool.deposit(weth.address, parseEther('100'), deployer.address, 0); + + await aave.mint(parseEther('1000000')); + await aave.approve(pool.address, parseEther('1000000')); + await pool.deposit(aave.address, parseEther('1000000'), deployer.address, 0); + + // Make a deposit for user + await weth.mint(parseEther('1000')); + await weth.approve(pool.address, parseEther('1000')); + await pool.deposit(weth.address, parseEther('1000'), userAddress, 0); + + await aave.mint(parseEther('1000000')); + await aave.approve(pool.address, parseEther('1000000')); + await pool.deposit(aave.address, parseEther('1000000'), userAddress, 0); + + await usdc.mint(usdcLiquidity); + await usdc.approve(pool.address, usdcLiquidity); + await pool.deposit(usdc.address, usdcLiquidity, userAddress, 0); + }); + describe('constructor', () => { it('should deploy with correct parameters', async () => { const { addressesProvider } = testEnv; @@ -2009,42 +2045,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { }); describe('executeOperation', () => { - beforeEach(async () => { - const { users, weth, dai, usdc, aave, 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); - - const usdcLiquidity = await convertToCurrencyDecimals(usdc.address, '2000000'); - await usdc.mint(usdcLiquidity); - await usdc.approve(pool.address, usdcLiquidity); - await pool.deposit(usdc.address, usdcLiquidity, deployer.address, 0); - - await weth.mint(parseEther('100')); - await weth.approve(pool.address, parseEther('100')); - await pool.deposit(weth.address, parseEther('100'), deployer.address, 0); - - await aave.mint(parseEther('1000000')); - await aave.approve(pool.address, parseEther('1000000')); - await pool.deposit(aave.address, parseEther('1000000'), deployer.address, 0); - - // Make a deposit for user - await weth.mint(parseEther('1000')); - await weth.approve(pool.address, parseEther('1000')); - await pool.deposit(weth.address, parseEther('1000'), userAddress, 0); - - await aave.mint(parseEther('1000000')); - await aave.approve(pool.address, parseEther('1000000')); - await pool.deposit(aave.address, parseEther('1000000'), userAddress, 0); - - await usdc.mint(usdcLiquidity); - await usdc.approve(pool.address, usdcLiquidity); - await pool.deposit(usdc.address, usdcLiquidity, userAddress, 0); - }); - it('should correctly swap tokens and repay debt', async () => { const { users, @@ -2554,7 +2554,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userWethBalance).to.be.eq(userWethBalanceBefore); }); - it('should correctly swap tokens and repay the whole stable debt with no leftovers', async () => { + it('should correctly swap tokens and repay the whole stable debt', async () => { const { users, pool, @@ -2644,7 +2644,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); }); - it('should correctly swap tokens and repay the whole variable debt with no leftovers', async () => { + it('should correctly swap tokens and repay the whole variable debt', async () => { const { users, pool, @@ -2736,5 +2736,462 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => { expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); }); }); + + describe('swapAndRepay', () => { + it('should correctly swap tokens and repay 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); + + await mockUniswapRouter.setAmountToSwap(weth.address, liquidityToSwap); + + await mockUniswapRouter.setDefaultMockValue(liquidityToSwap); + + await uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, expectedDaiAmount, 1, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }); + + 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.lt(expectedDaiAmount); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); + + it('should correctly swap tokens and repay debt with permit', 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; + const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); + + await mockUniswapRouter.setAmountToSwap(weth.address, liquidityToSwap); + + await mockUniswapRouter.setDefaultMockValue(liquidityToSwap); + + const chainId = DRE.network.config.chainId || BUIDLEREVM_CHAINID; + const deadline = MAX_UINT_AMOUNT; + const nonce = (await aWETH._nonces(userAddress)).toNumber(); + const msgParams = buildPermitParams( + chainId, + aWETH.address, + '1', + await aWETH.name(), + userAddress, + uniswapRepayAdapter.address, + nonce, + deadline, + liquidityToSwap.toString() + ); + + const ownerPrivateKey = require('../test-wallets.js').accounts[1].secretKey; + if (!ownerPrivateKey) { + throw new Error('INVALID_OWNER_PK'); + } + + const { v, r, s } = getSignatureFromTypedData(ownerPrivateKey, msgParams); + + await uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, expectedDaiAmount, 1, { + amount: liquidityToSwap, + deadline, + v, + r, + s, + }); + + 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.lt(expectedDaiAmount); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); + + it('should revert if there is not debt to repay', async () => { + const { users, weth, aWETH, oracle, dai, uniswapRepayAdapter } = testEnv; + const user = users[0].signer; + + 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) + ); + + const liquidityToSwap = amountWETHtoSwap; + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + + await mockUniswapRouter.setAmountToSwap(weth.address, liquidityToSwap); + + await mockUniswapRouter.setDefaultMockValue(liquidityToSwap); + + await expect( + uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, expectedDaiAmount, 1, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }) + ).to.be.reverted; + }); + + it('should revert when max amount allowed to swap is bigger than max slippage', 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 bigMaxAmountToSwap = amountWETHtoSwap.mul(2); + await aWETH.connect(user).approve(uniswapRepayAdapter.address, bigMaxAmountToSwap); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, bigMaxAmountToSwap); + + await mockUniswapRouter.setDefaultMockValue(bigMaxAmountToSwap); + + await expect( + uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, bigMaxAmountToSwap, expectedDaiAmount, 1, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }) + ).to.be.revertedWith('maxAmountToSwap exceed max slippage'); + }); + + it('should swap, repay debt and pull the needed ATokens leaving no leftovers', 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); + const userWethBalanceBefore = await weth.balanceOf(userAddress); + + const actualWEthSwapped = new BigNumber(liquidityToSwap.toString()) + .multipliedBy(0.995) + .toFixed(0); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, actualWEthSwapped); + + await mockUniswapRouter.setDefaultMockValue(actualWEthSwapped); + + await uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, expectedDaiAmount, 1, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }); + + 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 adapterAEthBalance = await aWETH.balanceOf(uniswapRepayAdapter.address); + const userWethBalance = await weth.balanceOf(userAddress); + + expect(adapterAEthBalance).to.be.eq(Zero); + 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.eq(userAEthBalanceBefore.sub(actualWEthSwapped)); + expect(userWethBalance).to.be.eq(userWethBalanceBefore); + }); + + 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); + + // Add a % to repay on top of the debt + const liquidityToSwap = new BigNumber(amountWETHtoSwap.toString()) + .multipliedBy(1.1) + .toFixed(0); + + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); + + // Add a % to repay on top of the debt + const amountToRepay = new BigNumber(expectedDaiAmount.toString()) + .multipliedBy(1.1) + .toFixed(0); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, amountWETHtoSwap); + await mockUniswapRouter.setDefaultMockValue(amountWETHtoSwap); + + await uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, amountToRepay, 1, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }); + + 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 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); + 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 + ); + + // Add a % to repay on top of the debt + const liquidityToSwap = new BigNumber(amountWETHtoSwap.toString()) + .multipliedBy(1.1) + .toFixed(0); + + await aWETH.connect(user).approve(uniswapRepayAdapter.address, liquidityToSwap); + const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); + + // Add a % to repay on top of the debt + const amountToRepay = new BigNumber(expectedDaiAmount.toString()) + .multipliedBy(1.1) + .toFixed(0); + + await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, amountWETHtoSwap); + await mockUniswapRouter.setDefaultMockValue(amountWETHtoSwap); + + await uniswapRepayAdapter + .connect(user) + .swapAndRepay(weth.address, dai.address, liquidityToSwap, amountToRepay, 2, { + amount: 0, + deadline: 0, + v: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }); + + 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); + 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); + expect(userDaiVariableDebtAmount).to.be.eq(Zero); + expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); + expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(liquidityToSwap)); + }); + }); }); });