Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jellyfish-api-core): add 'path' and 'verbose' params to testpool… #1074

Closed
wants to merge 1 commit into from
Closed
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
12 changes: 11 additions & 1 deletion docs/node/CATEGORIES/06-poolpair.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ Create a test pool swap transaction to check pool swap's return result

```ts title="client.poolpair.testPoolSwap()"
interface poolpair {
testPoolSwap (metadata: PoolSwapMetadata): Promise<string>
testPoolSwap (
metadata: PoolSwapMetadata,
path: 'auto' | 'direct' = 'direct',
verbose: boolean = false
): Promise<string | EstimatedCompositePath>
}

interface PoolSwapMetadata {
Expand All @@ -225,6 +229,12 @@ interface PoolSwapMetadata {
tokenTo: string
maxPrice?: number
}

interface EstimatedCompositePath {
amount: string,
path: 'auto' | 'direct',
pools: string[]
}
```

## removePoolLiquidity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { ContainerAdapterClient } from '../../container_adapter_client'
import BigNumber from 'bignumber.js'
import { addPoolLiquidity, createPoolPair, createToken, getNewAddress, mintTokens } from '@defichain/testing'
import {
addPoolLiquidity,
createPoolPair,
createToken,
getNewAddress,
mintTokens,
sendTokensToAddress
} from '@defichain/testing'
import { RpcApiError } from '../../../src'
import { poolpair } from '@defichain/jellyfish-api-core'
import { EstimatedCompositePath } from '../../../src/category/poolpair'

describe('Poolpair', () => {
const container = new MasterNodeRegTestContainer()
Expand Down Expand Up @@ -49,7 +57,7 @@ describe('Poolpair', () => {
const reserveAAfter: BigNumber = new BigNumber(poolpair.reserveA).plus(666)
const reserveBAfter: BigNumber = new BigNumber(poolpair.totalLiquidity).pow(2).div(reserveAAfter)

const result = await client.poolpair.testPoolSwap({
const result = await client.poolpair.testPoolSwap<string>({
from: tokenAddress,
tokenFrom: 'CAT',
amountFrom: 666,
Expand Down Expand Up @@ -80,7 +88,7 @@ describe('Poolpair', () => {
shareAddress: poolLiquidityAddress
})

const receive = await client.poolpair.testPoolSwap({
const receive = await client.poolpair.testPoolSwap<string>({
from: tokenAddress,
tokenFrom: 'ELF',
amountFrom: 666,
Expand Down Expand Up @@ -113,7 +121,7 @@ describe('Poolpair', () => {
shareAddress: poolLiquidityAddress
})

const promise = client.poolpair.testPoolSwap({
const promise = client.poolpair.testPoolSwap<string>({
from: tokenAddress,
tokenFrom: 'FOX',
amountFrom: 666,
Expand Down Expand Up @@ -147,7 +155,7 @@ describe('Poolpair', () => {
const poolpairResultBefore: poolpair.PoolPairsResult = await container.call('getpoolpair', ['DOG-DFI'])
expect(Object.keys(poolpairResultBefore).length).toStrictEqual(1)

await client.poolpair.testPoolSwap({
await client.poolpair.testPoolSwap<string>({
from: tokenAddress,
tokenFrom: 'CAT',
amountFrom: 666,
Expand All @@ -167,7 +175,7 @@ describe('Poolpair', () => {
await mintTokens(container, 'BAT')
await createPoolPair(container, 'BAT', 'DFI')

const promise = client.poolpair.testPoolSwap({
const promise = client.poolpair.testPoolSwap<string>({
from: tokenBatAddress,
tokenFrom: 'BAT',
amountFrom: 13,
Expand All @@ -177,4 +185,207 @@ describe('Poolpair', () => {
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow('Lack of liquidity')
})

it.skip('testpoolswap does direct swap when \'auto\' path specified and direct swap available', async () => {
// Create ZOO-DFI pool and add liquidity
const zooDfiPoolAddress = await getNewAddress(container)
await createToken(container, 'ZOO')
await mintTokens(container, 'ZOO')
await createPoolPair(container, 'ZOO', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'ZOO',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: zooDfiPoolAddress
})

// Create an address that has ZOO and wants to swap for DFI
const fromAddress = await getNewAddress(container)
await sendTokensToAddress(container, fromAddress, 1000, 'ZOO')

// Get the testPoolSwap result as '<zooAmount>@<pool>'
const metadata = {
from: fromAddress,
to: zooDfiPoolAddress,
tokenFrom: 'ZOO',
tokenTo: 'DFI',
amountFrom: 666
}
const testResult = await client.poolpair.testPoolSwap<string>(metadata, 'auto')
const testResultVerbose = await client.poolpair.testPoolSwap<EstimatedCompositePath>(metadata, 'auto', true)

// TODO(limeli): should be a direct swap, but it's making a composite swap instead. Why?
expect(testResultVerbose.amount).toStrictEqual(testResult)
expect(testResultVerbose.path).toStrictEqual('auto')
expect(testResultVerbose.pools).toStrictEqual(['12'])
expect(testResult).toStrictEqual('199.87995198@0')

// Actually perform the swap
const txn = await client.poolpair.poolSwap({
from: fromAddress,
to: zooDfiPoolAddress,
tokenFrom: 'ZOO',
tokenTo: 'DFI',
amountFrom: 666
})
expect(txn.length).toStrictEqual(64)
await container.generate(1)

// Verify ZOO-DFI pool state
const zooDfiPair: poolpair.PoolPairsResult = await container.call('getpoolpair', ['ZOO-DFI'])
const zooDfiPool: poolpair.PoolPairInfo = Object.values(zooDfiPair)[0]

expect(new BigNumber(zooDfiPool.reserveA).toFixed(4)) // ZOO
.toStrictEqual(new BigNumber(1666).toFixed(4))

expect(new BigNumber(zooDfiPool.reserveB).toFixed(4)) // DFI
.toStrictEqual(new BigNumber(300.120048).toFixed(4))

// Check test swap result against actual swap result
const testResultDfi = new BigNumber(testResult.split('@')[0])
expect(testResultDfi.toFixed(4))
.toStrictEqual( // initial dfi reserve - current dfi reserve
new BigNumber(500).minus(new BigNumber(zooDfiPool.reserveB)).toFixed(4)
)
})

it('testpoolswap does compositeswap when \'auto\' path specified and no direct swap available', async () => {
// Create BEE-DFI pool and add liquidity
const beeDfiPoolAddress = await getNewAddress(container)
await createToken(container, 'BEE')
await mintTokens(container, 'BEE')
await createPoolPair(container, 'BEE', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'BEE',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: beeDfiPoolAddress
})

// Create FLY-DFI pool and add liquidity
const flyDfiPoolAddress = await getNewAddress(container)
await createToken(container, 'FLY')
await mintTokens(container, 'FLY')
await createPoolPair(container, 'FLY', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'FLY',
amountA: 2000,
tokenB: 'DFI',
amountB: 500,
shareAddress: flyDfiPoolAddress
})

// Create an address that has BEE and wants to swap for FLY
const fromAddress = await getNewAddress(container)
await sendTokensToAddress(container, fromAddress, 1000, 'BEE')

// Get the testPoolSwap result as '<flyAmount>@<pool>'
const testResult = await client.poolpair.testPoolSwap<string>({
from: fromAddress,
to: beeDfiPoolAddress,
tokenFrom: 'BEE',
tokenTo: 'FLY',
amountFrom: 666
}, 'auto')

// Actually perform the composite swap, then check it against testPoolSwap result
const txn = await client.poolpair.compositeSwap({
from: fromAddress,
to: beeDfiPoolAddress,
tokenFrom: 'BEE',
tokenTo: 'FLY',
amountFrom: 666
})
expect(txn.length).toStrictEqual(64)
await container.generate(1)

// Verify BEE-DFI pool state
const beeDfiPair: poolpair.PoolPairsResult = await container.call('getpoolpair', ['BEE-DFI'])
const beeDfiPool: poolpair.PoolPairInfo = Object.values(beeDfiPair)[0]

expect(new BigNumber(beeDfiPool.reserveA).toFixed(4)) // BEE
.toStrictEqual(new BigNumber(1666).toFixed(4))

expect(new BigNumber(beeDfiPool.reserveB).toFixed(4)) // DFI
.toStrictEqual(new BigNumber(300.120048).toFixed(4))

// Verify FLY-DFI pool state
const flyDfiPair: poolpair.PoolPairsResult = await container.call('getpoolpair', ['FLY-DFI'])
const flyDfiPool: poolpair.PoolPairInfo = Object.values(flyDfiPair)[0]

expect(new BigNumber(flyDfiPool.reserveA).toFixed(4)) // FLY
.toStrictEqual(new BigNumber(1428.816467).toFixed(4))

expect(new BigNumber(flyDfiPool.reserveB).toFixed(4)) // DFI
.toStrictEqual(new BigNumber(699.879952).toFixed(4))

// Compare testpoolswap compositeswap result with actual compositeswap result
// 666 BEE for 571.18353344 FLY
const testResultFlyAmount = new BigNumber(testResult.split('@')[0])
expect(testResultFlyAmount.toFixed(4))
.toStrictEqual(new BigNumber(571.1835).toFixed(4))
const flyReserveDiff = new BigNumber(2000).minus(new BigNumber(flyDfiPool.reserveA))

expect(testResultFlyAmount.toFixed(4)).toStrictEqual(flyReserveDiff.toFixed(4))
})

it('testpoolswap(..., \'direct\') should fail if no pool exists for a direct swap', async () => {
// GOO-DFI
const tokenAddress = await getNewAddress(container)
await createToken(container, 'GOO')
await mintTokens(container, 'GOO')
await createPoolPair(container, 'GOO', 'DFI')

// LOO-DFI
await createToken(container, 'LOO')
await mintTokens(container, 'LOO')
await createPoolPair(container, 'LOO', 'DFI')

// Try to swap GOO-LOO directly
const promise = client.poolpair.testPoolSwap<string>({
from: tokenAddress,
tokenFrom: 'GOO',
amountFrom: 13,
to: await getNewAddress(container),
tokenTo: 'LOO'
}, 'direct')
await expect(promise)
.rejects
.toThrow('RpcApiError: \'Direct pool pair not found. ' +
'Use \'auto\' mode to use composite swap.\', code: -32600, method: testpoolswap')
})

it('should return swap path when verbose=true', async () => {
// Create FOO-DFI pool and add liquidity
const tokenAddress = await getNewAddress(container)
await createToken(container, 'FOO')
await mintTokens(container, 'FOO')
await createPoolPair(container, 'FOO', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'FOO',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: tokenAddress
})

const metadata = {
from: await getNewAddress(container),
to: tokenAddress,
tokenFrom: 'FOO',
tokenTo: 'DFI',
amountFrom: 666
}
const testResult = await client.poolpair.testPoolSwap<string>(metadata, 'direct')
const testResultVerbose = await client.poolpair.testPoolSwap<EstimatedCompositePath>(metadata, 'direct', true)

const amount = testResult.split('@')[0]
const amountFromVerbose = testResult.split('@')[0]
expect(amount).toStrictEqual('199.87995198')
expect(amountFromVerbose).toStrictEqual(amount)
expect(testResultVerbose.path).toStrictEqual('direct')
expect(testResultVerbose.pools).toStrictEqual(['20'])
})
})
19 changes: 16 additions & 3 deletions packages/jellyfish-api-core/src/category/poolpair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,23 @@ export class PoolPair {
* Create a test pool swap transaction to check pool swap's return result
*
* @param {PoolSwapMetadata} metadata a provided information to create test pool swap transaction
* @param {'auto' | 'direct'} [path='direct'] one of auto/direct.
* Note: the default will be switched to auto in the upcoming versions.
* - auto: automatically use composite swap or direct swap as needed.
* - direct: uses direct path only or fails.
* @param {boolean} [verbose=false] returns estimated composite path when true,
* otherwise returns a string formatted as 'amount@token' swapped.
* @param {string} metadata.from address of the owner of tokenFrom
* @param {string} metadata.tokenFrom swap from token {symbol/id}
* @param {number} metadata.amountFrom amount from tokenA
* @param {string} metadata.to address of the owner of tokenTo
* @param {string} metadata.tokenTo swap to token {symbol/id}
* @param {number} [metadata.maxPrice] acceptable max price
* @return {Promise<string>} formatted as 'amount@token' swapped
* @return {Promise<string | EstimatedCompositePath>} formatted as 'amount@token' swapped or
* the estimated composite path.
*/
async testPoolSwap (metadata: PoolSwapMetadata): Promise<string> {
return await this.client.call('testpoolswap', [metadata], 'bignumber')
async testPoolSwap<T extends string | EstimatedCompositePath>(metadata: PoolSwapMetadata, path: 'auto' | 'direct' = 'direct', verbose: boolean = false): Promise<T> {
return await this.client.call('testpoolswap', [metadata, path, verbose], 'bignumber')
}

/**
Expand Down Expand Up @@ -255,3 +262,9 @@ export interface PoolSwapMetadata {
tokenTo: string
maxPrice?: number
}

export interface EstimatedCompositePath {
amount: string
path: 'auto' | 'direct'
pools: string[]
}