add more mpt flag validations (#2856)

* remove else condition

* validation txfee

* clidation

* lint

* lint

* comments

* lint

* more typechecking

* rm newline

* refactor

* null check

* revert null check

* reuse test
This commit is contained in:
Shawn Xie
2024-12-19 15:53:16 -05:00
committed by GitHub
parent 303c2b983c
commit 7bf6fecc71
4 changed files with 76 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
import { ValidationError } from '../../errors' import { ValidationError } from '../../errors'
import { isHex, INTEGER_SANITY_CHECK } from '../utils' import { isHex, INTEGER_SANITY_CHECK, isFlagEnabled } from '../utils'
import { import {
BaseTransaction, BaseTransaction,
@@ -7,11 +7,13 @@ import {
validateBaseTransaction, validateBaseTransaction,
validateOptionalField, validateOptionalField,
isString, isString,
isNumber,
} from './common' } from './common'
import type { TransactionMetadataBase } from './metadata' import type { TransactionMetadataBase } from './metadata'
// 2^63 - 1 // 2^63 - 1
const MAX_AMT = '9223372036854775807' const MAX_AMT = '9223372036854775807'
const MAX_TRANSFER_FEE = 50000
/** /**
* Transaction Flags for an MPTokenIssuanceCreate Transaction. * Transaction Flags for an MPTokenIssuanceCreate Transaction.
@@ -112,6 +114,7 @@ export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase {
mpt_issuance_id?: string mpt_issuance_id?: string
} }
/* eslint-disable max-lines-per-function -- Not needed to reduce function */
/** /**
* Verify the form and type of an MPTokenIssuanceCreate at runtime. * Verify the form and type of an MPTokenIssuanceCreate at runtime.
* *
@@ -124,6 +127,8 @@ export function validateMPTokenIssuanceCreate(
validateBaseTransaction(tx) validateBaseTransaction(tx)
validateOptionalField(tx, 'MaximumAmount', isString) validateOptionalField(tx, 'MaximumAmount', isString)
validateOptionalField(tx, 'MPTokenMetadata', isString) validateOptionalField(tx, 'MPTokenMetadata', isString)
validateOptionalField(tx, 'TransferFee', isNumber)
validateOptionalField(tx, 'AssetScale', isNumber)
if (typeof tx.MPTokenMetadata === 'string' && tx.MPTokenMetadata === '') { if (typeof tx.MPTokenMetadata === 'string' && tx.MPTokenMetadata === '') {
throw new ValidationError( throw new ValidationError(
@@ -150,12 +155,25 @@ export function validateMPTokenIssuanceCreate(
} }
} }
const MAX_TRANSFER_FEE = 50000
if (typeof tx.TransferFee === 'number') { if (typeof tx.TransferFee === 'number') {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary
const flags = tx.Flags as number | MPTokenIssuanceCreateFlagsInterface
const isTfMPTCanTransfer =
typeof flags === 'number'
? isFlagEnabled(flags, MPTokenIssuanceCreateFlags.tfMPTCanTransfer)
: flags.tfMPTCanTransfer ?? false
if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) { if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) {
throw new ValidationError( throw new ValidationError(
'MPTokenIssuanceCreate: TransferFee out of range', `MPTokenIssuanceCreate: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`,
)
}
if (tx.TransferFee && !isTfMPTCanTransfer) {
throw new ValidationError(
'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag',
) )
} }
} }
} }
/* eslint-enable max-lines-per-function */

View File

@@ -68,15 +68,19 @@ export function validateMPTokenIssuanceSet(tx: Record<string, unknown>): void {
validateRequiredField(tx, 'MPTokenIssuanceID', isString) validateRequiredField(tx, 'MPTokenIssuanceID', isString)
validateOptionalField(tx, 'Holder', isAccount) validateOptionalField(tx, 'Holder', isAccount)
if (typeof tx.Flags === 'number') { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary
const flags = tx.Flags const flags = tx.Flags as number | MPTokenIssuanceSetFlagsInterface
if ( const isTfMPTLock =
isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTLock) && typeof flags === 'number'
isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTUnlock) ? isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTLock)
) { : flags.tfMPTLock ?? false
throw new ValidationError('MPTokenIssuanceSet: flag conflict')
} const isTfMPTUnlock =
} else { typeof flags === 'number'
throw new Error('tx.Flags is not a number') ? isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTUnlock)
: flags.tfMPTUnlock ?? false
if (isTfMPTLock && isTfMPTUnlock) {
throw new ValidationError('MPTokenIssuanceSet: flag conflict')
} }
} }

View File

@@ -21,7 +21,7 @@ describe('MPTokenIssuanceCreate', function () {
MaximumAmount: '9223372036854775807', MaximumAmount: '9223372036854775807',
AssetScale: 2, AssetScale: 2,
TransferFee: 1, TransferFee: 1,
Flags: 2, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer,
MPTokenMetadata: convertStringToHex('http://xrpl.org'), MPTokenMetadata: convertStringToHex('http://xrpl.org'),
} as any } as any
@@ -106,7 +106,7 @@ describe('MPTokenIssuanceCreate', function () {
assert.throws( assert.throws(
() => validate(invalid), () => validate(invalid),
ValidationError, ValidationError,
'MPTokenIssuanceCreate: TransferFee out of range', 'MPTokenIssuanceCreate: TransferFee must be between 0 and 50000',
) )
invalid = { invalid = {
@@ -118,7 +118,32 @@ describe('MPTokenIssuanceCreate', function () {
assert.throws( assert.throws(
() => validate(invalid), () => validate(invalid),
ValidationError, ValidationError,
'MPTokenIssuanceCreate: TransferFee out of range', 'MPTokenIssuanceCreate: TransferFee must be between 0 and 50000',
)
invalid = {
TransactionType: 'MPTokenIssuanceCreate',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
TransferFee: 100,
} as any
assert.throws(
() => validate(invalid),
ValidationError,
'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag',
)
invalid = {
TransactionType: 'MPTokenIssuanceCreate',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
TransferFee: 100,
Flags: { tfMPTCanClawback: true },
} as any
assert.throws(
() => validate(invalid),
ValidationError,
'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag',
) )
}) })
}) })

View File

@@ -19,6 +19,7 @@ describe('MPTokenIssuanceSet', function () {
} as any } as any
assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) assert.doesNotThrow(() => validate(validMPTokenIssuanceSet))
validMPTokenIssuanceSet = { validMPTokenIssuanceSet = {
TransactionType: 'MPTokenIssuanceSet', TransactionType: 'MPTokenIssuanceSet',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
@@ -54,15 +55,23 @@ describe('MPTokenIssuanceSet', function () {
}) })
it(`throws w/ conflicting flags`, function () { it(`throws w/ conflicting flags`, function () {
/* eslint-disable no-bitwise -- Bitwise operation needed for flag combination */
const invalid = { const invalid = {
TransactionType: 'MPTokenIssuanceSet', TransactionType: 'MPTokenIssuanceSet',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
MPTokenIssuanceID: TOKEN_ID, MPTokenIssuanceID: TOKEN_ID,
Flags:
MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock,
} as any } as any
/* eslint-enable no-bitwise -- Re-enable bitwise rule */
invalid.Flags =
// eslint-disable-next-line no-bitwise -- not needed
MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock
assert.throws(
() => validate(invalid),
ValidationError,
'MPTokenIssuanceSet: flag conflict',
)
invalid.Flags = { tfMPTLock: true, tfMPTUnlock: true }
assert.throws( assert.throws(
() => validate(invalid), () => validate(invalid),