PHP WebShell
Текущая директория: /opt/BitGoJS/node_modules/@mysten/bcs/src
Просмотр файла: index.ts
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
/*
* BCS implementation {@see https://github.com/diem/bcs } for JavaScript.
* Intended to be used for Move applications; supports both NodeJS and browser.
*
* For more details and examples {@see README.md }.
*
* @module bcs
* @property {BcsReader}
*/
import { toB64, fromB64 } from './b64';
import { toHEX, fromHEX } from './hex';
import bs58 from 'bs58';
const SUI_ADDRESS_LENGTH = 32;
function toLittleEndian(bigint: bigint, size: number) {
let result = new Uint8Array(size);
let i = 0;
while (bigint > 0) {
result[i] = Number(bigint % BigInt(256));
bigint = bigint / BigInt(256);
i += 1;
}
return result;
}
const toB58 = (buffer: Uint8Array) => bs58.encode(buffer);
const fromB58 = (str: string) => bs58.decode(str);
// Re-export all encoding dependencies.
export { toB58, fromB58, toB64, fromB64, fromHEX, toHEX };
/**
* Supported encodings.
* Used in `Reader.toString()` as well as in `decodeStr` and `encodeStr` functions.
*/
export type Encoding = 'base58' | 'base64' | 'hex';
/**
* Allows for array definitions for names.
* @example
* ```
* bcs.registerStructType(['vector', BCS.STRING], ...);
* // equals
* bcs.registerStructType('vector<string>', ...);
* ```
*/
export type TypeName = string | [string, ...(TypeName | string)[]];
/**
* Class used for reading BCS data chunk by chunk. Meant to be used
* by some wrapper, which will make sure that data is valid and is
* matching the desired format.
*
* @example
* // data for this example is:
* // { a: u8, b: u32, c: bool, d: u64 }
*
* let reader = new BcsReader("647f1a060001ffffe7890423c78a050102030405");
* let field1 = reader.read8();
* let field2 = reader.read32();
* let field3 = reader.read8() === '1'; // bool
* let field4 = reader.read64();
* // ....
*
* Reading vectors is another deal in bcs. To read a vector, you first need to read
* its length using {@link readULEB}. Here's an example:
* @example
* // data encoded: { field: [1, 2, 3, 4, 5] }
* let reader = new BcsReader("050102030405");
* let vec_length = reader.readULEB();
* let elements = [];
* for (let i = 0; i < vec_length; i++) {
* elements.push(reader.read8());
* }
* console.log(elements); // [1,2,3,4,5]
*
* @param {String} data HEX-encoded data (serialized BCS)
*/
export class BcsReader {
private dataView: DataView;
private bytePosition: number = 0;
/**
* @param {Uint8Array} data Data to use as a buffer.
*/
constructor(data: Uint8Array) {
this.dataView = new DataView(data.buffer);
}
/**
* Shift current cursor position by `bytes`.
*
* @param {Number} bytes Number of bytes to
* @returns {this} Self for possible chaining.
*/
shift(bytes: number) {
this.bytePosition += bytes;
return this;
}
/**
* Read U8 value from the buffer and shift cursor by 1.
* @returns
*/
read8(): number {
let value = this.dataView.getUint8(this.bytePosition);
this.shift(1);
return value;
}
/**
* Read U16 value from the buffer and shift cursor by 2.
* @returns
*/
read16(): number {
let value = this.dataView.getUint16(this.bytePosition, true);
this.shift(2);
return value;
}
/**
* Read U32 value from the buffer and shift cursor by 4.
* @returns
*/
read32(): number {
let value = this.dataView.getUint32(this.bytePosition, true);
this.shift(4);
return value;
}
/**
* Read U64 value from the buffer and shift cursor by 8.
* @returns
*/
read64(): string {
let value1 = this.read32();
let value2 = this.read32();
let result = value2.toString(16) + value1.toString(16).padStart(8, '0');
return BigInt('0x' + result).toString(10);
}
/**
* Read U128 value from the buffer and shift cursor by 16.
*/
read128(): string {
let value1 = BigInt(this.read64());
let value2 = BigInt(this.read64());
let result = value2.toString(16) + value1.toString(16).padStart(16, '0');
return BigInt('0x' + result).toString(10);
}
/**
* Read U128 value from the buffer and shift cursor by 32.
* @returns
*/
read256(): string {
let value1 = BigInt(this.read128());
let value2 = BigInt(this.read128());
let result = value2.toString(16) + value1.toString(16).padStart(32, '0');
return BigInt('0x' + result).toString(10);
}
/**
* Read `num` number of bytes from the buffer and shift cursor by `num`.
* @param num Number of bytes to read.
*/
readBytes(num: number): Uint8Array {
let start = this.bytePosition + this.dataView.byteOffset;
let value = new Uint8Array(this.dataView.buffer, start, num);
this.shift(num);
return value;
}
/**
* Read ULEB value - an integer of varying size. Used for enum indexes and
* vector lengths.
* @returns {Number} The ULEB value.
*/
readULEB(): number {
let start = this.bytePosition + this.dataView.byteOffset;
let buffer = new Uint8Array(this.dataView.buffer, start);
let { value, length } = ulebDecode(buffer);
this.shift(length);
return value;
}
/**
* Read a BCS vector: read a length and then apply function `cb` X times
* where X is the length of the vector, defined as ULEB in BCS bytes.
* @param cb Callback to process elements of vector.
* @returns {Array<Any>} Array of the resulting values, returned by callback.
*/
readVec(cb: (reader: BcsReader, i: number, length: number) => any): any[] {
let length = this.readULEB();
let result = [];
for (let i = 0; i < length; i++) {
result.push(cb(this, i, length));
}
return result;
}
}
/**
* Class used to write BCS data into a buffer. Initializer requires
* some size of a buffer to init; default value for this buffer is 1KB.
*
* Most methods are chainable, so it is possible to write them in one go.
*
* @example
* let serialized = new BcsWriter()
* .write8(10)
* .write32(1000000)
* .write64(10000001000000)
* .hex();
*/
interface BcsWriterOptions {
/** The initial size (in bytes) of the buffer tht will be allocated */
size?: number;
/** The maximum size (in bytes) that the buffer is allowed to grow to */
maxSize?: number;
/** The amount of bytes that will be allocated whenever additional memory is required */
allocateSize?: number;
}
export class BcsWriter {
private dataView: DataView;
private bytePosition: number = 0;
private size: number;
private maxSize: number;
private allocateSize: number;
constructor({ size = 1024, maxSize, allocateSize = 1024 }: BcsWriterOptions = {}) {
this.size = size;
this.maxSize = maxSize || size;
this.allocateSize = allocateSize;
this.dataView = new DataView(new ArrayBuffer(size));
}
private ensureSizeOrGrow(bytes: number) {
const requiredSize = this.bytePosition + bytes;
if (requiredSize > this.size) {
const nextSize = Math.min(this.maxSize, this.size + this.allocateSize);
if (requiredSize > nextSize) {
throw new Error(
`Attempting to serialize to BCS, but buffer does not have enough size. Allocated size: ${this.size}, Max size: ${this.maxSize}, Required size: ${requiredSize}`,
);
}
this.size = nextSize;
const nextBuffer = new ArrayBuffer(this.size);
new Uint8Array(nextBuffer).set(new Uint8Array(this.dataView.buffer));
this.dataView = new DataView(nextBuffer);
}
}
/**
* Shift current cursor position by `bytes`.
*
* @param {Number} bytes Number of bytes to
* @returns {this} Self for possible chaining.
*/
shift(bytes: number): this {
this.bytePosition += bytes;
return this;
}
/**
* Write a U8 value into a buffer and shift cursor position by 1.
* @param {Number} value Value to write.
* @returns {this}
*/
write8(value: number | bigint): this {
this.ensureSizeOrGrow(1);
this.dataView.setUint8(this.bytePosition, Number(value));
return this.shift(1);
}
/**
* Write a U16 value into a buffer and shift cursor position by 2.
* @param {Number} value Value to write.
* @returns {this}
*/
write16(value: number | bigint): this {
this.ensureSizeOrGrow(2);
this.dataView.setUint16(this.bytePosition, Number(value), true);
return this.shift(2);
}
/**
* Write a U32 value into a buffer and shift cursor position by 4.
* @param {Number} value Value to write.
* @returns {this}
*/
write32(value: number | bigint): this {
this.ensureSizeOrGrow(4);
this.dataView.setUint32(this.bytePosition, Number(value), true);
return this.shift(4);
}
/**
* Write a U64 value into a buffer and shift cursor position by 8.
* @param {bigint} value Value to write.
* @returns {this}
*/
write64(value: number | bigint): this {
toLittleEndian(BigInt(value), 8).forEach((el) => this.write8(el));
return this;
}
/**
* Write a U128 value into a buffer and shift cursor position by 16.
*
* @param {bigint} value Value to write.
* @returns {this}
*/
write128(value: number | bigint): this {
toLittleEndian(BigInt(value), 16).forEach((el) => this.write8(el));
return this;
}
/**
* Write a U256 value into a buffer and shift cursor position by 16.
*
* @param {bigint} value Value to write.
* @returns {this}
*/
write256(value: number | bigint): this {
toLittleEndian(BigInt(value), 32).forEach((el) => this.write8(el));
return this;
}
/**
* Write a ULEB value into a buffer and shift cursor position by number of bytes
* written.
* @param {Number} value Value to write.
* @returns {this}
*/
writeULEB(value: number): this {
ulebEncode(value).forEach((el) => this.write8(el));
return this;
}
/**
* Write a vector into a buffer by first writing the vector length and then calling
* a callback on each passed value.
*
* @param {Array<Any>} vector Array of elements to write.
* @param {WriteVecCb} cb Callback to call on each element of the vector.
* @returns {this}
*/
writeVec(vector: any[], cb: (writer: BcsWriter, el: any, i: number, len: number) => void): this {
this.writeULEB(vector.length);
Array.from(vector).forEach((el, i) => cb(this, el, i, vector.length));
return this;
}
/**
* Adds support for iterations over the object.
* @returns {Uint8Array}
*/
*[Symbol.iterator](): Iterator<number, Iterable<number>> {
for (let i = 0; i < this.bytePosition; i++) {
yield this.dataView.getUint8(i);
}
return this.toBytes();
}
/**
* Get underlying buffer taking only value bytes (in case initial buffer size was bigger).
* @returns {Uint8Array} Resulting bcs.
*/
toBytes(): Uint8Array {
return new Uint8Array(this.dataView.buffer.slice(0, this.bytePosition));
}
/**
* Represent data as 'hex' or 'base64'
* @param encoding Encoding to use: 'base64' or 'hex'
*/
toString(encoding: Encoding): string {
return encodeStr(this.toBytes(), encoding);
}
}
// Helper utility: write number as an ULEB array.
// Original code is taken from: https://www.npmjs.com/package/uleb128 (no longer exists)
function ulebEncode(num: number): number[] {
let arr = [];
let len = 0;
if (num === 0) {
return [0];
}
while (num > 0) {
arr[len] = num & 0x7f;
if ((num >>= 7)) {
arr[len] |= 0x80;
}
len += 1;
}
return arr;
}
// Helper utility: decode ULEB as an array of numbers.
// Original code is taken from: https://www.npmjs.com/package/uleb128 (no longer exists)
function ulebDecode(arr: number[] | Uint8Array): {
value: number;
length: number;
} {
let total = 0;
let shift = 0;
let len = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
let byte = arr[len];
len += 1;
total |= (byte & 0x7f) << shift;
if ((byte & 0x80) === 0) {
break;
}
shift += 7;
}
return {
value: total,
length: len,
};
}
/**
* Set of methods that allows data encoding/decoding as standalone
* BCS value or a part of a composed structure/vector.
*/
export interface TypeInterface {
encode: (
self: BCS,
data: any,
options: BcsWriterOptions | undefined,
typeParams: TypeName[],
) => BcsWriter;
decode: (self: BCS, data: Uint8Array, typeParams: TypeName[]) => any;
_encodeRaw: (
writer: BcsWriter,
data: any,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => BcsWriter;
_decodeRaw: (
reader: BcsReader,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => any;
}
/**
* Struct type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerStructType`.
*/
export type StructTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition;
};
/**
* Enum type definition. Used as input format in BcsConfig.types
* as well as an argument type for `bcs.registerEnumType`.
*
* Value can be either `string` when invariant has a type or `null`
* when invariant is empty.
*
* @example
* bcs.registerEnumType('Option<T>', {
* some: 'T',
* none: null
* });
*/
export type EnumTypeDefinition = {
[key: string]: TypeName | StructTypeDefinition | null;
};
/**
* Configuration that is passed into BCS constructor.
*/
export type BcsConfig = {
/**
* Defines type name for the vector / array type.
* In Move: `vector<T>` or `vector`.
*/
vectorType: string;
/**
* Address length. Varies depending on a platform and
* has to be specified for the `address` type.
*/
addressLength: number;
/**
* Custom encoding for address. Supported values are
* either 'hex' or 'base64'.
*/
addressEncoding?: 'hex' | 'base64';
/**
* Opening and closing symbol for type parameters. Can be
* any pair of symbols (eg `['(', ')']`); default value follows
* Rust and Move: `<` and `>`.
*/
genericSeparators?: [string, string];
/**
* Type definitions for the BCS. This field allows spawning
* BCS instance from JSON or another prepared configuration.
* Optional.
*/
types?: {
structs?: { [key: string]: StructTypeDefinition };
enums?: { [key: string]: EnumTypeDefinition };
aliases?: { [key: string]: string };
};
/**
* Whether to auto-register primitive types on launch.
*/
withPrimitives?: boolean;
};
/**
* BCS implementation for Move types and few additional built-ins.
*/
export class BCS {
// Prefefined types constants
static readonly U8: string = 'u8';
static readonly U16: string = 'u16';
static readonly U32: string = 'u32';
static readonly U64: string = 'u64';
static readonly U128: string = 'u128';
static readonly U256: string = 'u256';
static readonly BOOL: string = 'bool';
static readonly VECTOR: string = 'vector';
static readonly ADDRESS: string = 'address';
static readonly STRING: string = 'string';
static readonly HEX: string = 'hex-string';
static readonly BASE58: string = 'base58-string';
static readonly BASE64: string = 'base64-string';
/**
* Map of kind `TypeName => TypeInterface`. Holds all
* callbacks for (de)serialization of every registered type.
*
* If the value stored is a string, it is treated as an alias.
*/
public types: Map<string, TypeInterface | string> = new Map();
/**
* Stored BcsConfig for the current instance of BCS.
*/
protected schema: BcsConfig;
/**
* Count temp keys to generate a new one when requested.
*/
protected counter: number = 0;
/**
* Name of the key to use for temporary struct definitions.
* Returns a temp key + index (for a case when multiple temp
* structs are processed).
*/
private tempKey() {
return `bcs-struct-${++this.counter}`;
}
/**
* Construct a BCS instance with a prepared schema.
*
* @param schema A prepared schema with type definitions
* @param withPrimitives Whether to register primitive types by default
*/
constructor(schema: BcsConfig | BCS) {
// if BCS instance is passed -> clone its schema
if (schema instanceof BCS) {
this.schema = schema.schema;
this.types = new Map(schema.types);
return;
}
this.schema = schema;
// Register address type under key 'address'.
this.registerAddressType(BCS.ADDRESS, schema.addressLength, schema.addressEncoding);
this.registerVectorType(schema.vectorType);
// Register struct types if they were passed.
if (schema.types && schema.types.structs) {
for (let name of Object.keys(schema.types.structs)) {
this.registerStructType(name, schema.types.structs[name]);
}
}
// Register enum types if they were passed.
if (schema.types && schema.types.enums) {
for (let name of Object.keys(schema.types.enums)) {
this.registerEnumType(name, schema.types.enums[name]);
}
}
// Register aliases if they were passed.
if (schema.types && schema.types.aliases) {
for (let name of Object.keys(schema.types.aliases)) {
this.registerAlias(name, schema.types.aliases[name]);
}
}
if (schema.withPrimitives !== false) {
registerPrimitives(this);
}
}
/**
* Serialize data into bcs.
*
* @example
* bcs.registerVectorType('vector<u8>', 'u8');
*
* let serialized = BCS
* .set('vector<u8>', [1,2,3,4,5,6])
* .toBytes();
*
* console.assert(toHex(serialized) === '06010203040506');
*
* @param type Name of the type to serialize (must be registered) or a struct type.
* @param data Data to serialize.
* @param size Serialization buffer size. Default 1024 = 1KB.
* @return A BCS reader instance. Usually you'd want to call `.toBytes()`
*/
public ser(
type: TypeName | StructTypeDefinition,
data: any,
options?: BcsWriterOptions,
): BcsWriter {
if (typeof type === 'string' || Array.isArray(type)) {
const { name, params } = this.parseTypeName(type);
return this.getTypeInterface(name).encode(this, data, options, params as string[]);
}
// Quick serialization without registering the type in the main struct.
if (typeof type === 'object') {
const key = this.tempKey();
const temp = new BCS(this);
return temp.registerStructType(key, type).ser(key, data, options);
}
throw new Error(`Incorrect type passed into the '.ser()' function. \n${JSON.stringify(type)}`);
}
/**
* Deserialize BCS into a JS type.
*
* @example
* let num = bcs.ser('u64', '4294967295').toString('hex');
* let deNum = bcs.de('u64', num, 'hex');
* console.assert(deNum.toString(10) === '4294967295');
*
* @param type Name of the type to deserialize (must be registered) or a struct type definition.
* @param data Data to deserialize.
* @param encoding Optional - encoding to use if data is of type String
* @return Deserialized data.
*/
public de(
type: TypeName | StructTypeDefinition,
data: Uint8Array | string,
encoding?: Encoding,
): any {
if (typeof data === 'string') {
if (encoding) {
data = decodeStr(data, encoding);
} else {
throw new Error('To pass a string to `bcs.de`, specify encoding');
}
}
// In case the type specified is already registered.
if (typeof type === 'string' || Array.isArray(type)) {
const { name, params } = this.parseTypeName(type);
return this.getTypeInterface(name).decode(this, data, params as string[]);
}
// Deserialize without registering a type using a temporary clone.
if (typeof type === 'object') {
const temp = new BCS(this);
const key = this.tempKey();
return temp.registerStructType(key, type).de(key, data, encoding);
}
throw new Error(`Incorrect type passed into the '.de()' function. \n${JSON.stringify(type)}`);
}
/**
* Check whether a `TypeInterface` has been loaded for a `type`.
* @param type Name of the type to check.
* @returns
*/
public hasType(type: string): boolean {
return this.types.has(type);
}
/**
* Create an alias for a type.
* WARNING: this can potentially lead to recursion
* @param name Alias to use
* @param forType Type to reference
* @returns
*
* @example
* ```
* let bcs = new BCS(getSuiMoveConfig());
* bcs.registerAlias('ObjectDigest', BCS.BASE58);
* let b58_digest = bcs.de('ObjectDigest', '<digest_bytes>', 'base64');
* ```
*/
public registerAlias(name: string, forType: string): BCS {
this.types.set(name, forType);
return this;
}
/**
* Method to register new types for BCS internal representation.
* For each registered type 2 callbacks must be specified and one is optional:
*
* - encodeCb(writer, data) - write a way to serialize data with BcsWriter;
* - decodeCb(reader) - write a way to deserialize data with BcsReader;
* - validateCb(data) - validate data - either return bool or throw an error
*
* @example
* // our type would be a string that consists only of numbers
* bcs.registerType('number_string',
* (writer, data) => writer.writeVec(data, (w, el) => w.write8(el)),
* (reader) => reader.readVec((r) => r.read8()).join(''), // read each value as u8
* (value) => /[0-9]+/.test(value) // test that it has at least one digit
* );
* console.log(Array.from(bcs.ser('number_string', '12345').toBytes()) == [5,1,2,3,4,5]);
*
* @param name
* @param encodeCb Callback to encode a value.
* @param decodeCb Callback to decode a value.
* @param validateCb Optional validator Callback to check type before serialization.
*/
public registerType(
typeName: TypeName,
encodeCb: (
writer: BcsWriter,
data: any,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => BcsWriter,
decodeCb: (
reader: BcsReader,
typeParams: TypeName[],
typeMap: { [key: string]: TypeName },
) => any,
validateCb: (data: any) => boolean = () => true,
): BCS {
const { name, params: generics } = this.parseTypeName(typeName);
this.types.set(name, {
encode(self: BCS, data, options: BcsWriterOptions, typeParams) {
const typeMap = (generics as string[]).reduce((acc: any, value: string, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return this._encodeRaw.call(self, new BcsWriter(options), data, typeParams, typeMap);
},
decode(self: BCS, data, typeParams) {
const typeMap = (generics as string[]).reduce((acc: any, value: string, index) => {
return Object.assign(acc, { [value]: typeParams[index] });
}, {});
return this._decodeRaw.call(self, new BcsReader(data), typeParams, typeMap);
},
// these methods should always be used with caution as they require pre-defined
// reader and writer and mainly exist to allow multi-field (de)serialization;
_encodeRaw(writer, data, typeParams, typeMap) {
if (validateCb(data)) {
return encodeCb.call(this, writer, data, typeParams, typeMap);
} else {
throw new Error(`Validation failed for type ${name}, data: ${data}`);
}
},
_decodeRaw(reader, typeParams, typeMap) {
return decodeCb.call(this, reader, typeParams, typeMap);
},
} as TypeInterface);
return this;
}
/**
* Register an address type which is a sequence of U8s of specified length.
* @example
* bcs.registerAddressType('address', SUI_ADDRESS_LENGTH);
* let addr = bcs.de('address', 'c3aca510c785c7094ac99aeaa1e69d493122444df50bb8a99dfa790c654a79af');
*
* @param name Name of the address type.
* @param length Byte length of the address.
* @param encoding Encoding to use for the address type
* @returns
*/
public registerAddressType(name: string, length: number, encoding: Encoding | void = 'hex'): BCS {
switch (encoding) {
case 'base64':
return this.registerType(
name,
function encodeAddress(writer, data: string) {
return fromB64(data).reduce((writer, el) => writer.write8(el), writer);
},
function decodeAddress(reader) {
return toB64(reader.readBytes(length));
},
);
case 'hex':
return this.registerType(
name,
function encodeAddress(writer, data: string) {
return fromHEX(data).reduce((writer, el) => writer.write8(el), writer);
},
function decodeAddress(reader) {
return toHEX(reader.readBytes(length));
},
);
default:
throw new Error('Unsupported encoding! Use either hex or base64');
}
}
/**
* Register custom vector type inside the bcs.
*
* @example
* bcs.registerVectorType('vector<T>'); // generic registration
* let array = bcs.de('vector<u8>', '06010203040506', 'hex'); // [1,2,3,4,5,6];
* let again = bcs.ser('vector<u8>', [1,2,3,4,5,6]).toString('hex');
*
* @param name Name of the type to register
* @param elementType Optional name of the inner type of the vector
* @return Returns self for chaining.
*/
private registerVectorType(typeName: string): BCS {
let { name, params } = this.parseTypeName(typeName);
if (params.length > 1) {
throw new Error('Vector can have only one type parameter; got ' + name);
}
return this.registerType(
typeName,
function encodeVector(
this: BCS,
writer: BcsWriter,
data: any[],
typeParams: TypeName[],
typeMap,
) {
return writer.writeVec(data, (writer, el) => {
let elementType: TypeName = typeParams[0];
if (!elementType) {
throw new Error(`Incorrect number of type parameters passed a to vector '${typeName}'`);
}
let { name, params } = this.parseTypeName(elementType);
if (this.hasType(name)) {
return this.getTypeInterface(name)._encodeRaw.call(this, writer, el, params, typeMap);
}
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in vector; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
return this.getTypeInterface(innerName)._encodeRaw.call(
this,
writer,
el,
innerParams,
typeMap,
);
});
},
function decodeVector(this: BCS, reader: BcsReader, typeParams, typeMap) {
return reader.readVec((reader) => {
let elementType: TypeName = typeParams[0];
if (!elementType) {
throw new Error(`Incorrect number of type parameters passed to a vector '${typeName}'`);
}
let { name, params } = this.parseTypeName(elementType);
if (this.hasType(name)) {
return this.getTypeInterface(name)._decodeRaw.call(this, reader, params, typeMap);
}
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in vector; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
return this.getTypeInterface(innerName)._decodeRaw.call(
this,
reader,
innerParams,
typeMap,
);
});
},
);
}
/**
* Safe method to register a custom Move struct. The first argument is a name of the
* struct which is only used on the FrontEnd and has no affect on serialization results,
* and the second is a struct description passed as an Object.
*
* The description object MUST have the same order on all of the platforms (ie in Move
* or in Rust).
*
* @example
* // Move / Rust struct
* // struct Coin {
* // value: u64,
* // owner: vector<u8>, // name // Vec<u8> in Rust
* // is_locked: bool,
* // }
*
* bcs.registerStructType('Coin', {
* value: bcs.U64,
* owner: bcs.STRING,
* is_locked: bcs.BOOL
* });
*
* // Created in Rust with diem/bcs
* // let rust_bcs_str = '80d1b105600000000e4269672057616c6c65742047757900';
* let rust_bcs_str = [ // using an Array here as BCS works with Uint8Array
* 128, 209, 177, 5, 96, 0, 0,
* 0, 14, 66, 105, 103, 32, 87,
* 97, 108, 108, 101, 116, 32, 71,
* 117, 121, 0
* ];
*
* // Let's encode the value as well
* let test_set = bcs.ser('Coin', {
* owner: 'Big Wallet Guy',
* value: '412412400000',
* is_locked: false,
* });
*
* console.assert(Array.from(test_set.toBytes()) === rust_bcs_str, 'Whoopsie, result mismatch');
*
* @param name Name of the type to register.
* @param fields Fields of the struct. Must be in the correct order.
* @return Returns BCS for chaining.
*/
public registerStructType(typeName: TypeName, fields: StructTypeDefinition): BCS {
// When an Object is passed, we register it under a new key and store it
// in the registered type system. This way we allow nested inline definitions.
for (let key in fields) {
let internalName = this.tempKey();
let value = fields[key];
// TODO: add a type guard here?
if (!Array.isArray(value) && typeof value !== 'string') {
fields[key] = internalName;
this.registerStructType(internalName, value as StructTypeDefinition);
}
}
let struct = Object.freeze(fields); // Make sure the order doesn't get changed
// IMPORTANT: we need to store canonical order of fields for each registered
// struct so we maintain it and allow developers to use any field ordering in
// their code (and not cause mismatches based on field order).
let canonicalOrder = Object.keys(struct);
// Holds generics for the struct definition. At this stage we can check that
// generic parameter matches the one defined in the struct.
let { name: structName, params: generics } = this.parseTypeName(typeName);
// Make sure all the types in the fields description are already known
// and that all the field types are strings.
return this.registerType(
typeName,
function encodeStruct(
this: BCS,
writer: BcsWriter,
data: { [key: string]: any },
typeParams,
typeMap,
) {
if (!data || data.constructor !== Object) {
throw new Error(`Expected ${structName} to be an Object, got: ${data}`);
}
if (typeParams.length !== generics.length) {
throw new Error(
`Incorrect number of generic parameters passed; expected: ${generics.length}, got: ${typeParams.length}`,
);
}
// follow the canonical order when serializing
for (let key of canonicalOrder) {
if (!(key in data)) {
throw new Error(`Struct ${structName} requires field ${key}:${struct[key]}`);
}
// Before deserializing, read the canonical field type.
const { name: fieldType, params: fieldParams } = this.parseTypeName(
struct[key] as TypeName,
);
// Check whether this type is a generic defined in this struct.
// If it is -> read the type parameter matching its index.
// If not - tread as a regular field.
if (!generics.includes(fieldType)) {
this.getTypeInterface(fieldType)._encodeRaw.call(
this,
writer,
data[key],
fieldParams,
typeMap,
);
} else {
const paramIdx = generics.indexOf(fieldType);
let { name, params } = this.parseTypeName(typeParams[paramIdx]);
// If the type from the type parameters already exists
// and known -> proceed with type decoding.
if (this.hasType(name)) {
this.getTypeInterface(name)._encodeRaw.call(
this,
writer,
data[key],
params as string[],
typeMap,
);
continue;
}
// Alternatively, if it's a global generic parameter...
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in ${structName}; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
this.getTypeInterface(innerName)._encodeRaw.call(
this,
writer,
data[key],
innerParams,
typeMap,
);
}
}
return writer;
},
function decodeStruct(this: BCS, reader: BcsReader, typeParams, typeMap) {
if (typeParams.length !== generics.length) {
throw new Error(
`Incorrect number of generic parameters passed; expected: ${generics.length}, got: ${typeParams.length}`,
);
}
let result: { [key: string]: any } = {};
for (let key of canonicalOrder) {
const { name: fieldName, params: fieldParams } = this.parseTypeName(
struct[key] as TypeName,
);
// if it's not a generic
if (!generics.includes(fieldName)) {
result[key] = this.getTypeInterface(fieldName)._decodeRaw.call(
this,
reader,
fieldParams as string[],
typeMap,
);
} else {
const paramIdx = generics.indexOf(fieldName);
let { name, params } = this.parseTypeName(typeParams[paramIdx]);
// If the type from the type parameters already exists
// and known -> proceed with type decoding.
if (this.hasType(name)) {
result[key] = this.getTypeInterface(name)._decodeRaw.call(
this,
reader,
params,
typeMap,
);
continue;
}
if (!(name in typeMap)) {
throw new Error(
`Unable to find a matching type definition for ${name} in ${structName}; make sure you passed a generic`,
);
}
let { name: innerName, params: innerParams } = this.parseTypeName(typeMap[name]);
result[key] = this.getTypeInterface(innerName)._decodeRaw.call(
this,
reader,
innerParams,
typeMap,
);
}
}
return result;
},
);
}
/**
* Safe method to register custom enum type where each invariant holds the value of another type.
* @example
* bcs.registerStructType('Coin', { value: 'u64' });
* bcs.registerEnumType('MyEnum', {
* single: 'Coin',
* multi: 'vector<Coin>',
* empty: null
* });
*
* console.log(
* bcs.de('MyEnum', 'AICWmAAAAAAA', 'base64'), // { single: { value: 10000000 } }
* bcs.de('MyEnum', 'AQIBAAAAAAAAAAIAAAAAAAAA', 'base64') // { multi: [ { value: 1 }, { value: 2 } ] }
* )
*
* // and serialization
* bcs.ser('MyEnum', { single: { value: 10000000 } }).toBytes();
* bcs.ser('MyEnum', { multi: [ { value: 1 }, { value: 2 } ] });
*
* @param name
* @param variants
*/
public registerEnumType(typeName: TypeName, variants: EnumTypeDefinition): BCS {
// When an Object is passed, we register it under a new key and store it
// in the registered type system. This way we allow nested inline definitions.
for (let key in variants) {
let internalName = this.tempKey();
let value = variants[key];
if (value !== null && !Array.isArray(value) && typeof value !== 'string') {
variants[key] = internalName;
this.registerStructType(internalName, value as StructTypeDefinition);
}
}
let struct = Object.freeze(variants); // Make sure the order doesn't get changed
// IMPORTANT: enum is an ordered type and we have to preserve ordering in BCS
let canonicalOrder = Object.keys(struct);
// Parse type parameters in advance to know the index of each generic parameter.
let { name, params: canonicalTypeParams } = this.parseTypeName(typeName);
return this.registerType(
typeName,
function encodeEnum(
this: BCS,
writer: BcsWriter,
data: { [key: string]: any | null },
typeParams,
typeMap,
) {
if (!data) {
throw new Error(`Unable to write enum "${name}", missing data.\nReceived: "${data}"`);
}
if (typeof data !== 'object') {
throw new Error(
`Incorrect data passed into enum "${name}", expected object with properties: "${canonicalOrder.join(
' | ',
)}".\nReceived: "${JSON.stringify(data)}"`,
);
}
let key = Object.keys(data)[0];
if (key === undefined) {
throw new Error(`Empty object passed as invariant of the enum "${name}"`);
}
let orderByte = canonicalOrder.indexOf(key);
if (orderByte === -1) {
throw new Error(
`Unknown invariant of the enum "${name}", allowed values: "${canonicalOrder.join(
' | ',
)}"; received "${key}"`,
);
}
let invariant = canonicalOrder[orderByte];
let invariantType = struct[invariant] as TypeName | null;
// write order byte
writer.write8(orderByte);
// When { "key": null } - empty value for the invariant.
if (invariantType === null) {
return writer;
}
let paramIndex = canonicalTypeParams.indexOf(invariantType);
let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex];
{
let { name, params } = this.parseTypeName(typeOrParam);
return this.getTypeInterface(name)._encodeRaw.call(
this,
writer,
data[key],
params,
typeMap,
);
}
},
function decodeEnum(this: BCS, reader: BcsReader, typeParams, typeMap) {
let orderByte = reader.readULEB();
let invariant = canonicalOrder[orderByte];
let invariantType = struct[invariant] as TypeName | null;
if (orderByte === -1) {
throw new Error(
`Decoding type mismatch, expected enum "${name}" invariant index, received "${orderByte}"`,
);
}
// Encode an empty value for the enum.
if (invariantType === null) {
return { [invariant]: true };
}
let paramIndex = canonicalTypeParams.indexOf(invariantType);
let typeOrParam = paramIndex === -1 ? invariantType : typeParams[paramIndex];
{
let { name, params } = this.parseTypeName(typeOrParam);
return {
[invariant]: this.getTypeInterface(name)._decodeRaw.call(this, reader, params, typeMap),
};
}
},
);
}
/**
* Get a set of encoders/decoders for specific type.
* Mainly used to define custom type de/serialization logic.
*
* @param type
* @returns {TypeInterface}
*/
public getTypeInterface(type: string): TypeInterface {
let typeInterface = this.types.get(type);
// Special case - string means an alias.
// Goes through the alias chain and tracks recursion.
if (typeof typeInterface === 'string') {
let chain: string[] = [];
while (typeof typeInterface === 'string') {
if (chain.includes(typeInterface)) {
throw new Error(`Recursive definition found: ${chain.join(' -> ')} -> ${typeInterface}`);
}
chain.push(typeInterface);
typeInterface = this.types.get(typeInterface);
}
}
if (typeInterface === undefined) {
throw new Error(`Type ${type} is not registered`);
}
return typeInterface;
}
/**
* Parse a type name and get the type's generics.
* @example
* let { typeName, typeParams } = parseTypeName('Option<Coin<SUI>>');
* // typeName: Option
* // typeParams: [ 'Coin<SUI>' ]
*
* @param name Name of the type to process
* @returns Object with typeName and typeParams listed as Array
*/
public parseTypeName(name: TypeName): {
name: string;
params: TypeName[];
} {
if (Array.isArray(name)) {
let [typeName, ...params] = name;
return { name: typeName, params };
}
if (typeof name !== 'string') {
throw new Error(`Illegal type passed as a name of the type: ${name}`);
}
let [left, right] = this.schema.genericSeparators || ['<', '>'];
let l_bound = name.indexOf(left);
let r_bound = Array.from(name).reverse().indexOf(right);
// if there are no generics - exit gracefully.
if (l_bound === -1 && r_bound === -1) {
return { name: name, params: [] };
}
// if one of the bounds is not defined - throw an Error.
if (l_bound === -1 || r_bound === -1) {
throw new Error(`Unclosed generic in name '${name}'`);
}
let typeName = name.slice(0, l_bound);
let params = splitGenericParameters(
name.slice(l_bound + 1, name.length - r_bound - 1),
this.schema.genericSeparators,
);
return { name: typeName, params };
}
}
/**
* Encode data with either `hex` or `base64`.
*
* @param {Uint8Array} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {String} Encoded value.
*/
export function encodeStr(data: Uint8Array, encoding: Encoding): string {
switch (encoding) {
case 'base58':
return toB58(data);
case 'base64':
return toB64(data);
case 'hex':
return toHEX(data);
default:
throw new Error('Unsupported encoding, supported values are: base64, hex');
}
}
/**
* Decode either `base64` or `hex` data.
*
* @param {String} data Data to encode.
* @param {String} encoding Encoding to use: base64 or hex
* @return {Uint8Array} Encoded value.
*/
export function decodeStr(data: string, encoding: Encoding): Uint8Array {
switch (encoding) {
case 'base58':
return fromB58(data);
case 'base64':
return fromB64(data);
case 'hex':
return fromHEX(data);
default:
throw new Error('Unsupported encoding, supported values are: base64, hex');
}
}
/**
* Register the base set of primitive and common types.
* Is called in the `BCS` constructor automatically but can
* be ignored if the `withPrimitives` argument is not set.
*/
export function registerPrimitives(bcs: BCS): void {
bcs.registerType(
BCS.U8,
function (writer: BcsWriter, data) {
return writer.write8(data);
},
function (reader: BcsReader) {
return reader.read8();
},
(u8) => u8 < 256,
);
bcs.registerType(
BCS.U16,
function (writer: BcsWriter, data) {
return writer.write16(data);
},
function (reader: BcsReader) {
return reader.read16();
},
(u16) => u16 < 65536,
);
bcs.registerType(
BCS.U32,
function (writer: BcsWriter, data) {
return writer.write32(data);
},
function (reader: BcsReader) {
return reader.read32();
},
(u32) => u32 <= 4294967296n,
);
bcs.registerType(
BCS.U64,
function (writer: BcsWriter, data) {
return writer.write64(data);
},
function (reader: BcsReader) {
return reader.read64();
},
);
bcs.registerType(
BCS.U128,
function (writer: BcsWriter, data: bigint) {
return writer.write128(data);
},
function (reader: BcsReader) {
return reader.read128();
},
);
bcs.registerType(
BCS.U256,
function (writer: BcsWriter, data) {
return writer.write256(data);
},
function (reader: BcsReader) {
return reader.read256();
},
);
bcs.registerType(
BCS.BOOL,
function (writer: BcsWriter, data) {
return writer.write8(data);
},
function (reader: BcsReader) {
return reader.read8().toString(10) === '1';
},
);
bcs.registerType(
BCS.STRING,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(data), (writer, el) => writer.write8(el.charCodeAt(0)));
},
function (reader: BcsReader) {
return reader
.readVec((reader) => reader.read8())
.map((el: bigint) => String.fromCharCode(Number(el)))
.join('');
},
(_str: string) => true,
);
bcs.registerType(
BCS.HEX,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromHEX(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toHEX(new Uint8Array(bytes));
},
);
bcs.registerType(
BCS.BASE58,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromB58(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toB58(new Uint8Array(bytes));
},
);
bcs.registerType(
BCS.BASE64,
function (writer: BcsWriter, data: string) {
return writer.writeVec(Array.from(fromB64(data)), (writer, el) => writer.write8(el));
},
function (reader: BcsReader) {
let bytes = reader.readVec((reader) => reader.read8());
return toB64(new Uint8Array(bytes));
},
);
}
export function getRustConfig(): BcsConfig {
return {
genericSeparators: ['<', '>'],
vectorType: 'Vec',
addressLength: SUI_ADDRESS_LENGTH,
addressEncoding: 'hex',
};
}
export function getSuiMoveConfig(): BcsConfig {
return {
genericSeparators: ['<', '>'],
vectorType: 'vector',
addressLength: SUI_ADDRESS_LENGTH,
addressEncoding: 'hex',
};
}
export function splitGenericParameters(
str: string,
genericSeparators: [string, string] = ['<', '>'],
) {
const [left, right] = genericSeparators;
const tok = [];
let word = '';
let nestedAngleBrackets = 0;
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === left) {
nestedAngleBrackets++;
}
if (char === right) {
nestedAngleBrackets--;
}
if (nestedAngleBrackets === 0 && char === ',') {
tok.push(word.trim());
word = '';
continue;
}
word += char;
}
tok.push(word.trim());
return tok;
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!