diff --git a/packages/ripple-binary-codec/HISTORY.md b/packages/ripple-binary-codec/HISTORY.md index 15884b6291..3d03aebd99 100644 --- a/packages/ripple-binary-codec/HISTORY.md +++ b/packages/ripple-binary-codec/HISTORY.md @@ -4,6 +4,7 @@ ### Fixed * Fix STNumber serialization logic to work with large mantissa scale [10^18, 10^19-1]. +* Error if a decimal is passed into a `UInt`-typed field. ## 2.6.0 (2025-12-16) diff --git a/packages/ripple-binary-codec/src/types/uint-16.ts b/packages/ripple-binary-codec/src/types/uint-16.ts index 5d680384e2..a217333d5b 100644 --- a/packages/ripple-binary-codec/src/types/uint-16.ts +++ b/packages/ripple-binary-codec/src/types/uint-16.ts @@ -29,7 +29,7 @@ class UInt16 extends UInt { return val } - if (typeof val === 'number') { + if (typeof val === 'number' && Number.isInteger(val)) { UInt16.checkUintRange(val, 0, 0xffff) const buf = new Uint8Array(UInt16.width) @@ -37,7 +37,7 @@ class UInt16 extends UInt { return new UInt16(buf) } - throw new Error('Can not construct UInt16 with given value') + throw new Error('Cannot construct UInt16 from given value') } /** diff --git a/packages/ripple-binary-codec/src/types/uint-32.ts b/packages/ripple-binary-codec/src/types/uint-32.ts index abb5d8a987..e188ae910c 100644 --- a/packages/ripple-binary-codec/src/types/uint-32.ts +++ b/packages/ripple-binary-codec/src/types/uint-32.ts @@ -37,7 +37,7 @@ class UInt32 extends UInt { return new UInt32(buf) } - if (typeof val === 'number') { + if (typeof val === 'number' && Number.isInteger(val)) { UInt32.checkUintRange(val, 0, 0xffffffff) writeUInt32BE(buf, val, 0) return new UInt32(buf) diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index 4c6ccca90e..bb18e6775a 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -53,7 +53,7 @@ class UInt64 extends UInt { let buf = new Uint8Array(UInt64.width) - if (typeof val === 'number') { + if (typeof val === 'number' && Number.isInteger(val)) { if (val < 0) { throw new Error('value must be an unsigned integer') } diff --git a/packages/ripple-binary-codec/src/types/uint-8.ts b/packages/ripple-binary-codec/src/types/uint-8.ts index 50c4cff773..7d7ae9753f 100644 --- a/packages/ripple-binary-codec/src/types/uint-8.ts +++ b/packages/ripple-binary-codec/src/types/uint-8.ts @@ -28,7 +28,7 @@ class UInt8 extends UInt { return val } - if (typeof val === 'number') { + if (typeof val === 'number' && Number.isInteger(val)) { UInt8.checkUintRange(val, 0, 0xff) const buf = new Uint8Array(UInt8.width) diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index e3165fa1ed..c737344da8 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -1,4 +1,4 @@ -import { UInt8, UInt64 } from '../src/types' +import { UInt8, UInt16, UInt32, UInt64 } from '../src/types' import { encode, decode } from '../src' const binary = @@ -195,3 +195,109 @@ it('UInt64 is parsed as base 10 for MPT amounts', () => { expect(typeof decodedToken.MPTAmount).toBe('string') expect(decodedToken.MPTAmount).toBe('100') }) + +describe('UInt decimal validation', () => { + describe('UInt8', () => { + it('should throw error when passed a decimal number', () => { + expect(() => UInt8.from(1.5)).toThrow( + new Error('Cannot construct UInt8 from given value'), + ) + }) + + it('should throw error when passed a negative decimal', () => { + expect(() => UInt8.from(-1.5)).toThrow( + new Error('Cannot construct UInt8 from given value'), + ) + }) + + it('should throw error when passed a small decimal', () => { + expect(() => UInt8.from(0.1)).toThrow( + new Error('Cannot construct UInt8 from given value'), + ) + }) + + it('should accept valid integer values', () => { + expect(() => UInt8.from(0)).not.toThrow() + expect(() => UInt8.from(1)).not.toThrow() + expect(() => UInt8.from(255)).not.toThrow() + }) + }) + + describe('UInt16', () => { + it('should throw error when passed a decimal number', () => { + expect(() => UInt16.from(100.5)).toThrow( + new Error('Cannot construct UInt16 from given value'), + ) + }) + + it('should throw error when passed a negative decimal', () => { + expect(() => UInt16.from(-100.5)).toThrow( + new Error('Cannot construct UInt16 from given value'), + ) + }) + + it('should throw error when passed a small decimal', () => { + expect(() => UInt16.from(0.001)).toThrow( + new Error('Cannot construct UInt16 from given value'), + ) + }) + + it('should accept valid integer values', () => { + expect(() => UInt16.from(0)).not.toThrow() + expect(() => UInt16.from(1000)).not.toThrow() + expect(() => UInt16.from(65535)).not.toThrow() + }) + }) + + describe('UInt32', () => { + it('should throw error when passed a decimal number', () => { + expect(() => UInt32.from(1000.5)).toThrow( + new Error('Cannot construct UInt32 from given value'), + ) + }) + + it('should throw error when passed a negative decimal', () => { + expect(() => UInt32.from(-1000.5)).toThrow( + new Error('Cannot construct UInt32 from given value'), + ) + }) + + it('should throw error when passed a small decimal', () => { + expect(() => UInt32.from(0.0001)).toThrow( + new Error('Cannot construct UInt32 from given value'), + ) + }) + + it('should accept valid integer values', () => { + expect(() => UInt32.from(0)).not.toThrow() + expect(() => UInt32.from(100000)).not.toThrow() + expect(() => UInt32.from(4294967295)).not.toThrow() + }) + }) + + describe('UInt64', () => { + it('should throw error when passed a decimal number', () => { + expect(() => UInt64.from(10000.5)).toThrow( + new Error('Cannot construct UInt64 from given value'), + ) + }) + + it('should throw error when passed a negative decimal', () => { + expect(() => UInt64.from(-10000.5)).toThrow( + new Error('Cannot construct UInt64 from given value'), + ) + }) + + it('should throw error when passed a small decimal', () => { + expect(() => UInt64.from(0.00001)).toThrow( + new Error('Cannot construct UInt64 from given value'), + ) + }) + + it('should accept valid integer values', () => { + expect(() => UInt64.from(0)).not.toThrow() + expect(() => UInt64.from(1000000)).not.toThrow() + expect(() => UInt64.from(BigInt('9223372036854775807'))).not.toThrow() + }) + }) +}) diff --git a/packages/xrpl/test/integration/transactions/lendingProtocol.test.ts b/packages/xrpl/test/integration/transactions/lendingProtocol.test.ts index 61a9d38cb1..826f8e207c 100644 --- a/packages/xrpl/test/integration/transactions/lendingProtocol.test.ts +++ b/packages/xrpl/test/integration/transactions/lendingProtocol.test.ts @@ -309,7 +309,7 @@ describe('Lending Protocol IT', () => { Account: loanBrokerWallet.address, LoanBrokerID: loanBrokerObjectId, PrincipalRequested: '100000', - InterestRate: 0.1, + InterestRate: 0, Counterparty: borrowerWallet.address, PaymentTotal: 1, }