From 1ff84a95bf81056d5d0c457ce88d0704101183ab Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 13 Sep 2024 22:15:37 +0200 Subject: [PATCH 1/7] feat: track tinlake totals --- src/mappings/handlers/ethHandlers.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mappings/handlers/ethHandlers.ts b/src/mappings/handlers/ethHandlers.ts index fa57bdb..2e1ea86 100644 --- a/src/mappings/handlers/ethHandlers.ts +++ b/src/mappings/handlers/ethHandlers.ts @@ -169,6 +169,8 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: const newLoans = await getNewLoans(existingLoanIds as number[], shelf) logger.info(`Found ${newLoans.length} new loans for pool ${poolId}`) + const pool = await PoolService.getById(poolId) + const nftIdCalls: PoolMulticall[] = [] for (const loanIndex of newLoans) { nftIdCalls.push({ @@ -318,6 +320,9 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: } } + let totalDebt = BigInt(0) + let totalBorrowed = BigInt(0) + let totalRepaid = BigInt(0) for (let i = 0; i < existingLoans.length; i++) { const loan = existingLoans[i] const loanIndex = loan.id.split('-')[1] @@ -357,7 +362,16 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: } logger.info(`Updating loan ${loan.id} for pool ${poolId}`) await loan.save() + + totalDebt += loan.outstandingDebt + totalBorrowed += loan.totalBorrowed + totalRepaid += loan.totalRepaid } + + pool.sumDebt = totalDebt + pool.sumBorrowedAmount = totalBorrowed + pool.sumRepaidAmount = totalRepaid + await pool.save() } } From 99c3c63d1eb842162796e61652c9a553d533ca9b Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 13 Sep 2024 22:17:35 +0200 Subject: [PATCH 2/7] feat: track wa interest rate --- schema.graphql | 4 ++++ src/mappings/handlers/ethHandlers.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/schema.graphql b/schema.graphql index 4e1eaa5..e30564e 100644 --- a/schema.graphql +++ b/schema.graphql @@ -55,6 +55,8 @@ type Pool @entity { availableReserve: BigInt maxReserve: BigInt + + weightedAverageInterestRatePerSec: BigInt # Aggregated transaction data over the last period sumBorrowedAmountByPeriod: BigInt @@ -121,6 +123,8 @@ type PoolSnapshot @entity { availableReserve: BigInt maxReserve: BigInt + weightedAverageInterestRatePerSec: BigInt + # Aggregated transaction data over the last period sumBorrowedAmountByPeriod: BigInt sumRepaidAmountByPeriod: BigInt diff --git a/src/mappings/handlers/ethHandlers.ts b/src/mappings/handlers/ethHandlers.ts index 2e1ea86..f7b64fd 100644 --- a/src/mappings/handlers/ethHandlers.ts +++ b/src/mappings/handlers/ethHandlers.ts @@ -323,6 +323,7 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: let totalDebt = BigInt(0) let totalBorrowed = BigInt(0) let totalRepaid = BigInt(0) + let totalInterestRatePerSec = BigInt(0) for (let i = 0; i < existingLoans.length; i++) { const loan = existingLoans[i] const loanIndex = loan.id.split('-')[1] @@ -366,11 +367,13 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: totalDebt += loan.outstandingDebt totalBorrowed += loan.totalBorrowed totalRepaid += loan.totalRepaid + totalInterestRatePerSec += loan.interestRatePerSec * loan.outstandingDebt } pool.sumDebt = totalDebt pool.sumBorrowedAmount = totalBorrowed pool.sumRepaidAmount = totalRepaid + pool.weightedAverageInterestRatePerSec = totalInterestRatePerSec / totalDebt await pool.save() } } From 7e52f73dccb9c21b76021888132664f9c82a8e8e Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 13 Sep 2024 22:26:47 +0200 Subject: [PATCH 3/7] feat: track counts --- schema.graphql | 9 ++++++++ src/mappings/handlers/ethHandlers.ts | 32 +++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/schema.graphql b/schema.graphql index e30564e..c32b195 100644 --- a/schema.graphql +++ b/schema.graphql @@ -98,6 +98,9 @@ type Pool @entity { sumUnscheduledRepaidAmount: BigInt sumNumberOfAssets: BigInt + sumBorrowsCount: BigInt + sumRepaysCount: BigInt + tranches: [Tranche] @derivedFrom(field: "pool") assets: [Asset] @derivedFrom(field: "pool") } @@ -164,6 +167,9 @@ type PoolSnapshot @entity { sumInterestRepaidAmount: BigInt sumUnscheduledRepaidAmount: BigInt sumNumberOfAssets: BigInt + + sumBorrowsCount: BigInt + sumRepaysCount: BigInt } type Tranche @entity { @@ -493,6 +499,9 @@ type Asset @entity { totalRepaidInterest: BigInt totalRepaidUnscheduled: BigInt + borrowsCount: BigInt + repaysCount: BigInt + borrowedAmountByPeriod: BigInt repaidAmountByPeriod: BigInt diff --git a/src/mappings/handlers/ethHandlers.ts b/src/mappings/handlers/ethHandlers.ts index f7b64fd..45a584b 100644 --- a/src/mappings/handlers/ethHandlers.ts +++ b/src/mappings/handlers/ethHandlers.ts @@ -320,10 +320,12 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: } } - let totalDebt = BigInt(0) - let totalBorrowed = BigInt(0) - let totalRepaid = BigInt(0) - let totalInterestRatePerSec = BigInt(0) + let sumDebt = BigInt(0) + let sumBorrowed = BigInt(0) + let sumRepaid = BigInt(0) + let sumInterestRatePerSec = BigInt(0) + let sumBorrowsCount = BigInt(0) + let sumRepaysCount = BigInt(0) for (let i = 0; i < existingLoans.length; i++) { const loan = existingLoans[i] const loanIndex = loan.id.split('-')[1] @@ -351,6 +353,7 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: loan.totalRepaid ? (loan.totalRepaid += loan.repaidAmountByPeriod) : (loan.totalRepaid = loan.repaidAmountByPeriod) + loan.repaysCount += 1 } if ( prevDebt * (loan.interestRatePerSec / BigInt(10) ** BigInt(27)) * BigInt(86400) < @@ -360,20 +363,25 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: loan.totalBorrowed ? (loan.totalBorrowed += loan.borrowedAmountByPeriod) : (loan.totalBorrowed = loan.borrowedAmountByPeriod) + loan.borrowsCount += 1 } logger.info(`Updating loan ${loan.id} for pool ${poolId}`) await loan.save() - totalDebt += loan.outstandingDebt - totalBorrowed += loan.totalBorrowed - totalRepaid += loan.totalRepaid - totalInterestRatePerSec += loan.interestRatePerSec * loan.outstandingDebt + sumDebt += loan.outstandingDebt + sumBorrowed += loan.totalBorrowed + sumRepaid += loan.totalRepaid + sumInterestRatePerSec += loan.interestRatePerSec * loan.outstandingDebt + sumBorrowsCount += loan.borrowsCount + sumRepaysCount += loan.repaysCount } - pool.sumDebt = totalDebt - pool.sumBorrowedAmount = totalBorrowed - pool.sumRepaidAmount = totalRepaid - pool.weightedAverageInterestRatePerSec = totalInterestRatePerSec / totalDebt + pool.sumDebt = sumDebt + pool.sumBorrowedAmount = sumBorrowed + pool.sumRepaidAmount = sumRepaid + pool.weightedAverageInterestRatePerSec = sumInterestRatePerSec / sumDebt + pool.sumBorrowsCount = sumBorrowsCount + pool.sumRepaysCount = sumRepaysCount await pool.save() } } From a2524aaa1fa5ec50dbf3e1044d6fa629b088ea8d Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 13 Sep 2024 22:29:25 +0200 Subject: [PATCH 4/7] fix: one type --- src/mappings/handlers/ethHandlers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mappings/handlers/ethHandlers.ts b/src/mappings/handlers/ethHandlers.ts index 45a584b..ce85314 100644 --- a/src/mappings/handlers/ethHandlers.ts +++ b/src/mappings/handlers/ethHandlers.ts @@ -353,7 +353,7 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: loan.totalRepaid ? (loan.totalRepaid += loan.repaidAmountByPeriod) : (loan.totalRepaid = loan.repaidAmountByPeriod) - loan.repaysCount += 1 + loan.repaysCount += BigInt(1) } if ( prevDebt * (loan.interestRatePerSec / BigInt(10) ** BigInt(27)) * BigInt(86400) < @@ -363,7 +363,7 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: loan.totalBorrowed ? (loan.totalBorrowed += loan.borrowedAmountByPeriod) : (loan.totalBorrowed = loan.borrowedAmountByPeriod) - loan.borrowsCount += 1 + loan.borrowsCount += BigInt(1) } logger.info(`Updating loan ${loan.id} for pool ${poolId}`) await loan.save() From f5321eb392b94d2de2d3644f76b12a57a4c5d064 Mon Sep 17 00:00:00 2001 From: Adam Stox Date: Sat, 21 Sep 2024 16:47:53 -0400 Subject: [PATCH 5/7] fix: default values and divide by 0 --- chains-evm/eth/centrifuge.yaml | 11 ++++++---- src/mappings/handlers/ethHandlers.ts | 30 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/chains-evm/eth/centrifuge.yaml b/chains-evm/eth/centrifuge.yaml index a73fdd8..3fde81a 100644 --- a/chains-evm/eth/centrifuge.yaml +++ b/chains-evm/eth/centrifuge.yaml @@ -4,15 +4,18 @@ network: #dictionary: 'https://gx.api.subquery.network/sq/subquery/eth-dictionary' dataSources: - kind: ethereum/Runtime #PoolManager V1 - startBlock: 18721030 + startBlock: 20778999 + # startBlock: 18721030 options: address: '0x78E9e622A57f70F1E0Ec652A4931E4e278e58142' - kind: ethereum/Runtime #PoolManager V2 - startBlock: 20432390 + startBlock: 20778999 + # startBlock: 20432390 options: address: '0x91808B5E2F6d7483D41A681034D7c9DbB64B9E29' - kind: ethereum/Runtime #Tinlake - startBlock: 11063000 + startBlock: 20778999 + # startBlock: 11063000 options: abi: navFeed assets: @@ -32,4 +35,4 @@ dataSources: - handler: handleEthBlock kind: ethereum/BlockHandler filter: - modulo: 300 \ No newline at end of file + modulo: 300 diff --git a/src/mappings/handlers/ethHandlers.ts b/src/mappings/handlers/ethHandlers.ts index ce85314..05ea65b 100644 --- a/src/mappings/handlers/ethHandlers.ts +++ b/src/mappings/handlers/ethHandlers.ts @@ -350,36 +350,36 @@ async function updateLoans(poolId: string, blockDate: Date, shelf: string, pile: if (prevDebt > currentDebt) { loan.repaidAmountByPeriod = prevDebt - currentDebt - loan.totalRepaid - ? (loan.totalRepaid += loan.repaidAmountByPeriod) - : (loan.totalRepaid = loan.repaidAmountByPeriod) - loan.repaysCount += BigInt(1) + loan.totalRepaid = (loan.totalRepaid || BigInt(0)) + loan.repaidAmountByPeriod + loan.repaysCount = (loan.repaysCount || BigInt(0)) + BigInt(1) } if ( prevDebt * (loan.interestRatePerSec / BigInt(10) ** BigInt(27)) * BigInt(86400) < (loan.outstandingDebt || BigInt(0)) ) { loan.borrowedAmountByPeriod = (loan.outstandingDebt || BigInt(0)) - prevDebt - loan.totalBorrowed - ? (loan.totalBorrowed += loan.borrowedAmountByPeriod) - : (loan.totalBorrowed = loan.borrowedAmountByPeriod) - loan.borrowsCount += BigInt(1) + loan.totalBorrowed = (loan.totalBorrowed || BigInt(0)) + loan.borrowedAmountByPeriod + loan.borrowsCount = (loan.borrowsCount || BigInt(0)) + BigInt(1) } logger.info(`Updating loan ${loan.id} for pool ${poolId}`) await loan.save() - sumDebt += loan.outstandingDebt - sumBorrowed += loan.totalBorrowed - sumRepaid += loan.totalRepaid - sumInterestRatePerSec += loan.interestRatePerSec * loan.outstandingDebt - sumBorrowsCount += loan.borrowsCount - sumRepaysCount += loan.repaysCount + sumDebt += loan.outstandingDebt || BigInt(0) + sumBorrowed += loan.totalBorrowed || BigInt(0) + sumRepaid += loan.totalRepaid || BigInt(0) + sumInterestRatePerSec += (loan.interestRatePerSec || BigInt(0)) * (loan.outstandingDebt || BigInt(0)) + sumBorrowsCount += loan.borrowsCount || BigInt(0) + sumRepaysCount += loan.repaysCount || BigInt(0) } pool.sumDebt = sumDebt pool.sumBorrowedAmount = sumBorrowed pool.sumRepaidAmount = sumRepaid - pool.weightedAverageInterestRatePerSec = sumInterestRatePerSec / sumDebt + if (sumDebt > BigInt(0)) { + pool.weightedAverageInterestRatePerSec = sumInterestRatePerSec / sumDebt + } else { + pool.weightedAverageInterestRatePerSec = BigInt(0) + } pool.sumBorrowsCount = sumBorrowsCount pool.sumRepaysCount = sumRepaysCount await pool.save() From 3444d45ab41a84d28061107391e2d8d9b5cd5612 Mon Sep 17 00:00:00 2001 From: Filippo Fontana Date: Thu, 31 Oct 2024 18:07:30 +0100 Subject: [PATCH 6/7] fix: network params --- chains-evm/eth/centrifuge.yaml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/chains-evm/eth/centrifuge.yaml b/chains-evm/eth/centrifuge.yaml index 3fde81a..c1a62d4 100644 --- a/chains-evm/eth/centrifuge.yaml +++ b/chains-evm/eth/centrifuge.yaml @@ -4,18 +4,15 @@ network: #dictionary: 'https://gx.api.subquery.network/sq/subquery/eth-dictionary' dataSources: - kind: ethereum/Runtime #PoolManager V1 - startBlock: 20778999 - # startBlock: 18721030 + startBlock: 18721030 options: address: '0x78E9e622A57f70F1E0Ec652A4931E4e278e58142' - kind: ethereum/Runtime #PoolManager V2 - startBlock: 20778999 - # startBlock: 20432390 + startBlock: 20432390 options: address: '0x91808B5E2F6d7483D41A681034D7c9DbB64B9E29' - kind: ethereum/Runtime #Tinlake - startBlock: 20778999 - # startBlock: 11063000 + startBlock: 11063000 options: abi: navFeed assets: From 99ad34e7ec3ca409232f16c330263fb12951c6d5 Mon Sep 17 00:00:00 2001 From: Filippo Fontana Date: Tue, 5 Nov 2024 14:30:16 +0100 Subject: [PATCH 7/7] fix: updated chain decoding of tranche currencies --- chains-cfg/_root.yaml | 28 ++ src/helpers/types.ts | 44 +-- src/mappings/handlers/blockHandlers.ts | 4 +- src/mappings/handlers/investmentsHandlers.ts | 280 +++++++++++++++---- src/mappings/handlers/poolsHandlers.ts | 16 +- src/mappings/services/currencyService.ts | 10 +- src/mappings/services/poolService.ts | 111 +++++++- src/mappings/services/trancheService.ts | 28 +- 8 files changed, 405 insertions(+), 116 deletions(-) diff --git a/chains-cfg/_root.yaml b/chains-cfg/_root.yaml index 5944b53..5a575f4 100644 --- a/chains-cfg/_root.yaml +++ b/chains-cfg/_root.yaml @@ -51,21 +51,49 @@ dataSources: - handler: handleInvestOrderUpdated kind: substrate/EventHandler filter: + specVersion: [1400] + module: investments + method: InvestOrderUpdated + - handler: handleInvestOrderUpdatedBefore1400 + kind: substrate/EventHandler + filter: + specVersion: [null,1399] module: investments method: InvestOrderUpdated - handler: handleRedeemOrderUpdated kind: substrate/EventHandler filter: + specVersion: [1400] + module: investments + method: RedeemOrderUpdated + - handler: handleRedeemOrderUpdatedBefore1400 + kind: substrate/EventHandler + filter: + specVersion: [null,1399] module: investments method: RedeemOrderUpdated - handler: handleInvestOrdersCollected kind: substrate/EventHandler filter: + specVersion: [1400] + module: investments + method: InvestOrdersCollected + - handler: handleInvestOrdersCollectedBefore1400 + kind: substrate/EventHandler + filter: + specVersion: [null,1399] module: investments method: InvestOrdersCollected - handler: handleRedeemOrdersCollected kind: substrate/EventHandler filter: + specVersion: [1400] + module: investments + method: RedeemOrdersCollected + - handler: handleRedeemOrdersCollectedBefore1400 + kind: substrate/EventHandler + filter: + specVersion: [null,1399] module: investments method: RedeemOrdersCollected # - handler: handleProxyAdded diff --git a/src/helpers/types.ts b/src/helpers/types.ts index ca6f2b7..cb0ae93 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -1,5 +1,6 @@ //find out types: const a = createType(api.registry, '[u8;32]', 18) import { AugmentedCall, AugmentedRpc, PromiseRpcResult } from '@polkadot/api/types' +import { Codec } from '@polkadot/types-codec/types' import { Enum, Null, Struct, u128, u32, u64, U8aFixed, Option, Vec, Bytes, Result, bool } from '@polkadot/types' import { AccountId32, Perquintill, Balance } from '@polkadot/types/interfaces' import { ITuple, Observable } from '@polkadot/types/types' @@ -39,16 +40,11 @@ export interface TrancheData extends Struct { } export interface TrancheEssence extends Struct { - currency: TrancheCurrency + currency: TrancheCurrency | TrancheCurrencyBefore1400 ty: TrancheTypeEnum metadata: TrancheMetadata } -export interface TrancheCurrency extends Struct { - poolId: u64 - trancheId: U8aFixed -} - export interface TrancheMetadata extends Struct { tokenName: Bytes tokenSymbol: Bytes @@ -57,7 +53,7 @@ export interface TrancheMetadata extends Struct { export interface TrancheDetails extends Struct { trancheType: TrancheTypeEnum seniority: u32 - currency: TrancheCurrency + currency: TrancheCurrency | TrancheCurrencyBefore1400 debt: u128 reserve: u128 loss: u128 @@ -71,6 +67,7 @@ export interface TrancheTypeEnum extends Enum { isNonResidual: boolean asNonResidual: { interestRatePerSec: u128; minRiskBuffer: Perquintill } asResidual: Null + readonly type: 'Residual' | 'NonResidual'; } export interface NavDetails extends Struct { @@ -89,7 +86,7 @@ export interface EpochExecutionInfo extends Struct { } export interface EpochExecutionTranche extends Struct { - currency: TrancheCurrency + currency: TrancheCurrency | TrancheCurrencyBefore1400 supply: u128 price: u128 invest: u128 @@ -114,7 +111,7 @@ export interface TokensCurrencyId extends Enum { isNative: boolean asNative: null isTranche: boolean - asTranche: ITuple<[poolId: u64, trancheId: U8aFixed]> //poolId, trancheId + asTranche: TrancheCurrency | TrancheCurrencyBefore1400 isAUSD: boolean asAUSD: null isForeignAsset: boolean @@ -457,22 +454,19 @@ export type PoolMetadataSetEvent = ITuple<[poolId: u64, metadata: Bytes]> export type EpochClosedExecutedEvent = ITuple<[poolId: u64, epochId: u32]> export type EpochSolutionEvent = ITuple<[poolId: u64, epochId: u32, solution: EpochSolution]> -export type InvestOrdersCollectedEvent = ITuple< - [ - investmentId: TrancheCurrency, - who: AccountId32, - processedOrders: Vec, - collection: InvestCollection, - outcome: Enum, - ] +export type InvestOrdersCollectedEvent = ITuple< + [investmentId: T, who: AccountId32, processedOrders: Vec, collection: InvestCollection, outcome: Enum] +> + +export type RedeemOrdersCollectedEvent = ITuple< + [investmentId: T, who: AccountId32, collections: Vec, collection: RedeemCollection, outcome: Enum] > -export type RedeemOrdersCollectedEvent = ITuple< - [investmentId: TrancheCurrency, who: AccountId32, collections: Vec, collection: RedeemCollection, outcome: Enum] +export type OrderUpdatedEvent = ITuple< + [investmentId: T, submittedAt: u64, who: AccountId32, amount: u128] > -export type OrderUpdatedEvent = ITuple< - [investmentId: TrancheCurrency, submittedAt: u64, who: AccountId32, amount: u128] +export type OrdersClearedEvent = ITuple< + [investmentId: T, orderId: u64, fulfillment: OrdersFulfillment] > -export type OrdersClearedEvent = ITuple<[investmentId: TrancheCurrency, orderId: u64, fulfillment: OrdersFulfillment]> export type TokensTransferEvent = ITuple< [currencyId: TokensCurrencyId, from: AccountId32, to: AccountId32, amount: u128] > @@ -520,3 +514,9 @@ export type ExtendedCall = typeof api.call & { } export type ApiQueryLoansActiveLoans = Vec> + +export type TrancheCurrency = ITuple<[poolId: u64, trancheId: U8aFixed]> +export interface TrancheCurrencyBefore1400 extends Struct { + poolId: u64 + trancheId: U8aFixed +} diff --git a/src/mappings/handlers/blockHandlers.ts b/src/mappings/handlers/blockHandlers.ts index de74ac0..402022e 100644 --- a/src/mappings/handlers/blockHandlers.ts +++ b/src/mappings/handlers/blockHandlers.ts @@ -64,10 +64,10 @@ async function _handleBlock(block: SubstrateBlock): Promise { const trancheTokenPrices = await pool.getTrancheTokenPrices() for (const tranche of tranches) { const index = tranche.index - if (trancheTokenPrices !== undefined) + if (trancheTokenPrices) await tranche.updatePrice(trancheTokenPrices[index].toBigInt(), block.block.header.number.toNumber()) await tranche.updateSupply() - await tranche.updateDebt(trancheData[tranche.trancheId].data.debt.toBigInt()) + await tranche.updateDebt(trancheData[tranche.trancheId].debt) await tranche.computeYield('yieldSinceLastPeriod', lastPeriodStart) await tranche.computeYield('yieldSinceInception') await tranche.computeYield('yieldYTD', beginningOfYear) diff --git a/src/mappings/handlers/investmentsHandlers.ts b/src/mappings/handlers/investmentsHandlers.ts index 3727cf9..d1c6ae3 100644 --- a/src/mappings/handlers/investmentsHandlers.ts +++ b/src/mappings/handlers/investmentsHandlers.ts @@ -3,40 +3,85 @@ import { errorHandler, missingPool } from '../../helpers/errorHandler' import { EpochService } from '../services/epochService' import { PoolService } from '../services/poolService' import { TrancheService } from '../services/trancheService' -import { InvestOrdersCollectedEvent, OrderUpdatedEvent, RedeemOrdersCollectedEvent } from '../../helpers/types' +import { + InvestOrdersCollectedEvent, + OrderUpdatedEvent, + RedeemOrdersCollectedEvent, + TrancheCurrencyBefore1400, +} from '../../helpers/types' import { OutstandingOrderService } from '../services/outstandingOrderService' import { InvestorTransactionData, InvestorTransactionService } from '../services/investorTransactionService' import { AccountService } from '../services/accountService' import { TrancheBalanceService } from '../services/trancheBalanceService' export const handleInvestOrderUpdated = errorHandler(_handleInvestOrderUpdated) -async function _handleInvestOrderUpdated(event: SubstrateEvent): Promise { - const [{ poolId, trancheId }, , address, newAmount] = event.event.data +async function _handleInvestOrderUpdated(event: SubstrateEvent) { + const [[poolId, trancheId], , address, amount] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return investOrderUpdated( + poolId.toString(), + trancheId.toHex(), + address.toString(), + amount.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +export const handleInvestOrderUpdatedBefore1400 = errorHandler(_handleInvestOrderUpdatedBefore1400) +async function _handleInvestOrderUpdatedBefore1400( + event: SubstrateEvent> +) { + const [{ poolId, trancheId }, , address, amount] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return investOrderUpdated( + poolId.toString(), + trancheId.toHex(), + address.toString(), + amount.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +async function investOrderUpdated( + poolId: string, + trancheId: string, + address: string, + newAmount: bigint, + blockNumber: number, + timestamp: Date, + hash: string +): Promise { logger.info( - `Invest order updated for tranche ${poolId.toString()}-${trancheId.toString()}. ` + - `amount: ${newAmount.toString()} ` + - `block ${event.block.block.header.number.toString()}` + `Invest order updated for tranche ${poolId}-${trancheId}. ` + `amount: ${newAmount} ` + `block ${blockNumber}` ) - const pool = await PoolService.getById(poolId.toString()) - if (pool === undefined) throw missingPool + const pool = await PoolService.getById(poolId) + if (!pool) throw missingPool - const account = await AccountService.getOrInit(address.toHex()) - const tranche = await TrancheService.getById(poolId.toString(), trancheId.toHex()) + const account = await AccountService.getOrInit(address) + const tranche = await TrancheService.getById(poolId, trancheId) // Update tranche price - await tranche.updatePriceFromRuntime(event.block.block.header.number.toNumber()) + await tranche.updatePriceFromRuntime(blockNumber) const orderData: InvestorTransactionData = { poolId: poolId.toString(), trancheId: trancheId.toString(), epochNumber: pool.currentEpoch, address: account.id, - hash: event.extrinsic.extrinsic.hash.toString(), - amount: newAmount.toBigInt(), + hash, + amount: newAmount, price: tranche.tokenPrice, fee: BigInt(0), - timestamp: event.block.timestamp, + timestamp, } if (orderData.amount > BigInt(0)) { @@ -60,8 +105,8 @@ async function _handleInvestOrderUpdated(event: SubstrateEvent): Promise { +async function _handleRedeemOrderUpdated(event: SubstrateEvent) { + const [[poolId, trancheId], , address, amount] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return redeemOrderUpdated( + poolId.toString(), + trancheId.toHex(), + address.toString(), + amount.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +export const handleRedeemOrderUpdatedBefore1400 = errorHandler(_handleRedeemOrderUpdatedBefore1400) +async function _handleRedeemOrderUpdatedBefore1400( + event: SubstrateEvent> +) { const [{ poolId, trancheId }, , address, amount] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return redeemOrderUpdated( + poolId.toString(), + trancheId.toHex(), + address.toString(), + amount.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +const redeemOrderUpdated = errorHandler(_redeemOrderUpdated) +async function _redeemOrderUpdated( + poolId: string, + trancheId: string, + address: string, + newAmount: bigint, + blockNumber: number, + timestamp: Date, + hash: string +): Promise { logger.info( - `Redeem order updated for tranche ${poolId.toString()}-${trancheId.toString()}. ` + - `amount: ${amount.toString()} at ` + - `block ${event.block.block.header.number.toString()}` + `Redeem order updated for tranche ${poolId}-${trancheId}. ` + `amount: ${newAmount} at ` + `block ${blockNumber}` ) // Get corresponding pool - const pool = await PoolService.getById(poolId.toString()) + const pool = await PoolService.getById(poolId) if (pool === undefined) throw missingPool - const account = await AccountService.getOrInit(address.toHex()) + const account = await AccountService.getOrInit(address) - const tranche = await TrancheService.getById(poolId.toString(), trancheId.toHex()) + const tranche = await TrancheService.getById(poolId, trancheId) - await tranche.updatePriceFromRuntime(event.block.block.header.number.toNumber()) + await tranche.updatePriceFromRuntime(blockNumber) const orderData: InvestorTransactionData = { poolId: poolId.toString(), trancheId: trancheId.toString(), epochNumber: pool.currentEpoch, address: account.id, - hash: event.extrinsic.extrinsic.hash.toString(), - amount: amount.toBigInt(), + hash, + amount: newAmount, price: tranche.tokenPrice, fee: BigInt(0), - timestamp: event.block.timestamp, + timestamp, } - if (amount.toBigInt() > BigInt(0)) { + if (newAmount > BigInt(0)) { // Post investor transaction const it = InvestorTransactionService.updateRedeemOrder(orderData) await it.save() @@ -121,8 +207,8 @@ async function _handleRedeemOrderUpdated(event: SubstrateEvent): Promise { - const [{ poolId, trancheId }, address, , investCollection] = event.event.data +async function _handleInvestOrdersCollected(event: SubstrateEvent) { + const [[poolId, trancheId], address, , { payoutInvestmentInvest }] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return investOrdersCollected( + poolId.toString(), + trancheId.toHex(), + address.toString(), + payoutInvestmentInvest.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +export const handleInvestOrdersCollectedBefore1400 = errorHandler(_handleInvestOrdersCollectedBefore1400) +async function _handleInvestOrdersCollectedBefore1400( + event: SubstrateEvent> +) { + const [{ poolId, trancheId }, address, , { payoutInvestmentInvest }] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return investOrdersCollected( + poolId.toString(), + trancheId.toHex(), + address.toString(), + payoutInvestmentInvest.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +async function investOrdersCollected( + poolId: string, + trancheId: string, + address: string, + payoutInvestmentInvest: bigint, + blockNumber: number, + timestamp: Date, + hash: string +): Promise { logger.info( - `Orders collected for tranche ${poolId.toString()}-${trancheId.toString()}. ` + - `Address: ${address.toHex()} at ` + - `block ${event.block.block.header.number.toString()} hash:${event.extrinsic.extrinsic.hash.toString()}` + `Orders collected for tranche ${poolId}-${trancheId}. ` + + `Address: ${address} at ` + + `block ${blockNumber} hash:${hash}` ) - const account = await AccountService.getOrInit(address.toHex()) + const account = await AccountService.getOrInit(address) if (account.isForeignEvm()) { logger.info('Skipping Address as collection is from another EVM Chain') return } - const pool = await PoolService.getById(poolId.toString()) + const pool = await PoolService.getById(poolId) if (!pool) throw missingPool const endEpochId = pool.lastEpochClosed logger.info(`Collection for ending epoch: ${endEpochId}`) - const tranche = await TrancheService.getById(poolId.toString(), trancheId.toHex()) + const tranche = await TrancheService.getById(poolId, trancheId) // Update tranche price - await tranche.updatePriceFromRuntime(event.block.block.header.number.toNumber()) + await tranche.updatePriceFromRuntime(blockNumber) await tranche.save() - const { payoutInvestmentInvest } = investCollection - const orderData = { poolId: poolId.toString(), trancheId: trancheId.toString(), epochNumber: endEpochId, address: account.id, - hash: event.extrinsic.extrinsic.hash.toString(), - timestamp: event.block.timestamp, + hash, + timestamp, price: tranche.tokenPrice, - amount: payoutInvestmentInvest.toBigInt(), + amount: payoutInvestmentInvest, } const trancheBalance = await TrancheBalanceService.getOrInit(orderData.address, orderData.poolId, orderData.trancheId) @@ -181,15 +307,57 @@ async function _handleInvestOrdersCollected(event: SubstrateEvent): Promise { - const [{ poolId, trancheId }, address, , redeemCollection] = event.event.data +async function _handleRedeemOrdersCollected(event: SubstrateEvent) { + const [[poolId, trancheId], address, , { payoutInvestmentRedeem }] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return redeemOrdersCollected( + poolId.toString(), + trancheId.toHex(), + address.toString(), + payoutInvestmentRedeem.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +export const handleRedeemOrdersCollectedBefore1400 = errorHandler(_handleRedeemOrdersCollectedBefore1400) +async function _handleRedeemOrdersCollectedBefore1400( + event: SubstrateEvent> +) { + const [{ poolId, trancheId }, address, , { payoutInvestmentRedeem }] = event.event.data + const blockNumber = event.block.block.header.number.toNumber() + const timestamp = event.block.timestamp + const hash = event.extrinsic.extrinsic.hash.toString() + return redeemOrdersCollected( + poolId.toString(), + trancheId.toHex(), + address.toString(), + payoutInvestmentRedeem.toBigInt(), + blockNumber, + timestamp, + hash + ) +} + +async function redeemOrdersCollected( + poolId: string, + trancheId: string, + address: string, + payoutInvestmentRedeem: bigint, + blockNumber: number, + timestamp: Date, + hash: string +): Promise { logger.info( - `Orders collected for tranche ${poolId.toString()}-${trancheId.toString()}. ` + - `Address: ${address.toHex()} ` + - `block ${event.block.block.header.number.toString()} hash:${event.extrinsic.extrinsic.hash.toString()}` + `Orders collected for tranche ${poolId}-${trancheId}. ` + + `Address: ${address} ` + + `block ${blockNumber} hash:${hash}` ) - const account = await AccountService.getOrInit(address.toHex()) + const account = await AccountService.getOrInit(address) if (account.isForeignEvm()) { logger.info('Skipping Address as collection is from another EVM Chain') return @@ -200,23 +368,21 @@ async function _handleRedeemOrdersCollected(event: SubstrateEvent): Promise { const [, , poolId, essence] = event.event.data + const formattedCurrency = + `${LOCAL_CHAIN_ID}-${essence.currency.type}-` + + `${currencyFormatters[essence.currency.type](essence.currency.value).join('-')}` logger.info( - `Pool ${poolId.toString()} with currency: ${essence.currency.type} ` + + `Pool ${poolId.toString()} with currency: ${formattedCurrency} ` + `created in block ${event.block.block.header.number}` ) @@ -59,14 +62,17 @@ async function _handlePoolCreated(event: SubstrateEvent): Prom const tranches = await Promise.all( essence.tranches.map((trancheEssence) => { - const trancheId = trancheEssence.currency.trancheId.toHex() + const trancheId = + 'trancheId' in trancheEssence.currency + ? trancheEssence.currency.trancheId.toHex() + : trancheEssence.currency[1].toHex() logger.info(`Creating tranche with id: ${pool.id}-${trancheId}`) return TrancheService.getOrSeed(pool.id, trancheId, blockchain.id) }) ) for (const [index, tranche] of tranches.entries()) { - await tranche.init(index, trancheData[tranche.trancheId].data) + await tranche.init(index, trancheData[tranche.trancheId]) await tranche.updateSupply() await tranche.save() @@ -109,10 +115,10 @@ async function _handlePoolUpdated(event: SubstrateEvent): Prom for (const [id, tranche] of Object.entries(tranches)) { logger.info(`Syncing tranche with id: ${id}`) const trancheService = await TrancheService.getOrSeed(poolId.toString(), id) - trancheService.init(tranche.index, tranche.data) + trancheService.init(tranche.index, tranche) await trancheService.activate() await trancheService.updateSupply() - await trancheService.updateDebt(tranche.data.debt.toBigInt()) + await trancheService.updateDebt(tranche.debt) await trancheService.save() const currency = await CurrencyService.getOrInit(blockchain.id, 'Tranche', pool.id, trancheService.trancheId) diff --git a/src/mappings/services/currencyService.ts b/src/mappings/services/currencyService.ts index 1489bc9..70d5bbf 100644 --- a/src/mappings/services/currencyService.ts +++ b/src/mappings/services/currencyService.ts @@ -58,11 +58,15 @@ export class CurrencyService extends Currency { export const currencyFormatters: CurrencyFormatters = { AUSD: () => [], - ForeignAsset: (value) => [value.toString(10)], + ForeignAsset: (value: TokensCurrencyId['asForeignAsset']) => [value.toString(10)], Native: () => [], Staking: () => ['BlockRewards'], - Tranche: (value) => [value[0].toString(10), value[1].toHex()], - LocalAsset: (value) => [value.toString(10)], + Tranche: (value: TokensCurrencyId['asTranche']) => { + return 'trancheId' in value + ? [value.poolId.toString(10), value.trancheId.toHex()] + : [value[0].toString(10), value[1].toHex()] + }, + LocalAsset: (value: TokensCurrencyId['asLocalAsset']) => [value.toString(10)], } export function formatEnumPayload(currencyType: string, ...currencyValue: string[]) { diff --git a/src/mappings/services/poolService.ts b/src/mappings/services/poolService.ts index 1c25737..a8e222e 100644 --- a/src/mappings/services/poolService.ts +++ b/src/mappings/services/poolService.ts @@ -1,6 +1,15 @@ import { Option, u128, Vec } from '@polkadot/types' import { paginatedGetter } from '../../helpers/paginatedGetter' -import { ExtendedCall, NavDetails, PoolDetails, PoolFeesList, PoolMetadata, TrancheDetails } from '../../helpers/types' +import type { + ExtendedCall, + NavDetails, + PoolDetails, + PoolFeesList, + PoolMetadata, + TrancheCurrency, + TrancheCurrencyBefore1400, + TrancheDetails, +} from '../../helpers/types' import { Pool } from '../../types' import { cid, readIpfs } from '../../helpers/ipfsFetch' import { EpochService } from './epochService' @@ -104,12 +113,7 @@ export class PoolService extends Pool { return this } - public initTinlake( - name: string, - currencyId: string, - timestamp: Date, - blockNumber: number - ) { + public initTinlake(name: string, currencyId: string, timestamp: Date, blockNumber: number) { logger.info(`Initialising tinlake pool ${this.id}`) this.isActive = true this.name = name @@ -265,7 +269,7 @@ export class PoolService extends Pool { public async updateNormalizedNAV() { const currency = await CurrencyService.get(this.currencyId) - if (!currency) throw new Error(`No currency with Id ${this.id} found!`) + if (!currency) throw new Error(`No currency with Id ${this.currencyId} found!`) const digitsMismatch = WAD_DIGITS - currency.decimals if (digitsMismatch === 0) { this.normalizedNAV = this.netAssetValue @@ -350,16 +354,79 @@ export class PoolService extends Pool { this.sumInterestAccruedByPeriod += amount } - public async getTranches() { + public async fetchTranchesFrom1400(): Promise { + logger.info(`Fetching tranches for pool: ${this.id} with specVersion >= 1400`) const poolResponse = await api.query.poolSystem.pool>(this.id) - logger.info(`Fetching tranches for pool: ${this.id}`) - if (poolResponse.isNone) throw new Error('Unable to fetch pool data!') + const poolData = poolResponse.unwrap() + const { ids, tranches } = poolData.tranches + const trancheData: TrancheData[] = [] + for (const [index, tranche] of tranches.entries()) { + const currency = tranche.currency as TrancheCurrency + trancheData.push({ + index, + id: ids[index].toHex(), + trancheType: tranche.trancheType.type, + seniority: tranche.seniority.toNumber(), + poolId: currency[0].toString(), + trancheId: currency[1].toHex(), + debt: tranche.debt.toBigInt(), + reserve: tranche.reserve.toBigInt(), + loss: tranche.loss.toBigInt(), + ratio: tranche.ratio.toBigInt(), + lastUpdatedInterest: tranche.lastUpdatedInterest.toBigInt(), + interestRatePerSec: tranche.trancheType.isNonResidual + ? tranche.trancheType.asNonResidual.interestRatePerSec.toBigInt() + : undefined, + minRiskBuffer: tranche.trancheType.isNonResidual + ? tranche.trancheType.asNonResidual.minRiskBuffer.toBigInt() + : undefined, + }) + } + return trancheData + } + public async fetchTranchesBefore1400(): Promise { + logger.info(`Fetching tranches for pool: ${this.id} with specVersion < 1400`) + const poolResponse = await api.query.poolSystem.pool>(this.id) + if (poolResponse.isNone) throw new Error('Unable to fetch pool data!') const poolData = poolResponse.unwrap() const { ids, tranches } = poolData.tranches + const trancheData: TrancheData[] = [] + for (const [index, tranche] of tranches.entries()) { + const currency = tranche.currency as TrancheCurrencyBefore1400 + trancheData.push({ + index, + id: ids[index].toHex(), + trancheType: tranche.trancheType.type, + seniority: tranche.seniority.toNumber(), + poolId: currency.poolId.toString(10), + trancheId: currency.trancheId.toString() as `0x${string}`, + debt: tranche.debt.toBigInt(), + reserve: tranche.reserve.toBigInt(), + loss: tranche.loss.toBigInt(), + ratio: tranche.ratio.toBigInt(), + lastUpdatedInterest: tranche.lastUpdatedInterest.toBigInt(), + interestRatePerSec: tranche.trancheType.isNonResidual + ? tranche.trancheType.asNonResidual.interestRatePerSec.toBigInt() + : undefined, + minRiskBuffer: tranche.trancheType.isNonResidual + ? tranche.trancheType.asNonResidual.minRiskBuffer.toBigInt() + : undefined, + }) + } + return trancheData + } - return tranches.reduce((obj, data, index) => ({ ...obj, [ids[index].toHex()]: { index, data } }), {}) + public async getTranches() { + const specVersion = api.runtimeVersion.specVersion.toNumber() + let tranches: TrancheData[] + if (specVersion >= 1400) { + tranches = await this.fetchTranchesFrom1400() + } else { + tranches = await this.fetchTranchesBefore1400() + } + return tranches.reduce((obj, data) => ({ ...obj, [data.id]: { ...data } }), {}) } public async getPortfolio(): Promise { @@ -528,8 +595,24 @@ export interface ActiveLoanData { } } -interface PoolTranches { - [trancheId: string]: { index: number; data: TrancheDetails } +export interface TrancheData { + index: number + id: `0x${string}` + trancheType: TrancheDetails['trancheType']['type'] + seniority: number + poolId: string + trancheId: `0x${string}` + debt: bigint + reserve: bigint + loss: bigint + ratio: bigint + lastUpdatedInterest: bigint + interestRatePerSec?: bigint + minRiskBuffer?: bigint +} + +export interface PoolTranches { + [trancheId: string]: TrancheData } interface PoolIpfsMetadata { diff --git a/src/mappings/services/trancheService.ts b/src/mappings/services/trancheService.ts index d4e9ac0..342bc4a 100644 --- a/src/mappings/services/trancheService.ts +++ b/src/mappings/services/trancheService.ts @@ -2,8 +2,9 @@ import { u128 } from '@polkadot/types' import { bnToBn, nToBigInt } from '@polkadot/util' import { paginatedGetter } from '../../helpers/paginatedGetter' import { WAD } from '../../config' -import { ExtendedCall, TrancheDetails } from '../../helpers/types' +import { ExtendedCall } from '../../helpers/types' import { Tranche, TrancheSnapshot } from '../../types' +import { TrancheData } from './poolService' const MAINNET_CHAINID = '0xb3db41421702df9a7fcac62b53ffeac85f7853cc4e689e0b93aeb3db18c09d82' @@ -25,11 +26,11 @@ export class TrancheService extends Tranche { return tranche } - public init(index: number, trancheData: TrancheDetails) { + public init(index: number, trancheData: TrancheData) { logger.info(`Initializing tranche ${this.id}`) this.index = index - this.isResidual = trancheData.trancheType.isResidual - this.seniority = trancheData.seniority.toNumber() + this.isResidual = trancheData.trancheType === 'Residual' + this.seniority = trancheData.seniority this.isActive = true this.sumOutstandingInvestOrdersByPeriod = BigInt(0) this.sumOutstandingRedeemOrdersByPeriod = BigInt(0) @@ -39,12 +40,10 @@ export class TrancheService extends Tranche { this.sumFulfilledRedeemOrdersCurrencyByPeriod = BigInt(0) this.tokenPrice = nToBigInt(WAD) - this.sumDebt = trancheData.debt.toBigInt() + this.sumDebt = trancheData.debt - if (!this.isResidual) { - this.interestRatePerSec = trancheData.trancheType.asNonResidual.interestRatePerSec.toBigInt() - this.minRiskBuffer = trancheData.trancheType.asNonResidual.minRiskBuffer.toBigInt() - } + this.interestRatePerSec = trancheData.interestRatePerSec + this.minRiskBuffer = trancheData.minRiskBuffer return this } @@ -263,10 +262,13 @@ export class TrancheService extends Tranche { } public async loadSnapshot(periodStart: Date) { - const snapshots = await TrancheSnapshot.getByFields([ - ['trancheId', '=', this.id], - ['periodId', '=', periodStart.toISOString()], - ], { limit: 100 }) + const snapshots = await TrancheSnapshot.getByFields( + [ + ['trancheId', '=', this.id], + ['periodId', '=', periodStart.toISOString()], + ], + { limit: 100 } + ) if (snapshots.length !== 1) { logger.warn(`Unable to load snapshot for asset ${this.id} for period ${periodStart.toISOString()}`) return