Skip to content

Commit

Permalink
Merge pull request #124 from embrio-tech/main
Browse files Browse the repository at this point in the history
218-track-poolFee-accruals (#224)
  • Loading branch information
hieronx authored Mar 15, 2024
2 parents f3e3e63 + a09cf16 commit ccf7eca
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 309 deletions.
28 changes: 27 additions & 1 deletion src/chaintypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const definitions: OverrideBundleDefinition = {
reserve: 'Balance',
total: 'Balance',
},
PoolFeesOfBucket: {
bucket: 'CfgTraitsFeePoolFeeBucket',
fees: ' Vec<CfgTypesPoolsPoolFee>',
},
PoolFeesList: 'Vec<PoolFeesOfBucket>',
},
},
],
Expand Down Expand Up @@ -51,7 +56,7 @@ const definitions: OverrideBundleDefinition = {
type: 'Option<PalletLoansEntitiesLoansActiveLoan>',
},
},
version: 1,
version: 2,
},
],
PoolsApi: [
Expand All @@ -71,6 +76,23 @@ const definitions: OverrideBundleDefinition = {
version: 1,
},
],
PoolFeesApi: [
{
methods: {
list_fees: {
description: 'Query pool fees status for a pool',
params: [
{
name: 'pool_id',
type: 'u64',
},
],
type: 'Option<PoolFeesList>',
},
},
version: 1,
},
],
},
rpc: {
pools: {
Expand Down Expand Up @@ -117,6 +139,10 @@ const definitions: OverrideBundleDefinition = {
},
}

// Fix for LoansApi old runtime v1
const loansApiRuntime = definitions['runtime']['LoansApi']
loansApiRuntime.push({ ...loansApiRuntime[0], version: 1 })

export default {
typesBundle: { spec: { 'centrifuge-devel': definitions, altair: definitions, centrifuge: definitions } },
}
36 changes: 33 additions & 3 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ export interface TokensCurrencyId extends Enum {
asForeignAsset: u32
isStaking: boolean
asStaking: Enum
isLocalAsset: boolean;
asLocalAsset: u32;
type: 'Native' | 'Tranche' | 'Ausd' | 'ForeignAsset' | 'Staking' | 'LocalAsset';
isLocalAsset: boolean
asLocalAsset: u32
type: 'Native' | 'Tranche' | 'Ausd' | 'ForeignAsset' | 'Staking' | 'LocalAsset'
}

export interface TrancheSolution extends Struct {
Expand Down Expand Up @@ -345,6 +345,11 @@ export interface PoolFeeBucket extends Enum {
type: 'Top'
}

export interface PoolFeesOfBucket extends Struct {
bucket: PoolFeeBucket
fees: Vec<PoolFee>
}

export interface PoolFeeEditor extends Enum {
isRoot: boolean
isAccount: boolean
Expand All @@ -360,6 +365,20 @@ export interface PoolFeeAmount extends Enum {
type: 'ShareOfPortfolioValuation' | 'AmountPerSecond'
}

interface PayableFeeAmount extends Enum {
readonly isAllPending: boolean
readonly isUpTo: boolean
readonly asUpTo: u128
readonly type: 'AllPending' | 'UpTo'
}

export interface PoolFeeAmounts extends Struct {
readonly feeType: PoolFeeType
readonly pending: u128
readonly disbursement: u128
readonly payable: PayableFeeAmount
}

export interface PoolFeeType extends Enum {
isFixed: boolean
asFixed: {
Expand All @@ -378,6 +397,13 @@ export interface PoolFeeInfo extends Struct {
feeType: PoolFeeType
}

export interface PoolFee extends Struct {
id: u64
destination: AccountId32
editor: PoolFeeEditor
amounts: PoolFeeAmounts
}

export type LoanAsset = ITuple<[collectionId: u64, itemId: u128]>
export type LoanCreatedEvent = ITuple<[poolId: u64, loanId: u64, loanInfo: LoanInfoCreated]>
export type LoanClosedEvent = ITuple<[poolId: u64, loanId: u64, collateralInfo: LoanAsset]>
Expand Down Expand Up @@ -424,6 +450,7 @@ export type PoolFeesRemovedEvent = ITuple<[poolId: u64, bucket: PoolFeeBucket, f
export type PoolFeesChargedEvent = ITuple<[poolId: u64, feeId: u64, amount: u128, pending: u128]>
export type PoolFeesUnchargedEvent = PoolFeesChargedEvent
export type PoolFeesPaidEvent = ITuple<[poolId: u64, feeId: u64, amount: u128, destination: AccountId32]>
export type PoolFeesList = Vec<PoolFeesOfBucket>

export type ExtendedRpc = typeof api.rpc & {
pools: {
Expand All @@ -441,4 +468,7 @@ export type ExtendedCall = typeof api.call & {
poolsApi: {
nav: AugmentedCall<'promise', (poolId: string) => Observable<Option<PoolNav>>>
}
poolFeesApi: {
listFees: AugmentedCall<'promise', (poolId: string) => Observable<Option<PoolFeesList>>>
}
}
27 changes: 26 additions & 1 deletion src/mappings/handlers/blockHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { SNAPSHOT_INTERVAL_SECONDS } from '../../config'
import { PoolService } from '../services/poolService'
import { TrancheService } from '../services/trancheService'
import { AssetService } from '../services/assetService'
import { PoolFeeService } from '../services/poolFeeService'
import { PoolFeeTransactionService } from '../services/poolFeeTransactionService'

const timekeeper = TimekeeperService.init()

Expand All @@ -16,7 +18,10 @@ async function _handleBlock(block: SubstrateBlock): Promise<void> {
const newPeriod = (await timekeeper).processBlock(block.timestamp)

if (newPeriod) {
logger.info(`It's a new period on block ${blockNumber}: ${block.timestamp.toISOString()}`)
const specVersion = api.runtimeVersion.specVersion.toNumber()
logger.info(
`It's a new period on block ${blockNumber}: ${block.timestamp.toISOString()} (specVersion: ${specVersion})`
)
const lastPeriodStart = new Date(blockPeriodStart.valueOf() - SNAPSHOT_INTERVAL_SECONDS * 1000)
const daysAgo30 = new Date(blockPeriodStart.valueOf() - 30 * 24 * 3600 * 1000)
const daysAgo90 = new Date(blockPeriodStart.valueOf() - 90 * 24 * 3600 * 1000)
Expand Down Expand Up @@ -57,6 +62,26 @@ async function _handleBlock(block: SubstrateBlock): Promise<void> {

await pool.updateNumberOfActiveAssets(BigInt(Object.keys(activeLoanData).length))
await pool.save()

//PoolFees Accruals
const accruedFees = await pool.getAccruedFees()
for (const accruals of accruedFees) {
const [feeId, pending, disbursement] = accruals
const poolFee = await PoolFeeService.getById(pool.id, feeId)
poolFee.updateAccruals(pending, disbursement)
await poolFee.save()

const poolFeeTransaction = PoolFeeTransactionService.accrue({
poolId: pool.id,
feeId,
blockNumber,
amount: poolFee.sumAccruedAmountByPeriod,
epochNumber: pool.currentEpoch,
hash: null,
timestamp: block.timestamp,
})
await poolFeeTransaction.save()
}
}

//Perform Snapshots and reset accumulators
Expand Down
4 changes: 2 additions & 2 deletions src/mappings/handlers/ethHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import {
PileAbi__factory,
MulticallAbi__factory,
} from '../../types/contracts'
import { Provider } from '@ethersproject/providers'
import { TimekeeperService, getPeriodStart } from '../../helpers/timekeeperService'
import { AssetService } from '../services/assetService'
import { evmStateSnapshotter } from '../../helpers/stateSnapshot'
import { Multicall3 } from '../../types/contracts/MulticallAbi'
import { BigNumber } from 'ethers'
import type { Provider } from '@ethersproject/providers'
import type { BigNumber } from '@ethersproject/bignumber'

const timekeeper = TimekeeperService.init()

Expand Down
2 changes: 1 addition & 1 deletion src/mappings/handlers/evmHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CurrencyService } from '../services/currencyService'
import { BlockchainService } from '../services/blockchainService'
import { CurrencyBalanceService } from '../services/currencyBalanceService'
import { InvestmentManagerAbi__factory, PoolManagerAbi__factory } from '../../types/contracts'
import { Provider } from '@ethersproject/providers'
import type { Provider } from '@ethersproject/providers'
import { TrancheBalanceService } from '../services/trancheBalanceService'

const ethApi = api as unknown as Provider
Expand Down
24 changes: 20 additions & 4 deletions src/mappings/services/poolFeeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,32 +63,48 @@ export class PoolFeeService extends PoolFee {
logger.info(`Removing PoolFee ${data.feeId}`)
const { poolId, feeId } = data
const poolFee = await this.get(`${poolId}-${feeId}`)
if(!poolFee) throw new Error('Unable to remove PoolFee. PoolFee does not exist.')
if (!poolFee) throw new Error('Unable to remove PoolFee. PoolFee does not exist.')
poolFee.isActive = false
return poolFee
}

public charge(data: Omit<PoolFeeData, 'amount'> & Required<Pick<PoolFeeData, 'amount'>>) {
logger.info(`Charging PoolFee ${data.feeId} with amount ${data.amount.toString(10)}`)
if(!this.isActive) throw new Error('Unable to charge inactive PolFee')
if (!this.isActive) throw new Error('Unable to charge inactive PolFee')
this.sumChargedAmount += data.amount
this.sumChargedAmountByPeriod += data.amount
this.pendingAmount += data.amount
return this
}

public uncharge(data: Omit<PoolFeeData, 'amount'> & Required<Pick<PoolFeeData, 'amount'>>) {
logger.info(`Uncharging PoolFee ${data.feeId} with amount ${data.amount.toString(10)}`)
if(!this.isActive) throw new Error('Unable to uncharge inactive PolFee')
if (!this.isActive) throw new Error('Unable to uncharge inactive PolFee')
this.sumChargedAmount -= data.amount
this.sumChargedAmountByPeriod -= data.amount
this.pendingAmount -= data.amount
return this
}

public pay(data: Omit<PoolFeeData, 'amount'> & Required<Pick<PoolFeeData, 'amount'>>) {
logger.info(`Paying PoolFee ${data.feeId} with amount ${data.amount.toString(10)}`)
if(!this.isActive) throw new Error('Unable to payinactive PolFee')
if (!this.isActive) throw new Error('Unable to payinactive PolFee')
this.sumPaidAmount += data.amount
this.sumPaidAmountByPeriod += data.amount
this.pendingAmount -= data.amount
return this
}

public updateAccruals(pending: bigint, disbursement: bigint) {
logger.info(
`Accruing PoolFee ${this.id} with amounts pending: ${pending.toString(10)} ` +
`disbursement: ${disbursement.toString(10)}`
)
this.pendingAmount = pending + disbursement

const newAccruedAmount = this.pendingAmount
this.sumAccruedAmountByPeriod = newAccruedAmount - this.sumAccruedAmount
this.sumAccruedAmount = newAccruedAmount
return this
}
}
31 changes: 30 additions & 1 deletion src/mappings/services/poolService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ExtendedRpc,
NavDetails,
PoolDetails,
PoolFeesList,
PoolMetadata,
PoolNav,
TrancheDetails,
Expand Down Expand Up @@ -136,17 +137,19 @@ export class PoolService extends Pool {
logger.info(`Updating portfolio valuation for pool: ${this.id} (state)`)
const navResponse = await api.query.loans.portfolioValuation<NavDetails>(this.id)
this.portfolioValuation = navResponse.value.toBigInt()
logger.info(`portfolio valuation: ${this.portfolioValuation.toString(10)}`)
return this
}

private async updatePortfolioValuationCall() {
logger.info(`Updating portfolio valuation for pool: ${this.id} (runtime)`)
const apiCall = api.call as ExtendedCall
logger.info(JSON.stringify(apiCall.poolsApi))
const navResponse = await apiCall.poolsApi.nav(this.id)
if (navResponse.isEmpty) logger.warn('Empty pv response')
this.portfolioValuation = navResponse
.unwrapOr<Pick<PoolNav, 'total'>>({ total: api.registry.createType('Balance', 0) })
.total.toBigInt()
logger.info(`portfolio valuation: ${this.portfolioValuation.toString(10)}`)
return this
}

Expand Down Expand Up @@ -228,7 +231,9 @@ export class PoolService extends Pool {

public async getPortfolio(): Promise<ActiveLoanData> {
const apiCall = api.call as ExtendedCall
logger.info(`Querying runtime loansApi.portfolio for pool: ${this.id}`)
const portfolioData = await apiCall.loansApi.portfolio(this.id)
logger.info(`${portfolioData.length} assets found.`)
return portfolioData.reduce<ActiveLoanData>((obj, current) => {
const totalRepaid = current[1].activeLoan.totalRepaid
const maturityDate = new Date(current[1].activeLoan.schedule.maturity.asFixed.date.toNumber() * 1000)
Expand Down Expand Up @@ -264,6 +269,30 @@ export class PoolService extends Pool {
}
return tokenPrices
}

public async getAccruedFees() {
const apiCall = api.call as ExtendedCall
const specVersion = api.runtimeVersion.specVersion.toNumber()
const specName = api.runtimeVersion.specName.toString()
switch (specName) {
case 'centrifuge-devel':
if (specVersion < 1040) return []
break
default:
if (specVersion < 1026) return []
break
}
logger.info(`Querying runtime poolFeesApi.listFees for pool ${this.id}`)
const poolFeesListRequest = await apiCall.poolFeesApi.listFees(this.id)
const poolFeesList = poolFeesListRequest.unwrapOr(<PoolFeesList>[])
const fees = poolFeesList.flatMap((poolFee) => poolFee.fees.filter((fee) => fee.amounts.feeType.isFixed))
const accruedFees = fees.map((fee): [feeId: string, pending: bigint, disbursement: bigint] => [
fee.id.toString(),
fee.amounts.pending.toBigInt(),
fee.amounts.disbursement.toBigInt(),
])
return accruedFees
}
}

export interface ActiveLoanData {
Expand Down
Loading

0 comments on commit ccf7eca

Please sign in to comment.