Skip to content

Commit 7fe27f3

Browse files
ckeshavakhancode
andauthored
fix #2911: Update OracleSet transaction model, unit, integ tests (#2913)
* fix #2911: Update OracleSet transaction model, unit, integ tests --------- Co-authored-by: Omar Khan <khancodegt@gmail.com>
1 parent ea4f606 commit 7fe27f3

File tree

4 files changed

+69
-11
lines changed

4 files changed

+69
-11
lines changed

packages/xrpl/HISTORY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
44

55
## Unreleased
66

7+
### Fixed
8+
* Fix `OracleSet` transaction to accept hexadecimal string values for `AssetPrice` field
9+
710
## 4.2.0 (2025-2-13)
811

912
### Added

packages/xrpl/src/models/transactions/oracleSet.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ValidationError } from '../../errors'
22
import { PriceData } from '../common'
3+
import { isHex } from '../utils'
34

45
import {
56
BaseTransaction,
@@ -12,6 +13,8 @@ import {
1213

1314
const PRICE_DATA_SERIES_MAX_LENGTH = 10
1415
const SCALE_MAX = 10
16+
const MINIMUM_ASSET_PRICE_LENGTH = 1
17+
const MAXIMUM_ASSET_PRICE_LENGTH = 16
1518

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

8386
validateOptionalField(tx, 'AssetClass', isString)
8487

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

145-
if (
146-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
147-
'AssetPrice' in priceData.PriceData &&
148-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
149-
!isNumber(priceData.PriceData.AssetPrice)
150-
) {
151-
throw new ValidationError('OracleSet: invalid field AssetPrice')
148+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-depth --
149+
we need to validate priceData.PriceData.AssetPrice value */
150+
if ('AssetPrice' in priceData.PriceData) {
151+
if (!isNumber(priceData.PriceData.AssetPrice)) {
152+
if (typeof priceData.PriceData.AssetPrice !== 'string') {
153+
throw new ValidationError(
154+
'OracleSet: Field AssetPrice must be a string or a number',
155+
)
156+
}
157+
if (!isHex(priceData.PriceData.AssetPrice)) {
158+
throw new ValidationError(
159+
'OracleSet: Field AssetPrice must be a valid hex string',
160+
)
161+
}
162+
if (
163+
priceData.PriceData.AssetPrice.length <
164+
MINIMUM_ASSET_PRICE_LENGTH ||
165+
priceData.PriceData.AssetPrice.length > MAXIMUM_ASSET_PRICE_LENGTH
166+
) {
167+
throw new ValidationError(
168+
`OracleSet: Length of AssetPrice field must be between ${MINIMUM_ASSET_PRICE_LENGTH} and ${MAXIMUM_ASSET_PRICE_LENGTH} characters long`,
169+
)
170+
}
171+
}
152172
}
173+
/* eslint-enable @typescript-eslint/no-unsafe-member-access, max-depth */
153174

154175
if (
155176
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
@@ -173,4 +194,5 @@ export function validateOracleSet(tx: Record<string, unknown>): void {
173194
}
174195
return true
175196
})
197+
/* eslint-enable max-statements, max-lines-per-function */
176198
}

packages/xrpl/test/integration/transactions/oracleSet.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ describe('OracleSet', function () {
3939
Scale: 3,
4040
},
4141
},
42+
{
43+
PriceData: {
44+
BaseAsset: 'XRP',
45+
QuoteAsset: 'INR',
46+
// Upper bound admissible value for AssetPrice field
47+
// large numeric values necessarily have to use str type in Javascript
48+
// number type uses double-precision floating point representation, hence represents a smaller range of values
49+
AssetPrice: 'ffffffffffffffff',
50+
Scale: 3,
51+
},
52+
},
4253
],
4354
Provider: stringToHex('chainlink'),
4455
URI: '6469645F6578616D706C65',
@@ -62,12 +73,18 @@ describe('OracleSet', function () {
6273
assert.equal(oracle.Owner, testContext.wallet.classicAddress)
6374
assert.equal(oracle.AssetClass, tx.AssetClass)
6475
assert.equal(oracle.Provider, tx.Provider)
65-
assert.equal(oracle.PriceDataSeries.length, 1)
76+
assert.equal(oracle.PriceDataSeries.length, 2)
6677
assert.equal(oracle.PriceDataSeries[0].PriceData.BaseAsset, 'XRP')
6778
assert.equal(oracle.PriceDataSeries[0].PriceData.QuoteAsset, 'USD')
6879
assert.equal(oracle.PriceDataSeries[0].PriceData.AssetPrice, '2e4')
6980
assert.equal(oracle.PriceDataSeries[0].PriceData.Scale, 3)
7081
assert.equal(oracle.Flags, 0)
82+
83+
// validate the serialization of large AssetPrice values
84+
assert.equal(
85+
oracle.PriceDataSeries[1].PriceData.AssetPrice,
86+
'ffffffffffffffff',
87+
)
7188
},
7289
TIMEOUT,
7390
)

packages/xrpl/test/models/oracleSet.test.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,24 @@ describe('OracleSet', function () {
167167
})
168168

169169
it(`throws w/ invalid AssetPrice of PriceDataSeries`, function () {
170-
tx.PriceDataSeries[0].PriceData.AssetPrice = '1234'
171-
const errorMessage = 'OracleSet: invalid field AssetPrice'
170+
// value cannot be parsed as hexadecimal number
171+
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ghij'
172+
const errorMessage =
173+
'OracleSet: Field AssetPrice must be a valid hex string'
174+
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
175+
assert.throws(() => validate(tx), ValidationError, errorMessage)
176+
})
177+
178+
it(`verifies valid AssetPrice of PriceDataSeries`, function () {
179+
// valid string which can be parsed as hexadecimal number
180+
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ab15'
181+
assert.doesNotThrow(() => validate(tx))
182+
})
183+
184+
it(`throws w/ invalid AssetPrice type in PriceDataSeries`, function () {
185+
tx.PriceDataSeries[0].PriceData.AssetPrice = ['sample', 'invalid', 'type']
186+
const errorMessage =
187+
'OracleSet: Field AssetPrice must be a string or a number'
172188
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
173189
assert.throws(() => validate(tx), ValidationError, errorMessage)
174190
})

0 commit comments

Comments
 (0)