Expose ABI messages on Contract/Blueprint (#2715)

* Expose ABI messages on Contract/Blueprint

* Rename actual files & exports

* Adjust .query signature (origin)

* Assert contracts.call
This commit is contained in:
Jaco Greeff
2020-10-12 18:27:47 +02:00
committed by GitHub
parent e64b5483ba
commit 8d8d5bd7d7
16 changed files with 149 additions and 51 deletions
+22 -1
View File
@@ -6,10 +6,12 @@ import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import { AccountId, EventRecord, Hash } from '@polkadot/types/interfaces';
import { AnyJson, CodecArg, ISubmittableResult } from '@polkadot/types/types';
import { AbiConstructor } from '../types';
import { MapConstructorExec } from './types';
import BN from 'bn.js';
import { SubmittableResult } from '@polkadot/api';
import ApiBase from '@polkadot/api/base';
import { isUndefined, stringCamelCase } from '@polkadot/util';
import Abi from '../Abi';
import { applyOnEvent, encodeMessage } from '../util';
@@ -29,13 +31,28 @@ export class BlueprintSubmittableResult<ApiType extends ApiTypes> extends Submit
export default class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly codeHash: Hash;
readonly #tx: MapConstructorExec<ApiType> = {};
constructor (api: ApiBase<ApiType>, abi: AnyJson | Abi, codeHash: string | Hash | Uint8Array, decorateMethod: DecorateMethod<ApiType>) {
super(api, abi, decorateMethod);
this.codeHash = this.registry.createType('Hash', codeHash);
this.abi.constructors.forEach((c): void => {
const messageName = stringCamelCase(c.identifier);
if (isUndefined(this.#tx[messageName])) {
this.#tx[messageName] = (endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]) =>
this.#deploy(c, endowment, gasLimit, params);
}
});
}
public createContract (constructorOrId: AbiConstructor | string| number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> {
public get tx (): MapConstructorExec<ApiType> {
return this.#tx;
}
#deploy = (constructorOrId: AbiConstructor | string| number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> => {
return this.api.tx.contracts
.instantiate(endowment, gasLimit, this.codeHash, encodeMessage(this.registry, this.abi.findConstructor(constructorOrId), params))
.withResultTransform((result: ISubmittableResult) =>
@@ -44,4 +61,8 @@ export default class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
))
);
}
public createContract (constructorOrId: AbiConstructor | string| number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> {
return this.#deploy(constructorOrId, endowment, gasLimit, params);
}
}
+46 -25
View File
@@ -1,69 +1,82 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod, ObsInnerType } from '@polkadot/api/types';
import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import { AccountId, ContractExecResult } from '@polkadot/types/interfaces';
import { AnyJson, CodecArg, IKeyringPair } from '@polkadot/types/types';
import { AnyJson, CodecArg } from '@polkadot/types/types';
import { AbiMessage, ContractCallOutcome } from '../types';
import { ContractCallResult, ContractRead, MapMessageExec, MapMessageRead } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import ApiBase from '@polkadot/api/base';
import { assert, isFunction } from '@polkadot/util';
import { assert, isFunction, isUndefined, stringCamelCase } from '@polkadot/util';
import Abi from '../Abi';
import { encodeMessage, formatData } from '../util';
import Base from './Base';
type ContractCallTypes = 'tx' | 'rpc';
type ContractCallResultSubscription<ApiType extends ApiTypes, CallType extends ContractCallTypes> = ApiType extends 'rxjs'
// eslint-disable-next-line no-use-before-define
? Observable<ContractCallResult<CallType>>
// eslint-disable-next-line no-use-before-define
: Promise<ObsInnerType<ContractCallResult<CallType>>>;
export interface ContractRead<ApiType extends ApiTypes> {
send (account: IKeyringPair | string | AccountId): ContractCallResultSubscription<ApiType, 'rpc'>;
}
export type ContractCallResult<CallType extends ContractCallTypes> = CallType extends 'rpc'
? Observable<ContractCallOutcome>
: Observable<SubmittableResult>;
const ERROR_NO_CALL = 'Your node does not expose the contracts.call RPC. This is most probably due to a runtime configuration.';
export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly address: AccountId;
readonly #tx: MapMessageExec<ApiType> = {};
readonly #query: MapMessageRead<ApiType> = {};
constructor (api: ApiBase<ApiType>, abi: AnyJson | Abi, address: string | AccountId, decorateMethod: DecorateMethod<ApiType>) {
super(api, abi, decorateMethod);
this.address = this.registry.createType('AccountId', address);
this.abi.messages.forEach((m): void => {
const messageName = stringCamelCase(m.identifier);
if (isUndefined(this.#tx[messageName])) {
this.#tx[messageName] = (value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) =>
this.#exec(m, value, gasLimit, params);
}
if (isUndefined(this.#query[messageName])) {
this.#query[messageName] = (origin: string | AccountId | Uint8Array, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) =>
this.#read(m, value, gasLimit, params).send(origin);
}
});
}
public get hasRpcContractsCall (): boolean {
return isFunction(this.api.rx.rpc.contracts?.call);
}
public exec (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic<ApiType> {
public get query (): MapMessageRead<ApiType> {
assert(this.hasRpcContractsCall, ERROR_NO_CALL);
return this.#query;
}
public get tx (): MapMessageExec<ApiType> {
return this.#tx;
}
#exec = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): SubmittableExtrinsic<ApiType> => {
return this.api.tx.contracts.call(this.address, value, gasLimit, encodeMessage(this.registry, this.abi.findMessage(messageOrId), params));
}
public read (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): ContractRead<ApiType> {
assert(this.hasRpcContractsCall, 'Your node does not support contract RPC read calls');
#read = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): ContractRead<ApiType> => {
assert(this.hasRpcContractsCall, ERROR_NO_CALL);
const message = this.abi.findMessage(messageOrId);
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((account: string | AccountId | Uint8Array): ContractCallResult<'rpc'> =>
send: this._decorateMethod((origin: string | AccountId | Uint8Array): ContractCallResult<'rpc'> =>
this.api.rx.rpc.contracts.call({
dest: this.address,
gasLimit,
inputData: encodeMessage(this.registry, message, params),
origin: account,
origin,
value
}).pipe(
map((result: ContractExecResult): ContractCallOutcome => ({
@@ -76,4 +89,12 @@ export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
)
};
}
public exec (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic<ApiType> {
return this.#exec(messageOrId, value, gasLimit, params);
}
public read (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): ContractRead<ApiType> {
return this.#read(messageOrId, value, gasLimit, params);
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2020 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { default as Blueprint } from './Blueprint';
export { default as Code } from './Code';
export { default as Blueprint, BlueprintSubmittableResult } from './Blueprint';
export { default as Code, CodeSubmittableResult } from './Code';
export { default as Contract } from './Contract';
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { SubmittableResult } from '@polkadot/api';
import { ApiTypes, ObsInnerType } from '@polkadot/api/types';
import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import { AccountId } from '@polkadot/types/interfaces';
import { CodecArg } from '@polkadot/types/types';
import { ContractCallOutcome } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
type ContractCallTypes = 'tx' | 'rpc';
type ContractCallResultSubscription<ApiType extends ApiTypes, CallType extends ContractCallTypes> = ApiType extends 'rxjs'
? Observable<ContractCallResult<CallType>>
: Promise<ObsInnerType<ContractCallResult<CallType>>>;
export interface ContractRead<ApiType extends ApiTypes> {
send (account: string | AccountId | Uint8Array): ContractCallResultSubscription<ApiType, 'rpc'>;
}
export type ContractCallResult<CallType extends ContractCallTypes> = CallType extends 'rpc'
? Observable<ContractCallOutcome>
: Observable<SubmittableResult>;
export interface MapConstructorExec<ApiType extends ApiTypes> {
[message: string]: (endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]) => SubmittableExtrinsic<ApiType>;
}
export interface MapMessageExec<ApiType extends ApiTypes> {
[message: string]: (value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) => SubmittableExtrinsic<ApiType>;
}
export interface MapMessageRead<ApiType extends ApiTypes> {
[message: string]: (origin: AccountId | string | Uint8Array, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) => ContractCallResultSubscription<ApiType, 'rpc'>;
}
+2 -5
View File
@@ -1,10 +1,7 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { CodeSubmittableResult } from './base/Code';
export { BlueprintSubmittableResult } from './base/Blueprint';
export { default as Abi } from './Abi';
export * from './rx';
export * from './promise';
export { Blueprint as BlueprintRx, Code as CodeRx, Contract as ContractRx } from './rx';
export { Blueprint as BlueprintPromise, Code as CodePromise, Contract as ContractPromise } from './promise';
@@ -8,9 +8,9 @@ import { ApiPromise } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/promise';
import Abi from '../Abi';
import Blueprint from '../base/Blueprint';
import BaseBlueprint from '../base/Blueprint';
export default class PromiseBlueprint extends Blueprint<'promise'> {
export default class Blueprint extends BaseBlueprint<'promise'> {
constructor (api: ApiPromise, abi: AnyJson | Abi, codeHash: string | Hash) {
super(api, abi, codeHash, decorateMethod);
}
@@ -6,9 +6,9 @@ import { decorateMethod } from '@polkadot/api/promise';
import { AnyJson } from '@polkadot/types/types';
import Abi from '../Abi';
import Code from '../base/Code';
import BaseCode from '../base/Code';
export default class PromiseCode extends Code<'promise'> {
export default class Code extends BaseCode<'promise'> {
constructor (api: ApiPromise, abi: AnyJson | Abi, wasm: Uint8Array | string | Buffer) {
super(api, abi, wasm, decorateMethod);
}
@@ -8,9 +8,9 @@ import { ApiPromise } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/promise';
import Abi from '../Abi';
import Contract from '../base/Contract';
import BaseContract from '../base/Contract';
export default class PromiseContract extends Contract<'promise'> {
export default class Contract extends BaseContract<'promise'> {
constructor (api: ApiPromise, abi: AnyJson | Abi, address: string | AccountId) {
super(api, abi, address, decorateMethod);
}
+3 -3
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { default as BlueprintPromise } from './PromiseBlueprint';
export { default as CodePromise } from './PromiseCode';
export { default as ContractPromise } from './PromiseContract';
export { default as Blueprint } from './Blueprint';
export { default as Code } from './Code';
export { default as Contract } from './Contract';
@@ -0,0 +1,7 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BlueprintSubmittableResult as BaseBlueprintSubmittableResult, CodeSubmittableResult as BaseCodeSubmittableResult } from '../base';
export type BlueprintSubmittableResult = BaseBlueprintSubmittableResult<'promise'>;
export type CodeSubmittableResult = BaseCodeSubmittableResult<'promise'>;
@@ -8,9 +8,9 @@ import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Blueprint from '../base/Blueprint';
import BaseBlueprint from '../base/Blueprint';
export default class RxBlueprint extends Blueprint<'rxjs'> {
export default class Blueprint extends BaseBlueprint<'rxjs'> {
constructor (api: ApiRx, abi: AnyJson | Abi, codeHash: string | Hash) {
super(api, abi, codeHash, decorateMethod);
}
@@ -7,9 +7,9 @@ import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Code from '../base/Code';
import BaseCode from '../base/Code';
export default class RxCode extends Code<'rxjs'> {
export default class Code extends BaseCode<'rxjs'> {
constructor (api: ApiRx, abi: AnyJson | Abi, wasm: Uint8Array | string | Buffer) {
super(api, abi, wasm, decorateMethod);
}
@@ -8,9 +8,9 @@ import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Contract from '../base/Contract';
import BaseContract from '../base/Contract';
export default class RxContract extends Contract<'rxjs'> {
export default class Contract extends BaseContract<'rxjs'> {
constructor (api: ApiRx, abi: AnyJson | Abi, address: string | AccountId) {
super(api, abi, address, decorateMethod);
}
+3 -3
View File
@@ -1,6 +1,6 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { default as BlueprintRx } from './RxBlueprint';
export { default as CodeRx } from './RxCode';
export { default as ContractRx } from './RxContract';
export { default as Blueprint } from './Blueprint';
export { default as Code } from './Code';
export { default as Contract } from './Contract';
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BlueprintSubmittableResult as BaseBlueprintSubmittableResult, CodeSubmittableResult as BaseCodeSubmittableResult } from '../base';
export type BlueprintSubmittableResult = BaseBlueprintSubmittableResult<'promise'>;
export type CodeSubmittableResult = BaseCodeSubmittableResult<'promise'>;