UNPKG

@polkadot/types-codec

Version:
216 lines (215 loc) 6.9 kB
import { BN, bnToBn, bnToU8a, isBn, isNumber, isString, isU8a, isUndefined, objectProperties, stringify, stringPascalCase, u8aToBn, u8aToHex, u8aToU8a } from '@polkadot/util'; import { compareArray } from '../utils/index.js'; function encodeSet(setValues, values) { const encoded = new BN(0); for (let i = 0, count = values.length; i < count; i++) { encoded.ior(bnToBn(setValues[values[i]] || 0)); } return encoded; } /** @internal */ function decodeSetArray(setValues, values) { const count = values.length; const result = new Array(count); for (let i = 0; i < count; i++) { const key = values[i]; if (isUndefined(setValues[key])) { throw new Error(`Set: Invalid key '${key}' passed to Set, allowed ${Object.keys(setValues).join(', ')}`); } result[i] = key; } return result; } /** @internal */ function decodeSetNumber(setValues, _value) { const bn = bnToBn(_value); const keys = Object.keys(setValues); const result = []; for (let i = 0, count = keys.length; i < count; i++) { const key = keys[i]; if (bn.and(bnToBn(setValues[key])).eq(bnToBn(setValues[key]))) { result.push(key); } } const computed = encodeSet(setValues, result); if (!bn.eq(computed)) { throw new Error(`Set: Mismatch decoding '${bn.toString()}', computed as '${computed.toString()}' with ${result.join(', ')}`); } return result; } /** @internal */ function decodeSet(setValues, value = 0, bitLength) { if (bitLength % 8 !== 0) { throw new Error(`Expected valid bitLength, power of 8, found ${bitLength}`); } const byteLength = bitLength / 8; if (isU8a(value)) { return value.length === 0 ? [] : decodeSetNumber(setValues, u8aToBn(value.subarray(0, byteLength), { isLe: true })); } else if (isString(value)) { return decodeSet(setValues, u8aToU8a(value), byteLength); } else if (value instanceof Set || Array.isArray(value)) { const input = Array.isArray(value) ? value : [...value.values()]; return decodeSetArray(setValues, input); } return decodeSetNumber(setValues, value); } /** * @name Set * @description * An Set is an array of string values, represented an an encoded type by * a bitwise representation of the values. */ export class CodecSet extends Set { registry; createdAtHash; initialU8aLength; isStorageFallback; __internal__allowed; __internal__byteLength; constructor(registry, setValues, value, bitLength = 8) { super(decodeSet(setValues, value, bitLength)); this.registry = registry; this.__internal__allowed = setValues; this.__internal__byteLength = bitLength / 8; } static with(values, bitLength) { return class extends CodecSet { static { const keys = Object.keys(values); const count = keys.length; const isKeys = new Array(count); for (let i = 0; i < count; i++) { isKeys[i] = `is${stringPascalCase(keys[i])}`; } objectProperties(this.prototype, isKeys, (_, i, self) => self.strings.includes(keys[i])); } constructor(registry, value) { super(registry, values, value, bitLength); } }; } /** * @description The length of the value when encoded as a Uint8Array */ get encodedLength() { return this.__internal__byteLength; } /** * @description returns a hash of the contents */ get hash() { return this.registry.hash(this.toU8a()); } /** * @description true is the Set contains no values */ get isEmpty() { return this.size === 0; } /** * @description The actual set values as a string[] */ get strings() { return [...super.values()]; } /** * @description The encoded value for the set members */ get valueEncoded() { return encodeSet(this.__internal__allowed, this.strings); } /** * @description adds a value to the Set (extended to allow for validity checking) */ add = (key) => { // ^^^ add = () property done to assign this instance's this, otherwise Set.add creates "some" chaos // we have the isUndefined(this._setValues) in here as well, add is used internally // in the Set constructor (so it is undefined at this point, and should allow) if (this.__internal__allowed && isUndefined(this.__internal__allowed[key])) { throw new Error(`Set: Invalid key '${key}' on add`); } super.add(key); return this; }; /** * @description Compares the value of the input to see if there is a match */ eq(other) { if (Array.isArray(other)) { // we don't actually care about the order, sort the values return compareArray(this.strings.sort(), other.sort()); } else if (other instanceof Set) { return this.eq([...other.values()]); } else if (isNumber(other) || isBn(other)) { return this.valueEncoded.eq(bnToBn(other)); } return false; } /** * @description Returns a breakdown of the hex encoding for this Codec */ inspect() { return { outer: [this.toU8a()] }; } /** * @description Returns a hex string representation of the value */ toHex() { return u8aToHex(this.toU8a()); } /** * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information */ toHuman() { return this.toJSON(); } /** * @description Converts the Object to JSON, typically used for RPC transfers */ toJSON() { return this.strings; } /** * @description The encoded value for the set members */ toNumber() { return this.valueEncoded.toNumber(); } /** * @description Converts the value in a best-fit primitive form */ toPrimitive() { return this.toJSON(); } /** * @description Returns the base runtime type name for this instance */ toRawType() { return stringify({ _set: this.__internal__allowed }); } /** * @description Returns the string representation of the value */ toString() { return `[${this.strings.join(', ')}]`; } /** * @description Encodes the value as a Uint8Array as per the SCALE specifications */ toU8a(_isBare) { return bnToU8a(this.valueEncoded, { bitLength: this.__internal__byteLength * 8, isLe: true }); } }