diff --git a/CHANGELOG.md b/CHANGELOG.md index bab13e7896..ad205073c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Changes: - Add Polkadot `EthereumAddress` as a known `PortableRegistry` override - De-dupe `PortableRegistry` namespace splitting operations - Adjust internal `AbstractArray` & `Struct` decoders with inital u8a lengths +- Shortcut `Struct` construction from `Struct` instance - Use internal `lookupName` in `createClass` (allows deeply recursive lookups) - Update known upgrade blocks for Westend & Kusama - Bump static metadata for latest Substrate, Polkadot & Kusama diff --git a/packages/types/src/codec/Struct.spec.ts b/packages/types/src/codec/Struct.spec.ts index 845f5f6886..2be02e4a6f 100644 --- a/packages/types/src/codec/Struct.spec.ts +++ b/packages/types/src/codec/Struct.spec.ts @@ -111,15 +111,11 @@ describe('Struct', (): void => { }); it('decodes from a Map input', (): void => { - const input = new Struct(registry, { - a: U32, - txt: Text - }, { a: 42, txt: 'fubar' }); const s = new Struct(registry, { txt: Text, foo: U32, bar: U32 - }, input); + }, new Map([['a', 42], ['txt', 'fubar']])); expect(s.toString()).toEqual('{"txt":"fubar","foo":0,"bar":0}'); }); diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index e51bbde49c..b4a0e225eb 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -11,75 +11,77 @@ import { compareMap, decodeU8a, mapToTypeMap } from './utils'; type TypesDef = Record>; /** @internal */ -function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map): Record { +function decodeStructFromObject (registry: Registry, Types: ConstructorDef, value: any, jsonMap: Map): Iterable<[string, Codec]> { let jsonObj: Record | undefined; const inputKeys = Object.keys(Types); assert(!Array.isArray(value) || value.length === inputKeys.length, () => `Struct: Unable to map ${stringify(value)} array to object with known keys ${inputKeys.join(', ')}`); - return inputKeys.reduce>((raw, key, index) => { - // The key in the JSON can be snake_case (or other cases), but in our - // Types, result or any other maps, it's camelCase - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const jsonKey = (jsonMap.get(key as any) && !value[key]) - ? jsonMap.get(key as any) - : key; - const Type = Types[key]; - - try { - if (Array.isArray(value)) { - // TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - (raw as any)[key] = value[index] instanceof Type - ? value[index] - : new Type(registry, value[index]); - } else if (value instanceof Map) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const mapped = value.get(jsonKey); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - (raw as any)[key] = mapped instanceof Type - ? mapped - : new Type(registry, mapped); - } else if (isObject(value)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - let assign = value[jsonKey as string]; - - if (isUndefined(assign)) { - if (isUndefined(jsonObj)) { - jsonObj = Object.entries(value).reduce((all: Record, [key, value]): Record => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - all[stringCamelCase(key)] = value; - - return all; - }, {}); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - assign = jsonObj[jsonKey as string]; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - (raw as any)[key] = assign instanceof Type - ? assign - : new Type(registry, assign); - } else { - throw new Error(`Cannot decode value ${stringify(value)} (typeof ${typeof value}), expected an input object with known keys`); - } - } catch (error) { - let type = Type.name; + return Object.entries( + inputKeys.reduce>((raw, key, index) => { + // The key in the JSON can be snake_case (or other cases), but in our + // Types, result or any other maps, it's camelCase + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const jsonKey = (jsonMap.get(key as any) && !value[key]) + ? jsonMap.get(key as any) + : key; + const Type = Types[key]; try { - type = new Type(registry).toRawType(); + if (Array.isArray(value)) { + // TS2322: Type 'Codec' is not assignable to type 'T[keyof S]'. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + (raw as any)[key] = value[index] instanceof Type + ? value[index] + : new Type(registry, value[index]); + } else if (value instanceof Map) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const mapped = value.get(jsonKey); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (raw as any)[key] = mapped instanceof Type + ? mapped + : new Type(registry, mapped); + } else if (isObject(value)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + let assign = value[jsonKey as string]; + + if (isUndefined(assign)) { + if (isUndefined(jsonObj)) { + jsonObj = Object.entries(value).reduce((all: Record, [key, value]): Record => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + all[stringCamelCase(key)] = value; + + return all; + }, {}); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + assign = jsonObj[jsonKey as string]; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + (raw as any)[key] = assign instanceof Type + ? assign + : new Type(registry, assign); + } else { + throw new Error(`Cannot decode value ${stringify(value)} (typeof ${typeof value}), expected an input object with known keys`); + } } catch (error) { - // ignore + let type = Type.name; + + try { + type = new Type(registry).toRawType(); + } catch (error) { + // ignore + } + + throw new Error(`Struct: failed on ${jsonKey as string}: ${type}:: ${(error as Error).message}`); } - throw new Error(`Struct: failed on ${jsonKey as string}: ${type}:: ${(error as Error).message}`); - } - - return raw; - }, {}); + return raw; + }, {}) + ); } /** @@ -98,7 +100,7 @@ function decodeStructFromObject (registry: Registry, Types: ConstructorDef, valu * @param jsonMap * @internal */ -function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map): [Record, number] { +function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown, jsonMap: Map): [Iterable<[string, Codec]>, number] { if (isHex(value)) { return decodeStruct(registry, Types, hexToU8a(value), jsonMap); } else if (isU8a(value)) { @@ -107,13 +109,17 @@ function decodeStruct (registry: Registry, Types: ConstructorDef, value: unknown // Transform array of values to {key: value} mapping return [ - keys.reduce>((raw, key, index) => { - raw[key] = values[index]; + Object.entries( + keys.reduce>((raw, key, index) => { + raw[key] = values[index]; - return raw; - }, {}), + return raw; + }, {}) + ), decodedLength ]; + } else if (value instanceof Struct) { + return [value as Iterable<[string, Codec]>, 0]; } // We assume from here that value is a JS object (Array, Map, Object) @@ -151,7 +157,7 @@ export class Struct< constructor (registry: Registry, Types: S, value?: V | Map | unknown[] | string | null, jsonMap: Map = new Map()) { const [decoded, decodedLength] = decodeStruct(registry, mapToTypeMap(registry, Types), value, jsonMap); - super(Object.entries(decoded) as [keyof S, Codec][]); + super(decoded); this.registry = registry; this.#initialU8aLength = decodedLength; diff --git a/packages/types/src/metadata/decorate/storage/getHasher.ts b/packages/types/src/metadata/decorate/storage/getHasher.ts index 771a56d5fc..7df99ca233 100644 --- a/packages/types/src/metadata/decorate/storage/getHasher.ts +++ b/packages/types/src/metadata/decorate/storage/getHasher.ts @@ -14,19 +14,19 @@ export type HasherFunction = (data: HasherInput) => Uint8Array; const DEFAULT_FN = (data: HasherInput): Uint8Array => xxhashAsU8a(data, 128); const HASHERS: Record = { - Blake2_128: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase + Blake2_128: (data: HasherInput) => // eslint-disable-line camelcase blake2AsU8a(data, 128), - Blake2_128Concat: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase + Blake2_128Concat: (data: HasherInput) => // eslint-disable-line camelcase u8aConcat(blake2AsU8a(data, 128), u8aToU8a(data)), - Blake2_256: (data: HasherInput): Uint8Array => // eslint-disable-line camelcase + Blake2_256: (data: HasherInput) => // eslint-disable-line camelcase blake2AsU8a(data, 256), - Identity: (data: HasherInput): Uint8Array => + Identity: (data: HasherInput) => u8aToU8a(data), - Twox128: (data: HasherInput): Uint8Array => + Twox128: (data: HasherInput) => xxhashAsU8a(data, 128), - Twox256: (data: HasherInput): Uint8Array => + Twox256: (data: HasherInput) => xxhashAsU8a(data, 256), - Twox64Concat: (data: HasherInput): Uint8Array => + Twox64Concat: (data: HasherInput) => u8aConcat(xxhashAsU8a(data, 64), u8aToU8a(data)) }; diff --git a/packages/types/src/metadata/decorate/storage/getStorage.ts b/packages/types/src/metadata/decorate/storage/getStorage.ts index ba03c398c2..59a200ceab 100644 --- a/packages/types/src/metadata/decorate/storage/getStorage.ts +++ b/packages/types/src/metadata/decorate/storage/getStorage.ts @@ -13,7 +13,7 @@ export function getStorage (registry: Registry): Storage { substrate: Object .entries(substrate) .reduce((storage: Record, [key, fn]): Record => { - (storage as Record)[key] = fn(registry); + storage[key] = fn(registry); return storage; }, {})