From 5599c577b20be7cd8c564d9e5708425882de5f1d Mon Sep 17 00:00:00 2001 From: hayes-mysten <135670682+hayes-mysten@users.noreply.github.com> Date: Mon, 18 Mar 2024 15:22:59 -0700 Subject: [PATCH] Remove legacy bcs registry (#16425) Describe the changes or additions included in this PR. How did you test the new or updated feature? --- If your changes are not user-facing and do not break anything, you can skip the following section. Otherwise, please briefly describe what has changed under the Release Notes section. - [ ] protocol change - [ ] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration --- apps/wallet/src/ui/app/WalletSigner.ts | 2 +- sdk/bcs/src/index.ts | 2 - sdk/bcs/src/legacy-registry.ts | 1144 ----------------- sdk/bcs/tests/alias.test.ts | 42 - sdk/bcs/tests/array.type.test.ts | 112 -- sdk/bcs/tests/bcs.test.ts | 156 +-- sdk/bcs/tests/config.test.ts | 58 - sdk/bcs/tests/encodings.test.ts | 53 +- sdk/bcs/tests/generics.test.ts | 49 - sdk/bcs/tests/inline-definition.test.ts | 50 - sdk/bcs/tests/nested.object.test.ts | 77 -- sdk/bcs/tests/parse-type-name.test.ts | 19 - sdk/bcs/tests/readme.test.ts | 276 ---- sdk/bcs/tests/serde.test.ts | 186 --- sdk/bcs/tests/vector.generics.test.ts | 21 - sdk/deepbook/src/client.ts | 29 +- sdk/deepbook/src/types/bcs.ts | 22 +- sdk/docs/pages/typescript/v1-migration.mdx | 4 + sdk/graphql-transport/package.json | 2 +- sdk/kiosk/src/bcs.ts | 36 +- sdk/kiosk/src/query/transfer-policy.ts | 8 +- sdk/kiosk/src/utils.ts | 15 +- sdk/typescript/package.json | 2 +- sdk/typescript/src/bcs/index.ts | 101 +- sdk/typescript/src/transactions/Inputs.ts | 4 - .../src/transactions/TransactionBlock.ts | 7 +- .../transactions/TransactionBlockPlugin.ts | 160 +-- .../__tests__/Transaction.test.ts | 2 +- .../src/transactions/__tests__/bcs.test.ts | 332 ++--- sdk/typescript/src/transactions/bcs.ts | 33 - .../src/transactions/blockData/v1.ts | 2 +- .../src/transactions/blockData/v2.ts | 1 - .../test/e2e/data/coin_metadata/Move.lock | 5 + .../test/e2e/data/dynamic_fields/Move.lock | 2 +- .../test/e2e/data/id_entry_args/Move.lock | 5 + .../test/e2e/data/serializer/Move.lock | 2 +- sdk/typescript/test/e2e/dev-inspect.test.ts | 2 +- .../test/unit/cryptography/keypair.test.ts | 4 +- .../cryptography/multisig.publickey.test.ts | 8 +- .../test/unit/cryptography/publickey.test.ts | 2 +- 40 files changed, 334 insertions(+), 2703 deletions(-) delete mode 100644 sdk/bcs/src/legacy-registry.ts delete mode 100644 sdk/bcs/tests/alias.test.ts delete mode 100644 sdk/bcs/tests/array.type.test.ts delete mode 100644 sdk/bcs/tests/config.test.ts delete mode 100644 sdk/bcs/tests/generics.test.ts delete mode 100644 sdk/bcs/tests/inline-definition.test.ts delete mode 100644 sdk/bcs/tests/nested.object.test.ts delete mode 100644 sdk/bcs/tests/parse-type-name.test.ts delete mode 100644 sdk/bcs/tests/readme.test.ts delete mode 100644 sdk/bcs/tests/serde.test.ts delete mode 100644 sdk/bcs/tests/vector.generics.test.ts delete mode 100644 sdk/typescript/src/transactions/bcs.ts diff --git a/apps/wallet/src/ui/app/WalletSigner.ts b/apps/wallet/src/ui/app/WalletSigner.ts index 2889a7eef0070f..c3f4f2ab9f2920 100644 --- a/apps/wallet/src/ui/app/WalletSigner.ts +++ b/apps/wallet/src/ui/app/WalletSigner.ts @@ -41,7 +41,7 @@ export abstract class WalletSigner { const signature = await this.signData( messageWithIntent( IntentScope.PersonalMessage, - bcs.ser(['vector', 'u8'], input.message).toBytes(), + bcs.vector(bcs.u8()).serialize(input.message).toBytes(), ), ); diff --git a/sdk/bcs/src/index.ts b/sdk/bcs/src/index.ts index e41d1eac1d0ba5..383014249e20b1 100644 --- a/sdk/bcs/src/index.ts +++ b/sdk/bcs/src/index.ts @@ -23,8 +23,6 @@ import { decodeStr, encodeStr, splitGenericParameters } from './utils.js'; import type { BcsWriterOptions } from './writer.js'; import { BcsWriter } from './writer.js'; -export * from './legacy-registry.js'; - // Re-export all encoding dependencies. export { bcs, diff --git a/sdk/bcs/src/legacy-registry.ts b/sdk/bcs/src/legacy-registry.ts deleted file mode 100644 index 63f56a6298a510..00000000000000 --- a/sdk/bcs/src/legacy-registry.ts +++ /dev/null @@ -1,1144 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { fromB58, toB58 } from './b58.js'; -import { fromB64, toB64 } from './b64.js'; -import { BcsType } from './bcs-type.js'; -import { fromHEX, toHEX } from './hex.js'; -import { BcsReader } from './reader.js'; -import type { Encoding } from './types.js'; -import { decodeStr, splitGenericParameters } from './utils.js'; -import type { BcsWriterOptions } from './writer.js'; -import { BcsWriter } from './writer.js'; - -/** - * Allows for array definitions for names. - * @example - * ``` - * bcs.registerStructType(['vector', BCS.STRING], ...); - * // equals - * bcs.registerStructType('vector', ...); - * ``` - */ -export type TypeName = string | [string, ...(TypeName | string)[]]; - -const SUI_ADDRESS_LENGTH = 32; - -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', { - * 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` 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 { - // Predefined types constants - static readonly U8 = 'u8'; - static readonly U16 = 'u16'; - static readonly U32 = 'u32'; - static readonly U64 = 'u64'; - static readonly U128 = 'u128'; - static readonly U256 = 'u256'; - static readonly BOOL = 'bool'; - static readonly VECTOR = 'vector'; - static readonly ADDRESS = 'address'; - static readonly STRING = 'string'; - static readonly HEX = 'hex-string'; - static readonly BASE58 = 'base58-string'; - static readonly BASE64 = '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 = 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'); - * - * let serialized = BCS - * .set('vector', [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', '', '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; - } - - /** - * Method to register BcsType instances to the registry - * Types are registered with a callback that provides BcsType instances for each generic - * passed to the type. - * - * - createType(...generics) - Return a BcsType instance - * - * @example - * // our type would be a string that consists only of numbers - * bcs.registerType('Box', (T) => { - * return bcs.struct({ - * value: T - * }); - * }); - - * console.log(Array.from(bcs.ser('Box', '12345').toBytes()) == [5,1,2,3,4,5]); - * - * @param name - * @param createType a Callback to create the BcsType with any passed in generics - */ - public registerBcsType( - typeName: TypeName, - createType: (...params: BcsType[]) => BcsType, - ) { - this.registerType( - typeName, - (writer, data, typeParams) => { - const generics = typeParams.map( - (param) => - new BcsType({ - name: String(param), - write: (data, writer) => { - const { name, params } = this.parseTypeName(param); - const typeInterface = this.getTypeInterface(name); - - const typeMap = (params as string[]).reduce((acc: any, value: string, index) => { - return Object.assign(acc, { [value]: typeParams[index] }); - }, {}); - - return typeInterface._encodeRaw.call(this, writer, data, params, typeMap); - }, - read: () => { - throw new Error('Not implemented'); - }, - }), - ); - - createType(...generics).write(data, writer); - return writer; - }, - (reader, typeParams) => { - const generics = typeParams.map( - (param) => - new BcsType({ - name: String(param), - write: (_data, _writer) => { - throw new Error('Not implemented'); - }, - read: (reader) => { - const { name, params } = this.parseTypeName(param); - const typeInterface = this.getTypeInterface(name); - - const typeMap = (params as string[]).reduce((acc: any, value: string, index) => { - return Object.assign(acc, { [value]: typeParams[index] }); - }, {}); - - return typeInterface._decodeRaw.call(this, reader, params, typeMap); - }, - }), - ); - - return createType(...generics).read(reader); - }, - ); - - 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'); // generic registration - * let array = bcs.de('vector', '06010203040506', 'hex'); // [1,2,3,4,5,6]; - * let again = bcs.ser('vector', [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, // name // Vec 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', - * 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>'); - * // typeName: Option - * // typeParams: [ 'Coin' ] - * - * @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 }; - } -} - -/** - * 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', - }; -} diff --git a/sdk/bcs/tests/alias.test.ts b/sdk/bcs/tests/alias.test.ts deleted file mode 100644 index 83f6dd5cf8f728..00000000000000 --- a/sdk/bcs/tests/alias.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Aliases', () => { - it('should support type aliases', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = 'this is a string'; - - bcs.registerAlias('MyString', BCS.STRING); - expect(serde(bcs, 'MyString', value)).toEqual(value); - }); - - it('should support recursive definitions in structs', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { name: 'Billy' }; - - bcs.registerAlias('UserName', BCS.STRING); - expect(serde(bcs, { name: 'UserName' }, value)).toEqual(value); - }); - - it('should spot recursive definitions', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = 'this is a string'; - - bcs.registerAlias('MyString', BCS.STRING); - bcs.registerAlias(BCS.STRING, 'MyString'); - - let error = null; - try { - serde(bcs, 'MyString', value); - } catch (e) { - error = e; - } - - expect(error).toBeInstanceOf(Error); - }); -}); diff --git a/sdk/bcs/tests/array.type.test.ts b/sdk/bcs/tests/array.type.test.ts deleted file mode 100644 index d62f32a89a6253..00000000000000 --- a/sdk/bcs/tests/array.type.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Array type', () => { - it('should support destructured type name in ser/de', () => { - const bcs = new BCS(getSuiMoveConfig()); - const values = ['this is a string']; - - expect(serde(bcs, ['vector', BCS.STRING], values)).toEqual(values); - }); - - it('should support destructured type name in struct', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { - name: 'Bob', - role: 'Admin', - meta: { - lastLogin: '23 Feb', - isActive: false, - }, - }; - - bcs.registerStructType('Metadata', { - lastLogin: BCS.STRING, - isActive: BCS.BOOL, - }); - - bcs.registerStructType(['User', 'T'], { - name: BCS.STRING, - role: BCS.STRING, - meta: 'T', - }); - - expect(serde(bcs, ['User', 'Metadata'], value)).toEqual(value); - }); - - it('should support destructured type name in enum', () => { - const bcs = new BCS(getSuiMoveConfig()); - const values = { some: ['this is a string'] }; - - bcs.registerEnumType(['Option', 'T'], { - none: null, - some: 'T', - }); - - expect(serde(bcs, ['Option', ['vector', 'string']], values)).toEqual(values); - }); - - it('should solve nested generic issue', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { - contents: { - content_one: { key: 'A', value: 'B' }, - content_two: { key: 'C', value: 'D' }, - }, - }; - - bcs.registerStructType(['Entry', 'K', 'V'], { - key: 'K', - value: 'V', - }); - - bcs.registerStructType(['Wrapper', 'A', 'B'], { - content_one: 'A', - content_two: 'B', - }); - - bcs.registerStructType(['VecMap', 'K', 'V'], { - contents: ['Wrapper', ['Entry', 'K', 'V'], ['Entry', 'V', 'K']], - }); - - expect(serde(bcs, ['VecMap', 'string', 'string'], value)).toEqual(value); - }); - - // More complicated invariant of the test case above - it('should support arrays in global generics', () => { - const bcs = new BCS(getSuiMoveConfig()); - bcs.registerEnumType(['Option', 'T'], { - none: null, - some: 'T', - }); - const value = { - contents: { - content_one: { key: { some: 'A' }, value: ['B'] }, - content_two: { key: [], value: { none: true } }, - }, - }; - - bcs.registerStructType(['Entry', 'K', 'V'], { - key: 'K', - value: 'V', - }); - - bcs.registerStructType(['Wrapper', 'A', 'B'], { - content_one: 'A', - content_two: 'B', - }); - - bcs.registerStructType(['VecMap', 'K', 'V'], { - contents: ['Wrapper', ['Entry', 'K', 'V'], ['Entry', 'V', 'K']], - }); - - expect(serde(bcs, ['VecMap', ['Option', 'string'], ['vector', 'string']], value)).toEqual( - value, - ); - }); -}); diff --git a/sdk/bcs/tests/bcs.test.ts b/sdk/bcs/tests/bcs.test.ts index 05f28ee2cc6f4c..57456ef8c5a0a7 100644 --- a/sdk/bcs/tests/bcs.test.ts +++ b/sdk/bcs/tests/bcs.test.ts @@ -3,136 +3,14 @@ import { describe, expect, it } from 'vitest'; -import { BCS, fromB64, getSuiMoveConfig } from './../src/index'; +import { bcs, fromB64 } from './../src/index'; describe('BCS: Primitives', () => { - it('should de/ser primitives: u8', () => { - const bcs = new BCS(getSuiMoveConfig()); - - expect(bcs.de('u8', fromB64('AQ=='))).toEqual(1); - expect(bcs.de('u8', fromB64('AA=='))).toEqual(0); - }); - - it('should ser/de u64', () => { - const bcs = new BCS(getSuiMoveConfig()); - - const exp = 'AO/Nq3hWNBI='; - const num = '1311768467750121216'; - const set = bcs.ser('u64', num).toString('base64'); - - expect(set).toEqual(exp); - expect(bcs.de('u64', exp, 'base64')).toEqual('1311768467750121216'); - }); - - it('should ser/de u128', () => { - const bcs = new BCS(getSuiMoveConfig()); - - const sample = 'AO9ld3CFjD48AAAAAAAAAA=='; - const num = BigInt('1111311768467750121216'); - - expect(bcs.de('u128', sample, 'base64').toString(10)).toEqual('1111311768467750121216'); - expect(bcs.ser('u128', num).toString('base64')).toEqual(sample); - }); - - it('should de/ser custom objects', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('Coin', { - value: BCS.U64, - owner: BCS.STRING, - is_locked: BCS.BOOL, - }); - - const rustBcs = 'gNGxBWAAAAAOQmlnIFdhbGxldCBHdXkA'; - const expected = { - owner: 'Big Wallet Guy', - value: '412412400000', - is_locked: false, - }; - - const setBytes = bcs.ser('Coin', expected); - - expect(bcs.de('Coin', fromB64(rustBcs))).toEqual(expected); - expect(setBytes.toString('base64')).toEqual(rustBcs); - }); - - it('should de/ser vectors', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // Rust-bcs generated vector with 1000 u8 elements (FF) - const sample = largebcsVec(); - - // deserialize data with JS - const deserialized = bcs.de('vector', fromB64(sample)); - - // create the same vec with 1000 elements - let arr = Array.from(Array(1000)).map(() => 255); - const serialized = bcs.ser('vector', arr); - - expect(deserialized.length).toEqual(1000); - expect(serialized.toString('base64')).toEqual(largebcsVec()); - }); - - it('should de/ser enums', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('Coin', { value: 'u64' }); - bcs.registerEnumType('Enum', { - single: 'Coin', - multi: 'vector', - }); - - // prepare 2 examples from Rust bcs - let example1 = fromB64('AICWmAAAAAAA'); - let example2 = fromB64('AQIBAAAAAAAAAAIAAAAAAAAA'); - - // serialize 2 objects with the same data and signature - let set1 = bcs.ser('Enum', { single: { value: 10000000 } }).toBytes(); - let set2 = bcs - .ser('Enum', { - multi: [{ value: 1 }, { value: 2 }], - }) - .toBytes(); - - // deserialize and compare results - expect(bcs.de('Enum', example1)).toEqual(bcs.de('Enum', set1)); - expect(bcs.de('Enum', example2)).toEqual(bcs.de('Enum', set2)); - }); - - it('should de/ser addresses', () => { - const bcs = new BCS( - Object.assign(getSuiMoveConfig(), { - addressLength: 16, - addressEncoding: 'hex', - }), - ); - - // Move Kitty example: - // Wallet { kitties: vector, owner: address } - // Kitty { id: 'u8' } - - // bcs.registerAddressType('address', 16, 'base64'); // Move has 16/20/32 byte addresses - bcs.registerStructType('Kitty', { id: 'u8' }); - bcs.registerStructType('Wallet', { - kitties: 'vector', - owner: 'address', - }); - - // Generated with Move CLI i.e. on the Move side - let sample = 'AgECAAAAAAAAAAAAAAAAAMD/7g=='; - let data = bcs.de('Wallet', fromB64(sample)); - - expect(data.kitties).toHaveLength(2); - expect(data.owner).toEqual('00000000000000000000000000c0ffee'); - }); - it('should support growing size', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('Coin', { - value: BCS.U64, - owner: BCS.STRING, - is_locked: BCS.BOOL, + const Coin = bcs.struct('Coin', { + value: bcs.u64(), + owner: bcs.string(), + is_locked: bcs.bool(), }); const rustBcs = 'gNGxBWAAAAAOQmlnIFdhbGxldCBHdXkA'; @@ -142,19 +20,17 @@ describe('BCS: Primitives', () => { is_locked: false, }; - const setBytes = bcs.ser('Coin', expected, { size: 1, maxSize: 1024 }); + const setBytes = Coin.serialize(expected, { size: 1, maxSize: 1024 }); - expect(bcs.de('Coin', fromB64(rustBcs))).toEqual(expected); - expect(setBytes.toString('base64')).toEqual(rustBcs); + expect(Coin.parse(fromB64(rustBcs))).toEqual(expected); + expect(setBytes.toBase64()).toEqual(rustBcs); }); it('should error when attempting to grow beyond the allowed size', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('Coin', { - value: BCS.U64, - owner: BCS.STRING, - is_locked: BCS.BOOL, + const Coin = bcs.struct('Coin', { + value: bcs.u64(), + owner: bcs.string(), + is_locked: bcs.bool(), }); const expected = { @@ -163,12 +39,6 @@ describe('BCS: Primitives', () => { is_locked: false, }; - expect(() => bcs.ser('Coin', expected, { size: 1 })).toThrowError(); + expect(() => Coin.serialize(expected, { size: 1 })).toThrowError(); }); }); - -// @ts-ignore - -function largebcsVec(): string { - return '6Af/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////'; -} diff --git a/sdk/bcs/tests/config.test.ts b/sdk/bcs/tests/config.test.ts deleted file mode 100644 index 794bb68958b039..00000000000000 --- a/sdk/bcs/tests/config.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getRustConfig, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Config', () => { - it('should work with Rust config', () => { - const bcs = new BCS(getRustConfig()); - const value = ['beep', 'boop', 'beep']; - expect(serde(bcs, 'Vec', value)).toEqual(value); - }); - - it('should work with Sui Move config', () => { - const bcs = new BCS(getSuiMoveConfig()); - let value = ['beep', 'boop', 'beep']; - expect(serde(bcs, 'vector', value)).toEqual(value); - }); - - it('should fork config', () => { - const bcs_v1 = new BCS(getSuiMoveConfig()); - bcs_v1.registerStructType('User', { name: 'string' }); - - const bcs_v2 = new BCS(bcs_v1); - bcs_v2.registerStructType('Worker', { user: 'User', experience: 'u64' }); - - expect(bcs_v1.hasType('Worker')).toBeFalsy(); - expect(bcs_v2.hasType('Worker')).toBeTruthy(); - }); - - it('should work with custom config', () => { - const bcs = new BCS({ - genericSeparators: ['[', ']'], - addressLength: 1, - addressEncoding: 'hex', - vectorType: 'array', - types: { - structs: { - SiteConfig: { tags: 'array[Name]' }, - }, - enums: { - 'Option[T]': { none: null, some: 'T' }, - }, - aliases: { - Name: 'string', - }, - }, - }); - - const value_1 = { tags: ['beep', 'boop', 'beep'] }; - expect(serde(bcs, 'SiteConfig', value_1)).toEqual(value_1); - - const value_2 = { some: ['what', 'do', 'we', 'test'] }; - expect(serde(bcs, 'Option[array[string]]', value_2)).toEqual(value_2); - }); -}); diff --git a/sdk/bcs/tests/encodings.test.ts b/sdk/bcs/tests/encodings.test.ts index 754388521e07ab..f60252088de4ce 100644 --- a/sdk/bcs/tests/encodings.test.ts +++ b/sdk/bcs/tests/encodings.test.ts @@ -3,57 +3,20 @@ import { describe, expect, it } from 'vitest'; -import { - BCS, - fromB58, - fromB64, - fromHEX, - getSuiMoveConfig, - toB58, - toB64, - toHEX, -} from './../src/index'; +import { bcs, fromB58, fromB64, fromHEX, toHEX } from './../src/index'; describe('BCS: Encodings', () => { it('should de/ser hex, base58 and base64', () => { - const bcs = new BCS(getSuiMoveConfig()); - - expect(bcs.de('u8', 'AA==', 'base64')).toEqual(0); - expect(bcs.de('u8', '00', 'hex')).toEqual(0); - expect(bcs.de('u8', '1', 'base58')).toEqual(0); + expect(bcs.u8().parse(fromB64('AA=='))).toEqual(0); + expect(bcs.u8().parse(fromHEX('00'))).toEqual(0); + expect(bcs.u8().parse(fromB58('1'))).toEqual(0); const STR = 'this is a test string'; - const str = bcs.ser('string', STR); - - expect(bcs.de('string', fromB58(str.toString('base58')), 'base58')).toEqual(STR); - expect(bcs.de('string', fromB64(str.toString('base64')), 'base64')).toEqual(STR); - expect(bcs.de('string', fromHEX(str.toString('hex')), 'hex')).toEqual(STR); - }); - - it('should de/ser native encoding types', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('TestStruct', { - hex: BCS.HEX, - base58: BCS.BASE58, - base64: BCS.BASE64, - }); - - let hex_str = toHEX(new Uint8Array([1, 2, 3, 4, 5, 6])); - let b58_str = toB58(new Uint8Array([1, 2, 3, 4, 5, 6])); - let b64_str = toB64(new Uint8Array([1, 2, 3, 4, 5, 6])); - - let serialized = bcs.ser('TestStruct', { - hex: hex_str, - base58: b58_str, - base64: b64_str, - }); - - let deserialized = bcs.de('TestStruct', serialized.toBytes()); + const str = bcs.string().serialize(STR); - expect(deserialized.hex).toEqual(hex_str); - expect(deserialized.base58).toEqual(b58_str); - expect(deserialized.base64).toEqual(b64_str); + expect(bcs.string().parse(fromB58(str.toBase58()))).toEqual(STR); + expect(bcs.string().parse(fromB64(str.toBase64()))).toEqual(STR); + expect(bcs.string().parse(fromHEX(str.toHex()))).toEqual(STR); }); it('should deserialize hex with leading 0s', () => { diff --git a/sdk/bcs/tests/generics.test.ts b/sdk/bcs/tests/generics.test.ts deleted file mode 100644 index cf99e1fead4803..00000000000000 --- a/sdk/bcs/tests/generics.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from './../src/index'; - -describe('BCS: Generics', () => { - it('should handle generics', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerEnumType('base::Option', { - none: null, - some: 'T', - }); - - expect(bcs.de('base::Option', '00', 'hex')).toEqual({ none: true }); - }); - - it('should handle nested generics', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerEnumType('base::Option', { - none: null, - some: 'T', - }); - - bcs.registerStructType('base::Container', { - tag: 'T', - data: 'base::Option', - }); - - expect(bcs.de('base::Container', '0000', 'hex')).toEqual({ - tag: false, - data: { none: true }, - }); - - bcs.registerStructType('base::Wrapper', { - wrapped: 'base::Container', - }); - - expect(bcs.de('base::Wrapper', '0000', 'hex')).toEqual({ - wrapped: { - tag: false, - data: { none: true }, - }, - }); - }); -}); diff --git a/sdk/bcs/tests/inline-definition.test.ts b/sdk/bcs/tests/inline-definition.test.ts deleted file mode 100644 index 822bb8b2f3ca78..00000000000000 --- a/sdk/bcs/tests/inline-definition.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Inline struct definitions', () => { - it('should de/serialize inline definition', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { - t1: 'Adam', - t2: '1000', - t3: ['aabbcc', '00aa00', '00aaffcc'], - }; - - expect( - serde( - bcs, - { - t1: 'string', - t2: 'u64', - t3: 'vector', - }, - value, - ), - ).toEqual(value); - }); - - it('should not contain a trace of the temp struct', () => { - const bcs = new BCS(getSuiMoveConfig()); - const _sr = bcs - .ser({ name: 'string', age: 'u8' }, { name: 'Charlie', age: 10 }) - .toString('hex'); - - expect(bcs.hasType('temp-struct')).toBe(false); - }); - - it('should avoid duplicate key', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('temp-struct', { a0: 'u8' }); - - const sr = serde(bcs, { b0: 'temp-struct' }, { b0: { a0: 0 } }); - - expect(bcs.hasType('temp-struct')).toBe(true); - expect(sr).toEqual({ b0: { a0: 0 } }); - }); -}); diff --git a/sdk/bcs/tests/nested.object.test.ts b/sdk/bcs/tests/nested.object.test.ts deleted file mode 100644 index 709b088a54d113..00000000000000 --- a/sdk/bcs/tests/nested.object.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Nested temp object', () => { - it('should support object as a type', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { name: { boop: 'beep', beep: '100' } }; - - bcs.registerStructType('Beep', { - name: { - boop: BCS.STRING, - beep: BCS.U64, - }, - }); - - expect(serde(bcs, 'Beep', value)).toEqual(value); - }); - - it('should support enum invariant as an object', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { - user: { - name: 'Bob', - age: 20, - }, - }; - - bcs.registerEnumType('AccountType', { - system: null, - user: { - name: BCS.STRING, - age: BCS.U8, - }, - }); - - expect(serde(bcs, 'AccountType', value)).toEqual(value); - }); - - it('should support a nested schema', () => { - const bcs = new BCS(getSuiMoveConfig()); - const value = { - some: { - account: { - user: 'Bob', - age: 20, - }, - meta: { - status: { - active: true, - }, - }, - }, - }; - - bcs.registerEnumType('Option', { - none: null, - some: { - account: { - user: BCS.STRING, - age: BCS.U8, - }, - meta: { - status: { - active: BCS.BOOL, - }, - }, - }, - }); - - expect(serde(bcs, 'Option', value)).toEqual(value); - }); -}); diff --git a/sdk/bcs/tests/parse-type-name.test.ts b/sdk/bcs/tests/parse-type-name.test.ts deleted file mode 100644 index 23ec4c98cf78c3..00000000000000 --- a/sdk/bcs/tests/parse-type-name.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src'; - -describe('parseTypeName', () => { - it('parses nested struct type from a string', () => { - const bcs = new BCS(getSuiMoveConfig()); - - const type = - '0x5::foo::Foo<0x5::bar::Bar, 0x6::amm::LP<0x2::sui::SUI, 0x7::example_coin::EXAMPLE_COIN>>'; - expect(bcs.parseTypeName(type)).toEqual({ - name: '0x5::foo::Foo', - params: ['0x5::bar::Bar', '0x6::amm::LP<0x2::sui::SUI, 0x7::example_coin::EXAMPLE_COIN>'], - }); - }); -}); diff --git a/sdk/bcs/tests/readme.test.ts b/sdk/bcs/tests/readme.test.ts deleted file mode 100644 index 2888ddb4177818..00000000000000 --- a/sdk/bcs/tests/readme.test.ts +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/** - * Please, use the code from this file to fill in the examples in - * the README. Manual needs to be correct for the best DevX. - */ - -import { describe, it } from 'vitest'; - -import { SUI_ADDRESS_LENGTH } from '../../typescript/src/utils'; -import { BCS, BcsWriter, getRustConfig, getSuiMoveConfig } from './../src/index'; - -describe('BCS: README Examples', () => { - it('quick start', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // registering types - bcs.registerAlias('UID', BCS.ADDRESS); - bcs.registerEnumType('Option', { - none: null, - some: 'T', - }); - bcs.registerStructType('Coin', { - id: 'UID', - value: BCS.U64, - }); - - // deserialization: BCS bytes into Coin - let bytes = bcs - .ser('Coin', { - id: '0000000000000000000000000000000000000000000000000000000000000001', - value: 1000000n, - }) - .toBytes(); - - let coin = bcs.de('Coin', bytes); - - // serialization: Object into bytes - let _data = bcs.ser('Option', { some: coin }).toString('hex'); - }); - - it('Example: All options used', () => { - const bcs = new BCS({ - vectorType: 'vector', - addressLength: SUI_ADDRESS_LENGTH, - addressEncoding: 'hex', - genericSeparators: ['<', '>'], - types: { - // define schema in the initializer - structs: { - User: { - name: BCS.STRING, - age: BCS.U8, - }, - }, - enums: {}, - aliases: { hex: BCS.HEX }, - }, - withPrimitives: true, - }); - - let _bytes = bcs.ser('User', { name: 'Adam', age: '30' }).toString('base64'); - }); - - it('initialization', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // use bcs.ser() to serialize data - const val = [1, 2, 3, 4]; - const ser = bcs.ser('vector', val).toBytes(); - - // use bcs.de() to deserialize data - const res = bcs.de('vector', ser); - - console.assert(res.toString() === val.toString()); - }); - - it('Example: Rust Config', () => { - const bcs = new BCS(getRustConfig()); - const val = [1, 2, 3, 4]; - const ser = bcs.ser('Vec', val).toBytes(); - const res = bcs.de('Vec', ser); - - console.assert(res.toString() === val.toString()); - }); - - it('Example: Primitive types', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // Integers - let _u8 = bcs.ser(BCS.U8, 100).toBytes(); - let _u64 = bcs.ser(BCS.U64, 1000000n).toString('hex'); - let _u128 = bcs.ser(BCS.U128, '100000010000001000000').toString('base64'); - - // Other types - let _bool = bcs.ser(BCS.BOOL, true).toString('hex'); - let _addr = bcs.ser(BCS.ADDRESS, '0000000000000000000000000000000000000001').toBytes(); - let _str = bcs.ser(BCS.STRING, 'this is an ascii string').toBytes(); - - // Vectors (vector) - let _u8_vec = bcs.ser('vector', [1, 2, 3, 4, 5, 6, 7]).toBytes(); - let _bool_vec = bcs.ser('vector', [true, true, false]).toBytes(); - let _str_vec = bcs.ser('vector', ['string1', 'string2', 'string3']).toBytes(); - - // Even vector of vector (...of vector) is an option - let _matrix = bcs - .ser('vector>', [ - [0, 0, 0], - [1, 1, 1], - [2, 2, 2], - ]) - .toBytes(); - }); - - it('Example: Ser/de and Encoding', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // bcs.ser() returns an instance of BcsWriter which can be converted to bytes or a string - let bcsWriter: BcsWriter = bcs.ser(BCS.STRING, 'this is a string'); - - // writer.toBytes() returns a Uint8Array - let bytes: Uint8Array = bcsWriter.toBytes(); - - // custom encodings can be chosen when needed (just like Buffer) - let hex: string = bcsWriter.toString('hex'); - let base64: string = bcsWriter.toString('base64'); - let base58: string = bcsWriter.toString('base58'); - - // bcs.de() reads BCS data and returns the value - // by default it expects data to be `Uint8Array` - let str1 = bcs.de(BCS.STRING, bytes); - - // alternatively, an encoding of input can be specified - let str2 = bcs.de(BCS.STRING, hex, 'hex'); - let str3 = bcs.de(BCS.STRING, base64, 'base64'); - let str4 = bcs.de(BCS.STRING, base58, 'base58'); - - console.assert((str1 === str2) === (str3 === str4), 'Result is the same'); - }); - - it('Example: Alias', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // When registering alias simply specify a new name for the type - bcs.registerAlias('ObjectDigest', BCS.BASE58); - - // ObjectDigest is now treated as base58 string - let _b58 = bcs.ser('ObjectDigest', 'Ldp').toBytes(); - - // we can override already existing definition - bcs.registerAlias('ObjectDigest', BCS.HEX); - - let _hex = bcs.ser('ObjectDigest', 'C0FFEE').toBytes(); - }); - - it('Example: Struct', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // register a custom type (it becomes available for using) - bcs.registerStructType('Balance', { - value: BCS.U64, - }); - - bcs.registerStructType('Coin', { - id: BCS.ADDRESS, - // reference another registered type - balance: 'Balance', - }); - - // value passed into ser function has to have the same - // structure as the definition - let _bytes = bcs - .ser('Coin', { - id: '0x0000000000000000000000000000000000000000000000000000000000000005', - balance: { - value: 100000000n, - }, - }) - .toBytes(); - }); - - it('Example: Generics', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // Container -> the name of the type - // T -> type parameter which has to be passed in `ser()` or `de()` methods - // If you're not familiar with generics, treat them as type Templates - bcs.registerStructType(['Container', 'T'], { - contents: 'T', - }); - - // When serializing, we have to pass the type to use for `T` - bcs - .ser(['Container', BCS.U8], { - contents: 100, - }) - .toString('hex'); - - // Reusing the same Container type with different contents. - // Mind that generics need to be passed as Array after the main type. - bcs - .ser(['Container', ['vector', BCS.BOOL]], { - contents: [true, false, true], - }) - .toString('hex'); - - // Using multiple generics - you can use any string for convenience and - // readability. See how we also use array notation for a field definition. - bcs.registerStructType(['VecMap', 'Key', 'Val'], { - keys: ['vector', 'Key'], - values: ['vector', 'Val'], - }); - - // To serialize VecMap, we can use: - bcs.ser(['VecMap', BCS.STRING, BCS.STRING], { - keys: ['key1', 'key2', 'key3'], - values: ['value1', 'value2', 'value3'], - }); - }); - - it('Example: Enum', () => { - const bcs = new BCS(getSuiMoveConfig()); - - bcs.registerEnumType('Option', { - none: null, - some: 'T', - }); - - bcs.registerEnumType('TransactionType', { - single: 'vector', - batch: 'vector>', - }); - - // any truthy value marks empty in struct value - let _optionNone = bcs.ser('Option', { - none: true, - }); - - // some now contains a value of type TransactionType - let _optionTx = bcs.ser('Option', { - some: { - single: [1, 2, 3, 4, 5, 6], - }, - }); - - // same type signature but a different enum invariant - batch - let _optionTxBatch = bcs.ser('Option', { - some: { - batch: [ - [1, 2, 3, 4, 5, 6], - [1, 2, 3, 4, 5, 6], - ], - }, - }); - }); - - it('Example: Inline Struct', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // Some value we want to serialize - const coin = { - id: '0000000000000000000000000000000000000000000000000000000000000005', - value: 1111333333222n, - }; - - // Instead of defining a type we pass struct schema as the first argument - let coin_bytes = bcs.ser({ id: BCS.ADDRESS, value: BCS.U64 }, coin).toBytes(); - - // Same with deserialization - let coin_restored = bcs.de({ id: BCS.ADDRESS, value: BCS.U64 }, coin_bytes); - - console.assert(coin.id === coin_restored.id, '`id` must match'); - console.assert(coin.value === coin_restored.value, '`value` must match'); - }); -}); diff --git a/sdk/bcs/tests/serde.test.ts b/sdk/bcs/tests/serde.test.ts deleted file mode 100644 index e0834d1a7fe4a1..00000000000000 --- a/sdk/bcs/tests/serde.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { SUI_ADDRESS_LENGTH } from '../../typescript/src/utils'; -import { BCS, getSuiMoveConfig } from './../src/index'; - -describe('BCS: Serde', () => { - it('should serialize primitives in both directions', () => { - const bcs = new BCS(getSuiMoveConfig()); - - expect(serde(bcs, 'u8', '0').toString(10)).toEqual('0'); - expect(serde(bcs, 'u8', '200').toString(10)).toEqual('200'); - expect(serde(bcs, 'u8', '255').toString(10)).toEqual('255'); - - expect(serde(bcs, 'u16', '10000').toString(10)).toEqual('10000'); - expect(serde(bcs, 'u32', '10000').toString(10)).toEqual('10000'); - expect(serde(bcs, 'u128', '154386538175093611946334810').toString(10)).toEqual( - '154386538175093611946334810', - ); - - expect( - serde(bcs, 'u256', '154386538175093611946334810000000000000000000000122').toString(10), - ).toEqual('154386538175093611946334810000000000000000000000122'); - - expect(bcs.ser('u256', '100000').toString('hex')).toEqual( - 'a086010000000000000000000000000000000000000000000000000000000000', - ); - - expect(serde(bcs, 'u64', '1000').toString(10)).toEqual('1000'); - expect(serde(bcs, 'u128', '1000').toString(10)).toEqual('1000'); - expect(serde(bcs, 'u256', '1000').toString(10)).toEqual('1000'); - - expect(serde(bcs, 'bool', true)).toEqual(true); - expect(serde(bcs, 'bool', false)).toEqual(false); - - expect( - serde(bcs, 'address', '0x000000000000000000000000e3edac2c684ddbba5ad1a2b90fb361100b2094af'), - ).toEqual('000000000000000000000000e3edac2c684ddbba5ad1a2b90fb361100b2094af'); - }); - - it('should serde structs', () => { - let bcs = new BCS(getSuiMoveConfig()); - - bcs.registerAddressType('address', SUI_ADDRESS_LENGTH, 'hex'); - bcs.registerStructType('Beep', { id: 'address', value: 'u64' }); - - let bytes = bcs - .ser('Beep', { - id: '0x00000000000000000000000045aacd9ed90a5a8e211502ac3fa898a3819f23b2', - value: 10000000, - }) - .toBytes(); - let struct = bcs.de('Beep', bytes); - - expect(struct.id).toEqual('00000000000000000000000045aacd9ed90a5a8e211502ac3fa898a3819f23b2'); - expect(struct.value.toString(10)).toEqual('10000000'); - }); - - it('should serde enums', () => { - let bcs = new BCS(getSuiMoveConfig()); - bcs.registerAddressType('address', SUI_ADDRESS_LENGTH, 'hex'); - bcs.registerEnumType('Enum', { - with_value: 'address', - no_value: null, - }); - - let addr = 'bb967ddbebfee8c40d8fdd2c24cb02452834cd3a7061d18564448f900eb9e66d'; - - expect(addr).toEqual( - bcs.de('Enum', bcs.ser('Enum', { with_value: addr }).toBytes()).with_value, - ); - expect( - 'no_value' in bcs.de('Enum', bcs.ser('Enum', { no_value: null }).toBytes()), - ).toBeTruthy(); - }); - - it('should serde vectors natively', () => { - let bcs = new BCS(getSuiMoveConfig()); - - { - let value = ['0', '255', '100']; - expect(serde(bcs, 'vector', value).map((e) => e.toString(10))).toEqual(value); - } - - { - let value = ['100000', '555555555', '1123123', '0', '1214124124214']; - expect(serde(bcs, 'vector', value).map((e) => e.toString(10))).toEqual(value); - } - - { - let value = ['100000', '555555555', '1123123', '0', '1214124124214']; - expect(serde(bcs, 'vector', value).map((e) => e.toString(10))).toEqual(value); - } - - { - let value = [true, false, false, true, false]; - expect(serde(bcs, 'vector', value)).toEqual(value); - } - - { - let value = [ - '000000000000000000000000e3edac2c684ddbba5ad1a2b90fb361100b2094af', - '0000000000000000000000000000000000000000000000000000000000000001', - '0000000000000000000000000000000000000000000000000000000000000002', - '000000000000000000000000c0ffeec0ffeec0ffeec0ffeec0ffeec0ffee1337', - ]; - - expect(serde(bcs, 'vector
', value)).toEqual(value); - } - - { - let value = [ - [true, false, true, true], - [true, true, false, true], - [false, true, true, true], - [true, true, true, false], - ]; - - expect(serde(bcs, 'vector>', value)).toEqual(value); - } - }); - - it('should structs and nested enums', () => { - let bcs = new BCS(getSuiMoveConfig()); - - bcs.registerStructType('User', { age: 'u64', name: 'string' }); - bcs.registerStructType('Coin', { balance: 'Balance' }); - bcs.registerStructType('Balance', { value: 'u64' }); - - bcs.registerStructType('Container', { - owner: 'address', - is_active: 'bool', - item: 'T', - }); - - { - let value = { age: '30', name: 'Bob' }; - expect(serde(bcs, 'User', value).age.toString(10)).toEqual(value.age); - expect(serde(bcs, 'User', value).name).toEqual(value.name); - } - - { - let value = { - owner: '0000000000000000000000000000000000000000000000000000000000000001', - is_active: true, - item: { balance: { value: '10000' } }, - }; - - // Deep Nested Generic! - let result = serde(bcs, 'Container>>', value); - - expect(result.owner).toEqual(value.owner); - expect(result.is_active).toEqual(value.is_active); - expect(result.item.balance.value.toString(10)).toEqual(value.item.balance.value); - } - }); - - it('should serde SuiObjectRef', () => { - const bcs = new BCS(getSuiMoveConfig()); - bcs.registerStructType('SuiObjectRef', { - objectId: 'address', - version: 'u64', - digest: 'ObjectDigest', - }); - - // console.log('base58', toB64('1Bhh3pU9gLXZhoVxkr5wyg9sX6')); - - bcs.registerAlias('ObjectDigest', BCS.STRING); - - const value = { - objectId: '5443700000000000000000000000000000000000000000000000000000000000', - version: '9180', - digest: 'hahahahahaha', - }; - - expect(serde(bcs, 'SuiObjectRef', value)).toEqual(value); - }); -}); - -function serde(bcs: BCS, type, data) { - let ser = bcs.ser(type, data).toString('hex'); - let de = bcs.de(type, ser, 'hex'); - return de; -} diff --git a/sdk/bcs/tests/vector.generics.test.ts b/sdk/bcs/tests/vector.generics.test.ts deleted file mode 100644 index bd2aa049869f3a..00000000000000 --- a/sdk/bcs/tests/vector.generics.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { describe, expect, it } from 'vitest'; - -import { BCS, getSuiMoveConfig } from '../src/index'; -import { serde } from './utils'; - -describe('BCS: Inline struct definitions', () => { - it('should de/serialize inline definition', () => { - const bcs = new BCS(getSuiMoveConfig()); - - // reported by kklas: vector returns [undefined] - bcs.registerStructType(['FooType', 'T'], { - generic_vec: ['vector', 'T'], - }); - - const value = { generic_vec: ['1', '2', '3'] }; - expect(serde(bcs, ['FooType', 'u64'], value)).toEqual(value); - }); -}); diff --git a/sdk/deepbook/src/client.ts b/sdk/deepbook/src/client.ts index 35d9ffca185d6d..21b3439f6da78e 100644 --- a/sdk/deepbook/src/client.ts +++ b/sdk/deepbook/src/client.ts @@ -1,6 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +import { bcs } from '@mysten/sui.js/bcs'; import type { OrderArguments, PaginatedEvents, PaginationArguments } from '@mysten/sui.js/client'; import { getFullnodeUrl, SuiClient } from '@mysten/sui.js/client'; import type { @@ -17,6 +18,7 @@ import { SUI_CLOCK_OBJECT_ID, } from '@mysten/sui.js/utils'; +import { BcsOrder } from './types/bcs.js'; import type { Level2BookStatusPoint, MarketPrice, @@ -25,7 +27,7 @@ import type { PoolSummary, UserPosition, } from './types/index.js'; -import { bcs, LimitOrderType, SelfMatchingPreventionStyle } from './types/index.js'; +import { LimitOrderType, SelfMatchingPreventionStyle } from './types/index.js'; import { CREATION_FEE, MODULE_CLOB, @@ -570,7 +572,7 @@ export class DeepBookClient { return undefined; } - return bcs.de('Order', Uint8Array.from(results![0].returnValues![0][0])); + return BcsOrder.parse(Uint8Array.from(results![0].returnValues![0][0])); } /** @@ -595,7 +597,7 @@ export class DeepBookClient { transactionBlock: txb, sender: this.currentAddress, }) - ).results![0].returnValues!.map(([bytes, _]) => BigInt(bcs.de('u64', Uint8Array.from(bytes)))); + ).results![0].returnValues!.map(([bytes, _]) => BigInt(bcs.U64.parse(Uint8Array.from(bytes)))); return { availableBaseAmount, lockedBaseAmount, @@ -633,7 +635,7 @@ export class DeepBookClient { return []; } - return bcs.de('vector', Uint8Array.from(results![0].returnValues![0][0])); + return bcs.vector(BcsOrder).parse(Uint8Array.from(results![0].returnValues![0][0])); } /** @@ -653,8 +655,8 @@ export class DeepBookClient { sender: this.currentAddress, }) ).results![0].returnValues!.map(([bytes, _]) => { - const opt = bcs.de('Option', Uint8Array.from(bytes)); - return 'Some' in opt ? BigInt(opt.Some) : undefined; + const opt = bcs.option(bcs.U64).parse(Uint8Array.from(bytes)); + return opt == null ? undefined : BigInt(opt); }); return { bestBidPrice: resp[0], bestAskPrice: resp[1] }; @@ -715,10 +717,16 @@ export class DeepBookClient { if (side === 'both') { const bidSide = results.results![0].returnValues!.map(([bytes, _]) => - bcs.de('vector', Uint8Array.from(bytes)).map((s: string) => BigInt(s)), + bcs + .vector(bcs.U64) + .parse(Uint8Array.from(bytes)) + .map((s: string) => BigInt(s)), ); const askSide = results.results![1].returnValues!.map(([bytes, _]) => - bcs.de('vector', Uint8Array.from(bytes)).map((s: string) => BigInt(s)), + bcs + .vector(bcs.U64) + .parse(Uint8Array.from(bytes)) + .map((s: string) => BigInt(s)), ); return [ bidSide[0].map((price: bigint, i: number) => ({ price, depth: bidSide[1][i] })), @@ -726,7 +734,10 @@ export class DeepBookClient { ]; } else { const result = results.results![0].returnValues!.map(([bytes, _]) => - bcs.de('vector', Uint8Array.from(bytes)).map((s: string) => BigInt(s)), + bcs + .vector(bcs.U64) + .parse(Uint8Array.from(bytes)) + .map((s) => BigInt(s)), ); return result[0].map((price: bigint, i: number) => ({ price, depth: result[1][i] })); } diff --git a/sdk/deepbook/src/types/bcs.ts b/sdk/deepbook/src/types/bcs.ts index bcbde1e122d9ad..85fa60507b5959 100644 --- a/sdk/deepbook/src/types/bcs.ts +++ b/sdk/deepbook/src/types/bcs.ts @@ -3,16 +3,14 @@ import { bcs } from '@mysten/sui.js/bcs'; -bcs.registerStructType('Order', { - orderId: 'u64', - clientOrderId: 'u64', - price: 'u64', - originalQuantity: 'u64', - quantity: 'u64', - isBid: 'bool', - owner: 'address', - expireTimestamp: 'u64', - selfMatchingPrevention: 'u8', +export const BcsOrder = bcs.struct('Order', { + orderId: bcs.u64(), + clientOrderId: bcs.u64(), + price: bcs.u64(), + originalQuantity: bcs.u64(), + quantity: bcs.u64(), + isBid: bcs.bool(), + owner: bcs.Address, + expireTimestamp: bcs.u64(), + selfMatchingPrevention: bcs.u8(), }); - -export { bcs }; diff --git a/sdk/docs/pages/typescript/v1-migration.mdx b/sdk/docs/pages/typescript/v1-migration.mdx index 341cce3e786f6f..ca26860075801e 100644 --- a/sdk/docs/pages/typescript/v1-migration.mdx +++ b/sdk/docs/pages/typescript/v1-migration.mdx @@ -60,3 +60,7 @@ Removed `is` and `assert` helpers - cleanup typescript/bcs exports - maybe remove intent exports from cryptography and just use bcs intent encoding/decoding directly - Should fromB64, toB64, fromHEX, toHEX still be exported from utils? +- Parse typeArguments from string in input +- rename normalizeInputs hook +- accept `(txb) -> txb.object` as txb argument? +- handle string and address structs when encoding raw values (see getPureSerializationType) diff --git a/sdk/graphql-transport/package.json b/sdk/graphql-transport/package.json index e7c65e5f9bde73..cf82dcb0f321af 100644 --- a/sdk/graphql-transport/package.json +++ b/sdk/graphql-transport/package.json @@ -31,7 +31,7 @@ "prettier:check": "prettier -c --ignore-unknown .", "prettier:fix": "prettier -w --ignore-unknown .", "test:e2e:nowait": "vitest run e2e", - "test:e2e:prepare": "docker-compose down && docker-compose up -d && cargo build --bin sui-test-validator --bin sui --profile dev && cross-env RUST_LOG=info,sui=error,anemo_tower=warn,consensus=off cargo run --bin sui-test-validator -- --with-indexer --use-indexer-v2 --pg-port 5435 --pg-db-name sui_indexer_v2 --graphql-host 127.0.0.1 --graphql-port 9125", + "test:e2e:prepare": "docker-compose down && docker-compose up -d && cargo build --bin sui-test-validator --bin sui --profile dev && cross-env RUST_LOG=info,sui=error,anemo_tower=warn,consensus=off cargo run --bin sui-test-validator -- --with-indexer --pg-port 5435 --pg-db-name sui_indexer_v2 --graphql-host 127.0.0.1 --graphql-port 9125", "test:e2e": "wait-on http://127.0.0.1:9123 -l --timeout 180000 && vitest" }, "repository": { diff --git a/sdk/kiosk/src/bcs.ts b/sdk/kiosk/src/bcs.ts index 54aac820e1fc0f..e34ae8f5471c62 100644 --- a/sdk/kiosk/src/bcs.ts +++ b/sdk/kiosk/src/bcs.ts @@ -11,31 +11,29 @@ import { } from './types/index.js'; // Register the `Kiosk` struct for faster queries. -bcs.registerStructType(KIOSK_TYPE, { - id: 'address', - profits: 'u64', - owner: 'address', - itemCount: 'u32', - allowExtensions: 'bool', +export const KioskType = bcs.struct(KIOSK_TYPE, { + id: bcs.Address, + profits: bcs.u64(), + owner: bcs.Address, + itemCount: bcs.u32(), + allowExtensions: bcs.bool(), }); // Register the `PurchaseCap` for faster queries. -bcs.registerStructType(KIOSK_PURCHASE_CAP, { - id: 'address', - kioskId: 'address', - itemId: 'address', - minPrice: 'u64', +export const KioskPurchaseCap = bcs.struct(KIOSK_PURCHASE_CAP, { + id: bcs.Address, + kioskId: bcs.Address, + itemId: bcs.Address, + minPrice: bcs.u64(), }); // Register the `TransferPolicyCreated` event data. -bcs.registerStructType(TRANSFER_POLICY_CREATED_EVENT, { - id: 'address', +export const TransferPolicyCreatedEvent = bcs.struct(TRANSFER_POLICY_CREATED_EVENT, { + id: bcs.Address, }); -bcs.registerStructType(TRANSFER_POLICY_TYPE, { - id: 'address', - balance: 'u64', - rules: ['vector', 'string'], +export const TransferPolicyType = bcs.struct(TRANSFER_POLICY_TYPE, { + id: bcs.Address, + balance: bcs.u64(), + rules: bcs.vector(bcs.string()), }); - -export { bcs }; diff --git a/sdk/kiosk/src/query/transfer-policy.ts b/sdk/kiosk/src/query/transfer-policy.ts index 1ff8754154d03a..4a30c99af5f5b9 100644 --- a/sdk/kiosk/src/query/transfer-policy.ts +++ b/sdk/kiosk/src/query/transfer-policy.ts @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import type { SuiClient } from '@mysten/sui.js/client'; -import { isValidSuiAddress } from '@mysten/sui.js/utils'; +import { fromB64, isValidSuiAddress } from '@mysten/sui.js/utils'; -import { bcs } from '../bcs.js'; +import '../bcs.js'; + +import { TransferPolicyType } from '../bcs.js'; import type { TransferPolicy, TransferPolicyCap } from '../types/index.js'; import { TRANSFER_POLICY_CAP_TYPE, @@ -48,7 +50,7 @@ export async function queryTransferPolicy( throw new Error(`Invalid policy: ${policy?.objectId}, expected object, got package`); } - const parsed = bcs.de(TRANSFER_POLICY_TYPE, policy.bcs.bcsBytes, 'base64'); + const parsed = TransferPolicyType.parse(fromB64(policy.bcs.bcsBytes)); return { id: policy?.objectId, diff --git a/sdk/kiosk/src/utils.ts b/sdk/kiosk/src/utils.ts index 7bc4ce8c0ae192..9609a54ee09c14 100644 --- a/sdk/kiosk/src/utils.ts +++ b/sdk/kiosk/src/utils.ts @@ -10,11 +10,16 @@ import type { SuiObjectDataOptions, SuiObjectResponse, } from '@mysten/sui.js/client'; -import { normalizeStructTag, normalizeSuiAddress, parseStructTag } from '@mysten/sui.js/utils'; - -import { bcs } from './bcs.js'; +import { + fromB64, + normalizeStructTag, + normalizeSuiAddress, + parseStructTag, +} from '@mysten/sui.js/utils'; + +import { KioskType } from './bcs.js'; import type { Kiosk, KioskData, KioskListing, TransferPolicyCap } from './types/index.js'; -import { KIOSK_TYPE, TRANSFER_POLICY_CAP_TYPE } from './types/index.js'; +import { TRANSFER_POLICY_CAP_TYPE } from './types/index.js'; const DEFAULT_QUERY_LIMIT = 50; @@ -29,7 +34,7 @@ export async function getKioskObject(client: SuiClient, id: string): Promise': { - None: null, - Some: 'T', - }, - }, - }, -}); - function unsafe_u64(options?: BcsTypeOptions) { return bcs .u64({ @@ -163,34 +142,6 @@ function optionEnum>(type: T) { }); } -/** - * Wrapper around Enum, which transforms any `T` into an object with `kind` property: - * @example - * ``` - * let bcsEnum = { TransferObjects: { objects: [], address: ... } } - * // becomes - * let translatedEnum = { kind: 'TransferObjects', objects: [], address: ... }; - * ``` - */ -function enumKind(type: BcsType) { - type Merge = T extends infer U ? { [K in keyof U]: U[K] } : never; - type EnumKindTransform = T extends infer U - ? Merge<(U[keyof U] extends null | boolean ? object : U[keyof U]) & { kind: keyof U }> - : never; - - return type.transform({ - input: (val: EnumKindTransform) => - ({ - [val.kind]: val, - }) as Input, - output: (val) => { - const key = Object.keys(val)[0] as keyof T; - - return { kind: key, ...val[key] } as EnumKindTransform; - }, - }); -} - const Address = bcs.bytes(SUI_ADDRESS_LENGTH).transform({ input: (val: string | Uint8Array) => typeof val === 'string' ? fromHEX(normalizeSuiAddress(val)) : val, @@ -438,52 +389,6 @@ const suiBcs = { TransactionExpiration, TransactionKind, TypeTag, - - // preserve backwards compatibility with old bcs export - ser: bcsRegistry.ser.bind(bcsRegistry), - de: bcsRegistry.de.bind(bcsRegistry), - getTypeInterface: bcsRegistry.getTypeInterface.bind(bcsRegistry), - hasType: bcsRegistry.hasType.bind(bcsRegistry), - parseTypeName: bcsRegistry.parseTypeName.bind(bcsRegistry), - registerAddressType: bcsRegistry.registerAddressType.bind(bcsRegistry), - registerAlias: bcsRegistry.registerAlias.bind(bcsRegistry), - registerBcsType: bcsRegistry.registerBcsType.bind(bcsRegistry), - registerEnumType: bcsRegistry.registerEnumType.bind(bcsRegistry), - registerStructType: bcsRegistry.registerStructType.bind(bcsRegistry), - registerType: bcsRegistry.registerType.bind(bcsRegistry), - types: bcsRegistry.types, }; -bcsRegistry.registerBcsType('utf8string', () => bcs.string({ name: 'utf8string' })); -bcsRegistry.registerBcsType('unsafe_u64', () => unsafe_u64()); -bcsRegistry.registerBcsType('enumKind', (T) => enumKind(T)); - -[ - Address, - Argument, - CallArg, - CompressedSignature, - GasData, - MultiSig, - MultiSigPkMap, - MultiSigPublicKey, - ObjectArg, - ObjectDigest, - ProgrammableMoveCall, - ProgrammableTransaction, - PublicKey, - SenderSignedData, - SharedObjectRef, - StructTag, - SuiObjectRef, - Transaction, - TransactionData, - TransactionDataV1, - TransactionExpiration, - TransactionKind, - TypeTag, -].forEach((type) => { - bcsRegistry.registerBcsType(type.name, () => type); -}); - -export { suiBcs as bcs, bcsRegistry }; +export { suiBcs as bcs }; diff --git a/sdk/typescript/src/transactions/Inputs.ts b/sdk/typescript/src/transactions/Inputs.ts index ff48a31dda41ae..98dc655c2dcfae 100644 --- a/sdk/typescript/src/transactions/Inputs.ts +++ b/sdk/typescript/src/transactions/Inputs.ts @@ -82,10 +82,6 @@ export function getIdFromCallArg(arg: string | CallArg) { return normalizeSuiAddress(arg.UnresolvedObject.value); } - if (arg.RawValue && arg.RawValue.type === 'Object') { - return normalizeSuiAddress(arg.RawValue.value as string); - } - return undefined; } diff --git a/sdk/typescript/src/transactions/TransactionBlock.ts b/sdk/typescript/src/transactions/TransactionBlock.ts index fb26a9427ac6ae..752dbe542de09e 100644 --- a/sdk/typescript/src/transactions/TransactionBlock.ts +++ b/sdk/typescript/src/transactions/TransactionBlock.ts @@ -224,7 +224,7 @@ export class TransactionBlock { ? parse(NormalizedCallArg, value) : value instanceof Uint8Array ? Inputs.Pure(value) - : { $kind: 'RawValue', RawValue: { type: 'Pure', value } }, + : { $kind: 'RawValue', RawValue: { value } }, ); }), }); @@ -281,7 +281,10 @@ export class TransactionBlock { : this.#input( 'object', typeof value === 'string' - ? { $kind: 'RawValue', RawValue: { type: 'Object', value: normalizeSuiAddress(value) } } + ? { + $kind: 'UnresolvedObject', + UnresolvedObject: { value: normalizeSuiAddress(value), typeSignatures: [] }, + } : value, ); } diff --git a/sdk/typescript/src/transactions/TransactionBlockPlugin.ts b/sdk/typescript/src/transactions/TransactionBlockPlugin.ts index fcdc490eba9417..55652890f6ab0a 100644 --- a/sdk/typescript/src/transactions/TransactionBlockPlugin.ts +++ b/sdk/typescript/src/transactions/TransactionBlockPlugin.ts @@ -6,21 +6,17 @@ import { parse } from 'valibot'; import { bcs } from '../bcs/index.js'; import type { SuiClient } from '../client/client.js'; -import type { SuiMoveNormalizedType } from '../client/index.js'; import { SUI_TYPE_ARG } from '../utils/index.js'; import { normalizeSuiAddress, normalizeSuiObjectId } from '../utils/sui-types.js'; -import type { - Argument, - CallArg, - OpenMoveTypeSignature, - OpenMoveTypeSignatureBody, - Transaction, -} from './blockData/v2.js'; +import type { Argument, CallArg, OpenMoveTypeSignature, Transaction } from './blockData/v2.js'; import { ObjectRef } from './blockData/v2.js'; import { Inputs, isMutableSharedObjectInput } from './Inputs.js'; -import { getPureSerializationType, isTxContext } from './serializer.js'; +import { + isTxContext, + normalizedTypeToMoveTypeSignature, + pureBcsSchemaFromOpenMoveTypeSignatureBody, +} from './serializer.js'; import type { TransactionBlockDataBuilder } from './TransactionBlockData.js'; -import { extractStructTag } from './utils.js'; export type MaybePromise = T | Promise; export interface TransactionBlockPlugin { @@ -276,7 +272,9 @@ export class DefaultTransactionBlockFeatures implements TransactionBlockPlugin { } return null; }); - const needsResolution = inputs.some((input) => input && input.RawValue); + const needsResolution = inputs.some( + (input) => input && (input.RawValue || input.UnresolvedObject), + ); if (needsResolution) { moveModulesToResolve.push(transaction.MoveCall); @@ -329,63 +327,39 @@ export class DefaultTransactionBlockFeatures implements TransactionBlockPlugin { const inputValue = input.RawValue?.value ?? input.UnresolvedObject?.value!; - const serType = getPureSerializationType(param, inputValue); + const typeSignature = normalizedTypeToMoveTypeSignature(param); - if (serType) { - inputs[inputs.indexOf(input)] = Inputs.Pure(bcs.ser(serType, inputValue).toBytes()); + if (typeof typeSignature.body !== 'object' || 'vector' in typeSignature.body) { + const schema = pureBcsSchemaFromOpenMoveTypeSignatureBody(typeSignature.body); + inputs[inputs.indexOf(input)] = Inputs.Pure(schema.serialize(inputValue)); return; } - const structVal = extractStructTag(param); - if (structVal != null || (typeof param === 'object' && 'TypeParameter' in param)) { - if (typeof inputValue !== 'string') { - throw new Error( - `Expect the argument to be an object id string, got ${JSON.stringify( - inputValue, - null, - 2, - )}`, - ); - } - - if (input.$kind === 'RawValue') { - inputs[inputs.indexOf(input)] = { - $kind: 'UnresolvedObject', - UnresolvedObject: { - value: inputValue, - typeSignatures: [normalizedTypeToSignature(param)], - }, - }; - } else { - input.UnresolvedObject.typeSignatures.push(normalizedTypeToSignature(param)); - } - - return; + if (typeof inputValue !== 'string') { + throw new Error( + `Expect the argument to be an object id string, got ${JSON.stringify( + inputValue, + null, + 2, + )}`, + ); } - throw new Error( - `Unknown call arg type ${JSON.stringify(param, null, 2)} for value ${JSON.stringify( - inputValue, - null, - 2, - )}`, - ); + if (input.$kind === 'RawValue') { + inputs[inputs.indexOf(input)] = { + $kind: 'UnresolvedObject', + UnresolvedObject: { + value: inputValue, + typeSignatures: [typeSignature], + }, + }; + } else { + input.UnresolvedObject.typeSignatures.push(typeSignature); + } }); }), ); } - - blockData.inputs.forEach((input, index) => { - if (input.RawValue?.type === 'Object') { - inputs[index] = { - $kind: 'UnresolvedObject', - UnresolvedObject: { - value: input.RawValue.value as string, - typeSignatures: [], - }, - }; - } - }); }; validate: NonNullable = async (blockData, options) => { @@ -431,73 +405,3 @@ function isReceivingType(type: OpenMoveTypeSignature): boolean { type.body.datatype.type === 'Receiving' ); } - -function normalizedTypeToSignature(type: SuiMoveNormalizedType): OpenMoveTypeSignature { - if (typeof type === 'object' && 'Reference' in type) { - return { - ref: '&', - body: normalizedTypeToSignatureBody(type.Reference), - }; - } - - if (typeof type === 'object' && 'MutableReference' in type) { - return { - ref: '&mut', - body: normalizedTypeToSignatureBody(type.MutableReference), - }; - } - - return { - ref: null, - body: normalizedTypeToSignatureBody(type), - }; -} - -function normalizedTypeToSignatureBody(type: SuiMoveNormalizedType): OpenMoveTypeSignatureBody { - switch (type) { - case 'Address': - return 'address'; - case 'Bool': - return 'bool'; - case 'Signer': - throw new Error('Signer type is not expected'); - case 'U8': - return 'u8'; - case 'U16': - return 'u16'; - case 'U32': - return 'u32'; - case 'U64': - return 'u64'; - case 'U128': - return 'u128'; - case 'U256': - return 'u256'; - } - - if ('Struct' in type) { - return { - datatype: { - package: type.Struct.address, - module: type.Struct.module, - type: type.Struct.name, - typeParameters: type.Struct.typeArguments.map((param) => - normalizedTypeToSignatureBody(param), - ), - }, - }; - } - if ('Vector' in type) { - return { - vector: normalizedTypeToSignatureBody(type.Vector), - }; - } - - if ('TypeParameter' in type) { - return { - typeParameter: type.TypeParameter, - }; - } - - throw new Error(`Unknown type ${JSON.stringify(type, null, 2)}`); -} diff --git a/sdk/typescript/src/transactions/__tests__/Transaction.test.ts b/sdk/typescript/src/transactions/__tests__/Transaction.test.ts index a921defe6792c6..5d94d79fb8946d 100644 --- a/sdk/typescript/src/transactions/__tests__/Transaction.test.ts +++ b/sdk/typescript/src/transactions/__tests__/Transaction.test.ts @@ -101,7 +101,7 @@ describe('offline build', () => { it('supports pre-serialized inputs as Uint8Array', async () => { const tx = setup(); - const inputBytes = bcs.ser('u64', 100n).toBytes(); + const inputBytes = bcs.U64.serialize(100n).toBytes(); // Use bytes directly in pure value: tx.add(Transactions.SplitCoins(tx.gas, [tx.pure(inputBytes)])); await tx.build(); diff --git a/sdk/typescript/src/transactions/__tests__/bcs.test.ts b/sdk/typescript/src/transactions/__tests__/bcs.test.ts index d5507065079d22..881e0527126d6e 100644 --- a/sdk/typescript/src/transactions/__tests__/bcs.test.ts +++ b/sdk/typescript/src/transactions/__tests__/bcs.test.ts @@ -1,163 +1,195 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// import { toB58 } from '@mysten/bcs'; +import { toB58 } from '@mysten/bcs'; import { expect, it } from 'vitest'; -import { bcs } from '../../bcs/index.js'; - -// import { normalizeSuiAddress } from '../../utils/sui-types.js'; -// import { PROGRAMMABLE_CALL, TRANSACTION } from '../index.js'; +import { bcs, TypeTagSerializer } from '../../bcs/index.js'; +import { normalizeSuiAddress } from '../../utils/sui-types.js'; // Oooh-weeee we nailed it! -// it('can serialize simplified programmable call struct', () => { -// const moveCall = { -// kind: 'MoveCall', -// target: '0x2::display::new', -// typeArguments: ['0x6::capy::Capy'], -// arguments: [ -// { kind: 'GasCoin' }, -// { -// kind: 'NestedResult', -// index: 0, -// resultIndex: 1, -// }, -// { kind: 'Input', index: 3 }, -// { kind: 'Result', index: 1 }, -// ], -// }; - -// const bytes = bcs.ser(PROGRAMMABLE_CALL, moveCall).toBytes(); -// const result = bcs.de(PROGRAMMABLE_CALL, bytes); - -// // since we normalize addresses when (de)serializing, the returned value differs -// // only check the module and the function; ignore address comparison (it's not an issue -// // with non-0x2 addresses). -// expect(result.arguments).toEqual(moveCall.arguments); -// expect(result.target.split('::').slice(1)).toEqual(moveCall.target.split('::').slice(1)); -// expect(result.typeArguments[0].split('::').slice(1)).toEqual( -// moveCall.typeArguments[0].split('::').slice(1), -// ); -// }); - -// it('can serialize enum with "kind" property', () => { -// const transaction = { -// kind: 'TransferObjects', -// objects: [], -// address: { kind: 'Input', index: 0 }, -// }; +it('can serialize simplified programmable call struct', () => { + const moveCall = { + package: '0x2', + module: 'display', + function: 'new', + typeArguments: [TypeTagSerializer.parseFromStr('0x6::capy::Capy', true)], + arguments: [ + { + $kind: 'GasCoin', + GasCoin: true, + }, + { + $kind: 'NestedResult', + NestedResult: [0, 1], + }, + { + $kind: 'Input', + Input: 3, + }, + { + $kind: 'Result', + Result: 1, + }, + ], + } satisfies typeof bcs.ProgrammableMoveCall.$inferType; -// const bytes = bcs.ser(TRANSACTION, transaction).toBytes(); -// const result = bcs.de(TRANSACTION, bytes); + const bytes = bcs.ProgrammableMoveCall.serialize(moveCall).toBytes(); + const result = bcs.ProgrammableMoveCall.parse(bytes); -// expect(result).toEqual(transaction); -// }); - -it('can serialize Option types using the legacy registry API', () => { - const none = bcs.ser('Option', { None: true }).toBytes(); - const some = bcs.ser('Option', { Some: 2 }).toBytes(); - - expect(none).toEqual(new Uint8Array([0])); - expect(some).toEqual(new Uint8Array([1, 2])); + // since we normalize addresses when (de)serializing, the returned value differs + // only check the module and the function; ignore address comparison (it's not an issue + // with non-0x2 addresses). + expect(result.arguments).toEqual(moveCall.arguments); + expect(result.function).toEqual(moveCall.function); + expect(result.module).toEqual(moveCall.module); + expect(normalizeSuiAddress(result.package)).toEqual(normalizeSuiAddress(moveCall.package)); + expect(result.typeArguments[0]).toMatchObject(moveCall.typeArguments[0]); }); -// function ref(): { objectId: string; version: string; digest: string } { -// return { -// objectId: normalizeSuiAddress((Math.random() * 100000).toFixed(0).padEnd(64, '0')), -// version: String((Math.random() * 10000).toFixed(0)), -// digest: toB58(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), -// }; -// } +function ref(): { objectId: string; version: string; digest: string } { + return { + objectId: normalizeSuiAddress((Math.random() * 100000).toFixed(0).padEnd(64, '0')), + version: String((Math.random() * 10000).toFixed(0)), + digest: toB58(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), + }; +} -// it('can serialize transaction data with a programmable transaction', () => { -// let sui = normalizeSuiAddress('0x2'); -// let txData = { -// V1: { -// sender: normalizeSuiAddress('0xBAD'), -// expiration: { None: true }, -// gasData: { -// payment: [ref()], -// owner: sui, -// price: '1', -// budget: '1000000', -// }, -// kind: { -// ProgrammableTransaction: { -// inputs: [ -// // first argument is the publisher object -// { Object: { ImmOrOwned: ref() } }, -// // second argument is a vector of names -// { -// Pure: Array.from( -// bcs.ser('vector', ['name', 'description', 'img_url']).toBytes(), -// ), -// }, -// // third argument is a vector of values -// { -// Pure: Array.from( -// bcs -// .ser('vector', [ -// 'Capy {name}', -// 'A cute little creature', -// 'https://api.capy.art/{id}/svg', -// ]) -// .toBytes(), -// ), -// }, -// // 4th and last argument is the account address to send display to -// { -// Pure: Array.from(bcs.ser('address', ref().objectId).toBytes()), -// }, -// ], -// transactions: [ -// { -// kind: 'MoveCall', -// target: `${sui}::display::new`, -// typeArguments: [`${sui}::capy::Capy`], -// arguments: [ -// // publisher object -// { kind: 'Input', index: 0 }, -// ], -// }, -// { -// kind: 'MoveCall', -// target: `${sui}::display::add_multiple`, -// typeArguments: [`${sui}::capy::Capy`], -// arguments: [ -// // result of the first transaction -// { kind: 'Result', index: 0 }, -// // second argument - vector of names -// { kind: 'Input', index: 1 }, -// // third argument - vector of values -// { kind: 'Input', index: 2 }, -// ], -// }, -// { -// kind: 'MoveCall', -// target: `${sui}::display::update_version`, -// typeArguments: [`${sui}::capy::Capy`], -// arguments: [ -// // result of the first transaction again -// { kind: 'Result', index: 0 }, -// ], -// }, -// { -// kind: 'TransferObjects', -// objects: [ -// // the display object -// { kind: 'Result', index: 0 }, -// ], -// // address is also an input -// address: { kind: 'Input', index: 3 }, -// }, -// ], -// }, -// }, -// }, -// }; +it('can serialize transaction data with a programmable transaction', () => { + let sui = normalizeSuiAddress('0x2'); + let txData = { + $kind: 'V1', + V1: { + sender: normalizeSuiAddress('0xBAD'), + expiration: { $kind: 'None', None: true }, + gasData: { + payment: [ref()], + owner: sui, + price: '1', + budget: '1000000', + }, + kind: { + $kind: 'ProgrammableTransaction', + ProgrammableTransaction: { + inputs: [ + // first argument is the publisher object + { + $kind: 'Object', + Object: { + $kind: 'ImmOrOwnedObject', + ImmOrOwnedObject: ref(), + }, + }, + // second argument is a vector of names + { + $kind: 'Pure', + Pure: Array.from( + bcs.vector(bcs.String).serialize(['name', 'description', 'img_url']).toBytes(), + ), + }, + // third argument is a vector of values + { + $kind: 'Pure', + Pure: Array.from( + bcs + .vector(bcs.String) + .serialize([ + 'Capy {name}', + 'A cute little creature', + 'https://api.capy.art/{id}/svg', + ]) + .toBytes(), + ), + }, + // 4th and last argument is the account address to send display to + { + $kind: 'Pure', + Pure: Array.from(bcs.Address.serialize(ref().objectId).toBytes()), + }, + ], + transactions: [ + { + $kind: 'MoveCall', + MoveCall: { + package: sui, + module: 'display', + function: 'new', + typeArguments: [TypeTagSerializer.parseFromStr(`${sui}::capy::Capy`)], + arguments: [ + // publisher object + { + $kind: 'Input', + Input: 0, + }, + ], + }, + }, + { + $kind: 'MoveCall', + MoveCall: { + package: sui, + module: 'display', + function: 'add_multiple', + typeArguments: [TypeTagSerializer.parseFromStr(`${sui}::capy::Capy`)], + arguments: [ + // result of the first transaction + { + $kind: 'Result', + Result: 0, + }, + // second argument - vector of names + { + $kind: 'Input', + Input: 1, + }, + // third argument - vector of values + { + $kind: 'Input', + Input: 2, + }, + ], + }, + }, + { + $kind: 'MoveCall', + MoveCall: { + package: sui, + module: 'display', + function: 'update_version', + typeArguments: [TypeTagSerializer.parseFromStr(`${sui}::capy::Capy`)], + arguments: [ + // result of the first transaction again + { + $kind: 'Result', + Result: 0, + }, + ], + }, + }, + { + $kind: 'TransferObjects', + TransferObjects: [ + [ + // the display object + { + $kind: 'Result', + Result: 0, + }, + ], + // address is also an input + { + $kind: 'Input', + Input: 3, + }, + ], + }, + ], + }, + }, + }, + } satisfies typeof bcs.TransactionData.$inferType; -// const type = 'TransactionData'; -// const bytes = bcs.ser(type, txData).toBytes(); -// const result = bcs.de(type, bytes); -// expect(result).toEqual(txData); -// }); + const bytes = bcs.TransactionData.serialize(txData).toBytes(); + const result = bcs.TransactionData.parse(bytes); + expect(result).toMatchObject(txData); +}); diff --git a/sdk/typescript/src/transactions/bcs.ts b/sdk/typescript/src/transactions/bcs.ts deleted file mode 100644 index adaa55c26b34bd..00000000000000 --- a/sdk/typescript/src/transactions/bcs.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import type { TypeName } from '@mysten/bcs'; - -export const ARGUMENT_INNER = 'Argument'; -export const VECTOR = 'vector'; -export const OPTION = 'Option'; -export const CALL_ARG = 'CallArg'; -export const TYPE_TAG = 'TypeTag'; -export const OBJECT_ARG = 'ObjectArg'; -export const PROGRAMMABLE_TX_BLOCK = 'ProgrammableTransaction'; -export const PROGRAMMABLE_CALL_INNER = 'ProgrammableMoveCall'; -export const TRANSACTION_INNER = 'Transaction'; -export const COMPRESSED_SIGNATURE = 'CompressedSignature'; -export const PUBLIC_KEY = 'PublicKey'; -export const MULTISIG_PUBLIC_KEY = 'MultiSigPublicKey'; -export const MULTISIG_PK_MAP = 'MultiSigPkMap'; -export const MULTISIG = 'MultiSig'; - -export const ENUM_KIND = 'EnumKind'; - -/** Wrapper around transaction Enum to support `kind` matching in TS */ -export const TRANSACTION: TypeName = TRANSACTION_INNER; -/** Wrapper around Argument Enum to support `kind` matching in TS */ -export const ARGUMENT: TypeName = ARGUMENT_INNER; - -/** Custom serializer for decoding package, module, function easier */ -export const PROGRAMMABLE_CALL = 'ProgrammableMoveCall'; - -/** Transaction types */ - -export type Option = { some: T } | { none: true }; diff --git a/sdk/typescript/src/transactions/blockData/v1.ts b/sdk/typescript/src/transactions/blockData/v1.ts index a6e13a1a8b6944..7916b2e7804ede 100644 --- a/sdk/typescript/src/transactions/blockData/v1.ts +++ b/sdk/typescript/src/transactions/blockData/v1.ts @@ -236,7 +236,7 @@ export function v1BlockDataFromTransactionBlockState( if (input.RawValue) { return { kind: 'Input', - type: input.RawValue.type === 'Object' ? 'object' : 'pure', + type: 'pure', index, value: input.RawValue.value, }; diff --git a/sdk/typescript/src/transactions/blockData/v2.ts b/sdk/typescript/src/transactions/blockData/v2.ts index f7772510d6ca5c..2e74550fcecfb5 100644 --- a/sdk/typescript/src/transactions/blockData/v2.ts +++ b/sdk/typescript/src/transactions/blockData/v2.ts @@ -246,7 +246,6 @@ const CallArg = safeEnum({ // added for sui:rawValues RawValue: object({ value: unknown(), - type: nullish(union([literal('Pure'), literal('Object')])), }), }); export type CallArg = Output; diff --git a/sdk/typescript/test/e2e/data/coin_metadata/Move.lock b/sdk/typescript/test/e2e/data/coin_metadata/Move.lock index eb40308a0da6ae..59bfcc2df4c80a 100644 --- a/sdk/typescript/test/e2e/data/coin_metadata/Move.lock +++ b/sdk/typescript/test/e2e/data/coin_metadata/Move.lock @@ -20,3 +20,8 @@ source = { local = "../../../../../../crates/sui-framework/packages/sui-framewor dependencies = [ { name = "MoveStdlib" }, ] + +[move.toolchain-version] +compiler-version = "1.20.0" +edition = "legacy" +flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock b/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock index f7b3205442ded0..51b0f53092744d 100644 --- a/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock +++ b/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.18.0" +compiler-version = "1.20.0" edition = "legacy" flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/id_entry_args/Move.lock b/sdk/typescript/test/e2e/data/id_entry_args/Move.lock index badb39a1684dc9..31e5ff559fbeaa 100644 --- a/sdk/typescript/test/e2e/data/id_entry_args/Move.lock +++ b/sdk/typescript/test/e2e/data/id_entry_args/Move.lock @@ -20,3 +20,8 @@ source = { local = "../../../../../../crates/sui-framework/packages/sui-framewor dependencies = [ { name = "MoveStdlib" }, ] + +[move.toolchain-version] +compiler-version = "1.20.0" +edition = "legacy" +flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/serializer/Move.lock b/sdk/typescript/test/e2e/data/serializer/Move.lock index 7b307496c90c27..c89711153acfc1 100644 --- a/sdk/typescript/test/e2e/data/serializer/Move.lock +++ b/sdk/typescript/test/e2e/data/serializer/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.16.0" +compiler-version = "1.20.0" edition = "legacy" flavor = "sui" diff --git a/sdk/typescript/test/e2e/dev-inspect.test.ts b/sdk/typescript/test/e2e/dev-inspect.test.ts index c8a58fe0b11d05..8dc58826eb5038 100644 --- a/sdk/typescript/test/e2e/dev-inspect.test.ts +++ b/sdk/typescript/test/e2e/dev-inspect.test.ts @@ -47,7 +47,7 @@ describe('Test dev inspect', () => { const obj = tx.moveCall({ target: `${packageId}::serializer_tests::return_struct`, typeArguments: ['0x2::coin::Coin<0x2::sui::SUI>'], - arguments: [tx.pure.address(coin_0.coinObjectId)], + arguments: [tx.object(coin_0.coinObjectId)], }); // TODO: Ideally dev inspect transactions wouldn't need this, but they do for now diff --git a/sdk/typescript/test/unit/cryptography/keypair.test.ts b/sdk/typescript/test/unit/cryptography/keypair.test.ts index 12684fb315261b..095e87f7fbb493 100644 --- a/sdk/typescript/test/unit/cryptography/keypair.test.ts +++ b/sdk/typescript/test/unit/cryptography/keypair.test.ts @@ -45,7 +45,7 @@ describe('Keypair', () => { it('`signWithIntent()` should return the correct signature', async () => { const data = new Uint8Array([0, 0, 0, 5, 72, 101, 108, 108, 111]); - const bytes = bcs.ser(['vector', 'u8'], data).toBytes(); + const bytes = bcs.vector(bcs.U8).serialize(data).toBytes(); const sig1 = await k1.signWithIntent(bytes, IntentScope.PersonalMessage); const sig2 = await k2.signWithIntent(data, IntentScope.TransactionData); @@ -103,7 +103,7 @@ describe('Keypair', () => { const sig2 = await k2.signPersonalMessage(data); const sig3 = await k3.signPersonalMessage(data); - const bytes = bcs.ser(['vector', 'u8'], data).toBytes(); + const bytes = bcs.vector(bcs.U8).serialize(data).toBytes(); expect(sig1.bytes).toEqual(toB64(bytes)); expect(sig1.bytes).toEqual('CQAAAAVIZWxsbw=='); diff --git a/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts b/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts index 1a08bf86d74725..5bd0ed97853474 100644 --- a/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts @@ -246,7 +246,7 @@ describe('Publickey', () => { const maxLength = 1 + (64 + 1) * MAX_SIGNER_IN_MULTISIG + 2; const tmp = new Uint8Array(maxLength); tmp.set([0x03]); - tmp.set(bcs.ser('u16', 3).toBytes(), 1); + tmp.set(bcs.U16.serialize(3).toBytes(), 1); let i = 3; for (const { publicKey, weight } of multiSigPublicKey.getPublicKeys()) { const bytes = publicKey.toSuiBytes(); @@ -297,7 +297,7 @@ describe('Publickey', () => { const intentMessage = messageWithIntent( IntentScope.PersonalMessage, - bcs.ser(['vector', 'u8'], data).toBytes(), + bcs.vector(bcs.U8).serialize(data).toBytes(), ); const digest = blake2b(intentMessage, { dkLen: 32 }); @@ -323,7 +323,7 @@ describe('Publickey', () => { const intentMessage = messageWithIntent( IntentScope.PersonalMessage, - bcs.ser(['vector', 'u8'], data).toBytes(), + bcs.vector(bcs.U8).serialize(data).toBytes(), ); const digest = blake2b(intentMessage, { dkLen: 32 }); @@ -423,7 +423,7 @@ describe('Publickey', () => { const multisig = multiSigPublicKey.combinePartialSignatures([sig1.signature, sig2.signature]); const bytes = fromB64(multisig); - const multiSigStruct: MultiSigStruct = bcs.de('MultiSig', bytes.slice(1)); + const multiSigStruct: MultiSigStruct = bcs.MultiSig.parse(bytes.slice(1)); const parsedPartialSignatures = parsePartialSignatures(multiSigStruct); diff --git a/sdk/typescript/test/unit/cryptography/publickey.test.ts b/sdk/typescript/test/unit/cryptography/publickey.test.ts index edcb0d6cfd1fea..99d01db4d42a99 100644 --- a/sdk/typescript/test/unit/cryptography/publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/publickey.test.ts @@ -81,7 +81,7 @@ describe('Publickey', () => { expect( await pk1.verifyWithIntent( - bcs.ser(['vector', 'u8'], data).toBytes(), + bcs.vector(bcs.U8).serialize(data).toBytes(), sig1.signature, IntentScope.PersonalMessage, ),