From 5b5caa1e80be4fa4ce2cb088cdd057acea2c0d63 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 5 Oct 2018 15:44:30 +0200 Subject: [PATCH] Raw decoding for extrinsics (#214) --- .../src/utils/createUnchecked.ts | 6 +- packages/types/src/Extrinsic.spec.js | 15 ++++ packages/types/src/Extrinsic.ts | 27 +++++-- packages/types/src/ExtrinsicSignature.ts | 9 +++ packages/types/src/Method.ts | 70 ++++++++++++++----- packages/types/src/codec/U8aFixed.ts | 16 +++-- 6 files changed, 110 insertions(+), 33 deletions(-) create mode 100644 packages/types/src/Extrinsic.spec.js diff --git a/packages/type-extrinsics/src/utils/createUnchecked.ts b/packages/type-extrinsics/src/utils/createUnchecked.ts index 8166523c18..4521ba4e2a 100644 --- a/packages/type-extrinsics/src/utils/createUnchecked.ts +++ b/packages/type-extrinsics/src/utils/createUnchecked.ts @@ -22,11 +22,7 @@ export default function createDescriptor ( const callIndex = new Uint8Array([index, meta.id.toNumber()]); let extrinsicFn: any; - // If the extrinsic function has an argument of type `Origin`, we ignore it - // FIXME should be `arg.type !== Origin`, but doesn't work... - const expectedArgs = meta.arguments.filter((arg) => - arg.type.toString() !== 'Origin' - ); + const expectedArgs = Method.filterOrigin(meta); extrinsicFn = (...args: any[]): Extrinsic => { if (expectedArgs.length.valueOf() !== args.length) { diff --git a/packages/types/src/Extrinsic.spec.js b/packages/types/src/Extrinsic.spec.js new file mode 100644 index 0000000000..4ee44310db --- /dev/null +++ b/packages/types/src/Extrinsic.spec.js @@ -0,0 +1,15 @@ +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import Extrinsic from './Extrinsic'; + +describe('Extrinsic', () => { + it('decodes a non-signed properly via fromJSON', () => { + const extrinsic = new Extrinsic().fromJSON('0x010200ea51b75b00000000'); + + expect(extrinsic.isSigned).toEqual(false); + expect(extrinsic.callIndex).toEqual(new Uint8Array([2, 0])); + expect(extrinsic.data).toEqual(new Uint8Array([234, 81, 183, 91, 0, 0, 0, 0])); + }); +}); diff --git a/packages/types/src/Extrinsic.ts b/packages/types/src/Extrinsic.ts index 3984bae1f1..e669eec077 100644 --- a/packages/types/src/Extrinsic.ts +++ b/packages/types/src/Extrinsic.ts @@ -6,6 +6,7 @@ import { KeyringPair } from '@polkadot/util-keyring/types'; import { AnyNumber, AnyU8a } from './types'; import blake2Asu8a from '@polkadot/util-crypto/blake2/asU8a'; +import hexToU8a from '@polkadot/util/hex/toU8a'; import u8aConcat from '@polkadot/util/u8a/concat'; import u8aToHex from '@polkadot/util/u8a/toHex'; @@ -42,8 +43,13 @@ export default class Extrinsic extends Struct { }, value); } - get method (): Method { - return this.raw.method as Method; + // the actual [sectionIndex, methodIndex] as used + get callIndex (): Uint8Array { + return this.method.callIndex; + } + + get data (): Uint8Array { + return this.method.data; } // convernience, encodes the extrinsic and returns the actual hash @@ -53,6 +59,10 @@ export default class Extrinsic extends Struct { ); } + get isSigned (): boolean { + return this.signature.isSigned; + } + get length (): number { return this.toU8a(true).length; } @@ -61,9 +71,8 @@ export default class Extrinsic extends Struct { return this.method.meta; } - // the actual [sectionIndex, methodIndex] as used - get callIndex (): Uint8Array { - return this.method.callIndex; + get method (): Method { + return this.raw.method as Method; } get signature (): ExtrinsicSignature { @@ -76,6 +85,12 @@ export default class Extrinsic extends Struct { return length + Compact.encode(length).length; } + fromJSON (input: any): Extrinsic { + super.fromU8a(hexToU8a(input)); + + return this; + } + fromU8a (input: Uint8Array): Extrinsic { const [offset, length] = Compact.decode(input); @@ -102,7 +117,7 @@ export default class Extrinsic extends Struct { } toHex (): string { - return u8aToHex(this.toU8a()); + return u8aToHex(this.toU8a(true)); } toJSON (): any { diff --git a/packages/types/src/ExtrinsicSignature.ts b/packages/types/src/ExtrinsicSignature.ts index 1673404d26..0b8b859b8d 100644 --- a/packages/types/src/ExtrinsicSignature.ts +++ b/packages/types/src/ExtrinsicSignature.ts @@ -43,6 +43,15 @@ export default class ExtrinsicSignature extends Struct { }, value); } + byteLength (): number { + // version has 1 byte, signature takes the rest + return 1 + ( + this.isSigned + ? super.byteLength() + : 0 + ); + } + get isSigned (): boolean { return this.signature.length !== 0; } diff --git a/packages/types/src/Method.ts b/packages/types/src/Method.ts index cd84cc1cd5..2084c117bb 100644 --- a/packages/types/src/Method.ts +++ b/packages/types/src/Method.ts @@ -9,7 +9,7 @@ import u8aConcat from '@polkadot/util/u8a/concat'; import createType from './codec/createType'; import Base from './codec/Base'; import MethodIndex from './MethodIndex'; -import { FunctionMetadata } from './Metadata'; +import { FunctionMetadata, FunctionArgumentMetadata } from './Metadata'; /** * Extrinsic function descriptor, as defined in @@ -17,6 +17,7 @@ import { FunctionMetadata } from './Metadata'; */ export default class Method extends MethodIndex { protected _args: Array; + protected _data: Uint8Array; protected _meta: FunctionMetadata; constructor (index: Method | AnyU8a, meta: FunctionMetadata, args: Array) { @@ -29,35 +30,72 @@ export default class Method extends MethodIndex { this._args = args; this._meta = meta; } + + this._data = Method.encode(this._meta, this._args); } - get args () { + static decode (meta: FunctionMetadata, data: Uint8Array): Array { + let offset = 0; + + return Method.filterOrigin(meta).map(({ type }) => { + const base = createType(type).fromU8a(data.subarray(offset)); + + offset += base.byteLength(); + + return base; + }); + } + + static encode (meta: FunctionMetadata, args: Array): Uint8Array { + const encoded = Method.filterOrigin(meta).map(({ type }, index) => + createType(type, args[index]).toU8a() + ); + + return u8aConcat(...encoded); + } + + // If the extrinsic function has an argument of type `Origin`, we ignore it + static filterOrigin (meta?: FunctionMetadata): Array { + // FIXME should be `arg.type !== Origin`, but doesn't work... + return meta + ? meta.arguments.filter(({ type }) => + type.toString() !== 'Origin' + ) + : []; + } + + byteLength (): number { + return super.byteLength() + this.data.length; + } + + get args (): Array { return this._args; } - get index () { + get data (): Uint8Array { + return this._data; + } + + get index (): Uint8Array { return this.raw; } - get meta () { + get meta (): FunctionMetadata { return this._meta; } - toU8a (isBare?: boolean): Uint8Array { - // FIXME Even when decoded, we will need access to the meta - const args = this.meta - ? this.meta.arguments - .filter((arg) => - arg.type.toString() !== 'Origin' - ) - .map((argument, index) => - createType(argument.type, this.args[index]).toU8a(isBare) - ) - : []; + fromU8a (input: Uint8Array): Method { + super.fromU8a(input); + this._data = input.subarray(super.byteLength()); + + return this; + } + + toU8a (isBare?: boolean): Uint8Array { return u8aConcat( super.toU8a(isBare), - ...args + this.data ); } } diff --git a/packages/types/src/codec/U8aFixed.ts b/packages/types/src/codec/U8aFixed.ts index 4598cb112d..9a79b2c044 100644 --- a/packages/types/src/codec/U8aFixed.ts +++ b/packages/types/src/codec/U8aFixed.ts @@ -18,27 +18,31 @@ export default class U8aFixed extends U8a { this._bitLength = bitLength; - this._trimLength(); + this._trim(); } - private _trimLength (): void { - this.raw = this.raw.subarray(0, this.byteLength()); + private _trimLength (): number { + return this._bitLength / 8; + } + + private _trim (): void { + this.raw = this.raw.subarray(0, this._trimLength()); } byteLength (): number { - return this._bitLength / 8; + return this._trimLength(); } fromJSON (input: any): U8aFixed { super.fromJSON(input); - this._trimLength(); + this._trim(); return this; } fromU8a (input: Uint8Array): U8aFixed { - super.fromU8a(input.subarray(0, this.byteLength())); + super.fromU8a(input.subarray(0, this._trimLength())); return this; }