From 4a3f837bb834e7df7fa763761e88092074ff651b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Uhl=C3=AD=C5=99?= Date: Tue, 1 Jun 2021 11:57:48 +0200 Subject: [PATCH] feat: gas price for deposit and withdraw tokens (#336) --- src/bee-debug.ts | 28 +++-- src/modules/debug/chequebook.ts | 44 +++++--- src/types/debug.ts | 9 +- .../modules/debug/chequebook.spec.ts | 12 ++- test/unit/bee-debug-class.spec.ts | 100 +++++++++++++++++- test/unit/nock.ts | 24 +++++ 6 files changed, 178 insertions(+), 39 deletions(-) diff --git a/src/bee-debug.ts b/src/bee-debug.ts index 24650018..2b256056 100644 --- a/src/bee-debug.ts +++ b/src/bee-debug.ts @@ -14,8 +14,6 @@ import type { LastChequesResponse, LastChequesForPeerResponse, LastCashoutActionResponse, - DepositTokensResponse, - WithdrawTokensResponse, Settlements, AllSettlements, RemovePeerResponse, @@ -27,7 +25,7 @@ import type { ChainState, } from './types' import { assertBeeUrl, stripLastSlash } from './utils/url' -import { assertAddress, assertInteger, assertNonNegativeInteger } from './utils/type' +import { assertAddress, assertNonNegativeInteger } from './utils/type' import { CashoutOptions } from './types' /** @@ -190,26 +188,34 @@ export class BeeDebug { * Deposit tokens from overlay address into chequebook * * @param amount Amount of tokens to deposit (must be positive integer) + * @param gasPrice Gas Price in WEI for the transaction call + * @return string Hash of the transaction */ - async depositTokens(amount: number | bigint): Promise { - assertInteger(amount) + async depositTokens(amount: number | bigint, gasPrice?: bigint): Promise { + assertNonNegativeInteger(amount) - if (amount < 0) throw new TypeError('must be positive number') + if (gasPrice) { + assertNonNegativeInteger(gasPrice) + } - return chequebook.depositTokens(this.url, amount) + return chequebook.depositTokens(this.url, amount, gasPrice) } /** * Withdraw tokens from the chequebook to the overlay address * * @param amount Amount of tokens to withdraw (must be positive integer) + * @param gasPrice Gas Price in WEI for the transaction call + * @return string Hash of the transaction */ - async withdrawTokens(amount: number | bigint): Promise { - assertInteger(amount) + async withdrawTokens(amount: number | bigint, gasPrice?: bigint): Promise { + assertNonNegativeInteger(amount) - if (amount < 0) throw new TypeError('must be positive number') + if (gasPrice) { + assertNonNegativeInteger(gasPrice) + } - return chequebook.withdrawTokens(this.url, amount) + return chequebook.withdrawTokens(this.url, amount, gasPrice) } /* diff --git a/src/modules/debug/chequebook.ts b/src/modules/debug/chequebook.ts index b6af6f2b..f4d9e043 100644 --- a/src/modules/debug/chequebook.ts +++ b/src/modules/debug/chequebook.ts @@ -3,11 +3,9 @@ import type { ChequebookAddressResponse, ChequebookBalanceResponse, LastCashoutActionResponse, - CashoutResponse, + TransactionResponse, LastChequesForPeerResponse, LastChequesResponse, - DepositTokensResponse, - WithdrawTokensResponse, } from '../../types' import { CashoutOptions } from '../../types' @@ -76,7 +74,7 @@ export async function cashoutLastCheque(url: string, peer: string, options?: Cas headers['gas-limit'] = options.gasLimit.toString() } - const response = await safeAxios({ + const response = await safeAxios({ method: 'post', url: url + chequebookEndpoint + `/cashout/${peer}`, responseType: 'json', @@ -119,33 +117,51 @@ export async function getLastCheques(url: string): Promise /** * Deposit tokens from overlay address into chequebook * - * @param url Bee debug url - * @param amount Amount of tokens to deposit + * @param url Bee debug url + * @param amount Amount of tokens to deposit + * @param gasPrice Gas Price in WEI for the transaction call + * @return string Hash of the transaction */ -export async function depositTokens(url: string, amount: number | bigint): Promise { - const response = await safeAxios({ +export async function depositTokens(url: string, amount: number | bigint, gasPrice?: bigint): Promise { + const headers: Record = {} + + if (gasPrice) { + headers['gas-price'] = gasPrice.toString() + } + + const response = await safeAxios({ method: 'post', url: url + chequebookEndpoint + '/deposit', responseType: 'json', params: { amount: amount.toString(10) }, + headers, }) - return response.data + return response.data.transactionHash } /** * Withdraw tokens from the chequebook to the overlay address * - * @param url Bee debug url - * @param amount Amount of tokens to withdraw + * @param url Bee debug url + * @param amount Amount of tokens to withdraw + * @param gasPrice Gas Price in WEI for the transaction call + * @return string Hash of the transaction */ -export async function withdrawTokens(url: string, amount: number | bigint): Promise { - const response = await safeAxios({ +export async function withdrawTokens(url: string, amount: number | bigint, gasPrice?: bigint): Promise { + const headers: Record = {} + + if (gasPrice) { + headers['gas-price'] = gasPrice.toString() + } + + const response = await safeAxios({ method: 'post', url: url + chequebookEndpoint + '/withdraw', responseType: 'json', params: { amount: amount.toString(10) }, + headers, }) - return response.data + return response.data.transactionHash } diff --git a/src/types/debug.ts b/src/types/debug.ts index 97132842..d65a409d 100644 --- a/src/types/debug.ts +++ b/src/types/debug.ts @@ -57,7 +57,7 @@ export interface LastCashoutActionResponse { result: CashoutResult | null } -export interface CashoutResponse { +export interface TransactionResponse { transactionHash: string } @@ -76,13 +76,6 @@ export interface LastChequesForPeerResponse { export interface LastChequesResponse { lastcheques: LastChequesForPeerResponse[] } -export interface DepositTokensResponse { - transactionHash: string -} - -export interface WithdrawTokensResponse { - transactionHash: string -} export interface PeerBalance { peer: string diff --git a/test/integration/modules/debug/chequebook.spec.ts b/test/integration/modules/debug/chequebook.spec.ts index 6d04be8f..d1166c94 100644 --- a/test/integration/modules/debug/chequebook.spec.ts +++ b/test/integration/modules/debug/chequebook.spec.ts @@ -6,9 +6,11 @@ import { withdrawTokens, } from '../../../../src/modules/debug/chequebook' import { isPrefixedHexString } from '../../../../src/utils/hex' -import { beeDebugUrl, sleep } from '../../../utils' +import { beeDebugUrl, commonMatchers, sleep } from '../../../utils' if (process.env.BEE_TEST_CHEQUEBOOK) { + commonMatchers() + describe('swap enabled chequebook', () => { test('address', async () => { const response = await getChequebookAddress(beeDebugUrl()) @@ -19,15 +21,15 @@ if (process.env.BEE_TEST_CHEQUEBOOK) { test('balance', async () => { const response = await getChequebookBalance(beeDebugUrl()) - expect(typeof response.availableBalance).toBe('bigint') - expect(typeof response.totalBalance).toBe('bigint') + expect(response.availableBalance).toBeType('bigint') + expect(response.totalBalance).toBeType('bigint') }) const TRANSACTION_TIMEOUT = 20 * 1000 const withDrawDepositTest = (amount: number | bigint) => async () => { const withdrawResponse = await withdrawTokens(beeDebugUrl(), amount) - expect(typeof withdrawResponse.transactionHash).toBe('string') + expect(withdrawResponse).toBeType('string') // TODO avoid sleep in tests // See https://github.com/ethersphere/bee/issues/1191 @@ -35,7 +37,7 @@ if (process.env.BEE_TEST_CHEQUEBOOK) { const depositResponse = await depositTokens(beeDebugUrl(), amount) - expect(typeof depositResponse.transactionHash).toBe('string') + expect(depositResponse).toBeType('string') // TODO avoid sleep in tests // See https://github.com/ethersphere/bee/issues/1191 diff --git a/test/unit/bee-debug-class.spec.ts b/test/unit/bee-debug-class.spec.ts index dc62fff7..993be624 100644 --- a/test/unit/bee-debug-class.spec.ts +++ b/test/unit/bee-debug-class.spec.ts @@ -1,4 +1,4 @@ -import { assertAllIsDone, cashoutLastChequeMock, MOCK_SERVER_URL } from './nock' +import { assertAllIsDone, cashoutLastChequeMock, depositTokensMock, MOCK_SERVER_URL, withdrawTokensMock } from './nock' import { BeeArgumentError, BeeDebug } from '../../src' import { testAddress } from '../utils' @@ -103,4 +103,102 @@ describe('BeeDebug class', () => { await expect(bee.cashoutLastCheque(testAddress, { gasLimit: BigInt('-1') })).rejects.toThrow(BeeArgumentError) }) }) + + describe('withdrawTokens', () => { + const TRANSACTION_HASH = '36b7efd913ca4cf880b8eeac5093fa27b0825906c600685b6abdd6566e6cfe8f' + const CASHOUT_RESPONSE = { + transactionHash: TRANSACTION_HASH, + } + + it('should not pass headers if no gas price is specified', async () => { + withdrawTokensMock('10').reply(201, CASHOUT_RESPONSE) + + const bee = new BeeDebug(MOCK_SERVER_URL) + await expect(bee.withdrawTokens(BigInt('10'))).resolves.toEqual(TRANSACTION_HASH) + assertAllIsDone() + }) + + it('should pass headers if gas price is specified', async () => { + withdrawTokensMock('10', '100000000000').reply(201, CASHOUT_RESPONSE) + + const bee = new BeeDebug(MOCK_SERVER_URL) + await expect(bee.withdrawTokens(BigInt('10'), BigInt('100000000000'))).resolves.toEqual(TRANSACTION_HASH) + assertAllIsDone() + }) + + it('should throw error if passed wrong amount', async () => { + const bee = new BeeDebug(MOCK_SERVER_URL) + + // @ts-ignore: Input testing + await expect(bee.withdrawTokens(true)).rejects.toThrow(TypeError) + + // @ts-ignore: Input testing + await expect(bee.withdrawTokens('asd')).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.withdrawTokens(null)).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.withdrawTokens()).rejects.toThrow(TypeError) + + await expect(bee.withdrawTokens(BigInt('-1'))).rejects.toThrow(BeeArgumentError) + }) + + it('should throw error if passed wrong gas price input', async () => { + const bee = new BeeDebug(MOCK_SERVER_URL) + + // @ts-ignore: Input testing + await expect(bee.withdrawTokens(BigInt('1'), true)).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.withdrawTokens(BigInt('1'), 'asd')).rejects.toThrow(TypeError) + await expect(bee.withdrawTokens(BigInt('1'), BigInt('-1'))).rejects.toThrow(BeeArgumentError) + }) + }) + + describe('depositTokens', () => { + const TRANSACTION_HASH = '36b7efd913ca4cf880b8eeac5093fa27b0825906c600685b6abdd6566e6cfe8f' + const CASHOUT_RESPONSE = { + transactionHash: TRANSACTION_HASH, + } + + it('should not pass headers if no gas price is specified', async () => { + depositTokensMock('10').reply(201, CASHOUT_RESPONSE) + + const bee = new BeeDebug(MOCK_SERVER_URL) + await expect(bee.depositTokens(BigInt('10'))).resolves.toEqual(TRANSACTION_HASH) + assertAllIsDone() + }) + + it('should pass headers if gas price is specified', async () => { + depositTokensMock('10', '100000000000').reply(201, CASHOUT_RESPONSE) + + const bee = new BeeDebug(MOCK_SERVER_URL) + await expect(bee.depositTokens(BigInt('10'), BigInt('100000000000'))).resolves.toEqual(TRANSACTION_HASH) + assertAllIsDone() + }) + + it('should throw error if passed wrong amount', async () => { + const bee = new BeeDebug(MOCK_SERVER_URL) + + // @ts-ignore: Input testing + await expect(bee.depositTokens(true)).rejects.toThrow(TypeError) + + // @ts-ignore: Input testing + await expect(bee.depositTokens('asd')).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.depositTokens(null)).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.depositTokens()).rejects.toThrow(TypeError) + + await expect(bee.depositTokens(BigInt('-1'))).rejects.toThrow(BeeArgumentError) + }) + + it('should throw error if passed wrong gas price input', async () => { + const bee = new BeeDebug(MOCK_SERVER_URL) + + // @ts-ignore: Input testing + await expect(bee.depositTokens(BigInt('1'), true)).rejects.toThrow(TypeError) + // @ts-ignore: Input testing + await expect(bee.depositTokens(BigInt('1'), 'asd')).rejects.toThrow(TypeError) + await expect(bee.depositTokens(BigInt('1'), BigInt('-1'))).rejects.toThrow(BeeArgumentError) + }) + }) }) diff --git a/test/unit/nock.ts b/test/unit/nock.ts index f32e355c..93f577a3 100644 --- a/test/unit/nock.ts +++ b/test/unit/nock.ts @@ -71,3 +71,27 @@ export function cashoutLastChequeMock(peer: string, gasPrice?: string, gasLimit? reqheaders: headers, }).post(`${CHEQUEBOOK_ENDPOINT}/cashout/${peer}`) } + +export function depositTokensMock(amount: string, gasPrice?: string): nock.Interceptor { + const headers: Record = {} + + if (gasPrice) { + headers['gas-price'] = gasPrice + } + + return nock(MOCK_SERVER_URL, { + reqheaders: headers, + }).post(`${CHEQUEBOOK_ENDPOINT}/deposit?amount=${amount}`) +} + +export function withdrawTokensMock(amount: string, gasPrice?: string): nock.Interceptor { + const headers: Record = {} + + if (gasPrice) { + headers['gas-price'] = gasPrice + } + + return nock(MOCK_SERVER_URL, { + reqheaders: headers, + }).post(`${CHEQUEBOOK_ENDPOINT}/withdraw?amount=${amount}`) +}