diff --git a/schema.graphql b/schema.graphql index 72f86a9f..7a285a95 100644 --- a/schema.graphql +++ b/schema.graphql @@ -485,6 +485,7 @@ type AssetSnapshot @entity { unrealizedProfitAtMarketPrice: BigInt unrealizedProfitAtNotional: BigInt + unrealizedProfitByPeriod: BigInt } type AssetPosition @entity { diff --git a/src/mappings/handlers/blockHandlers.ts b/src/mappings/handlers/blockHandlers.ts index 4c1a3735..a7e625f6 100644 --- a/src/mappings/handlers/blockHandlers.ts +++ b/src/mappings/handlers/blockHandlers.ts @@ -65,12 +65,11 @@ async function _handleBlock(block: SubstrateBlock): Promise { pool.resetUnrealizedProfit() for (const loanId in activeLoanData) { const asset = await AssetService.getById(pool.id, loanId) - const previousPrice = asset.currentPrice + await asset.loadSnapshot(lastPeriodStart) await asset.updateActiveAssetData(activeLoanData[loanId]) await asset.updateUnrealizedProfit( await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.currentPrice), - await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.notional), - await AssetPositionService.computeUnrealizedProfitByPeriod(asset.id, asset.currentPrice, previousPrice) + await AssetPositionService.computeUnrealizedProfitAtPrice(asset.id, asset.notional) ) await asset.save() await pool.increaseInterestAccrued(asset.interestAccruedByPeriod) diff --git a/src/mappings/handlers/loansHandlers.ts b/src/mappings/handlers/loansHandlers.ts index b788e527..beec41c4 100644 --- a/src/mappings/handlers/loansHandlers.ts +++ b/src/mappings/handlers/loansHandlers.ts @@ -103,6 +103,7 @@ async function _handleLoanCreated(event: SubstrateEvent) { export const handleLoanBorrowed = errorHandler(_handleLoanBorrowed) async function _handleLoanBorrowed(event: SubstrateEvent): Promise { const [poolId, loanId, borrowAmount] = event.event.data + const specVersion = api.runtimeVersion.specVersion.toNumber() const pool = await PoolService.getById(poolId.toString()) if (!pool) throw missingPool @@ -144,6 +145,9 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr await asset.borrow(amount) if (borrowAmount.isExternal) { + // Prices were based on the settlement prices only until spec version 1025 + if (specVersion < 1025) await asset.updateCurrentPrice(borrowAmount.asExternal.settlementPrice.toBigInt()) + await asset.increaseQuantity(borrowAmount.asExternal.quantity.toBigInt()) await AssetPositionService.buy( asset.id, @@ -174,6 +178,7 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr export const handleLoanRepaid = errorHandler(_handleLoanRepaid) async function _handleLoanRepaid(event: SubstrateEvent) { const [poolId, loanId, { principal, interest, unscheduled }] = event.event.data + const specVersion = api.runtimeVersion.specVersion.toNumber() const pool = await PoolService.getById(poolId.toString()) if (!pool) throw missingPool @@ -218,6 +223,10 @@ async function _handleLoanRepaid(event: SubstrateEvent) { let realizedProfitFifo: bigint if (principal.isExternal) { const { quantity, settlementPrice } = principal.asExternal + + // Prices were based on the settlement prices only until spec version 1025 + if (specVersion < 1025) await asset.updateCurrentPrice(settlementPrice.toBigInt()) + await asset.decreaseQuantity(quantity.toBigInt()) realizedProfitFifo = await AssetPositionService.sellFifo( asset.id, @@ -297,7 +306,8 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent( - (result, position) => result + position.holdingQuantity, - BigInt(0) - ) - return nToBigInt(bnToBn(sellingPrice).sub(bnToBn(purchasePrice)).mul(bnToBn(sellingQuantity)).div(WAD)) - } } diff --git a/src/mappings/services/assetService.ts b/src/mappings/services/assetService.ts index 59b7f15f..2b2b58f7 100644 --- a/src/mappings/services/assetService.ts +++ b/src/mappings/services/assetService.ts @@ -2,12 +2,14 @@ import { Option } from '@polkadot/types' import { bnToBn, nToBigInt } from '@polkadot/util' import { WAD } from '../../config' import { ApiQueryLoansActiveLoans, LoanPricingAmount, NftItemMetadata } from '../../helpers/types' -import { Asset, AssetType, AssetValuationMethod, AssetStatus } from '../../types' +import { Asset, AssetType, AssetValuationMethod, AssetStatus, AssetSnapshot } from '../../types' import { ActiveLoanData } from './poolService' import { cid, readIpfs } from '../../helpers/ipfsFetch' export const ONCHAIN_CASH_ASSET_ID = '0' export class AssetService extends Asset { + snapshot?: AssetSnapshot + static init( poolId: string, assetId: string, @@ -119,6 +121,11 @@ export class AssetService extends Asset { Object.assign(this, decodedAssetSpecs) } + public updateCurrentPrice(currentPrice: bigint) { + logger.info(`Updating current price for asset ${this.id} to ${currentPrice}`) + this.currentPrice = currentPrice + } + public activate() { logger.info(`Activating asset ${this.id}`) this.isActive = true @@ -132,14 +139,19 @@ export class AssetService extends Asset { } public async updateActiveAssetData(activeAssetData: ActiveLoanData[keyof ActiveLoanData]) { - const oldOutstaidingInterest = this.outstandingInterest - const oldTotalRepaidInterest = this.totalRepaidInterest + // Current price was always 0 until spec version 1025 + const specVersion = api.runtimeVersion.specVersion.toNumber() + if (specVersion < 1025) delete activeAssetData.currentPrice + // Set all active asset values Object.assign(this, activeAssetData) - const deltaRepaidInterestAmount = this.totalRepaid - oldTotalRepaidInterest - this.interestAccruedByPeriod = this.outstandingInterest - oldOutstaidingInterest + deltaRepaidInterestAmount + if(this.snapshot) { + const deltaRepaidInterestAmount = this.totalRepaid - this.snapshot.totalRepaidInterest + this.interestAccruedByPeriod = + this.outstandingInterest - this.snapshot.outstandingInterest + deltaRepaidInterestAmount logger.info(`Updated outstanding debt for asset: ${this.id} to ${this.outstandingDebt.toString()}`) + } } public async updateItemMetadata() { @@ -208,20 +220,40 @@ export class AssetService extends Asset { if (loanData.pricing.isInternal) throw new Error(`Asset ${this.id} is not of type External!`) const { outstandingQuantity, latestSettlementPrice } = loanData.pricing.asExternal this.outstandingQuantity = outstandingQuantity.toBigInt() - this.currentPrice = latestSettlementPrice.toBigInt() + this.updateCurrentPrice(latestSettlementPrice.toBigInt()) logger.info( `Updated outstandingQuantity: ${outstandingQuantity.toString(10)} ` + `currentPrice: ${latestSettlementPrice.toString(10)} for asset ${this.id}` ) } - public updateUnrealizedProfit(atMarketPrice: bigint, atNotional: bigint, byPeriod: bigint) { + public updateUnrealizedProfit(atMarketPrice: bigint, atNotional: bigint) { logger.info( - `Updating unrealizedProfit for asset ${this.id} atMarketPrice: ${atMarketPrice} atNotional: ${atNotional}` + `Updating unrealizedProfit for asset ${this.id} with atMarketPrice: ${atMarketPrice}, atNotional: ${atNotional}` ) this.unrealizedProfitAtMarketPrice = atMarketPrice this.unrealizedProfitAtNotional = atNotional - this.unrealizedProfitByPeriod = byPeriod + if (!!this.snapshot && this.snapshot.outstandingQuantity > 0 && this.snapshot.currentPrice > 0) { + logger.info(`byPeriod: ${this.outstandingQuantity} x (${this.currentPrice} - ${this.snapshot.currentPrice})`) + this.unrealizedProfitByPeriod = nToBigInt( + bnToBn(this.outstandingQuantity) + .mul(bnToBn(this.currentPrice - this.snapshot.currentPrice)) + .div(WAD) + ) + logger.info(`byPeriod: ${this.unrealizedProfitByPeriod}`) + } + } + + public async loadSnapshot(periodStart: Date) { + const snapshots = await AssetSnapshot.getByFields([ + ['assetId', '=', this.id], + ['periodStart', '=', periodStart], + ]) + if (snapshots.length !== 1) { + logger.warn(`Unable to load snapshot for asset ${this.id} for period ${periodStart.toISOString()}`) + return + } + this.snapshot = snapshots.pop() } }