From f2a82ddd0fe5f1909542ff695da08d0953727e8e Mon Sep 17 00:00:00 2001 From: Jaco Date: Fri, 18 Jun 2021 16:23:54 +0300 Subject: [PATCH] Allow for nested fixed vec defintions (#3673) * Allow for nested fixed vec defintions * struct with nested struct & tuple * extract conversion to type string * Include < & > in split iteration * Allow for nested ( & ) * CHANGELOG --- CHANGELOG.md | 1 + packages/types/src/create/getTypeDef.spec.ts | 165 +++++++++++++++++-- packages/types/src/create/getTypeDef.ts | 37 ++++- 3 files changed, 181 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0165f491a7..8c3d8bab48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changes: +- Allow for type decoding of nested fixed vectors, tuples and structs - Added `childstate_getKeysPaged` RPC - Align static metadata with latest Substrate - Adjust error message for contracts V3 (extrinsic from runtime) diff --git a/packages/types/src/create/getTypeDef.spec.ts b/packages/types/src/create/getTypeDef.spec.ts index 5c45f78aa3..12553ed61e 100644 --- a/packages/types/src/create/getTypeDef.spec.ts +++ b/packages/types/src/create/getTypeDef.spec.ts @@ -252,28 +252,167 @@ describe('getTypeDef', (): void => { }); }); - // FIXME - not handled atm - it.skip('creates a nested fixed vec', (): void => { + it('creates a nested fixed vec', (): void => { expect( - getTypeDef('[[u8;32];3]') + getTypeDef('[[bool; 6]; 3; MyType]') ).toEqual({ - ext: { - length: 3, - type: '[u8;32]' - }, + displayName: 'MyType', info: TypeDefInfo.VecFixed, + length: 3, sub: { - ext: { - length: 32 - }, info: TypeDefInfo.VecFixed, + length: 6, sub: { info: TypeDefInfo.Plain, - type: 'u8' + type: 'bool' }, - type: '[u8;32]' + type: '[bool;6]' }, - type: '[[u8;32];3]' + type: '[[bool;6];3;MyType]' + }); + }); + + it('creates a nested tuple', (): void => { + expect( + getTypeDef('((u32, u64), u128)') + ).toEqual({ + info: TypeDefInfo.Tuple, + sub: [ + { + info: TypeDefInfo.Tuple, + sub: [ + { + info: TypeDefInfo.Plain, + type: 'u32' + }, + { + info: TypeDefInfo.Plain, + type: 'u64' + } + ], + type: '(u32,u64)' + }, + { + info: TypeDefInfo.Plain, + type: 'u128' + } + ], + type: '((u32,u64),u128)' + }); + }); + + it('creates a nested enum with tuple/struct', (): void => { + expect( + getTypeDef(JSON.stringify({ + _enum: { + A: 'u32', + B: '(u32, bool)', + C: { + d: 'AccountId', + e: 'Balance' + } + } + })) + ).toEqual({ + info: TypeDefInfo.Enum, + sub: [ + { + index: 0, + info: TypeDefInfo.Plain, + name: 'A', + type: 'u32' + }, + { + index: 1, + info: TypeDefInfo.Tuple, + name: 'B', + sub: [ + { + info: TypeDefInfo.Plain, + type: 'u32' + }, + { + info: TypeDefInfo.Plain, + type: 'bool' + } + ], + type: '(u32,bool)' + }, + { + index: 2, + info: TypeDefInfo.Struct, + name: 'C', + sub: [ + { + info: TypeDefInfo.Plain, + name: 'd', + type: 'AccountId' + }, + { + info: TypeDefInfo.Plain, + name: 'e', + type: 'Balance' + } + ], + type: '{"d":"AccountId","e":"Balance"}' + } + ], + type: '{"_enum":{"A":"u32","B":"(u32,bool)","C":{"d":"AccountId","e":"Balance"}}}' + }); + }); + + it('creates a nested struct with struct/tuple', (): void => { + expect( + getTypeDef(JSON.stringify({ + a: 'u32', + b: '(u32, bool)', + c: { + d: 'AccountId', + e: 'Balance' + } + })) + ).toEqual({ + info: TypeDefInfo.Struct, + sub: [ + { + info: TypeDefInfo.Plain, + name: 'a', + type: 'u32' + }, + { + info: TypeDefInfo.Tuple, + name: 'b', + sub: [ + { + info: TypeDefInfo.Plain, + type: 'u32' + }, + { + info: TypeDefInfo.Plain, + type: 'bool' + } + ], + type: '(u32,bool)' + }, + { + info: TypeDefInfo.Struct, + name: 'c', + sub: [ + { + info: TypeDefInfo.Plain, + name: 'd', + type: 'AccountId' + }, + { + info: TypeDefInfo.Plain, + name: 'e', + type: 'Balance' + } + ], + type: '{"d":"AccountId","e":"Balance"}' + } + ], + type: '{"a":"u32","b":"(u32,bool)","c":{"d":"AccountId","e":"Balance"}}' }); }); diff --git a/packages/types/src/create/getTypeDef.ts b/packages/types/src/create/getTypeDef.ts index 573d6cf249..3574674475 100644 --- a/packages/types/src/create/getTypeDef.ts +++ b/packages/types/src/create/getTypeDef.ts @@ -3,7 +3,7 @@ import type { TypeDef } from './types'; -import { assert, isNumber } from '@polkadot/util'; +import { assert, isNumber, isString } from '@polkadot/util'; import { sanitize } from './sanitize'; import { TypeDefInfo } from './types'; @@ -16,6 +16,12 @@ interface TypeDefOptions { const MAX_NESTED = 64; +function getTypeString (typeOrObj: any): string { + return isString(typeOrObj) + ? typeOrObj.toString() + : JSON.stringify(typeOrObj); +} + function isRustEnum (details: Record | Record): details is Record { const values = Object.values(details); @@ -44,9 +50,8 @@ function _decodeEnum (value: TypeDef, details: string[] | Record type: 'Null' })); } else if (isRustEnum(details)) { - value.sub = Object.entries(details).map(([name, type], index): TypeDef => ({ - // eslint-disable-next-line @typescript-eslint/no-use-before-define - ...getTypeDef(type || 'Null', { name }, count), + value.sub = Object.entries(details).map(([name, typeOrObj], index): TypeDef => ({ + ...getTypeDef(getTypeString(typeOrObj || 'Null'), { name }, count), index })); } else { @@ -96,8 +101,7 @@ function _decodeStruct (value: TypeDef, type: string, _: string, count: number): ? new Map(Object.entries(parsed._alias)) : undefined; value.sub = keys.filter((name) => !['_alias'].includes(name)).map((name): TypeDef => - // eslint-disable-next-line @typescript-eslint/no-use-before-define - getTypeDef(parsed[name], { name }, count) + getTypeDef(getTypeString(parsed[name]), { name }, count) ); return value; @@ -106,7 +110,24 @@ function _decodeStruct (value: TypeDef, type: string, _: string, count: number): // decode a fixed vector, e.g. [u8;32] // eslint-disable-next-line @typescript-eslint/no-unused-vars function _decodeFixedVec (value: TypeDef, type: string, _: string, count: number): TypeDef { - const [vecType, strLength, displayName] = type.substr(1, type.length - 2).split(';'); + const max = type.length - 1; + let index = -1; + let inner = 0; + + for (let i = 1; (i < max) && (index === -1); i++) { + if (type[i] === ';' && inner === 0) { + index = i; + } else if (['[', '(', '<'].includes(type[i])) { + inner++; + } else if ([']', ')', '>'].includes(type[i])) { + inner--; + } + } + + assert(index !== -1, () => `${type}: Unable to extract location of ';'`); + + const vecType = type.substr(1, index - 1); + const [strLength, displayName] = type.substr(index + 1, max - index - 1).split(';'); const length = parseInt(strLength.trim(), 10); // as a first round, only u8 via u8aFixed, we can add more support @@ -114,7 +135,6 @@ function _decodeFixedVec (value: TypeDef, type: string, _: string, count: number value.displayName = displayName; value.length = length; - // eslint-disable-next-line @typescript-eslint/no-use-before-define value.sub = getTypeDef(vecType, {}, count); return value; @@ -124,7 +144,6 @@ function _decodeFixedVec (value: TypeDef, type: string, _: string, count: number function _decodeTuple (value: TypeDef, _: string, subType: string, count: number): TypeDef { value.sub = subType.length === 0 ? [] - // eslint-disable-next-line @typescript-eslint/no-use-before-define : typeSplit(subType).map((inner) => getTypeDef(inner, {}, count)); return value;