Skip to content

Commit

Permalink
feat: track future cashflows per asset
Browse files Browse the repository at this point in the history
Fixes #196
  • Loading branch information
filo87 committed Jun 28, 2024
1 parent 8a13a97 commit 7a70b55
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 25 deletions.
9 changes: 9 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,15 @@ type AssetTransaction @entity {
realizedProfitFifo: BigInt
}

type AssetCashflow @entity {
id: ID! # pool id - asset id - cf timestamp
asset: Asset! @index

timestamp: Date!
principal: BigInt!
interest: BigInt!
}

type OracleTransaction @entity {
id: ID! # extrinsic hash - timestamp - oracle key
timestamp: Date!
Expand Down
19 changes: 19 additions & 0 deletions src/chaintypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const latestTypes = {
bucket: 'CfgTraitsFeePoolFeeBucket',
fees: 'Vec<CfgTypesPoolsPoolFee>',
},
CashflowPayment: {
when: 'Seconds',
principal: 'Balance',
interest: 'Balance',
},
}

const definitions: OverrideBundleDefinition = {
Expand Down Expand Up @@ -73,6 +78,20 @@ const definitions: OverrideBundleDefinition = {
],
type: 'Option<PalletLoansEntitiesLoansActiveLoan>',
},
expected_cashflows: {
description: 'Retrieve expected cashflows',
params: [
{
name: 'pool_id',
type: 'u64',
},
{
name: 'loan_id',
type: 'u64',
},
],
type: 'Vec<CashflowPayment>',
},
},
version: 2,
},
Expand Down
43 changes: 25 additions & 18 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export interface LoanPricing extends Enum {
asExternal: {
priceId: OracleKey
maxBorrowAmount: LoanExternalPricingMaxBorrowAmount
notional: u128,
notional: u128
maxPriceVariation: u128
}
}
Expand Down Expand Up @@ -403,30 +403,36 @@ export interface PoolFee extends Struct {
}

export interface OracleKey extends Enum {
readonly isIsin: boolean;
readonly asIsin: U8aFixed;
readonly isConversionRatio: boolean;
readonly asConversionRatio: ITuple<[TokensCurrencyId, TokensCurrencyId]>;
readonly isPoolLoanId: boolean;
readonly asPoolLoanId: ITuple<[u64, u64]>;
readonly type: 'Isin' | 'ConversionRatio' | 'PoolLoanId';
readonly isIsin: boolean
readonly asIsin: U8aFixed
readonly isConversionRatio: boolean
readonly asConversionRatio: ITuple<[TokensCurrencyId, TokensCurrencyId]>
readonly isPoolLoanId: boolean
readonly asPoolLoanId: ITuple<[u64, u64]>
readonly type: 'Isin' | 'ConversionRatio' | 'PoolLoanId'
}

interface DevelopmentRuntimeOriginCaller extends Enum {
readonly isSystem: boolean;
readonly asSystem: unknown//FrameSupportDispatchRawOrigin;
readonly isVoid: boolean;
readonly isCouncil: boolean;
readonly isSystem: boolean
readonly asSystem: unknown //FrameSupportDispatchRawOrigin;
readonly isVoid: boolean
readonly isCouncil: boolean
readonly asCouncil: unknown //PalletCollectiveRawOrigin;
readonly isLiquidityPoolsGateway: boolean;
readonly isLiquidityPoolsGateway: boolean
readonly asLiquidityPoolsGateway: unknown //PalletLiquidityPoolsGatewayOriginGatewayOrigin;
readonly isPolkadotXcm: boolean;
readonly asPolkadotXcm: unknown//PalletXcmOrigin;
readonly isCumulusXcm: boolean;
readonly isPolkadotXcm: boolean
readonly asPolkadotXcm: unknown //PalletXcmOrigin;
readonly isCumulusXcm: boolean
readonly asCumulusXcm: unknown //CumulusPalletXcmOrigin;
readonly isEthereum: boolean;
readonly isEthereum: boolean
readonly asEthereum: unknown //PalletEthereumRawOrigin;
readonly type: 'System' | 'Void' | 'Council' | 'LiquidityPoolsGateway' | 'PolkadotXcm' | 'CumulusXcm' | 'Ethereum';
readonly type: 'System' | 'Void' | 'Council' | 'LiquidityPoolsGateway' | 'PolkadotXcm' | 'CumulusXcm' | 'Ethereum'
}

export interface CashflowPayment extends Struct {
when: u64
principal: Balance
interest: Balance
}

export type LoanAsset = ITuple<[collectionId: u64, itemId: u128]>
Expand Down Expand Up @@ -492,6 +498,7 @@ export type ExtendedRpc = typeof api.rpc & {
export type ExtendedCall = typeof api.call & {
loansApi: {
portfolio: AugmentedCall<'promise', (poolId: string) => Observable<Vec<ITuple<[u64, LoanInfoActivePortfolio]>>>>
expectedCashflows: AugmentedCall<'promise', (poolId: string, loanId: string) => Observable<Vec<CashflowPayment>>>
}
poolsApi: {
nav: AugmentedCall<'promise', (poolId: string) => Observable<Option<PoolNav>>>
Expand Down
30 changes: 23 additions & 7 deletions src/mappings/handlers/loansHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AssetType, AssetValuationMethod } from '../../types'
import { bnToBn, nToBigInt } from '@polkadot/util'
import { WAD } from '../../config'
import { AssetPositionService } from '../services/assetPositionService'
import { AssetCashflowService } from '../services/assetCashflowService'

export const handleLoanCreated = errorHandler(_handleLoanCreated)
async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {
Expand Down Expand Up @@ -96,6 +97,9 @@ async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedEvent>) {
// Update pool info
await pool.increaseNumberOfAssets()
await pool.save()

// Record cashflows
await AssetCashflowService.recordAssetCashflows(pool.id, asset.id)
}

export const handleLoanBorrowed = errorHandler(_handleLoanBorrowed)
Expand Down Expand Up @@ -168,6 +172,9 @@ async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedEvent>): Pr
await epoch.save()
}
await asset.save()

// Record cashflows
await AssetCashflowService.recordAssetCashflows(pool.id, asset.id)
}

export const handleLoanRepaid = errorHandler(_handleLoanRepaid)
Expand Down Expand Up @@ -245,22 +252,28 @@ async function _handleLoanRepaid(event: SubstrateEvent<LoanRepaidEvent>) {
}

await asset.save()

// Record cashflows
await AssetCashflowService.recordAssetCashflows(pool.id, asset.id)
}

export const handleLoanWrittenOff = errorHandler(_handleLoanWrittenOff)
async function _handleLoanWrittenOff(event: SubstrateEvent<LoanWrittenOffEvent>) {
const [poolId, loanId, status] = event.event.data
logger.info(`Loan writtenoff event for pool: ${poolId.toString()} loanId: ${loanId.toString()}`)
const { percentage, penalty } = status
const loan = await AssetService.getById(poolId.toString(), loanId.toString())
await loan.writeOff(percentage.toBigInt(), penalty.toBigInt())
await loan.save()
const asset = await AssetService.getById(poolId.toString(), loanId.toString())
await asset.writeOff(percentage.toBigInt(), penalty.toBigInt())
await asset.save()

const pool = await PoolService.getById(poolId.toString())
if (pool === undefined) throw missingPool

await pool.increaseWriteOff(loan.writtenOffAmountByPeriod)
await pool.increaseWriteOff(asset.writtenOffAmountByPeriod)
await pool.save()

// Record cashflows
await AssetCashflowService.recordAssetCashflows(pool.id, asset.id)
}

export const handleLoanClosed = errorHandler(_handleLoanClosed)
Expand All @@ -273,9 +286,9 @@ async function _handleLoanClosed(event: SubstrateEvent<LoanClosedEvent>) {

const account = await AccountService.getOrInit(event.extrinsic.extrinsic.signer.toHex())

const loan = await AssetService.getById(poolId.toString(), loanId.toString())
await loan.close()
await loan.save()
const asset = await AssetService.getById(poolId.toString(), loanId.toString())
await asset.close()
await asset.save()

const epoch = await EpochService.getById(pool.id, pool.currentEpoch)
if (!epoch) throw new Error('Epoch not found!')
Expand All @@ -289,6 +302,9 @@ async function _handleLoanClosed(event: SubstrateEvent<LoanClosedEvent>) {
timestamp: event.block.timestamp,
})
await at.save()

// Record cashflows
await AssetCashflowService.recordAssetCashflows(pool.id, asset.id)
}

export const handleLoanDebtTransferred = errorHandler(_handleLoanDebtTransferred)
Expand Down
29 changes: 29 additions & 0 deletions src/mappings/services/assetCashflowService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ExtendedCall } from '../../helpers/types'
import { AssetCashflow } from '../../types/models/AssetCashflow'

export class AssetCashflowService extends AssetCashflow {
static init(poolId: string, assetId: string, timestamp: Date, principal: bigint, interest: bigint) {
logger.info(`Initialising new AssetCashflow with Id ${chainId}`)
return new this(
`${poolId}-${assetId}-${timestamp}`,
assetId,
timestamp,
principal,
interest
)
}

static async recordAssetCashflows(poolId: string, assetId: string) {
const specVersion = api.runtimeVersion.specVersion.toNumber()
if(specVersion < 1103) return
const apiCall = api.call as ExtendedCall
const response = await apiCall.loansApi.expectedCashflows(poolId, assetId)
const saves = response.map( ( cf ) => {
const { when, principal, interest } = cf
const timestamp = new Date(when.toNumber() * 1000)
const cashflow = this.init(poolId, assetId, timestamp, principal.toBigInt(), interest.toBigInt())
return cashflow.save()
})
return Promise.all(saves)
}
}

0 comments on commit 7a70b55

Please sign in to comment.