Skip to content
This repository has been archived by the owner on May 7, 2024. It is now read-only.

Commit

Permalink
74 index loanentity data types (#86)
Browse files Browse the repository at this point in the history
* feat: create loanService

* refactor: improved service getters

* feat: increase outstandingDebt on borrowings

* feat: basics for handle loan prices

* feat: save priced loans

* feat: activate loan when priced

* feat: handle writeoffs

* feat: handle loan closed and write offs
  • Loading branch information
filo87 authored Aug 30, 2022
1 parent 1ea5072 commit 1c3cc19
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@ jobs:
--projectName="$SUBQL_PROJ_NAME" \
--ipfsCID="$IPFSCID" \
--type=primary \
--indexerVersion="v1.6.1" \
--indexerVersion="v1.9.1" \
--queryVersion="v1.5.0"
23 changes: 21 additions & 2 deletions project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ dataSources:
- kind: substrate/Runtime
startBlock: 1
mapping:

file: ./dist/index.js
handlers:
- handler: handleBlock
Expand Down Expand Up @@ -74,11 +73,31 @@ dataSources:
filter:
module: proxy
method: AnonymousCreated
- handler: handleBorrowings
- handler: handleLoanCreated
kind: substrate/EventHandler
filter:
module: loans
method: Created
- handler: handleLoanBorrowed
kind: substrate/EventHandler
filter:
module: loans
method: Borrowed
- handler: handleLoanPriced
kind: substrate/EventHandler
filter:
module: loans
method: Priced
- handler: handleLoanRepaid
kind: substrate/EventHandler
filter:
module: loans
method: Repaid
- handler: handleLoanClosed
kind: substrate/EventHandler
filter:
module: loans
method: Closed
- handler: handleTokenTransfer
kind: substrate/EventHandler
filter:
Expand Down
9 changes: 8 additions & 1 deletion schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,19 @@ type Loan @entity {

# collateral:
status: LoanStatus!
# TODO: how to store loan type data

type: String
spec: String

interestRatePerSec: BigInt
outstandingDebt: BigInt

totalBorrowed: BigInt
totalRepaid: BigInt

writeOffIndex: Int
writtenOffPercentage: BigInt
penaltyInterestRatePerSec: BigInt
adminWrittenOff: Boolean

pool: Pool!
Expand Down
16 changes: 14 additions & 2 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,26 @@ export interface AssetMetadata extends Struct {
existentialDeposit: u128
}

export type LoanAsset = ITuple<[u64, u128]>
export interface LoanSpecs extends Struct {
advanceRate: u128
value: u128
probabilityOfDefault?: u128
lossGivenDefault?: u128
discountRate?: u128
maturityDate?: u64
}

export type LoanAsset = ITuple<[u64, u128]>
export type PoolEvent = ITuple<[u64]>

// poolId, loanId, collateral
export type LoanCreatedClosedEvent = ITuple<[u64, u128, LoanAsset]>
// poolId, loanId, amount
export type LoanBorrowedEvent = ITuple<[u64, u128, u128]>
export type LoanBorrowedRepaidEvent = ITuple<[u64, u128, u128]>
//poolId, loanId, interestRatePerSec, loanType
export type LoanPricedEvent = ITuple<[u64, u128, u128, Enum]>
//poolId, loanId, percentage, penaltyInterestRatePerSec, writeOffGroupIndex
export type LoanWrittenOffEvent = ITuple<[u64, u128, u128, u128, Option<u32>]>

export type EpochEvent = ITuple<[u64, u32]>
export type EpochSolutionEvent = ITuple<[u64, u32, EpochSolution]>
Expand Down
71 changes: 66 additions & 5 deletions src/mappings/handlers/loansHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,74 @@
import { SubstrateEvent } from '@subql/types'
import { LoanBorrowedEvent } from '../../helpers/types'
import {
LoanBorrowedRepaidEvent,
LoanCreatedClosedEvent,
LoanPricedEvent,
LoanWrittenOffEvent,
} from '../../helpers/types'
import { errorHandler } from '../../helpers/errorHandler'
import { PoolService } from '../services/poolService'
import { LoanService } from '../services/loanService'

export const handleBorrowings = errorHandler(_handleBorrowings)
async function _handleBorrowings(event: SubstrateEvent<LoanBorrowedEvent>): Promise<void> {
const [poolId, , amount] = event.event.data
logger.info(`Pool: ${poolId.toString()} borrowed ${amount.toString()}`)
export const handleLoanCreated = errorHandler(_handleLoanCreated)
async function _handleLoanCreated(event: SubstrateEvent<LoanCreatedClosedEvent>) {
const [poolId, loanId] = event.event.data
logger.info(`Loan created event for pool: ${poolId.toString()} loan: ${loanId.toString()}`)

const loan = await LoanService.init(poolId.toString(), loanId.toString(), event.block.timestamp)
await loan.save()
}

export const handleLoanBorrowed = errorHandler(_handleLoanBorrowed)
async function _handleLoanBorrowed(event: SubstrateEvent<LoanBorrowedRepaidEvent>): Promise<void> {
const [poolId, loanId, amount] = event.event.data
logger.info(`Loan borrowed event for pool: ${poolId.toString()} amount: ${amount.toString()}`)

// Update loan amount
const loan = await LoanService.getById(poolId.toString(), loanId.toString())
await loan.borrow(amount.toBigInt())
await loan.save()

// Update poolState info
const poolService = await PoolService.getById(poolId.toString())
await poolService.increaseTotalBorrowings(amount.toBigInt())
await poolService.save()
}

export const handleLoanPriced = errorHandler(_handleLoanPriced)
async function _handleLoanPriced(event: SubstrateEvent<LoanPricedEvent>) {
const [poolId, loanId, interestRatePerSec, loanType] = event.event.data
logger.info(`Loan priced event for pool: ${poolId.toString()} loan: ${loanId.toString()}`)
const loan = await LoanService.getById(poolId.toString(), loanId.toString())
await loan.activate()
await loan.updateInterestRate(interestRatePerSec.toBigInt())
await loan.updateLoanType(loanType.type, loanType.inner.toJSON())
await loan.save()
}

export const handleLoanRepaid = errorHandler(_handleLoanRepaid)
async function _handleLoanRepaid(event: SubstrateEvent<LoanBorrowedRepaidEvent>) {
const [poolId, loanId, amount] = event.event.data
logger.info(`Loan borrowed event for pool: ${poolId.toString()} amount: ${amount.toString()}`)
const loan = await LoanService.getById(poolId.toString(), loanId.toString())
await loan.repay(amount.toBigInt())
await loan.save()
}

export const handleLoanWrittenOff = errorHandler(_handleLoanWrittenOff)
async function _handleLoanWrittenOff(event: SubstrateEvent<LoanWrittenOffEvent>) {
const [poolId, loanId, percentage, penaltyInterestRatePerSec, writeOffGroupIndex] = event.event.data
logger.info(`Loan writtenoff event for pool: ${poolId.toString()} loanId: ${loanId.toString()}`)
const loan = await LoanService.getById(poolId.toString(), loanId.toString())
const writeOffIndex = writeOffGroupIndex.isSome ? writeOffGroupIndex.unwrap().toNumber() : null
await loan.writeOff(percentage.toBigInt(), penaltyInterestRatePerSec.toBigInt(), writeOffIndex)
await loan.save()
}

export const handleLoanClosed = errorHandler(_handleLoanClosed)
async function _handleLoanClosed(event: SubstrateEvent<LoanCreatedClosedEvent>) {
const [poolId, loanId] = event.event.data
logger.info(`Loan closed event for pool: ${poolId.toString()} loanId: ${loanId.toString()}`)
const loan = await LoanService.getById(poolId.toString(), loanId.toString())
await loan.close()
await loan.save()
}
14 changes: 7 additions & 7 deletions src/mappings/handlers/poolsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async function _handleEpochClosed(event: SubstrateEvent<EpochEvent>): Promise<vo
)
// Close the current epoch and open a new one
const tranches = await TrancheService.getByPoolId(poolId.toString())
const epoch = await EpochService.getById(`${poolId.toString()}-${epochId.toString()}`)
const epoch = await EpochService.getById(poolId.toString(), epochId.toNumber())
await epoch.closeEpoch(event.block.timestamp)
await epoch.save()

Expand All @@ -85,7 +85,7 @@ async function _handleEpochExecuted(event: SubstrateEvent<EpochEvent>): Promise<
block ${event.block.block.header.number.toString()}`
)

const epoch = await EpochService.getById(`${poolId.toString()}-${epochId.toString()}`)
const epoch = await EpochService.getById(poolId.toString(), epochId.toNumber())
await epoch.executeEpoch(event.block.timestamp)
await epoch.save()

Expand All @@ -95,7 +95,7 @@ async function _handleEpochExecuted(event: SubstrateEvent<EpochEvent>): Promise<

// Compute and save aggregated order fulfillment
const tranches = await TrancheService.getByPoolId(poolId.toString())
const nextEpoch = await EpochService.getById(`${poolId.toString()}-${(epochId.toNumber() + 1).toString()}`)
const nextEpoch = await EpochService.getById(poolId.toString(), epochId.toNumber() + 1)
for (const tranche of tranches) {
const epochState = epoch.epochStates.find((epochState) => epochState.trancheId === tranche.tranche.trancheId)
await tranche.updateSupply()
Expand Down Expand Up @@ -212,12 +212,12 @@ async function _handleInvestOrderUpdated(event: SubstrateEvent<OrderEvent>): Pro
await oo.save()

// Update tranche outstanding total
const tranche = await TrancheService.getById(`${poolId.toString()}-${trancheId.toHex()}`)
const tranche = await TrancheService.getById(poolId.toString(), trancheId.toHex())
await tranche.updateOutstandingInvestOrders(newAmount.toBigInt(), oldAmount.toBigInt())
await tranche.save()

// Update epochState outstanding total
const epoch = await EpochService.getById(`${poolId.toString()}-${pool.pool.currentEpoch}`)
const epoch = await EpochService.getById(poolId.toString(), pool.pool.currentEpoch)
await epoch.updateOutstandingInvestOrders(trancheId.toHex(), newAmount.toBigInt(), oldAmount.toBigInt())
await epoch.save()
}
Expand Down Expand Up @@ -257,12 +257,12 @@ async function _handleRedeemOrderUpdated(event: SubstrateEvent<OrderEvent>): Pro
await oo.save()

// Update tranche outstanding total
const tranche = await TrancheService.getById(`${poolId.toString()}-${trancheId.toHex()}`)
const tranche = await TrancheService.getById(poolId.toString(), trancheId.toHex())
await tranche.updateOutstandingRedeemOrders(newAmount.toBigInt(), oldAmount.toBigInt())
await tranche.save()

// Update epochState outstanding total
const epoch = await EpochService.getById(`${poolId.toString()}-${pool.pool.currentEpoch}`)
const epoch = await EpochService.getById(poolId.toString(), pool.pool.currentEpoch)
await epoch.updateOutstandingRedeemOrders(trancheId.toHex(), newAmount.toBigInt(), oldAmount.toBigInt())
await epoch.save()
}
Expand Down
16 changes: 8 additions & 8 deletions src/mappings/services/epochService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ export class EpochService {
this.epochStates = epochStates
}

static init = async (poolId: string, epochId: number, trancheIds: string[], timestamp: Date) => {
logger.info(`Initialising epoch ${epochId} for pool ${poolId}`)
const epoch = new Epoch(`${poolId}-${epochId.toString()}`)
static init = async (poolId: string, epochNr: number, trancheIds: string[], timestamp: Date) => {
logger.info(`Initialising epoch ${epochNr} for pool ${poolId}`)
const epoch = new Epoch(`${poolId}-${epochNr.toString()}`)

epoch.index = epochId
epoch.index = epochNr
epoch.poolId = poolId
epoch.openedAt = timestamp

Expand All @@ -26,7 +26,7 @@ export class EpochService {

const epochStates: EpochState[] = []
for (const trancheId of trancheIds) {
const epochState = new EpochState(`${poolId}-${epochId}-${trancheId}`)
const epochState = new EpochState(`${poolId}-${epochNr}-${trancheId}`)
epochState.epochId = epoch.id
epochState.trancheId = trancheId
epochState.outstandingInvestOrders = BigInt(0)
Expand All @@ -36,10 +36,10 @@ export class EpochService {
return new EpochService(epoch, epochStates)
}

static getById = async (epochId: string) => {
const epoch = await Epoch.get(epochId)
static getById = async (poolId: string, epochNr: number) => {
const epoch = await Epoch.get(`${poolId}-${epochNr.toString()}`)
if (epoch === undefined) return undefined
const epochStates = await EpochState.getByEpochId(epochId)
const epochStates = await EpochState.getByEpochId(`${poolId}-${epochNr.toString()}`)
return new EpochService(epoch, epochStates)
}

Expand Down
73 changes: 73 additions & 0 deletions src/mappings/services/loanService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { AnyJson } from '@polkadot/types/types'
import { Loan, LoanStatus } from '../../types'

export class LoanService {
readonly loan: Loan

constructor(loan: Loan) {
this.loan = loan
}

static init = async (poolId: string, loanId: string, timestamp: Date) => {
logger.info(`Initialising loan ${loanId} for pool ${poolId}`)
const loan = new Loan(`${poolId}-${loanId}`)

loan.createdAt = timestamp
loan.poolId = poolId
loan.status = LoanStatus.CREATED
loan.outstandingDebt = BigInt(0)
loan.totalBorrowed = BigInt(0)
loan.totalRepaid = BigInt(0)

return new LoanService(loan)
}

static getById = async (poolId: string, loanId: string) => {
const loan = await Loan.get(`${poolId}-${loanId}`)
if (loan === undefined) return undefined
return new LoanService(loan)
}

public save = async () => {
await this.loan.save()
}

public borrow = (amount: bigint) => {
logger.info(`Increasing outstanding debt for loan ${this.loan.id} by ${amount}`)
this.loan.totalBorrowed = this.loan.totalBorrowed + amount
}

public repay = (amount: bigint) => {
logger.info(`Decreasing outstanding debt for loan ${this.loan.id} by ${amount}`)
this.loan.outstandingDebt = this.loan.totalRepaid + amount
}

public updateInterestRate = (interestRatePerSec: bigint) => {
logger.info(`Updating interest rate for loan ${this.loan.id} to ${interestRatePerSec}`)
this.loan.interestRatePerSec = interestRatePerSec
}

public writeOff = (percentage: bigint, penaltyInterestRatePerSec: bigint, writeOffIndex: number) => {
logger.info(`Writing off loan ${this.loan.id} with ${percentage}`)
this.loan.writtenOffPercentage = percentage
this.loan.penaltyInterestRatePerSec = penaltyInterestRatePerSec
this.loan.writeOffIndex = writeOffIndex
}

public updateLoanType = (loanType: string, loanSpec?: AnyJson) => {
logger.info(`Updating loan type for loan ${this.loan.id} to ${loanType}`)
this.loan.type = loanType
const specBuff = Buffer.from(JSON.stringify(loanSpec))
this.loan.spec = specBuff.toString('base64')
}

public activate = () => {
logger.info(`Activating loan ${this.loan.id}`)
this.loan.status = LoanStatus.ACTIVE
}

public close = () => {
logger.info(`Closing loan ${this.loan.id}`)
this.loan.status = LoanStatus.CLOSED
}
}
6 changes: 3 additions & 3 deletions src/mappings/services/trancheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export class TrancheService {
return new TrancheService(tranche, trancheState)
}

static getById = async (trancheId: string) => {
const tranche = await Tranche.get(trancheId)
const trancheState = await TrancheState.get(trancheId)
static getById = async (poolId: string, trancheId: string) => {
const tranche = await Tranche.get(`${poolId}-${trancheId}`)
const trancheState = await TrancheState.get(`${poolId}-${trancheId}`)
if (tranche === undefined || trancheState === undefined) return undefined
return new TrancheService(tranche, trancheState)
}
Expand Down

0 comments on commit 1c3cc19

Please sign in to comment.