diff --git a/buidler.config.ts b/buidler.config.ts index 9db632a2..03f42645 100644 --- a/buidler.config.ts +++ b/buidler.config.ts @@ -9,7 +9,8 @@ usePlugin('buidler-typechain'); usePlugin('solidity-coverage'); usePlugin('@nomiclabs/buidler-waffle'); usePlugin('@nomiclabs/buidler-etherscan'); -usePlugin('buidler-gas-reporter'); +//usePlugin('buidler-gas-reporter'); + const DEFAULT_BLOCK_GAS_LIMIT = 10000000; const DEFAULT_GAS_PRICE = 10; const HARDFORK = 'istanbul'; diff --git a/contracts/tokenization/StableDebtToken.sol b/contracts/tokenization/StableDebtToken.sol index 6401bfc9..fe352b32 100644 --- a/contracts/tokenization/StableDebtToken.sol +++ b/contracts/tokenization/StableDebtToken.sol @@ -167,6 +167,10 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { uint256 previousSupply = totalSupply(); + //since the total supply and each single user debt accrue separately, + //there might be accumulation errors so that the last borrower repaying + //might actually try to repay more than the available debt supply. + //in this case we simply set the total supply and the avg stable rate to 0 if (previousSupply <= amount) { _avgStableRate = 0; _totalSupply = 0; @@ -232,32 +236,51 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { ); } + /** + * @dev returns the principal, total supply and the average borrow rate + **/ function getSupplyData() public override view returns (uint256, uint256, uint256) { uint256 avgRate = _avgStableRate; return (super.totalSupply(), _calcTotalSupply(avgRate), avgRate); } + /** + * @dev returns the the total supply and the average stable rate + **/ function getTotalSupplyAndAvgRate() public override view returns (uint256, uint256) { uint256 avgRate = _avgStableRate; return (_calcTotalSupply(avgRate), avgRate); } + /** + * @dev returns the total supply + **/ function totalSupply() public override view returns (uint256) { return _calcTotalSupply(_avgStableRate); } - + + /** + * @dev returns the timestamp at which the total supply was updated + **/ function getTotalSupplyLastUpdated() public override view returns(uint40) { return _totalSupplyTimestamp; } /** * @dev Returns the principal debt balance of the user from + * @param user the user * @return The debt balance of the user since the last burn/mint action **/ function principalBalanceOf(address user) external virtual override view returns (uint256) { return super.balanceOf(user); } + + /** + * @dev calculates the total supply + * @param avgRate the average rate at which calculate the total supply + * @return The debt balance of the user since the last burn/mint action + **/ function _calcTotalSupply(uint256 avgRate) internal view returns(uint256) { uint256 principalSupply = super.totalSupply(); @@ -273,6 +296,12 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { return principalSupply.rayMul(cumulatedInterest); } + /** + * @dev mints stable debt tokens to an user + * @param account the account receiving the debt tokens + * @param amount the amount being minted + * @param oldTotalSupply the total supply before the minting event + **/ function _mint(address account, uint256 amount, uint256 oldTotalSupply) internal { uint256 oldAccountBalance = _balances[account]; @@ -283,9 +312,14 @@ contract StableDebtToken is IStableDebtToken, DebtTokenBase { } } + /** + * @dev burns stable debt tokens of an user + * @param account the user getting his debt burned + * @param amount the amount being burned + * @param oldTotalSupply the total supply before the burning event + **/ function _burn(address account, uint256 amount, uint256 oldTotalSupply) internal { - uint256 oldAccountBalance = _balances[account]; _balances[account] = oldAccountBalance.sub(amount, 'ERC20: burn amount exceeds balance'); diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 3e60955d..ba874e6e 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -375,6 +375,12 @@ export const borrow = async ( txCost ); + console.log("total stable debt actual: ", reserveDataAfter.totalStableDebt.toFixed()); + console.log("total stable debt expected: ", expectedReserveData.totalStableDebt.toFixed()); + + console.log("total variable debt actual: ", reserveDataAfter.totalVariableDebt.toFixed()); + console.log("total variable debt expected: ", expectedReserveData.totalVariableDebt.toFixed()); + expectEqual(reserveDataAfter, expectedReserveData); expectEqual(userDataAfter, expectedUserData); @@ -479,6 +485,14 @@ export const repay = async ( txCost ); + + console.log("total stable debt actual: ", reserveDataAfter.totalStableDebt.toFixed()); + console.log("total stable debt expected: ", expectedReserveData.totalStableDebt.toFixed()); + + console.log("total variable debt actual: ", reserveDataAfter.totalVariableDebt.toFixed()); + console.log("total variable debt expected: ", expectedReserveData.totalVariableDebt.toFixed()); + + expectEqual(reserveDataAfter, expectedReserveData); expectEqual(userDataAfter, expectedUserData); @@ -741,9 +755,12 @@ export const getContractsData = async ( sender?: string ) => { const {pool} = testEnv; - const reserveData = await getReserveData(pool, reserve); - const userData = await getUserData(pool, reserve, user, sender || user); - const timestamp = await timeLatest(); + + const [userData, reserveData, timestamp] = await Promise.all([ + getUserData(pool, reserve, user, sender || user), + getReserveData(pool, reserve), + timeLatest(), + ]); return { reserveData, diff --git a/test/helpers/utils/calculations.ts b/test/helpers/utils/calculations.ts index 795dbaa3..3cd05b69 100644 --- a/test/helpers/utils/calculations.ts +++ b/test/helpers/utils/calculations.ts @@ -97,6 +97,8 @@ export const calcExpectedUserDataAfterWithdraw = ( currentTimestamp: BigNumber, txCost: BigNumber ): UserReserveData => { + console.log('Checking withdraw'); + const expectedUserData = <UserReserveData>{}; const aTokenBalance = calcExpectedATokenBalance( @@ -183,7 +185,9 @@ export const calcExpectedReserveDataAfterDeposit = ( ); expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( - reserveDataBeforeAction, + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.lastUpdateTimestamp, txTimestamp ); expectedReserveData.totalVariableDebt = calcExpectedTotalVariableDebt( @@ -252,7 +256,9 @@ export const calcExpectedReserveDataAfterWithdraw = ( ); expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( - reserveDataBeforeAction, + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.lastUpdateTimestamp, txTimestamp ); expectedReserveData.totalVariableDebt = calcExpectedTotalVariableDebt( @@ -313,33 +319,71 @@ export const calcExpectedReserveDataAfterBorrow = ( expectedReserveData.lastUpdateTimestamp = txTimestamp; if (borrowRateMode == RateMode.Stable) { + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; - expectedReserveData.totalVariableDebt = reserveDataBeforeAction.scaledVariableDebt.rayMul( - expectedReserveData.variableBorrowIndex + const expectedVariableDebtAfterTx = calcExpectedTotalVariableDebt( + reserveDataBeforeAction, + txTimestamp ); + const expectedStableDebtUntilTx = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.lastUpdateTimestamp, + txTimestamp + ); + + expectedReserveData.principalStableDebt = expectedStableDebtUntilTx.plus(amountBorrowedBN); + expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalStableDebt, + expectedStableDebtUntilTx, amountBorrowedBN, reserveDataBeforeAction.stableBorrowRate ); - expectedReserveData.principalStableDebt = reserveDataBeforeAction.totalStableDebt.plus( - amountBorrowedBN + const totalLiquidityAfterTx = expectedReserveData.availableLiquidity + .plus(expectedReserveData.principalStableDebt) + .plus(expectedVariableDebtAfterTx); + + const utilizationRateAfterTx = calcExpectedUtilizationRate( + expectedReserveData.principalStableDebt, //the expected principal debt is the total debt immediately after the tx + expectedVariableDebtAfterTx, + totalLiquidityAfterTx ); + const ratesAfterTx = calcExpectedInterestRates( + reserveDataBeforeAction.symbol, + reserveDataBeforeAction.marketStableRate, + utilizationRateAfterTx, + expectedReserveData.principalStableDebt, + expectedVariableDebtAfterTx, + expectedReserveData.averageStableBorrowRate + ); + + expectedReserveData.liquidityRate = ratesAfterTx[0]; + + expectedReserveData.stableBorrowRate = ratesAfterTx[1]; + + expectedReserveData.variableBorrowRate = ratesAfterTx[2]; + expectedReserveData.totalStableDebt = calcExpectedTotalStableDebt( - { - ...reserveDataBeforeAction, - principalStableDebt: expectedReserveData.principalStableDebt, - averageStableBorrowRate: expectedReserveData.averageStableBorrowRate, - lastUpdateTimestamp: txTimestamp, - }, + expectedReserveData.principalStableDebt, + expectedReserveData.averageStableBorrowRate, + txTimestamp, currentTimestamp ); + expectedReserveData.totalVariableDebt = reserveDataBeforeAction.scaledVariableDebt.rayMul( + calcExpectedReserveNormalizedDebt( + expectedReserveData.variableBorrowRate, + expectedReserveData.variableBorrowIndex, + txTimestamp, + currentTimestamp + ) + ); + expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity .plus(expectedReserveData.totalVariableDebt) .plus(expectedReserveData.totalStableDebt); @@ -349,21 +393,6 @@ export const calcExpectedReserveDataAfterBorrow = ( expectedReserveData.totalVariableDebt, expectedReserveData.totalLiquidity ); - - const rates = calcExpectedInterestRates( - reserveDataBeforeAction.symbol, - reserveDataBeforeAction.marketStableRate, - expectedReserveData.utilizationRate, - expectedReserveData.totalStableDebt, - expectedReserveData.totalVariableDebt, - expectedReserveData.averageStableBorrowRate - ); - - expectedReserveData.liquidityRate = rates[0]; - - expectedReserveData.stableBorrowRate = rates[1]; - - expectedReserveData.variableBorrowRate = rates[2]; } else { expectedReserveData.principalStableDebt = reserveDataBeforeAction.principalStableDebt; @@ -406,7 +435,12 @@ export const calcExpectedReserveDataAfterBorrow = ( expectedReserveData.variableBorrowRate = rates[2]; expectedReserveData.totalVariableDebt = expectedReserveData.scaledVariableDebt.rayMul( - calcExpectedReserveNormalizedDebt(expectedReserveData, currentTimestamp) + calcExpectedReserveNormalizedDebt( + expectedReserveData.variableBorrowRate, + expectedReserveData.variableBorrowIndex, + txTimestamp, + currentTimestamp + ) ); expectedReserveData.totalLiquidity = expectedReserveData.availableLiquidity @@ -469,16 +503,32 @@ export const calcExpectedReserveDataAfterRepay = ( ); if (borrowRateMode == RateMode.Stable) { - expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = reserveDataBeforeAction.totalStableDebt.minus( - amountRepaidBN + const expectedDebt = calcExpectedTotalStableDebt( + reserveDataBeforeAction.principalStableDebt, + reserveDataBeforeAction.averageStableBorrowRate, + reserveDataBeforeAction.lastUpdateTimestamp, + txTimestamp ); - expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( - reserveDataBeforeAction.averageStableBorrowRate, - reserveDataBeforeAction.totalStableDebt, - amountRepaidBN.negated(), - userDataBeforeAction.stableBorrowRate + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = expectedDebt.minus( + amountRepaidBN ); + //due to accumulation errors, the total stable debt might be smaller than the last user debt. + //in this case we simply set the total supply and avg stable rate to 0. + if (expectedReserveData.principalStableDebt.lt(0)) { + expectedReserveData.principalStableDebt = expectedReserveData.totalStableDebt = new BigNumber( + 0 + ); + expectedReserveData.averageStableBorrowRate = new BigNumber(0); + } else { + expectedReserveData.averageStableBorrowRate = calcExpectedAverageStableBorrowRate( + reserveDataBeforeAction.averageStableBorrowRate, + expectedDebt, + amountRepaidBN.negated(), + userDataBeforeAction.stableBorrowRate + ); + } + expectedReserveData.scaledVariableDebt = reserveDataBeforeAction.scaledVariableDebt; expectedReserveData.totalVariableDebt = reserveDataBeforeAction.totalVariableDebt; } else { @@ -636,22 +686,18 @@ export const calcExpectedUserDataAfterRepay = ( userDataBeforeAction.stableRateLastUpdated, currentTimestamp ); - + let totalRepaidBN = new BigNumber(totalRepaid); if (totalRepaidBN.abs().eq(MAX_UINT_AMOUNT)) { - totalRepaidBN = - rateMode == RateMode.Stable - ? stableDebt - : variableDebt; + totalRepaidBN = rateMode == RateMode.Stable ? stableDebt : variableDebt; } if (rateMode == RateMode.Stable) { - expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt; expectedUserData.currentVariableDebt = userDataBeforeAction.currentVariableDebt; expectedUserData.principalStableDebt = expectedUserData.currentStableDebt = stableDebt.minus( - totalRepaid + totalRepaidBN ); if (expectedUserData.currentStableDebt.eq('0')) { @@ -664,15 +710,17 @@ export const calcExpectedUserDataAfterRepay = ( expectedUserData.stableRateLastUpdated = txTimestamp; } } else { - expectedUserData.currentStableDebt = userDataBeforeAction.principalStableDebt; expectedUserData.principalStableDebt = stableDebt; expectedUserData.stableBorrowRate = userDataBeforeAction.stableBorrowRate; expectedUserData.stableRateLastUpdated = userDataBeforeAction.stableRateLastUpdated; - - expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt.minus(totalRepaidBN.rayDiv(expectedDataAfterAction.variableBorrowIndex)); - expectedUserData.currentVariableDebt = expectedUserData.scaledVariableDebt.rayMul(expectedDataAfterAction.variableBorrowIndex); + expectedUserData.scaledVariableDebt = userDataBeforeAction.scaledVariableDebt.minus( + totalRepaidBN.rayDiv(expectedDataAfterAction.variableBorrowIndex) + ); + expectedUserData.currentVariableDebt = expectedUserData.scaledVariableDebt.rayMul( + expectedDataAfterAction.variableBorrowIndex + ); } expectedUserData.liquidityRate = expectedDataAfterAction.liquidityRate; @@ -1028,23 +1076,17 @@ const calcExpectedAverageStableBorrowRate = ( .decimalPlaces(0, BigNumber.ROUND_DOWN); }; -const calcExpectedVariableDebtUserIndex = ( - reserveDataBeforeAction: ReserveData, - expectedUserBalanceAfterAction: BigNumber, - currentTimestamp: BigNumber -) => { - if (expectedUserBalanceAfterAction.eq(0)) { - return new BigNumber(0); - } - return calcExpectedReserveNormalizedDebt(reserveDataBeforeAction, currentTimestamp); -}; - export const calcExpectedVariableDebtTokenBalance = ( reserveData: ReserveData, userData: UserReserveData, currentTimestamp: BigNumber ) => { - const normalizedDebt = calcExpectedReserveNormalizedDebt(reserveData, currentTimestamp); + const normalizedDebt = calcExpectedReserveNormalizedDebt( + reserveData.variableBorrowRate, + reserveData.variableBorrowIndex, + reserveData.lastUpdateTimestamp, + currentTimestamp + ); const {scaledVariableDebt} = userData; @@ -1237,11 +1279,11 @@ const calcExpectedReserveNormalizedIncome = ( }; const calcExpectedReserveNormalizedDebt = ( - reserveData: ReserveData, + variableBorrowRate: BigNumber, + variableBorrowIndex: BigNumber, + lastUpdateTimestamp: BigNumber, currentTimestamp: BigNumber ) => { - const {variableBorrowRate, variableBorrowIndex, lastUpdateTimestamp} = reserveData; - //if utilization rate is 0, nothing to compound if (variableBorrowRate.eq('0')) { return variableBorrowIndex; @@ -1300,14 +1342,19 @@ const calcExpectedVariableBorrowIndex = (reserveData: ReserveData, timestamp: Bi return cumulatedInterest.rayMul(reserveData.variableBorrowIndex); }; -const calcExpectedTotalStableDebt = (reserveData: ReserveData, timestamp: BigNumber) => { +const calcExpectedTotalStableDebt = ( + principalStableDebt: BigNumber, + averageStableBorrowRate: BigNumber, + lastUpdateTimestamp: BigNumber, + currentTimestamp: BigNumber +) => { const cumulatedInterest = calcCompoundedInterest( - reserveData.averageStableBorrowRate, - timestamp, - reserveData.lastUpdateTimestamp + averageStableBorrowRate, + currentTimestamp, + lastUpdateTimestamp ); - return cumulatedInterest.rayMul(reserveData.principalStableDebt); + return cumulatedInterest.rayMul(principalStableDebt); }; const calcExpectedTotalVariableDebt = ( diff --git a/test/scenario.spec.ts b/test/scenario.spec.ts index 54fe7433..98e0c164 100644 --- a/test/scenario.spec.ts +++ b/test/scenario.spec.ts @@ -10,7 +10,7 @@ import {executeStory} from './helpers/scenario-engine'; const scenarioFolder = './test/helpers/scenarios/'; -const selectedScenarios: string[] = []; +const selectedScenarios: string[] = ['borrow-repay-variable.json']; fs.readdirSync(scenarioFolder).forEach((file) => { if (selectedScenarios.length > 0 && !selectedScenarios.includes(file)) return;