diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index 55010304..41b13f07 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -290,6 +290,22 @@ interface ILendingPool { uint16 referralCode ) external; + /** + * @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another. + * - It's not possible to release one asset to swap for the same + * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface + * @param fromAsset Asset to swap from + * @param toAsset Asset to swap to + * @param params a bytes array to be sent (if needed) to the receiver contract with extra data + **/ + function swapLiquidity( + address receiverAddress, + address fromAsset, + address toAsset, + uint256 amountToSwap, + bytes calldata params + ) external; + /** * @dev accessory functions to fetch data from the core contract **/ diff --git a/contracts/interfaces/ISwapAdapter.sol b/contracts/interfaces/ISwapAdapter.sol index c51ee0fd..8f261d81 100644 --- a/contracts/interfaces/ISwapAdapter.sol +++ b/contracts/interfaces/ISwapAdapter.sol @@ -18,4 +18,4 @@ interface ISwapAdapter { address fundsDestination, bytes calldata params ) external; -} \ No newline at end of file +} diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 074c337f..ba5e7894 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -20,6 +20,7 @@ import {UserConfiguration} from '../libraries/configuration/UserConfiguration.so import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol'; import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol'; import {IFlashLoanReceiver} from '../flashloan/interfaces/IFlashLoanReceiver.sol'; +import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol'; import {LendingPoolLiquidationManager} from './LendingPoolLiquidationManager.sol'; import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol'; @@ -580,6 +581,43 @@ contract LendingPool is VersionedInitializable, ILendingPool { } } + /** + * @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another. + * - It's not possible to release one asset to swap for the same + * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface + * @param fromAsset Asset to swap from + * @param toAsset Asset to swap to + * @param params a bytes array to be sent (if needed) to the receiver contract with extra data + **/ + function swapLiquidity( + address receiverAddress, + address fromAsset, + address toAsset, + uint256 amountToSwap, + bytes calldata params + ) external override { + address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager(); + + //solium-disable-next-line + (bool success, bytes memory result) = liquidationManager.delegatecall( + abi.encodeWithSignature( + 'swapLiquidity(address,address,address,uint256,bytes)', + receiverAddress, + fromAsset, + toAsset, + amountToSwap, + params + ) + ); + require(success, Errors.FAILED_COLLATERAL_SWAP); + + (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string)); + + if (returnCode != 0) { + revert(string(abi.encodePacked(returnMessage))); + } + } + /** * @dev accessory functions to fetch data from the core contract **/ diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol index 4f1c1ba3..664e32f4 100644 --- a/contracts/lendingpool/LendingPoolLiquidationManager.sol +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -110,6 +110,26 @@ contract LendingPoolLiquidationManager is VersionedInitializable { string errorMsg; } + struct SwapLiquidityLocalVars { + uint256 healthFactor; + uint256 amountToReceive; + uint256 userBalanceBefore; + IAToken fromReserveAToken; + IAToken toReserveAToken; + uint256 errorCode; + string errorMsg; + } + + struct AvailableCollateralToLiquidateLocalVars { + uint256 userCompoundedBorrowBalance; + uint256 liquidationBonus; + uint256 collateralPrice; + uint256 principalCurrencyPrice; + uint256 maxAmountCollateralToLiquidate; + uint256 principalDecimals; + uint256 collateralDecimals; + } + /** * @dev as the contract extends the VersionedInitializable contract to match the state * of the LendingPool contract, the getRevision() function is needed. @@ -253,7 +273,12 @@ contract LendingPoolLiquidationManager is VersionedInitializable { ); //burn the equivalent amount of atoken - vars.collateralAtoken.burn(user, msg.sender, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex); + vars.collateralAtoken.burn( + user, + msg.sender, + vars.maxCollateralToLiquidate, + collateralReserve.liquidityIndex + ); } //transfers the principal currency to the aToken @@ -356,7 +381,12 @@ contract LendingPoolLiquidationManager is VersionedInitializable { //updating collateral reserve indexes collateralReserve.updateCumulativeIndexesAndTimestamp(); - vars.collateralAtoken.burn(user, receiver, vars.maxCollateralToLiquidate, collateralReserve.liquidityIndex); + vars.collateralAtoken.burn( + user, + receiver, + vars.maxCollateralToLiquidate, + collateralReserve.liquidityIndex + ); if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) { usersConfig[user].setUsingAsCollateral(collateralReserve.id, false); @@ -375,7 +405,12 @@ contract LendingPoolLiquidationManager is VersionedInitializable { //updating debt reserve debtReserve.updateCumulativeIndexesAndTimestamp(); - debtReserve.updateInterestRates(principal, vars.principalAToken, vars.actualAmountToLiquidate, 0); + debtReserve.updateInterestRates( + principal, + vars.principalAToken, + vars.actualAmountToLiquidate, + 0 + ); IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate); if (vars.userVariableDebt >= vars.actualAmountToLiquidate) { @@ -411,14 +446,97 @@ contract LendingPoolLiquidationManager is VersionedInitializable { return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS); } - struct AvailableCollateralToLiquidateLocalVars { - uint256 userCompoundedBorrowBalance; - uint256 liquidationBonus; - uint256 collateralPrice; - uint256 principalCurrencyPrice; - uint256 maxAmountCollateralToLiquidate; - uint256 principalDecimals; - uint256 collateralDecimals; + /** + * @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another. + * - It's not possible to release one asset to swap for the same + * @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface + * @param fromAsset Asset to swap from + * @param toAsset Asset to swap to + * @param params a bytes array to be sent (if needed) to the receiver contract with extra data + **/ + function swapLiquidity( + address receiverAddress, + address fromAsset, + address toAsset, + uint256 amountToSwap, + bytes calldata params + ) external returns (uint256, string memory) { + ReserveLogic.ReserveData storage fromReserve = reserves[fromAsset]; + ReserveLogic.ReserveData storage toReserve = reserves[toAsset]; + + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + SwapLiquidityLocalVars memory vars; + + (vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity( + fromReserve, + toReserve, + fromAsset, + toAsset + ); + + if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) { + return (vars.errorCode, vars.errorMsg); + } + + vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress); + vars.toReserveAToken = IAToken(toReserve.aTokenAddress); + + fromReserve.updateCumulativeIndexesAndTimestamp(); + toReserve.updateCumulativeIndexesAndTimestamp(); + + if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) { + usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false); + } + + fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap); + + vars.fromReserveAToken.burn( + msg.sender, + receiverAddress, + amountToSwap, + fromReserve.liquidityIndex + ); + // Notifies the receiver to proceed, sending as param the underlying already transferred + ISwapAdapter(receiverAddress).executeOperation( + fromAsset, + toAsset, + amountToSwap, + address(this), + params + ); + + vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress); + if (vars.amountToReceive != 0) { + IERC20(toAsset).transferFrom( + receiverAddress, + address(vars.toReserveAToken), + vars.amountToReceive + ); + vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex); + toReserve.updateInterestRates( + toAsset, + address(vars.toReserveAToken), + vars.amountToReceive, + 0 + ); + } + + (, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData( + msg.sender, + reserves, + usersConfig[msg.sender], + reservesList, + addressesProvider.getPriceOracle() + ); + + if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { + return ( + uint256(Errors.LiquidationErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD), + Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + } + + return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS); } /** diff --git a/contracts/libraries/helpers/Errors.sol b/contracts/libraries/helpers/Errors.sol index 6a681b14..3ab451b1 100644 --- a/contracts/libraries/helpers/Errors.sol +++ b/contracts/libraries/helpers/Errors.sol @@ -39,8 +39,10 @@ library Errors { string public constant CALLER_NOT_LENDING_POOL_CONFIGURATOR = '27'; // 'The actual balance of the protocol is inconsistent' string public constant INVALID_FLASHLOAN_MODE = '43'; //Invalid flashloan mode selected string public constant BORROW_ALLOWANCE_ARE_NOT_ENOUGH = '54'; // User borrows on behalf, but allowance are too small - string public constant REENTRANCY_NOT_ALLOWED = '52'; + string public constant REENTRANCY_NOT_ALLOWED = '57'; string public constant FAILED_REPAY_WITH_COLLATERAL = '53'; + string public constant FAILED_COLLATERAL_SWAP = '55'; + string public constant INVALID_EQUAL_ASSETS_TO_SWAP = '56'; // require error messages - aToken string public constant CALLER_MUST_BE_LENDING_POOL = '28'; // 'The caller of this function must be a lending pool' @@ -82,6 +84,8 @@ library Errors { CURRRENCY_NOT_BORROWED, HEALTH_FACTOR_ABOVE_THRESHOLD, NOT_ENOUGH_LIQUIDITY, - NO_ACTIVE_RESERVE + NO_ACTIVE_RESERVE, + HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD, + INVALID_EQUAL_ASSETS_TO_SWAP } } diff --git a/contracts/libraries/logic/ReserveLogic.sol b/contracts/libraries/logic/ReserveLogic.sol index bccc5113..32e16599 100644 --- a/contracts/libraries/logic/ReserveLogic.sol +++ b/contracts/libraries/logic/ReserveLogic.sol @@ -190,7 +190,7 @@ library ReserveLogic { ReserveData storage reserve, uint256 totalLiquidity, uint256 amount - ) internal { + ) external { uint256 amountToLiquidityRatio = amount.wadToRay().rayDiv(totalLiquidity.wadToRay()); uint256 result = amountToLiquidityRatio.add(WadRayMath.ray()); @@ -252,7 +252,7 @@ library ReserveLogic { address aTokenAddress, uint256 liquidityAdded, uint256 liquidityTaken - ) internal { + ) external { UpdateInterestRatesLocalVars memory vars; vars.stableDebtTokenAddress = reserve.stableDebtTokenAddress; diff --git a/contracts/libraries/logic/ValidationLogic.sol b/contracts/libraries/logic/ValidationLogic.sol index 84c1d861..336e18ad 100644 --- a/contracts/libraries/logic/ValidationLogic.sol +++ b/contracts/libraries/logic/ValidationLogic.sol @@ -347,12 +347,11 @@ library ValidationLogic { uint256 userHealthFactor, uint256 userStableDebt, uint256 userVariableDebt - ) internal view returns(uint256, string memory) { - if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) { - return ( - uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), - Errors.NO_ACTIVE_RESERVE - ); + ) internal view returns (uint256, string memory) { + if ( + !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive() + ) { + return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE); } if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) { @@ -362,8 +361,7 @@ library ValidationLogic { ); } - bool isCollateralEnabled = - collateralReserve.configuration.getLiquidationThreshold() > 0 && + bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 && userConfig.isUsingAsCollateral(collateralReserve.id); //if collateral isn't enabled as collateral by user, it cannot be liquidated @@ -402,12 +400,11 @@ library ValidationLogic { uint256 userHealthFactor, uint256 userStableDebt, uint256 userVariableDebt - ) internal view returns(uint256, string memory) { - if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) { - return ( - uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), - Errors.NO_ACTIVE_RESERVE - ); + ) internal view returns (uint256, string memory) { + if ( + !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive() + ) { + return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE); } if ( @@ -420,8 +417,7 @@ library ValidationLogic { } if (msg.sender != user) { - bool isCollateralEnabled = - collateralReserve.configuration.getLiquidationThreshold() > 0 && + bool isCollateralEnabled = collateralReserve.configuration.getLiquidationThreshold() > 0 && userConfig.isUsingAsCollateral(collateralReserve.id); //if collateral isn't enabled as collateral by user, it cannot be liquidated @@ -442,4 +438,31 @@ library ValidationLogic { return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS); } + + /** + * @dev Validates the swapLiquidity() action + * @param fromReserve The reserve data of the asset to swap from + * @param toReserve The reserve data of the asset to swap to + * @param fromAsset Address of the asset to swap from + * @param toAsset Address of the asset to swap to + **/ + function validateSwapLiquidity( + ReserveLogic.ReserveData storage fromReserve, + ReserveLogic.ReserveData storage toReserve, + address fromAsset, + address toAsset + ) internal view returns (uint256, string memory) { + if (!fromReserve.configuration.getActive() || !toReserve.configuration.getActive()) { + return (uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE), Errors.NO_ACTIVE_RESERVE); + } + + if (fromAsset == toAsset) { + return ( + uint256(Errors.LiquidationErrors.INVALID_EQUAL_ASSETS_TO_SWAP), + Errors.INVALID_EQUAL_ASSETS_TO_SWAP + ); + } + + return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS); + } } diff --git a/contracts/mocks/flashloan/MockSwapAdapter.sol b/contracts/mocks/flashloan/MockSwapAdapter.sol index 85c7d84b..7fdd6d35 100644 --- a/contracts/mocks/flashloan/MockSwapAdapter.sol +++ b/contracts/mocks/flashloan/MockSwapAdapter.sol @@ -57,4 +57,4 @@ contract MockSwapAdapter is ISwapAdapter { 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/deployed-contracts.json b/deployed-contracts.json index d3f80214..00c4ef4f 100644 --- a/deployed-contracts.json +++ b/deployed-contracts.json @@ -493,9 +493,6 @@ "MockSwapAdapter": { "buidlerevm": { "address": "0xBEF0d4b9c089a5883741fC14cbA352055f35DDA2" - }, - "localhost": { - "address": "0x749258D38b0473d96FEcc14cC5e7DCE12d7Bd6f6" } } } \ No newline at end of file diff --git a/helpers/types.ts b/helpers/types.ts index c965d658..4d8ed0fa 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -42,7 +42,7 @@ export enum eContractid { AaveProtocolTestHelpers = 'AaveProtocolTestHelpers', IERC20Detailed = 'IERC20Detailed', StableDebtToken = 'StableDebtToken', - VariableDebtToken = 'VariableDebtToken', + VariableDebtToken = 'VariableDebtToken' } export enum ProtocolErrors { diff --git a/test/collateral-swap.spec.ts b/test/collateral-swap.spec.ts new file mode 100644 index 00000000..cbae27f5 --- /dev/null +++ b/test/collateral-swap.spec.ts @@ -0,0 +1,198 @@ +import {makeSuite, TestEnv} from './helpers/make-suite'; +import {MockSwapAdapter} from '../types/MockSwapAdapter'; +import {getMockSwapAdapter} from '../helpers/contracts-helpers'; +import {ProtocolErrors} from '../helpers/types'; +import {ethers} from 'ethers'; +import {APPROVAL_AMOUNT_LENDING_POOL} from '../helpers/constants'; +import {getContractsData, getTxCostAndTimestamp} from './helpers/actions'; +import {calcExpectedATokenBalance} from './helpers/utils/calculations'; +import {waitForTx} from './__setup.spec'; +import {advanceBlock, timeLatest} from '../helpers/misc-utils'; + +const {expect} = require('chai'); + +makeSuite('LendingPool SwapDeposit function', (testEnv: TestEnv) => { + let _mockSwapAdapter = {} as MockSwapAdapter; + const {HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD} = ProtocolErrors; + + before(async () => { + _mockSwapAdapter = await getMockSwapAdapter(); + }); + + it('Deposits WETH into the reserve', async () => { + const {pool, weth, users} = testEnv; + const amountToDeposit = ethers.utils.parseEther('1'); + + for (const signer of [weth.signer, users[2].signer]) { + const connectedWETH = weth.connect(signer); + await connectedWETH.mint(amountToDeposit); + await connectedWETH.approve(pool.address, APPROVAL_AMOUNT_LENDING_POOL); + await pool + .connect(signer) + .deposit(weth.address, amountToDeposit, await signer.getAddress(), '0'); + } + }); + it('User tries to swap more then he can, revert expected', async () => { + const {pool, weth, dai} = testEnv; + await expect( + pool.swapLiquidity( + _mockSwapAdapter.address, + weth.address, + dai.address, + ethers.utils.parseEther('1.1'), + '0x10' + ) + ).to.be.revertedWith('55'); + }); + + it('User tries to swap asset on equal asset, revert expected', async () => { + const {pool, weth} = testEnv; + await expect( + pool.swapLiquidity( + _mockSwapAdapter.address, + weth.address, + weth.address, + ethers.utils.parseEther('0.1'), + '0x10' + ) + ).to.be.revertedWith('56'); + }); + + it('User tries to swap more then available on the reserve', async () => { + const {pool, weth, dai, users, aEth, deployer} = testEnv; + + await pool.borrow(weth.address, ethers.utils.parseEther('0.1'), 1, 0, deployer.address); + await pool.connect(users[2].signer).withdraw(weth.address, ethers.utils.parseEther('1')); + + await expect( + pool.swapLiquidity( + _mockSwapAdapter.address, + weth.address, + dai.address, + ethers.utils.parseEther('1'), + '0x10' + ) + ).to.be.revertedWith('55'); + }); + + it('User tries to swap correct amount', async () => { + const {pool, weth, dai, aEth, aDai} = testEnv; + const userAddress = await pool.signer.getAddress(); + const amountToSwap = ethers.utils.parseEther('0.25'); + + const amountToReturn = ethers.utils.parseEther('0.5'); + await _mockSwapAdapter.setAmountToReturn(amountToReturn); + + const { + reserveData: wethReserveDataBefore, + userData: wethUserDataBefore, + } = await getContractsData(weth.address, userAddress, testEnv); + + const {reserveData: daiReserveDataBefore, userData: daiUserDataBefore} = await getContractsData( + dai.address, + userAddress, + testEnv + ); + + const reserveBalanceWETHBefore = await weth.balanceOf(aEth.address); + const reserveBalanceDAIBefore = await dai.balanceOf(aDai.address); + + const txReceipt = await waitForTx( + await pool.swapLiquidity( + _mockSwapAdapter.address, + weth.address, + dai.address, + amountToSwap, + '0x10' + ) + ); + const {txTimestamp} = await getTxCostAndTimestamp(txReceipt); + const userATokenBalanceWETHAfter = await aEth.balanceOf(userAddress); + const userATokenBalanceDAIAfter = await aDai.balanceOf(userAddress); + + const reserveBalanceWETHAfter = await weth.balanceOf(aEth.address); + const reserveBalanceDAIAfter = await dai.balanceOf(aDai.address); + + expect(userATokenBalanceWETHAfter.toString()).to.be.equal( + calcExpectedATokenBalance(wethReserveDataBefore, wethUserDataBefore, txTimestamp) + .minus(amountToSwap.toString()) + .toString(), + 'was burned incorrect amount of user funds' + ); + expect(userATokenBalanceDAIAfter.toString()).to.be.equal( + calcExpectedATokenBalance(daiReserveDataBefore, daiUserDataBefore, txTimestamp) + .plus(amountToReturn.toString()) + .toString(), + 'was minted incorrect amount of user funds' + ); + + expect(reserveBalanceWETHAfter.toString()).to.be.equal( + reserveBalanceWETHBefore.sub(amountToSwap).toString(), + 'was sent incorrect amount if reserve funds' + ); + expect(reserveBalanceDAIAfter.toString()).to.be.equal( + reserveBalanceDAIBefore.add(amountToReturn).toString(), + 'was received incorrect amount if reserve funds' + ); + }); + + it('User tries to drop HF below one', async () => { + const {pool, weth, dai, deployer} = testEnv; + const amountToSwap = ethers.utils.parseEther('0.3'); + + const amountToReturn = ethers.utils.parseEther('0.5'); + await _mockSwapAdapter.setAmountToReturn(amountToReturn); + + await pool.borrow(weth.address, ethers.utils.parseEther('0.3'), 1, 0, deployer.address); + + await expect( + pool.swapLiquidity(_mockSwapAdapter.address, weth.address, dai.address, amountToSwap, '0x10') + ).to.be.revertedWith(HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD); + }); + + it('Should set usage as collateral to false if no leftovers after swap', async () => { + const {pool, weth, dai, aEth, users} = testEnv; + const userAddress = await pool.signer.getAddress(); + + // add more liquidity to allow user 0 to swap everything he has + await weth.connect(users[2].signer).mint(ethers.utils.parseEther('1')); + await pool + .connect(users[2].signer) + .deposit(weth.address, ethers.utils.parseEther('1'), users[2].address, '0'); + + // cleanup borrowings, to be abe to swap whole weth + const amountToRepay = ethers.utils.parseEther('0.5'); + await weth.mint(amountToRepay); + await pool.repay(weth.address, amountToRepay, '1', userAddress); + const txTimestamp = (await timeLatest()).plus(100); + + const { + reserveData: wethReserveDataBefore, + userData: wethUserDataBefore, + } = await getContractsData(weth.address, userAddress, testEnv); + const amountToSwap = calcExpectedATokenBalance( + wethReserveDataBefore, + wethUserDataBefore, + txTimestamp.plus('1') + ); + + await advanceBlock(txTimestamp.toNumber()); + + await pool.swapLiquidity( + _mockSwapAdapter.address, + weth.address, + dai.address, + amountToSwap.toString(), + '0x10' + ); + const {userData: wethUserDataAfter} = await getContractsData( + weth.address, + userAddress, + testEnv + ); + expect(wethUserDataAfter.usageAsCollateralEnabled).to.be.equal( + false, + 'usageAsCollateralEnabled are not set to false' + ); + }); +}); diff --git a/test/flashloan.spec.ts b/test/flashloan.spec.ts index 952bdab4..85eec131 100644 --- a/test/flashloan.spec.ts +++ b/test/flashloan.spec.ts @@ -1,3 +1,5 @@ +import BigNumber from 'bignumber.js'; + import {TestEnv, makeSuite} from './helpers/make-suite'; import {APPROVAL_AMOUNT_LENDING_POOL, oneRay} from '../helpers/constants'; import { @@ -8,7 +10,6 @@ import { import {ethers} from 'ethers'; import {MockFlashLoanReceiver} from '../types/MockFlashLoanReceiver'; import {ProtocolErrors, eContractid} from '../helpers/types'; -import BigNumber from 'bignumber.js'; import {VariableDebtToken} from '../types/VariableDebtToken'; import {StableDebtToken} from '../types/StableDebtToken'; @@ -28,7 +29,7 @@ makeSuite('LendingPool FlashLoan function', (testEnv: TestEnv) => { _mockFlashLoanReceiver = await getMockFlashLoanReceiver(); }); - it('Deposits ETH into the reserve', async () => { + it('Deposits WETH into the reserve', async () => { const {pool, weth} = testEnv; const userAddress = await pool.signer.getAddress(); const amountToDeposit = ethers.utils.parseEther('1'); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 3f9fea4b..2f3200e0 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -722,7 +722,7 @@ const getDataBeforeAction = async ( }; }; -const getTxCostAndTimestamp = async (tx: ContractReceipt) => { +export const getTxCostAndTimestamp = async (tx: ContractReceipt) => { if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) { throw new Error('No tx blocknumber'); } diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 23f2a374..575c7653 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -919,7 +919,7 @@ const calcExpectedScaledATokenBalance = ( .minus(amountTaken.rayDiv(index)); }; -const calcExpectedATokenBalance = ( +export const calcExpectedATokenBalance = ( reserveDataBeforeAction: ReserveData, userDataBeforeAction: UserReserveData, currentTimestamp: BigNumber