From ae9ff1695e0bd429f25b03a9ec84a93d7932851b Mon Sep 17 00:00:00 2001 From: Filippo Date: Mon, 5 Feb 2024 18:16:44 +0100 Subject: [PATCH] feat: track loan KPIs in Pool and PoolsnapshotsFixes #188 * feat: track loan KPIs in Pool and Poolsnapshots Fixes #188 --- schema.graphql | 16 +++++++ src/mappings/handlers/loansHandlers.ts | 58 +++++++++++------------ src/mappings/services/loanService.ts | 3 ++ src/mappings/services/poolService.test.ts | 16 +++++-- src/mappings/services/poolService.ts | 44 +++++++++++------ 5 files changed, 89 insertions(+), 48 deletions(-) diff --git a/schema.graphql b/schema.graphql index e14b031d..d804a3fa 100644 --- a/schema.graphql +++ b/schema.graphql @@ -36,6 +36,10 @@ type Pool @entity { # Aggregated transaction data over the last period sumBorrowedAmountByPeriod: BigInt sumRepaidAmountByPeriod: BigInt + sumPrincipalRepaidAmountByPeriod: BigInt + sumInterestRepaidAmountByPeriod: BigInt + sumUnscheduledRepaidAmountByPeriod: BigInt + sumInvestedAmountByPeriod: BigInt sumRedeemedAmountByPeriod: BigInt sumNumberOfLoansByPeriod: BigInt @@ -47,6 +51,9 @@ type Pool @entity { # Cumulated transaction data since pool creation sumBorrowedAmount: BigInt sumRepaidAmount: BigInt + sumPrincipalRepaidAmount: BigInt + sumInterestRepaidAmount: BigInt + sumUnscheduledRepaidAmount: BigInt sumNumberOfLoans: BigInt tranches: [Tranche] @derivedFrom(field: "pool") @@ -72,15 +79,24 @@ type PoolSnapshot @entity { # Aggregated transaction data over the last period sumBorrowedAmountByPeriod: BigInt sumRepaidAmountByPeriod: BigInt + sumPrincipalRepaidAmountByPeriod: BigInt + sumInterestRepaidAmountByPeriod: BigInt + sumUnscheduledRepaidAmountByPeriod: BigInt + sumInvestedAmountByPeriod: BigInt sumRedeemedAmountByPeriod: BigInt sumNumberOfLoansByPeriod: BigInt sumNumberOfActiveLoans: BigInt + sumDebtOverdue: BigInt + sumDebtWrittenOffByPeriod: BigInt # Cumulated transaction data since pool creation sumBorrowedAmount: BigInt sumRepaidAmount: BigInt + sumPrincipalRepaidAmount: BigInt + sumInterestRepaidAmount: BigInt + sumUnscheduledRepaidAmount: BigInt sumNumberOfLoans: BigInt } diff --git a/src/mappings/handlers/loansHandlers.ts b/src/mappings/handlers/loansHandlers.ts index fd95088e..04139b00 100644 --- a/src/mappings/handlers/loansHandlers.ts +++ b/src/mappings/handlers/loansHandlers.ts @@ -1,4 +1,5 @@ import { SubstrateEvent } from '@subql/types' +import { nToBigInt } from '@polkadot/util' import { LoanBorrowedEvent, LoanClosedEvent, @@ -21,7 +22,7 @@ async function _handleLoanCreated(event: SubstrateEvent) { logger.info(`Loan created event for pool: ${poolId.toString()} loan: ${loanId.toString()}`) const pool = await PoolService.getById(poolId.toString()) - if (pool === undefined) throw missingPool + if (!pool) throw missingPool const account = await AccountService.getOrInit(event.extrinsic.extrinsic.signer.toHex()) @@ -82,13 +83,13 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr const [poolId, loanId, borrowAmount] = event.event.data const pool = await PoolService.getById(poolId.toString()) - if (pool === undefined) throw missingPool + if (!pool) throw missingPool const amount = borrowAmount.isInternal - ? borrowAmount.asInternal.toString() - : borrowAmount.asExternal.quantity.mul(borrowAmount.asExternal.settlementPrice).div(WAD).toString() + ? borrowAmount.asInternal.toBigInt() + : nToBigInt(borrowAmount.asExternal.quantity.toBn().mul(borrowAmount.asExternal.settlementPrice.toBn()).div(WAD)) - if (amount == '0') return + if (amount === BigInt(0)) return logger.info(`Loan borrowed event for pool: ${poolId.toString()} amount: ${amount.toString()}`) @@ -97,7 +98,7 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr // Update loan amount const loan = await LoanService.getById(poolId.toString(), loanId.toString()) await loan.activate() - await loan.borrow(BigInt(amount)) + await loan.borrow(amount) await loan.updateItemMetadata() await loan.save() @@ -108,15 +109,15 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr epochNumber: pool.currentEpoch, hash: event.extrinsic.extrinsic.hash.toString(), timestamp: event.block.timestamp, - amount: BigInt(amount), - principalAmount: BigInt(amount), - quantity: borrowAmount.isExternal ? BigInt(borrowAmount.asExternal.quantity.toString()) : null, - settlementPrice: borrowAmount.isExternal ? BigInt(borrowAmount.asExternal.settlementPrice.toString()) : null, + amount: amount, + principalAmount: amount, + quantity: borrowAmount.isExternal ? borrowAmount.asExternal.quantity.toBigInt() : null, + settlementPrice: borrowAmount.isExternal ? borrowAmount.asExternal.settlementPrice.toBigInt() : null, }) await bt.save() // Update pool info - await pool.increaseBorrowings(BigInt(amount)) + await pool.increaseBorrowings(amount) await pool.save() // Update epoch info @@ -131,21 +132,21 @@ async function _handleLoanRepaid(event: SubstrateEvent) { const [poolId, loanId, { principal, interest, unscheduled }] = event.event.data const pool = await PoolService.getById(poolId.toString()) - if (pool === undefined) throw missingPool + if (!pool) throw missingPool const principalAmount = principal.isInternal - ? principal.asInternal - : principal.asExternal.quantity.mul(principal.asExternal.settlementPrice).div(WAD) - const amount = principalAmount.add(interest).add(unscheduled).toString() + ? principal.asInternal.toBigInt() + : nToBigInt(principal.asExternal.quantity.toBn().mul(principal.asExternal.settlementPrice.toBn()).div(WAD)) + const amount = principalAmount + interest.toBigInt() + unscheduled.toBigInt() - if (amount == '0') return + if (amount === BigInt(0)) return logger.info(`Loan repaid event for pool: ${poolId.toString()} amount: ${amount.toString()}`) const account = await AccountService.getOrInit(event.extrinsic.extrinsic.signer.toHex()) const loan = await LoanService.getById(poolId.toString(), loanId.toString()) - await loan.repay(BigInt(amount)) + await loan.repay(amount) await loan.updateItemMetadata() await loan.save() @@ -156,23 +157,23 @@ async function _handleLoanRepaid(event: SubstrateEvent) { epochNumber: pool.currentEpoch, hash: event.extrinsic.extrinsic.hash.toString(), timestamp: event.block.timestamp, - amount: BigInt(amount), - principalAmount: BigInt(principalAmount.toString()), - interestAmount: BigInt(interest.toString()), - unscheduledAmount: BigInt(unscheduled.toString()), - quantity: principal.isExternal ? BigInt(principal.asExternal.quantity.toString()) : null, - settlementPrice: principal.isExternal ? BigInt(principal.asExternal.settlementPrice.toString()) : null, + amount: amount, + principalAmount: principalAmount, + interestAmount: interest.toBigInt(), + unscheduledAmount: unscheduled.toBigInt(), + quantity: principal.isExternal ? principal.asExternal.quantity.toBigInt() : null, + settlementPrice: principal.isExternal ? principal.asExternal.settlementPrice.toBigInt() : null, }) await bt.save() // Update pool info - await pool.increaseRepayments(BigInt(amount)) + await pool.increaseRepayments(principalAmount, interest.toBigInt(), unscheduled.toBigInt()) await pool.save() // Update epoch info const epoch = await EpochService.getById(pool.id, pool.currentEpoch) - if (epoch === undefined) throw new Error('Epoch not found!') - await epoch.increaseRepayments(BigInt(amount)) + if (!epoch) throw new Error('Epoch not found!') + await epoch.increaseRepayments(amount) await epoch.save() } @@ -224,9 +225,9 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent = { poolId: poolId.toString(), - //loanId: loanId.toString(), address: account.id, epochNumber: pool.currentEpoch, hash: event.extrinsic.extrinsic.hash.toString(), diff --git a/src/mappings/services/loanService.ts b/src/mappings/services/loanService.ts index 3d4b56c1..99727bbe 100644 --- a/src/mappings/services/loanService.ts +++ b/src/mappings/services/loanService.ts @@ -21,6 +21,9 @@ export class LoanService extends Loan { loan.totalRepaidInterest = BigInt(0) loan.totalRepaidUnscheduled = BigInt(0) + loan.borrowedAmountByPeriod = BigInt(0) + loan.repaidAmountByPeriod = BigInt(0) + return loan } diff --git a/src/mappings/services/poolService.test.ts b/src/mappings/services/poolService.test.ts index d776ae00..6f55caf5 100644 --- a/src/mappings/services/poolService.test.ts +++ b/src/mappings/services/poolService.test.ts @@ -87,13 +87,13 @@ describe('Given a new pool, when initialised', () => { describe('Given an existing pool,', () => { test('when the nav is updated, then the value is fetched and set correctly', async () => { await pool.updatePortfolioValuation() - expect(api.query.loans.portfolioValuation).toBeCalled() + expect(api.query.loans.portfolioValuation).toHaveBeenCalled() expect(pool.portfolioValuation).toBe(BigInt(100000000000000)) }) test('when the pool state is updated, then the values are fetched and set correctly', async () => { await pool.updateState() - expect(api.query.poolSystem.pool).toBeCalledWith(poolId) + expect(api.query.poolSystem.pool).toHaveBeenCalledWith(poolId) expect(pool).toMatchObject({ totalReserve: BigInt(91000000000000), availableReserve: BigInt(92000000000000), @@ -110,10 +110,16 @@ describe('Given an existing pool,', () => { }) test('when total repaid are registered, then values are incremented correctly', async () => { - await pool.increaseRepayments(BigInt('17500000000000000')) + await pool.increaseRepayments(BigInt('17500000000000000'), BigInt('17500000000000000'), BigInt('17500000000000000')) expect(pool).toMatchObject({ - sumRepaidAmountByPeriod: BigInt('17500000000000000'), - sumRepaidAmount: BigInt('17500000000000000'), + sumRepaidAmountByPeriod: BigInt('17500000000000000') + BigInt('17500000000000000') + BigInt('17500000000000000'), + sumRepaidAmount: BigInt('17500000000000000') + BigInt('17500000000000000') + BigInt('17500000000000000'), + sumPrincipalRepaidAmountByPeriod: BigInt('17500000000000000'), + sumPrincipalRepaidAmount: BigInt('17500000000000000'), + sumInterestRepaidAmountByPeriod: BigInt('17500000000000000'), + sumInterestRepaidAmount: BigInt('17500000000000000'), + sumUnscheduledRepaidAmountByPeriod: BigInt('17500000000000000'), + sumUnscheduledRepaidAmount: BigInt('17500000000000000'), }) }) diff --git a/src/mappings/services/poolService.ts b/src/mappings/services/poolService.ts index 504a8c68..c827761a 100644 --- a/src/mappings/services/poolService.ts +++ b/src/mappings/services/poolService.ts @@ -51,12 +51,18 @@ export class PoolService extends Pool { this.sumBorrowedAmountByPeriod = BigInt(0) this.sumRepaidAmountByPeriod = BigInt(0) + this.sumPrincipalRepaidAmountByPeriod = BigInt(0) + this.sumInterestRepaidAmountByPeriod = BigInt(0) + this.sumUnscheduledRepaidAmountByPeriod = BigInt(0) this.sumInvestedAmountByPeriod = BigInt(0) this.sumRedeemedAmountByPeriod = BigInt(0) this.sumNumberOfLoansByPeriod = BigInt(0) this.sumBorrowedAmount = BigInt(0) this.sumRepaidAmount = BigInt(0) + this.sumPrincipalRepaidAmount = BigInt(0) + this.sumInterestRepaidAmount = BigInt(0) + this.sumUnscheduledRepaidAmount = BigInt(0) this.sumNumberOfLoans = BigInt(0) this.currencyId = currencyId @@ -127,9 +133,19 @@ export class PoolService extends Pool { this.sumBorrowedAmount += borrowedAmount } - public increaseRepayments(repaidAmount: bigint) { - this.sumRepaidAmountByPeriod += repaidAmount - this.sumRepaidAmount += repaidAmount + public increaseRepayments( + principalRepaidAmount: bigint, + interestRepaidAmount: bigint, + unscheduledRepaidAmount: bigint + ) { + this.sumRepaidAmountByPeriod += principalRepaidAmount + interestRepaidAmount + unscheduledRepaidAmount + this.sumRepaidAmount += principalRepaidAmount + interestRepaidAmount + unscheduledRepaidAmount + this.sumPrincipalRepaidAmountByPeriod += principalRepaidAmount + this.sumPrincipalRepaidAmount += principalRepaidAmount + this.sumInterestRepaidAmountByPeriod += interestRepaidAmount + this.sumInterestRepaidAmount += interestRepaidAmount + this.sumUnscheduledRepaidAmountByPeriod += unscheduledRepaidAmount + this.sumUnscheduledRepaidAmount += unscheduledRepaidAmount } public increaseInvestments(currencyAmount: bigint) { @@ -221,19 +237,19 @@ export class PoolService extends Pool { export interface ActiveLoanData { [loanId: string]: { - outstandingPrincipal: bigint, - outstandingInterest: bigint, - outstandingDebt: bigint, - presentValue: bigint, - actualMaturityDate: Date, + outstandingPrincipal: bigint + outstandingInterest: bigint + outstandingDebt: bigint + presentValue: bigint + actualMaturityDate: Date timeToMaturity: number - actualOriginationDate: Date, - writeOffPercentage: bigint, - totalBorrowed: bigint, + actualOriginationDate: Date + writeOffPercentage: bigint + totalBorrowed: bigint totalRepaid: bigint - totalRepaidPrincipal: bigint, - totalRepaidInterest: bigint, - totalRepaidUnscheduled: bigint, + totalRepaidPrincipal: bigint + totalRepaidInterest: bigint + totalRepaidUnscheduled: bigint } }