Skip to content

Commit

Permalink
Aliter: Implement DeliverMax alias in Payment transactions, through a…
Browse files Browse the repository at this point in the history
…utofill method (#2689)


Co-authored-by: Omar Khan <khancodegt@gmail.com>
  • Loading branch information
ckeshava and khancode authored Jun 27, 2024
1 parent a067885 commit 39fed49
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 9 deletions.
38 changes: 35 additions & 3 deletions packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class Client extends EventEmitter<EventTypes> {
* const client = new Client('wss://s.altnet.rippletest.net:51233')
* ```
*/
// eslint-disable-next-line max-lines-per-function -- okay because we have to set up all the connection handlers
/* eslint-disable max-lines-per-function -- the constructor requires more lines to implement the logic */
public constructor(server: string, options: ClientOptions = {}) {
super()
if (typeof server !== 'string' || !/wss?(?:\+unix)?:\/\//u.exec(server)) {
Expand Down Expand Up @@ -290,6 +290,7 @@ class Client extends EventEmitter<EventTypes> {
this.emit('path_find', path)
})
}
/* eslint-enable max-lines-per-function */

/**
* Get the url that the client is connected to.
Expand Down Expand Up @@ -638,15 +639,17 @@ class Client extends EventEmitter<EventTypes> {
* @param signersCount - The expected number of signers for this transaction.
* Only used for multisigned transactions.
* @returns The autofilled transaction.
* @throws ValidationError If Amount and DeliverMax fields are not identical in a Payment Transaction
*/

// eslint-disable-next-line complexity -- handling Payment transaction API v2 requires more logic
public async autofill<T extends SubmittableTransaction>(
transaction: T,
signersCount?: number,
): Promise<T> {
const tx = { ...transaction }

setValidAddresses(tx)

setTransactionFlagsToNumber(tx)

const promises: Array<Promise<void>> = []
Expand All @@ -666,6 +669,34 @@ class Client extends EventEmitter<EventTypes> {
promises.push(checkAccountDeleteBlockers(this, tx))
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
if (tx.TransactionType === 'Payment' && tx.DeliverMax != null) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount == null) {
// If only DeliverMax is provided, use it to populate the Amount field
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a known RPC-level property
tx.Amount = tx.DeliverMax
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount != null && tx.Amount !== tx.DeliverMax) {
return Promise.reject(
new ValidationError(
'PaymentTransaction: Amount and DeliverMax fields must be identical when both are provided',
),
)
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
delete tx.DeliverMax
}

return Promise.all(promises).then(() => tx)
}

Expand Down Expand Up @@ -909,7 +940,7 @@ class Client extends EventEmitter<EventTypes> {
* @param options.limit - Limit number of balances to return.
* @returns An array of XRP/non-XRP balances for the given account.
*/
// eslint-disable-next-line max-lines-per-function -- Longer definition is required for end users to see the definition.
/* eslint-disable max-lines-per-function -- getBalances requires more lines to implement logic */
public async getBalances(
address: string,
options: {
Expand Down Expand Up @@ -957,6 +988,7 @@ class Client extends EventEmitter<EventTypes> {
)
return balances.slice(0, options.limit)
}
/* eslint-enable max-lines-per-function */

/**
* Fetch orderbook (buy/sell orders) between two currency pairs. This checks both sides of the orderbook
Expand Down
65 changes: 63 additions & 2 deletions packages/xrpl/test/client/autofill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Payment,
Transaction,
} from '../../src'
import { ValidationError } from '../../src/errors'
import rippled from '../fixtures/rippled'
import {
setupClient,
Expand All @@ -22,6 +23,8 @@ const LastLedgerSequence = 2908734

describe('client.autofill', function () {
let testContext: XrplTestContext
const AMOUNT = '1234'
let paymentTx: Payment

async function setupMockRippledVersionAndID(
buildVersion: string,
Expand All @@ -40,10 +43,68 @@ describe('client.autofill', function () {
await testContext.client.connect()
}

beforeEach(async () => {
beforeAll(async () => {
testContext = await setupClient()
})
afterEach(async () => teardownClient(testContext))
afterAll(async () => teardownClient(testContext))

beforeEach(async () => {
paymentTx = {
TransactionType: 'Payment',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
DestinationTag: 1,
Fee: '12',
Flags: 2147483648,
LastLedgerSequence: 65953073,
Sequence: 65923914,
SigningPubKey:
'02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654',
TxnSignature:
'3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB',
InvoiceID:
'6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B',
Paths: [
[{ currency: 'BTC', issuer: 'r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X' }],
],
SendMax: '100000000',
}
})

it('Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field', async function () {
const txResult = await testContext.client.autofill(paymentTx)

assert.strictEqual(txResult.Amount, AMOUNT)
})

it('Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount
const txResult = await testContext.client.autofill(paymentTx)

assert.strictEqual(txResult.Amount, AMOUNT)
})

it('Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount

const txResult = await testContext.client.autofill(paymentTx)

assert.strictEqual(txResult.Amount, AMOUNT)
assert.strictEqual('DeliverMax' in txResult, false)
})

it('Validate Payment transaction API v2: Payment Transaction: differing DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = '6789'
paymentTx.Amount = '1234'

await assertRejects(testContext.client.autofill(paymentTx), ValidationError)
})

it('should not autofill if fields are present', async function () {
const tx: Transaction = {
Expand Down
78 changes: 74 additions & 4 deletions packages/xrpl/test/integration/transactions/payment.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Payment } from '../../../src'
import { assert } from 'chai'

import { Payment, Wallet } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
Expand All @@ -12,24 +14,92 @@ const TIMEOUT = 20000

describe('Payment', function () {
let testContext: XrplIntegrationTestContext
let paymentTx: Payment
const AMOUNT = '10000000'
// This wallet is used for DeliverMax related tests
let senderWallet: Wallet

beforeEach(async () => {
// this payment transaction JSON needs to be refreshed before every test.
// Because, we tinker with Amount and DeliverMax fields in the API v2 tests
paymentTx = {
TransactionType: 'Payment',
Account: senderWallet.classicAddress,
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
}
})

beforeAll(async () => {
testContext = await setupClient(serverUrl)
senderWallet = await generateFundedWallet(testContext.client)
})
afterEach(async () => teardownClient(testContext))
afterAll(async () => teardownClient(testContext))

it(
'base',
async () => {
const wallet2 = await generateFundedWallet(testContext.client)
const tx: Payment = {
TransactionType: 'Payment',
Account: testContext.wallet.classicAddress,
Destination: wallet2.classicAddress,
Destination: senderWallet.classicAddress,
Amount: '1000',
}
await testTransaction(testContext.client, tx, testContext.wallet)
},
TIMEOUT,
)

it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field',
async () => {
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)

assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)

it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount

const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)

assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)

it(
'Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount

const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)

assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
})

0 comments on commit 39fed49

Please sign in to comment.