diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index dceeb64e..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": [ - "prettier/@typescript-eslint", - "plugin:prettier/recommended" - ] -} diff --git a/package.json b/package.json index 78363286..97e55e19 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,17 @@ "singleQuote": true, "trailingComma": "es5" }, + "eslint": { + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "plugin:prettier/recommended" + ] + }, "resolutions": { "**/typescript": "^4.0.5", "**/@typescript-eslint/eslint-plugin": "^4.6.1", diff --git a/src/helpers/pool-math.ts b/src/helpers/pool-math.ts index 258f6b59..980a483d 100644 --- a/src/helpers/pool-math.ts +++ b/src/helpers/pool-math.ts @@ -2,11 +2,12 @@ import BigNumber from 'bignumber.js'; import { BigNumberValue, + pow10, valueToBigNumber, valueToZDBigNumber, - pow10, } from './bignumber'; import * as RayMath from './ray-math'; +import { RAY } from './ray-math'; import { SECONDS_PER_YEAR } from './constants'; export const LTV_PRECISION = 4; @@ -127,3 +128,56 @@ export function calculateAverageRate( .multipliedBy(SECONDS_PER_YEAR) .toString(); } + +export function currentATokenBalance( + scaledATokenBalance: BigNumberValue, + liquidityIndex: BigNumberValue, + currentLiquidityRate: BigNumberValue, + lastUpdateTimestamp: number, + currentTimestamp: number +): BigNumber { + const principalBalanceRay = RayMath.wadToRay(scaledATokenBalance); + const normalizedIncome = getNormalizedIncome( + liquidityIndex, + currentLiquidityRate, + lastUpdateTimestamp, + currentTimestamp + ); + const scaledATokenBalanceRay = RayMath.rayMul( + principalBalanceRay, + normalizedIncome + ); + return RayMath.rayToWad(scaledATokenBalanceRay); +} + +function getNormalizedIncome( + liquidityIndex: BigNumberValue, + currentLiquidityRate: BigNumberValue, + lastUpdateTimestamp: number, + currentTimestamp: number +): BigNumber { + liquidityIndex = valueToZDBigNumber(liquidityIndex); + currentLiquidityRate = valueToZDBigNumber(currentLiquidityRate); + if (currentTimestamp === lastUpdateTimestamp) { + return liquidityIndex; + } + const linearInterest = calculateLinearInterest( + currentLiquidityRate, + lastUpdateTimestamp, + currentTimestamp + ); + return RayMath.rayMul(linearInterest, liquidityIndex); +} + +function calculateLinearInterest( + currentLiquidityRate: BigNumberValue, + lastUpdateTimestamp: number, + currentTimestamp: number +): BigNumber { + currentLiquidityRate = valueToZDBigNumber(currentLiquidityRate); + const timeDifference = currentTimestamp - lastUpdateTimestamp; + return currentLiquidityRate + .multipliedBy(timeDifference) + .div(SECONDS_PER_YEAR) + .plus(RAY); +} diff --git a/src/test/v2/computation-and-formatting.test.ts b/src/test/v2/computation-and-formatting.test.ts index c10a76ae..c938b6e0 100644 --- a/src/test/v2/computation-and-formatting.test.ts +++ b/src/test/v2/computation-and-formatting.test.ts @@ -4,6 +4,8 @@ import { formatUserSummaryData, } from '../../v2/computations-and-formatting'; import BigNumber from 'bignumber.js'; +import { currentATokenBalance } from '../../helpers/pool-math'; +import * as RayMath from '../../helpers/ray-math'; const mockReserve: ReserveData = { underlyingAsset: '0xff795577d9ac8bd7d90ee22b6c1703490b6512fd', @@ -151,5 +153,55 @@ describe('computations and formattings', () => { expect(new BigNumber(second.totalDebt).gte(first.totalDebt)).toBe(true); }); + + it('should compute collateral balance from blockchain data', () => { + // data exported from user 0xa5a69107816c5e3dfa5561e6b621dfe6294f6e5b + // at block number: 11581421 + // reserve: YFI + const scaledATokenBalance = '161316503206059870'; + const liquidityIndex = '1001723339432542553527150680'; + const currentLiquidityRate = '22461916953455574582370088'; + const lastUpdateTimestamp = 1609673617; + // at a later time, but on the same block + // expected balance computed with hardhat + const currentTimestamp = 1609675535; + const expectedATokenBalance = '161594727054623229'; + const underlyingBalance = currentATokenBalance( + scaledATokenBalance, + liquidityIndex, + currentLiquidityRate, + lastUpdateTimestamp, + currentTimestamp + ).toString(); + expect(underlyingBalance).toBe(expectedATokenBalance); + }); + + it('should compute collateral balance from subgraph data', () => { + // id: 0xa5a69107816c5e3dfa5561e6b621dfe6294f6e5b0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e0xb53c1a33016b2dc2ff3653530bff1848a515c8c5 + const mockUserReserve = { + scaledATokenBalance: '161316503206059870', + currentATokenBalance: '161422626192524099', + lastUpdateTimestamp: 1609361267, + liquidityRate: '356057734567773693054943410', + }; + const scaledATokenBalanceRay = RayMath.wadToRay( + mockUserReserve.scaledATokenBalance + ); + const currentATokenBalanceRay = RayMath.wadToRay( + mockUserReserve.currentATokenBalance + ); + const liquidityIndex = RayMath.rayDiv( + currentATokenBalanceRay, + scaledATokenBalanceRay + ); + const underlyingBalance = currentATokenBalance( + mockUserReserve.scaledATokenBalance, + liquidityIndex, + mockUserReserve.liquidityRate, + mockUserReserve.lastUpdateTimestamp, + mockUserReserve.lastUpdateTimestamp + ).toString(); + expect(underlyingBalance).toBe(mockUserReserve.currentATokenBalance); + }); }); }); diff --git a/src/v1/computations-and-formatting.ts b/src/v1/computations-and-formatting.ts index 1b1ff122..65eeb709 100644 --- a/src/v1/computations-and-formatting.ts +++ b/src/v1/computations-and-formatting.ts @@ -47,7 +47,7 @@ export function getCompoundedBorrowBalance( let cumulatedInterest; if (userReserve.borrowRateMode === BorrowRateMode.Variable) { - let compoundedInterest = calculateCompoundedInterest( + const compoundedInterest = calculateCompoundedInterest( reserve.variableBorrowRate, currentTimestamp, reserve.lastUpdateTimestamp diff --git a/src/v2/computations-and-formatting.ts b/src/v2/computations-and-formatting.ts index 9fd6d679..9f51b773 100644 --- a/src/v2/computations-and-formatting.ts +++ b/src/v2/computations-and-formatting.ts @@ -2,28 +2,29 @@ import BigNumber from 'bignumber.js'; import { BigNumberValue, - valueToBigNumber, - valueToZDBigNumber, normalize, pow10, + valueToBigNumber, + valueToZDBigNumber, } from '../helpers/bignumber'; import { calculateAvailableBorrowsETH, + calculateAverageRate, + calculateCompoundedInterest, calculateHealthFactorFromBalances, + currentATokenBalance, getCompoundedBalance, getCompoundedStableBalance, - calculateAverageRate, LTV_PRECISION, - calculateCompoundedInterest, } from '../helpers/pool-math'; import { rayMul } from '../helpers/ray-math'; import { + ComputedReserveData, ComputedUserReserve, ReserveData, + ReserveRatesData, UserReserveData, UserSummaryData, - ReserveRatesData, - ComputedReserveData, } from './types'; import { ETH_DECIMALS, RAY_DECIMALS, USD_DECIMALS } from '../helpers/constants'; @@ -75,7 +76,7 @@ export function computeUserReserveData( price: { priceInEth }, decimals, } = poolReserve; - const underlyingBalance = getCompoundedBalance( + const underlyingBalance = currentATokenBalance( userReserve.scaledATokenBalance, poolReserve.liquidityIndex, poolReserve.liquidityRate,