Skip to content

Commit

Permalink
chore(whale-migration): whale sync (#1449)
Browse files Browse the repository at this point in the history
* chore(whale-migration): whale sync

- Fix imports
- Set source code to be at parity with whale repo as of 19/05/2022 16:35 GMT+8

* chore(whale-migration): revert minor changes
  • Loading branch information
eli-lim authored May 20, 2022
1 parent 3ecb15e commit 252fa2b
Show file tree
Hide file tree
Showing 27 changed files with 1,044 additions and 331 deletions.
5 changes: 4 additions & 1 deletion apps/whale/src/module.api/_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { TransactionController } from './transaction.controller'
import { ApiValidationPipe } from './pipes/api.validation.pipe'
import { AddressController } from './address.controller'
import { PoolPairController } from './poolpair.controller'
import { PoolPairService, PoolSwapPathFindingService } from './poolpair.service'
import { PoolPairService } from './poolpair.service'
import { MasternodeService } from './masternode.service'
import { DeFiDCache } from './cache/defid.cache'
import { SemaphoreCache } from './cache/semaphore.cache'
Expand All @@ -29,6 +29,8 @@ import { FeeController } from './fee.controller'
import { RawtxController } from './rawtx.controller'
import { LoanController } from './loan.controller'
import { LoanVaultService } from './loan.vault.service'
import { PoolSwapPathFindingService } from './poolswap.pathfinding.service'
import { PoolPairPricesService } from './poolpair.prices.service'

/**
* Exposed ApiModule for public interfacing
Expand Down Expand Up @@ -67,6 +69,7 @@ import { LoanVaultService } from './loan.vault.service'
SemaphoreCache,
PoolPairService,
PoolSwapPathFindingService,
PoolPairPricesService,
MasternodeService,
LoanVaultService,
{
Expand Down
152 changes: 152 additions & 0 deletions apps/whale/src/module.api/address.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RpcApiError } from '@defichain/jellyfish-api-core'
import { Testing } from '@defichain/jellyfish-testing'
import { ForbiddenException } from '@nestjs/common'
import BigNumber from 'bignumber.js'
import { RegTestFoundationKeys } from '@defichain/jellyfish-network'

const container = new MasterNodeRegTestContainer()
let app: NestFastifyApplication
Expand Down Expand Up @@ -209,6 +210,157 @@ describe('listAccountHistory', () => {
})
})

describe('getAccount', () => {
beforeAll(async () => {
await container.start()
await container.waitForWalletCoinbaseMaturity()

colAddr = await testing.generateAddress()
usdcAddr = await testing.generateAddress()
poolAddr = await testing.generateAddress()
emptyAddr = await testing.generateAddress()

await testing.token.dfi({ address: colAddr, amount: 20000 })
await testing.generate(1)

await testing.token.create({ symbol: 'USDC', collateralAddress: colAddr })
await testing.generate(1)

await testing.token.mint({ symbol: 'USDC', amount: 10000 })
await testing.generate(1)

await testing.rpc.account.accountToAccount(colAddr, { [usdcAddr]: '10000@USDC' })
await testing.generate(1)

await testing.rpc.poolpair.createPoolPair({
tokenA: 'DFI',
tokenB: 'USDC',
commission: 0,
status: true,
ownerAddress: poolAddr
})
await testing.generate(1)

const poolPairsKeys = Object.keys(await testing.rpc.poolpair.listPoolPairs())
expect(poolPairsKeys.length).toStrictEqual(1)
dfiUsdc = poolPairsKeys[0]

// set LP_SPLIT, make LM gain rewards, MANDATORY
// ensure `no_rewards` flag turned on
// ensure do not get response without txid
await testing.container.call('setgov', [{ LP_SPLITS: { [dfiUsdc]: 1.0 } }])
await container.generate(1)

await testing.rpc.poolpair.addPoolLiquidity({
[colAddr]: '5000@DFI',
[usdcAddr]: '5000@USDC'
}, poolAddr)
await testing.generate(1)

await testing.rpc.poolpair.poolSwap({
from: colAddr,
tokenFrom: 'DFI',
amountFrom: 555,
to: usdcAddr,
tokenTo: 'USDC'
})
await testing.generate(1)

await testing.rpc.poolpair.removePoolLiquidity(poolAddr, '2@DFI-USDC')
await testing.generate(1)

// for testing same block pagination
await testing.token.create({ symbol: 'APE', collateralAddress: colAddr })
await testing.generate(1)

await testing.token.create({ symbol: 'CAT', collateralAddress: colAddr })
await testing.token.create({ symbol: 'DOG', collateralAddress: colAddr })
await testing.generate(1)

await testing.token.create({ symbol: 'ELF', collateralAddress: colAddr })
await testing.token.create({ symbol: 'FOX', collateralAddress: colAddr })
await testing.token.create({ symbol: 'RAT', collateralAddress: colAddr })
await testing.token.create({ symbol: 'BEE', collateralAddress: colAddr })
await testing.token.create({ symbol: 'COW', collateralAddress: colAddr })
await testing.token.create({ symbol: 'OWL', collateralAddress: colAddr })
await testing.token.create({ symbol: 'ELK', collateralAddress: colAddr })
await testing.generate(1)

await testing.token.create({ symbol: 'PIG', collateralAddress: colAddr })
await testing.token.create({ symbol: 'KOI', collateralAddress: colAddr })
await testing.token.create({ symbol: 'FLY', collateralAddress: colAddr })
await testing.generate(1)

app = await createTestingApp(container)
controller = app.get(AddressController)

await testing.generate(1)
})

afterAll(async () => {
await stopTestingApp(container, app)
})

it('should getAccount', async () => {
const history = await controller.listAccountHistory(colAddr, { size: 30 })
for (const h of history.data) {
if (['sent', 'receive'].includes(h.type)) {
continue
}
const acc = await controller.getAccountHistory(colAddr, h.block.height, h.txn)
expect(acc?.owner).toStrictEqual(h.owner)
expect(acc?.txid).toStrictEqual(h.txid)
expect(acc?.txn).toStrictEqual(h.txn)
}

const poolHistory = await controller.listAccountHistory(poolAddr, { size: 30 })
for (const h of poolHistory.data) {
if (['sent', 'receive'].includes(h.type)) {
continue
}
const acc = await controller.getAccountHistory(poolAddr, h.block.height, h.txn)
expect(acc?.owner).toStrictEqual(h.owner)
expect(acc?.txid).toStrictEqual(h.txid)
expect(acc?.txn).toStrictEqual(h.txn)
}
})

it('should be failed for non-existence data', async () => {
const promise = controller.getAccountHistory(await container.getNewAddress(), Number(`${'0'.repeat(64)}`), 1)
await expect(promise).rejects.toThrow('Record not found')
})

it('should be failed as invalid height', async () => {
{ // NaN
const promise = controller.getAccountHistory(await container.getNewAddress(), Number('NotANumber'), 1)
await expect(promise).rejects.toThrow('JSON value is not an integer as expected')
}

{ // negative height
const promise = controller.getAccountHistory(await container.getNewAddress(), -1, 1)
await expect(promise).rejects.toThrow('Record not found')
}
})

it('should be failed as getting unsupport tx type - sent, received, blockReward', async () => {
const history = await controller.listAccountHistory(colAddr, { size: 30 })
for (const h of history.data) {
if (['sent', 'receive'].includes(h.type)) {
const promise = controller.getAccountHistory(colAddr, h.block.height, h.txn)
await expect(promise).rejects.toThrow('Record not found')
}
}

const operatorAccHistory = await container.call('listaccounthistory', [RegTestFoundationKeys[0].operator.address])
for (const h of operatorAccHistory) {
if (['blockReward'].includes(h.type)) {
const promise = controller.getAccountHistory(RegTestFoundationKeys[0].operator.address, h.blockHeight, h.txn)
await expect(promise).rejects.toThrow('Record not found')
}
}
})
})

describe('getBalance', () => {
beforeAll(async () => {
await container.start()
Expand Down
50 changes: 34 additions & 16 deletions apps/whale/src/module.api/address.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BigNumber from 'bignumber.js'
import { ConflictException, Controller, ForbiddenException, Get, Inject, Param, Query } from '@nestjs/common'
import { BadRequestException, ConflictException, Controller, ForbiddenException, Get, Inject, NotFoundException, Param, ParseIntPipe, Query } from '@nestjs/common'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import { ApiPagedResponse } from './_core/api.paged.response'
import { DeFiDCache } from './cache/defid.cache'
Expand Down Expand Up @@ -31,6 +31,26 @@ export class AddressController {
) {
}

@Get('/history/:height/:txno')
async getAccountHistory (
@Param('address') address: string,
@Param('height', ParseIntPipe) height: number,
@Param('txno', ParseIntPipe) txno: number
): Promise<AddressHistory> {
try {
const accountHistory = await this.rpcClient.account.getAccountHistory(address, height, txno)
if (Object.keys(accountHistory).length === 0) {
throw new NotFoundException('Record not found')
}
return mapAddressHistory(accountHistory)
} catch (err) {
if (err instanceof NotFoundException) {
throw err
}
throw new BadRequestException(err)
}
}

/**
* @param {string} address to list participate account history
* @param {PaginationQuery} query
Expand Down Expand Up @@ -81,7 +101,7 @@ export class AddressController {
})
}

const history = mapAddressHistory(list)
const history = list.map(each => mapAddressHistory(each))

return ApiPagedResponse.of(history, query.size, item => {
return `${item.txid}-${item.type}-${item.block.height}`
Expand Down Expand Up @@ -195,19 +215,17 @@ function mapAddressToken (id: string, tokenInfo: TokenInfo, value: BigNumber): A
}
}

function mapAddressHistory (list: AccountHistory[]): AddressHistory[] {
return list.map(each => {
return {
owner: each.owner,
txid: each.txid,
txn: each.txn,
type: each.type,
amounts: each.amounts,
block: {
height: each.blockHeight,
hash: each.blockHash,
time: each.blockTime
}
function mapAddressHistory (history: AccountHistory): AddressHistory {
return {
owner: history.owner,
txid: history.txid,
txn: history.txn,
type: history.type,
amounts: history.amounts,
block: {
height: history.blockHeight,
hash: history.blockHash,
time: history.blockTime
}
})
}
}
52 changes: 51 additions & 1 deletion apps/whale/src/module.api/cache/defid.cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Cache } from 'cache-manager'
import { JsonRpcClient } from '@defichain/jellyfish-api-jsonrpc'
import { TokenInfo, TokenResult } from '@defichain/jellyfish-api-core/dist/category/token'
import { CachePrefix, GlobalCache } from './global.cache'
import { PoolPairInfo } from '@defichain/jellyfish-api-core/dist/category/poolpair'
import { PoolPairInfo, PoolPairsResult } from '@defichain/jellyfish-api-core/dist/category/poolpair'
import { GetLoanSchemeResult } from '@defichain/jellyfish-api-core/dist/category/loan'

@Injectable()
Expand Down Expand Up @@ -61,6 +61,27 @@ export class DeFiDCache extends GlobalCache {
return await this.get<PoolPairInfo>(CachePrefix.POOL_PAIR_INFO, id, this.fetchPoolPairInfo.bind(this))
}

/**
* Retrieve poolPair info from cached list of poolPairs as getPoolPair rpc
* tends to be more expensive
* @param {string} id - id of the poolPair
*/
async getPoolPairInfoFromPoolPairs (id: string): Promise<PoolPairInfo | undefined> {
const poolPairsById = await this.listPoolPairs(60)
if (poolPairsById === undefined) {
return undefined
}
return poolPairsById[id]
}

async listPoolPairs (ttlSeconds: number): Promise<PoolPairsResult | undefined> {
return await this.get<PoolPairsResult>(CachePrefix.POOL_PAIRS, '*', this.fetchPoolPairs.bind(this),
{
ttl: ttlSeconds
}
)
}

private async fetchPoolPairInfo (id: string): Promise<PoolPairInfo | undefined> {
try {
const result = await this.rpcClient.poolpair.getPoolPair(id)
Expand All @@ -76,4 +97,33 @@ export class DeFiDCache extends GlobalCache {
throw err
}
}

private async fetchPoolPairs (): Promise<PoolPairsResult> {
const result: PoolPairsResult = {}
let next: number | null = 0

// Follow pagination chain until the end
while (true) {
const poolPairs: PoolPairsResult = await this.rpcClient.poolpair.listPoolPairs({
start: next,
including_start: next === 0, // only for the first
limit: 1000
}, true)

const poolPairIds: string[] = Object.keys(poolPairs)

// At the end of pagination chain - no more data to fetch
if (poolPairIds.length === 0) {
break
}

// Add to results
for (const poolPairId of poolPairIds) {
result[poolPairId] = poolPairs[poolPairId]
}
next = Number(poolPairIds[poolPairIds.length - 1])
}

return result
}
}
3 changes: 2 additions & 1 deletion apps/whale/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,
POOL_PAIRS = 4,
}

export class GlobalCache {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ describe('get', () => {
await controller.getCollateral('999')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect((err as NotFoundException).getResponse()).toStrictEqual({
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find collateral token',
error: 'Not Found'
Expand Down
6 changes: 3 additions & 3 deletions apps/whale/src/module.api/loan.scheme.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ describe('get', () => {
await controller.getScheme('999')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect((err as NotFoundException).getResponse()).toStrictEqual({
error: 'Not Found',
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find scheme'
message: 'Unable to find scheme',
error: 'Not Found'
})
}
})
Expand Down
2 changes: 1 addition & 1 deletion apps/whale/src/module.api/loan.token.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ describe('get', () => {
await controller.getLoanToken('999')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect((err as NotFoundException).getResponse()).toStrictEqual({
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find loan token',
error: 'Not Found'
Expand Down
4 changes: 2 additions & 2 deletions apps/whale/src/module.api/loan.vault.controller.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('get', () => {
await controller.getVault('0530ab29a9f09416a014a4219f186f1d5d530e9a270a9f941275b3972b43ebb7')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect((err as NotFoundException).getResponse()).toStrictEqual({
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find vault',
error: 'Not Found'
Expand All @@ -138,7 +138,7 @@ describe('get', () => {
await controller.getVault('999')
} catch (err) {
expect(err).toBeInstanceOf(NotFoundException)
expect((err as NotFoundException).getResponse()).toStrictEqual({
expect(err.response).toStrictEqual({
statusCode: 404,
message: 'Unable to find vault',
error: 'Not Found'
Expand Down
Loading

0 comments on commit 252fa2b

Please sign in to comment.