From 3aa0dbc570be1b76b8a13e97951f0d9099051908 Mon Sep 17 00:00:00 2001 From: eboado Date: Tue, 8 Sep 2020 15:05:53 +0200 Subject: [PATCH] - Added tests of repayWithCollateral(), only for self-liquidation. --- contracts/interfaces/ISwapAdapter.sol | 21 + .../LendingPoolLiquidationManager.sol | 14 +- contracts/mocks/flashloan/MockSwapAdapter.sol | 43 ++ helpers/contracts-helpers.ts | 15 + helpers/types.ts | 1 + package.json | 1 + test/__setup.spec.ts | 4 + test/helpers/actions.ts | 2 +- test/helpers/make-suite.ts | 6 + test/repay-with-collateral.spec.ts | 471 ++++++++++++++++++ 10 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 contracts/interfaces/ISwapAdapter.sol create mode 100644 contracts/mocks/flashloan/MockSwapAdapter.sol create mode 100644 test/repay-with-collateral.spec.ts diff --git a/contracts/interfaces/ISwapAdapter.sol b/contracts/interfaces/ISwapAdapter.sol new file mode 100644 index 00000000..c51ee0fd --- /dev/null +++ b/contracts/interfaces/ISwapAdapter.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface ISwapAdapter { + + /** + * @dev Swaps an `amountToSwap` of an asset to another, approving a `fundsDestination` to pull the funds + * @param assetToSwapFrom Origin asset + * @param assetToSwapTo Destination asset + * @param amountToSwap How much `assetToSwapFrom` needs to be swapped + * @param fundsDestination Address that will be pulling the swapped funds + * @param params Additional variadic field to include extra params + */ + function executeOperation( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + address fundsDestination, + bytes calldata params + ) external; +} \ No newline at end of file diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 7bbf149b..b79bfed7 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -21,7 +21,7 @@ import {Helpers} from '../libraries/helpers/Helpers.sol'; import {WadRayMath} from '../libraries/math/WadRayMath.sol'; import {PercentageMath} from '../libraries/math/PercentageMath.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; -import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; +import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; /** * @title LendingPoolLiquidationManager contract @@ -393,12 +393,14 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate); + address principalAToken = debtReserve.aTokenAddress; + // Notifies the receiver to proceed, sending as param the underlying already transferred - IFlashLoanReceiver(receiver).executeOperation( + ISwapAdapter(receiver).executeOperation( collateral, - address(vars.collateralAtoken), + principal, vars.maxCollateralToLiquidate, - 0, + address(this), params ); @@ -406,11 +408,11 @@ contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializabl debtReserve.updateCumulativeIndexesAndTimestamp(); debtReserve.updateInterestRates( principal, - debtReserve.aTokenAddress, + principalAToken, vars.actualAmountToLiquidate, 0 ); - IERC20(principal).transferFrom(receiver, debtReserve.aTokenAddress, vars.actualAmountToLiquidate); + IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate); if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn( diff --git a/contracts/mocks/flashloan/MockSwapAdapter.sol b/contracts/mocks/flashloan/MockSwapAdapter.sol new file mode 100644 index 00000000..6b2f2330 --- /dev/null +++ b/contracts/mocks/flashloan/MockSwapAdapter.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import {MintableERC20} from '../tokens/MintableERC20.sol'; +import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; +import {ISwapAdapter} from '../../interfaces/ISwapAdapter.sol'; +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +contract MockSwapAdapter is ISwapAdapter { + + uint256 amountToReturn; + ILendingPoolAddressesProvider public addressesProvider; + + event Swapped(address fromAsset, address toAsset, uint256 fromAmount, uint256 receivedAmount); + + constructor(ILendingPoolAddressesProvider provider) public { + addressesProvider = provider; + } + + function setAmountToReturn(uint256 amount) public { + amountToReturn = amount; + } + + function executeOperation( + address assetToSwapFrom, + address assetToSwapTo, + uint256 amountToSwap, + address fundsDestination, + bytes calldata params + ) external override { + params; + IERC20(assetToSwapFrom).transfer(address(1), amountToSwap); // We don't want to keep funds here + MintableERC20(assetToSwapTo).mint(amountToReturn); + IERC20(assetToSwapTo).approve(fundsDestination, amountToReturn); + + emit Swapped(assetToSwapFrom, assetToSwapTo, amountToSwap, amountToReturn); + } + + function burnAsset(IERC20 asset, uint256 amount) public { + uint256 amountToBurn = (amount == type(uint256).max) ? asset.balanceOf(address(this)) : amount; + asset.transfer(address(0), amountToBurn); + } +} \ No newline at end of file diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index b5b483d3..909676a1 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -31,6 +31,7 @@ import BigNumber from 'bignumber.js'; import {Ierc20Detailed} from '../types/Ierc20Detailed'; import {StableDebtToken} from '../types/StableDebtToken'; import {VariableDebtToken} from '../types/VariableDebtToken'; +import { MockSwapAdapter } from '../types/MockSwapAdapter'; export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => { const currentNetwork = BRE.network.name; @@ -212,6 +213,11 @@ export const deployMockFlashLoanReceiver = async (addressesProvider: tEthereumAd addressesProvider, ]); +export const deployMockSwapAdapter = async (addressesProvider: tEthereumAddress) => + await deployContract(eContractid.MockSwapAdapter, [ + addressesProvider, + ]); + export const deployWalletBalancerProvider = async (addressesProvider: tEthereumAddress) => await deployContract(eContractid.WalletBalanceProvider, [ addressesProvider, @@ -387,6 +393,15 @@ export const getMockFlashLoanReceiver = async (address?: tEthereumAddress) => { ); }; +export const getMockSwapAdapter = async (address?: tEthereumAddress) => { + return await getContract( + eContractid.MockSwapAdapter, + address || + (await getDb().get(`${eContractid.MockSwapAdapter}.${BRE.network.name}`).value()) + .address + ); +}; + export const getLendingRateOracle = async (address?: tEthereumAddress) => { return await getContract( eContractid.LendingRateOracle, diff --git a/helpers/types.ts b/helpers/types.ts index 106e5376..68ec503d 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -32,6 +32,7 @@ export enum eContractid { LendingPoolLiquidationManager = 'LendingPoolLiquidationManager', InitializableAdminUpgradeabilityProxy = 'InitializableAdminUpgradeabilityProxy', MockFlashLoanReceiver = 'MockFlashLoanReceiver', + MockSwapAdapter = 'MockSwapAdapter', WalletBalanceProvider = 'WalletBalanceProvider', AToken = 'AToken', MockAToken = 'MockAToken', diff --git a/package.json b/package.json index 34e56bfa..94ec647c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "types-gen": "typechain --target ethers-v5 --outDir ./types './artifacts/*.json'", "test": "buidler test", "test-scenarios": "buidler test test/__setup.spec.ts test/scenario.spec.ts", + "test-repay-with-collateral": "buidler test test/__setup.spec.ts test/repay-with-collateral.spec.ts", "dev:coverage": "buidler coverage", "dev:deployment": "buidler dev-deployment", "dev:deployExample": "buidler deploy-Example", diff --git a/test/__setup.spec.ts b/test/__setup.spec.ts index 342b7b64..689ecf3a 100644 --- a/test/__setup.spec.ts +++ b/test/__setup.spec.ts @@ -23,6 +23,7 @@ import { deployStableDebtToken, deployVariableDebtToken, deployGenericAToken, + deployMockSwapAdapter, } from '../helpers/contracts-helpers'; import {LendingPoolAddressesProvider} from '../types/LendingPoolAddressesProvider'; import {ContractTransaction, Signer} from 'ethers'; @@ -503,6 +504,9 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { const mockFlashLoanReceiver = await deployMockFlashLoanReceiver(addressesProvider.address); await insertContractAddressInDb(eContractid.MockFlashLoanReceiver, mockFlashLoanReceiver.address); + const mockSwapAdapter = await deployMockSwapAdapter(addressesProvider.address) + await insertContractAddressInDb(eContractid.MockSwapAdapter, mockSwapAdapter.address) + await deployWalletBalancerProvider(addressesProvider.address); const testHelpers = await deployAaveProtocolTestHelpers(addressesProvider.address); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index be0daac5..4442b052 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -845,7 +845,7 @@ const getTxCostAndTimestamp = async (tx: ContractReceipt) => { return {txCost, txTimestamp}; }; -const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { +export const getContractsData = async (reserve: string, user: string, testEnv: TestEnv) => { const {pool} = testEnv; const reserveData = await getReserveData(pool, reserve); const userData = await getUserData(pool, reserve, user); diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index e6e5df98..a0787a17 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -9,6 +9,7 @@ import { getMintableErc20, getLendingPoolConfiguratorProxy, getPriceOracle, + getMockSwapAdapter, } from '../../helpers/contracts-helpers'; import {tEthereumAddress} from '../../helpers/types'; import {LendingPool} from '../../types/LendingPool'; @@ -23,6 +24,7 @@ import bignumberChai from 'chai-bignumber'; import {almostEqual} from './almost-equal'; import {PriceOracle} from '../../types/PriceOracle'; import {LendingPoolAddressesProvider} from '../../types/LendingPoolAddressesProvider'; +import { MockSwapAdapter } from '../../types/MockSwapAdapter'; chai.use(bignumberChai()); chai.use(almostEqual()); @@ -44,6 +46,7 @@ export interface TestEnv { usdc: MintableErc20; lend: MintableErc20; addressesProvider: LendingPoolAddressesProvider; + mockSwapAdapter: MockSwapAdapter; } let buidlerevmSnapshotId: string = '0x1'; @@ -67,6 +70,7 @@ const testEnv: TestEnv = { usdc: {} as MintableErc20, lend: {} as MintableErc20, addressesProvider: {} as LendingPoolAddressesProvider, + mockSwapAdapter: {} as MockSwapAdapter } as TestEnv; export async function initializeMakeSuite() { @@ -125,6 +129,8 @@ export async function initializeMakeSuite() { testEnv.usdc = await getMintableErc20(usdcAddress); testEnv.lend = await getMintableErc20(lendAddress); testEnv.weth = await getMintableErc20(wethAddress); + + testEnv.mockSwapAdapter = await getMockSwapAdapter() } export function makeSuite(name: string, tests: (testEnv: TestEnv) => void) { diff --git a/test/repay-with-collateral.spec.ts b/test/repay-with-collateral.spec.ts new file mode 100644 index 00000000..c22dd7a7 --- /dev/null +++ b/test/repay-with-collateral.spec.ts @@ -0,0 +1,471 @@ +import {TestEnv, makeSuite} from './helpers/make-suite'; +import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants'; +import {ethers} from 'ethers'; +import BigNumber from 'bignumber.js'; +import { + calcExpectedVariableDebtTokenBalance, + calcExpectedStableDebtTokenBalance, +} from './helpers/utils/calculations'; +import {getContractsData} from './helpers/actions'; +import {waitForTx} from './__setup.spec'; +import {timeLatest} from '../helpers/misc-utils'; +import {tEthereumAddress} from '../helpers/types'; + +const {expect} = require('chai'); +const {parseUnits, parseEther} = ethers.utils; + +const expectRepayWithCollateralEvent = ( + events: ethers.Event[], + pool: tEthereumAddress, + collateral: tEthereumAddress, + borrowing: tEthereumAddress, + user: tEthereumAddress +) => { + if (!events || events.length < 14) { + expect(false, 'INVALID_EVENTS_LENGTH_ON_REPAY_COLLATERAL'); + } + + const repayWithCollateralEvent = events[13]; + + expect(repayWithCollateralEvent.address).to.be.equal(pool); + expect(`0x${repayWithCollateralEvent.topics[1].slice(26)}`.toLowerCase()).to.be.equal( + collateral.toLowerCase() + ); + expect(`0x${repayWithCollateralEvent.topics[2].slice(26)}`).to.be.equal(borrowing.toLowerCase()); + expect(`0x${repayWithCollateralEvent.topics[3].slice(26)}`.toLowerCase()).to.be.equal( + user.toLowerCase() + ); +}; + +makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => { + it('User 1 provides some liquidity for others to borrow', async () => { + const {pool, weth, dai, usdc} = testEnv; + + await weth.mint(parseEther('200')); + await weth.approve(pool.address, parseEther('200')); + await pool.deposit(weth.address, parseEther('200'), 0); + await dai.mint(parseEther('20000')); + await dai.approve(pool.address, parseEther('20000')); + await pool.deposit(dai.address, parseEther('20000'), 0); + await usdc.mint(parseEther('20000')); + await usdc.approve(pool.address, parseEther('20000')); + await pool.deposit(usdc.address, parseEther('20000'), 0); + }); + + it('User 2 deposit WETH and borrows DAI at Variable', async () => { + const {pool, weth, dai, users} = testEnv; + const user = users[1]; + const amountToDeposit = ethers.utils.parseEther('1'); + const amountToBorrow = ethers.utils.parseEther('20'); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(dai.address, amountToBorrow, 2, 0); + }); + + it('User 2 tries to repay his DAI Variable loan using his WETH collateral. First half the amount, after that, the rest', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[1]; + + const amountToRepay = parseEther('10'); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(dai.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(dai.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + daiReserveDataBefore, + daiUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(daiUserDataBefore.currentVariableDebt); + + expect(daiUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(daiUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ) + ); + }); + + it('User 3 deposits WETH and borrows USDC at Variable', async () => { + const {pool, weth, usdc, users} = testEnv; + const user = users[2]; + const amountToDeposit = parseEther('10'); + const amountToBorrow = parseUnits('40', 6); + + await weth.connect(user.signer).mint(amountToDeposit); + + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrow, 2, 0); + }); + + it('User 3 repays completely his USDC loan by swapping his WETH collateral', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + + const amountToRepay = parseUnits('10', 6); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.almostEqual( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString(), + 'INVALID_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + }); + + it('User tries to repay with his collateral a currency he havent borrow', async () => { + const {pool, weth, dai, users, mockSwapAdapter} = testEnv; + const user = users[2]; + + const amountToRepay = parseUnits('10', 6); + + await expect( + pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ).to.be.revertedWith('revert CURRRENCY_NOT_BORROWED'); + }); + + it('User tries to repay with his collateral all his variable debt and part of the stable', async () => { + const {pool, weth, usdc, users, mockSwapAdapter, oracle} = testEnv; + const user = users[2]; + + const amountToDeposit = parseEther('20'); + const amountToBorrowStable = parseUnits('40', 6); + const amountToBorrowVariable = parseUnits('40', 6); + + await weth.connect(user.signer).mint(amountToDeposit); + + await pool.connect(user.signer).deposit(weth.address, amountToDeposit, '0'); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrowVariable, 2, 0); + + await pool.connect(user.signer).borrow(usdc.address, amountToBorrowStable, 1, 0); + + const amountToRepay = parseUnits('80', 6); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const { + reserveData: usdcReserveDataBefore, + userData: usdcUserDataBefore, + } = await getContractsData(usdc.address, user.address, testEnv); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + const txReceipt = await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + usdc.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: usdcUserDataAfter} = await getContractsData( + usdc.address, + user.address, + testEnv + ); + + const collateralPrice = await oracle.getAssetPrice(weth.address); + const principalPrice = await oracle.getAssetPrice(usdc.address); + + const collateralDecimals = ( + await pool.getReserveConfigurationData(weth.address) + ).decimals.toString(); + const principalDecimals = ( + await pool.getReserveConfigurationData(usdc.address) + ).decimals.toString(); + + const expectedCollateralLiquidated = new BigNumber(principalPrice.toString()) + .times(new BigNumber(amountToRepay.toString()).times(105)) + .times(new BigNumber(10).pow(collateralDecimals)) + .div( + new BigNumber(collateralPrice.toString()).times(new BigNumber(10).pow(principalDecimals)) + ) + .div(100) + .decimalPlaces(0, BigNumber.ROUND_DOWN); + + const expectedVariableDebtIncrease = calcExpectedVariableDebtTokenBalance( + usdcReserveDataBefore, + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentVariableDebt); + + const expectedStableDebtIncrease = calcExpectedStableDebtTokenBalance( + usdcUserDataBefore, + new BigNumber(repayWithCollateralTimestamp) + ).minus(usdcUserDataBefore.currentStableDebt); + + expect(usdcUserDataAfter.currentVariableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .toString() + : '0', + 'INVALID_VARIABLE_DEBT_POSITION' + ); + + const stableDebtRepaid = new BigNumber(usdcUserDataBefore.currentVariableDebt) + .minus(amountToRepay.toString()) + .plus(expectedVariableDebtIncrease) + .abs(); + + expect(usdcUserDataAfter.currentStableDebt).to.be.bignumber.equal( + new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .gte(0) + ? new BigNumber(usdcUserDataBefore.currentStableDebt) + .minus(stableDebtRepaid) + .plus(expectedStableDebtIncrease) + .toString() + : '0', + 'INVALID_STABLE_DEBT_POSITION' + ); + + expect(wethUserDataAfter.currentATokenBalance).to.be.bignumber.equal( + new BigNumber(wethUserDataBefore.currentATokenBalance).minus( + expectedCollateralLiquidated.toString() + ), + 'INVALID_COLLATERAL_POSITION' + ); + + const eventsEmitted = txReceipt.events || []; + + expectRepayWithCollateralEvent( + eventsEmitted, + pool.address, + weth.address, + usdc.address, + user.address + ); + }); + + // WIP + it('User tries to repay a bigger amount that what can be swapped of a particular collateral, repaying only the maximum allowed by that collateral', async () => { + const {pool, weth, dai, users, mockSwapAdapter, oracle} = testEnv; + const user = users[3]; + + const amountToDepositWeth = parseEther('0.1'); + const amountToDepositDAI = parseEther('500'); + const amountToBorrowVariable = parseEther('80'); + + await weth.connect(user.signer).mint(amountToDepositWeth); + await dai.connect(user.signer).mint(amountToDepositDAI); + await weth.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await dai.connect(user.signer).approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + + await pool.connect(user.signer).deposit(weth.address, amountToDepositWeth, '0'); + await pool.connect(user.signer).deposit(dai.address, amountToDepositDAI, '0'); + + await pool.connect(user.signer).borrow(dai.address, amountToBorrowVariable, 2, 0); + + const amountToRepay = parseEther('80'); + + const {userData: wethUserDataBefore} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + user.address, + testEnv + ); + + console.log('BEFORE'); + console.log(wethUserDataBefore.currentATokenBalance.toString()); + console.log(daiUserDataBefore.currentVariableDebt.toString()); + console.log(daiUserDataBefore.currentStableDebt.toString()); + + await mockSwapAdapter.setAmountToReturn(amountToRepay); + const txReceipt = await waitForTx( + await pool + .connect(user.signer) + .repayWithCollateral( + weth.address, + dai.address, + user.address, + amountToRepay, + mockSwapAdapter.address, + '0x' + ) + ); + const repayWithCollateralTimestamp = await timeLatest(); + + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + user.address, + testEnv + ); + + const {userData: daiUserDataAfter} = await getContractsData(dai.address, user.address, testEnv); + + console.log('AFTER'); + console.log(wethUserDataAfter.currentATokenBalance.toString()); + console.log(daiUserDataAfter.currentVariableDebt.toString()); + console.log(daiUserDataAfter.currentStableDebt.toString()); + }); +});