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

Add testpoolswap RPC #411

Merged
merged 3 commits into from
Jun 23, 2021
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
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 { RpcApiError } from '../../../src'
import { PoolPairInfo, PoolPairsResult } from '@defichain/jellyfish-api-core/category/poolpair'

describe('Poolpair', () => {
const container = new MasterNodeRegTestContainer()
const client = new ContainerAdapterClient(container)

beforeAll(async () => {
await container.start()
await container.waitForReady()
await container.waitForWalletCoinbaseMaturity()
})

afterAll(async () => {
await container.stop()
})

it('should testpoolswap', async () => {
const tokenAddress = await getNewAddress(container)
const dfiAddress = await getNewAddress(container)
const poolLiquidityAddress = await getNewAddress(container)

await createToken(container, 'CAT', { collateralAddress: tokenAddress })
await mintTokens(container, 'CAT', { address: dfiAddress })
await createPoolPair(container, 'CAT', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'CAT',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: poolLiquidityAddress
})

const poolpairResult: PoolPairsResult = await container.call('getpoolpair', ['CAT-DFI'])
expect(Object.keys(poolpairResult).length).toStrictEqual(1)

// Note(canonbrother): simulate poolswap calculation to find reserveB
// case: swap 666 CAT to DFI
// 1000 : 500 = sqrt(1000 * 500) = 707.10678118
// 1666 : ? = sqrt(1666 * ?) = 707.10678118
// ? = 707.10678118^2 / 1666 = 300.120048014
// 1666 : 300.120048014 = 707.10678118
// 500 - 300.120048014 = 199.879951986 <-- testpoolswap returns
const poolpair: PoolPairInfo = Object.values(poolpairResult)[0]
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({
from: tokenAddress,
tokenFrom: 'CAT',
amountFrom: 666,
to: await getNewAddress(container),
tokenTo: 'DFI'
}) // 199.99999729@0

const testPoolSwapResultAmount = new BigNumber(result.split('@')[0]) // 199.99999729
const swapped = new BigNumber(poolpair.reserveB).minus(reserveBAfter) // 199.87995198635029880408

// Note(canonbrother): the result has slightly diff, use toFixed() to ignore the decimals
expect(swapped.toFixed(0)).toStrictEqual(testPoolSwapResultAmount.toFixed(0))
})

it('should testpoolswap with maxPrice', async () => {
expect.assertions(0)

const tokenAddress = await getNewAddress(container)
const dfiAddress = await getNewAddress(container)
const poolLiquidityAddress = await getNewAddress(container)

await createToken(container, 'ELF', { collateralAddress: tokenAddress })
await mintTokens(container, 'ELF', { address: dfiAddress })
await createPoolPair(container, 'ELF', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'ELF',
amountA: 200,
tokenB: 'DFI',
amountB: 500,
shareAddress: poolLiquidityAddress
})

try {
await client.poolpair.testPoolSwap({
from: tokenAddress,
tokenFrom: 'ELF',
amountFrom: 666,
to: await getNewAddress(container),
tokenTo: 'DFI',
// reserveA/reserveB: 0.4
// reserveB/reserveA: 2.5
// if set maxPrice lower than 2.5 will hit error
maxPrice: 2.5
})
} catch (err) {
// should not reach here
expect(err).toBeUndefined()
}
canonbrother marked this conversation as resolved.
Show resolved Hide resolved
})

it('should be failed as maxPrice is set lower than reserveB/reserveA', async () => {
const tokenAddress = await getNewAddress(container)
const dfiAddress = await getNewAddress(container)
const poolLiquidityAddress = await getNewAddress(container)

await createToken(container, 'FOX', { collateralAddress: tokenAddress })
await mintTokens(container, 'FOX', { address: dfiAddress })
await createPoolPair(container, 'FOX', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'FOX',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: poolLiquidityAddress
})

const promise = client.poolpair.testPoolSwap({
from: tokenAddress,
tokenFrom: 'FOX',
amountFrom: 666,
to: await getNewAddress(container),
tokenTo: 'DFI',
// reserveA/reserveB: 2
// reserveB/reserveA: 0.5
// set maxPrice lower than 0.5 will hit error
maxPrice: 0.4
})
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow('Price is higher than indicated')
})

it('testpoolswap should not affect the ori poolpair data', async () => {
const tokenAddress = await getNewAddress(container)
const dfiAddress = await getNewAddress(container)
const poolLiquidityAddress = await getNewAddress(container)

await createToken(container, 'DOG', { collateralAddress: tokenAddress })
await mintTokens(container, 'DOG', { address: dfiAddress })
await createPoolPair(container, 'DOG', 'DFI')
await addPoolLiquidity(container, {
tokenA: 'DOG',
amountA: 1000,
tokenB: 'DFI',
amountB: 500,
shareAddress: poolLiquidityAddress
})

const poolpairResultBefore: PoolPairsResult = await container.call('getpoolpair', ['DOG-DFI'])
expect(Object.keys(poolpairResultBefore).length).toStrictEqual(1)

await client.poolpair.testPoolSwap({
from: tokenAddress,
tokenFrom: 'CAT',
amountFrom: 666,
to: await getNewAddress(container),
tokenTo: 'DFI'
})

const poolpairResultAfter: PoolPairsResult = await container.call('getpoolpair', ['DOG-DFI'])
expect(Object.keys(poolpairResultAfter).length).toStrictEqual(1)

expect(poolpairResultBefore).toStrictEqual(poolpairResultAfter)
})

it('should be failed as lack of liquidity', async () => {
const tokenBatAddress = await getNewAddress(container)
await createToken(container, 'BAT')
await mintTokens(container, 'BAT')
await createPoolPair(container, 'BAT', 'DFI')

const promise = client.poolpair.testPoolSwap({
from: tokenBatAddress,
tokenFrom: 'BAT',
amountFrom: 13,
to: await getNewAddress(container),
tokenTo: 'DFI'
})
await expect(promise).rejects.toThrow(RpcApiError)
await expect(promise).rejects.toThrow('Lack of liquidity')
})
})
25 changes: 25 additions & 0 deletions packages/jellyfish-api-core/src/category/poolpair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ export class PoolPair {
const { isMineOnly = true } = options
return await this.client.call('listpoolshares', [pagination, verbose, isMineOnly], 'bignumber')
}

/**
* Create a test pool swap transaction to check pool swap's return result
*
* @param {TestPoolSwapMetadata} metadata a provided information to create test pool swap transaction
* @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 {to} metadata.to address of the owner of tokenTo
* @param {tokenTo} metadata.tokenTo swap to token {symbol/id}
* @param {maxPrice} [metadata.maxPrice] acceptable max price
* @return {Promise<string>} // return format 'amount@token'
canonbrother marked this conversation as resolved.
Show resolved Hide resolved
*/
async testPoolSwap (metadata: TestPoolSwapMetadata): Promise<string> {
return await this.client.call('testpoolswap', [metadata], 'bignumber')
}
}

export interface CreatePoolPairMetadata {
Expand Down Expand Up @@ -181,3 +197,12 @@ export interface AddPoolLiquidityUTXO {
export interface PoolShareOptions {
isMineOnly?: boolean
}

export interface TestPoolSwapMetadata {
from: string
tokenFrom: string
amountFrom: number
to: string
tokenTo: string
maxPrice?: number
}
2 changes: 1 addition & 1 deletion packages/testing/src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export async function mintTokens (
options?: MintTokensOptions
): Promise<string> {
const address = options?.address ?? await getNewAddress(container)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this module is due for a refactoring soon, I will create a ticket

const utxoAmount = options?.utxoAmount ?? 100
const utxoAmount = options?.utxoAmount ?? 2000
const mintAmount = options?.mintAmount ?? 2000

await utxosToAccount(container, utxoAmount, { address })
Expand Down
21 changes: 21 additions & 0 deletions website/docs/jellyfish/api/poolpair.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,24 @@ interface PoolShareOptions {
isMineOnly?: boolean
}
```

## testPoolSwap

Create a test pool swap transaction to check pool swap's return result

```ts title="client.poolpair.testPoolSwap()"
interface poolpair {
testPoolSwap (metadata: TestPoolSwapMetadata): Promise<string> {
return await this.client.call('testpoolswap', [metadata], 'bignumber')
}
}

interface TestPoolSwapMetadata {
from: string
tokenFrom: string
amountFrom: number
to: string
tokenTo: string
maxPrice?: number
}
```