2022-09-05 23:14:43 +03:00
|
|
|
|
2022-09-09 06:21:08 +03:00
|
|
|
import {
|
|
|
|
defineProperties, concat, getBytesCopy, getNumber, hexlify,
|
2022-12-10 02:21:45 +03:00
|
|
|
toBeArray, toBigInt, toNumber,
|
2023-01-26 19:32:56 +03:00
|
|
|
assert, assertArgument
|
2022-09-09 06:21:08 +03:00
|
|
|
} from "../../utils/index.js";
|
2022-09-05 23:14:43 +03:00
|
|
|
|
|
|
|
import type { BigNumberish, BytesLike } from "../../utils/index.js";
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
/**
|
|
|
|
* @_ignore:
|
|
|
|
*/
|
2022-11-28 05:54:49 +03:00
|
|
|
export const WordSize: number = 32;
|
2022-09-05 23:14:43 +03:00
|
|
|
const Padding = new Uint8Array(WordSize);
|
|
|
|
|
|
|
|
// Properties used to immediate pass through to the underlying object
|
|
|
|
// - `then` is used to detect if an object is a Promise for await
|
|
|
|
const passProperties = [ "then" ];
|
|
|
|
|
|
|
|
const _guard = { };
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
function throwError(name: string, error: Error): never {
|
|
|
|
const wrapped = new Error(`deferred error during ABI decoding triggered accessing ${ name }`);
|
|
|
|
(<any>wrapped).error = error;
|
|
|
|
throw wrapped;
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* A [[Result]] is a sub-class of Array, which allows accessing any
|
|
|
|
* of its values either positionally by its index or, if keys are
|
|
|
|
* provided by its name.
|
|
|
|
*
|
|
|
|
* @_docloc: api/abi
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
export class Result extends Array<any> {
|
2023-01-26 19:32:56 +03:00
|
|
|
readonly #names: ReadonlyArray<null | string>;
|
2022-09-05 23:14:43 +03:00
|
|
|
|
|
|
|
[ K: string | number ]: any
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
*/
|
2023-01-26 19:32:56 +03:00
|
|
|
constructor(...args: Array<any>) {
|
|
|
|
// To properly sub-class Array so the other built-in
|
|
|
|
// functions work, the constructor has to behave fairly
|
|
|
|
// well. So, in the event we are created via fromItems()
|
|
|
|
// we build the read-only Result object we want, but on
|
|
|
|
// any other input, we use the default constructor
|
|
|
|
|
|
|
|
// constructor(guard: any, items: Array<any>, keys?: Array<null | string>);
|
|
|
|
const guard = args[0];
|
|
|
|
let items: Array<any> = args[1];
|
|
|
|
let names: Array<null | string> = (args[2] || [ ]).slice();
|
|
|
|
|
|
|
|
let wrap = true;
|
|
|
|
if (guard !== _guard) {
|
|
|
|
items = args;
|
|
|
|
names = [ ];
|
|
|
|
wrap = false;
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
2023-01-26 19:32:56 +03:00
|
|
|
|
|
|
|
// Can't just pass in ...items since an array of length 1
|
|
|
|
// is a special case in the super.
|
|
|
|
super(items.length);
|
|
|
|
items.forEach((item, index) => { this[index] = item; });
|
|
|
|
|
|
|
|
// Find all unique keys
|
|
|
|
const nameCounts = names.reduce((accum, name) => {
|
|
|
|
if (typeof(name) === "string") {
|
|
|
|
accum.set(name, (accum.get(name) || 0) + 1);
|
|
|
|
}
|
|
|
|
return accum;
|
|
|
|
}, <Map<string, number>>(new Map()));
|
|
|
|
|
|
|
|
// Remove any key thats not unique
|
|
|
|
this.#names = Object.freeze(items.map((item, index) => {
|
|
|
|
const name = names[index];
|
|
|
|
if (name != null && nameCounts.get(name) === 1) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (!wrap) { return; }
|
|
|
|
|
|
|
|
// A wrapped Result is immutable
|
2022-09-05 23:14:43 +03:00
|
|
|
Object.freeze(this);
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
// Proxy indices and names so we can trap deferred errors
|
2022-09-05 23:14:43 +03:00
|
|
|
return new Proxy(this, {
|
|
|
|
get: (target, prop, receiver) => {
|
|
|
|
if (typeof(prop) === "string") {
|
2023-01-26 19:32:56 +03:00
|
|
|
|
|
|
|
// Index accessor
|
2022-09-05 23:14:43 +03:00
|
|
|
if (prop.match(/^[0-9]+$/)) {
|
2022-09-09 06:21:08 +03:00
|
|
|
const index = getNumber(prop, "%index");
|
2022-09-05 23:14:43 +03:00
|
|
|
if (index < 0 || index >= this.length) {
|
|
|
|
throw new RangeError("out of result range");
|
|
|
|
}
|
|
|
|
|
|
|
|
const item = target[index];
|
|
|
|
if (item instanceof Error) {
|
2023-01-26 19:32:56 +03:00
|
|
|
throwError(`index ${ index }`, item);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass important checks (like `then` for Promise) through
|
2023-01-26 19:32:56 +03:00
|
|
|
if (passProperties.indexOf(prop) >= 0) {
|
2022-09-05 23:14:43 +03:00
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
}
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
const value = target[prop];
|
|
|
|
if (value instanceof Function) {
|
|
|
|
// Make sure functions work with private variables
|
|
|
|
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy#no_private_property_forwarding
|
|
|
|
return function(this: any, ...args: Array<any>) {
|
|
|
|
return value.apply((this === receiver) ? target: this, args);
|
|
|
|
};
|
|
|
|
|
|
|
|
} else if (!(prop in target)) {
|
|
|
|
// Possible name accessor
|
|
|
|
return target.getValue.apply((this === receiver) ? target: this, [ prop ]);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Reflect.get(target, prop, receiver);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
/**
|
|
|
|
* Returns the Result as a normal Array.
|
|
|
|
*
|
|
|
|
* This will throw if there are any outstanding deferred
|
|
|
|
* errors.
|
|
|
|
*/
|
|
|
|
toArray(): Array<any> {
|
2023-02-19 05:42:01 +03:00
|
|
|
const result: Array<any> = [ ];
|
2023-01-26 19:32:56 +03:00
|
|
|
this.forEach((item, index) => {
|
2023-02-19 05:42:01 +03:00
|
|
|
if (item instanceof Error) { throwError(`index ${ index }`, item); }
|
|
|
|
result.push(item);
|
2023-01-26 19:32:56 +03:00
|
|
|
});
|
2023-02-19 05:42:01 +03:00
|
|
|
return result;
|
2023-01-26 19:32:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the Result as an Object with each name-value pair.
|
|
|
|
*
|
|
|
|
* This will throw if any value is unnamed, or if there are
|
|
|
|
* any outstanding deferred errors.
|
|
|
|
*/
|
|
|
|
toObject(): Record<string, any> {
|
|
|
|
return this.#names.reduce((accum, name, index) => {
|
|
|
|
assert(name != null, "value at index ${ index } unnamed", "UNSUPPORTED_OPERATION", {
|
|
|
|
operation: "toObject()"
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add values for names that don't conflict
|
|
|
|
if (!(name in accum)) {
|
|
|
|
accum[name] = this.getValue(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return accum;
|
|
|
|
}, <Record<string, any>>{});
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
2023-01-26 19:32:56 +03:00
|
|
|
slice(start?: number | undefined, end?: number | undefined): Result {
|
2022-09-05 23:14:43 +03:00
|
|
|
if (start == null) { start = 0; }
|
2023-02-19 05:42:01 +03:00
|
|
|
if (start < 0) {
|
|
|
|
start += this.length;
|
|
|
|
if (start < 0) { start = 0; }
|
|
|
|
}
|
|
|
|
|
2022-09-05 23:14:43 +03:00
|
|
|
if (end == null) { end = this.length; }
|
2023-02-19 05:42:01 +03:00
|
|
|
if (end < 0) {
|
|
|
|
end += this.length;
|
|
|
|
if (end < 0) { end = 0; }
|
|
|
|
}
|
|
|
|
if (end > this.length) { end = this.length; }
|
2022-09-05 23:14:43 +03:00
|
|
|
|
2023-08-15 03:00:45 +03:00
|
|
|
const result: Array<any> = [ ], names: Array<null | string> = [ ];
|
2022-09-05 23:14:43 +03:00
|
|
|
for (let i = start; i < end; i++) {
|
2023-01-26 19:32:56 +03:00
|
|
|
result.push(this[i]);
|
|
|
|
names.push(this.#names[i]);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
2023-01-26 19:32:56 +03:00
|
|
|
|
|
|
|
return new Result(_guard, result, names);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
|
|
|
filter(callback: (el: any, index: number, array: Result) => boolean, thisArg?: any): Result {
|
2023-08-15 03:00:45 +03:00
|
|
|
const result: Array<any> = [ ], names: Array<null | string> = [ ];
|
2023-01-26 19:32:56 +03:00
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
|
|
const item = this[i];
|
|
|
|
if (item instanceof Error) {
|
|
|
|
throwError(`index ${ i }`, item);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callback.call(thisArg, item, i, this)) {
|
|
|
|
result.push(item);
|
|
|
|
names.push(this.#names[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Result(_guard, result, names);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
2023-05-16 00:18:46 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
|
|
|
map<T extends any = any>(callback: (el: any, index: number, array: Result) => T, thisArg?: any): Array<T> {
|
|
|
|
const result: Array<T> = [ ];
|
|
|
|
for (let i = 0; i < this.length; i++) {
|
|
|
|
const item = this[i];
|
|
|
|
if (item instanceof Error) {
|
|
|
|
throwError(`index ${ i }`, item);
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push(callback.call(thisArg, item, i, this));
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* Returns the value for %%name%%.
|
|
|
|
*
|
|
|
|
* Since it is possible to have a key whose name conflicts with
|
|
|
|
* a method on a [[Result]] or its superclass Array, or any
|
|
|
|
* JavaScript keyword, this ensures all named values are still
|
|
|
|
* accessible by name.
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
getValue(name: string): any {
|
2023-01-26 19:32:56 +03:00
|
|
|
const index = this.#names.indexOf(name);
|
|
|
|
if (index === -1) { return undefined; }
|
|
|
|
|
|
|
|
const value = this[index];
|
|
|
|
|
|
|
|
if (value instanceof Error) {
|
|
|
|
throwError(`property ${ JSON.stringify(name) }`, (<any>value).error);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
2023-01-26 19:32:56 +03:00
|
|
|
return value;
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* Creates a new [[Result]] for %%items%% with each entry
|
|
|
|
* also accessible by its corresponding name in %%keys%%.
|
|
|
|
*/
|
|
|
|
static fromItems(items: Array<any>, keys?: Array<null | string>): Result {
|
2022-09-05 23:14:43 +03:00
|
|
|
return new Result(_guard, items, keys);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* Returns all errors found in a [[Result]].
|
|
|
|
*
|
|
|
|
* Since certain errors encountered when creating a [[Result]] do
|
|
|
|
* not impact the ability to continue parsing data, they are
|
|
|
|
* deferred until they are actually accessed. Hence a faulty string
|
|
|
|
* in an Event that is never used does not impact the program flow.
|
|
|
|
*
|
|
|
|
* However, sometimes it may be useful to access, identify or
|
|
|
|
* validate correctness of a [[Result]].
|
|
|
|
*
|
|
|
|
* @_docloc api/abi
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
export function checkResultErrors(result: Result): Array<{ path: Array<string | number>, error: Error }> {
|
|
|
|
// Find the first error (if any)
|
|
|
|
const errors: Array<{ path: Array<string | number>, error: Error }> = [ ];
|
|
|
|
|
|
|
|
const checkErrors = function(path: Array<string | number>, object: any): void {
|
|
|
|
if (!Array.isArray(object)) { return; }
|
|
|
|
for (let key in object) {
|
|
|
|
const childPath = path.slice();
|
|
|
|
childPath.push(key);
|
|
|
|
|
|
|
|
try {
|
|
|
|
checkErrors(childPath, object[key]);
|
|
|
|
} catch (error: any) {
|
|
|
|
errors.push({ path: childPath, error: error });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
checkErrors([ ], result);
|
|
|
|
|
|
|
|
return errors;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function getValue(value: BigNumberish): Uint8Array {
|
2022-12-10 02:21:45 +03:00
|
|
|
let bytes = toBeArray(value);
|
2022-09-05 23:14:43 +03:00
|
|
|
|
2022-11-05 01:08:37 +03:00
|
|
|
assert (bytes.length <= WordSize, "value out-of-bounds",
|
|
|
|
"BUFFER_OVERRUN", { buffer: bytes, length: WordSize, offset: bytes.length });
|
2022-09-05 23:14:43 +03:00
|
|
|
|
|
|
|
if (bytes.length !== WordSize) {
|
2022-09-09 06:21:08 +03:00
|
|
|
bytes = getBytesCopy(concat([ Padding.slice(bytes.length % WordSize), bytes ]));
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
export abstract class Coder {
|
|
|
|
|
|
|
|
// The coder name:
|
|
|
|
// - address, uint256, tuple, array, etc.
|
|
|
|
readonly name!: string;
|
|
|
|
|
|
|
|
// The fully expanded type, including composite types:
|
|
|
|
// - address, uint256, tuple(address,bytes), uint256[3][4][], etc.
|
|
|
|
readonly type!: string;
|
|
|
|
|
|
|
|
// The localName bound in the signature, in this example it is "baz":
|
|
|
|
// - tuple(address foo, uint bar) baz
|
|
|
|
readonly localName!: string;
|
|
|
|
|
|
|
|
// Whether this type is dynamic:
|
|
|
|
// - Dynamic: bytes, string, address[], tuple(boolean[]), etc.
|
|
|
|
// - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8)
|
|
|
|
readonly dynamic!: boolean;
|
|
|
|
|
|
|
|
constructor(name: string, type: string, localName: string, dynamic: boolean) {
|
|
|
|
defineProperties<Coder>(this, { name, type, localName, dynamic }, {
|
|
|
|
name: "string", type: "string", localName: "string", dynamic: "boolean"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_throwError(message: string, value: any): never {
|
2022-10-25 11:06:00 +03:00
|
|
|
assertArgument(false, message, this.localName, value);
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract encode(writer: Writer, value: any): number;
|
|
|
|
abstract decode(reader: Reader): any;
|
|
|
|
|
|
|
|
abstract defaultValue(): any;
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
export class Writer {
|
|
|
|
// An array of WordSize lengthed objects to concatenation
|
|
|
|
#data: Array<Uint8Array>;
|
|
|
|
#dataLength: number;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.#data = [ ];
|
|
|
|
this.#dataLength = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get data(): string {
|
|
|
|
return concat(this.#data);
|
|
|
|
}
|
|
|
|
get length(): number { return this.#dataLength; }
|
|
|
|
|
|
|
|
#writeData(data: Uint8Array): number {
|
|
|
|
this.#data.push(data);
|
|
|
|
this.#dataLength += data.length;
|
|
|
|
return data.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
appendWriter(writer: Writer): number {
|
2022-09-09 06:21:08 +03:00
|
|
|
return this.#writeData(getBytesCopy(writer.data));
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Arrayish item; pad on the right to *nearest* WordSize
|
|
|
|
writeBytes(value: BytesLike): number {
|
2022-09-09 06:21:08 +03:00
|
|
|
let bytes = getBytesCopy(value);
|
2022-09-05 23:14:43 +03:00
|
|
|
const paddingOffset = bytes.length % WordSize;
|
|
|
|
if (paddingOffset) {
|
2022-09-09 06:21:08 +03:00
|
|
|
bytes = getBytesCopy(concat([ bytes, Padding.slice(paddingOffset) ]))
|
2022-09-05 23:14:43 +03:00
|
|
|
}
|
|
|
|
return this.#writeData(bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Numeric item; pad on the left *to* WordSize
|
|
|
|
writeValue(value: BigNumberish): number {
|
|
|
|
return this.#writeData(getValue(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inserts a numeric place-holder, returning a callback that can
|
|
|
|
// be used to asjust the value later
|
|
|
|
writeUpdatableValue(): (value: BigNumberish) => void {
|
|
|
|
const offset = this.#data.length;
|
|
|
|
this.#data.push(Padding);
|
|
|
|
this.#dataLength += WordSize;
|
|
|
|
return (value: BigNumberish) => {
|
|
|
|
this.#data[offset] = getValue(value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:54:49 +03:00
|
|
|
/**
|
|
|
|
* @_ignore
|
|
|
|
*/
|
2022-09-05 23:14:43 +03:00
|
|
|
export class Reader {
|
|
|
|
// Allows incomplete unpadded data to be read; otherwise an error
|
|
|
|
// is raised if attempting to overrun the buffer. This is required
|
|
|
|
// to deal with an old Solidity bug, in which event data for
|
|
|
|
// external (not public thoguh) was tightly packed.
|
|
|
|
readonly allowLoose!: boolean;
|
|
|
|
|
|
|
|
readonly #data: Uint8Array;
|
|
|
|
#offset: number;
|
|
|
|
|
|
|
|
constructor(data: BytesLike, allowLoose?: boolean) {
|
|
|
|
defineProperties<Reader>(this, { allowLoose: !!allowLoose });
|
|
|
|
|
2022-09-09 06:21:08 +03:00
|
|
|
this.#data = getBytesCopy(data);
|
2022-09-05 23:14:43 +03:00
|
|
|
|
|
|
|
this.#offset = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
get data(): string { return hexlify(this.#data); }
|
|
|
|
get dataLength(): number { return this.#data.length; }
|
|
|
|
get consumed(): number { return this.#offset; }
|
|
|
|
get bytes(): Uint8Array { return new Uint8Array(this.#data); }
|
|
|
|
|
|
|
|
#peekBytes(offset: number, length: number, loose?: boolean): Uint8Array {
|
|
|
|
let alignedLength = Math.ceil(length / WordSize) * WordSize;
|
|
|
|
if (this.#offset + alignedLength > this.#data.length) {
|
|
|
|
if (this.allowLoose && loose && this.#offset + length <= this.#data.length) {
|
|
|
|
alignedLength = length;
|
|
|
|
} else {
|
2022-11-05 01:08:37 +03:00
|
|
|
assert(false, "data out-of-bounds", "BUFFER_OVERRUN", {
|
2022-09-09 06:21:08 +03:00
|
|
|
buffer: getBytesCopy(this.#data),
|
2022-09-05 23:14:43 +03:00
|
|
|
length: this.#data.length,
|
|
|
|
offset: this.#offset + alignedLength
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.#data.slice(this.#offset, this.#offset + alignedLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a sub-reader with the same underlying data, but offset
|
|
|
|
subReader(offset: number): Reader {
|
|
|
|
return new Reader(this.#data.slice(this.#offset + offset), this.allowLoose);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read bytes
|
|
|
|
readBytes(length: number, loose?: boolean): Uint8Array {
|
|
|
|
let bytes = this.#peekBytes(0, length, !!loose);
|
|
|
|
this.#offset += bytes.length;
|
|
|
|
// @TODO: Make sure the length..end bytes are all 0?
|
|
|
|
return bytes.slice(0, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read a numeric values
|
|
|
|
readValue(): bigint {
|
|
|
|
return toBigInt(this.readBytes(WordSize));
|
|
|
|
}
|
|
|
|
|
|
|
|
readIndex(): number {
|
|
|
|
return toNumber(this.readBytes(WordSize));
|
|
|
|
}
|
|
|
|
}
|