From 595d169f5d0d194caeea7b2256c74a8734ceec46 Mon Sep 17 00:00:00 2001 From: eboado Date: Sat, 13 Jun 2020 11:12:49 +0200 Subject: [PATCH] - Migrated all the scenarios tests, except for borrow-repay-variable --- helpers/misc-utils.ts | 13 + test/helpers/actions.ts | 1291 +++++++++++++++++-------------- test/helpers/scenario-engine.ts | 336 ++++---- test/scenario.spec.ts | 16 +- 4 files changed, 896 insertions(+), 760 deletions(-) diff --git a/helpers/misc-utils.ts b/helpers/misc-utils.ts index 15fd61d6..e1322116 100644 --- a/helpers/misc-utils.ts +++ b/helpers/misc-utils.ts @@ -32,3 +32,16 @@ export const evmSnapshot = async () => export const evmRevert = async (id: string) => BRE.ethereum.send("evm_revert", [id]); + +export const timeLatest = async () => { + const block = await BRE.ethers.provider.getBlock("latest"); + return new BigNumber(block.timestamp); +}; + +export const advanceBlock = async (timestamp: number) => + await BRE.ethers.provider.send("evm_mine", [timestamp]); + +export const increaseTime = async (secondsToIncrease: number) => + await BRE.ethers.provider.send("evm_increaseTime", [secondsToIncrease]); + + diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 2082c0f5..f1384da6 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -25,24 +25,26 @@ import { import { getMintableErc20, convertToCurrencyDecimals, + getAToken, } from "../../helpers/contracts-helpers"; -import {MOCK_ETH_ADDRESS} from "../../helpers/constants"; +import { + MOCK_ETH_ADDRESS, + ONE_YEAR, + MAX_UINT_AMOUNT, +} from "../../helpers/constants"; import {TestEnv, SignerWithAddress} from "./make-suite"; -import {BRE} from "../../helpers/misc-utils"; +import {BRE, increaseTime, timeLatest} from "../../helpers/misc-utils"; import chai from "chai"; import {ReserveData, UserReserveData} from "./utils/interfaces"; import {waitForTx} from "../__setup.spec"; import {ContractReceipt} from "ethers/contract"; import {ethers} from "ethers"; +import {AToken} from "../../types/AToken"; +import {tEthereumAddress} from "../../helpers/types"; const {expect} = chai; -const timeLatest = async () => { - const block = await BRE.ethers.provider.getBlock("latest"); - return new BigNumber(block.timestamp); -}; - const almostEqualOrEqual = function ( this: any, expected: ReserveData | UserReserveData, @@ -250,570 +252,661 @@ export const deposit = async ( } }; -// export const redeem = async ( -// reserveSymbol: string, -// amount: string, -// user: string, -// expectedResult: string, -// revertMessage?: string -// ) => { - -// const { -// aTokenInstance, -// reserve, -// txOptions, -// userData: userDataBefore, -// reserveData: reserveDataBefore, -// } = await getDataBeforeAction(reserveSymbol, user); - -// let amountToRedeem = '0'; - -// if (amount !== '-1') { -// amountToRedeem = await convertToCurrencyDecimals(reserve, amount); -// } else { -// amountToRedeem = MAX_UINT_AMOUNT; -// } - -// if (expectedResult === 'success') { -// const txResult = await aTokenInstance.redeem(amountToRedeem, txOptions); - -// const { -// reserveData: reserveDataAfter, -// userData: userDataAfter, -// timestamp, -// } = await getContractsData(reserve, user); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const expectedReserveData = calcExpectedReserveDataAfterRedeem( -// amountToRedeem, -// reserveDataBefore, -// userDataBefore, -// txTimestamp -// ); - -// const expectedUserData = calcExpectedUserDataAfterRedeem( -// amountToRedeem, -// reserveDataBefore, -// expectedReserveData, -// userDataBefore, -// txTimestamp, -// timestamp, -// txCost -// ); - -// const actualAmountRedeemed = userDataBefore.currentATokenBalance.minus( -// expectedUserData.currentATokenBalance -// ); - -// expectEqual(reserveDataAfter, expectedReserveData); -// expectEqual(userDataAfter, expectedUserData); - -// truffleAssert.eventEmitted(txResult, 'Redeem', (ev: any) => { -// const {_from, _value} = ev; -// return _from === user && new BigNumber(_value).isEqualTo(actualAmountRedeemed); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert(aTokenInstance.redeem(amountToRedeem, txOptions), revertMessage); -// } -// }; - -// export const borrow = async ( -// reserveSymbol: string, -// amount: string, -// interestRateMode: string, -// user: string, -// timeTravel: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {lendingPoolInstance, artifacts} = configuration; - -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); - -// const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( -// reserve, -// user -// ); - -// const amountToBorrow = await convertToCurrencyDecimals(reserve, amount); - -// const txOptions: any = { -// from: user, -// }; - -// if (expectedResult === 'success') { -// const txResult = await lendingPoolInstance.borrow( -// reserve, -// amountToBorrow, -// interestRateMode, -// '0', -// txOptions -// ); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// if (timeTravel) { -// const secondsToTravel = new BigNumber(timeTravel) -// .multipliedBy(ONE_YEAR) -// .div(365) -// .toNumber(); - -// await time.increase(secondsToTravel); -// } - -// const { -// reserveData: reserveDataAfter, -// userData: userDataAfter, -// timestamp, -// } = await getContractsData(reserve, user); - -// const expectedReserveData = calcExpectedReserveDataAfterBorrow( -// amountToBorrow, -// interestRateMode, -// reserveDataBefore, -// userDataBefore, -// txTimestamp, -// timestamp -// ); - -// const expectedUserData = calcExpectedUserDataAfterBorrow( -// amountToBorrow, -// interestRateMode, -// reserveDataBefore, -// expectedReserveData, -// userDataBefore, -// txTimestamp, -// timestamp, -// txCost -// ); -// expectEqual(reserveDataAfter, expectedReserveData); -// expectEqual(userDataAfter, expectedUserData); - -// truffleAssert.eventEmitted(txResult, 'Borrow', (ev: any) => { -// const {_reserve, _user, _amount, _borrowRateMode, _borrowRate, _originationFee} = ev; -// return ( -// _reserve.toLowerCase() === reserve.toLowerCase() && -// _user.toLowerCase() === user.toLowerCase() && -// new BigNumber(_amount).eq(amountToBorrow) && -// new BigNumber(_borrowRateMode).eq(expectedUserData.borrowRateMode) && -// new BigNumber(_borrowRate).eq(expectedUserData.borrowRate) && -// new BigNumber(_originationFee).eq( -// expectedUserData.originationFee.minus(userDataBefore.originationFee) -// ) -// ); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert( -// lendingPoolInstance.borrow(reserve, amountToBorrow, interestRateMode, '0', txOptions), -// revertMessage -// ); -// } -// }; - -// export const repay = async ( -// reserveSymbol: string, -// amount: string, -// user: string, -// onBehalfOf: string, -// sendValue: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {lendingPoolInstance, artifacts, ethereumAddress} = configuration; - -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); - -// const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( -// reserve, -// onBehalfOf -// ); - -// let amountToRepay = '0'; - -// if (amount !== '-1') { -// amountToRepay = await convertToCurrencyDecimals(reserve, amount); -// } else { -// amountToRepay = MAX_UINT_AMOUNT; -// } - -// const txOptions: any = { -// from: user, -// }; - -// if (ethereumAddress === reserve) { -// if (sendValue) { -// if (sendValue !== '-1') { -// const valueToSend = await convertToCurrencyDecimals(reserve, sendValue); -// txOptions.value = valueToSend; -// } else { -// txOptions.value = userDataBefore.currentBorrowBalance -// .plus(await convertToCurrencyDecimals(reserve, '0.1')) -// .toFixed(0); //add 0.1 ETH to the repayment amount to cover for accrued interest during tx execution -// } -// } else { -// txOptions.value = amountToRepay; -// } -// } - -// if (expectedResult === 'success') { -// const txResult = await lendingPoolInstance.repay(reserve, amountToRepay, onBehalfOf, txOptions); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const { -// reserveData: reserveDataAfter, -// userData: userDataAfter, -// timestamp, -// } = await getContractsData(reserve, onBehalfOf); - -// const expectedReserveData = calcExpectedReserveDataAfterRepay( -// amountToRepay, -// reserveDataBefore, -// userDataBefore, -// txTimestamp, -// timestamp -// ); - -// const expectedUserData = calcExpectedUserDataAfterRepay( -// amountToRepay, -// reserveDataBefore, -// expectedReserveData, -// userDataBefore, -// user, -// onBehalfOf, -// txTimestamp, -// timestamp, -// txCost -// ); - -// expectEqual(reserveDataAfter, expectedReserveData); -// expectEqual(userDataAfter, expectedUserData); - -// truffleAssert.eventEmitted(txResult, 'Repay', (ev: any) => { -// const {_reserve, _user, _repayer} = ev; - -// return ( -// _reserve.toLowerCase() === reserve.toLowerCase() && -// _user.toLowerCase() === onBehalfOf.toLowerCase() && -// _repayer.toLowerCase() === user.toLowerCase() -// ); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert( -// lendingPoolInstance.repay(reserve, amountToRepay, onBehalfOf, txOptions), -// revertMessage -// ); -// } -// }; - -// export const setUseAsCollateral = async ( -// reserveSymbol: string, -// user: string, -// useAsCollateral: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {lendingPoolInstance, artifacts} = configuration; - -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); - -// const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( -// reserve, -// user -// ); - -// const txOptions: any = { -// from: user, -// }; - -// const useAsCollateralBool = useAsCollateral.toLowerCase() === 'true'; - -// if (expectedResult === 'success') { -// const txResult = await lendingPoolInstance.setUserUseReserveAsCollateral( -// reserve, -// useAsCollateralBool, -// txOptions -// ); - -// const {txCost} = await getTxCostAndTimestamp(txResult); - -// const {userData: userDataAfter} = await getContractsData(reserve, user); - -// const expectedUserData = calcExpectedUserDataAfterSetUseAsCollateral( -// useAsCollateral.toLocaleLowerCase() === 'true', -// reserveDataBefore, -// userDataBefore, -// txCost -// ); - -// expectEqual(userDataAfter, expectedUserData); -// if (useAsCollateralBool) { -// truffleAssert.eventEmitted(txResult, 'ReserveUsedAsCollateralEnabled', (ev: any) => { -// const {_reserve, _user} = ev; -// return _reserve === reserve && _user === user; -// }); -// } else { -// truffleAssert.eventEmitted(txResult, 'ReserveUsedAsCollateralDisabled', (ev: any) => { -// const {_reserve, _user} = ev; -// return _reserve === reserve && _user === user; -// }); -// } -// } else if (expectedResult === 'revert') { -// await expectRevert( -// lendingPoolInstance.setUserUseReserveAsCollateral(reserve, useAsCollateralBool, txOptions), -// revertMessage -// ); -// } -// }; - -// export const swapBorrowRateMode = async ( -// reserveSymbol: string, -// user: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {lendingPoolInstance, artifacts} = configuration; - -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); - -// const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( -// reserve, -// user -// ); - -// const txOptions: any = { -// from: user, -// }; - -// if (expectedResult === 'success') { -// const txResult = await lendingPoolInstance.swapBorrowRateMode(reserve, txOptions); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const {reserveData: reserveDataAfter, userData: userDataAfter} = await getContractsData( -// reserve, -// user -// ); - -// const expectedReserveData = calcExpectedReserveDataAfterSwapRateMode( -// reserveDataBefore, -// userDataBefore, -// txTimestamp -// ); - -// const expectedUserData = calcExpectedUserDataAfterSwapRateMode( -// reserveDataBefore, -// expectedReserveData, -// userDataBefore, -// txCost, -// txTimestamp -// ); - -// expectEqual(reserveDataAfter, expectedReserveData); -// expectEqual(userDataAfter, expectedUserData); - -// truffleAssert.eventEmitted(txResult, 'Swap', (ev: any) => { -// const {_user, _reserve, _newRateMode, _newRate} = ev; -// return ( -// _user === user && -// _reserve == reserve && -// new BigNumber(_newRateMode).eq(expectedUserData.borrowRateMode) && -// new BigNumber(_newRate).eq(expectedUserData.borrowRate) -// ); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert(lendingPoolInstance.swapBorrowRateMode(reserve, txOptions), revertMessage); -// } -// }; - -// export const rebalanceStableBorrowRate = async ( -// reserveSymbol: string, -// user: string, -// target: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {lendingPoolInstance, artifacts} = configuration; - -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); - -// const {reserveData: reserveDataBefore, userData: userDataBefore} = await getContractsData( -// reserve, -// target -// ); - -// const txOptions: any = { -// from: user, -// }; - -// if (expectedResult === 'success') { -// const txResult = await lendingPoolInstance.rebalanceStableBorrowRate( -// reserve, -// target, -// txOptions -// ); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const {reserveData: reserveDataAfter, userData: userDataAfter} = await getContractsData( -// reserve, -// target -// ); - -// const expectedReserveData = calcExpectedReserveDataAfterStableRateRebalance( -// reserveDataBefore, -// userDataBefore, -// txTimestamp -// ); - -// const expectedUserData = calcExpectedUserDataAfterStableRateRebalance( -// reserveDataBefore, -// expectedReserveData, -// userDataBefore, -// txCost, -// txTimestamp -// ); - -// expectEqual(reserveDataAfter, expectedReserveData); -// expectEqual(userDataAfter, expectedUserData); - -// truffleAssert.eventEmitted(txResult, 'RebalanceStableBorrowRate', (ev: any) => { -// const {_user, _reserve, _newStableRate} = ev; -// return ( -// _user.toLowerCase() === target.toLowerCase() && -// _reserve.toLowerCase() === reserve.toLowerCase() && -// new BigNumber(_newStableRate).eq(expectedUserData.borrowRate) -// ); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert( -// lendingPoolInstance.rebalanceStableBorrowRate(reserve, target, txOptions), -// revertMessage -// ); -// } -// }; - -// export const redirectInterestStream = async ( -// reserveSymbol: string, -// user: string, -// to: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const { -// aTokenInstance, -// reserve, -// txOptions, -// userData: fromDataBefore, -// reserveData: reserveDataBefore, -// } = await getDataBeforeAction(reserveSymbol, user); - -// const {userData: toDataBefore} = await getContractsData(reserve, to); - -// if (expectedResult === 'success') { -// const txResult = await aTokenInstance.redirectInterestStream(to, txOptions); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const {userData: fromDataAfter} = await getContractsData(reserve, user); - -// const {userData: toDataAfter} = await getContractsData(reserve, to); - -// const [expectedFromData, expectedToData] = calcExpectedUsersDataAfterRedirectInterest( -// reserveDataBefore, -// fromDataBefore, -// toDataBefore, -// user, -// to, -// true, -// txCost, -// txTimestamp -// ); - -// expectEqual(fromDataAfter, expectedFromData); -// expectEqual(toDataAfter, expectedToData); - -// truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => { -// const {_from, _to} = ev; -// return _from === user -// && _to === (to === user ? NIL_ADDRESS : to); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert(aTokenInstance.redirectInterestStream(to, txOptions), revertMessage); -// } -// }; - -// export const redirectInterestStreamOf = async ( -// reserveSymbol: string, -// user: string, -// from: string, -// to: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const { -// aTokenInstance, -// reserve, -// txOptions, -// userData: fromDataBefore, -// reserveData: reserveDataBefore, -// } = await getDataBeforeAction(reserveSymbol, from); - -// const {userData: toDataBefore} = await getContractsData(reserve, user); - -// if (expectedResult === 'success') { -// const txResult = await aTokenInstance.redirectInterestStreamOf(from, to, txOptions); - -// const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); - -// const {userData: fromDataAfter} = await getContractsData(reserve, from); - -// const {userData: toDataAfter} = await getContractsData(reserve, to); - -// const [expectedFromData, exptectedToData] = calcExpectedUsersDataAfterRedirectInterest( -// reserveDataBefore, -// fromDataBefore, -// toDataBefore, -// from, -// to, -// from === user, -// txCost, -// txTimestamp -// ); - -// expectEqual(fromDataAfter, expectedFromData); -// expectEqual(toDataAfter, exptectedToData); - -// truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => { -// const {_from, _to} = ev; -// return _from.toLowerCase() === from.toLowerCase() && _to.toLowerCase() === to.toLowerCase(); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert(aTokenInstance.redirectInterestStreamOf(from, to, txOptions), revertMessage); -// } -// }; - -// export const allowInterestRedirectionTo = async ( -// reserveSymbol: string, -// user: string, -// to: string, -// expectedResult: string, -// revertMessage?: string -// ) => { -// const {aTokenInstance, txOptions} = await getDataBeforeAction(reserveSymbol, user); - -// if (expectedResult === 'success') { -// const txResult = await aTokenInstance.allowInterestRedirectionTo(to, txOptions); - -// truffleAssert.eventEmitted(txResult, 'InterestRedirectionAllowanceChanged', (ev: any) => { -// const {_from, _to} = ev; -// return _from.toLowerCase() === user.toLowerCase() && _to.toLowerCase() === to.toLowerCase(); -// }); -// } else if (expectedResult === 'revert') { -// await expectRevert(aTokenInstance.allowInterestRedirectionTo(to, txOptions), revertMessage); -// } -// }; +export const redeem = async ( + reserveSymbol: string, + amount: string, + user: SignerWithAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const { + aTokenInstance, + reserve, + userData: userDataBefore, + reserveData: reserveDataBefore, + } = await getDataBeforeAction(reserveSymbol, user.address, testEnv); + + let amountToRedeem = "0"; + + if (amount !== "-1") { + amountToRedeem = ( + await convertToCurrencyDecimals(reserve, amount) + ).toString(); + } else { + amountToRedeem = MAX_UINT_AMOUNT; + } + + if (expectedResult === "success") { + const txResult = await waitForTx( + await aTokenInstance.connect(user.signer).redeem(amountToRedeem) + ); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + timestamp, + } = await getContractsData(reserve, user.address, testEnv); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const expectedReserveData = calcExpectedReserveDataAfterRedeem( + amountToRedeem, + reserveDataBefore, + userDataBefore, + txTimestamp + ); + + const expectedUserData = calcExpectedUserDataAfterRedeem( + amountToRedeem, + reserveDataBefore, + expectedReserveData, + userDataBefore, + txTimestamp, + timestamp, + txCost + ); + + const actualAmountRedeemed = userDataBefore.currentATokenBalance.minus( + expectedUserData.currentATokenBalance + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, "Redeem", (ev: any) => { + // const {_from, _value} = ev; + // return ( + // _from === user && new BigNumber(_value).isEqualTo(actualAmountRedeemed) + // ); + // }); + } else if (expectedResult === "revert") { + await expect( + aTokenInstance.connect(user.signer).redeem(amountToRedeem), + revertMessage + ).to.be.reverted; + } +}; + +export const borrow = async ( + reserveSymbol: string, + amount: string, + interestRateMode: string, + user: SignerWithAddress, + timeTravel: string, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {pool} = testEnv; + + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + + const { + reserveData: reserveDataBefore, + userData: userDataBefore, + } = await getContractsData(reserve, user.address, testEnv); + + const amountToBorrow = await convertToCurrencyDecimals(reserve, amount); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await pool + .connect(user.signer) + .borrow(reserve, amountToBorrow, interestRateMode, "0") + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + if (timeTravel) { + const secondsToTravel = new BigNumber(timeTravel) + .multipliedBy(ONE_YEAR) + .div(365) + .toNumber(); + + await increaseTime(secondsToTravel); + // await advanceBlock(new Date().getTime()); + // TODO + // await time.increase(secondsToTravel); + } + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + timestamp, + } = await getContractsData(reserve, user.address, testEnv); + + const expectedReserveData = calcExpectedReserveDataAfterBorrow( + amountToBorrow.toString(), + interestRateMode, + reserveDataBefore, + userDataBefore, + txTimestamp, + timestamp + ); + + const expectedUserData = calcExpectedUserDataAfterBorrow( + amountToBorrow.toString(), + interestRateMode, + reserveDataBefore, + expectedReserveData, + userDataBefore, + txTimestamp, + timestamp, + txCost + ); + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, "Borrow", (ev: any) => { + // const { + // _reserve, + // _user, + // _amount, + // _borrowRateMode, + // _borrowRate, + // _originationFee, + // } = ev; + // return ( + // _reserve.toLowerCase() === reserve.toLowerCase() && + // _user.toLowerCase() === user.toLowerCase() && + // new BigNumber(_amount).eq(amountToBorrow) && + // new BigNumber(_borrowRateMode).eq(expectedUserData.borrowRateMode) && + // new BigNumber(_borrowRate).eq(expectedUserData.borrowRate) && + // new BigNumber(_originationFee).eq( + // expectedUserData.originationFee.minus(userDataBefore.originationFee) + // ) + // ); + // }); + } else if (expectedResult === "revert") { + await expect( + pool + .connect(user.signer) + .borrow(reserve, amountToBorrow, interestRateMode, "0"), + revertMessage + ).to.be.reverted; + } +}; + +export const repay = async ( + reserveSymbol: string, + amount: string, + user: SignerWithAddress, + onBehalfOf: SignerWithAddress, + sendValue: string, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {pool} = testEnv; + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + + const { + reserveData: reserveDataBefore, + userData: userDataBefore, + } = await getContractsData(reserve, onBehalfOf.address, testEnv); + + let amountToRepay = "0"; + + if (amount !== "-1") { + amountToRepay = ( + await convertToCurrencyDecimals(reserve, amount) + ).toString(); + } else { + amountToRepay = ethers.utils.bigNumberify(MAX_UINT_AMOUNT).toString(); + } + amountToRepay = "0x" + new BigNumber(amountToRepay).toString(16); + + const txOptions: any = {}; + + if (MOCK_ETH_ADDRESS === reserve) { + if (sendValue) { + if (sendValue !== "-1") { + const valueToSend = await convertToCurrencyDecimals(reserve, sendValue); + txOptions.value = + "0x" + new BigNumber(valueToSend.toString()).toString(16); + } else { + txOptions.value = + "0x" + + userDataBefore.currentBorrowBalance + .plus((await convertToCurrencyDecimals(reserve, "0.1")).toString()) + .toString(16); //add 0.1 ETH to the repayment amount to cover for accrued interest during tx execution + } + } else { + txOptions.value = amountToRepay; + } + } + + if (expectedResult === "success") { + const txResult = await waitForTx( + await pool + .connect(user.signer) + .repay(reserve, amountToRepay, onBehalfOf.address, txOptions) + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + timestamp, + } = await getContractsData(reserve, onBehalfOf.address, testEnv); + + const expectedReserveData = calcExpectedReserveDataAfterRepay( + amountToRepay, + reserveDataBefore, + userDataBefore, + txTimestamp, + timestamp + ); + + const expectedUserData = calcExpectedUserDataAfterRepay( + amountToRepay, + reserveDataBefore, + expectedReserveData, + userDataBefore, + user.address, + onBehalfOf.address, + txTimestamp, + timestamp, + txCost + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, "Repay", (ev: any) => { + // const {_reserve, _user, _repayer} = ev; + + // return ( + // _reserve.toLowerCase() === reserve.toLowerCase() && + // _user.toLowerCase() === onBehalfOf.toLowerCase() && + // _repayer.toLowerCase() === user.toLowerCase() + // ); + // }); + } else if (expectedResult === "revert") { + await expect( + pool + .connect(user.signer) + .repay(reserve, amountToRepay, onBehalfOf.address, txOptions), + revertMessage + ).to.be.reverted; + } +}; + +export const setUseAsCollateral = async ( + reserveSymbol: string, + user: SignerWithAddress, + useAsCollateral: string, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {pool} = testEnv; + + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + + const { + reserveData: reserveDataBefore, + userData: userDataBefore, + } = await getContractsData(reserve, user.address, testEnv); + + const useAsCollateralBool = useAsCollateral.toLowerCase() === "true"; + + if (expectedResult === "success") { + const txResult = await waitForTx( + await pool + .connect(user.signer) + .setUserUseReserveAsCollateral(reserve, useAsCollateralBool) + ); + + const {txCost} = await getTxCostAndTimestamp(txResult); + + const {userData: userDataAfter} = await getContractsData( + reserve, + user.address, + testEnv + ); + + const expectedUserData = calcExpectedUserDataAfterSetUseAsCollateral( + useAsCollateral.toLocaleLowerCase() === "true", + reserveDataBefore, + userDataBefore, + txCost + ); + + expectEqual(userDataAfter, expectedUserData); + // if (useAsCollateralBool) { + // truffleAssert.eventEmitted(txResult, 'ReserveUsedAsCollateralEnabled', (ev: any) => { + // const {_reserve, _user} = ev; + // return _reserve === reserve && _user === user; + // }); + // } else { + // truffleAssert.eventEmitted(txResult, 'ReserveUsedAsCollateralDisabled', (ev: any) => { + // const {_reserve, _user} = ev; + // return _reserve === reserve && _user === user; + // }); + // } + } else if (expectedResult === "revert") { + await expect( + pool + .connect(user.signer) + .setUserUseReserveAsCollateral(reserve, useAsCollateralBool), + revertMessage + ).to.be.reverted; + } +}; + +export const swapBorrowRateMode = async ( + reserveSymbol: string, + user: SignerWithAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {pool} = testEnv; + + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + + const { + reserveData: reserveDataBefore, + userData: userDataBefore, + } = await getContractsData(reserve, user.address, testEnv); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await pool.connect(user.signer).swapBorrowRateMode(reserve) + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + } = await getContractsData(reserve, user.address, testEnv); + + const expectedReserveData = calcExpectedReserveDataAfterSwapRateMode( + reserveDataBefore, + userDataBefore, + txTimestamp + ); + + const expectedUserData = calcExpectedUserDataAfterSwapRateMode( + reserveDataBefore, + expectedReserveData, + userDataBefore, + txCost, + txTimestamp + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, "Swap", (ev: any) => { + // const {_user, _reserve, _newRateMode, _newRate} = ev; + // return ( + // _user === user && + // _reserve == reserve && + // new BigNumber(_newRateMode).eq(expectedUserData.borrowRateMode) && + // new BigNumber(_newRate).eq(expectedUserData.borrowRate) + // ); + // }); + } else if (expectedResult === "revert") { + await expect( + pool.connect(user.signer).swapBorrowRateMode(reserve), + revertMessage + ).to.be.reverted; + } +}; + +export const rebalanceStableBorrowRate = async ( + reserveSymbol: string, + user: SignerWithAddress, + target: SignerWithAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {pool} = testEnv; + + const reserve = await getReserveAddressFromSymbol(reserveSymbol); + + const { + reserveData: reserveDataBefore, + userData: userDataBefore, + } = await getContractsData(reserve, target.address, testEnv); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await pool + .connect(user.signer) + .rebalanceStableBorrowRate(reserve, target.address) + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const { + reserveData: reserveDataAfter, + userData: userDataAfter, + } = await getContractsData(reserve, target.address, testEnv); + + const expectedReserveData = calcExpectedReserveDataAfterStableRateRebalance( + reserveDataBefore, + userDataBefore, + txTimestamp + ); + + const expectedUserData = calcExpectedUserDataAfterStableRateRebalance( + reserveDataBefore, + expectedReserveData, + userDataBefore, + txCost, + txTimestamp + ); + + expectEqual(reserveDataAfter, expectedReserveData); + expectEqual(userDataAfter, expectedUserData); + + // truffleAssert.eventEmitted(txResult, 'RebalanceStableBorrowRate', (ev: any) => { + // const {_user, _reserve, _newStableRate} = ev; + // return ( + // _user.toLowerCase() === target.toLowerCase() && + // _reserve.toLowerCase() === reserve.toLowerCase() && + // new BigNumber(_newStableRate).eq(expectedUserData.borrowRate) + // ); + // }); + } else if (expectedResult === "revert") { + await expect( + pool + .connect(user.signer) + .rebalanceStableBorrowRate(reserve, target.address), + revertMessage + ).to.be.reverted; + } +}; + +export const redirectInterestStream = async ( + reserveSymbol: string, + user: SignerWithAddress, + to: tEthereumAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const { + aTokenInstance, + reserve, + userData: fromDataBefore, + reserveData: reserveDataBefore, + } = await getDataBeforeAction(reserveSymbol, user.address, testEnv); + + const {userData: toDataBefore} = await getContractsData(reserve, to, testEnv); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await aTokenInstance.connect(user.signer).redirectInterestStream(to) + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const {userData: fromDataAfter} = await getContractsData( + reserve, + user.address, + testEnv + ); + + const {userData: toDataAfter} = await getContractsData( + reserve, + to, + testEnv + ); + + const [ + expectedFromData, + expectedToData, + ] = calcExpectedUsersDataAfterRedirectInterest( + reserveDataBefore, + fromDataBefore, + toDataBefore, + user.address, + to, + true, + txCost, + txTimestamp + ); + + expectEqual(fromDataAfter, expectedFromData); + expectEqual(toDataAfter, expectedToData); + + // truffleAssert.eventEmitted(txResult, 'InterestStreamRedirected', (ev: any) => { + // const {_from, _to} = ev; + // return _from === user + // && _to === (to === user ? NIL_ADDRESS : to); + // }); + } else if (expectedResult === "revert") { + await expect( + aTokenInstance.connect(user.signer).redirectInterestStream(to), + revertMessage + ).to.be.reverted; + } +}; + +export const redirectInterestStreamOf = async ( + reserveSymbol: string, + user: SignerWithAddress, + from: tEthereumAddress, + to: tEthereumAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const { + aTokenInstance, + reserve, + userData: fromDataBefore, + reserveData: reserveDataBefore, + } = await getDataBeforeAction(reserveSymbol, from, testEnv); + + const {userData: toDataBefore} = await getContractsData( + reserve, + user.address, + testEnv + ); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await aTokenInstance + .connect(user.signer) + .redirectInterestStreamOf(from, to) + ); + + const {txCost, txTimestamp} = await getTxCostAndTimestamp(txResult); + + const {userData: fromDataAfter} = await getContractsData( + reserve, + from, + testEnv + ); + + const {userData: toDataAfter} = await getContractsData( + reserve, + to, + testEnv + ); + + const [ + expectedFromData, + exptectedToData, + ] = calcExpectedUsersDataAfterRedirectInterest( + reserveDataBefore, + fromDataBefore, + toDataBefore, + from, + to, + from === user.address, + txCost, + txTimestamp + ); + + expectEqual(fromDataAfter, expectedFromData); + expectEqual(toDataAfter, exptectedToData); + + // truffleAssert.eventEmitted( + // txResult, + // "InterestStreamRedirected", + // (ev: any) => { + // const {_from, _to} = ev; + // return ( + // _from.toLowerCase() === from.toLowerCase() && + // _to.toLowerCase() === to.toLowerCase() + // ); + // } + // ); + } else if (expectedResult === "revert") { + await expect( + aTokenInstance.connect(user.signer).redirectInterestStreamOf(from, to), + revertMessage + ).to.be.reverted; + } +}; + +export const allowInterestRedirectionTo = async ( + reserveSymbol: string, + user: SignerWithAddress, + to: tEthereumAddress, + expectedResult: string, + testEnv: TestEnv, + revertMessage?: string +) => { + const {aTokenInstance} = await getDataBeforeAction( + reserveSymbol, + user.address, + testEnv + ); + + if (expectedResult === "success") { + const txResult = await waitForTx( + await aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to) + ); + + // truffleAssert.eventEmitted( + // txResult, + // "InterestRedirectionAllowanceChanged", + // (ev: any) => { + // const {_from, _to} = ev; + // return ( + // _from.toLowerCase() === user.toLowerCase() && + // _to.toLowerCase() === to.toLowerCase() + // ); + // } + // ); + } else if (expectedResult === "revert") { + await expect( + aTokenInstance.connect(user.signer).allowInterestRedirectionTo(to), + revertMessage + ).to.be.reverted; + } +}; const expectEqual = ( actual: UserReserveData | ReserveData, @@ -825,41 +918,35 @@ const expectEqual = ( } }; -// interface ActionData { -// reserve: string; -// reserveData: ReserveData; -// userData: UserReserveData; -// aTokenInstance: ATokenInstance; -// txOptions: any; -// } +interface ActionData { + reserve: string; + reserveData: ReserveData; + userData: UserReserveData; + aTokenInstance: AToken; +} -// const getDataBeforeAction = async (reserveSymbol: string, user: string): Promise => { -// const {artifacts} = configuration; +const getDataBeforeAction = async ( + reserveSymbol: string, + user: tEthereumAddress, + testEnv: TestEnv +): Promise => { + const reserve = await getReserveAddressFromSymbol(reserveSymbol); -// const reserve = await getReserveAddressFromSymbol(reserveSymbol, artifacts); + const {reserveData, userData} = await getContractsData( + reserve, + user, + testEnv + ); -// const {reserveData, userData} = await getContractsData(reserve, user); + const aTokenInstance = await getAToken(reserveData.aTokenAddress); -// const {aTokenAddress} = reserveData; - -// const aTokenInstance: ATokenInstance = await getTruffleContractInstance( -// artifacts, -// ContractId.AToken, -// aTokenAddress -// ); - -// const txOptions: any = { -// from: user, -// }; - -// return { -// reserve, -// reserveData, -// userData, -// aTokenInstance, -// txOptions, -// }; -// }; + return { + reserve, + reserveData, + userData, + aTokenInstance, + }; +}; const getTxCostAndTimestamp = async (tx: ContractReceipt) => { if (!tx.blockNumber || !tx.transactionHash || !tx.cumulativeGasUsed) { diff --git a/test/helpers/scenario-engine.ts b/test/helpers/scenario-engine.ts index f6a70537..51a22ccf 100644 --- a/test/helpers/scenario-engine.ts +++ b/test/helpers/scenario-engine.ts @@ -1,5 +1,19 @@ import {TestEnv, SignerWithAddress} from "./make-suite"; -import {mint, approve, deposit} from "./actions"; +import { + mint, + approve, + deposit, + borrow, + redeem, + repay, + setUseAsCollateral, + swapBorrowRateMode, + rebalanceStableBorrowRate, + redirectInterestStream, + redirectInterestStreamOf, + allowInterestRedirectionTo, +} from "./actions"; +import {RateMode} from "../../helpers/types"; export interface Action { name: string; @@ -31,7 +45,7 @@ const executeAction = async ( users: SignerWithAddress[], testEnv: TestEnv ) => { - const {reserve, user} = action.args; + const {reserve, user: userIndex} = action.args; const {name, expected, revertMessage} = action; if (!name || name === "") { @@ -40,7 +54,7 @@ const executeAction = async ( if (!reserve || reserve === "") { throw "Invalid reserve selected for deposit"; } - if (!user || user === "") { + if (!userIndex || userIndex === "") { throw `Invalid user selected to deposit into the ${reserve} reserve`; } @@ -48,7 +62,7 @@ const executeAction = async ( throw `An expected resut for action ${name} is required`; } - const userAddress = users[parseInt(user)]; + const user = users[parseInt(userIndex)]; switch (name) { case "mint": @@ -58,11 +72,11 @@ const executeAction = async ( throw `Invalid amount of ${reserve} to mint`; } - await mint(reserve, amount, userAddress); + await mint(reserve, amount, user); break; case "approve": - await approve(reserve, userAddress, testEnv); + await approve(reserve, user, testEnv); break; case "deposit": @@ -76,7 +90,7 @@ const executeAction = async ( await deposit( reserve, amount, - userAddress, + user, sendValue, expected, testEnv, @@ -85,181 +99,189 @@ const executeAction = async ( } break; - // case "redeem": - // { - // const {amount} = action.args; + case "redeem": + { + const {amount} = action.args; - // if (!amount || amount === "") { - // throw `Invalid amount to redeem from the ${reserve} reserve`; - // } + if (!amount || amount === "") { + throw `Invalid amount to redeem from the ${reserve} reserve`; + } - // await redeem(reserve, amount, userAddress, expected, revertMessage); - // } - // break; - // case "borrow": - // { - // const {amount, borrowRateMode, timeTravel} = action.args; + await redeem(reserve, amount, user, expected, testEnv, revertMessage); + } + break; + case "borrow": + { + const {amount, borrowRateMode, timeTravel} = action.args; - // if (!amount || amount === "") { - // throw `Invalid amount to borrow from the ${reserve} reserve`; - // } + if (!amount || amount === "") { + throw `Invalid amount to borrow from the ${reserve} reserve`; + } - // let rateMode: string = RateMode.None; + let rateMode: string = RateMode.None; - // if (borrowRateMode === "none") { - // RateMode.None; - // } else if (borrowRateMode === "stable") { - // rateMode = RateMode.Stable; - // } else if (borrowRateMode === "variable") { - // rateMode = RateMode.Variable; - // } else { - // //random value, to test improper selection of the parameter - // rateMode = "4"; - // } + if (borrowRateMode === "none") { + RateMode.None; + } else if (borrowRateMode === "stable") { + rateMode = RateMode.Stable; + } else if (borrowRateMode === "variable") { + rateMode = RateMode.Variable; + } else { + //random value, to test improper selection of the parameter + rateMode = "4"; + } - // await borrow( - // reserve, - // amount, - // rateMode, - // userAddress, - // timeTravel, - // expected, - // revertMessage - // ); - // } - // break; + await borrow( + reserve, + amount, + rateMode, + user, + timeTravel, + expected, + testEnv, + revertMessage + ); + } + break; - // case "repay": - // { - // const {amount, sendValue} = action.args; - // let {onBehalfOf} = action.args; + case "repay": + { + const {amount, sendValue} = action.args; + let {onBehalfOf: onBehalfOfIndex} = action.args; - // if (!amount || amount === "") { - // throw `Invalid amount to repay into the ${reserve} reserve`; - // } + if (!amount || amount === "") { + throw `Invalid amount to repay into the ${reserve} reserve`; + } - // if (!onBehalfOf || onBehalfOf === "") { - // console.log( - // "WARNING: No onBehalfOf specified for a repay action. Defaulting to the repayer address" - // ); - // onBehalfOf = userAddress; - // } else { - // onBehalfOf = users[parseInt(onBehalfOf)]; - // } + let userToRepayOnBehalf: SignerWithAddress; + if (!onBehalfOfIndex || onBehalfOfIndex === "") { + console.log( + "WARNING: No onBehalfOf specified for a repay action. Defaulting to the repayer address" + ); + userToRepayOnBehalf = user; + } else { + userToRepayOnBehalf = users[parseInt(onBehalfOfIndex)]; + } - // await repay( - // reserve, - // amount, - // userAddress, - // onBehalfOf, - // sendValue, - // expected, - // revertMessage - // ); - // } - // break; + await repay( + reserve, + amount, + user, + userToRepayOnBehalf, + sendValue, + expected, + testEnv, + revertMessage + ); + } + break; - // case "setUseAsCollateral": - // { - // const {useAsCollateral} = action.args; + case "setUseAsCollateral": + { + const {useAsCollateral} = action.args; - // if (!useAsCollateral || useAsCollateral === "") { - // throw `A valid value for useAsCollateral needs to be set when calling setUseReserveAsCollateral on reserve ${reserve}`; - // } - // await setUseAsCollateral( - // reserve, - // userAddress, - // useAsCollateral, - // expected, - // revertMessage - // ); - // } - // break; + if (!useAsCollateral || useAsCollateral === "") { + throw `A valid value for useAsCollateral needs to be set when calling setUseReserveAsCollateral on reserve ${reserve}`; + } + await setUseAsCollateral( + reserve, + user, + useAsCollateral, + expected, + testEnv, + revertMessage + ); + } + break; - // case "swapBorrowRateMode": - // await swapBorrowRateMode(reserve, userAddress, expected, revertMessage); - // break; + case "swapBorrowRateMode": + await swapBorrowRateMode(reserve, user, expected, testEnv, revertMessage); + break; - // case "rebalanceStableBorrowRate": - // { - // const {target} = action.args; + case "rebalanceStableBorrowRate": + { + const {target: targetIndex} = action.args; - // if (!target || target === "") { - // throw `A target must be selected when trying to rebalance a stable rate`; - // } - // const targetAddress = users[parseInt(target)]; + if (!targetIndex || targetIndex === "") { + throw `A target must be selected when trying to rebalance a stable rate`; + } + const target = users[parseInt(targetIndex)]; - // await rebalanceStableBorrowRate( - // reserve, - // userAddress, - // targetAddress, - // expected, - // revertMessage - // ); - // } - // break; + await rebalanceStableBorrowRate( + reserve, + user, + target, + expected, + testEnv, + revertMessage + ); + } + break; - // case "redirectInterestStream": - // { - // const {to} = action.args; + case "redirectInterestStream": + { + const {to: toIndex} = action.args; - // if (!to || to === "") { - // throw `A target must be selected when trying to redirect the interest`; - // } - // const toAddress = users[parseInt(to)]; + if (!toIndex || toIndex === "") { + throw `A target must be selected when trying to redirect the interest`; + } + const toUser = users[parseInt(toIndex)]; - // await redirectInterestStream( - // reserve, - // userAddress, - // toAddress, - // expected, - // revertMessage - // ); - // } - // break; + await redirectInterestStream( + reserve, + user, + toUser.address, + expected, + testEnv, + revertMessage + ); + } + break; - // case "redirectInterestStreamOf": - // { - // const {from, to} = action.args; + case "redirectInterestStreamOf": + { + const {from: fromIndex, to: toIndex} = action.args; - // if (!from || from === "") { - // throw `A from address must be specified when trying to redirect the interest`; - // } - // if (!to || to === "") { - // throw `A target must be selected when trying to redirect the interest`; - // } - // const toAddress = users[parseInt(to)]; - // const fromAddress = users[parseInt(from)]; + if (!fromIndex || fromIndex === "") { + throw `A from address must be specified when trying to redirect the interest`; + } + if (!toIndex || toIndex === "") { + throw `A target must be selected when trying to redirect the interest`; + } + const toUser = users[parseInt(toIndex)]; + const fromUser = users[parseInt(fromIndex)]; - // await redirectInterestStreamOf( - // reserve, - // userAddress, - // fromAddress, - // toAddress, - // expected, - // revertMessage - // ); - // } - // break; + await redirectInterestStreamOf( + reserve, + user, + fromUser.address, + toUser.address, + expected, + testEnv, + revertMessage + ); + } + break; - // case "allowInterestRedirectionTo": - // { - // const {to} = action.args; + case "allowInterestRedirectionTo": + { + const {to: toIndex} = action.args; - // if (!to || to === "") { - // throw `A target must be selected when trying to redirect the interest`; - // } - // const toAddress = users[parseInt(to)]; + if (!toIndex || toIndex === "") { + throw `A target must be selected when trying to redirect the interest`; + } + const toUser = users[parseInt(toIndex)]; - // await allowInterestRedirectionTo( - // reserve, - // userAddress, - // toAddress, - // expected, - // revertMessage - // ); - // } - // break; + await allowInterestRedirectionTo( + reserve, + user, + toUser.address, + expected, + testEnv, + revertMessage + ); + } + break; default: throw `Invalid action requested: ${name}`; } diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 4517746e..181896c7 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -13,7 +13,21 @@ BigNumber.config({DECIMAL_PLACES: 0, ROUNDING_MODE: BigNumber.ROUND_DOWN}); const scenarioFolder = "./test/helpers/scenarios/"; fs.readdirSync(scenarioFolder).forEach((file) => { - if (file !== "deposit.json") return; + if ( + ![ + "borrow-negatives.json", + "borrow-repay-stable.json", + "deposit.json", + "redeem-negatives.json", + "redeem.json", + "set-use-as-collateral.json", + "swap-rate-mode.json", + "rebalance-stable-rate.json", + "interest-redirection.json", + "interest-redirection-negatives.json", + ].includes(file) + ) + return; const scenario = require(`./helpers/scenarios/${file}`);