{"version":3,"sources":["../../src/bcs/serializer.ts"],"sourcesContent":["// Copyright © Aptos Foundation\n// SPDX-License-Identifier: Apache-2.0\n\n/* eslint-disable no-bitwise */\nimport {\n  MAX_U128_BIG_INT,\n  MAX_U16_NUMBER,\n  MAX_U32_NUMBER,\n  MAX_U64_BIG_INT,\n  MAX_U8_NUMBER,\n  MAX_U256_BIG_INT,\n} from \"./consts\";\nimport { Hex } from \"../core/hex\";\nimport { AnyNumber, Uint16, Uint32, Uint8 } from \"../types\";\n\n// This class is intended to be used as a base class for all serializable types.\n// It can be used to facilitate composable serialization of a complex type and\n// in general to serialize a type to its BCS representation.\nexport abstract class Serializable {\n  abstract serialize(serializer: Serializer): void;\n\n  /**\n   * Serializes a `Serializable` value to its BCS representation.\n   * This function is the Typescript SDK equivalent of `bcs::to_bytes` in Move.\n   * @returns the BCS representation of the Serializable instance as a byte buffer\n   */\n  bcsToBytes(): Uint8Array {\n    const serializer = new Serializer();\n    this.serialize(serializer);\n    return serializer.toUint8Array();\n  }\n\n  /**\n   * Helper function to get a value's BCS-serialized bytes as a Hex instance.\n   * @returns a Hex instance with the BCS-serialized bytes loaded into its underlying Uint8Array\n   */\n  bcsToHex(): Hex {\n    const bcsBytes = this.bcsToBytes();\n    return Hex.fromHexInput(bcsBytes);\n  }\n}\n\nexport class Serializer {\n  private buffer: ArrayBuffer;\n\n  private offset: number;\n\n  // Constructs a serializer with a buffer of size `length` bytes, 64 bytes by default.\n  // `length` must be greater than 0.\n  constructor(length: number = 64) {\n    if (length <= 0) {\n      throw new Error(\"Length needs to be greater than 0\");\n    }\n    this.buffer = new ArrayBuffer(length);\n    this.offset = 0;\n  }\n\n  private ensureBufferWillHandleSize(bytes: number) {\n    while (this.buffer.byteLength < this.offset + bytes) {\n      const newBuffer = new ArrayBuffer(this.buffer.byteLength * 2);\n      new Uint8Array(newBuffer).set(new Uint8Array(this.buffer));\n      this.buffer = newBuffer;\n    }\n  }\n\n  protected appendToBuffer(values: Uint8Array) {\n    this.ensureBufferWillHandleSize(values.length);\n    new Uint8Array(this.buffer, this.offset).set(values);\n    this.offset += values.length;\n  }\n\n  private serializeWithFunction(\n    fn: (byteOffset: number, value: number, littleEndian?: boolean) => void,\n    bytesLength: number,\n    value: number,\n  ) {\n    this.ensureBufferWillHandleSize(bytesLength);\n    const dv = new DataView(this.buffer, this.offset);\n    fn.apply(dv, [0, value, true]);\n    this.offset += bytesLength;\n  }\n\n  /**\n   * Serializes a string. UTF8 string is supported.\n   *\n   * The number of bytes in the string content is serialized first, as a uleb128-encoded u32 integer.\n   * Then the string content is serialized as UTF8 encoded bytes.\n   *\n   * BCS layout for \"string\": string_length | string_content\n   * where string_length is a u32 integer encoded as a uleb128 integer, equal to the number of bytes in string_content.\n   *\n   * @example\n   * ```ts\n   * const serializer = new Serializer();\n   * serializer.serializeStr(\"1234abcd\");\n   * assert(serializer.toUint8Array() === new Uint8Array([8, 49, 50, 51, 52, 97, 98, 99, 100]));\n   * ```\n   */\n  serializeStr(value: string) {\n    const textEncoder = new TextEncoder();\n    this.serializeBytes(textEncoder.encode(value));\n  }\n\n  /**\n   * Serializes an array of bytes.\n   *\n   * BCS layout for \"bytes\": bytes_length | bytes\n   * where bytes_length is a u32 integer encoded as a uleb128 integer, equal to the length of the bytes array.\n   */\n  serializeBytes(value: Uint8Array) {\n    this.serializeU32AsUleb128(value.length);\n    this.appendToBuffer(value);\n  }\n\n  /**\n   * Serializes an array of bytes with known length. Therefore, length doesn't need to be\n   * serialized to help deserialization.\n   *\n   * When deserializing, the number of bytes to deserialize needs to be passed in.\n   */\n  serializeFixedBytes(value: Uint8Array) {\n    this.appendToBuffer(value);\n  }\n\n  /**\n   * Serializes a boolean value.\n   *\n   * BCS layout for \"boolean\": One byte. \"0x01\" for true and \"0x00\" for false.\n   */\n  serializeBool(value: boolean) {\n    ensureBoolean(value);\n    const byteValue = value ? 1 : 0;\n    this.appendToBuffer(new Uint8Array([byteValue]));\n  }\n\n  /**\n   * Serializes a uint8 number.\n   *\n   * BCS layout for \"uint8\": One byte. Binary format in little-endian representation.\n   */\n  @checkNumberRange(0, MAX_U8_NUMBER)\n  serializeU8(value: Uint8) {\n    this.appendToBuffer(new Uint8Array([value]));\n  }\n\n  /**\n   * Serializes a uint16 number.\n   *\n   * BCS layout for \"uint16\": Two bytes. Binary format in little-endian representation.\n   * @example\n   * ```ts\n   * const serializer = new Serializer();\n   * serializer.serializeU16(4660);\n   * assert(serializer.toUint8Array() === new Uint8Array([0x34, 0x12]));\n   * ```\n   */\n  @checkNumberRange(0, MAX_U16_NUMBER)\n  serializeU16(value: Uint16) {\n    this.serializeWithFunction(DataView.prototype.setUint16, 2, value);\n  }\n\n  /**\n   * Serializes a uint32 number.\n   *\n   * BCS layout for \"uint32\": Four bytes. Binary format in little-endian representation.\n   * @example\n   * ```ts\n   * const serializer = new Serializer();\n   * serializer.serializeU32(305419896);\n   * assert(serializer.toUint8Array() === new Uint8Array([0x78, 0x56, 0x34, 0x12]));\n   * ```\n   */\n  @checkNumberRange(0, MAX_U32_NUMBER)\n  serializeU32(value: Uint32) {\n    this.serializeWithFunction(DataView.prototype.setUint32, 4, value);\n  }\n\n  /**\n   * Serializes a uint64 number.\n   *\n   * BCS layout for \"uint64\": Eight bytes. Binary format in little-endian representation.\n   * @example\n   * ```ts\n   * const serializer = new Serializer();\n   * serializer.serializeU64(1311768467750121216);\n   * assert(serializer.toUint8Array() === new Uint8Array([0x00, 0xEF, 0xCD, 0xAB, 0x78, 0x56, 0x34, 0x12]));\n   * ```\n   */\n  @checkNumberRange(BigInt(0), MAX_U64_BIG_INT)\n  serializeU64(value: AnyNumber) {\n    const low = BigInt(value) & BigInt(MAX_U32_NUMBER);\n    const high = BigInt(value) >> BigInt(32);\n\n    // write little endian number\n    this.serializeU32(Number(low));\n    this.serializeU32(Number(high));\n  }\n\n  /**\n   * Serializes a uint128 number.\n   *\n   * BCS layout for \"uint128\": Sixteen bytes. Binary format in little-endian representation.\n   */\n  @checkNumberRange(BigInt(0), MAX_U128_BIG_INT)\n  serializeU128(value: AnyNumber) {\n    const low = BigInt(value) & MAX_U64_BIG_INT;\n    const high = BigInt(value) >> BigInt(64);\n\n    // write little endian number\n    this.serializeU64(low);\n    this.serializeU64(high);\n  }\n\n  /**\n   * Serializes a uint256 number.\n   *\n   * BCS layout for \"uint256\": Sixteen bytes. Binary format in little-endian representation.\n   */\n  @checkNumberRange(BigInt(0), MAX_U256_BIG_INT)\n  serializeU256(value: AnyNumber) {\n    const low = BigInt(value) & MAX_U128_BIG_INT;\n    const high = BigInt(value) >> BigInt(128);\n\n    // write little endian number\n    this.serializeU128(low);\n    this.serializeU128(high);\n  }\n\n  /**\n   * Serializes a uint32 number with uleb128.\n   *\n   * BCS uses uleb128 encoding in two cases: (1) lengths of variable-length sequences and (2) tags of enum values\n   */\n  @checkNumberRange(0, MAX_U32_NUMBER)\n  serializeU32AsUleb128(val: Uint32) {\n    let value = val;\n    const valueArray = [];\n    while (value >>> 7 !== 0) {\n      valueArray.push((value & 0x7f) | 0x80);\n      value >>>= 7;\n    }\n    valueArray.push(value);\n    this.appendToBuffer(new Uint8Array(valueArray));\n  }\n\n  /**\n   * Returns the buffered bytes\n   */\n  toUint8Array(): Uint8Array {\n    return new Uint8Array(this.buffer).slice(0, this.offset);\n  }\n\n  /**\n   * Serializes a `Serializable` value, facilitating composable serialization.\n   *\n   * @param value The Serializable value to serialize\n   *\n   * @example\n   * // Define the MoveStruct class that implements the Serializable interface\n   * class MoveStruct extends Serializable {\n   *     constructor(\n   *         public creatorAddress: AccountAddress, // where AccountAddress extends Serializable\n   *         public collectionName: string,\n   *         public tokenName: string\n   *     ) {}\n   *\n   *     serialize(serializer: Serializer): void {\n   *         serializer.serialize(this.creatorAddress);  // Composable serialization of another Serializable object\n   *         serializer.serializeStr(this.collectionName);\n   *         serializer.serializeStr(this.tokenName);\n   *     }\n   * }\n   *\n   * // Construct a MoveStruct\n   * const moveStruct = new MoveStruct(new AccountAddress(...), \"MyCollection\", \"TokenA\");\n   *\n   * // Serialize a string, a u64 number, and a MoveStruct instance.\n   * const serializer = new Serializer();\n   * serializer.serializeStr(\"ExampleString\");\n   * serializer.serializeU64(12345678);\n   * serializer.serialize(moveStruct);\n   *\n   * // Get the bytes from the Serializer instance\n   * const serializedBytes = serializer.toUint8Array();\n   *\n   * @returns the serializer instance\n   */\n  serialize<T extends Serializable>(value: T): void {\n    // NOTE: The `serialize` method called by `value` is defined in `value`'s\n    // Serializable interface, not the one defined in this class.\n    value.serialize(this);\n  }\n\n  /**\n   * Serializes an array of BCS Serializable values to a serializer instance.\n   * Note that this does not return anything. The bytes are added to the serializer instance's byte buffer.\n   *\n   * @param values The array of BCS Serializable values\n   * @example\n   * const addresses = new Array<AccountAddress>(\n   *   AccountAddress.from(\"0x1\"),\n   *   AccountAddress.from(\"0x2\"),\n   *   AccountAddress.from(\"0xa\"),\n   *   AccountAddress.from(\"0xb\"),\n   * );\n   * const serializer = new Serializer();\n   * serializer.serializeVector(addresses);\n   * const serializedBytes = serializer.toUint8Array();\n   * // serializedBytes is now the BCS-serialized bytes\n   * // The equivalent value in Move would be:\n   * // `bcs::to_bytes(&vector<address> [@0x1, @0x2, @0xa, @0xb])`;\n   */\n  serializeVector<T extends Serializable>(values: Array<T>): void {\n    this.serializeU32AsUleb128(values.length);\n    values.forEach((item) => {\n      item.serialize(this);\n    });\n  }\n}\n\nexport function ensureBoolean(value: unknown): asserts value is boolean {\n  if (typeof value !== \"boolean\") {\n    throw new Error(`${value} is not a boolean value`);\n  }\n}\n\nexport const outOfRangeErrorMessage = (value: AnyNumber, min: AnyNumber, max: AnyNumber) =>\n  `${value} is out of range: [${min}, ${max}]`;\n\nexport function validateNumberInRange<T extends AnyNumber>(value: T, minValue: T, maxValue: T) {\n  const valueBigInt = BigInt(value);\n  if (valueBigInt > BigInt(maxValue) || valueBigInt < BigInt(minValue)) {\n    throw new Error(outOfRangeErrorMessage(value, minValue, maxValue));\n  }\n}\n\n/**\n * A decorator to ensure the input argument for a function is within a range.\n * @param minValue The input argument must be >= minValue\n * @param maxValue The input argument must be <= maxValue\n */\nfunction checkNumberRange<T extends AnyNumber>(minValue: T, maxValue: T) {\n  return (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => {\n    const childFunction = descriptor.value;\n    // eslint-disable-next-line no-param-reassign\n    descriptor.value = function deco(value: AnyNumber) {\n      validateNumberInRange(value, minValue, maxValue);\n      return childFunction.apply(this, [value]);\n    };\n\n    return descriptor;\n  };\n}\n"],"mappings":"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","names":["Serializable","serializer","Serializer","bcsBytes","Hex","length","bytes","newBuffer","values","fn","bytesLength","value","dv","textEncoder","ensureBoolean","byteValue","low","MAX_U32_NUMBER","high","MAX_U64_BIG_INT","MAX_U128_BIG_INT","val","valueArray","item","__decorateClass","checkNumberRange","MAX_U8_NUMBER","MAX_U16_NUMBER","MAX_U256_BIG_INT","outOfRangeErrorMessage","min","max","validateNumberInRange","minValue","maxValue","valueBigInt","target","propertyKey","descriptor","childFunction"]}