From 3b9f348223c0f7e032da25492666ecb73ec0fcfc Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 21 Feb 2020 09:55:18 +0100 Subject: [PATCH] Cleanup IdentityFields (re-use Set) (#1912) * Cleanup IdentityFields * Small cleanups * Re-add commented IdentityFields --- packages/types/src/codec/Set.ts | 129 ++++++++++-------- .../src/primitive/IdentityFields.spec.ts | 12 +- .../types/src/primitive/IdentityFields.ts | 29 +--- 3 files changed, 79 insertions(+), 91 deletions(-) diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index 7f3e6fe352..af0a939516 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -6,7 +6,7 @@ import { H256 } from '../interfaces/runtime'; import { Codec, Constructor, Registry } from '../types'; import BN from 'bn.js'; -import { assert, bnToBn, isU8a, isNumber, isString, isUndefined, stringCamelCase, stringUpperFirst, u8aToHex, isBn, u8aToU8a } from '@polkadot/util'; +import { assert, bnToBn, bnToU8a, isBn, isU8a, isNumber, isString, isUndefined, stringCamelCase, stringUpperFirst, u8aToHex, u8aToBn, u8aToU8a } from '@polkadot/util'; import { blake2AsU8a } from '@polkadot/util-crypto'; import Raw from './Raw'; @@ -14,6 +14,60 @@ import { compareArray } from './utils'; type SetValues = Record; +function encodeSet (setValues: SetValues, value: string[]): BN { + return value.reduce((result, value): BN => { + return result.or(bnToBn(setValues[value] || 0)); + }, new BN(0)); +} + +/** @internal */ +function decodeSetArray (setValues: SetValues, value: string[]): string[] { + return value.reduce((result, key): string[] => { + assert(!isUndefined(setValues[key]), `Set: Invalid key '${key}' passed to Set, allowed ${Object.keys(setValues).join(', ')}`); + + result.push(key); + + return result; + }, [] as string[]); +} + +/** @internal */ +function decodeSetNumber (setValues: SetValues, _value: BN | number): string[] { + const bn = bnToBn(_value); + const result = Object.keys(setValues).reduce((result, key): string[] => { + if (bn.and(bnToBn(setValues[key])).eq(bnToBn(setValues[key]))) { + result.push(key); + } + + return result; + }, [] as string[]); + + const computed = encodeSet(setValues, result); + + assert(bn.eq(computed), `Set: Mismatch decoding '${bn}', computed as '${computed}' with ${result}`); + + return result; +} + +/** @internal */ +function decodeSet (setValues: SetValues, value: string[] | Set | Uint8Array | BN | number | string = 0, byteLength: number): string[] { + if (isString(value)) { + return decodeSet(setValues, u8aToU8a(value), byteLength); + } else if (isU8a(value)) { + return value.length === 0 + ? [] + : decodeSetNumber(setValues, u8aToBn(value.subarray(0, byteLength), { isLe: false })); + } 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 @@ -24,71 +78,26 @@ type SetValues = Record; export default class CodecSet extends Set implements Codec { public readonly registry: Registry; + protected _byteLength: number; + protected _setValues: SetValues; - constructor (registry: Registry, setValues: SetValues, value?: string[] | Set | Uint8Array | BN | number | string) { - super(CodecSet.decodeSet(setValues, value)); + constructor (registry: Registry, setValues: SetValues, value?: string[] | Set | Uint8Array | BN | number | string, bitLength = 8) { + assert(bitLength % 8 === 0, `Expected valid bitLength, power of 8, found ${bitLength}`); + + const byteLength = bitLength / 8; + + super(decodeSet(setValues, value, byteLength)); this.registry = registry; + this._byteLength = byteLength; this._setValues = setValues; } - /** @internal */ - public static decodeSet (setValues: SetValues, value: string[] | Set | Uint8Array | BN | number | string = 0): string[] { - if (isString(value)) { - return CodecSet.decodeSet(setValues, u8aToU8a(value)); - } else if (isU8a(value)) { - return value.length === 0 - ? [] - : CodecSet.decodeSetNumber(setValues, value[0]); - } else if (value instanceof Set) { - return CodecSet.decodeSetArray(setValues, [...value.values()]); - } else if (Array.isArray(value)) { - return CodecSet.decodeSetArray(setValues, value); - } - - return CodecSet.decodeSetNumber(setValues, value); - } - - /** @internal */ - private static decodeSetArray (setValues: SetValues, value: string[]): string[] { - return value.reduce((result, key): string[] => { - assert(!isUndefined(setValues[key]), `Set: Invalid key '${key}' passed to Set, allowed ${Object.keys(setValues).join(', ')}`); - - result.push(key); - - return result; - }, [] as string[]); - } - - /** @internal */ - private static decodeSetNumber (setValues: SetValues, _value: BN | number): string[] { - const bn = bnToBn(_value); - const result = Object.keys(setValues).reduce((result, key): string[] => { - if (bn.and(bnToBn(setValues[key])).eq(bnToBn(setValues[key]))) { - result.push(key); - } - - return result; - }, [] as string[]); - - const computed = CodecSet.encodeSet(setValues, result); - - assert(bn.eq(computed), `Set: Mismatch decoding '${bn}', computed as '${computed}' with ${result}`); - - return result; - } - - public static encodeSet (setValues: SetValues, value: string[]): BN { - return value.reduce((result, value): BN => { - return result.or(bnToBn(setValues[value] || 0)); - }, new BN(0)); - } - - public static with (values: SetValues): Constructor { + public static with (values: SetValues, bitLength?: number): Constructor { return class extends CodecSet { constructor (registry: Registry, value?: any) { - super(registry, values, value); + super(registry, values, value, bitLength); Object.keys(values).forEach((_key): void => { const name = stringUpperFirst(stringCamelCase(_key)); @@ -110,7 +119,7 @@ export default class CodecSet extends Set implements Codec { * @description The length of the value when encoded as a Uint8Array */ public get encodedLength (): number { - return 1; + return this._byteLength; } /** @@ -138,7 +147,7 @@ export default class CodecSet extends Set implements Codec { * @description The encoded value for the set members */ public get valueEncoded (): BN { - return CodecSet.encodeSet(this._setValues, this.strings); + return encodeSet(this._setValues, this.strings); } /** @@ -219,6 +228,6 @@ export default class CodecSet extends Set implements Codec { */ // eslint-disable-next-line @typescript-eslint/no-unused-vars public toU8a (isBare?: boolean): Uint8Array { - return new Uint8Array([this.valueEncoded.toNumber()]); + return bnToU8a(this.valueEncoded, { isLe: false, bitLength: this._byteLength * 8 }); } } diff --git a/packages/types/src/primitive/IdentityFields.spec.ts b/packages/types/src/primitive/IdentityFields.spec.ts index fa8dfc6f8f..d6c0e0944e 100644 --- a/packages/types/src/primitive/IdentityFields.spec.ts +++ b/packages/types/src/primitive/IdentityFields.spec.ts @@ -20,9 +20,15 @@ describe('IdentityFields', (): void => { ).toBe(true); }); - it('encodes to a valid hex value', (): void => { + it('encodes to a valid u8a value', (): void => { expect( - new IdentityFields(registry, ['Display', 'Legal']).toHex() - ).toEqual('0x0300000000000000'); + new IdentityFields(registry, ['Display', 'Legal']).toU8a() + ).toEqual(new Uint8Array([0, 0, 0, 0, 0, 0, 0, 3])); + }); + + it('decodes from a u8a', (): void => { + expect( + new IdentityFields(registry, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 1 + 2 + 64])).toHuman() + ).toEqual(['Display', 'Legal', 'Image']); }); }); diff --git a/packages/types/src/primitive/IdentityFields.ts b/packages/types/src/primitive/IdentityFields.ts index 4206a595b0..d86ae83cf9 100644 --- a/packages/types/src/primitive/IdentityFields.ts +++ b/packages/types/src/primitive/IdentityFields.ts @@ -5,21 +5,8 @@ import { Registry } from '../types'; import BN from 'bn.js'; -import { isString, isU8a, u8aToU8a } from '@polkadot/util'; import Set from '../codec/Set'; -import { createType } from '../create/createType'; - -/** @internal */ -function decodeFields (registry: Registry, value?: any): any { - if (isString(value)) { - return decodeFields(registry, u8aToU8a(value.toString())); - } else if (isU8a(value)) { - return createType(registry, 'u64', value); - } - - return value; -} /** * @name IdentityFields @@ -37,20 +24,6 @@ export default class IdentityFields extends Set { PgpFingerprint: new BN(0b0000000000000000000000000000000000000000000000000000000000100000), Image: new BN(0b0000000000000000000000000000000000000000000000000000000001000000), Twitter: new BN(0b0000000000000000000000000000000000000000000000000000000010000000) - }, decodeFields(registry, value)); - } - - /** - * @description The length of the value when encoded as a Uint8Array - */ - public get encodedLength (): number { - return 8; - } - - /** - * @description Encodes the value as a Uint8Array as per the SCALE specifications - */ - public toU8a (): Uint8Array { - return createType(this.registry, 'u64', this.valueEncoded).toU8a(); + }, value, 64); } }