Skip to content
This repository has been archived by the owner on Jun 3, 2022. It is now read-only.

feat(module.api): add lp rewardpct #869

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/module.api/cache/defid.cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PoolPairInfo } from '@defichain/jellyfish-api-core/dist/category/poolpa
import { TokenInfo } from '@defichain/jellyfish-api-core/dist/category/token'
import { Cache } from 'cache-manager'
import { CachePrefix } from '@src/module.api/cache/global.cache'
import { Testing } from '@defichain/jellyfish-testing'
import BigNumber from 'bignumber.js'

const container = new MasterNodeRegTestContainer()
let client: JsonRpcClient
Expand Down Expand Up @@ -178,3 +180,80 @@ describe('getTokenInfo', () => {
expect(dfi).toBeUndefined()
})
})

describe('getStockLpRewardPct', () => {
beforeAll(async () => {
await container.start()
await container.waitForReady()
await container.waitForWalletCoinbaseMaturity()
client = new JsonRpcClient(await container.getCachedRpcUrl())

testingModule = await Test.createTestingModule({
imports: [CacheModule.register()],
providers: [
{ provide: JsonRpcClient, useValue: client },
DeFiDCache
]
}).compile()

cache = testingModule.get<Cache>(CACHE_MANAGER)
defiCache = testingModule.get(DeFiDCache)

await container.waitForWalletBalanceGTE(110)

await createToken(container, 'BAT') // 1
await container.generate(1)

await createPoolPair(container, 'BAT', 'DFI') // 2
await container.generate(1)

// loan pool pair setup
const testing = Testing.create(container)
const oracleId = await container.call('appointoracle', [await testing.generateAddress(), [
{ token: 'lA', currency: 'USD' }, // 3
{ token: 'lB', currency: 'USD' } // 4
], 1])
await testing.generate(1)

await testing.rpc.oracle.setOracleData(oracleId, Math.floor(new Date().getTime() / 1000), {
prices: [
{ tokenAmount: '1@lA', currency: 'USD' }, // 5
{ tokenAmount: '2@lB', currency: 'USD' } // 6
]
})
await testing.generate(1)

const loanTokens = ['lA', 'lB']
for (const lt of loanTokens) {
await testing.container.call('setloantoken', [{
symbol: lt,
fixedIntervalPriceId: `${lt}/USD`,
mintable: false,
interest: new BigNumber(0.02)
}])
await testing.generate(1)
}

for (const lt of loanTokens) {
await testing.poolpair.create({ tokenA: lt, tokenB: 'DFI' })
await testing.generate(1)
}

await container.call('setgov', [{ LP_LOAN_TOKEN_SPLITS: { 5: 1 } }])
await testing.generate(1)

// calling with non loan token pp returned with assumed zero %
const nonLoanLpPct = await defiCache.getStockLpRewardPct('3')
expect(nonLoanLpPct.toFixed()).toStrictEqual('0')

await container.stop()
})

it('should get from cache via get as container RPC is killed', async () => {
const lA = await defiCache.getStockLpRewardPct('5')
expect(lA.toFixed()).toStrictEqual('1')

const lB = await defiCache.getStockLpRewardPct('6')
expect(lB.toFixed()).toStrictEqual('0') // naturally zero when govvar have no value for this id
})
})
28 changes: 28 additions & 0 deletions src/module.api/cache/defid.cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TokenInfo, TokenResult } from '@defichain/jellyfish-api-core/dist/categ
import { CachePrefix, GlobalCache } from '@src/module.api/cache/global.cache'
import { PoolPairInfo } from '@defichain/jellyfish-api-core/dist/category/poolpair'
import { GetLoanSchemeResult } from '@defichain/jellyfish-api-core/dist/category/loan'
import BigNumber from 'bignumber.js'

@Injectable()
export class DeFiDCache extends GlobalCache {
Expand Down Expand Up @@ -76,4 +77,31 @@ export class DeFiDCache extends GlobalCache {
throw err
}
}

async getStockLpRewardPct (poolId: string): Promise<BigNumber> {
const all = (await this.get<Record<string, BigNumber>>(
CachePrefix.GOVERNANCE,
'LP_LOAN_TOKEN_SPLITS',
this.fetchAllStockLpRewardPct.bind(this))
) as Record<string, BigNumber>

const thisPool = all[poolId]
return thisPool === undefined ? new BigNumber(0) : new BigNumber(thisPool)
}

private async fetchAllStockLpRewardPct (): Promise<Record<string, BigNumber>> {
const { LP_LOAN_TOKEN_SPLITS: rewardPct } = await this.rpcClient.masternode.getGov('LP_LOAN_TOKEN_SPLITS')
if (rewardPct === undefined) {
// unexpected (absolutely existed in prod)
throw new Error('LP_LOAN_TOKEN_SPLITS govvar missing')
}

const tokenIds = Object.keys(rewardPct)
const result: Record<string, BigNumber> = {}
tokenIds.forEach(t => {
result[t] = new BigNumber(rewardPct[t])
})

return result
}
}
3 changes: 2 additions & 1 deletion src/module.api/cache/global.cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export enum CachePrefix {
TOKEN_INFO = 0,
POOL_PAIR_INFO = 1,
TOKEN_INFO_SYMBOL = 2,
LOAN_SCHEME_INFO = 3
LOAN_SCHEME_INFO = 3,
GOVERNANCE = 4
}

export class GlobalCache {
Expand Down
115 changes: 111 additions & 4 deletions src/module.api/poolpair.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { NestFastifyApplication } from '@nestjs/platform-fastify'
import { createTestingApp, stopTestingApp, waitForIndexedHeight } from '@src/e2e.module'
import { addPoolLiquidity, createPoolPair, createToken, getNewAddress, mintTokens } from '@defichain/testing'
import { NotFoundException } from '@nestjs/common'
import { Testing } from '@defichain/jellyfish-testing'
import BigNumber from 'bignumber.js'

const container = new MasterNodeRegTestContainer()
let app: NestFastifyApplication
Expand Down Expand Up @@ -115,6 +117,49 @@ async function setup (): Promise<void> {

await container.call('setgov', [{ LP_SPLITS: { 14: 1.0 } }])
await container.generate(1)

// loan token LP setup
const testing = Testing.create(container)
const loanTokens = ['lA', 'lB', 'lC']
const oracleId = await container.call('appointoracle', [await testing.generateAddress(), [
{ token: 'lA', currency: 'USD' },
{ token: 'lB', currency: 'USD' },
{ token: 'lC', currency: 'USD' }
], 1])
await testing.generate(1)

await testing.rpc.oracle.setOracleData(oracleId, Math.floor(new Date().getTime() / 1000), {
prices: [
{ tokenAmount: '1@lA', currency: 'USD' },
{ tokenAmount: '2@lB', currency: 'USD' },
{ tokenAmount: '3@lC', currency: 'USD' }
]
})
await testing.generate(1)

for (const lt of loanTokens) {
await testing.container.call('setloantoken', [{
symbol: lt,
fixedIntervalPriceId: `${lt}/USD`,
mintable: false,
interest: new BigNumber(0.02)
}])
await testing.generate(1)
}

for (const lt of loanTokens) {
await testing.poolpair.create({ tokenA: lt, tokenB: 'DFI' })
await testing.generate(1)
}

await container.call('setgov', [{
LP_LOAN_TOKEN_SPLITS: {
// 29: 0,
30: 0.4,
31: 0.6
}
}])
await testing.generate(1)
}

describe('list', () => {
Expand All @@ -123,7 +168,7 @@ describe('list', () => {
size: 30
})

expect(response.data.length).toStrictEqual(12)
expect(response.data.length).toStrictEqual(15)
expect(response.page).toBeUndefined()

expect(response.data[1]).toStrictEqual({
Expand Down Expand Up @@ -173,6 +218,13 @@ describe('list', () => {
h24: 0
}
})

expect(response.data[12].symbol).toStrictEqual('lA-DFI')
expect(response.data[12].rewardPct).toStrictEqual('0')
expect(response.data[13].symbol).toStrictEqual('lB-DFI')
expect(response.data[13].rewardPct).toStrictEqual('0.4')
expect(response.data[14].symbol).toStrictEqual('lC-DFI')
expect(response.data[14].rewardPct).toStrictEqual('0.6')
})

it('should list with pagination', async () => {
Expand All @@ -185,11 +237,11 @@ describe('list', () => {
expect(first.data[1].symbol).toStrictEqual('B-DFI')

const next = await controller.list({
size: 11,
size: 14,
next: first.page?.next
})

expect(next.data.length).toStrictEqual(10)
expect(next.data.length).toStrictEqual(13)
expect(next.page?.next).toBeUndefined()
expect(next.data[0].symbol).toStrictEqual('C-DFI')
expect(next.data[1].symbol).toStrictEqual('D-DFI')
Expand Down Expand Up @@ -261,11 +313,64 @@ describe('get', () => {
})
})

it('loan token pool "rewardPct" should use gov value', async () => {
const response = await controller.get('31')

expect(response).toStrictEqual({
id: '31',
symbol: 'lC-DFI',
displaySymbol: 'dlC-DFI',
name: '-Default Defi token',
status: true,
tokenA: {
id: expect.any(String),
symbol: 'lC',
reserve: '0',
blockCommission: '0',
displaySymbol: 'dlC'
},
tokenB: {
id: '0',
symbol: 'DFI',
reserve: '0',
blockCommission: '0',
displaySymbol: 'DFI'
},
apr: {
// legit empty pool value
reward: Infinity,
total: NaN,
commission: NaN
},
commission: '0',
totalLiquidity: {
token: '0',
usd: '0'
},
tradeEnabled: false,
ownerAddress: expect.any(String),
priceRatio: {
ab: '0',
ba: '0'
},
rewardPct: '0.6',
customRewards: undefined,
creation: {
tx: expect.any(String),
height: expect.any(Number)
},
volume: {
d30: 0,
h24: 0
}
})
})

it('should throw error while getting non-existent poolpair', async () => {
expect.assertions(2)
try {
await controller.get('999')
} catch (err) {
} catch (err: any) {
expect(err).toBeInstanceOf(NotFoundException)
expect(err.response).toStrictEqual({
statusCode: 404,
Expand Down Expand Up @@ -679,6 +784,8 @@ describe('get list swappable tokens', () => {
swappableTokens: [
{ id: '7', symbol: 'G', displaySymbol: 'dG' },
{ id: '0', symbol: 'DFI', displaySymbol: 'DFI' },
{ id: '27', symbol: 'lB', displaySymbol: 'dlB' },
{ id: '26', symbol: 'lA', displaySymbol: 'dlA' },
{ id: '24', symbol: 'USDT', displaySymbol: 'dUSDT' },
{ id: '6', symbol: 'F', displaySymbol: 'dF' },
{ id: '5', symbol: 'E', displaySymbol: 'dE' },
Expand Down
10 changes: 6 additions & 4 deletions src/module.api/poolpair.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export class PoolPairController {
const totalLiquidityUsd = await this.poolPairService.getTotalLiquidityUsd(info)
const apr = await this.poolPairService.getAPR(id, info)
const volume = await this.poolPairService.getUSDVolume(id)
items.push(mapPoolPair(id, info, totalLiquidityUsd, apr, volume))
const rewardPct = await this.poolPairService.getRewardPct(id, info)
items.push(mapPoolPair(id, info, totalLiquidityUsd, apr, volume, rewardPct))
}

const response = ApiPagedResponse.of(items, query.size, item => {
Expand All @@ -78,7 +79,8 @@ export class PoolPairController {
const totalLiquidityUsd = await this.poolPairService.getTotalLiquidityUsd(info)
const apr = await this.poolPairService.getAPR(id, info)
const volume = await this.poolPairService.getUSDVolume(id)
return mapPoolPair(String(id), info, totalLiquidityUsd, apr, volume)
const rewardPct = await this.poolPairService.getRewardPct(id, info)
return mapPoolPair(String(id), info, totalLiquidityUsd, apr, volume, rewardPct)
}

/**
Expand Down Expand Up @@ -184,7 +186,7 @@ export class PoolPairController {
}
}

function mapPoolPair (id: string, info: PoolPairInfo, totalLiquidityUsd?: BigNumber, apr?: PoolPairData['apr'], volume?: PoolPairData['volume']): PoolPairData {
function mapPoolPair (id: string, info: PoolPairInfo, totalLiquidityUsd?: BigNumber, apr?: PoolPairData['apr'], volume?: PoolPairData['volume'], rewardPct?: BigNumber): PoolPairData {
const [symbolA, symbolB] = info.symbol.split('-')

return {
Expand Down Expand Up @@ -218,7 +220,7 @@ function mapPoolPair (id: string, info: PoolPairInfo, totalLiquidityUsd?: BigNum
},
tradeEnabled: info.tradeEnabled,
ownerAddress: info.ownerAddress,
rewardPct: info.rewardPct.toFixed(),
rewardPct: rewardPct?.toFixed() ?? info.rewardPct.toFixed(),
customRewards: info.customRewards,
creation: {
tx: info.creationTx,
Expand Down
21 changes: 19 additions & 2 deletions src/module.api/poolpair.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class PoolPairService {
if (Object.values(result).length > 0) {
return Object.values(result)[0]
}
} catch (err) {
} catch (err: any) {
if (err?.payload?.message !== 'Pool not found') {
Comment on lines +63 to 64
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (err: any) {
if (err?.payload?.message !== 'Pool not found') {
} catch (err) {
if (err instanceof RpcApiError && err?.payload?.message !== 'Pool not found') {

We can potentially use instanceof here to avoid casting errors to any

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this requires RpcApiError to be imported so will need manual commits I think 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite reluctant to make changes here, since it is a generic issue and all over the codebase. I think we should open an issue to clean it up if we have to.

Copy link

@kodemon kodemon Mar 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we want to set the typescript rules to disable the useUnknownInCatchVariables until we formally address this so we don't have to cast back to any for our try/catch.

But that could also be part of a separate PR.

Copy link
Contributor Author

@ivan-zynesis ivan-zynesis Mar 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omg, I didn't actually look deep into this. JS 😅
Agreed, we should have the rule to have consistent linter for every dev.

image

throw err
}
Expand All @@ -71,7 +71,7 @@ export class PoolPairService {
if (Object.values(result).length > 0) {
return Object.values(result)[0]
}
} catch (err) {
} catch (err: any) {
if (err?.payload?.message !== 'Pool not found') {
kodemon marked this conversation as resolved.
Show resolved Hide resolved
throw err
}
Expand Down Expand Up @@ -387,6 +387,23 @@ export class PoolPairService {
total: reward.plus(commission).toNumber()
}
}

async getRewardPct (id: string, info: PoolPairInfo): Promise<BigNumber> {
if (!info.rewardPct.isZero()) {
return info.rewardPct
}

const token = await this.deFiDCache.getTokenInfo(info.idTokenA)
if (token === undefined) {
throw new Error(`Pool ${id} not found`)
}

if (!token.isLoanToken) {
return new BigNumber(0)
}

return await this.deFiDCache.getStockLpRewardPct(id)
}
}

function findPoolSwapDfTx (vouts: TransactionVout[]): PoolSwapDfTx | undefined {
Expand Down