diff --git a/packages/rpc-core/src/formatting.spec.ts b/packages/rpc-core/src/formatting.spec.ts index a229020654..08c602cf28 100644 --- a/packages/rpc-core/src/formatting.spec.ts +++ b/packages/rpc-core/src/formatting.spec.ts @@ -19,9 +19,10 @@ const BALANCE_KEYS = [ /* v3-, using xxHash */ '0x4af2c53fce3ec33c6ccccf22e926f1a7', /* v4+, using blake2_256 */ '0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad' ]; +const OPTION_BYTES_HEX = '0x210100000000000000000000000000000000000000000000000000000000000000000000000000000000011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce'; -function formattingTests (version: string, storage: Storage, encodedValues: [string, string]) { - const [ENC_ONE, ENC_TWO] = encodedValues; +function formattingTests (version: string, storage: Storage, encodedValues: [string, string, string]) { + const [ENC_ONE, ENC_TWO, CONTRACT_KEY] = encodedValues; describe(`formatting with Metadata ${version}`, () => { let api: Api; @@ -36,16 +37,25 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str : null ) ), - subscribe: jest.fn((type, method, params, cb) => - cb(null, { - block: '0x1234', - changes: [ - [ENC_ONE, '0x01020000000000000000000000000000'], - [ENC_TWO, '0x02010000000000000000000000000000'] - ] - }) - ) + subscribe: jest.fn((type, method, params, cb) => { + // this emulates https://github.com/polkadot-js/api/issues/1051 + params[0][0] === CONTRACT_KEY + ? cb(null, { + block: '0x2345', + changes: [ + [CONTRACT_KEY, OPTION_BYTES_HEX] + ] + }) + : cb(null, { + block: '0x1234', + changes: [ + [ENC_ONE, '0x01020000000000000000000000000000'], + [ENC_TWO, '0x02010000000000000000000000000000'] + ] + }); + }) }; + api = new Api(provider); }); @@ -94,28 +104,39 @@ function formattingTests (version: string, storage: Storage, encodedValues: [str value.map((balance: BN) => balance.toNumber() ) - ).toEqual([513, 258]); + ).toEqual([0x0201, 0x0102]); done(); }); }); + + it('handles the case where Option are retrieved', (done) => { + api.state + .subscribeStorage([[storage.contract.pristineCode, '0x00']]) + .subscribe((value: any) => { + // console.error(value); + + // expect(value.toHex()).toBe(OPTION_BYTES_HEX); + done(); + }); + }); }); } -formattingTests( - 'v3', - fromMetadata(new Metadata(rpcMetadataV3)), - ['0x4af2c53fce3ec33c6ccccf22e926f1a7', '0x3e62f7ed6e788e1337bce2a97b68a12a'] -); +formattingTests('v3', fromMetadata(new Metadata(rpcMetadataV3)), [ + '0x4af2c53fce3ec33c6ccccf22e926f1a7', + '0x3e62f7ed6e788e1337bce2a97b68a12a', + '0x777519cd81f845abdb40d253923d6098' +]); -formattingTests( - 'v4', - fromMetadata(new Metadata(rpcMetadataV4)), - ['0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad', '0x1dbb0224910f42a14e7f1406b24c6fe8157296691b02a78756e01946038fffab'] -); +formattingTests('v4', fromMetadata(new Metadata(rpcMetadataV4)), [ + '0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad', + '0x1dbb0224910f42a14e7f1406b24c6fe8157296691b02a78756e01946038fffab', + '0xc7879f4faa637a90d782070a3cb6be99a9fb0316e19a0454ce93c4f0a34712f1' +]); -formattingTests( - 'v5', - fromMetadata(new Metadata(rpcMetadataV5)), - ['0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad', '0x1dbb0224910f42a14e7f1406b24c6fe8157296691b02a78756e01946038fffab'] -); +formattingTests('v5', fromMetadata(new Metadata(rpcMetadataV5)), [ + '0xec8f96437274a883afcac82d01a9defeb68209cd4f2c084632813692aa5e65ad', + '0x1dbb0224910f42a14e7f1406b24c6fe8157296691b02a78756e01946038fffab', + '0xc7879f4faa637a90d782070a3cb6be99a9fb0316e19a0454ce93c4f0a34712f1' +]); diff --git a/packages/types/src/codec/Option.spec.ts b/packages/types/src/codec/Option.spec.ts index 0f4b8857bb..f2f1d239e9 100644 --- a/packages/types/src/codec/Option.spec.ts +++ b/packages/types/src/codec/Option.spec.ts @@ -3,6 +3,8 @@ // of the Apache-2.0 license. See the LICENSE file for details. import Option from './Option'; +import Bytes from '../primitive/Bytes'; +import U32 from '../primitive/U32'; import Text from '../primitive/Text'; const testDecode = (type: string, input: any, expected: any) => @@ -33,12 +35,28 @@ describe('Option', () => { ).toEqual('hello'); }); + it('properly converts correctly toHex (Bytes)', () => { + // Option for a parachain head, however, this is effectively an + // Option> (hence the length, since it is from storage) + const HEX = '0x210100000000000000000000000000000000000000000000000000000000000000000000000000000000011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce'; + expect( + new Option(Bytes, HEX).toHex().substr(2) + ).toEqual(HEX.substr(6)); + }); + + it.only('properly converts correctly toHex (U64)', () => { + const HEX = '0x12345678'; + expect( + (new Option(U32, HEX).unwrap() as U32).toNumber() + ).toEqual(0x12345678); + }); + testDecode('string (with)', 'foo', 'foo'); testDecode('string (without)', undefined, ''); testDecode('Uint8Array (with)', Uint8Array.from([1, 12, 102, 111, 111]), 'foo'); testDecode('Uint8Array (without)', Uint8Array.from([0]), ''); - testEncode('toHex', '0x010c666f6f'); + testEncode('toHex', '0x0c666f6f'); testEncode('toString', 'foo'); testEncode('toU8a', Uint8Array.from([1, 12, 102, 111, 111])); diff --git a/packages/types/src/codec/Option.ts b/packages/types/src/codec/Option.ts index edbb16e75e..29c486c9f5 100644 --- a/packages/types/src/codec/Option.ts +++ b/packages/types/src/codec/Option.ts @@ -38,7 +38,7 @@ export default class Option extends Base implements Codec { } else if (isU8a(value)) { // the isU8a check happens last in the if-tree - since the wrapped value // may be an instance of it, so Type and Option checks go in first - return value[0] === 0 + return !value.length || value[0] === 0 ? new Null() : new Type(value.subarray(1)); } @@ -105,7 +105,11 @@ export default class Option extends Base implements Codec { * @description Returns a hex string representation of the value */ toHex (): string { - return u8aToHex(this.toU8a()); + // This attempts to align with the JSON encoding - actually in this case + // the isSome value is correct, however the `isNone` may be problematic + return this.isNone + ? '0x' + : u8aToHex(this.toU8a().subarray(1)); } /** diff --git a/packages/types/src/codec/createType.spec.ts b/packages/types/src/codec/createType.spec.ts index 0b40f6a0d9..74d384eb88 100644 --- a/packages/types/src/codec/createType.spec.ts +++ b/packages/types/src/codec/createType.spec.ts @@ -250,7 +250,7 @@ describe('createType', () => { expect( () => createType('DoubleMap>', base, true) - ).toThrow(/Encoding for input doesn't match output, created 0x00 from 0x/); + ).toThrow(/ Input doesn't match output, received 0x, created 0x00/); }); it('throw error when create base is a StorageData with null value and isPedantic is true', () => { @@ -258,6 +258,6 @@ describe('createType', () => { expect( () => createType('Vec<(BlockNumber,EventIndex)>', base, true) - ).toThrow(/Encoding for input doesn't match output, created 0x00 from 0x/); + ).toThrow(/Input doesn't match output, received 0x, created 0x00/); }); }); diff --git a/packages/types/src/codec/createType.ts b/packages/types/src/codec/createType.ts index 1418d38427..b7aedd791b 100644 --- a/packages/types/src/codec/createType.ts +++ b/packages/types/src/codec/createType.ts @@ -6,6 +6,7 @@ import { assert } from '@polkadot/util'; import { Codec, Constructor } from '../types'; import Null from '../primitive/Null'; +import StorageData from '../primitive/StorageData'; import Text from '../primitive/Text'; import Compact from './Compact'; import Enum from './Enum'; @@ -267,11 +268,19 @@ function initType (Type: Constructor, value?: any, isPedantic?: boolean): Codec // in pedantic mode, actually check that the encoding matches that supplied - this // is much slower, but ensures that we have a 100% grasp on the actual provided value - if (isPedantic && value && value.toHex) { + if (isPedantic && value && value.toHex && value.toU8a) { const inHex = value.toHex(true); const crHex = created.toHex(true); - assert(crHex === inHex, `Encoding for input doesn't match output, created ${crHex} from ${inHex}`); + assert( + inHex === crHex || // check that the hex matches, if matching, all-ok + ( + (value instanceof StorageData) && // input is from storage + (created instanceof Uint8Array) && // we are a variable-lneght structure + (value.toU8a(true).toString() === created.toU8a().toString()) // strip the input length + ), + `Input doesn't match output, received ${inHex}, created ${crHex}` + ); } return created; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 1477f7eb29..0a611c3382 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -60,7 +60,7 @@ export interface Codec { eq (other?: any): boolean; /** - * @description Returns a hex string representation of the value. isLe returns a LE (number-only) repersentation + * @description Returns a hex string representation of the value. isLe returns a LE (number-only) representation */ toHex (isLe?: boolean): string;