diff --git a/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts b/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts index 0acaf5af22..23911e8c87 100644 --- a/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts +++ b/packages/metadata/src/Decorated/consts/fromMetadata/fromMetadata.spec.ts @@ -15,6 +15,12 @@ const consts = fromMetadata(registry, metadata); describe('fromMetadata', (): void => { it('should return constants with the correct type and value', (): void => { expect(consts.democracy.cooloffPeriod).toBeInstanceOf(ClassOf(registry, 'BlockNumber')); - expect(consts.democracy.cooloffPeriod.toHex()).toEqual('0x000c4e00'); + // 3 second blocks, 28 days + expect(consts.democracy.cooloffPeriod.toNumber()).toEqual(28 * 24 * 60 * (60 / 3)); + }); + + it('correctly handles bytes', (): void => { + // 0x34 removes as the length prefix removed + expect(consts.session.dedupKeyPrefix.toHex()).toEqual('0x3a73657373696f6e3a6b657973'); }); }); diff --git a/packages/metadata/src/Decorated/consts/fromMetadata/index.ts b/packages/metadata/src/Decorated/consts/fromMetadata/index.ts index e78df20419..02a1ecaf57 100644 --- a/packages/metadata/src/Decorated/consts/fromMetadata/index.ts +++ b/packages/metadata/src/Decorated/consts/fromMetadata/index.ts @@ -10,6 +10,8 @@ import { stringCamelCase } from '@polkadot/util'; import Metadata from '../../../Metadata'; +const AS_STRIPPED = ['Bytes']; + /** * Retrieve the parameter types (module constants) from the runtime metadata. * @@ -25,13 +27,20 @@ export default function fromMetadata (registry: Registry, metadata: Metadata): C // For access, we change the index names, i.e. Democracy.EnactmentPeriod -> democracy.enactmentPeriod result[stringCamelCase(name.toString())] = moduleMetadata.constants.reduce((newModule: ModuleConstants, meta): ModuleConstants => { - const codec = createTypeUnsafe(registry, meta.type.toString(), [meta.value]); + // we convert to Data for the raw data-only bytes - in the case of Bytes, the data has + // a length prefix encoded when received, leading to double-encoding unless removed + const type = meta.type.toString(); + const codec: ConstantCodec = createTypeUnsafe(registry, type, [ + AS_STRIPPED.includes(type) + ? meta.value.toU8a(true) + : meta.value + ]); // This is not a perfect idea, however as it stands with number-only constants on the metadata // does not have any effect. However, this could become problematic in cases where items are // exposed that contain their own metadata. As of now, the compatibility with current, e.g. // storage is the driving factor, one consistent way of handling interfaces - (codec as ConstantCodec).meta = meta; + codec.meta = meta; newModule[stringCamelCase(meta.name.toString())] = codec; return newModule; diff --git a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts index 28e630548a..cd86202e84 100644 --- a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts +++ b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.spec.ts @@ -86,7 +86,7 @@ describe('createFunction', (): void => { key1: new Text(registry, 'AccountId'), key2: new Text(registry, 'AccountId'), value: new Text(registry, 'Balance'), - key2Hasher: new Text(registry, 'twox_128') + key2Hasher: new StorageHasher(registry, 'Twox128') } } } as any @@ -118,7 +118,7 @@ describe('createFunction', (): void => { key1: new Text(registry, 'Null'), key2: new Text(registry, 'Hash'), value: new Text(registry, 'Vec<(BlockNumber,EventIndex)>'), - key2Hasher: new Text(registry, 'blake2_256') + key2Hasher: new StorageHasher(registry, 'Blake2_256') } } } as any @@ -129,4 +129,43 @@ describe('createFunction', (): void => { // the value of the not Null type key does effect the result expect(u8aToHex(storageFn(['any', [1, 2, 3]]))).not.toEqual(u8aToHex(storageFn(['any', [1, 2, 3, 4]]))); }); + + it('allows creating of known DoubleMap keys (with Bytes)', (): void => { + const storageFn = createFunction(registry, { + prefix: 'Session', + section: 'session', + method: 'NextKeys', + meta: { + type: { + isDoubleMap: true, + asDoubleMap: { + hasher: new StorageHasher(registry, 'Twox64Concat'), + key1: new Text(registry, 'Bytes'), + key2: new Text(registry, 'AccountId'), + value: new Text(registry, 'SessionKeys5'), + key2Hasher: new StorageHasher(registry, 'Blake2_256') + } + } + } as any + }, { metaVersion: 9 }); + + expect( + u8aToHex( + storageFn([ + // hex, without length prefix + '0x3a73657373696f6e3a6b657973', + // address + 'DB2mp5nNhbFN86J9hxoAog8JALMhDXgwvWMxrRMLNUFMEY4' + ]) + ) + ).toEqual( + '0x' + + '5901' + // length + 'cec5070d609dd3497f72bde07fc96ba0' + // twox 128 + '4c014e6bf8b8c2c011e7290b85696bb3' + // twox 128 + '9fe6329cc0b39e09' + // twox 64 + '343a73657373696f6e3a6b657973' + // twox 64 (concat, with length) + '5eb36b60f0fc4b9177116eba3e5cd57fea6289a57f5f5b9ffeb0475c66e7a521' // blake2 + ); + }); }); diff --git a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts index d6706bf21d..c711933a6f 100644 --- a/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts +++ b/packages/metadata/src/Decorated/storage/fromMetadata/createFunction.ts @@ -78,8 +78,8 @@ function createKeyDoubleMap (registry: Registry, itemFn: CreateItemFn, stringKey const [key1, key2] = args; const map = type.asDoubleMap; - const val1 = createTypeUnsafe(registry, map.key1.toString(), [key1]).toU8a(true); - const val2 = createTypeUnsafe(registry, map.key2.toString(), [key2]).toU8a(true); + const val1 = createTypeUnsafe(registry, map.key1.toString(), [key1]).toU8a(); + const val2 = createTypeUnsafe(registry, map.key2.toString(), [key2]).toU8a(); // as per createKey, always add the length prefix (underlying it is Bytes) return Compact.addLengthPrefix( diff --git a/packages/types/src/primitive/Bytes.spec.ts b/packages/types/src/primitive/Bytes.spec.ts index 371cfca545..a3b2138d1b 100644 --- a/packages/types/src/primitive/Bytes.spec.ts +++ b/packages/types/src/primitive/Bytes.spec.ts @@ -7,34 +7,60 @@ import Bytes from './Bytes'; describe('Bytes', (): void => { const registry = new TypeRegistry(); - const CODE = new Uint8Array([0x14, 0x3a, 0x63, 0x6f, 0x64, 0x65]); + const NUM = [0x3a, 0x63, 0x6f, 0x64, 0x65]; + const U8A = new Uint8Array([0x14, ...NUM]); + const HEX = '0x3a636f6465'; describe('construction', (): void => { it('decodes when input is string', (): void => { expect( new Bytes(registry, ':code').toU8a() - ).toEqual(CODE); + ).toEqual(U8A); }); it('decodes when hex is not length prefixed', (): void => { expect( - new Bytes(registry, '0x3a636f6465').toU8a() - ).toEqual(CODE); + new Bytes(registry, HEX).toU8a() + ).toEqual(U8A); + }); + + it('decodes from UInt8Array', (): void => { + expect( + new Bytes(registry, U8A).toU8a() + ).toEqual(U8A); }); it('decodes from number[]', (): void => { expect( - new Bytes(registry, [0x3a, 0x63, 0x6f, 0x64, 0x65]).toU8a() - ).toEqual(CODE); + new Bytes(registry, NUM).toU8a() + ).toEqual(U8A); }); it('creates via storagedata (no prefix)', (): void => { expect( new Bytes( registry, - createType(registry, 'StorageData', '0x3a636f6465') + createType(registry, 'StorageData', HEX) ).toU8a() - ).toEqual(CODE); + ).toEqual(U8A); + }); + + it('encodes from itself', (): void => { + expect( + new Bytes(registry, new Bytes(registry, HEX)).toU8a() + ).toEqual(U8A); + }); + + it('strips length with toU8a(true)', (): void => { + expect( + new Bytes(registry, HEX).toU8a(true) + ).toEqual(U8A.subarray(1)); + }); + + it('strips length with toHex', (): void => { + expect( + new Bytes(registry, HEX).toHex() + ).toEqual(HEX); }); }); });