import {makeSuite, TestEnv} from './helpers/make-suite'; import { convertToCurrencyDecimals, getContract, buildPermitParams, getSignatureFromTypedData, } from '../helpers/contracts-helpers'; import {getMockUniswapRouter} from '../helpers/contracts-getters'; import { deployUniswapLiquiditySwapAdapter, deployUniswapRepayAdapter, } from '../helpers/contracts-deployments'; import {MockUniswapV2Router02} from '../types/MockUniswapV2Router02'; import {Zero} from '@ethersproject/constants'; import BigNumber from 'bignumber.js'; import {BRE, evmRevert, evmSnapshot} from '../helpers/misc-utils'; import {ethers} from 'ethers'; import {eContractid} from '../helpers/types'; import {AToken} from '../types/AToken'; import {StableDebtToken} from '../types/StableDebtToken'; import {BUIDLEREVM_CHAINID} from '../helpers/buidler-constants'; import {MAX_UINT_AMOUNT, USD_ADDRESS} from '../helpers/constants'; const {parseEther} = ethers.utils; const {expect} = require('chai'); makeSuite('Uniswap adapters', (testEnv: TestEnv) => { let mockUniswapRouter: MockUniswapV2Router02; let evmSnapshotId: string; before(async () => { mockUniswapRouter = await getMockUniswapRouter(); }); beforeEach(async () => { evmSnapshotId = await evmSnapshot(); }); afterEach(async () => { await evmRevert(evmSnapshotId); }); describe('BaseUniswapAdapter', () => { describe('getAmountsOut', () => { it('should return the estimated amountOut and prices for the asset swap', async () => { const {weth, dai, uniswapLiquiditySwapAdapter, oracle} = testEnv; const amountIn = parseEther('1'); const flashloanPremium = amountIn.mul(9).div(10000); const amountToSwap = amountIn.sub(flashloanPremium); const wethPrice = await oracle.getAssetPrice(weth.address); const daiPrice = await oracle.getAssetPrice(dai.address); const usdPrice = await oracle.getAssetPrice(USD_ADDRESS); const expectedDaiAmount = await convertToCurrencyDecimals( dai.address, new BigNumber(amountToSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const outPerInPrice = amountToSwap .mul(parseEther('1')) .mul(parseEther('1')) .div(expectedDaiAmount.mul(parseEther('1'))); const ethUsdValue = amountIn .mul(wethPrice) .div(parseEther('1')) .mul(usdPrice) .div(parseEther('1')); const daiUsdValue = expectedDaiAmount .mul(daiPrice) .div(parseEther('1')) .mul(usdPrice) .div(parseEther('1')); await mockUniswapRouter.setAmountOut( amountToSwap, weth.address, dai.address, expectedDaiAmount ); const result = await uniswapLiquiditySwapAdapter.getAmountsOut( amountIn, weth.address, dai.address ); expect(result['0']).to.be.eq(expectedDaiAmount); expect(result['1']).to.be.eq(outPerInPrice); expect(result['2']).to.be.eq(ethUsdValue); expect(result['3']).to.be.eq(daiUsdValue); }); it('should work correctly with different decimals', async () => { const {lend, usdc, uniswapLiquiditySwapAdapter, oracle} = testEnv; const amountIn = parseEther('10'); const flashloanPremium = amountIn.mul(9).div(10000); const amountToSwap = amountIn.sub(flashloanPremium); const lendPrice = await oracle.getAssetPrice(lend.address); const usdcPrice = await oracle.getAssetPrice(usdc.address); const usdPrice = await oracle.getAssetPrice(USD_ADDRESS); const expectedUSDCAmount = await convertToCurrencyDecimals( usdc.address, new BigNumber(amountToSwap.toString()).div(usdcPrice.toString()).toFixed(0) ); const outPerInPrice = amountToSwap .mul(parseEther('1')) .mul('1000000') .div(expectedUSDCAmount.mul(parseEther('1'))); const lendUsdValue = amountIn .mul(lendPrice) .div(parseEther('1')) .mul(usdPrice) .div(parseEther('1')); const usdcUsdValue = expectedUSDCAmount .mul(usdcPrice) .div('1000000') .mul(usdPrice) .div(parseEther('1')); await mockUniswapRouter.setAmountOut( amountToSwap, lend.address, usdc.address, expectedUSDCAmount ); const result = await uniswapLiquiditySwapAdapter.getAmountsOut( amountIn, lend.address, usdc.address ); expect(result['0']).to.be.eq(expectedUSDCAmount); expect(result['1']).to.be.eq(outPerInPrice); expect(result['2']).to.be.eq(lendUsdValue); expect(result['3']).to.be.eq(usdcUsdValue); }); }); 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 () => { const {addressesProvider} = testEnv; await deployUniswapLiquiditySwapAdapter([ addressesProvider.address, mockUniswapRouter.address, ]); }); it('should revert if not valid addresses provider', async () => { expect( deployUniswapLiquiditySwapAdapter([mockUniswapRouter.address, mockUniswapRouter.address]) ).to.be.reverted; }); }); describe('executeOperation', () => { beforeEach(async () => { const {users, weth, dai, usdc, 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 usdcAmount = await convertToCurrencyDecimals(usdc.address, '10'); await usdc.mint(usdcAmount); await usdc.approve(pool.address, usdcAmount); await pool.deposit(usdc.address, usdcAmount, 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, 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(weth.address, expectedDaiAmount); // User will swap liquidity 10 aEth to aDai const liquidityToSwap = parseEther('10'); await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.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 params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address], [expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ) .to.emit(uniswapLiquiditySwapAdapter, 'Swapped') .withArgs(weth.address, dai.address, flashloanAmount.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 aWETH.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)); }); it('should correctly swap and deposit multiple tokens', async () => { const { users, weth, oracle, dai, aDai, aWETH, usdc, 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 expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const amountUSDCtoSwap = await convertToCurrencyDecimals(usdc.address, '10'); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await usdc.decimals()).toString(); const principalDecimals = (await dai.decimals()).toString(); const expectedDaiAmountForUsdc = await convertToCurrencyDecimals( dai.address, new BigNumber(amountUSDCtoSwap.toString()) .times( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(daiPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Make a deposit for user await usdc.connect(user).mint(amountUSDCtoSwap); await usdc.connect(user).approve(pool.address, amountUSDCtoSwap); await pool.connect(user).deposit(usdc.address, amountUSDCtoSwap, userAddress, 0); const aUsdcData = await pool.getReserveData(usdc.address); const aUsdc = await getContract(eContractid.AToken, aUsdcData.aTokenAddress); await mockUniswapRouter.setAmountToReturn(weth.address, expectedDaiAmountForEth); await mockUniswapRouter.setAmountToReturn(usdc.address, expectedDaiAmountForUsdc); await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, amountWETHtoSwap); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); await aUsdc.connect(user).approve(uniswapLiquiditySwapAdapter.address, amountUSDCtoSwap); const userAUsdcBalanceBefore = await aUsdc.balanceOf(userAddress); // Subtract the FL fee from the amount to be swapped 0,09% const wethFlashloanAmount = new BigNumber(amountWETHtoSwap.toString()) .div(1.0009) .toFixed(0); const usdcFlashloanAmount = new BigNumber(amountUSDCtoSwap.toString()) .div(1.0009) .toFixed(0); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, dai.address], [expectedDaiAmountForEth, expectedDaiAmountForUsdc], [0, 0], [0, 0], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ] ); await pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address, usdc.address], [wethFlashloanAmount.toString(), usdcFlashloanAmount.toString()], [0, 0], userAddress, params, 0 ); 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 aWETH.balanceOf(userAddress); const userAUsdcBalance = await aUsdc.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(expectedDaiAmountForEth.add(expectedDaiAmountForUsdc)); expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(amountWETHtoSwap)); expect(userAUsdcBalance).to.be.lt(userAUsdcBalanceBefore); expect(userAUsdcBalance).to.be.gte(userAUsdcBalanceBefore.sub(amountUSDCtoSwap)); }); it('should correctly swap and deposit multiple tokens using permit', async () => { const { users, weth, oracle, dai, aDai, aWETH, usdc, pool, uniswapLiquiditySwapAdapter, } = testEnv; const user = users[0].signer; const userAddress = users[0].address; const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID; const deadline = MAX_UINT_AMOUNT; const ownerPrivateKey = require('../test-wallets.js').accounts[1].secretKey; if (!ownerPrivateKey) { throw new Error('INVALID_OWNER_PK'); } const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); const daiPrice = await oracle.getAssetPrice(dai.address); const expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const amountUSDCtoSwap = await convertToCurrencyDecimals(usdc.address, '10'); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await usdc.decimals()).toString(); const principalDecimals = (await dai.decimals()).toString(); const expectedDaiAmountForUsdc = await convertToCurrencyDecimals( dai.address, new BigNumber(amountUSDCtoSwap.toString()) .times( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(daiPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Make a deposit for user await usdc.connect(user).mint(amountUSDCtoSwap); await usdc.connect(user).approve(pool.address, amountUSDCtoSwap); await pool.connect(user).deposit(usdc.address, amountUSDCtoSwap, userAddress, 0); const aUsdcData = await pool.getReserveData(usdc.address); const aUsdc = await getContract(eContractid.AToken, aUsdcData.aTokenAddress); await mockUniswapRouter.setAmountToReturn(weth.address, expectedDaiAmountForEth); await mockUniswapRouter.setAmountToReturn(usdc.address, expectedDaiAmountForUsdc); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); const userAUsdcBalanceBefore = await aUsdc.balanceOf(userAddress); // IMPORTANT: Round down to work equal to solidity to get the correct value for permit call BigNumber.config({ ROUNDING_MODE: 1, //round down }); const wethFlashloanAmountBN = new BigNumber(amountWETHtoSwap.toString()).div(1.0009); const wethFlashloanAmount = wethFlashloanAmountBN.toFixed(0); const wethFlashloanFee = wethFlashloanAmountBN.multipliedBy(9).div(10000); const wethAmountToPermit = wethFlashloanAmountBN.plus(wethFlashloanFee).toFixed(0); const usdcFlashloanAmountBN = new BigNumber(amountUSDCtoSwap.toString()).div(1.0009); const usdcFlashloanAmount = usdcFlashloanAmountBN.toFixed(0); const usdcFlashloanFee = usdcFlashloanAmountBN.multipliedBy(9).div(10000); const usdcAmountToPermit = usdcFlashloanAmountBN.plus(usdcFlashloanFee).toFixed(0); const aWethNonce = (await aWETH._nonces(userAddress)).toNumber(); const aWethMsgParams = buildPermitParams( chainId, aWETH.address, '1', await aWETH.name(), userAddress, uniswapLiquiditySwapAdapter.address, aWethNonce, deadline, wethAmountToPermit.toString() ); const {v: aWETHv, r: aWETHr, s: aWETHs} = getSignatureFromTypedData( ownerPrivateKey, aWethMsgParams ); const aUsdcNonce = (await aUsdc._nonces(userAddress)).toNumber(); const aUsdcMsgParams = buildPermitParams( chainId, aUsdc.address, '1', await aUsdc.name(), userAddress, uniswapLiquiditySwapAdapter.address, aUsdcNonce, deadline, usdcAmountToPermit.toString() ); const {v: aUsdcv, r: aUsdcr, s: aUsdcs} = getSignatureFromTypedData( ownerPrivateKey, aUsdcMsgParams ); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, dai.address], [expectedDaiAmountForEth, expectedDaiAmountForUsdc], [deadline, deadline], [aWETHv, aUsdcv], [aWETHr, aUsdcr], [aWETHs, aUsdcs], ] ); await pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address, usdc.address], [wethFlashloanAmount.toString(), usdcFlashloanAmount.toString()], [0, 0], userAddress, params, 0 ); 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 aWETH.balanceOf(userAddress); const userAUsdcBalance = await aUsdc.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(expectedDaiAmountForEth.add(expectedDaiAmountForUsdc)); expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(amountWETHtoSwap)); expect(userAUsdcBalance).to.be.lt(userAUsdcBalanceBefore); expect(userAUsdcBalance).to.be.gte(userAUsdcBalanceBefore.sub(amountUSDCtoSwap)); // Restore round up BigNumber.config({ ROUNDING_MODE: 0, //round up }); }); it('should correctly swap tokens with permit', async () => { const {users, weth, oracle, dai, aDai, 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(weth.address, expectedDaiAmount); // User will swap liquidity 10 aEth to aDai const liquidityToSwap = parseEther('10'); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); // IMPORTANT: Round down to work equal to solidity to get the correct value for permit call BigNumber.config({ ROUNDING_MODE: 1, //round down }); // Subtract the FL fee from the amount to be swapped 0,09% const flashloanAmountBN = new BigNumber(liquidityToSwap.toString()).div(1.0009); const flashloanAmount = flashloanAmountBN.toFixed(0); const flashloanFee = flashloanAmountBN.multipliedBy(9).div(10000); const amountToPermit = flashloanAmountBN.plus(flashloanFee); const chainId = BRE.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, uniswapLiquiditySwapAdapter.address, nonce, deadline, amountToPermit.toFixed(0).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); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [[dai.address], [expectedDaiAmount], [deadline], [v], [r], [s]] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ) .to.emit(uniswapLiquiditySwapAdapter, 'Swapped') .withArgs(weth.address, dai.address, flashloanAmount.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 aWETH.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)); // Restore round up BigNumber.config({ ROUNDING_MODE: 0, //round up }); }); 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(weth.address, 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); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params2 = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount], [0, 0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params2, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params3 = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount], [0], [0, 0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params3, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params4 = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount], [0], [0], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params4, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params5 = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params5, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params6 = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address, weth.address], [expectedDaiAmount, expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params6, 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(weth.address, 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); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address], [expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); 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, usdc, oracle, dai, aDai, uniswapLiquiditySwapAdapter, pool, deployer, } = testEnv; const user = users[0].signer; const userAddress = users[0].address; const amountUSDCtoSwap = await convertToCurrencyDecimals(usdc.address, '10'); const liquidity = await convertToCurrencyDecimals(usdc.address, '20000'); // Provide liquidity await usdc.mint(liquidity); await usdc.approve(pool.address, liquidity); await pool.deposit(usdc.address, liquidity, deployer.address, 0); // Make a deposit for user await usdc.connect(user).mint(amountUSDCtoSwap); await usdc.connect(user).approve(pool.address, amountUSDCtoSwap); await pool.connect(user).deposit(usdc.address, amountUSDCtoSwap, userAddress, 0); const usdcPrice = await oracle.getAssetPrice(usdc.address); const daiPrice = await oracle.getAssetPrice(dai.address); // usdc 6 const collateralDecimals = (await usdc.decimals()).toString(); const principalDecimals = (await dai.decimals()).toString(); const expectedDaiAmount = await convertToCurrencyDecimals( dai.address, new BigNumber(amountUSDCtoSwap.toString()) .times( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(daiPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); await mockUniswapRouter.connect(user).setAmountToReturn(usdc.address, expectedDaiAmount); const aUsdcData = await pool.getReserveData(usdc.address); const aUsdc = await getContract(eContractid.AToken, aUsdcData.aTokenAddress); const aUsdcBalance = await aUsdc.balanceOf(userAddress); await aUsdc.connect(user).approve(uniswapLiquiditySwapAdapter.address, aUsdcBalance); // Subtract the FL fee from the amount to be swapped 0,09% const flashloanAmount = new BigNumber(amountUSDCtoSwap.toString()).div(1.0009).toFixed(0); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address], [expectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [usdc.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ) .to.emit(uniswapLiquiditySwapAdapter, 'Swapped') .withArgs(usdc.address, dai.address, flashloanAmount.toString(), expectedDaiAmount); const adapterUsdcBalance = await usdc.balanceOf(uniswapLiquiditySwapAdapter.address); const adapterDaiBalance = await dai.balanceOf(uniswapLiquiditySwapAdapter.address); const adapterDaiAllowance = await dai.allowance( uniswapLiquiditySwapAdapter.address, userAddress ); const aDaiBalance = await aDai.balanceOf(userAddress); expect(adapterUsdcBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(adapterDaiAllowance).to.be.eq(Zero); expect(aDaiBalance).to.be.eq(expectedDaiAmount); }); it('should revert when min amount to receive exceeds the max slippage amount', 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(weth.address, expectedDaiAmount); const smallExpectedDaiAmount = expectedDaiAmount.div(2); // 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); const params = ethers.utils.defaultAbiCoder.encode( ['address[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]'], [ [dai.address], [smallExpectedDaiAmount], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapLiquiditySwapAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ).to.be.revertedWith('minAmountOut exceed max slippage'); }); }); 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, 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(weth.address, expectedDaiAmount); // User will swap liquidity 10 aEth to aDai const liquidityToSwap = parseEther('10'); await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap], [expectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ) .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 aWETH.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)); }); it('should correctly swap tokens using permit', async () => { const {users, weth, oracle, dai, aDai, 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(weth.address, expectedDaiAmount); // User will swap liquidity 10 aEth to aDai const liquidityToSwap = parseEther('10'); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); const chainId = BRE.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, uniswapLiquiditySwapAdapter.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 expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap], [expectedDaiAmount], [ { deadline, v, r, s, }, ] ) ) .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 aWETH.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)); }); it('should revert if inconsistent params', async () => { const {users, weth, dai, uniswapLiquiditySwapAdapter, oracle} = 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) ); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address, dai.address], [dai.address], [amountWETHtoSwap], [expectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address, weth.address], [amountWETHtoSwap], [expectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap, amountWETHtoSwap], [expectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); await expect( uniswapLiquiditySwapAdapter .connect(user) .swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap], [expectedDaiAmount], [] ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap], [expectedDaiAmount, expectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); }); it('should revert when min amount to receive exceeds the max slippage amount', async () => { const {users, weth, oracle, dai, aWETH, uniswapLiquiditySwapAdapter} = 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) ); await mockUniswapRouter.setAmountToReturn(weth.address, expectedDaiAmount); const smallExpectedDaiAmount = expectedDaiAmount.div(2); // User will swap liquidity 10 aEth to aDai const liquidityToSwap = parseEther('10'); await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, liquidityToSwap); await expect( uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address], [dai.address], [amountWETHtoSwap], [smallExpectedDaiAmount], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ) ).to.be.revertedWith('minAmountOut exceed max slippage'); }); it('should correctly swap tokens and deposit multiple tokens', async () => { const { users, weth, usdc, oracle, dai, aDai, aWETH, uniswapLiquiditySwapAdapter, pool, } = 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 expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const amountUSDCtoSwap = await convertToCurrencyDecimals(usdc.address, '10'); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await usdc.decimals()).toString(); const principalDecimals = (await dai.decimals()).toString(); const expectedDaiAmountForUsdc = await convertToCurrencyDecimals( dai.address, new BigNumber(amountUSDCtoSwap.toString()) .times( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(daiPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Make a deposit for user await usdc.connect(user).mint(amountUSDCtoSwap); await usdc.connect(user).approve(pool.address, amountUSDCtoSwap); await pool.connect(user).deposit(usdc.address, amountUSDCtoSwap, userAddress, 0); const aUsdcData = await pool.getReserveData(usdc.address); const aUsdc = await getContract(eContractid.AToken, aUsdcData.aTokenAddress); await mockUniswapRouter.setAmountToReturn(weth.address, expectedDaiAmountForEth); await mockUniswapRouter.setAmountToReturn(usdc.address, expectedDaiAmountForUsdc); await aWETH.connect(user).approve(uniswapLiquiditySwapAdapter.address, amountWETHtoSwap); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); await aUsdc.connect(user).approve(uniswapLiquiditySwapAdapter.address, amountUSDCtoSwap); const userAUsdcBalanceBefore = await aUsdc.balanceOf(userAddress); await uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address, usdc.address], [dai.address, dai.address], [amountWETHtoSwap, amountUSDCtoSwap], [expectedDaiAmountForEth, expectedDaiAmountForUsdc], [ { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, { deadline: 0, v: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, ] ); 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 aWETH.balanceOf(userAddress); const userAUsdcBalance = await aUsdc.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(expectedDaiAmountForEth.add(expectedDaiAmountForUsdc)); expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(amountWETHtoSwap)); expect(userAUsdcBalance).to.be.lt(userAUsdcBalanceBefore); expect(userAUsdcBalance).to.be.gte(userAUsdcBalanceBefore.sub(amountUSDCtoSwap)); }); it('should correctly swap tokens and deposit multiple tokens using permit', async () => { const { users, weth, usdc, oracle, dai, aDai, aWETH, uniswapLiquiditySwapAdapter, pool, } = testEnv; const user = users[0].signer; const userAddress = users[0].address; const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID; const deadline = MAX_UINT_AMOUNT; const ownerPrivateKey = require('../test-wallets.js').accounts[1].secretKey; if (!ownerPrivateKey) { throw new Error('INVALID_OWNER_PK'); } const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); const daiPrice = await oracle.getAssetPrice(dai.address); const expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const amountUSDCtoSwap = await convertToCurrencyDecimals(usdc.address, '10'); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await usdc.decimals()).toString(); const principalDecimals = (await dai.decimals()).toString(); const expectedDaiAmountForUsdc = await convertToCurrencyDecimals( dai.address, new BigNumber(amountUSDCtoSwap.toString()) .times( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(daiPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Make a deposit for user await usdc.connect(user).mint(amountUSDCtoSwap); await usdc.connect(user).approve(pool.address, amountUSDCtoSwap); await pool.connect(user).deposit(usdc.address, amountUSDCtoSwap, userAddress, 0); const aUsdcData = await pool.getReserveData(usdc.address); const aUsdc = await getContract(eContractid.AToken, aUsdcData.aTokenAddress); await mockUniswapRouter.setAmountToReturn(weth.address, expectedDaiAmountForEth); await mockUniswapRouter.setAmountToReturn(usdc.address, expectedDaiAmountForUsdc); const userAEthBalanceBefore = await aWETH.balanceOf(userAddress); const userAUsdcBalanceBefore = await aUsdc.balanceOf(userAddress); const aWethNonce = (await aWETH._nonces(userAddress)).toNumber(); const aWethMsgParams = buildPermitParams( chainId, aWETH.address, '1', await aWETH.name(), userAddress, uniswapLiquiditySwapAdapter.address, aWethNonce, deadline, amountWETHtoSwap.toString() ); const {v: aWETHv, r: aWETHr, s: aWETHs} = getSignatureFromTypedData( ownerPrivateKey, aWethMsgParams ); const aUsdcNonce = (await aUsdc._nonces(userAddress)).toNumber(); const aUsdcMsgParams = buildPermitParams( chainId, aUsdc.address, '1', await aUsdc.name(), userAddress, uniswapLiquiditySwapAdapter.address, aUsdcNonce, deadline, amountUSDCtoSwap.toString() ); const {v: aUsdcv, r: aUsdcr, s: aUsdcs} = getSignatureFromTypedData( ownerPrivateKey, aUsdcMsgParams ); await uniswapLiquiditySwapAdapter.connect(user).swapAndDeposit( [weth.address, usdc.address], [dai.address, dai.address], [amountWETHtoSwap, amountUSDCtoSwap], [expectedDaiAmountForEth, expectedDaiAmountForUsdc], [ { deadline, v: aWETHv, r: aWETHr, s: aWETHs, }, { deadline, v: aUsdcv, r: aUsdcr, s: aUsdcs, }, ] ); 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 aWETH.balanceOf(userAddress); const userAUsdcBalance = await aUsdc.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(expectedDaiAmountForEth.add(expectedDaiAmountForUsdc)); expect(userAEthBalance).to.be.lt(userAEthBalanceBefore); expect(userAEthBalance).to.be.gte(userAEthBalanceBefore.sub(amountWETHtoSwap)); expect(userAUsdcBalance).to.be.lt(userAUsdcBalanceBefore); expect(userAUsdcBalance).to.be.gte(userAUsdcBalanceBefore.sub(amountUSDCtoSwap)); }); }); }); describe('UniswapRepayAdapter', () => { describe('constructor', () => { it('should deploy with correct parameters', async () => { const {addressesProvider} = testEnv; await deployUniswapRepayAdapter([addressesProvider.address, mockUniswapRouter.address]); }); it('should revert if not valid addresses provider', async () => { expect(deployUniswapRepayAdapter([mockUniswapRouter.address, mockUniswapRouter.address])).to .be.reverted; }); }); describe('executeOperation', () => { beforeEach(async () => { const {users, weth, dai, usdc, lend, 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, '20000'); 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 lend.mint(parseEther('1000000')); await lend.approve(pool.address, parseEther('1000000')); await pool.deposit(lend.address, parseEther('1000000'), 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); await lend.mint(parseEther('1000000')); await lend.approve(pool.address, parseEther('1000000')); await pool.deposit(lend.address, parseEther('1000000'), userAddress, 0); }); 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); // 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); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [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, flashloanAmount.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); 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); // IMPORTANT: Round down to work equal to solidity to get the correct value for permit call BigNumber.config({ ROUNDING_MODE: 1, //round down }); // Subtract the FL fee from the amount to be swapped 0,09% const flashloanAmountBN = new BigNumber(liquidityToSwap.toString()).div(1.0009); const flashloanAmount = flashloanAmountBN.toFixed(0); const flashloanFee = flashloanAmountBN.multipliedBy(9).div(10000); const amountToPermit = flashloanAmountBN.plus(flashloanFee); const chainId = BRE.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, amountToPermit.toFixed(0).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 mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [[dai.address], 0, [expectedDaiAmount], [1], [deadline], [v], [r], [s]] ); 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, flashloanAmount.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); 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)); // Restore round up BigNumber.config({ ROUNDING_MODE: 0, //round up }); }); it('should correctly swap tokens and repay debt for multiple tokens', async () => { const { users, pool, weth, oracle, dai, uniswapRepayAdapter, lend, usdc, helpersContract, aWETH, } = testEnv; const user = users[0].signer; const userAddress = users[0].address; const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); const amountLendToSwap = await convertToCurrencyDecimals(lend.address, '1'); const daiPrice = await oracle.getAssetPrice(dai.address); const expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const lendPrice = await oracle.getAssetPrice(lend.address); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await lend.decimals()).toString(); const principalDecimals = (await usdc.decimals()).toString(); const expectedUsdcAmountForLend = await convertToCurrencyDecimals( usdc.address, new BigNumber(amountLendToSwap.toString()) .times( new BigNumber(lendPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Open user Debt await pool.connect(user).borrow(dai.address, expectedDaiAmountForEth, 1, 0, userAddress); await pool.connect(user).borrow(usdc.address, expectedUsdcAmountForLend, 1, 0, userAddress); const daiStableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).stableDebtTokenAddress; const daiStableDebtContract = await getContract( eContractid.StableDebtToken, daiStableDebtTokenAddress ); const usdcStableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(usdc.address) ).stableDebtTokenAddress; const usdcStableDebtContract = await getContract( eContractid.StableDebtToken, usdcStableDebtTokenAddress ); const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress); const userUsdcStableDebtAmountBefore = await usdcStableDebtContract.balanceOf(userAddress); // Subtract the FL fee from the amount to be swapped 0,09% const wethFlashloanAmount = new BigNumber(amountWETHtoSwap.toString()) .div(1.0009) .toFixed(0); const lendFlashloanAmount = new BigNumber(amountLendToSwap.toString()) .div(1.0009) .toFixed(0); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, wethFlashloanAmount); await mockUniswapRouter.connect(user).setAmountToSwap(lend.address, lendFlashloanAmount); await aWETH.connect(user).approve(uniswapRepayAdapter.address, amountWETHtoSwap); const lendData = await pool.getReserveData(lend.address); const aLend = await getContract(eContractid.AToken, lendData.aTokenAddress); await aLend.connect(user).approve(uniswapRepayAdapter.address, amountLendToSwap); const aWETHBalanceBefore = await aWETH.balanceOf(userAddress); const aLendBalanceBefore = await aLend.balanceOf(userAddress); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address, usdc.address], 0, [expectedDaiAmountForEth, expectedUsdcAmountForLend], [1, 1], [0, 0], [0, 0], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ] ); await pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address, lend.address], [wethFlashloanAmount.toString(), lendFlashloanAmount.toString()], [0, 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 userUsdcStableDebtAmount = await usdcStableDebtContract.balanceOf(userAddress); const aWETHBalance = await aWETH.balanceOf(userAddress); const aLendBalance = await aLend.balanceOf(userAddress); expect(adapterWethBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmountForEth); expect(userDaiStableDebtAmount).to.be.lt(expectedDaiAmountForEth); expect(userUsdcStableDebtAmountBefore).to.be.gte(expectedUsdcAmountForLend); expect(userUsdcStableDebtAmount).to.be.lt(expectedUsdcAmountForLend); expect(aWETHBalance).to.be.lt(aWETHBalanceBefore); expect(aLendBalance).to.be.lt(aLendBalanceBefore); }); it('should swap tokens and repay debt for multiple tokens using permit', async () => { const { users, pool, weth, oracle, dai, uniswapRepayAdapter, lend, usdc, helpersContract, aWETH, } = testEnv; const user = users[0].signer; const userAddress = users[0].address; const chainId = BRE.network.config.chainId || BUIDLEREVM_CHAINID; const deadline = MAX_UINT_AMOUNT; const ownerPrivateKey = require('../test-wallets.js').accounts[1].secretKey; if (!ownerPrivateKey) { throw new Error('INVALID_OWNER_PK'); } const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); const amountLendToSwap = await convertToCurrencyDecimals(lend.address, '1'); const daiPrice = await oracle.getAssetPrice(dai.address); const expectedDaiAmountForEth = await convertToCurrencyDecimals( dai.address, new BigNumber(amountWETHtoSwap.toString()).div(daiPrice.toString()).toFixed(0) ); const lendPrice = await oracle.getAssetPrice(lend.address); const usdcPrice = await oracle.getAssetPrice(usdc.address); const collateralDecimals = (await lend.decimals()).toString(); const principalDecimals = (await usdc.decimals()).toString(); const expectedUsdcAmountForLend = await convertToCurrencyDecimals( usdc.address, new BigNumber(amountLendToSwap.toString()) .times( new BigNumber(lendPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) ) .div( new BigNumber(usdcPrice.toString()).times(new BigNumber(10).pow(collateralDecimals)) ) .toFixed(0) ); // Open user Debt await pool.connect(user).borrow(dai.address, expectedDaiAmountForEth, 1, 0, userAddress); await pool.connect(user).borrow(usdc.address, expectedUsdcAmountForLend, 1, 0, userAddress); const daiStableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(dai.address) ).stableDebtTokenAddress; const daiStableDebtContract = await getContract( eContractid.StableDebtToken, daiStableDebtTokenAddress ); const usdcStableDebtTokenAddress = ( await helpersContract.getReserveTokensAddresses(usdc.address) ).stableDebtTokenAddress; const usdcStableDebtContract = await getContract( eContractid.StableDebtToken, usdcStableDebtTokenAddress ); const userDaiStableDebtAmountBefore = await daiStableDebtContract.balanceOf(userAddress); const userUsdcStableDebtAmountBefore = await usdcStableDebtContract.balanceOf(userAddress); const lendData = await pool.getReserveData(lend.address); const aLend = await getContract(eContractid.AToken, lendData.aTokenAddress); const aWETHBalanceBefore = await aWETH.balanceOf(userAddress); const aLendBalanceBefore = await aLend.balanceOf(userAddress); // IMPORTANT: Round down to work equal to solidity to get the correct value for permit call BigNumber.config({ ROUNDING_MODE: 1, //round down }); const wethFlashloanAmountBN = new BigNumber(amountWETHtoSwap.toString()).div(1.0009); const wethFlashloanAmount = wethFlashloanAmountBN.toFixed(0); const wethFlashloanFee = wethFlashloanAmountBN.multipliedBy(9).div(10000); const wethAmountToPermit = wethFlashloanAmountBN.plus(wethFlashloanFee).toFixed(0); const lendFlashloanAmountBN = new BigNumber(amountLendToSwap.toString()).div(1.0009); const lendFlashloanAmount = lendFlashloanAmountBN.toFixed(0); const lendFlashloanFee = lendFlashloanAmountBN.multipliedBy(9).div(10000); const lendAmountToPermit = lendFlashloanAmountBN.plus(lendFlashloanFee).toFixed(0); const aWethNonce = (await aWETH._nonces(userAddress)).toNumber(); const aWethMsgParams = buildPermitParams( chainId, aWETH.address, '1', await aWETH.name(), userAddress, uniswapRepayAdapter.address, aWethNonce, deadline, wethAmountToPermit.toString() ); const {v: aWETHv, r: aWETHr, s: aWETHs} = getSignatureFromTypedData( ownerPrivateKey, aWethMsgParams ); const aLendNonce = (await aLend._nonces(userAddress)).toNumber(); const aLendMsgParams = buildPermitParams( chainId, aLend.address, '1', await aLend.name(), userAddress, uniswapRepayAdapter.address, aLendNonce, deadline, lendAmountToPermit.toString() ); const {v: aLendv, r: aLendr, s: aLends} = getSignatureFromTypedData( ownerPrivateKey, aLendMsgParams ); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, wethFlashloanAmount); await mockUniswapRouter.connect(user).setAmountToSwap(lend.address, lendFlashloanAmount); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address, usdc.address], 0, [expectedDaiAmountForEth, expectedUsdcAmountForLend], [1, 1], [deadline, deadline], [aWETHv, aLendv], [aWETHr, aLendr], [aWETHs, aLends], ] ); await pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address, lend.address], [wethFlashloanAmount.toString(), lendFlashloanAmount.toString()], [0, 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 userUsdcStableDebtAmount = await usdcStableDebtContract.balanceOf(userAddress); const aWETHBalance = await aWETH.balanceOf(userAddress); const aLendBalance = await aLend.balanceOf(userAddress); expect(adapterWethBalance).to.be.eq(Zero); expect(adapterDaiBalance).to.be.eq(Zero); expect(userDaiStableDebtAmountBefore).to.be.gte(expectedDaiAmountForEth); expect(userDaiStableDebtAmount).to.be.lt(expectedDaiAmountForEth); expect(userUsdcStableDebtAmountBefore).to.be.gte(expectedUsdcAmountForLend); expect(userUsdcStableDebtAmount).to.be.lt(expectedUsdcAmountForLend); expect(aWETHBalance).to.be.lt(aWETHBalanceBefore); expect(aLendBalance).to.be.lt(aLendBalanceBefore); // Restore round up BigNumber.config({ ROUNDING_MODE: 0, //round up }); }); 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(weth.address, flashloanAmount); const params1 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address, weth.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); 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[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount, expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); 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[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1, 1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params3, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params4 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0, 0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params4, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params5 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0, 0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params5, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params6 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params6, 0 ) ).to.be.revertedWith('INCONSISTENT_PARAMS'); const params7 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], [ '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', ], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params7, 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(weth.address, flashloanAmount); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); 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; const userAddress = users[0].address; const amountWETHtoSwap = await convertToCurrencyDecimals(weth.address, '10'); await weth.connect(user).mint(amountWETHtoSwap); await weth.connect(user).transfer(uniswapRepayAdapter.address, amountWETHtoSwap); 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 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(weth.address, flashloanAmount); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ).to.be.reverted; }); it('should revert if there is not debt to repay', async () => { const {users, pool, weth, oracle, dai, uniswapRepayAdapter, aWETH} = 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(uniswapRepayAdapter.address, amountWETHtoSwap); 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); // 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); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ).to.be.reverted; }); 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; 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); await aWETH.connect(user).approve(uniswapRepayAdapter.address, amountWETHtoSwap); // Subtract the FL fee from the amount to be swapped 0,09% const bigMaxAmountToSwap = amountWETHtoSwap.mul(2); const flashloanAmount = new BigNumber(bigMaxAmountToSwap.toString()).div(1.0009).toFixed(0); await mockUniswapRouter.connect(user).setAmountToSwap(weth.address, flashloanAmount); const params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [0], [0], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ['0x0000000000000000000000000000000000000000000000000000000000000000'], ] ); await expect( pool .connect(user) .flashLoan( uniswapRepayAdapter.address, [weth.address], [flashloanAmount.toString()], [0], userAddress, params, 0 ) ).to.be.revertedWith('maxAmountToSwap exceed max slippage'); }); it('should swap tokens, repay debt and deposit in pool 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 params = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 0, [expectedDaiAmount], [1], [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); 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(userAEthBalance).to.be.gte( userAEthBalanceBefore.sub(liquidityToSwap).add(leftOverWeth.toString()) ); }); 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 = ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256', 'uint256[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', ], [ [dai.address], 1, [expectedDaiAmount], [1], [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())); }); }); }); });