diff --git a/schema.graphql b/schema.graphql index d8552d1a..f0a963bd 100644 --- a/schema.graphql +++ b/schema.graphql @@ -67,6 +67,8 @@ type Pool @entity { sumPoolFeesPaidAmount: BigInt #Applies to All sumPoolFeesPendingAmount: BigInt #Applies to All + sumRealizedProfitFifoByPeriod: BigInt + # Cumulated transaction data since pool creation sumBorrowedAmount: BigInt sumRepaidAmount: BigInt @@ -123,6 +125,8 @@ type PoolSnapshot @entity { sumPoolFeesPaidAmount: BigInt #Applies to All sumPoolFeesPendingAmount: BigInt #Applies to All + sumRealizedProfitFifoByPeriod: BigInt + # Cumulated transaction data since pool creation sumBorrowedAmount: BigInt sumRepaidAmount: BigInt @@ -314,6 +318,8 @@ type AssetTransaction @entity { # only applies to debt transfers fromAsset: Asset toAsset: Asset + + realizedProfitFifo: BigInt } type OracleTransaction @entity { @@ -429,6 +435,8 @@ type Asset @entity { writtenOffPercentageByPeriod: BigInt writtenOffAmountByPeriod: BigInt penaltyInterestRatePerSec: BigInt + + positions: [AssetPosition] @derivedFrom(field: "asset") } type AssetSnapshot @entity { @@ -465,6 +473,14 @@ type AssetSnapshot @entity { penaltyInterestRatePerSec: BigInt } +type AssetPosition @entity { + id: ID! + asset: Asset! @index + timestamp: Date! + holdingQuantity: BigInt! + purchasePrice: BigInt! +} + type PureProxy @entity { id: ID! diff --git a/src/mappings/handlers/loansHandlers.ts b/src/mappings/handlers/loansHandlers.ts index 2ee3a70c..a5fbae00 100644 --- a/src/mappings/handlers/loansHandlers.ts +++ b/src/mappings/handlers/loansHandlers.ts @@ -17,6 +17,7 @@ import { EpochService } from '../services/epochService' import { AssetType, AssetValuationMethod } from '../../types' import { bnToBn, nToBigInt } from '@polkadot/util' import { WAD } from '../../config' +import { AssetPositionService } from '../services/assetPositionService' export const handleLoanCreated = errorHandler(_handleLoanCreated) async function _handleLoanCreated(event: SubstrateEvent) { @@ -142,6 +143,13 @@ async function _handleLoanBorrowed(event: SubstrateEvent): Pr if (borrowAmount.isExternal) { await asset.increaseQuantity(borrowAmount.asExternal.quantity.toBigInt()) + await AssetPositionService.buy( + asset.id, + assetTransactionBaseData.hash, + assetTransactionBaseData.timestamp, + assetTransactionBaseData.quantity, + assetTransactionBaseData.settlementPrice + ) } const at = await AssetTransactionService.borrowed(assetTransactionBaseData) @@ -205,11 +213,19 @@ async function _handleLoanRepaid(event: SubstrateEvent) { } else { await asset.repay(amount) + let realizedProfitFifo: bigint if (principal.isExternal) { - await asset.decreaseQuantity(principal.asExternal.quantity.toBigInt()) + const { quantity, settlementPrice } = principal.asExternal + await asset.decreaseQuantity(quantity.toBigInt()) + realizedProfitFifo = await AssetPositionService.sellFifo( + asset.id, + quantity.toBigInt(), + settlementPrice.toBigInt() + ) + await pool.increaseRealizedProfitFifo(realizedProfitFifo) } - const at = await AssetTransactionService.repaid(assetTransactionBaseData) + const at = await AssetTransactionService.repaid({ ...assetTransactionBaseData, realizedProfitFifo }) await at.save() // Update pool info @@ -313,8 +329,16 @@ async function _handleLoanDebtTransferred(event: SubstrateEvent BigInt(0)) { + return this.init(assetId, hash, timestamp, quantity, price).save() + } else { + logger.warn(`Skipping asset position ${assetId}-${hash} as quantity is 0`) + return Promise.resolve() + } + } + + static async sellFifo(assetId: string, sellingQuantity: bigint, sellingPrice: bigint) { + logger.info( + `Selling positions for ${assetId} ` + + `sellingQuantity: ${sellingQuantity.toString(10)} sellingPrice: ${sellingPrice.toString(10)}` + ) + const positions = await this.getByAssetId(assetId) + positions.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf()) + + const sellPositions: [assetPosition: AssetPosition, sellQuantity: bigint][] = [] + let remainingQuantity = sellingQuantity + while (remainingQuantity > BigInt(0)) { + const currentPosition = positions.pop() + if (!currentPosition) throw new Error(`No positions to sell for asset ${assetId}`) + if (remainingQuantity > currentPosition.holdingQuantity) { + const soldQuantity = currentPosition.holdingQuantity + currentPosition['holdingQuantity'] = BigInt(0) + sellPositions.push([currentPosition, soldQuantity]) + remainingQuantity -= soldQuantity + } else { + const soldQuantity = remainingQuantity + currentPosition['holdingQuantity'] -= soldQuantity + sellPositions.push([currentPosition, soldQuantity]) + remainingQuantity -= soldQuantity + } + } + + const profitFromSale = nToBigInt(bnToBn(sellingPrice).mul(bnToBn(sellingQuantity)).div(WAD)) + const costOfBuy = nToBigInt( + sellPositions.reduce( + (totalCost, line) => totalCost.add(bnToBn(line[0].purchasePrice).mul(bnToBn(line[1]).div(WAD))), + new BN(0) + ) + ) + + const dbUpdates = sellPositions.map((line) => + line[0].holdingQuantity > BigInt(0) ? line[0].save() : this.remove(line[0].id) + ) + + await Promise.all(dbUpdates) + return profitFromSale - costOfBuy + } +} diff --git a/src/mappings/services/assetTransactionService.ts b/src/mappings/services/assetTransactionService.ts index 23569ad1..51662636 100644 --- a/src/mappings/services/assetTransactionService.ts +++ b/src/mappings/services/assetTransactionService.ts @@ -15,6 +15,7 @@ export interface AssetTransactionData { readonly assetId: string readonly fromAssetId?: string readonly toAssetId?: string + readonly realizedProfitFifo?: bigint } export class AssetTransactionService extends AssetTransaction { @@ -38,7 +39,7 @@ export class AssetTransactionService extends AssetTransaction { tx.settlementPrice = data.settlementPrice ?? null tx.fromAssetId = data.fromAssetId ? `${data.poolId}-${data.fromAssetId}` : null tx.toAssetId = data.toAssetId ? `${data.poolId}-${data.toAssetId}` : null - + tx.realizedProfitFifo = data.realizedProfitFifo ?? null return tx } diff --git a/src/mappings/services/poolService.ts b/src/mappings/services/poolService.ts index e72a0488..9bb6877c 100644 --- a/src/mappings/services/poolService.ts +++ b/src/mappings/services/poolService.ts @@ -72,6 +72,7 @@ export class PoolService extends Pool { this.sumPoolFeesPaidAmountByPeriod = BigInt(0) this.deltaPortfolioValuationByPeriod = BigInt(0) this.sumInterestAccruedByPeriod = BigInt(0) + this.sumRealizedProfitFifoByPeriod = BigInt(0) this.sumBorrowedAmount = BigInt(0) this.sumRepaidAmount = BigInt(0) @@ -403,6 +404,11 @@ export class PoolService extends Pool { logger.info(`Updating sumPoolFeesPendingAmount for pool ${this.id} to ${pendingAmount.toString(10)}`) this.sumPoolFeesPendingAmount = pendingAmount } + + public increaseRealizedProfitFifo(amount: bigint) { + logger.info(`Increasing umRealizedProfitFifoByPeriod for pool ${this.id} by ${amount.toString(10)}`) + this.sumRealizedProfitFifoByPeriod += amount + } } export interface ActiveLoanData {