From cf01c41b3317a5af4c625112f85648deb02648b6 Mon Sep 17 00:00:00 2001 From: Keith Ingram Date: Thu, 31 Oct 2019 19:47:14 +0200 Subject: [PATCH] Contract API for Rx/promises (#1520) * wip with rxjs/promise * add outcome process * contract call decoding solved * restore messages as dict, cleanup * linting * linting * type fixes --- packages/api-contract/src/Abi.spec.ts | 10 +- packages/api-contract/src/Abi.ts | 23 +- packages/api-contract/src/ContractRegistry.ts | 89 ++++--- packages/api-contract/src/RxBase.ts | 31 --- packages/api-contract/src/RxBlueprint.ts | 72 ------ packages/api-contract/src/RxCode.ts | 83 ------- packages/api-contract/src/RxContract.ts | 41 --- packages/api-contract/src/base/Blueprint.ts | 75 ++++++ packages/api-contract/src/base/Code.ts | 71 ++++++ packages/api-contract/src/base/Contract.ts | 99 ++++++++ packages/api-contract/src/base/index.ts | 7 + packages/api-contract/src/base/util.ts | 74 ++++++ packages/api-contract/src/index.ts | 5 +- packages/api-contract/src/method.ts | 20 -- .../src/promise/PromiseBlueprint.ts | 18 ++ .../api-contract/src/promise/PromiseCode.ts | 17 ++ .../src/promise/PromiseContract.ts | 18 ++ packages/api-contract/src/promise/index.ts | 7 + packages/api-contract/src/rx/RxBlueprint.ts | 18 ++ packages/api-contract/src/rx/RxCode.ts | 17 ++ packages/api-contract/src/rx/RxContract.ts | 18 ++ packages/api-contract/src/rx/index.ts | 7 + packages/api-contract/src/types.ts | 71 ++++-- packages/api-contract/src/util.ts | 23 ++ .../test/compare/incrementer.test.json | 188 ++++++++++++++ .../test/contracts/Incrementer.json | 1 - .../test/contracts/incrementer.json | 233 ++++++++++++++++++ packages/api/src/base/Decorate.ts | 16 +- packages/api/src/base/Init.ts | 2 +- packages/api/src/base/index.ts | 11 +- packages/api/src/promise/Api.ts | 2 +- packages/api/src/promise/index.ts | 3 +- packages/api/src/rx/Api.ts | 2 +- packages/api/src/rx/index.ts | 3 +- packages/api/src/submittable/Submittable.ts | 22 +- .../api/src/submittable/createSubmittable.ts | 4 +- packages/api/src/submittable/types.ts | 17 +- packages/api/src/types.ts | 38 +-- packages/api/src/util/decorate.ts | 10 +- 39 files changed, 1080 insertions(+), 386 deletions(-) delete mode 100644 packages/api-contract/src/RxBase.ts delete mode 100644 packages/api-contract/src/RxBlueprint.ts delete mode 100644 packages/api-contract/src/RxCode.ts delete mode 100644 packages/api-contract/src/RxContract.ts create mode 100644 packages/api-contract/src/base/Blueprint.ts create mode 100644 packages/api-contract/src/base/Code.ts create mode 100644 packages/api-contract/src/base/Contract.ts create mode 100644 packages/api-contract/src/base/index.ts create mode 100644 packages/api-contract/src/base/util.ts delete mode 100644 packages/api-contract/src/method.ts create mode 100644 packages/api-contract/src/promise/PromiseBlueprint.ts create mode 100644 packages/api-contract/src/promise/PromiseCode.ts create mode 100644 packages/api-contract/src/promise/PromiseContract.ts create mode 100644 packages/api-contract/src/promise/index.ts create mode 100644 packages/api-contract/src/rx/RxBlueprint.ts create mode 100644 packages/api-contract/src/rx/RxCode.ts create mode 100644 packages/api-contract/src/rx/RxContract.ts create mode 100644 packages/api-contract/src/rx/index.ts create mode 100644 packages/api-contract/src/util.ts create mode 100644 packages/api-contract/test/compare/incrementer.test.json delete mode 100644 packages/api-contract/test/contracts/Incrementer.json create mode 100644 packages/api-contract/test/contracts/incrementer.json diff --git a/packages/api-contract/src/Abi.spec.ts b/packages/api-contract/src/Abi.spec.ts index 212ff06e1d..6a63a2baab 100644 --- a/packages/api-contract/src/Abi.spec.ts +++ b/packages/api-contract/src/Abi.spec.ts @@ -2,21 +2,21 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import erc20Abi from '../test/contracts/Erc20.json'; +import incrementerAbi from '../test/contracts/incrementer.json'; import { Abi } from '.'; describe('Abi', (): void => { - describe('erc20', (): void => { + describe('incrementer', (): void => { let abi: Abi; beforeEach((): void => { - abi = new Abi(erc20Abi); + abi = new Abi(incrementerAbi); }); it('has the attached methods', (): void => { - expect(Object.keys(abi.messages)).toEqual( - ['totalSupply', 'balanceOf', 'allowance', 'transfer', 'approve', 'transferFrom'] + expect(Object.values(abi.abi.contract.messages).map(({ name }): string => name)).toEqual( + ['inc', 'get', 'compare'] ); }); }); diff --git a/packages/api-contract/src/Abi.ts b/packages/api-contract/src/Abi.ts index bc9a6fdeb7..c1b14db3e2 100644 --- a/packages/api-contract/src/Abi.ts +++ b/packages/api-contract/src/Abi.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { ContractABI, ContractABIPre, ContractABIFn, InterfaceAbi, AbiMessages } from './types'; +import { AbiConstructors, AbiMessages, ContractABI, ContractABIPre, ContractABIFn, InterfaceAbi } from './types'; import { stringCamelCase } from '@polkadot/util'; import ContractRegistry from './ContractRegistry'; @@ -10,9 +10,9 @@ import ContractRegistry from './ContractRegistry'; export default class ContractAbi extends ContractRegistry implements InterfaceAbi { public readonly abi: ContractABI; - public readonly constructors: ContractABIFn[]; + public readonly constructors: AbiConstructors; - public readonly messages: AbiMessages = {}; + public readonly messages: AbiMessages; public constructor (abi: ContractABIPre) { super(abi); @@ -25,16 +25,21 @@ export default class ContractAbi extends ContractRegistry implements InterfaceAb const abi = this.convertAbi(abiPre); const constructors = abi.contract.constructors.map( (constructor, index): ContractABIFn => { - return this.createMethod(`constructor ${index}`, constructor); + return this.createMessage(`constructor ${index}`, constructor); } ); - const messages: AbiMessages = {}; - abi.contract.messages.forEach((method): void => { - const name = stringCamelCase(method.name); + const messages: AbiMessages = abi.contract.messages.reduce( + (result: AbiMessages, message): AbiMessages => { + const name = stringCamelCase(message.name); - messages[name] = this.createMethod(`messages.${name}`, method); - }); + return { + ...result, + [name]: this.createMessage(`messages.${name}`, message) + }; + }, + {} + ); return [abi, constructors, messages]; } diff --git a/packages/api-contract/src/ContractRegistry.ts b/packages/api-contract/src/ContractRegistry.ts index c29b7ec663..4eb6953634 100644 --- a/packages/api-contract/src/ContractRegistry.ts +++ b/packages/api-contract/src/ContractRegistry.ts @@ -2,38 +2,55 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { CodecArg, TypeDef } from '@polkadot/types/types'; -import { ContractABIArgBasePre, ContractABIContract, ContractABIContractPre, ContractABIEvent, ContractABIEventPre, ContractABIFn, ContractABIFnArg, ContractABIMethod, ContractABIMethodBase, ContractABIMethodPre, ContractABI, ContractABIPre, ContractABIRange, ContractABIRangePre, ContractABIStorage, ContractABIStorageLayout, ContractABIStorageLayoutPre, ContractABIStoragePre, ContractABIStorageStruct, ContractABIStorageStructPre, ContractABITypePre } from './types'; -import { Compact, u32, createType } from '@polkadot/types'; +import { CodecArg, Constructor, TypeDef } from '@polkadot/types/types'; +import { ContractABIArgBasePre, ContractABIContract, ContractABIContractPre, ContractABIEvent, ContractABIEventPre, ContractABIFn, ContractABIFnArg, ContractABIMessage, ContractABIMessageBase, ContractABIMessagePre, ContractABI, ContractABIMessageCommon, ContractABIPre, ContractABIRange, ContractABIRangePre, ContractABIStorage, ContractABIStorageLayout, ContractABIStorageLayoutPre, ContractABIStoragePre, ContractABIStorageStruct, ContractABIStorageStructPre, ContractABITypePre } from './types'; +import { Compact, u32, createClass, createType, encodeType } from '@polkadot/types'; import { assert, hexToU8a, isNumber, isString, isNull, isObject, isUndefined, stringCamelCase, isHex, hexToNumber } from '@polkadot/util'; import MetaRegistry from './MetaRegistry'; -import { createArgClass } from './method'; // parse a selector, this can be a number (older) or of [, , ...]. However, // just catch everything (since this is now non-standard for u32 anyway) -function parseSelector (fnname: string, input: number | string | number[] | string[]): u32 { +function parseSelector (fnname: string, input: ContractABIMessageCommon['selector']): u32 { if (isNumber(input)) { return createType('u32', input); } else if (isHex(input)) { return createType('u32', hexToU8a(input)); - } else if (Array.isArray(input)) { - assert(input.length === 4, `${fnname}: Invalid selector length`); + } else if (typeof input === 'string') { + try { + const array = JSON.parse(input); - return createType('u32', Uint8Array.from( - // the as number[] is here to pacify TS, it doesn't quite know how to handle the cb - (input as number[]).map((value: string | number): number => - isHex(value) - ? hexToNumber(value.toString()) - : value - ) - )); + assert(array.length === 4, `${fnname}: Invalid selector length`); + + return createType('u32', Uint8Array.from( + // the as number[] is here to pacify TS, it doesn't quite know how to handle the cb + (array as number[]).map((value: string | number): number => + isHex(value) + ? hexToNumber(value.toString()) + : value + ) + )); + } catch (e) { + console.error(e); + } } throw new Error(`${fnname}: Unable to parse selector`); } +function createArgClass (args: ContractABIFnArg[], baseDef: Record): Constructor { + return createClass( + JSON.stringify( + args.reduce((base: Record, { name, type }): Record => { + base[name] = encodeType(type); + + return base; + }, baseDef) + ) + ); +} + export default class ContractRegistry extends MetaRegistry { // Contract ABI helpers public validateArgs (name: string, args: ContractABIArgBasePre[]): void { @@ -49,7 +66,7 @@ export default class ContractRegistry extends MetaRegistry { } public validateConstructors ({ contract: { constructors } }: ContractABIPre): void { - constructors.forEach((constructor: ContractABIMethodPre, index): void => { + constructors.forEach((constructor: ContractABIMessagePre, index): void => { const unknownKeys = Object.keys(constructor).filter((key): boolean => !['args', 'docs', 'name', 'selector'].includes(key)); assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI constructor`); @@ -58,20 +75,20 @@ export default class ContractRegistry extends MetaRegistry { }); } - public validateMethods ({ contract: { messages } }: ContractABIPre): void { - messages.forEach((method): void => { - const unknownKeys = Object.keys(method).filter((key): boolean => !['args', 'docs', 'mutates', 'name', 'selector', 'return_type'].includes(key)); - const fnname = `messages.${method.name}`; + public validateMessages ({ contract: { messages } }: ContractABIPre): void { + messages.forEach((message): void => { + const unknownKeys = Object.keys(message).filter((key): boolean => !['args', 'docs', 'mutates', 'name', 'selector', 'return_type'].includes(key)); + const fnname = `messages.${message.name}`; - assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${method.name}`); + assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${message.name}`); - const { name, selector, return_type: returnType } = method; + const { name, selector, return_type: returnType } = message; assert(isNumber(name) && isString(this.stringAt(name)), `Expected name for ${fnname}`); assert(isNull(returnType) || (isNumber(returnType.ty) && isObject(this.typeDefAt(returnType.ty))), `Expected return_type for ${fnname}`); parseSelector(fnname, selector); - this.validateArgs(fnname, method.args); + this.validateArgs(fnname, message.args); }); } @@ -85,19 +102,19 @@ export default class ContractRegistry extends MetaRegistry { assert(constructors && messages && isString(this.stringAt(name)), 'ABI should have constructors, messages & name sections'); this.validateConstructors(abi); - this.validateMethods(abi); + this.validateMessages(abi); } - public createMethod (name: string, method: Partial & ContractABIMethodBase): ContractABIFn { - const args = method.args.map(({ name, type }): ContractABIFnArg => { + public createMessage (name: string, message: Partial & ContractABIMessageBase): ContractABIFn { + const args = message.args.map(({ name, type }): ContractABIFnArg => { assert(isObject(type), `Invalid type at index ${type}`); return { name: stringCamelCase(name), type }; }); - const Clazz = createArgClass(args, isUndefined(method.selector) ? {} : { __selector: 'u32' }); - const baseStruct: { [index: string]: any } = { __selector: isUndefined(method.selector) ? undefined : parseSelector(name, method.selector) }; + const Clazz = createArgClass(args, isUndefined(message.selector) ? {} : { __selector: 'u32' }); + const baseStruct: { [index: string]: any } = { __selector: isUndefined(message.selector) ? undefined : parseSelector(name, message.selector) }; const encoder = (...params: CodecArg[]): Uint8Array => { assert(params.length === args.length, `Expected ${args.length} arguments to contract ${name}, found ${params.length}`); @@ -115,8 +132,8 @@ export default class ContractRegistry extends MetaRegistry { const fn = (encoder as ContractABIFn); fn.args = args; - fn.isConstant = !(method.mutates || false); - fn.type = method.returnType || null; + fn.isConstant = !(message.mutates || false); + fn.type = message.returnType || null; return fn; } @@ -140,7 +157,7 @@ export default class ContractRegistry extends MetaRegistry { public convertContract ({ constructors, messages, name, events, ...contract }: ContractABIContractPre): ContractABIContract { return { constructors: this.convertConstructors(constructors), - messages: messages.map(method => this.convertMethod(method)), + messages: messages.map(message => this.convertMessage(message)), name: this.stringAt(name), ...(events ? { events: events.map(event => this.convertEvent(event)) } @@ -149,17 +166,17 @@ export default class ContractRegistry extends MetaRegistry { }; } - public convertConstructors (constructors: ContractABIMethodPre[]): ContractABIMethod[] { + public convertConstructors (constructors: ContractABIMessagePre[]): ContractABIMessage[] { return constructors.map( - (constructor: ContractABIMethodPre): ContractABIMethod => { - return this.convertMethod(constructor); + (constructor: ContractABIMessagePre): ContractABIMessage => { + return this.convertMessage(constructor); } ); } - public convertMethod ({ args, name, return_type: returnType, ...method }: ContractABIMethodPre): ContractABIMethod { + public convertMessage ({ args, name, return_type: returnType, ...message }: ContractABIMessagePre): ContractABIMessage { return { - ...method, + ...message, args: this.convertArgs(args), name: this.stringAt(name), returnType: returnType ? this.convertType(returnType) : null diff --git a/packages/api-contract/src/RxBase.ts b/packages/api-contract/src/RxBase.ts deleted file mode 100644 index 650cadd47e..0000000000 --- a/packages/api-contract/src/RxBase.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017-2019 @polkadot/api-contract 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 { SubmittableModuleExtrinsics } from '@polkadot/api/types'; -import { ApiObject, ContractABIPre, ContractBase } from './types'; - -import { assert } from '@polkadot/util'; - -import Abi from './Abi'; - -// NOTE Experimental, POC, bound to change -export default abstract class RxBase implements ContractBase<'rxjs'> { - public readonly abi: Abi; - - public readonly api: ApiObject<'rxjs'>; - - public readonly apiContracts: SubmittableModuleExtrinsics<'rxjs'>; - - public constructor (api: ApiObject<'rxjs'>, abi: ContractABIPre | Abi) { - this.abi = abi instanceof Abi - ? abi - : new Abi(abi); - this.api = api; - - // cater for substrate 2.x & 1.x (in order) - this.apiContracts = api.tx.contracts || api.tx.contract; - - assert(this.apiContracts && this.apiContracts.putCode, 'You need to connect to a node with the contracts module, the metadata does not enable api.tx.contracts on this instance'); - } -} diff --git a/packages/api-contract/src/RxBlueprint.ts b/packages/api-contract/src/RxBlueprint.ts deleted file mode 100644 index f36863e93f..0000000000 --- a/packages/api-contract/src/RxBlueprint.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2017-2019 @polkadot/api-contract 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 { SubmittableResultImpl } from '@polkadot/api/types'; -import { AccountId, Address, Hash } from '@polkadot/types/interfaces'; -import { IKeyringPair } from '@polkadot/types/types'; -import { ContractABIPre } from './types'; - -import BN from 'bn.js'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { ApiRx, SubmittableResult } from '@polkadot/api'; -import { createType } from '@polkadot/types'; -import { assert } from '@polkadot/util'; - -import Abi from './Abi'; -import RxBase from './RxBase'; -import RxContract from './RxContract'; - -type BlueprintCreateResultSubscription = Observable; - -export interface BlueprintCreate { - signAndSend (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription; -} - -class BlueprintCreateResult extends SubmittableResult { - public readonly contract?: RxContract; - - public constructor (result: SubmittableResultImpl, contract?: RxContract) { - super(result); - - this.contract = contract; - } -} - -// NOTE Experimental, POC, bound to change -export default class Blueprint extends RxBase { - public readonly codeHash: Hash; - - public constructor (api: ApiRx, abi: ContractABIPre | Abi, codeHash: string | Hash) { - super(api, abi); - - this.codeHash = createType('Hash', codeHash); - } - - public deployContract (constructorIndex = 0, endowment: number | BN, maxGas: number | BN, ...params: any[]): BlueprintCreate { - assert(!!this.abi.constructors[constructorIndex], `Specified constructor index ${constructorIndex} does not exist`); - const signAndSend = (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription => { - return this.apiContracts - .create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params)) - .signAndSend(account) - .pipe(map(this.createResult)); - }; - - return { signAndSend }; - } - - private createResult = (result: SubmittableResult): BlueprintCreateResult => { - let contract: RxContract | undefined; - - if (result.isFinalized) { - const record = result.findRecord('contract', 'Instantiated'); - - if (record) { - contract = new RxContract(this.api, this.abi, record.event.data[1] as Address); - } - } - - return new BlueprintCreateResult(result, contract); - } -} diff --git a/packages/api-contract/src/RxCode.ts b/packages/api-contract/src/RxCode.ts deleted file mode 100644 index a419f574a5..0000000000 --- a/packages/api-contract/src/RxCode.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017-2019 @polkadot/api-contract 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 { SubmittableResultImpl } from '@polkadot/api/types'; -import { AccountId, Address, Hash } from '@polkadot/types/interfaces'; -import { IKeyringPair } from '@polkadot/types/types'; -import { ContractABIPre } from './types'; - -import BN from 'bn.js'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { ApiRx, SubmittableResult } from '@polkadot/api'; -import { compactAddLength, u8aToU8a } from '@polkadot/util'; - -import Abi from './Abi'; -import RxBase from './RxBase'; -import RxBlueprint from './RxBlueprint'; - -// Ok, tried, failed, eventually ... well, we are only trying with RxJs as a -// start, so take a big fat shortcut with this version, real intended version -// follows this and is commented out -type CodePutCodeResultSubscription = Observable; -// type ICodePutCodeResultSubscription = -// ApiType extends 'rxjs' -// ? Observable -// : Promise<() => void>; - -// unlike the ISubmittableExtrinsic, it doesn't extend IExtrinsic and only -// implements one var iant of signAndSend - this is purely done to see what -// is hanging where, with the bare-mainimum from a simplicity perspective -// Very obvious is that it supports only 1 version of signAndSend -// (we need to revisit this, but probably ok for initial?) -// eslint-disable-next-line @typescript-eslint/interface-name-prefix -export interface CodePutCode { - signAndSend (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription; -} - -class CodePutCodeResult extends SubmittableResult { - public readonly blueprint?: RxBlueprint; - - public constructor (result: SubmittableResultImpl, blueprint?: RxBlueprint) { - super(result); - - this.blueprint = blueprint; - } -} - -// NOTE Experimental, POC, bound to change -export default class RxCode extends RxBase { - public readonly code: Uint8Array; - - public constructor (api: ApiRx, abi: ContractABIPre | Abi, wasm: string | Uint8Array) { - super(api, abi); - - this.code = u8aToU8a(wasm); - } - - public createBlueprint (maxGas: number | BN): CodePutCode { - const signAndSend = (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription => { - return this.apiContracts - .putCode(maxGas, compactAddLength(this.code)) - .signAndSend(account) - .pipe(map(this.createResult)); - }; - - return { signAndSend }; - } - - private createResult = (result: SubmittableResultImpl): CodePutCodeResult => { - let blueprint: RxBlueprint | undefined; - - if (result.isFinalized) { - const record = result.findRecord('contract', 'CodeStored'); - - if (record) { - blueprint = new RxBlueprint(this.api, this.abi, record.event.data[0] as Hash); - } - } - - return new CodePutCodeResult(result, blueprint); - } -} diff --git a/packages/api-contract/src/RxContract.ts b/packages/api-contract/src/RxContract.ts deleted file mode 100644 index 1a5573a4f4..0000000000 --- a/packages/api-contract/src/RxContract.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017-2019 @polkadot/api-contract 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 { AccountId, Address } from '@polkadot/types/interfaces'; -import { IKeyringPair } from '@polkadot/types/types'; -import { ContractABIPre, ContractABIFn, InterfaceContract, InterfaceContractCalls } from './types'; - -import BN from 'bn.js'; -import { Observable } from 'rxjs'; -import { ApiRx, SubmittableResult } from '@polkadot/api'; -import { createType } from '@polkadot/types'; -import Abi from './Abi'; -import RxBase from './RxBase'; - -export type ContractCallResultSubscription = Observable; - -export interface ContractCall { - signAndSend (account: IKeyringPair | string | AccountId | Address): ContractCallResultSubscription; -} - -type CallResult = (value: BN | number, maxGas: BN | number, ...params: any[]) => ContractCall; - -// NOTE Experimental, POC, bound to change -export default class RxContract extends RxBase implements InterfaceContract { - public readonly address: Address; - - public readonly calls: InterfaceContractCalls = {}; - - public constructor (api: ApiRx, abi: ContractABIPre | Abi, address: string | AccountId | Address) { - super(api, abi); - - this.address = createType('Address', address); - - Object.entries(this.abi.messages).forEach(([name]): void => { - this.calls[name] = (fn: ContractABIFn): CallResult => - (value: BN | number, maxGas: BN | number, ...params: any[]): ContractCall => - this.apiContracts.call(this.address, value, maxGas, fn(...params)); - }); - } -} diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts new file mode 100644 index 0000000000..ce7e9bbd82 --- /dev/null +++ b/packages/api-contract/src/base/Blueprint.ts @@ -0,0 +1,75 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { ApiTypes, DecorateMethod, SubmittableResultImpl } from '@polkadot/api/types'; +import { AccountId, Address, Hash } from '@polkadot/types/interfaces'; +import { IKeyringPair } from '@polkadot/types/types'; +import { ApiObject, ContractABIPre } from '../types'; + +import BN from 'bn.js'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { SubmittableResult } from '@polkadot/api'; +import { createType } from '@polkadot/types'; +import { assert } from '@polkadot/util'; + +import Abi from '../Abi'; +import Contract from './Contract'; +import { BaseWithTx } from './util'; + +type BlueprintCreateResultSubscription = Observable>; + +export interface BlueprintCreate { + signAndSend (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription; +} + +class BlueprintCreateResult extends SubmittableResult { + public readonly contract?: Contract; + + public constructor (result: SubmittableResultImpl, contract?: Contract) { + super(result); + + this.contract = contract; + } +} + +// NOTE Experimental, POC, bound to change +export default class Blueprint extends BaseWithTx { + public readonly codeHash: Hash; + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod, codeHash: string | Hash) { + super(api, abi, decorateMethod); + + this.codeHash = createType('Hash', codeHash); + } + + public deployContract (constructorIndex = 0, endowment: number | BN, maxGas: number | BN, ...params: any[]): BlueprintCreate { + assert(!!this.abi.constructors[constructorIndex], `Specified constructor index ${constructorIndex} does not exist`); + + return { + signAndSend: this.decorateMethod( + (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription => { + return this.apiContracts + .create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params)) + .signAndSend(account) + .pipe(map(this.createResult)); + } + ) + }; + } + + private createResult = (result: SubmittableResult): BlueprintCreateResult => { + let contract: Contract | undefined; + + if (result.isFinalized) { + const record = result.findRecord('contract', 'Instantiated'); + + if (record) { + contract = new Contract(this.api, this.abi, this.decorateMethod, record.event.data[1] as Address); + } + } + + return new BlueprintCreateResult(result, contract); + } +} diff --git a/packages/api-contract/src/base/Code.ts b/packages/api-contract/src/base/Code.ts new file mode 100644 index 0000000000..80a209b8ad --- /dev/null +++ b/packages/api-contract/src/base/Code.ts @@ -0,0 +1,71 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { ApiTypes, DecorateMethod, SubmittableResultImpl } from '@polkadot/api/types'; +import { AccountId, Address, Hash } from '@polkadot/types/interfaces'; +import { IKeyringPair } from '@polkadot/types/types'; +import { ApiObject, ContractABIPre } from '../types'; + +import BN from 'bn.js'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { SubmittableResult } from '@polkadot/api'; +import { compactAddLength, u8aToU8a } from '@polkadot/util'; + +import Abi from '../Abi'; +import Blueprint from './Blueprint'; +import { BaseWithTx } from './util'; + +type CodePutCodeResultSubscription = Observable>; + +// eslint-disable-next-line @typescript-eslint/interface-name-prefix +export interface CodePutCode { + signAndSend (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription; +} + +class CodePutCodeResult extends SubmittableResult { + public readonly blueprint?: Blueprint; + + public constructor (result: SubmittableResultImpl, blueprint?: Blueprint) { + super(result); + + this.blueprint = blueprint; + } +} + +// NOTE Experimental, POC, bound to change +export default class Code extends BaseWithTx { + public readonly code: Uint8Array; + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod, wasm: string | Uint8Array) { + super(api, abi, decorateMethod); + + this.code = u8aToU8a(wasm); + } + + public createBlueprint = (maxGas: number | BN): CodePutCode => { + return { + signAndSend: this.decorateMethod( + (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription => this.apiContracts + .putCode(maxGas, compactAddLength(this.code)) + .signAndSend(account) + .pipe(map(this.createResult)) + ) + }; + } + + private createResult = (result: SubmittableResultImpl): CodePutCodeResult => { + let blueprint: Blueprint | undefined; + + if (result.isFinalized) { + const record = result.findRecord('contract', 'CodeStored'); + + if (record) { + blueprint = new Blueprint(this.api, this.abi, this.decorateMethod, record.event.data[0] as Hash); + } + } + + return new CodePutCodeResult(result, blueprint); + } +} diff --git a/packages/api-contract/src/base/Contract.ts b/packages/api-contract/src/base/Contract.ts new file mode 100644 index 0000000000..5b230af658 --- /dev/null +++ b/packages/api-contract/src/base/Contract.ts @@ -0,0 +1,99 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { ApiTypes, DecorateMethod, ObsInnerType } from '@polkadot/api/types'; +import { AccountId, Address, ContractExecResult } from '@polkadot/types/interfaces'; +import { IKeyringPair } from '@polkadot/types/types'; +import { ApiObject, ContractABIMessage, ContractABIPre, ContractCallOutcome } from '../types'; + +import BN from 'bn.js'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { SubmittableResult } from '@polkadot/api'; +import { createType } from '@polkadot/types'; +import Abi from '../Abi'; +import { formatData } from '../util'; +import { BaseWithTxAndRpcCall } from './util'; + +type ContractCallTypes = 'tx' | 'rpc'; + +type ContractCallResultSubscription = ApiType extends 'rxjs' + ? Observable> + : Promise>>; + +// eslint-disable-next-line @typescript-eslint/interface-name-prefix +export interface ContractCall { + send (account: IKeyringPair | string | AccountId | Address): ContractCallResultSubscription; +} + +export type ContractCallResult = CallType extends 'rpc' + ? Observable + : Observable; + +export default class Contract extends BaseWithTxAndRpcCall { + public readonly address: Address; + + public call (as: 'rpc', message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall; + public call (as: 'tx', message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall; + public call (as: CallType, message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall { + const { fn, def } = this.getMessage(message); + + return { + send: this.decorateMethod( + as === 'rpc' + ? (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'rpc'> => { + return this.rpcContractsCall( + createType('ContractCallRequest', { + origin: account, + dest: this.address.toString(), + value, + gasLimit, + inputData: fn(...params) + }) + ) + .pipe( + map((result: ContractExecResult): ContractCallOutcome => + this.createOutcome(result, createType('AccountId', account), def, params) + ) + ); + } + : (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'tx'> => { + return this.apiContracts + .call(this.address, value, gasLimit, fn(...params)) + .signAndSend(account); + } + ) + }; + } + + private createOutcome (result: ContractExecResult, origin: AccountId, message: ContractABIMessage, params: any[]): ContractCallOutcome { + let output: string; + if (result.isSuccess) { + const { data } = result.asSuccess; + output = message.returnType + ? formatData(data, message.returnType).toString() + : data.toHex(); + } else { + output = 'Error'; + } + + const outcome = { + time: Date.now(), + message, + origin, + params, + result: result, + success: result.isSuccess, + output: output && output.length ? output : '()' + }; + + return outcome; + } + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod, address: string | AccountId | Address) { + super(api, abi, decorateMethod); + + this.address = createType('Address', address); + } +} diff --git a/packages/api-contract/src/base/index.ts b/packages/api-contract/src/base/index.ts new file mode 100644 index 0000000000..fbf7b803ba --- /dev/null +++ b/packages/api-contract/src/base/index.ts @@ -0,0 +1,7 @@ +// Copyright 2017-2019 @polkadot/api authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export { default as Blueprint } from './Blueprint'; +export { default as Code } from './Code'; +export { default as Contract } from './Contract'; diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts new file mode 100644 index 0000000000..298fd434ae --- /dev/null +++ b/packages/api-contract/src/base/util.ts @@ -0,0 +1,74 @@ +// Copyright 2017-2019 @polkadot/api 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 { ApiTypes, DecorateMethod, DecoratedRpc, SubmittableModuleExtrinsics } from '@polkadot/api/types'; +import { ApiObject, ContractABIMessage, ContractABIPre, ContractBase, ContractMessage } from '../types'; +import { RpcInterface } from '@polkadot/rpc-core/jsonrpc.types'; + +import { assert, stringCamelCase } from '@polkadot/util'; +import Abi from '../Abi'; + +export abstract class Base implements ContractBase { + public readonly abi: Abi; + + public readonly api: ApiObject; + + public readonly decorateMethod: DecorateMethod; + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod) { + this.abi = abi instanceof Abi + ? abi + : new Abi(abi); + this.api = api; + this.decorateMethod = decorateMethod; + } + + public get messages (): ContractMessage[] { + return this.abi.abi.contract.messages + .map( + (def: ContractABIMessage, index): ContractMessage => ({ + index, + def, + fn: this.abi.messages[def.name] || this.abi.messages[stringCamelCase(name)] + }) + ); + } + + public getMessage (nameOrIndex?: string | number): ContractMessage { + const index = nameOrIndex + ? typeof nameOrIndex === 'number' + ? nameOrIndex + : this.abi.abi.contract.messages.findIndex((message): boolean => nameOrIndex === message.name || nameOrIndex === stringCamelCase(message.name)) + : 0; + const def = this.abi.abi.contract.messages[index]; + assert(!!def, `Attempted to access a contract message that does not exist: ${name}`); + + const fn = this.abi.messages[def.name] || this.abi.messages[stringCamelCase(def.name)]; + return { index, def, fn }; + } +} + +export abstract class BaseWithTx extends Base { + protected get apiContracts (): SubmittableModuleExtrinsics<'rxjs'> { + return this.api.rx.tx.contracts; + } + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod) { + super(api, abi, decorateMethod); + + assert(this.api.rx.tx.contracts && this.api.rx.tx.contracts.putCode, 'You need to connect to a node with the contracts module, the metadata does not enable api.tx.contracts on this instance'); + } +} + +export abstract class BaseWithTxAndRpcCall extends BaseWithTx { + protected get rpcContractsCall (): DecoratedRpc<'rxjs', RpcInterface>['contracts']['call'] { + return this.api.rx.rpc.contracts.call; + } + + public constructor (api: ApiObject, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod) { + super(api, abi, decorateMethod); + + assert(this.api.rx.rpc.contracts && this.api.rx.tx.contracts.call, 'You need to connect to a node with the contracts module, the metadata does not enable api.rpc.contracts.call on this instance'); + } +} diff --git a/packages/api-contract/src/index.ts b/packages/api-contract/src/index.ts index 279c62ab32..99750e528a 100644 --- a/packages/api-contract/src/index.ts +++ b/packages/api-contract/src/index.ts @@ -3,6 +3,5 @@ // of the Apache-2.0 license. See the LICENSE file for details. export { default as Abi } from './Abi'; -export { default as RxBlueprint } from './RxBlueprint'; -export { default as RxCode } from './RxCode'; -export { default as RxContract } from './RxContract'; +export * from './rx'; +export * from './promise'; diff --git a/packages/api-contract/src/method.ts b/packages/api-contract/src/method.ts deleted file mode 100644 index 509b1c5b25..0000000000 --- a/packages/api-contract/src/method.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017-2019 @polkadot/api-contract 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 { Constructor } from '@polkadot/types/types'; -import { ContractABIFnArg } from './types'; - -import { createClass, encodeType } from '@polkadot/types'; - -export function createArgClass (args: ContractABIFnArg[], baseDef: Record): Constructor { - return createClass( - JSON.stringify( - args.reduce((base: Record, { name, type }): Record => { - base[name] = encodeType(type); - - return base; - }, baseDef) - ) - ); -} diff --git a/packages/api-contract/src/promise/PromiseBlueprint.ts b/packages/api-contract/src/promise/PromiseBlueprint.ts new file mode 100644 index 0000000000..24ca5f569c --- /dev/null +++ b/packages/api-contract/src/promise/PromiseBlueprint.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { Hash } from '@polkadot/types/interfaces'; +import { ContractABIPre } from '../types'; + +import { ApiPromise } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/promise'; + +import Abi from '../Abi'; +import Blueprint from '../base/Blueprint'; + +export default class PromiseBlueprint extends Blueprint<'promise'> { + public constructor (api: ApiPromise, abi: ContractABIPre | Abi, codeHash: string | Hash) { + super(api, abi, decorateMethod, codeHash); + } +} diff --git a/packages/api-contract/src/promise/PromiseCode.ts b/packages/api-contract/src/promise/PromiseCode.ts new file mode 100644 index 0000000000..a3f25c7df5 --- /dev/null +++ b/packages/api-contract/src/promise/PromiseCode.ts @@ -0,0 +1,17 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { ContractABIPre } from '../types'; + +import { ApiPromise } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/promise'; + +import Abi from '../Abi'; +import Code from '../base/Code'; + +export default class PromiseCode extends Code<'promise'> { + public constructor (api: ApiPromise, abi: ContractABIPre | Abi, wasm: string | Uint8Array) { + super(api, abi, decorateMethod, wasm); + } +} diff --git a/packages/api-contract/src/promise/PromiseContract.ts b/packages/api-contract/src/promise/PromiseContract.ts new file mode 100644 index 0000000000..0c81c10a40 --- /dev/null +++ b/packages/api-contract/src/promise/PromiseContract.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { AccountId } from '@polkadot/types/interfaces'; +import { ContractABIPre } from '../types'; + +import { ApiPromise } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/promise'; + +import Abi from '../Abi'; +import Contract from '../base/Contract'; + +export default class PromiseContract extends Contract<'promise'> { + public constructor (api: ApiPromise, abi: ContractABIPre | Abi, address: string | AccountId) { + super(api, abi, decorateMethod, address); + } +} diff --git a/packages/api-contract/src/promise/index.ts b/packages/api-contract/src/promise/index.ts new file mode 100644 index 0000000000..46cb8257a9 --- /dev/null +++ b/packages/api-contract/src/promise/index.ts @@ -0,0 +1,7 @@ +// Copyright 2017-2019 @polkadot/api-contract authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export { default as PromiseBlueprint } from './PromiseBlueprint'; +export { default as PromiseCode } from './PromiseCode'; +export { default as PromiseContract } from './PromiseContract'; diff --git a/packages/api-contract/src/rx/RxBlueprint.ts b/packages/api-contract/src/rx/RxBlueprint.ts new file mode 100644 index 0000000000..836239f7fd --- /dev/null +++ b/packages/api-contract/src/rx/RxBlueprint.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { Hash } from '@polkadot/types/interfaces'; +import { ContractABIPre } from '../types'; + +import { ApiRx } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/rx'; + +import Abi from '../Abi'; +import Blueprint from '../base/Blueprint'; + +export default class RxBlueprint extends Blueprint<'rxjs'> { + public constructor (api: ApiRx, abi: ContractABIPre | Abi, codeHash: string | Hash) { + super(api, abi, decorateMethod, codeHash); + } +} diff --git a/packages/api-contract/src/rx/RxCode.ts b/packages/api-contract/src/rx/RxCode.ts new file mode 100644 index 0000000000..4caafe11b7 --- /dev/null +++ b/packages/api-contract/src/rx/RxCode.ts @@ -0,0 +1,17 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { ContractABIPre } from '../types'; + +import { ApiRx } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/rx'; + +import Abi from '../Abi'; +import Code from '../base/Code'; + +export default class RxCode extends Code<'rxjs'> { + public constructor (api: ApiRx, abi: ContractABIPre | Abi, wasm: string | Uint8Array) { + super(api, abi, decorateMethod, wasm); + } +} diff --git a/packages/api-contract/src/rx/RxContract.ts b/packages/api-contract/src/rx/RxContract.ts new file mode 100644 index 0000000000..5f18685416 --- /dev/null +++ b/packages/api-contract/src/rx/RxContract.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2019 @polkadot/api-contract 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 { AccountId } from '@polkadot/types/interfaces'; +import { ContractABIPre } from '../types'; + +import { ApiRx } from '@polkadot/api'; +import { decorateMethod } from '@polkadot/api/rx'; + +import Abi from '../Abi'; +import Contract from '../base/Contract'; + +export default class RxContract extends Contract<'rxjs'> { + public constructor (api: ApiRx, abi: ContractABIPre | Abi, address: string | AccountId) { + super(api, abi, decorateMethod, address); + } +} diff --git a/packages/api-contract/src/rx/index.ts b/packages/api-contract/src/rx/index.ts new file mode 100644 index 0000000000..bd764b82e0 --- /dev/null +++ b/packages/api-contract/src/rx/index.ts @@ -0,0 +1,7 @@ +// Copyright 2017-2019 @polkadot/api-contract authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +export { default as RxBlueprint } from './RxBlueprint'; +export { default as RxCode } from './RxCode'; +export { default as RxContract } from './RxContract'; diff --git a/packages/api-contract/src/types.ts b/packages/api-contract/src/types.ts index 08ba39e439..edd96cfffa 100644 --- a/packages/api-contract/src/types.ts +++ b/packages/api-contract/src/types.ts @@ -2,17 +2,26 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { SubmittableModuleExtrinsics } from '@polkadot/api/types'; -import { Address } from '@polkadot/types/interfaces'; +import { ApiTypes, DecorateMethod } from '@polkadot/api/types'; +import { AccountId, Address, ContractExecResult } from '@polkadot/types/interfaces'; import { CodecArg } from '@polkadot/types/types'; import { MetaRegistryJson, StringIndex, TypeIndex, TypeDef } from '@polkadot/types/codec/create/types'; +// import { Observable } from 'rxjs'; import { ApiPromise, ApiRx } from '@polkadot/api'; -export type ApiObject = ApiType extends 'rxjs' +export type ApiObject = ApiType extends 'rxjs' ? ApiRx : ApiPromise; +export interface ContractBase { + readonly abi: InterfaceAbi; + readonly api: ApiObject; + readonly decorateMethod: DecorateMethod; + getMessage: (name: string) => ContractMessage; + messages: ContractMessage[]; +} + export interface ContractABITypePre { ty: TypeIndex; display_name: StringIndex[]; @@ -28,31 +37,31 @@ export interface ContractABIArgBase { type: TypeDef; } -export type ContractABIMethodArgPre = ContractABIArgBasePre; +export type ContractABIMessageArgPre = ContractABIArgBasePre; -export type ContractABIMethodArg = ContractABIArgBase; +export type ContractABIMessageArg = ContractABIArgBase; -export interface ContractABIMethodBase { - args: ContractABIMethodArg[]; +export interface ContractABIMessageBase { + args: ContractABIMessageArg[]; } -export interface ContractABIMethodBasePre { - args: ContractABIMethodArgPre[]; +export interface ContractABIMessageBasePre { + args: ContractABIMessageArgPre[]; } -export interface ContractABIMethodCommon { +export interface ContractABIMessageCommon { docs?: ContractABIDocs; mutates?: boolean; - selector: number; + selector: string | number; } -export interface ContractABIMethodPre extends ContractABIMethodCommon, ContractABIMethodBasePre { +export interface ContractABIMessagePre extends ContractABIMessageCommon, ContractABIMessageBasePre { name: StringIndex; return_type: ContractABITypePre | null; } -export interface ContractABIMethod extends ContractABIMethodCommon { - args: ContractABIMethodArg[]; +export interface ContractABIMessage extends ContractABIMessageCommon { + args: ContractABIMessageArg[]; name: string; returnType: TypeDef | null; } @@ -62,16 +71,16 @@ export interface ContractABIContractCommon { } export interface ContractABIContractPre extends ContractABIContractCommon { - constructors: ContractABIMethodPre[]; - messages: ContractABIMethodPre[]; + constructors: ContractABIMessagePre[]; + messages: ContractABIMessagePre[]; name: StringIndex; events?: ContractABIEventPre[]; docs?: ContractABIDocs; } export interface ContractABIContract extends ContractABIContractCommon { - constructors: ContractABIMethod[]; - messages: ContractABIMethod[]; + constructors: ContractABIMessage[]; + messages: ContractABIMessage[]; name: string; events?: ContractABIEvent[]; docs?: ContractABIDocs; @@ -162,20 +171,22 @@ export type ContractABIStoragePre = ContractABIStorageStructPre; export type ContractABIStorage = ContractABIStorageStruct; +export interface ContractMessage { + index: number; + fn: ContractABIFn; + def: ContractABIMessage; +} + +export type AbiConstructors = ContractABIFn[]; + export type AbiMessages = Record; export interface InterfaceAbi { readonly abi: ContractABI; - readonly constructors: ContractABIFn[]; + readonly constructors: AbiConstructors; readonly messages: AbiMessages; } -export interface ContractBase { - readonly abi: InterfaceAbi; - readonly api: ApiObject; - readonly apiContracts: SubmittableModuleExtrinsics; -} - export interface InterfaceContractCalls { [index: string]: Function; } @@ -184,3 +195,13 @@ export interface InterfaceContract { readonly address: Address; readonly calls: InterfaceContractCalls; } + +export interface ContractCallOutcome { + time: number; + result: ContractExecResult; + origin: AccountId; + output: string; + params: any[]; + success: boolean; + message: ContractABIMessage; +} diff --git a/packages/api-contract/src/util.ts b/packages/api-contract/src/util.ts new file mode 100644 index 0000000000..037a2f75a4 --- /dev/null +++ b/packages/api-contract/src/util.ts @@ -0,0 +1,23 @@ +// Copyright 2017-2019 @polkadot/rpc-core 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 { Codec, TypeDefInfo, TypeDef } from '@polkadot/types/types'; + +import { Bytes, Option, createClass } from '@polkadot/types'; +import { createType, createTypeUnsafe } from '@polkadot/types/codec'; +import { compactStripLength } from '@polkadot/util'; + +export function formatData (value: Bytes, { info, type }: TypeDef): Codec { + const input = compactStripLength(value.toU8a(true))[1]; + const data = createType('Data', input); + + if (info === TypeDefInfo.Option) { + return new Option( + createClass(type), + createTypeUnsafe(type, [data], true) + ); + } + + return createTypeUnsafe(type, [data], true); +} diff --git a/packages/api-contract/test/compare/incrementer.test.json b/packages/api-contract/test/compare/incrementer.test.json new file mode 100644 index 0000000000..3ef1637ccc --- /dev/null +++ b/packages/api-contract/test/compare/incrementer.test.json @@ -0,0 +1,188 @@ +[ + { + "info": 9, + "type": "{ \"value\": \"{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }\"} }\"} }", + "sub": [ + { + "info": 9, + "type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }\"} }", + "sub": [ + { + "info": 9, + "type": "{ \"cell\": \"([u8;32])\"} }", + "sub": [ + { + "info": 10, + "type": "([u8;32])", + "sub": [ + { + "info": 12, + "type": "[u8;32]", + "ext": { + "length": 32, + "type": "u8" + }, + "sub": { + "info": 6, + "type": "u8" + }, + "index": 0 + } + ], + "name": "cell", + "namespace": "ink_core::storage::key" + } + ], + "name": "cell", + "namespace": "ink_core::storage::cell::sync_cell", + "params": [ + { + "info": 6, + "type": "u32" + } + ] + } + ], + "name": "value", + "namespace": "ink_core::storage::value", + "params": [ + { + "info": 6, + "type": "u32" + } + ] + } + ], + "name": "Incrementer", + "namespace": "incrementer::normal" + }, + { + "info": 9, + "type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }\"} }", + "sub": [ + { + "info": 9, + "type": "{ \"cell\": \"([u8;32])\"} }", + "sub": [ + { + "info": 10, + "type": "([u8;32])", + "sub": [ + { + "info": 12, + "type": "[u8;32]", + "ext": { + "length": 32, + "type": "u8" + }, + "sub": { + "info": 6, + "type": "u8" + }, + "index": 0 + } + ], + "name": "cell", + "namespace": "ink_core::storage::key" + } + ], + "name": "cell", + "namespace": "ink_core::storage::cell::sync_cell", + "params": [ + { + "info": 6, + "type": "u32" + } + ] + } + ], + "name": "Value", + "namespace": "ink_core::storage::value", + "params": [ + { + "info": 6, + "type": "u32" + } + ] + }, + { + "info": 6, + "type": "u32" + }, + { + "info": 9, + "type": "{ \"cell\": \"([u8;32])\"} }", + "sub": [ + { + "info": 10, + "type": "([u8;32])", + "sub": [ + { + "info": 12, + "type": "[u8;32]", + "ext": { + "length": 32, + "type": "u8" + }, + "sub": { + "info": 6, + "type": "u8" + }, + "index": 0 + } + ], + "name": "cell", + "namespace": "ink_core::storage::key" + } + ], + "name": "SyncCell", + "namespace": "ink_core::storage::cell::sync_cell", + "params": [ + { + "info": 6, + "type": "u32" + } + ] + }, + { + "info": 10, + "type": "([u8;32])", + "sub": [ + { + "info": 12, + "type": "[u8;32]", + "ext": { + "length": 32, + "type": "u8" + }, + "sub": { + "info": 6, + "type": "u8" + }, + "index": 0 + } + ], + "name": "Key", + "namespace": "ink_core::storage::key" + }, + { + "info": 12, + "type": "[u8;32]", + "ext": { + "length": 32, + "type": "u8" + }, + "sub": { + "info": 6, + "type": "u8" + } + }, + { + "info": 6, + "type": "u8" + }, + { + "info": 6, + "type": "bool" + } +] \ No newline at end of file diff --git a/packages/api-contract/test/contracts/Incrementer.json b/packages/api-contract/test/contracts/Incrementer.json deleted file mode 100644 index 043229baa1..0000000000 --- a/packages/api-contract/test/contracts/Incrementer.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"Incrementer","deploy":{"args":[{"name":"init_value","type":"u32"}]},"messages":[{"name":"inc","selector":257544423,"mutates":true,"args":[{"name":"by","type":"u32"}],"return_type":null},{"name":"get","selector":4266279973,"mutates":false,"args":[],"return_type":"u32"},{"name":"compare","selector":363906316,"mutates":false,"args":[{"name":"with","type":"u32"}],"return_type":"bool"}]} \ No newline at end of file diff --git a/packages/api-contract/test/contracts/incrementer.json b/packages/api-contract/test/contracts/incrementer.json new file mode 100644 index 0000000000..e7d8c57ef8 --- /dev/null +++ b/packages/api-contract/test/contracts/incrementer.json @@ -0,0 +1,233 @@ +{ + "registry": { + "strings": [ + "Incrementer", + "incrementer", + "normal", + "value", + "Value", + "ink_core", + "storage", + "cell", + "SyncCell", + "sync_cell", + "Key", + "key", + "on_deploy", + "init_value", + "u32", + "inc", + "by", + "get", + "compare", + "with", + "bool" + ], + "types": [ + { + "id": { + "custom.name": 1, + "custom.namespace": [ + 2, + 3 + ], + "custom.params": [] + }, + "def": { + "struct.fields": [ + { + "name": 4, + "type": 2 + } + ] + } + }, + { + "id": { + "custom.name": 5, + "custom.namespace": [ + 6, + 7, + 4 + ], + "custom.params": [ + 3 + ] + }, + "def": { + "struct.fields": [ + { + "name": 8, + "type": 4 + } + ] + } + }, + { + "id": "u32", + "def": "builtin" + }, + { + "id": { + "custom.name": 9, + "custom.namespace": [ + 6, + 7, + 8, + 10 + ], + "custom.params": [ + 3 + ] + }, + "def": { + "struct.fields": [ + { + "name": 8, + "type": 5 + } + ] + } + }, + { + "id": { + "custom.name": 11, + "custom.namespace": [ + 6, + 7, + 12 + ], + "custom.params": [] + }, + "def": { + "tuple_struct.types": [ + 6 + ] + } + }, + { + "id": { + "array.len": 32, + "array.type": 7 + }, + "def": "builtin" + }, + { + "id": "u8", + "def": "builtin" + }, + { + "id": "bool", + "def": "builtin" + } + ] + }, + "storage": { + "struct.type": 1, + "struct.fields": [ + { + "name": 4, + "layout": { + "struct.type": 2, + "struct.fields": [ + { + "name": 8, + "layout": { + "range.offset": "0x0000000000000000000000000000000000000000000000000000000000000000", + "range.len": 1, + "range.elem_type": 3 + } + } + ] + } + } + ] + }, + "contract": { + "name": 1, + "constructors": [ + { + "name": 13, + "selector": "[\"0x00\",\"0x00\",\"0x00\",\"0x00\"]", + "args": [ + { + "name": 14, + "type": { + "ty": 3, + "display_name": [ + 15 + ] + } + } + ], + "docs": [ + "Initializes the value to the initial value." + ] + } + ], + "messages": [ + { + "name": 16, + "selector": "[\"0x0F\",\"0x59\",\"0xD0\",\"0xE7\"]", + "mutates": true, + "args": [ + { + "name": 17, + "type": { + "ty": 3, + "display_name": [ + 15 + ] + } + } + ], + "return_type": null, + "docs": [ + "Flips the current state of our smart contract." + ] + }, + { + "name": 18, + "selector": "[\"0xFE\",\"0x4A\",\"0x44\",\"0x25\"]", + "mutates": false, + "args": [], + "return_type": { + "ty": 3, + "display_name": [ + 15 + ] + }, + "docs": [ + "Returns the current state." + ] + }, + { + "name": 19, + "selector": "[\"0x15\",\"0xB0\",\"0xC5\",\"0x0C\"]", + "mutates": false, + "args": [ + { + "name": 20, + "type": { + "ty": 3, + "display_name": [ + 15 + ] + } + } + ], + "return_type": { + "ty": 8, + "display_name": [ + 21 + ] + }, + "docs": [ + "Returns `true` if the internal value is greater than or equal to the provided value." + ] + } + ], + "events": [], + "docs": [] + } +} \ No newline at end of file diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index ca95adefee..5471907652 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -39,7 +39,7 @@ interface MetaDecoration { toJSON: () => any; } -export default abstract class Decorate extends Events { +export default abstract class Decorate extends Events { // HACK Use BN import so decorateDerive works... yes, wtf. protected __phantom = new BN(0); @@ -183,7 +183,7 @@ export default abstract class Decorate extends Events { }); } - protected decorateRpc (rpc: RpcCore, decorateMethod: DecorateMethod): DecoratedRpc { + protected decorateRpc (rpc: RpcCore, decorateMethod: DecorateMethod): DecoratedRpc { return rpc.sections.reduce((out, _sectionName): DecoratedRpc => { const sectionName = _sectionName as keyof DecoratedRpc; @@ -201,7 +201,7 @@ export default abstract class Decorate extends Events { }, {} as unknown as DecoratedRpc); } - protected decorateMulti (decorateMethod: DecorateMethod): QueryableStorageMulti { + protected decorateMulti (decorateMethod: DecorateMethod): QueryableStorageMulti { return decorateMethod((calls: QueryableStorageMultiArg[]): Observable => this._rpcCore.state.subscribeStorage( calls.map((arg: QueryableStorageMultiArg): [QueryableStorageEntry, ...Arg[]] => @@ -211,7 +211,7 @@ export default abstract class Decorate extends Events { : [arg.creator] as any))); } - protected decorateExtrinsics (extrinsics: ModulesWithCalls, decorateMethod: DecorateMethod): SubmittableExtrinsics { + protected decorateExtrinsics (extrinsics: ModulesWithCalls, decorateMethod: DecorateMethod): SubmittableExtrinsics { const creator = createSubmittable(this._type, this._rx as ApiInterfaceRx, decorateMethod); return Object.entries(extrinsics).reduce((out, [name, section]): SubmittableExtrinsics => { @@ -225,14 +225,14 @@ export default abstract class Decorate extends Events { }, creator as unknown as SubmittableExtrinsics); } - private decorateExtrinsicEntry (method: CallFunction, creator: (value: Call | Uint8Array | string) => SubmittableExtrinsic): SubmittableExtrinsicFunction { + private decorateExtrinsicEntry (method: CallFunction, creator: (value: Call | Uint8Array | string) => SubmittableExtrinsic): SubmittableExtrinsicFunction { const decorated = (...params: Arg[]): SubmittableExtrinsic => creator(method(...params)); return this.decorateFunctionMeta(method, decorated as any) as SubmittableExtrinsicFunction; } - protected decorateStorage (storage: Storage, decorateMethod: DecorateMethod): QueryableStorage { + protected decorateStorage (storage: Storage, decorateMethod: DecorateMethod): QueryableStorage { return Object.entries(storage).reduce((out, [name, section]): QueryableStorage => { out[name] = Object.entries(section).reduce((out, [name, method]): QueryableModuleStorage => { out[name] = this.decorateStorageEntry(method, decorateMethod); @@ -244,7 +244,7 @@ export default abstract class Decorate extends Events { }, {} as unknown as QueryableStorage); } - private decorateStorageEntry (creator: StorageEntry, decorateMethod: DecorateMethod): QueryableStorageEntry { + private decorateStorageEntry (creator: StorageEntry, decorateMethod: DecorateMethod): QueryableStorageEntry { // get the storage arguments, with DoubleMap as an array entry, otherwise spread const getArgs = (...args: any[]): any[] => creator.meta.type.isDoubleMap ? [creator, args] @@ -288,7 +288,7 @@ export default abstract class Decorate extends Events { return this.decorateFunctionMeta(creator, decorated) as unknown as QueryableStorageEntry; } - private decorateStorageEntryLinked (creator: StorageEntry, decorateMethod: DecorateMethod): ReturnType> { + private decorateStorageEntryLinked (creator: StorageEntry, decorateMethod: DecorateMethod): ReturnType> { const result: Map]> = new Map(); let subject: BehaviorSubject; let head: Codec | null = null; diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index 4555569a7b..e3a44ac852 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -53,7 +53,7 @@ const TYPES_SPEC: Record> = { const l = logger('api/decorator'); -export default abstract class Init extends Decorate { +export default abstract class Init extends Decorate { private _healthTimer: NodeJS.Timeout | null = null; public constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { diff --git a/packages/api/src/base/index.ts b/packages/api/src/base/index.ts index 3f3c8f337c..92538ab1ec 100644 --- a/packages/api/src/base/index.ts +++ b/packages/api/src/base/index.ts @@ -6,7 +6,7 @@ import { RpcInterface } from '@polkadot/rpc-core/jsonrpc.types'; import { Hash, RuntimeVersion } from '@polkadot/types/interfaces'; import { InterfaceRegistry } from '@polkadot/types/interfaceRegistry'; import { CallFunction, InterfaceTypes, RegistryTypes, SignerPayloadRawBase } from '@polkadot/types/types'; -import { ApiOptions, ApiTypes, DecoratedRpc, DecorateMethod, QueryableStorage, QueryableStorageMulti, SubmittableExtrinsics, Signer } from '../types'; +import { ApiInterfaceRx, ApiOptions, ApiTypes, DecoratedRpc, DecorateMethod, QueryableStorage, QueryableStorageMulti, SubmittableExtrinsics, Signer } from '../types'; import { Constants } from '@polkadot/api-metadata/consts/types'; import { GenericCall, Metadata, createType, getTypeRegistry } from '@polkadot/types'; @@ -33,7 +33,7 @@ function assertResult (value: T | undefined): T { return value as T; } -export default abstract class ApiBase extends Init { +export default abstract class ApiBase extends Init { /** * @description Create an instance of the class * @@ -192,6 +192,13 @@ export default abstract class ApiBase extends Init { return assertResult(this._runtimeVersion); } + /** + * @description The underlying Rx API interface + */ + public get rx (): Pick { + return assertResult(this._rx as Pick); + } + /** * @description The type of this API instance, either 'rxjs' or 'promise' */ diff --git a/packages/api/src/promise/Api.ts b/packages/api/src/promise/Api.ts index 173542cc87..ebc2f91eac 100644 --- a/packages/api/src/promise/Api.ts +++ b/packages/api/src/promise/Api.ts @@ -60,7 +60,7 @@ function promiseTracker (resolve: (value: () => void) => void, reject: (value: E /** * @description Decorate method for ApiPromise, where the results are converted to the Promise equivalent */ -function decorateMethod (method: Method, options?: DecorateMethodOptions): StorageEntryPromiseOverloads { +export function decorateMethod (method: Method, options?: DecorateMethodOptions): StorageEntryPromiseOverloads { const needsCallback = options && options.methodName && options.methodName.includes('subscribe'); return function (...args: any[]): Promise>> | UnsubscribePromise { diff --git a/packages/api/src/promise/index.ts b/packages/api/src/promise/index.ts index 3e3d208c7e..7f9f3fec79 100644 --- a/packages/api/src/promise/index.ts +++ b/packages/api/src/promise/index.ts @@ -2,6 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import ApiPromise from './Api'; +import ApiPromise, { decorateMethod } from './Api'; export default ApiPromise; +export { decorateMethod }; diff --git a/packages/api/src/rx/Api.ts b/packages/api/src/rx/Api.ts index c37b006f76..a9f980f548 100644 --- a/packages/api/src/rx/Api.ts +++ b/packages/api/src/rx/Api.ts @@ -9,7 +9,7 @@ import { from, Observable } from 'rxjs'; import ApiBase from '../base'; -function decorateMethod (method: Method): Method { +export function decorateMethod (method: Method): Method { return method; } diff --git a/packages/api/src/rx/index.ts b/packages/api/src/rx/index.ts index edd5756c99..620c97fb3a 100644 --- a/packages/api/src/rx/index.ts +++ b/packages/api/src/rx/index.ts @@ -2,6 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import ApiRx from './Api'; +import ApiRx, { decorateMethod } from './Api'; export default ApiRx; +export { decorateMethod }; diff --git a/packages/api/src/submittable/Submittable.ts b/packages/api/src/submittable/Submittable.ts index c7cc94fcc7..6519d282be 100644 --- a/packages/api/src/submittable/Submittable.ts +++ b/packages/api/src/submittable/Submittable.ts @@ -6,7 +6,7 @@ import { AccountId, Address, Call, Extrinsic, ExtrinsicEra, ExtrinsicStatus, Hash, Header, Index } from '@polkadot/types/interfaces'; import { Callback, Codec, Constructor, IKeyringPair, SignatureOptions } from '@polkadot/types/types'; import { ApiInterfaceRx, ApiTypes, SignerResult } from '../types'; -import { SignerOptions, SubmittableExtrinsic, SubmittableResultImpl, SubmitableResultResult, SubmitableResultSubscription } from './types'; +import { SignerOptions, SubmittableExtrinsic, SubmittableResultImpl, SubmittableResultResult, SubmittableResultSubscription } from './types'; import { Observable, combineLatest, of } from 'rxjs'; import { first, map, mergeMap, switchMap, tap } from 'rxjs/operators'; @@ -17,7 +17,7 @@ import { filterEvents, isKeyringPair } from '../util'; import ApiBase from '../base'; import SubmittableResult from './Result'; -interface SubmittableOptions { +interface SubmittableOptions { api: ApiInterfaceRx; decorateMethod: ApiBase['decorateMethod']; type: ApiTypes; @@ -32,7 +32,7 @@ const DEFAULT_MORTAL_LENGTH = 5 * ONE_MINUTE; const _Extrinsic: Constructor = ClassOf('Extrinsic'); -export default class Submittable extends _Extrinsic implements SubmittableExtrinsic { +export default class Submittable extends _Extrinsic implements SubmittableExtrinsic { private readonly _api: ApiInterfaceRx; private readonly _decorateMethod: ApiBase['decorateMethod']; @@ -61,16 +61,16 @@ export default class Submittable extends _Extrinsic implements Submitta } // signAndSend with an immediate Hash result - public signAndSend (account: IKeyringPair | string | AccountId | Address, options?: Partial): SubmitableResultResult; + public signAndSend (account: IKeyringPair | string | AccountId | Address, options?: Partial): SubmittableResultResult; // signAndSend with a subscription, i.e. callback provided - public signAndSend (account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SubmitableResultSubscription; + public signAndSend (account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SubmittableResultSubscription; // signAndSend with options and a callback - public signAndSend (account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SubmitableResultSubscription; + public signAndSend (account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SubmittableResultSubscription; // signAndSend implementation for all 3 cases above - public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial | Callback, optionalStatusCb?: Callback): SubmitableResultResult | SubmitableResultSubscription { + public signAndSend (account: IKeyringPair | string | AccountId | Address, optionsOrStatus?: Partial | Callback, optionalStatusCb?: Callback): SubmittableResultResult | SubmittableResultSubscription { const [options, statusCb] = this._makeSignAndSendOptions(optionsOrStatus, optionalStatusCb); const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb); const address = isKeyringPair(account) ? account.address : account.toString(); @@ -95,17 +95,17 @@ export default class Submittable extends _Extrinsic implements Submitta : this._sendObservable(updateId); }) ) as Observable) // FIXME This is wrong, SubmittableResult is _not_ a codec - )(statusCb) as SubmitableResultResult | SubmitableResultSubscription; + )(statusCb) as SubmittableResultResult | SubmittableResultSubscription; } // send with an immediate Hash result - public send (): SubmitableResultResult; + public send (): SubmittableResultResult; // send with a status callback - public send (statusCb: Callback): SubmitableResultSubscription; + public send (statusCb: Callback): SubmittableResultSubscription; // send implementation for both immediate Hash and statusCb variants - public send (statusCb?: Callback): SubmitableResultResult | SubmitableResultSubscription { + public send (statusCb?: Callback): SubmittableResultResult | SubmittableResultSubscription { const isSubscription = this._api.hasSubscriptions && (this._ignoreStatusCb || !!statusCb); return this._decorateMethod( diff --git a/packages/api/src/submittable/createSubmittable.ts b/packages/api/src/submittable/createSubmittable.ts index 8d2c387de9..d473d4dd51 100644 --- a/packages/api/src/submittable/createSubmittable.ts +++ b/packages/api/src/submittable/createSubmittable.ts @@ -9,11 +9,11 @@ import { SubmittableExtrinsic } from './types'; import ApiBase from '../base'; -type Creator = (extrinsic: Call | Uint8Array | string) => SubmittableExtrinsic; +type Creator = (extrinsic: Call | Uint8Array | string) => SubmittableExtrinsic; let Submittable: Constructor>; -export default function createSubmittable (type: ApiTypes, api: ApiInterfaceRx, decorateMethod: ApiBase['decorateMethod']): Creator { +export default function createSubmittable (type: ApiTypes, api: ApiInterfaceRx, decorateMethod: ApiBase['decorateMethod']): Creator { return (extrinsic: Call | Uint8Array | string): SubmittableExtrinsic => { // HACK This is not great, but basically what we do here is to lazily only require the class // right at the point it is actually needed - delaying initialization diff --git a/packages/api/src/submittable/types.ts b/packages/api/src/submittable/types.ts index ebfc58b164..e2fb08eec2 100644 --- a/packages/api/src/submittable/types.ts +++ b/packages/api/src/submittable/types.ts @@ -4,6 +4,7 @@ import { AccountId, Address, ExtrinsicStatus, EventRecord, Hash } from '@polkadot/types/interfaces'; import { AnyNumber, AnyU8a, Callback, IExtrinsic, IExtrinsicEra, IKeyringPair, SignatureOptions } from '@polkadot/types/types'; +import { ApiTypes } from '../types'; import { Observable } from 'rxjs'; @@ -22,12 +23,12 @@ export interface SubmittableResultValue { status: ExtrinsicStatus; } -export type SubmitableResultResult = +export type SubmittableResultResult = ApiType extends 'rxjs' ? Observable : Promise; -export type SubmitableResultSubscription = +export type SubmittableResultSubscription = ApiType extends 'rxjs' ? Observable : Promise<() => void>; @@ -39,16 +40,16 @@ export interface SignerOptions { tip?: AnyNumber; } -export interface SubmittableExtrinsic extends IExtrinsic { - send(): SubmitableResultResult; +export interface SubmittableExtrinsic extends IExtrinsic { + send(): SubmittableResultResult; - send(statusCb: Callback): SubmitableResultSubscription; + send(statusCb: Callback): SubmittableResultSubscription; sign(account: IKeyringPair, _options: Partial): this; - signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial): SubmitableResultResult; + signAndSend(account: IKeyringPair | string | AccountId | Address, options?: Partial): SubmittableResultResult; - signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SubmitableResultSubscription; + signAndSend(account: IKeyringPair | string | AccountId | Address, statusCb: Callback): SubmittableResultSubscription; - signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SubmitableResultSubscription; + signAndSend(account: IKeyringPair | string | AccountId | Address, options: Partial, statusCb?: Callback): SubmittableResultSubscription; } diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index d5c1b6fb9d..6a4b7dc202 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -51,11 +51,11 @@ export interface DecorateMethodOptions { methodName?: string; } -export type DecorateMethod = Observable>(method: Method, options?: DecorateMethodOptions) => any; +export type DecorateMethod = Observable>(method: Method, options?: DecorateMethodOptions) => any; // Here are the return types of these parts of the api: // - api.query.*.*: no exact typings -// - api.tx.*.*: SubmittableExtrinsic +// - api.tx.*.*: SubmittableExtrinsic // - api.derive.*.*: MethodResult // - api.rpc.*.*: no exact typings (for now, FIXME: should be MethodResult, like in derive) @@ -73,22 +73,22 @@ export interface PromiseResult { } // FIXME The day TS has higher-kinded types, we can remove this hardcoded stuff -export type PromiseOrObs = ApiType extends 'rxjs' +export type PromiseOrObs = ApiType extends 'rxjs' ? Observable : Promise // FIXME The day TS has higher-kinded types, we can remove this hardcoded stuff -export type MethodResult = ApiType extends 'rxjs' +export type MethodResult = ApiType extends 'rxjs' ? RxResult : PromiseResult; -export type DecoratedRpcSection = { +export type DecoratedRpcSection = { [Method in keyof Section]: Section[Method] extends AnyFunction ? MethodResult : never } -export type DecoratedRpc = { +export type DecoratedRpc = { [Section in keyof AllSections]: DecoratedRpcSection } @@ -101,7 +101,7 @@ interface StorageEntryPromiseMulti { (args: (CodecArg[] | CodecArg)[], callback: Callback): UnsubscribePromise; } -export interface StorageEntryBase { +export interface StorageEntryBase { at: (hash: Hash | Uint8Array | string, ...args: Parameters) => PromiseOrObs>>; creator: StorageEntry; hash: (...args: Parameters) => PromiseOrObs; @@ -118,33 +118,33 @@ export interface StorageEntryPromiseOverloads { (arg1: CodecArg, arg2: CodecArg, callback: Callback): UnsubscribePromise; } -export type StorageEntryExact = MethodResult & StorageEntryBase +export type StorageEntryExact = MethodResult & StorageEntryBase // This is the most generic typings we can have for a storage entry function type GenericStorageEntryFunction = (arg1?: CodecArg, arg2?: CodecArg) => Observable -export type QueryableStorageEntry = +export type QueryableStorageEntry = ApiType extends 'rxjs' ? StorageEntryExact<'rxjs', GenericStorageEntryFunction> : StorageEntryExact<'promise', GenericStorageEntryFunction> & StorageEntryPromiseOverloads; -export interface QueryableModuleStorage { +export interface QueryableModuleStorage { [index: string]: QueryableStorageEntry; } -export type QueryableStorageMultiArg = +export type QueryableStorageMultiArg = QueryableStorageEntry | [QueryableStorageEntry, ...CodecArg[]]; -export interface QueryableStorageMultiBase { +export interface QueryableStorageMultiBase { (calls: QueryableStorageMultiArg[]): Observable; } -export interface QueryableStorageMultiPromise { +export interface QueryableStorageMultiPromise { (calls: QueryableStorageMultiArg[], callback: Callback): UnsubscribePromise; } -export type QueryableStorageMulti = +export type QueryableStorageMulti = ApiType extends 'rxjs' ? QueryableStorageMultiBase : QueryableStorageMultiPromise; @@ -152,21 +152,21 @@ export type QueryableStorageMulti = // QueryableStorageExact will hold the exact typed api.query.*.* generated from // metadata. For now it's empty, it's ready to be module augmented. // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface QueryableStorageExact { } +export interface QueryableStorageExact { } -export interface QueryableStorage extends QueryableStorageExact { +export interface QueryableStorage extends QueryableStorageExact { [index: string]: QueryableModuleStorage; } -export interface SubmittableExtrinsicFunction extends CallFunction { +export interface SubmittableExtrinsicFunction extends CallFunction { (...params: CodecArg[]): SubmittableExtrinsic; } -export interface SubmittableModuleExtrinsics { +export interface SubmittableModuleExtrinsics { [index: string]: SubmittableExtrinsicFunction; } -export interface SubmittableExtrinsics { +export interface SubmittableExtrinsics { (extrinsic: Uint8Array | string): SubmittableExtrinsic; [index: string]: SubmittableModuleExtrinsics; } diff --git a/packages/api/src/util/decorate.ts b/packages/api/src/util/decorate.ts index b44fe5f58a..47d107b061 100644 --- a/packages/api/src/util/decorate.ts +++ b/packages/api/src/util/decorate.ts @@ -3,14 +3,14 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { AnyFunction } from '@polkadot/types/types'; -import { DecorateMethod, MethodResult } from '../types'; +import { ApiTypes, DecorateMethod, MethodResult } from '../types'; // Most generic typings for `api.derive.*.*` type AnyDerive = Record>; // Exact typings for a particular section `api.derive.section.*` -type DeriveSection> = { [MethodName in keyof Section]: MethodResult }; +type DeriveSection> = { [MethodName in keyof Section]: MethodResult }; // Exact typings for all sections `api.derive.*.*` -export type DeriveAllSections = { [SectionName in keyof AllSections]: DeriveSection }; +export type DeriveAllSections = { [SectionName in keyof AllSections]: DeriveSection }; // A technically unsafe version of Object.keys(obj) that assumes that // obj only has known properties of T @@ -21,7 +21,7 @@ function keys (obj: T): (keyof T)[] { /** * This is a methods decorator which keeps all type information. */ -function decorateMethods> ( +function decorateMethods> ( section: Section, decorateMethod: DecorateMethod ): DeriveSection { @@ -43,7 +43,7 @@ function decorateMethods> ( /** * This is a section decorator which keeps all type information. */ -export function decorateSections ( +export function decorateSections ( allSections: AllSections, decorateMethod: DecorateMethod ): DeriveAllSections {