Skip to content

Commit

Permalink
fix: investment positions (#259)
Browse files Browse the repository at this point in the history
* fix: investment positions

* chore: upgrade dependencies

* fix: update getByFields arguments
  • Loading branch information
filo87 authored Oct 31, 2024
1 parent f3373af commit 9ec23c4
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 298 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
"@polkadot/api": "^12",
"@polkadot/typegen": "^12",
"@subql/cli": "^5.2.6",
"@subql/common-substrate": "latest",
"@subql/common-substrate": "^4.3.2",
"@subql/node-ethereum": "^5.1.3",
"@subql/testing": "latest",
"@subql/types": "latest",
"@subql/testing": "^2.2.2",
"@subql/types": "^3.11.3",
"@types/jest": "^29.1.2",
"@types/node-fetch": "^2.6.11",
"@typescript-eslint/eslint-plugin": "^6.15.0",
Expand Down
1 change: 0 additions & 1 deletion schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -676,5 +676,4 @@ type PoolFeeTransaction @entity {
epoch: Epoch!

amount: BigInt

}
4 changes: 3 additions & 1 deletion src/mappings/handlers/blockHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ async function _handleBlock(block: SubstrateBlock): Promise<void> {
await tranche.save()

// Compute TrancheBalances Unrealized Profit
const trancheBalances = (await TrancheBalanceService.getByTrancheId(tranche.id)) as TrancheBalanceService[]
const trancheBalances = (await TrancheBalanceService.getByTrancheId(tranche.id, {
limit: 100,
})) as TrancheBalanceService[]
for (const trancheBalance of trancheBalances) {
const unrealizedProfit = await InvestorPositionService.computeUnrealizedProfitAtPrice(
trancheBalance.accountId,
Expand Down
5 changes: 3 additions & 2 deletions src/mappings/handlers/ethHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ async function updateLoans(
navFeed: string
) {
logger.info(`Updating loans for pool ${poolId}`)
let existingLoans = await AssetService.getByPoolId(poolId)
let existingLoans = await AssetService.getByPoolId(poolId, { limit: 100 })
const existingLoanIds = existingLoans?.map((loan) => parseInt(loan.id.split('-')[1]))
const newLoans = await getNewLoans(existingLoanIds as number[], shelf)
logger.info(`Found ${newLoans.length} new loans for pool ${poolId}`)
Expand Down Expand Up @@ -276,7 +276,8 @@ async function updateLoans(
}

// update all loans
existingLoans = (await AssetService.getByPoolId(poolId))?.filter((loan) => loan.status !== AssetStatus.CLOSED) || []
existingLoans =
(await AssetService.getByPoolId(poolId, { limit: 100 }))?.filter((loan) => loan.status !== AssetStatus.CLOSED) || []
logger.info(`Updating ${existingLoans?.length} existing loans for pool ${poolId}`)
const loanDetailsCalls: PoolMulticall[] = []
existingLoans.forEach((loan) => {
Expand Down
61 changes: 40 additions & 21 deletions src/mappings/handlers/evmHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { Provider } from '@ethersproject/providers'
import { TrancheBalanceService } from '../services/trancheBalanceService'
import { escrows, userEscrows } from '../../config'
import { InvestorPositionService } from '../services/investorPositionService'
import { getPeriodStart } from '../../helpers/timekeeperService'

const _ethApi = api as unknown as Provider
//const networkPromise = typeof ethApi.getNetwork === 'function' ? ethApi.getNetwork() : null
Expand Down Expand Up @@ -62,6 +63,7 @@ async function _handleEvmTransfer(event: TransferLog): Promise<void> {
const [fromEvmAddress, toEvmAddress, amount] = event.args
logger.info(`Transfer ${fromEvmAddress}-${toEvmAddress} of ${amount.toString()} at block: ${event.blockNumber}`)

const timestamp = new Date(Number(event.block.timestamp) * 1000)
const evmTokenAddress = event.address
const chainId = await getNodeEvmChainId() //(await networkPromise).chainId.toString(10)
const evmBlockchain = await BlockchainService.getOrInit(chainId)
Expand All @@ -74,12 +76,16 @@ async function _handleEvmTransfer(event: TransferLog): Promise<void> {
const isFromEscrow = fromEvmAddress === escrowAddress
const _isFromUserEscrow = fromEvmAddress === userEscrowAddress

const trancheId = evmToken.trancheId.split('-')[1]
const tranche = await TrancheService.getById(evmToken.poolId, trancheId)

const orderData: Omit<InvestorTransactionData, 'address'> = {
poolId: evmToken.poolId,
trancheId: evmToken.trancheId.split('-')[1],
trancheId: trancheId,
hash: event.transactionHash,
timestamp: new Date(Number(event.block.timestamp) * 1000),
timestamp: timestamp,
amount: amount.toBigInt(),
price: tranche.snapshot?.tokenPrice,
}

const isLpTokenMigrationDay =
Expand Down Expand Up @@ -128,27 +134,40 @@ async function _handleEvmTransfer(event: TransferLog): Promise<void> {

// Handle Transfer In and Out
if (isFromUserAddress && isToUserAddress) {
const txIn = InvestorTransactionService.transferIn({ ...orderData, address: toAccount.id })
if (!isLpTokenMigrationDay)
await InvestorPositionService.buy(
txIn.accountId,
txIn.trancheId,
txIn.hash,
txIn.timestamp,
txIn.tokenAmount,
txIn.tokenPrice
)
await txIn.save()
await tranche.loadSnapshot(getPeriodStart(timestamp))
const price = tranche.tokenPrice

const txOut = InvestorTransactionService.transferOut({ ...orderData, address: fromAccount.id })
const txIn = InvestorTransactionService.transferIn({ ...orderData, address: toAccount.id, price })
await txIn.save()
if (!isLpTokenMigrationDay)
try {
await InvestorPositionService.buy(
txIn.accountId,
txIn.trancheId,
txIn.hash,
txIn.timestamp,
txIn.tokenAmount,
txIn.tokenPrice
)
} catch (error) {
logger.error(`Unable to save buy investor position: ${error}`)
// TODO: Fallback use PoolManager Contract to read price
}

const txOut = InvestorTransactionService.transferOut({ ...orderData, address: fromAccount.id, price })
if (!isLpTokenMigrationDay) {
const profit = await InvestorPositionService.sellFifo(
txOut.accountId,
txOut.trancheId,
txOut.tokenAmount,
txOut.tokenPrice
)
await txOut.setRealizedProfitFifo(profit)
try {
const profit = await InvestorPositionService.sellFifo(
txOut.accountId,
txOut.trancheId,
txOut.tokenAmount,
txOut.tokenPrice
)
await txOut.setRealizedProfitFifo(profit)
} catch (error) {
logger.error(`Unable to save sell investor position: ${error}`)
// TODO: Fallback use PoolManager Contract to read price
}
}
await txOut.save()
}
Expand Down
2 changes: 1 addition & 1 deletion src/mappings/services/assetCashflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class AssetCashflowService extends AssetCashflow {

static async clearAssetCashflows(assetId: string) {
logger.info(`Clearing AssetCashflows for asset: ${assetId}`)
const cashflows = await this.getByAssetId(assetId)
const cashflows = await this.getByAssetId(assetId, { limit: 100 })
const deletes = cashflows.map((cf) => this.remove(cf.id))
return Promise.all(deletes)
}
Expand Down
4 changes: 2 additions & 2 deletions src/mappings/services/assetPositionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class AssetPositionService extends AssetPosition {
`sellingQuantity: ${sellingQuantity.toString(10)} sellingPrice: ${sellingPrice.toString(10)}`
)
if (sellingQuantity <= BigInt(0)) return BigInt(0)
const positions = await this.getByAssetId(assetId)
const positions = await this.getByAssetId(assetId, { limit: 100 })
positions.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf())

const sellPositions: [assetPosition: AssetPosition, sellQuantity: bigint][] = []
Expand Down Expand Up @@ -67,7 +67,7 @@ export class AssetPositionService extends AssetPosition {
static async computeUnrealizedProfitAtPrice(assetId: string, sellingPrice: bigint) {
if (!sellingPrice || sellingPrice <= BigInt(0)) return BigInt(0)
logger.info(`Computing unrealizedProfit at price ${sellingPrice} for asset ${assetId}`)
const sellingPositions = await this.getByAssetId(assetId)
const sellingPositions = await this.getByAssetId(assetId, { limit: 100 })
const sellingQuantity = sellingPositions.reduce<bigint>(
(result, position) => result + position.holdingQuantity,
BigInt(0)
Expand Down
4 changes: 2 additions & 2 deletions src/mappings/services/assetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class AssetService extends Asset {
await AssetService.getByFields([
['collateralNftClassId', '=', collectionId],
['collateralNftItemId', '=', itemId],
])
], { limit: 100 })
).pop() as AssetService
return asset
}
Expand Down Expand Up @@ -263,7 +263,7 @@ export class AssetService extends Asset {
const snapshots = await AssetSnapshot.getByFields([
['assetId', '=', 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
Expand Down
2 changes: 1 addition & 1 deletion src/mappings/services/epochService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class EpochService extends Epoch {
static async getById(poolId: string, epochNr: number) {
const epoch = (await this.get(`${poolId}-${epochNr.toString()}`)) as EpochService
if (!epoch) return undefined
const epochStates = await EpochState.getByEpochId(`${poolId}-${epochNr.toString(10)}`)
const epochStates = await EpochState.getByEpochId(`${poolId}-${epochNr.toString(10)}`, { limit: 100 })
epoch.states = epochStates
return epoch
}
Expand Down
20 changes: 17 additions & 3 deletions src/mappings/services/investorPositionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import assert from 'assert'

export class InvestorPositionService extends InvestorPosition {
static init(accountId: string, trancheId: string, hash: string, timestamp: Date, quantity: bigint, price: bigint) {
const [ poolId ] = trancheId.split('-')
const [poolId] = trancheId.split('-')
assert(quantity, 'Missing quantity')
assert(price, 'Missing price')
assert(hash, 'Missing hash')
Expand All @@ -27,12 +27,20 @@ export class InvestorPositionService extends InvestorPosition {
}

static async sellFifo(accountId: string, trancheId: string, sellingQuantity: bigint, sellingPrice: bigint) {
assert(sellingPrice, 'Missing price')
assert(sellingQuantity, 'Missing quantity')
logger.info(
`Selling positions for ${trancheId} ` +
`sellingQuantity: ${sellingQuantity.toString(10)} sellingPrice: ${sellingPrice.toString(10)}`
)
if (sellingQuantity <= BigInt(0)) return BigInt(0)
const positions = await this.getByFields([['accountId', '=', accountId], ['trancheId', '=', trancheId]])
const positions = await this.getByFields(
[
['accountId', '=', accountId],
['trancheId', '=', trancheId],
],
{ limit: 100 }
)
positions.sort((a, b) => b.timestamp.valueOf() - a.timestamp.valueOf())

const sellPositions: [InvestorPosition: InvestorPosition, sellQuantity: bigint][] = []
Expand Down Expand Up @@ -72,7 +80,13 @@ export class InvestorPositionService extends InvestorPosition {
static async computeUnrealizedProfitAtPrice(accountId: string, trancheId: string, sellingPrice: bigint) {
if (!sellingPrice || sellingPrice <= BigInt(0)) return BigInt(0)
logger.info(`Computing unrealizedProfit at price ${sellingPrice} for tranche ${trancheId}`)
const sellingPositions = await this.getByFields([['accountId', '=', accountId], ['trancheId', '=', trancheId]])
const sellingPositions = await this.getByFields(
[
['accountId', '=', accountId],
['trancheId', '=', trancheId],
],
{ limit: 100 }
)
const sellingQuantity = sellingPositions.reduce<bigint>(
(result, position) => result + position.holdingQuantity,
BigInt(0)
Expand Down
2 changes: 1 addition & 1 deletion src/mappings/services/poolFeeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class PoolFeeService extends PoolFee {

static async computeSumPendingFees(poolId: string): Promise<bigint> {
logger.info(`Computing pendingFees for pool: ${poolId} `)
const poolFees = await this.getByPoolId(poolId)
const poolFees = await this.getByPoolId(poolId, { limit: 100 })
return poolFees.reduce((sumPendingAmount, poolFee) => (sumPendingAmount + poolFee.pendingAmount), BigInt(0))
}
}
18 changes: 16 additions & 2 deletions src/mappings/services/trancheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Tranche, TrancheSnapshot } from '../../types'
const MAINNET_CHAINID = '0xb3db41421702df9a7fcac62b53ffeac85f7853cc4e689e0b93aeb3db18c09d82'

export class TrancheService extends Tranche {
snapshot?: TrancheSnapshot

static seed(poolId: string, trancheId: string, blockchain = '0') {
const id = `${poolId}-${trancheId}`
logger.info(`Seeding tranche ${id}`)
Expand Down Expand Up @@ -151,7 +153,7 @@ export class TrancheService extends Tranche {

let trancheSnapshot: TrancheSnapshot
if (referencePeriodStart) {
const trancheSnapshots = await TrancheSnapshot.getByPeriodId(referencePeriodStart.toISOString())
const trancheSnapshots = await TrancheSnapshot.getByPeriodId(referencePeriodStart.toISOString(), { limit: 100 })
if (trancheSnapshots.length === 0) {
logger.warn(`No tranche snapshot exist for pool ${this.poolId} with reference date ${referencePeriodStart}`)
return this
Expand Down Expand Up @@ -186,7 +188,7 @@ export class TrancheService extends Tranche {
`Computing annualized yield ${yieldField} for tranche ${this.trancheId} of ` +
`pool ${this.poolId} with reference date ${referencePeriodStart}`
)
const trancheSnapshots = await TrancheSnapshot.getByPeriodId(referencePeriodStart.toISOString())
const trancheSnapshots = await TrancheSnapshot.getByPeriodId(referencePeriodStart.toISOString(), { limit: 100 })
if (trancheSnapshots.length === 0) {
logger.warn(`No tranche snapshot found pool ${this.poolId} with reference date ${referencePeriodStart}`)
return this
Expand Down Expand Up @@ -259,6 +261,18 @@ export class TrancheService extends Tranche {
logger.info(`Activating tranche ${this.id}`)
this.isActive = true
}

public async loadSnapshot(periodStart: Date) {
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
}
this.snapshot = snapshots.pop()
}
}

type BigIntFields<T> = { [K in keyof T]: T[K] extends bigint ? K : never }[keyof T]
Loading

0 comments on commit 9ec23c4

Please sign in to comment.