Skip to content

Commit

Permalink
feat(jellyfish-api-core): add 'path' and 'verbose' params to testpool…
Browse files Browse the repository at this point in the history
…swap rpc (#1065)
  • Loading branch information
eli-lim committed Feb 17, 2022
1 parent a8b0690 commit a473ac5
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 10 deletions.
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[]
}

0 comments on commit a473ac5

Please sign in to comment.