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

Fix: parseTransactionFlags unintentionally modifies transaction #2825

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.

## Unreleased Changes
* Fix for parseTransactionFlags to no longer modify a passed in transaction when returning a parsed mapping
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be under a heading that matches the actions - see https://keepachangelog.com/en/1.1.0/

* Deprecate setTransactionFlagsToNumber. Should use convertTxFlagsToNumber instead
* Add convertTxFlagsToNumber as a utility function in xrpl to return the number conversion of a transaction's flags

### Added
* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion
Expand Down
4 changes: 2 additions & 2 deletions packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import type {
OnEventToListenerMap,
} from '../models/methods/subscribe'
import type { SubmittableTransaction } from '../models/transactions'
import { setTransactionFlagsToNumber } from '../models/utils/flags'
import { convertTxFlagsToNumber } from '../models/utils/flags'
import {
ensureClassicAddress,
submitRequest,
Expand Down Expand Up @@ -665,7 +665,7 @@ class Client extends EventEmitter<EventTypes> {
const tx = { ...transaction }

setValidAddresses(tx)
setTransactionFlagsToNumber(tx)
tx.Flags = convertTxFlagsToNumber(tx)

const promises: Array<Promise<void>> = []
if (tx.NetworkID == null) {
Expand Down
3 changes: 2 additions & 1 deletion packages/xrpl/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
*/
export * as LedgerEntry from './ledger'
export {
setTransactionFlagsToNumber,
parseAccountRootFlags,
setTransactionFlagsToNumber,
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
convertTxFlagsToNumber,
parseTransactionFlags,
} from './utils/flags'
export * from './methods'
Expand Down
82 changes: 49 additions & 33 deletions packages/xrpl/src/models/utils/flags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign -- param reassign is safe */
/* eslint-disable no-bitwise -- flags require bitwise operations */
import { ValidationError } from '../../errors'
import {
Expand All @@ -8,7 +7,6 @@ import {
import { AccountSetTfFlags } from '../transactions/accountSet'
import { AMMDepositFlags } from '../transactions/AMMDeposit'
import { AMMWithdrawFlags } from '../transactions/AMMWithdraw'
import { GlobalFlags } from '../transactions/common'
import { NFTokenCreateOfferFlags } from '../transactions/NFTokenCreateOffer'
import { NFTokenMintFlags } from '../transactions/NFTokenMint'
import { OfferCreateFlags } from '../transactions/offerCreate'
Expand Down Expand Up @@ -60,34 +58,50 @@ const txToFlag = {
/**
* Sets a transaction's flags to its numeric representation.
*
* @deprecated
* This utility function is deprecated.
* Use convertTxFlagsToNumber() instead and use the returned value to modify the tx.Flags from the caller.
*
* @param tx - A transaction to set its flags to its numeric representation.
*/
export function setTransactionFlagsToNumber(tx: Transaction): void {
if (tx.Flags == null) {
tx.Flags = 0
return
}
// eslint-disable-next-line no-console -- intended deprecation warning
console.warn(
'This function is deprecated. Use convertTxFlagsToNumber() instead and use the returned value to modify the tx.Flags from the caller.',
)

achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line no-param-reassign -- intended param reassign in setter
tx.Flags = convertTxFlagsToNumber(tx)
}

/**
* Returns a transaction's flags as its numeric representation.
*
* @param tx - A transaction to parse flags for
* @returns A numerical representation of a transaction's flags
*/
export function convertTxFlagsToNumber(tx: Transaction): number {
if (typeof tx.Flags === 'number') {
return
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
ckeshava marked this conversation as resolved.
Show resolved Hide resolved
return tx.Flags
}

tx.Flags = txToFlag[tx.TransactionType]
? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType])
: 0
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access
const flagEnum = txToFlag[tx.TransactionType]

achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
return Object.keys(flags).reduce((resultFlags, flag) => {
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
if (flagEnum[flag] == null) {
throw new ValidationError(
`flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags
}, 0)
if (flagEnum && tx.Flags) {
return Object.keys(tx.Flags).reduce((resultFlags, flag) => {
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
if (flagEnum[flag] == null) {
throw new ValidationError(
`flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
)
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
return tx.Flags?.[flag] ? resultFlags | flagEnum[flag] : resultFlags
}, 0)
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
}
return 0
}
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand All @@ -97,22 +111,24 @@ function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
* @returns A map with all flags as booleans.
*/
export function parseTransactionFlags(tx: Transaction): object {
setTransactionFlagsToNumber(tx)
if (typeof tx.Flags !== 'number' || !tx.Flags || tx.Flags === 0) {
const flags = convertTxFlagsToNumber(tx)
if (flags === 0) {
return {}
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
}

const flags = tx.Flags
const flagsMap = {}
const booleanFlagMap = {}

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access
const flagEnum = txToFlag[tx.TransactionType]
Object.values(flagEnum).forEach((flag) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
if (typeof flag === 'string' && isFlagEnabled(flags, flagEnum[flag])) {
flagsMap[flag] = true
const transactionTypeFlags = txToFlag[tx.TransactionType]
Object.values(transactionTypeFlags).forEach((flag) => {
if (
typeof flag === 'string' &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
isFlagEnabled(flags, transactionTypeFlags[flag])
) {
booleanFlagMap[flag] = true
}
})

return flagsMap
return booleanFlagMap
}
22 changes: 11 additions & 11 deletions packages/xrpl/test/models/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { AccountRootFlags } from '../../src/models/ledger'
import { isFlagEnabled } from '../../src/models/utils'
import {
setTransactionFlagsToNumber,
convertTxFlagsToNumber,
parseAccountRootFlags,
parseTransactionFlags,
} from '../../src/models/utils/flags'
Expand Down Expand Up @@ -71,8 +71,8 @@ describe('Models Utils', function () {
const { tfPassive, tfFillOrKill } = OfferCreateFlags
const expected: number = tfPassive | tfFillOrKill

setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
const flagNum = convertTxFlagsToNumber(tx)
achowdhry-ripple marked this conversation as resolved.
Show resolved Hide resolved
assert.strictEqual(flagNum, expected)
})

it('sets PaymentChannelClaimFlags to its numeric value', function () {
Expand All @@ -90,8 +90,8 @@ describe('Models Utils', function () {
const { tfRenew } = PaymentChannelClaimFlags
const expected: number = tfRenew

setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
const flagNum = convertTxFlagsToNumber(tx)
assert.strictEqual(flagNum, expected)
})

it('sets PaymentTransactionFlags to its numeric value', function () {
Expand All @@ -110,8 +110,8 @@ describe('Models Utils', function () {
const { tfPartialPayment, tfLimitQuality } = PaymentFlags
const expected: number = tfPartialPayment | tfLimitQuality

setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
const flagNum = convertTxFlagsToNumber(tx)
assert.strictEqual(flagNum, expected)
})

it('sets TrustSetFlags to its numeric value', function () {
Expand All @@ -137,8 +137,8 @@ describe('Models Utils', function () {
const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = TrustSetFlags
const expected: number = tfSetfAuth | tfClearNoRipple | tfClearFreeze

setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
const flagNum = convertTxFlagsToNumber(tx)
assert.strictEqual(flagNum, expected)
})

it('sets other transaction types flags to its numeric value', function () {
Expand All @@ -148,8 +148,8 @@ describe('Models Utils', function () {
Flags: {},
}

setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, 0)
const flagNum = convertTxFlagsToNumber(tx)
assert.strictEqual(flagNum, 0)
})

// eslint-disable-next-line complexity -- Simpler to list them all out at once.
Expand Down
Loading