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'>;
}