Skip to content
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
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr

## Unreleased

### Fixed
* Fix `OracleSet` transaction to accept hexadecimal string values for `AssetPrice` field

## 4.2.0 (2025-2-13)

### Added
Expand Down
38 changes: 30 additions & 8 deletions packages/xrpl/src/models/transactions/oracleSet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ValidationError } from '../../errors'
import { PriceData } from '../common'
import { isHex } from '../utils'

import {
BaseTransaction,
Expand All @@ -12,6 +13,8 @@ import {

const PRICE_DATA_SERIES_MAX_LENGTH = 10
const SCALE_MAX = 10
const MINIMUM_ASSET_PRICE_LENGTH = 1
const MAXIMUM_ASSET_PRICE_LENGTH = 16

/**
* Creates a new Oracle ledger entry or updates the fields of an existing one, using the Oracle ID.
Expand Down Expand Up @@ -82,7 +85,7 @@ export function validateOracleSet(tx: Record<string, unknown>): void {

validateOptionalField(tx, 'AssetClass', isString)

// eslint-disable-next-line max-lines-per-function -- necessary to validate many fields
/* eslint-disable max-statements, max-lines-per-function -- necessary to validate many fields */
validateRequiredField(tx, 'PriceDataSeries', (value) => {
if (!Array.isArray(value)) {
throw new ValidationError('OracleSet: PriceDataSeries must be an array')
Expand Down Expand Up @@ -142,14 +145,32 @@ export function validateOracleSet(tx: Record<string, unknown>): void {
)
}

if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
'AssetPrice' in priceData.PriceData &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
!isNumber(priceData.PriceData.AssetPrice)
) {
throw new ValidationError('OracleSet: invalid field AssetPrice')
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-depth --
we need to validate priceData.PriceData.AssetPrice value */
if ('AssetPrice' in priceData.PriceData) {
if (!isNumber(priceData.PriceData.AssetPrice)) {
if (typeof priceData.PriceData.AssetPrice !== 'string') {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a string or a number',
)
}
if (!isHex(priceData.PriceData.AssetPrice)) {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a valid hex string',
)
}
if (
priceData.PriceData.AssetPrice.length <
MINIMUM_ASSET_PRICE_LENGTH ||
priceData.PriceData.AssetPrice.length > MAXIMUM_ASSET_PRICE_LENGTH
) {
throw new ValidationError(
`OracleSet: Length of AssetPrice field must be between ${MINIMUM_ASSET_PRICE_LENGTH} and ${MAXIMUM_ASSET_PRICE_LENGTH} characters long`,
)
}
}
}
/* eslint-enable @typescript-eslint/no-unsafe-member-access, max-depth */

if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
Expand All @@ -173,4 +194,5 @@ export function validateOracleSet(tx: Record<string, unknown>): void {
}
return true
})
/* eslint-enable max-statements, max-lines-per-function */
}
19 changes: 18 additions & 1 deletion packages/xrpl/test/integration/transactions/oracleSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ describe('OracleSet', function () {
Scale: 3,
},
},
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'INR',
// Upper bound admissible value for AssetPrice field
// large numeric values necessarily have to use str type in Javascript
// number type uses double-precision floating point representation, hence represents a smaller range of values
AssetPrice: 'ffffffffffffffff',
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
Expand All @@ -62,12 +73,18 @@ describe('OracleSet', function () {
assert.equal(oracle.Owner, testContext.wallet.classicAddress)
assert.equal(oracle.AssetClass, tx.AssetClass)
assert.equal(oracle.Provider, tx.Provider)
assert.equal(oracle.PriceDataSeries.length, 1)
assert.equal(oracle.PriceDataSeries.length, 2)
assert.equal(oracle.PriceDataSeries[0].PriceData.BaseAsset, 'XRP')
assert.equal(oracle.PriceDataSeries[0].PriceData.QuoteAsset, 'USD')
assert.equal(oracle.PriceDataSeries[0].PriceData.AssetPrice, '2e4')
assert.equal(oracle.PriceDataSeries[0].PriceData.Scale, 3)
assert.equal(oracle.Flags, 0)

// validate the serialization of large AssetPrice values
assert.equal(
oracle.PriceDataSeries[1].PriceData.AssetPrice,
'ffffffffffffffff',
)
},
TIMEOUT,
)
Expand Down
20 changes: 18 additions & 2 deletions packages/xrpl/test/models/oracleSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,24 @@ describe('OracleSet', function () {
})

it(`throws w/ invalid AssetPrice of PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.AssetPrice = '1234'
const errorMessage = 'OracleSet: invalid field AssetPrice'
// value cannot be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ghij'
const errorMessage =
'OracleSet: Field AssetPrice must be a valid hex string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})

it(`verifies valid AssetPrice of PriceDataSeries`, function () {
// valid string which can be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ab15'
assert.doesNotThrow(() => validate(tx))
})

it(`throws w/ invalid AssetPrice type in PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.AssetPrice = ['sample', 'invalid', 'type']
const errorMessage =
'OracleSet: Field AssetPrice must be a string or a number'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
Expand Down
Loading