mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-21 04:35:49 +00:00
refactored src/types (#86)
Final refactor of the src/types directory. Refactored Amount and STObject classes, and finalized SerializedType and Comparable classes.
This commit is contained in:
@@ -19,7 +19,7 @@ function serializeObject(object, opts = <any>{}) {
|
|||||||
bytesList.put(prefix);
|
bytesList.put(prefix);
|
||||||
}
|
}
|
||||||
const filter = signingFieldsOnly ? (f) => f.isSigningField : undefined;
|
const filter = signingFieldsOnly ? (f) => f.isSigningField : undefined;
|
||||||
coreTypes.STObject.from(object).toBytesSink(bytesList, filter);
|
coreTypes.STObject.from(object, filter).toBytesSink(bytesList);
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
bytesList.put(suffix);
|
bytesList.put(suffix);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { serializeUIntN } from "../utils/bytes-utils";
|
import { serializeUIntN } from "../utils/bytes-utils";
|
||||||
import * as enums from "./definitions.json";
|
import * as enums from "./definitions.json";
|
||||||
|
import { SerializedType } from "../types/serialized-type";
|
||||||
|
|
||||||
const TYPE_WIDTH = 2;
|
const TYPE_WIDTH = 2;
|
||||||
const LEDGER_ENTRY_WIDTH = 2;
|
const LEDGER_ENTRY_WIDTH = 2;
|
||||||
@@ -46,6 +47,10 @@ class Bytes {
|
|||||||
toBytesSink(sink): void {
|
toBytesSink(sink): void {
|
||||||
sink.put(this.bytes);
|
sink.put(this.bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toBytes(): Uint8Array {
|
||||||
|
return this.bytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -88,7 +93,7 @@ interface FieldInstance {
|
|||||||
readonly ordinal: number;
|
readonly ordinal: number;
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly header: Buffer;
|
readonly header: Buffer;
|
||||||
readonly associatedType: any;
|
readonly associatedType: typeof SerializedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildField([name, info]: [string, FieldInfo]): FieldInstance {
|
function buildField([name, info]: [string, FieldInfo]): FieldInstance {
|
||||||
@@ -103,7 +108,7 @@ function buildField([name, info]: [string, FieldInfo]): FieldInstance {
|
|||||||
ordinal: (typeOrdinal << 16) | info.nth,
|
ordinal: (typeOrdinal << 16) | info.nth,
|
||||||
type: new Bytes(info.type, typeOrdinal, TYPE_WIDTH),
|
type: new Bytes(info.type, typeOrdinal, TYPE_WIDTH),
|
||||||
header: field,
|
header: field,
|
||||||
associatedType: undefined, // For later assignment in ./types/index.js
|
associatedType: SerializedType, // For later assignment in ./types/index.js
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as assert from "assert";
|
import * as assert from "assert";
|
||||||
import { Field, FieldInstance } from "../enums";
|
import { Field, FieldInstance } from "../enums";
|
||||||
|
import { SerializedType } from "../types/serialized-type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BinaryParser is used to compute fields and values from a HexString
|
* BinaryParser is used to compute fields and values from a HexString
|
||||||
@@ -43,10 +44,7 @@ class BinaryParser {
|
|||||||
* @return The bytes
|
* @return The bytes
|
||||||
*/
|
*/
|
||||||
read(n: number): Buffer {
|
read(n: number): Buffer {
|
||||||
assert(
|
assert(n <= this.bytes.byteLength);
|
||||||
n <= this.bytes.byteLength,
|
|
||||||
n + " greater than " + this.bytes.byteLength
|
|
||||||
);
|
|
||||||
|
|
||||||
const slice = this.bytes.slice(0, n);
|
const slice = this.bytes.slice(0, n);
|
||||||
this.skip(n);
|
this.skip(n);
|
||||||
@@ -156,7 +154,7 @@ class BinaryParser {
|
|||||||
* @param type The type that you want to read from the BinaryParser
|
* @param type The type that you want to read from the BinaryParser
|
||||||
* @return The instance of that type read from the BinaryParser
|
* @return The instance of that type read from the BinaryParser
|
||||||
*/
|
*/
|
||||||
readType(type) {
|
readType(type: typeof SerializedType): SerializedType {
|
||||||
return type.fromParser(this);
|
return type.fromParser(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +164,7 @@ class BinaryParser {
|
|||||||
* @param field The field that you wan to get the type of
|
* @param field The field that you wan to get the type of
|
||||||
* @return The type associated with the given field
|
* @return The type associated with the given field
|
||||||
*/
|
*/
|
||||||
typeForField(field: FieldInstance) {
|
typeForField(field: FieldInstance): typeof SerializedType {
|
||||||
return field.associatedType;
|
return field.associatedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,14 +174,14 @@ class BinaryParser {
|
|||||||
* @param field The field that you want to get the associated value for
|
* @param field The field that you want to get the associated value for
|
||||||
* @return The value associated with the given field
|
* @return The value associated with the given field
|
||||||
*/
|
*/
|
||||||
readFieldValue(field: FieldInstance) {
|
readFieldValue(field: FieldInstance): SerializedType {
|
||||||
const type = this.typeForField(field);
|
const type = this.typeForField(field);
|
||||||
if (!type) {
|
if (!type) {
|
||||||
throw new Error(`unsupported: (${field.name}, ${field.type.name})`);
|
throw new Error(`unsupported: (${field.name}, ${field.type.name})`);
|
||||||
}
|
}
|
||||||
const sizeHint = field.isVariableLengthEncoded
|
const sizeHint = field.isVariableLengthEncoded
|
||||||
? this.readVariableLengthLength()
|
? this.readVariableLengthLength()
|
||||||
: null;
|
: undefined;
|
||||||
const value = type.fromParser(this, sizeHint);
|
const value = type.fromParser(this, sizeHint);
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -198,7 +196,7 @@ class BinaryParser {
|
|||||||
*
|
*
|
||||||
* @return The field and value
|
* @return The field and value
|
||||||
*/
|
*/
|
||||||
readFieldAndValue() {
|
readFieldAndValue(): [FieldInstance, SerializedType] {
|
||||||
const field = this.readField();
|
const field = this.readField();
|
||||||
return [field, this.readFieldValue(field)];
|
return [field, this.readFieldValue(field)];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as assert from "assert";
|
import * as assert from "assert";
|
||||||
import { FieldInstance } from "../enums";
|
import { FieldInstance } from "../enums";
|
||||||
|
import { SerializedType } from "../types/serialized-type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bytes list is a collection of buffer objects
|
* Bytes list is a collection of buffer objects
|
||||||
@@ -61,7 +62,7 @@ class BinarySerializer {
|
|||||||
*
|
*
|
||||||
* @param value a SerializedType value
|
* @param value a SerializedType value
|
||||||
*/
|
*/
|
||||||
write(value): void {
|
write(value: SerializedType): void {
|
||||||
value.toBytesSink(this.sink);
|
value.toBytesSink(this.sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ class BinarySerializer {
|
|||||||
* @param type the type to write
|
* @param type the type to write
|
||||||
* @param value a value of that type
|
* @param value a value of that type
|
||||||
*/
|
*/
|
||||||
writeType(type, value): void {
|
writeType(type: typeof SerializedType, value: SerializedType): void {
|
||||||
this.write(type.from(value));
|
this.write(type.from(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,9 +125,10 @@ class BinarySerializer {
|
|||||||
* @param field field to write to BinarySerializer
|
* @param field field to write to BinarySerializer
|
||||||
* @param value value to write to BinarySerializer
|
* @param value value to write to BinarySerializer
|
||||||
*/
|
*/
|
||||||
writeFieldAndValue(field: FieldInstance, value): void {
|
writeFieldAndValue(field: FieldInstance, value: SerializedType): void {
|
||||||
const associatedValue = field.associatedType.from(value);
|
const associatedValue = field.associatedType.from(value);
|
||||||
assert(associatedValue.toBytesSink, field.name);
|
assert(associatedValue.toBytesSink, field.name);
|
||||||
|
|
||||||
this.sink.put(field.header);
|
this.sink.put(field.header);
|
||||||
|
|
||||||
if (field.isVariableLengthEncoded) {
|
if (field.isVariableLengthEncoded) {
|
||||||
@@ -141,7 +143,7 @@ class BinarySerializer {
|
|||||||
*
|
*
|
||||||
* @param value length encoded value to write to BytesList
|
* @param value length encoded value to write to BytesList
|
||||||
*/
|
*/
|
||||||
public writeLengthEncoded(value): void {
|
public writeLengthEncoded(value: SerializedType): void {
|
||||||
const bytes = new BytesList();
|
const bytes = new BytesList();
|
||||||
value.toBytesSink(bytes);
|
value.toBytesSink(bytes);
|
||||||
this.put(this.encodeVariableLength(bytes.getLength()));
|
this.put(this.encodeVariableLength(bytes.getLength()));
|
||||||
|
|||||||
@@ -1,225 +1,218 @@
|
|||||||
import { makeClass } from "../utils/make-class";
|
import { Decimal } from "decimal.js";
|
||||||
const _ = require("lodash");
|
import { SerializedType } from "./serialized-type";
|
||||||
const assert = require("assert");
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
const Decimal = require("decimal.js");
|
import { Currency } from "./currency";
|
||||||
const { SerializedType } = require("./serialized-type");
|
import { AccountID } from "./account-id";
|
||||||
const { bytesToHex } = require("../utils/bytes-utils");
|
|
||||||
const { Currency } = require("./currency");
|
|
||||||
const { AccountID } = require("./account-id");
|
|
||||||
const { UInt64 } = require("./uint-64");
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants for validating amounts
|
||||||
|
*/
|
||||||
const MIN_IOU_EXPONENT = -96;
|
const MIN_IOU_EXPONENT = -96;
|
||||||
const MAX_IOU_EXPONENT = 80;
|
const MAX_IOU_EXPONENT = 80;
|
||||||
const MAX_IOU_PRECISION = 16;
|
const MAX_IOU_PRECISION = 16;
|
||||||
const MIN_IOU_MANTISSA = "1000" + "0000" + "0000" + "0000"; // 16 digits
|
const MAX_DROPS = new Decimal("1e17");
|
||||||
const MAX_IOU_MANTISSA = "9999" + "9999" + "9999" + "9999"; // ..
|
|
||||||
const MAX_IOU = new Decimal(`${MAX_IOU_MANTISSA}e${MAX_IOU_EXPONENT}`);
|
|
||||||
const MIN_IOU = new Decimal(`${MIN_IOU_MANTISSA}e${MIN_IOU_EXPONENT}`);
|
|
||||||
const DROPS_PER_XRP = new Decimal("1e6");
|
|
||||||
const MAX_NETWORK_DROPS = new Decimal("1e17");
|
|
||||||
const MIN_XRP = new Decimal("1e-6");
|
const MIN_XRP = new Decimal("1e-6");
|
||||||
const MAX_XRP = MAX_NETWORK_DROPS.dividedBy(DROPS_PER_XRP);
|
|
||||||
|
|
||||||
// Never use exponential form
|
/**
|
||||||
|
* decimal.js configuration for Amount IOUs
|
||||||
|
*/
|
||||||
Decimal.config({
|
Decimal.config({
|
||||||
toExpPos: MAX_IOU_EXPONENT + MAX_IOU_PRECISION,
|
toExpPos: MAX_IOU_EXPONENT + MAX_IOU_PRECISION,
|
||||||
toExpNeg: MIN_IOU_EXPONENT - MAX_IOU_PRECISION,
|
toExpNeg: MIN_IOU_EXPONENT - MAX_IOU_PRECISION,
|
||||||
});
|
});
|
||||||
|
|
||||||
const AMOUNT_PARAMETERS_DESCRIPTION = `
|
/**
|
||||||
Native values must be described in drops, a million of which equal one XRP.
|
* Interface for JSON objects that represent amounts
|
||||||
This must be an integer number, with the absolute value not exceeding \
|
*/
|
||||||
${MAX_NETWORK_DROPS}
|
interface AmountObject {
|
||||||
|
value: string;
|
||||||
IOU values must have a maximum precision of ${MAX_IOU_PRECISION} significant \
|
currency: string;
|
||||||
digits. They are serialized as\na canonicalised mantissa and exponent.
|
issuer: string;
|
||||||
|
|
||||||
The valid range for a mantissa is between ${MIN_IOU_MANTISSA} and \
|
|
||||||
${MAX_IOU_MANTISSA}
|
|
||||||
The exponent must be >= ${MIN_IOU_EXPONENT} and <= ${MAX_IOU_EXPONENT}
|
|
||||||
|
|
||||||
Thus the largest serializable IOU value is:
|
|
||||||
${MAX_IOU.toString()}
|
|
||||||
|
|
||||||
And the smallest:
|
|
||||||
${MIN_IOU.toString()}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function isDefined(val) {
|
|
||||||
return !_.isUndefined(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function raiseIllegalAmountError(value) {
|
/**
|
||||||
throw new Error(
|
* Class for serializing/Deserializing Amounts
|
||||||
`${value.toString()} is an illegal amount\n` + AMOUNT_PARAMETERS_DESCRIPTION
|
*/
|
||||||
|
class Amount extends SerializedType {
|
||||||
|
static defaultAmount: Amount = new Amount(
|
||||||
|
Buffer.from("4000000000000000", "hex")
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const parsers = {
|
constructor(bytes: Buffer) {
|
||||||
string(str) {
|
super(bytes ?? Amount.defaultAmount.bytes);
|
||||||
// Using /^\d+$/ here fixes #31
|
|
||||||
if (!str.match(/^\d+$/)) {
|
|
||||||
raiseIllegalAmountError(str);
|
|
||||||
}
|
}
|
||||||
return [new Decimal(str).dividedBy(DROPS_PER_XRP), Currency.XRP];
|
|
||||||
},
|
|
||||||
object(object) {
|
|
||||||
assert(isDefined(object.currency), "currency must be defined");
|
|
||||||
assert(isDefined(object.issuer), "issuer must be defined");
|
|
||||||
return [
|
|
||||||
new Decimal(object.value),
|
|
||||||
Currency.from(object.currency),
|
|
||||||
AccountID.from(object.issuer),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Amount = makeClass(
|
/**
|
||||||
{
|
* Construct an amount from an IOU or string amount
|
||||||
Amount(value, currency, issuer, validate = true) {
|
*
|
||||||
this.value = value || new Decimal("0");
|
* @param value An Amount, object representing an IOU, or a string representing an integer amount
|
||||||
this.currency = currency || Currency.XRP;
|
* @returns An Amount object
|
||||||
this.issuer = issuer || null;
|
*/
|
||||||
if (validate) {
|
static from(value: Amount | AmountObject | string): Amount {
|
||||||
this.assertValueIsValid();
|
if (value instanceof Amount) {
|
||||||
}
|
|
||||||
},
|
|
||||||
mixins: SerializedType,
|
|
||||||
statics: {
|
|
||||||
from(value) {
|
|
||||||
if (value instanceof this) {
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
const parser = parsers[typeof value];
|
|
||||||
if (parser) {
|
const amount = Buffer.alloc(8);
|
||||||
return new this(...parser(value));
|
if (typeof value === "string") {
|
||||||
|
Amount.assertXrpIsValid(value);
|
||||||
|
|
||||||
|
const number = BigInt(value);
|
||||||
|
amount.writeBigUInt64BE(number);
|
||||||
|
|
||||||
|
amount[0] |= 0x40;
|
||||||
|
|
||||||
|
return new Amount(amount);
|
||||||
|
} else if (typeof value === "object") {
|
||||||
|
const number = new Decimal(value.value);
|
||||||
|
Amount.assertIouIsValid(number);
|
||||||
|
|
||||||
|
if (number.isZero()) {
|
||||||
|
amount[0] |= 0x80;
|
||||||
|
} else {
|
||||||
|
const integerNumberString = number
|
||||||
|
.times(`1e${-(number.e - 15)}`)
|
||||||
|
.abs()
|
||||||
|
.toString();
|
||||||
|
amount.writeBigUInt64BE(BigInt(integerNumberString));
|
||||||
|
|
||||||
|
amount[0] |= 0x80;
|
||||||
|
|
||||||
|
if (number.gt(new Decimal(0))) {
|
||||||
|
amount[0] |= 0x40;
|
||||||
}
|
}
|
||||||
throw new Error(`unsupported value: ${value}`);
|
|
||||||
},
|
const exponent = number.e - 15;
|
||||||
fromParser(parser) {
|
const exponentByte = 97 + exponent;
|
||||||
|
amount[0] |= exponentByte >>> 2;
|
||||||
|
amount[1] |= (exponentByte & 0x03) << 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currency = Currency.from(value.currency).toBytes();
|
||||||
|
const issuer = AccountID.from(value.issuer).toBytes();
|
||||||
|
return new Amount(Buffer.concat([amount, currency, issuer]));
|
||||||
|
}
|
||||||
|
throw new Error("Invalid type to construct an Amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an amount from a BinaryParser
|
||||||
|
*
|
||||||
|
* @param parser BinaryParser to read the Amount from
|
||||||
|
* @returns An Amount object
|
||||||
|
*/
|
||||||
|
static fromParser(parser: BinaryParser): Amount {
|
||||||
|
const isXRP = parser.peek() & 0x80;
|
||||||
|
const numBytes = isXRP ? 48 : 8;
|
||||||
|
return new Amount(parser.read(numBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the JSON representation of this Amount
|
||||||
|
*
|
||||||
|
* @returns the JSON interpretation of this.bytes
|
||||||
|
*/
|
||||||
|
toJSON(): AmountObject | string {
|
||||||
|
if (this.isNative()) {
|
||||||
|
const bytes = this.bytes;
|
||||||
|
const isPositive = bytes[0] & 0x40;
|
||||||
|
const sign = isPositive ? "" : "-";
|
||||||
|
|
||||||
|
bytes[0] &= 0x3f;
|
||||||
|
return `${sign}${bytes.readBigUInt64BE().toString()}`;
|
||||||
|
} else {
|
||||||
|
const parser = new BinaryParser(this.toString());
|
||||||
const mantissa = parser.read(8);
|
const mantissa = parser.read(8);
|
||||||
|
const currency = Currency.fromParser(parser);
|
||||||
|
const issuer = AccountID.fromParser(parser);
|
||||||
|
|
||||||
const b1 = mantissa[0];
|
const b1 = mantissa[0];
|
||||||
const b2 = mantissa[1];
|
const b2 = mantissa[1];
|
||||||
|
|
||||||
const isIOU = b1 & 0x80;
|
|
||||||
const isPositive = b1 & 0x40;
|
const isPositive = b1 & 0x40;
|
||||||
const sign = isPositive ? "" : "-";
|
const sign = isPositive ? "" : "-";
|
||||||
|
|
||||||
if (isIOU) {
|
|
||||||
mantissa[0] = 0;
|
|
||||||
const currency = parser.readType(Currency);
|
|
||||||
const issuer = parser.readType(AccountID);
|
|
||||||
const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97;
|
const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97;
|
||||||
|
|
||||||
|
mantissa[0] = 0;
|
||||||
mantissa[1] &= 0x3f;
|
mantissa[1] &= 0x3f;
|
||||||
// decimal.js won't accept e notation with hex
|
const value = new Decimal(`${sign}0x${mantissa.toString("hex")}`).times(
|
||||||
const value = new Decimal(`${sign}0x${bytesToHex(mantissa)}`).times(
|
`1e${exponent}`
|
||||||
"1e" + exponent
|
|
||||||
);
|
);
|
||||||
return new this(value, currency, issuer, false);
|
Amount.assertIouIsValid(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
issuer: issuer.toJSON(),
|
||||||
|
currency: currency.toJSON(),
|
||||||
|
value: value.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mantissa[0] &= 0x3f;
|
/**
|
||||||
const drops = new Decimal(`${sign}0x${bytesToHex(mantissa)}`);
|
* Validate XRP amount
|
||||||
const xrpValue = drops.dividedBy(DROPS_PER_XRP);
|
*
|
||||||
return new this(xrpValue, Currency.XRP, null, false);
|
* @param amount String representing XRP amount
|
||||||
},
|
* @returns void, but will throw if invalid amount
|
||||||
},
|
*/
|
||||||
assertValueIsValid() {
|
private static assertXrpIsValid(amount: string): void {
|
||||||
// zero is always a valid amount value
|
if (amount.indexOf(".") !== -1) {
|
||||||
if (!this.isZero()) {
|
throw new Error("XRP amounts must be integer");
|
||||||
if (this.isNative()) {
|
|
||||||
const abs = this.value.abs();
|
|
||||||
if (abs.lt(MIN_XRP) || abs.gt(MAX_XRP)) {
|
|
||||||
// value is in XRP scale, but show the value in canonical json form
|
|
||||||
raiseIllegalAmountError(this.value.times(DROPS_PER_XRP));
|
|
||||||
}
|
}
|
||||||
this.verifyNoDecimal(this.value); // This is a secondary fix for #31
|
|
||||||
} else {
|
const decimal = new Decimal(amount);
|
||||||
const p = this.value.precision();
|
if (!decimal.isZero()) {
|
||||||
const e = this.exponent();
|
if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) {
|
||||||
|
throw new Error("Invalid XRP amount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate IOU.value amount
|
||||||
|
*
|
||||||
|
* @param decimal Decimal.js object representing IOU.value
|
||||||
|
* @returns void, but will throw if invalid amount
|
||||||
|
*/
|
||||||
|
private static assertIouIsValid(decimal: Decimal): void {
|
||||||
|
if (!decimal.isZero()) {
|
||||||
|
const p = decimal.precision();
|
||||||
|
const e = decimal.e - 15;
|
||||||
if (
|
if (
|
||||||
p > MAX_IOU_PRECISION ||
|
p > MAX_IOU_PRECISION ||
|
||||||
e > MAX_IOU_EXPONENT ||
|
e > MAX_IOU_EXPONENT ||
|
||||||
e < MIN_IOU_EXPONENT
|
e < MIN_IOU_EXPONENT
|
||||||
) {
|
) {
|
||||||
raiseIllegalAmountError(this.value);
|
throw new Error("Decimal precision out of range");
|
||||||
|
}
|
||||||
|
this.verifyNoDecimal(decimal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
isNative() {
|
|
||||||
return this.currency.isNative();
|
|
||||||
},
|
|
||||||
mantissa() {
|
|
||||||
// This is a tertiary fix for #31
|
|
||||||
const integerNumberString = this.verifyNoDecimal();
|
|
||||||
|
|
||||||
return UInt64.from(BigInt(integerNumberString));
|
/**
|
||||||
},
|
* Ensure that the value after being multiplied by the exponent does not contain a decimal.
|
||||||
verifyNoDecimal() {
|
*
|
||||||
const integerNumberString = this.value
|
* @param decimal a Decimal object
|
||||||
.times("1e" + -this.exponent())
|
* @returns a string of the object without a decimal
|
||||||
|
*/
|
||||||
|
private static verifyNoDecimal(decimal: Decimal): void {
|
||||||
|
const integerNumberString = decimal
|
||||||
|
.times(`1e${-(decimal.e - 15)}`)
|
||||||
.abs()
|
.abs()
|
||||||
.toString();
|
.toString();
|
||||||
// Ensure that the value (after being multiplied by the exponent)
|
|
||||||
// does not contain a decimal. From the bn.js README:
|
|
||||||
// "decimals are not supported in this library."
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
// https://github.com/indutny/bn.js/blob/9cb459f044853b46615464eea1a3ddfc7006463b/README.md
|
|
||||||
if (integerNumberString.indexOf(".") !== -1) {
|
|
||||||
raiseIllegalAmountError(integerNumberString);
|
|
||||||
}
|
|
||||||
return integerNumberString;
|
|
||||||
},
|
|
||||||
isZero() {
|
|
||||||
return this.value.isZero();
|
|
||||||
},
|
|
||||||
exponent() {
|
|
||||||
return this.isNative() ? -6 : this.value.e - 15;
|
|
||||||
},
|
|
||||||
valueString() {
|
|
||||||
return (this.isNative()
|
|
||||||
? this.value.times(DROPS_PER_XRP)
|
|
||||||
: this.value
|
|
||||||
).toString();
|
|
||||||
},
|
|
||||||
toBytesSink(sink) {
|
|
||||||
const isNative = this.isNative();
|
|
||||||
const notNegative = !this.value.isNegative();
|
|
||||||
const mantissa = this.mantissa().toBytes();
|
|
||||||
|
|
||||||
if (isNative) {
|
if (integerNumberString.indexOf(".") !== -1) {
|
||||||
mantissa[0] |= notNegative ? 0x40 : 0;
|
throw new Error("Decimal place found in integerNumberString");
|
||||||
sink.put(mantissa);
|
|
||||||
} else {
|
|
||||||
mantissa[0] |= 0x80;
|
|
||||||
if (!this.isZero()) {
|
|
||||||
if (notNegative) {
|
|
||||||
mantissa[0] |= 0x40;
|
|
||||||
}
|
}
|
||||||
const exponent = this.value.e - 15;
|
|
||||||
const exponentByte = 97 + exponent;
|
|
||||||
mantissa[0] |= exponentByte >>> 2;
|
|
||||||
mantissa[1] |= (exponentByte & 0x03) << 6;
|
|
||||||
}
|
}
|
||||||
sink.put(mantissa);
|
|
||||||
this.currency.toBytesSink(sink);
|
/**
|
||||||
this.issuer.toBytesSink(sink);
|
* Test if this amount is in units of Native Currency(XRP)
|
||||||
|
*
|
||||||
|
* @returns true if Native (XRP)
|
||||||
|
*/
|
||||||
|
private isNative(): boolean {
|
||||||
|
return (this.bytes[0] & 0x80) === 0;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
toJSON() {
|
|
||||||
const valueString = this.valueString();
|
|
||||||
if (this.isNative()) {
|
|
||||||
return valueString;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
value: valueString,
|
|
||||||
currency: this.currency.toJSON(),
|
|
||||||
issuer: this.issuer.toJSON(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
export { Amount };
|
export { Amount };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { SerializedTypeClass } from "./serialized-type";
|
import { SerializedType } from "./serialized-type";
|
||||||
import { BinaryParser } from "../serdes/binary-parser";
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable length encoded type
|
* Variable length encoded type
|
||||||
*/
|
*/
|
||||||
class Blob extends SerializedTypeClass {
|
class Blob extends SerializedType {
|
||||||
constructor(bytes: Buffer) {
|
constructor(bytes: Buffer) {
|
||||||
super(bytes);
|
super(bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ComparableClass } from "./serialized-type";
|
import { Comparable } from "./serialized-type";
|
||||||
import { BinaryParser } from "../serdes/binary-parser";
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class defining how to encode and decode hashes
|
* Base class defining how to encode and decode hashes
|
||||||
*/
|
*/
|
||||||
class Hash extends ComparableClass {
|
class Hash extends Comparable {
|
||||||
static readonly width: number;
|
static readonly width: number;
|
||||||
|
|
||||||
constructor(bytes: Buffer) {
|
constructor(bytes: Buffer) {
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ import {
|
|||||||
import { AccountID } from "./account-id";
|
import { AccountID } from "./account-id";
|
||||||
import { Amount } from "./amount";
|
import { Amount } from "./amount";
|
||||||
import { Blob } from "./blob";
|
import { Blob } from "./blob";
|
||||||
const { Currency } = require("./currency");
|
import { Currency } from "./currency";
|
||||||
const { Hash128 } = require("./hash-128");
|
import { Hash128 } from "./hash-128";
|
||||||
const { Hash160 } = require("./hash-160");
|
import { Hash160 } from "./hash-160";
|
||||||
const { Hash256 } = require("./hash-256");
|
import { Hash256 } from "./hash-256";
|
||||||
const { PathSet } = require("./path-set");
|
import { PathSet } from "./path-set";
|
||||||
const { STArray } = require("./st-array");
|
import { STArray } from "./st-array";
|
||||||
const { STObject } = require("./st-object");
|
import { STObject } from "./st-object";
|
||||||
const { UInt16 } = require("./uint-16");
|
import { UInt16 } from "./uint-16";
|
||||||
const { UInt32 } = require("./uint-32");
|
import { UInt32 } from "./uint-32";
|
||||||
const { UInt64 } = require("./uint-64");
|
import { UInt64 } from "./uint-64";
|
||||||
const { UInt8 } = require("./uint-8");
|
import { UInt8 } from "./uint-8";
|
||||||
const { Vector256 } = require("./vector-256");
|
import { Vector256 } from "./vector-256";
|
||||||
|
|
||||||
const coreTypes = {
|
const coreTypes = {
|
||||||
AccountID,
|
AccountID,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AccountID } from "./account-id";
|
import { AccountID } from "./account-id";
|
||||||
import { Currency } from "./currency";
|
import { Currency } from "./currency";
|
||||||
import { BinaryParser } from "../serdes/binary-parser";
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
import { SerializedTypeClass } from "./serialized-type";
|
import { SerializedType } from "./serialized-type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants for separating Paths in a PathSet
|
* Constants for separating Paths in a PathSet
|
||||||
@@ -28,7 +28,7 @@ interface HopObject {
|
|||||||
/**
|
/**
|
||||||
* Serialize and Deserialize a Hop
|
* Serialize and Deserialize a Hop
|
||||||
*/
|
*/
|
||||||
class Hop extends SerializedTypeClass {
|
class Hop extends SerializedType {
|
||||||
/**
|
/**
|
||||||
* Create a Hop from a HopObject
|
* Create a Hop from a HopObject
|
||||||
*
|
*
|
||||||
@@ -123,7 +123,7 @@ class Hop extends SerializedTypeClass {
|
|||||||
/**
|
/**
|
||||||
* Class for serializing/deserializing Paths
|
* Class for serializing/deserializing Paths
|
||||||
*/
|
*/
|
||||||
class Path extends SerializedTypeClass {
|
class Path extends SerializedType {
|
||||||
/**
|
/**
|
||||||
* construct a Path from an array of Hops
|
* construct a Path from an array of Hops
|
||||||
*
|
*
|
||||||
@@ -184,7 +184,7 @@ class Path extends SerializedTypeClass {
|
|||||||
/**
|
/**
|
||||||
* Deserialize and Serialize the PathSet type
|
* Deserialize and Serialize the PathSet type
|
||||||
*/
|
*/
|
||||||
class PathSet extends SerializedTypeClass {
|
class PathSet extends SerializedType {
|
||||||
/**
|
/**
|
||||||
* Construct a PathSet from an Array of Arrays representing paths
|
* Construct a PathSet from an Array of Arrays representing paths
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
import { BytesList } from "../serdes/binary-serializer";
|
import { BytesList } from "../serdes/binary-serializer";
|
||||||
const { bytesToHex, slice } = require("../utils/bytes-utils");
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for all binary-codec types
|
* The base class for all binary-codec types
|
||||||
*/
|
*/
|
||||||
class SerializedTypeClass {
|
class SerializedType {
|
||||||
protected readonly bytes: Buffer = Buffer.alloc(0);
|
protected readonly bytes: Buffer = Buffer.alloc(0);
|
||||||
|
|
||||||
constructor(bytes: Buffer) {
|
constructor(bytes: Buffer) {
|
||||||
this.bytes = bytes ?? Buffer.alloc(0);
|
this.bytes = bytes ?? Buffer.alloc(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromParser(parser: BinaryParser, hint?: number): SerializedType {
|
||||||
|
throw new Error("fromParser not implemented");
|
||||||
|
return this.fromParser(parser, hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
static from(value: any): SerializedType {
|
||||||
|
throw new Error("from not implemented");
|
||||||
|
return this.from(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the bytes representation of a SerializedType to a BytesList
|
* Write the bytes representation of a SerializedType to a BytesList
|
||||||
*
|
*
|
||||||
@@ -63,24 +73,24 @@ class SerializedTypeClass {
|
|||||||
/**
|
/**
|
||||||
* Base class for SerializedTypes that are comparable
|
* Base class for SerializedTypes that are comparable
|
||||||
*/
|
*/
|
||||||
class ComparableClass extends SerializedTypeClass {
|
class Comparable extends SerializedType {
|
||||||
lt(other: ComparableClass): boolean {
|
lt(other: Comparable): boolean {
|
||||||
return this.compareTo(other) < 0;
|
return this.compareTo(other) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
eq(other: ComparableClass): boolean {
|
eq(other: Comparable): boolean {
|
||||||
return this.compareTo(other) === 0;
|
return this.compareTo(other) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
gt(other: ComparableClass): boolean {
|
gt(other: Comparable): boolean {
|
||||||
return this.compareTo(other) > 0;
|
return this.compareTo(other) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
gte(other: ComparableClass): boolean {
|
gte(other: Comparable): boolean {
|
||||||
return this.compareTo(other) > -1;
|
return this.compareTo(other) > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
lte(other: ComparableClass): boolean {
|
lte(other: Comparable): boolean {
|
||||||
return this.compareTo(other) < 1;
|
return this.compareTo(other) < 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,71 +100,9 @@ class ComparableClass extends SerializedTypeClass {
|
|||||||
* @param other The comparable object to compare this to
|
* @param other The comparable object to compare this to
|
||||||
* @returns A number denoting the relationship of this and other
|
* @returns A number denoting the relationship of this and other
|
||||||
*/
|
*/
|
||||||
compareTo(other: ComparableClass): number {
|
compareTo(other: Comparable): number {
|
||||||
throw new Error("cannot compare " + this + " and " + other);
|
throw new Error(`cannot compare ${this} and ${other}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Comparable = {
|
export { SerializedType, Comparable };
|
||||||
lt(other) {
|
|
||||||
return this.compareTo(other) < 0;
|
|
||||||
},
|
|
||||||
eq(other) {
|
|
||||||
return this.compareTo(other) === 0;
|
|
||||||
},
|
|
||||||
gt(other) {
|
|
||||||
return this.compareTo(other) > 0;
|
|
||||||
},
|
|
||||||
gte(other) {
|
|
||||||
return this.compareTo(other) > -1;
|
|
||||||
},
|
|
||||||
lte(other) {
|
|
||||||
return this.compareTo(other) < 1;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const SerializedType = {
|
|
||||||
toBytesSink(sink) {
|
|
||||||
sink.put(this._bytes);
|
|
||||||
},
|
|
||||||
toHex() {
|
|
||||||
return bytesToHex(this.toBytes());
|
|
||||||
},
|
|
||||||
toBytes() {
|
|
||||||
if (this._bytes) {
|
|
||||||
return slice(this._bytes);
|
|
||||||
}
|
|
||||||
const bl = new BytesList();
|
|
||||||
this.toBytesSink(bl);
|
|
||||||
return bl.toBytes();
|
|
||||||
},
|
|
||||||
toJSON() {
|
|
||||||
return this.toHex();
|
|
||||||
},
|
|
||||||
toString() {
|
|
||||||
return this.toHex();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function ensureArrayLikeIs(Type, arrayLike) {
|
|
||||||
return {
|
|
||||||
withChildren(Child) {
|
|
||||||
if (arrayLike instanceof Type) {
|
|
||||||
return arrayLike;
|
|
||||||
}
|
|
||||||
const obj = new Type();
|
|
||||||
for (let i = 0; i < arrayLike.length; i++) {
|
|
||||||
obj.push(Child.from(arrayLike[i]));
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
ensureArrayLikeIs,
|
|
||||||
SerializedType,
|
|
||||||
SerializedTypeClass,
|
|
||||||
Comparable,
|
|
||||||
ComparableClass,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { SerializedTypeClass } from "./serialized-type";
|
import { SerializedType } from "./serialized-type";
|
||||||
import { STObject } from "./st-object";
|
import { STObject } from "./st-object";
|
||||||
import { BinaryParser } from "../serdes/binary-parser";
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
|
|
||||||
const ARRAY_END_MARKER = 0xf1;
|
const ARRAY_END_MARKER = Buffer.from([0xf1]);
|
||||||
const OBJECT_END_MARKER = 0xe1;
|
const ARRAY_END_MARKER_NAME = "ArrayEndMarker";
|
||||||
|
|
||||||
|
const OBJECT_END_MARKER = Buffer.from([0xe1]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for serializing and deserializing Arrays of Objects
|
* Class for serializing and deserializing Arrays of Objects
|
||||||
*/
|
*/
|
||||||
class STArray extends SerializedTypeClass {
|
class STArray extends SerializedType {
|
||||||
/**
|
/**
|
||||||
* Construct an STArray from a BinaryParser
|
* Construct an STArray from a BinaryParser
|
||||||
*
|
*
|
||||||
@@ -20,18 +22,18 @@ class STArray extends SerializedTypeClass {
|
|||||||
|
|
||||||
while (!parser.end()) {
|
while (!parser.end()) {
|
||||||
const field = parser.readField();
|
const field = parser.readField();
|
||||||
if (field.name === "ArrayEndMarker") {
|
if (field.name === ARRAY_END_MARKER_NAME) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes.push(
|
bytes.push(
|
||||||
field.header,
|
field.header,
|
||||||
parser.readFieldValue(field).toBytes(),
|
parser.readFieldValue(field).toBytes(),
|
||||||
Buffer.from([OBJECT_END_MARKER])
|
OBJECT_END_MARKER
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes.push(Buffer.from([ARRAY_END_MARKER]));
|
bytes.push(ARRAY_END_MARKER);
|
||||||
return new STArray(Buffer.concat(bytes));
|
return new STArray(Buffer.concat(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ class STArray extends SerializedTypeClass {
|
|||||||
bytes.push(STObject.from(obj).toBytes());
|
bytes.push(STObject.from(obj).toBytes());
|
||||||
});
|
});
|
||||||
|
|
||||||
bytes.push(Buffer.from([ARRAY_END_MARKER]));
|
bytes.push(ARRAY_END_MARKER);
|
||||||
return new STArray(Buffer.concat(bytes));
|
return new STArray(Buffer.concat(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ class STArray extends SerializedTypeClass {
|
|||||||
|
|
||||||
while (!arrayParser.end()) {
|
while (!arrayParser.end()) {
|
||||||
const field = arrayParser.readField();
|
const field = arrayParser.readField();
|
||||||
if (field.name === "ArrayEndMarker") {
|
if (field.name === ARRAY_END_MARKER_NAME) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,79 +1,103 @@
|
|||||||
import { makeClass } from "../utils/make-class";
|
|
||||||
import { Field } from "../enums";
|
import { Field } from "../enums";
|
||||||
const _ = require("lodash");
|
import { SerializedType } from "./serialized-type";
|
||||||
const { BinarySerializer } = require("../serdes/binary-serializer");
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
const { SerializedType } = require("./serialized-type");
|
import { BinarySerializer, BytesList } from "../serdes/binary-serializer";
|
||||||
|
|
||||||
const STObject = makeClass(
|
const OBJECT_END_MARKER = Buffer.from([0xe1]);
|
||||||
{
|
const OBJECT_END_MARKER_NAME = "ObjectEndMarker";
|
||||||
mixins: SerializedType,
|
const OBJECT_FIELD_TYPE_NAME = "STObject";
|
||||||
statics: {
|
|
||||||
fromParser(parser, hint) {
|
/**
|
||||||
const end = typeof hint === "number" ? parser.pos() + hint : null;
|
* Class for Serializing/Deserializing objects
|
||||||
const so = new this();
|
*/
|
||||||
while (!parser.end(end)) {
|
class STObject extends SerializedType {
|
||||||
|
/**
|
||||||
|
* Construct a STObject from a BinaryParser
|
||||||
|
*
|
||||||
|
* @param parser BinaryParser to read STObject from
|
||||||
|
* @returns A STObject object
|
||||||
|
*/
|
||||||
|
static fromParser(parser: BinaryParser): STObject {
|
||||||
|
const list: BytesList = new BytesList();
|
||||||
|
const bytes: BinarySerializer = new BinarySerializer(list);
|
||||||
|
|
||||||
|
while (!parser.end()) {
|
||||||
const field = parser.readField();
|
const field = parser.readField();
|
||||||
if (field.name === "ObjectEndMarker") {
|
if (field.name === OBJECT_END_MARKER_NAME) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
so[field.name] = parser.readFieldValue(field);
|
|
||||||
|
const associatedValue = parser.readFieldValue(field);
|
||||||
|
|
||||||
|
bytes.writeFieldAndValue(field, associatedValue);
|
||||||
|
if (field.type.name === OBJECT_FIELD_TYPE_NAME) {
|
||||||
|
bytes.put(OBJECT_END_MARKER);
|
||||||
}
|
}
|
||||||
return so;
|
}
|
||||||
},
|
|
||||||
from(value) {
|
return new STObject(list.toBytes());
|
||||||
if (value instanceof this) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a STObject from a JSON object
|
||||||
|
*
|
||||||
|
* @param value An object to include
|
||||||
|
* @param filter optional, denote which field to include in serialized object
|
||||||
|
* @returns a STObject object
|
||||||
|
*/
|
||||||
|
static from(
|
||||||
|
value: STObject | object,
|
||||||
|
filter?: (...any) => boolean
|
||||||
|
): STObject {
|
||||||
|
if (value instanceof STObject) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (typeof value === "object") {
|
|
||||||
return _.transform(
|
const list: BytesList = new BytesList();
|
||||||
value,
|
const bytes: BinarySerializer = new BinarySerializer(list);
|
||||||
(so, val, key) => {
|
|
||||||
const field = Field[key];
|
let sorted = Object.keys(value)
|
||||||
if (field) {
|
.map((f) => Field[f])
|
||||||
so[field.name] = field.associatedType.from(val);
|
.filter((f) => f !== undefined && f.isSerialized)
|
||||||
} else {
|
.sort((a, b) => {
|
||||||
so[key] = val;
|
return a.ordinal - b.ordinal;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filter !== undefined) {
|
||||||
|
sorted = sorted.filter(filter);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
new this()
|
sorted.forEach((field) => {
|
||||||
);
|
const associatedValue = field.associatedType.from(value[field.name]);
|
||||||
}
|
|
||||||
throw new Error(`${value} is unsupported`);
|
bytes.writeFieldAndValue(field, associatedValue);
|
||||||
},
|
if (field.type.name === OBJECT_FIELD_TYPE_NAME) {
|
||||||
},
|
bytes.put(OBJECT_END_MARKER);
|
||||||
fieldKeys() {
|
|
||||||
return Object.keys(this)
|
|
||||||
.map((k) => Field[k])
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
toJSON() {
|
|
||||||
// Otherwise seemingly result will have same prototype as `this`
|
|
||||||
const accumulator = {}; // of only `own` properties
|
|
||||||
return _.transform(
|
|
||||||
this,
|
|
||||||
(result, value, key) => {
|
|
||||||
result[key] = value && value.toJSON ? value.toJSON() : value;
|
|
||||||
},
|
|
||||||
accumulator
|
|
||||||
);
|
|
||||||
},
|
|
||||||
toBytesSink(sink, filter = () => true) {
|
|
||||||
const serializer = new BinarySerializer(sink);
|
|
||||||
const fields = this.fieldKeys();
|
|
||||||
const sorted = _.sortBy(fields, "ordinal");
|
|
||||||
sorted.filter(filter).forEach((field) => {
|
|
||||||
const value = this[field.name];
|
|
||||||
if (!field.isSerialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
serializer.writeFieldAndValue(field, value);
|
|
||||||
if (field.type.name === "STObject") {
|
|
||||||
serializer.put(Buffer.from([0xe1]));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
},
|
return new STObject(list.toBytes());
|
||||||
undefined
|
}
|
||||||
);
|
|
||||||
|
/**
|
||||||
|
* Get the JSON interpretation of this.bytes
|
||||||
|
*
|
||||||
|
* @returns a JSON object
|
||||||
|
*/
|
||||||
|
toJSON(): object {
|
||||||
|
const objectParser = new BinaryParser(this.toString());
|
||||||
|
const accumulator = {};
|
||||||
|
|
||||||
|
while (!objectParser.end()) {
|
||||||
|
const field = objectParser.readField();
|
||||||
|
if (field.name === OBJECT_END_MARKER_NAME) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
accumulator[field.name] = objectParser.readFieldValue(field).toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export { STObject };
|
export { STObject };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComparableClass } from "./serialized-type";
|
import { Comparable } from "./serialized-type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare numbers and bigints n1 and n2
|
* Compare numbers and bigints n1 and n2
|
||||||
@@ -14,7 +14,7 @@ function compare(n1: number | bigint, n2: number | bigint): number {
|
|||||||
/**
|
/**
|
||||||
* Base class for serializing and deserializing unsigned integers.
|
* Base class for serializing and deserializing unsigned integers.
|
||||||
*/
|
*/
|
||||||
abstract class UInt extends ComparableClass {
|
abstract class UInt extends Comparable {
|
||||||
protected static width: number;
|
protected static width: number;
|
||||||
|
|
||||||
constructor(bytes: Buffer) {
|
constructor(bytes: Buffer) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SerializedTypeClass } from "./serialized-type";
|
import { SerializedType } from "./serialized-type";
|
||||||
import { BinaryParser } from "../serdes/binary-parser";
|
import { BinaryParser } from "../serdes/binary-parser";
|
||||||
import { Hash256 } from "./hash-256";
|
import { Hash256 } from "./hash-256";
|
||||||
import { BytesList } from "../serdes/binary-serializer";
|
import { BytesList } from "../serdes/binary-serializer";
|
||||||
@@ -6,7 +6,7 @@ import { BytesList } from "../serdes/binary-serializer";
|
|||||||
/**
|
/**
|
||||||
* Class for serializing and deserializing vectors of Hash256
|
* Class for serializing and deserializing vectors of Hash256
|
||||||
*/
|
*/
|
||||||
class Vector256 extends SerializedTypeClass {
|
class Vector256 extends SerializedType {
|
||||||
constructor(bytes: Buffer) {
|
constructor(bytes: Buffer) {
|
||||||
super(bytes);
|
super(bytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function amountErrorTests () {
|
|||||||
describe('Amount', function () {
|
describe('Amount', function () {
|
||||||
it('can be parsed from', function () {
|
it('can be parsed from', function () {
|
||||||
expect(Amount.from('1000000') instanceof Amount).toBe(true)
|
expect(Amount.from('1000000') instanceof Amount).toBe(true)
|
||||||
expect(Amount.from('1000000').valueString()).toEqual('1000000')
|
expect(Amount.from('1000000').toJSON()).toEqual('1000000')
|
||||||
const fixture = {
|
const fixture = {
|
||||||
value: '1',
|
value: '1',
|
||||||
issuer: '0000000000000000000000000000000000000000',
|
issuer: '0000000000000000000000000000000000000000',
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ describe('ripple-binary-codec', function () {
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
test(`${name}[${testN}] can encode ${truncateForDisplay(json(t.json))} to ${truncateForDisplay(t.binary)}`,
|
test(`${name}[${testN}] can encode ${truncateForDisplay(json(t.json))} to ${truncateForDisplay(t.binary)}`,
|
||||||
() => {
|
() => {
|
||||||
expect(t.binary).toEqual(encode(t.json))
|
expect(encode(t.json)).toEqual(t.binary)
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
test(`${name}[${testN}] can decode ${truncateForDisplay(t.binary)} to ${truncateForDisplay(json(t.json))}`,
|
test(`${name}[${testN}] can decode ${truncateForDisplay(t.binary)} to ${truncateForDisplay(json(t.json))}`,
|
||||||
() => {
|
() => {
|
||||||
const decoded = decode(t.binary)
|
const decoded = decode(t.binary)
|
||||||
expect(t.json).toEqual(decoded)
|
expect(decoded).toEqual(t.json)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -138,19 +138,19 @@ function transactionParsingTests () {
|
|||||||
{
|
{
|
||||||
const [field, value] = readField()
|
const [field, value] = readField()
|
||||||
expect(field).toEqual(Field.TakerPays)
|
expect(field).toEqual(Field.TakerPays)
|
||||||
expect(value.currency.isNative()).toEqual(true)
|
expect(value.isNative()).toEqual(true)
|
||||||
expect(value.currency.toJSON()).toEqual('XRP')
|
expect(value.toJSON()).toEqual('98957503520')
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const [field, value] = readField()
|
const [field, value] = readField()
|
||||||
expect(field).toEqual(Field.TakerGets)
|
expect(field).toEqual(Field.TakerGets)
|
||||||
expect(value.currency.isNative()).toEqual(false)
|
expect(value.isNative()).toEqual(false)
|
||||||
expect(value.issuer.toJSON()).toEqual(tx_json.TakerGets.issuer)
|
expect(value.toJSON().issuer).toEqual(tx_json.TakerGets.issuer)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const [field, value] = readField()
|
const [field, value] = readField()
|
||||||
expect(field).toEqual(Field.Fee)
|
expect(field).toEqual(Field.Fee)
|
||||||
expect(value.currency.isNative()).toEqual(true)
|
expect(value.isNative()).toEqual(true)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const [field, value] = readField()
|
const [field, value] = readField()
|
||||||
@@ -197,9 +197,11 @@ function amountParsingTests () {
|
|||||||
const value = parser.readType(Amount)
|
const value = parser.readType(Amount)
|
||||||
// May not actually be in canonical form. The fixtures are to be used
|
// May not actually be in canonical form. The fixtures are to be used
|
||||||
// also for json -> binary;
|
// also for json -> binary;
|
||||||
assertEqualAmountJSON(toJSON(value), (f.test_json))
|
const json = toJSON(value)
|
||||||
|
assertEqualAmountJSON(json, (f.test_json))
|
||||||
if (f.exponent) {
|
if (f.exponent) {
|
||||||
expect(value.exponent()).toEqual(f.exponent)
|
const exponent = new Decimal(json.value);
|
||||||
|
expect(exponent.e-15).toEqual(f.exponent)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -65,10 +65,7 @@ describe('encoding and decoding tx_json', function () {
|
|||||||
})
|
})
|
||||||
expect(() => {
|
expect(() => {
|
||||||
encode(my_tx)
|
encode(my_tx)
|
||||||
}).toThrow({
|
}).toThrow()
|
||||||
name: 'Error',
|
|
||||||
message: amount_parameters_message('1000.001')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
test('throws when Fee is invalid', function () {
|
test('throws when Fee is invalid', function () {
|
||||||
const my_tx = Object.assign({}, tx_json, {
|
const my_tx = Object.assign({}, tx_json, {
|
||||||
@@ -77,10 +74,7 @@ describe('encoding and decoding tx_json', function () {
|
|||||||
})
|
})
|
||||||
expect(() => {
|
expect(() => {
|
||||||
encode(my_tx)
|
encode(my_tx)
|
||||||
}).toThrow({
|
}).toThrow()
|
||||||
name: 'Error',
|
|
||||||
message: amount_parameters_message('10.123')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
test('throws when Amount and Fee are invalid', function () {
|
test('throws when Amount and Fee are invalid', function () {
|
||||||
const my_tx = Object.assign({}, tx_json, {
|
const my_tx = Object.assign({}, tx_json, {
|
||||||
@@ -89,10 +83,7 @@ describe('encoding and decoding tx_json', function () {
|
|||||||
})
|
})
|
||||||
expect(() => {
|
expect(() => {
|
||||||
encode(my_tx)
|
encode(my_tx)
|
||||||
}).toThrow({
|
}).toThrow()
|
||||||
name: 'Error',
|
|
||||||
message: amount_parameters_message('1000.789')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
test('throws when Amount is a number instead of a string-encoded integer',
|
test('throws when Amount is a number instead of a string-encoded integer',
|
||||||
function () {
|
function () {
|
||||||
@@ -101,11 +92,9 @@ describe('encoding and decoding tx_json', function () {
|
|||||||
})
|
})
|
||||||
expect(() => {
|
expect(() => {
|
||||||
encode(my_tx)
|
encode(my_tx)
|
||||||
}).toThrow({
|
}).toThrow()
|
||||||
name: 'Error',
|
|
||||||
message: 'unsupported value: 1000.789'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('throws when Fee is a number instead of a string-encoded integer',
|
test('throws when Fee is a number instead of a string-encoded integer',
|
||||||
function () {
|
function () {
|
||||||
const my_tx = Object.assign({}, tx_json, {
|
const my_tx = Object.assign({}, tx_json, {
|
||||||
@@ -113,9 +102,6 @@ describe('encoding and decoding tx_json', function () {
|
|||||||
})
|
})
|
||||||
expect(() => {
|
expect(() => {
|
||||||
encode(my_tx)
|
encode(my_tx)
|
||||||
}).toThrow({
|
}).toThrow()
|
||||||
name: 'Error',
|
|
||||||
message: 'unsupported value: 1234.56'
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('SerializedType interfaces', () => {
|
|||||||
expect(Value.from(newJSON).toJSON()).toEqual(newJSON)
|
expect(Value.from(newJSON).toJSON()).toEqual(newJSON)
|
||||||
})
|
})
|
||||||
describe(`${name} supports all methods of the SerializedType mixin`, () => {
|
describe(`${name} supports all methods of the SerializedType mixin`, () => {
|
||||||
_.keys(SerializedType).forEach(k => {
|
_.keys(SerializedType.prototype).forEach(k => {
|
||||||
test(`new ${name}.prototype.${k} !== undefined`, () => {
|
test(`new ${name}.prototype.${k} !== undefined`, () => {
|
||||||
expect(Value.prototype[k]).not.toBe(undefined)
|
expect(Value.prototype[k]).not.toBe(undefined)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user