aave-protocol-v2/test/uniswapAdapters.spec.ts
2020-11-04 16:51:21 -03:00

2888 lines
104 KiB
TypeScript

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<AToken>(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<AToken>(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<AToken>(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<AToken>(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<AToken>(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<StableDebtToken>(
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<StableDebtToken>(
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<StableDebtToken>(
eContractid.StableDebtToken,
daiStableDebtTokenAddress
);
const usdcStableDebtTokenAddress = (
await helpersContract.getReserveTokensAddresses(usdc.address)
).stableDebtTokenAddress;
const usdcStableDebtContract = await getContract<StableDebtToken>(
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<AToken>(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<StableDebtToken>(
eContractid.StableDebtToken,
daiStableDebtTokenAddress
);
const usdcStableDebtTokenAddress = (
await helpersContract.getReserveTokensAddresses(usdc.address)
).stableDebtTokenAddress;
const usdcStableDebtContract = await getContract<StableDebtToken>(
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<AToken>(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<StableDebtToken>(
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<StableDebtToken>(
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()));
});
});
});
});