From 2f157cfb27a3d17febded260c84e50e4d79bcf67 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 10 Jul 2019 16:37:40 +0200 Subject: [PATCH] Extrinsics with version decoding handler (#1090) --- packages/types/src/primitive/Address.ts | 3 +- packages/types/src/type/Extrinsic.ts | 101 ++++++++------ packages/types/src/type/ExtrinsicV1.ts | 175 ++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 41 deletions(-) create mode 100644 packages/types/src/type/ExtrinsicV1.ts diff --git a/packages/types/src/primitive/Address.ts b/packages/types/src/primitive/Address.ts index b58915c3ef..b3be4e58e1 100644 --- a/packages/types/src/primitive/Address.ts +++ b/packages/types/src/primitive/Address.ts @@ -2,11 +2,12 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Codec } from '../types'; + import BN from 'bn.js'; import { hexToU8a, isBn, isHex, isNumber, isU8a, u8aConcat, u8aToHex, u8aToU8a, u8aToBn } from '@polkadot/util'; import { decodeAddress } from '@polkadot/util-crypto'; -import { Codec } from '../types'; import Base from '../codec/Base'; import AccountId from './AccountId'; import AccountIndex from './AccountIndex'; diff --git a/packages/types/src/type/Extrinsic.ts b/packages/types/src/type/Extrinsic.ts index 13459537af..78efcde0c0 100644 --- a/packages/types/src/type/Extrinsic.ts +++ b/packages/types/src/type/Extrinsic.ts @@ -4,21 +4,20 @@ import { AnyNumber, AnyU8a, ArgsDef, Codec, IExtrinsic, IKeyringPair, SignatureOptions } from '../types'; -import { assert, isHex, isU8a, u8aToHex, u8aToU8a } from '@polkadot/util'; -import { blake2AsU8a } from '@polkadot/util-crypto'; +import { assert, isHex, isU8a, u8aToU8a } from '@polkadot/util'; +import Base from '../codec/Base'; import Compact from '../codec/Compact'; -import Struct from '../codec/Struct'; import { FunctionMetadata } from '../Metadata/v6/Calls'; import Method from '../primitive/Method'; import Address from '../primitive/Address'; import Hash from '../primitive/Hash'; import ExtrinsicSignature from './ExtrinsicSignature'; +import ExtrinsicV1, { ExtrinsicValueV1 } from './ExtrinsicV1'; -type ExtrinsicValue = { - method?: Method - signature?: ExtrinsicSignature -}; +type ExtrinsicValue = ExtrinsicValueV1; + +const UNMASK_VERSION = 0b01111111; /** * @name Extrinsic @@ -32,15 +31,12 @@ type ExtrinsicValue = { * - signed, to create a transaction * - left as is, to create an inherent */ -export default class Extrinsic extends Struct implements IExtrinsic { +export default class Extrinsic extends Base implements IExtrinsic, Codec { constructor (value?: ExtrinsicValue | AnyU8a | Method) { - super({ - signature: ExtrinsicSignature, - method: Method - }, Extrinsic.decodeExtrinsic(value)); + super(Extrinsic.decodeExtrinsic(value)); } - static decodeExtrinsic (value: ExtrinsicValue | AnyU8a | Method = new Uint8Array()): ExtrinsicValue | Array | Uint8Array { + static decodeExtrinsic (value?: ExtrinsicValue | AnyU8a | Method): ExtrinsicV1 { if (Array.isArray(value) || isHex(value)) { // Instead of the block below, it should simply be: // return Extrinsic.decodeExtrinsic(hexToU8a(value as string)); @@ -58,7 +54,7 @@ export default class Extrinsic extends Struct implements IExtrinsic { ); } else if (isU8a(value)) { if (!value.length) { - return new Uint8Array(); + return new ExtrinsicV1(new Uint8Array()); } const [offset, length] = Compact.decodeU8a(value); @@ -66,14 +62,27 @@ export default class Extrinsic extends Struct implements IExtrinsic { assert(total <= value.length, `Extrinsic: required length less than remainder, expected at least ${total}, found ${value.length}`); - return value.subarray(offset, total); + return Extrinsic.decodeU8a(value.subarray(offset, total)); } else if (value instanceof Method) { - return { + return new ExtrinsicV1({ method: value - }; + }); } - return value; + return new ExtrinsicV1(value); + } + + static decodeU8a (value: Uint8Array): ExtrinsicV1 { + // decode the actual version string + const version = value[0] & UNMASK_VERSION; + + switch (version) { + case 1: + return new ExtrinsicV1(value); + + default: + throw new Error(`Unsupported extrinsic version ${version}`); + } } /** @@ -108,67 +117,70 @@ export default class Extrinsic extends Struct implements IExtrinsic { * @description The length of the value when encoded as a Uint8Array */ get encodedLength (): number { - const length = this.length; - - return length + Compact.encodeU8a(length).length; + return this.raw.encodedLength; } /** * @description Convernience function, encodes the extrinsic and returns the actual hash */ get hash (): Hash { - return new Hash( - blake2AsU8a(this.toU8a(), 256) - ); + return this.raw.hash; } /** * @description `true` is method has `Origin` argument (compatibility with [[Method]]) */ get hasOrigin (): boolean { - return this.method.hasOrigin; + return this.raw.hasOrigin; + } + + /** + * @description Checks if the value is an empty value + */ + get isEmpty (): boolean { + return this.raw.isEmpty; } /** * @description `true` id the extrinsic is signed */ get isSigned (): boolean { - return this.signature.isSigned; + return this.raw.isSigned; } /** * @description The length of the encoded value */ get length (): number { - return this.toU8a(true).length; + return this.raw.length; } /** * @description The [[FunctionMetadata]] that describes the extrinsic */ get meta (): FunctionMetadata { - return this.method.meta; + return this.raw.meta; } /** * @description The [[Method]] this extrinsic wraps */ get method (): Method { - return this.get('method') as Method; + return this.raw.method; } /** * @description The [[ExtrinsicSignature]] */ get signature (): ExtrinsicSignature { - return this.get('signature') as ExtrinsicSignature; + return this.raw.signature; } /** * @description Add an [[ExtrinsicSignature]] to the extrinsic (already generated) */ addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | string, nonce: AnyNumber, era?: Uint8Array): Extrinsic { - this.signature.addSignature(signer, signature, nonce, era); + this.raw.addSignature(signer, signature, nonce, era); return this; } @@ -177,30 +189,43 @@ export default class Extrinsic extends Struct implements IExtrinsic { * @description Sign the extrinsic with a specific keypair */ sign (account: IKeyringPair, options: SignatureOptions): Extrinsic { - this.signature.sign(this.method, account, options); + this.raw.sign(account, options); return this; } + /** + * @description Compares the value of the input to see if there is a match + */ + eq (other?: any): boolean { + return this.raw.eq(other); + } + /** * @description Returns a hex string representation of the value */ toHex (): string { - return u8aToHex(this.toU8a()); + return this.raw.toHex(); } /** * @description Converts the Object to JSON, typically used for RPC transfers */ toJSON (): string { - return this.toHex(); + return this.raw.toJSON(); + } + + /** + * @description Returns the string representation of the value + */ + toString (): string { + return this.raw.toString(); } /** * @description Returns the base runtime type name for this instance */ toRawType (): string { - // We are treating this in the same way we do a primitive, this is known return 'Extrinsic'; } @@ -209,10 +234,6 @@ export default class Extrinsic extends Struct implements IExtrinsic { * @param isBare true when the value has none of the type-specific prefixes (internal) */ toU8a (isBare?: boolean): Uint8Array { - const encoded = super.toU8a(); - - return isBare - ? encoded - : Compact.addLengthPrefix(encoded); + return this.raw.toU8a(isBare); } } diff --git a/packages/types/src/type/ExtrinsicV1.ts b/packages/types/src/type/ExtrinsicV1.ts new file mode 100644 index 0000000000..b074bad702 --- /dev/null +++ b/packages/types/src/type/ExtrinsicV1.ts @@ -0,0 +1,175 @@ +// Copyright 2017-2019 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { AnyNumber, ArgsDef, Codec, IExtrinsic, IKeyringPair, SignatureOptions } from '../types'; + +import { u8aToHex } from '@polkadot/util'; +import { blake2AsU8a } from '@polkadot/util-crypto'; + +import Compact from '../codec/Compact'; +import Struct from '../codec/Struct'; +import { FunctionMetadata } from '../Metadata/v6/Calls'; +import Method from '../primitive/Method'; +import Address from '../primitive/Address'; +import Hash from '../primitive/Hash'; +import ExtrinsicSignature from './ExtrinsicSignature'; + +export type ExtrinsicValueV1 = { + method?: Method + signature?: ExtrinsicSignature +}; + +/** + * @name ExtrinsicV1 + * @description + * The first generation of compact extrinsics + */ +export default class ExtrinsicV1 extends Struct implements IExtrinsic { + constructor (value?: Uint8Array | ExtrinsicValueV1) { + super({ + signature: ExtrinsicSignature, + method: Method + }, value); + } + + /** + * @description The arguments passed to for the call, exposes args so it is compatible with [[Method]] + */ + get args (): Array { + return this.method.args; + } + + /** + * @description Thge argument defintions, compatible with [[Method]] + */ + get argsDef (): ArgsDef { + return this.method.argsDef; + } + + /** + * @description The actual `[sectionIndex, methodIndex]` as used in the Method + */ + get callIndex (): Uint8Array { + return this.method.callIndex; + } + + /** + * @description The actual data for the Method + */ + get data (): Uint8Array { + return this.method.data; + } + + /** + * @description The length of the value when encoded as a Uint8Array + */ + get encodedLength (): number { + const length = this.length; + + return length + Compact.encodeU8a(length).length; + } + + /** + * @description Convernience function, encodes the extrinsic and returns the actual hash + */ + get hash (): Hash { + return new Hash( + blake2AsU8a(this.toU8a(), 256) + ); + } + + /** + * @description `true` is method has `Origin` argument (compatibility with [[Method]]) + */ + get hasOrigin (): boolean { + return this.method.hasOrigin; + } + + /** + * @description `true` id the extrinsic is signed + */ + get isSigned (): boolean { + return this.signature.isSigned; + } + + /** + * @description The length of the encoded value + */ + get length (): number { + return this.toU8a(true).length; + } + + /** + * @description The [[FunctionMetadata]] that describes the extrinsic + */ + get meta (): FunctionMetadata { + return this.method.meta; + } + + /** + * @description The [[Method]] this extrinsic wraps + */ + get method (): Method { + return this.get('method') as Method; + } + + /** + * @description The [[ExtrinsicSignature]] + */ + get signature (): ExtrinsicSignature { + return this.get('signature') as ExtrinsicSignature; + } + + /** + * @description Add an [[ExtrinsicSignature]] to the extrinsic (already generated) + */ + addSignature (signer: Address | Uint8Array | string, signature: Uint8Array | string, nonce: AnyNumber, era?: Uint8Array): ExtrinsicV1 { + this.signature.addSignature(signer, signature, nonce, era); + + return this; + } + + /** + * @description Sign the extrinsic with a specific keypair + */ + sign (account: IKeyringPair, options: SignatureOptions): ExtrinsicV1 { + this.signature.sign(this.method, account, options); + + return this; + } + + /** + * @description Returns a hex string representation of the value + */ + toHex (): string { + return u8aToHex(this.toU8a()); + } + + /** + * @description Converts the Object to JSON, typically used for RPC transfers + */ + toJSON (): string { + return this.toHex(); + } + + /** + * @description Returns the base runtime type name for this instance + */ + toRawType (): string { + // We are treating this in the same way we do a primitive, this is known + return 'Extrinsic'; + } + + /** + * @description Encodes the value as a Uint8Array as per the SCALE specifications + * @param isBare true when the value has none of the type-specific prefixes (internal) + */ + toU8a (isBare?: boolean): Uint8Array { + const encoded = super.toU8a(); + + return isBare + ? encoded + : Compact.addLengthPrefix(encoded); + } +}