Expose instantiate on contract Blueprint (#2706)

* Expose instantiate on contract Blueprint

* Allow BigInt & string types

* CodecArg

* Selector

* Cleanup selectors

* Remove Bytes override
This commit is contained in:
Jaco Greeff
2020-10-11 18:51:21 +02:00
committed by GitHub
parent 4cb563ba89
commit af0b4362c9
7 changed files with 97 additions and 111 deletions
+30 -46
View File
@@ -1,37 +1,14 @@
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { AnyJson, CodecArg, Constructor, Registry } from '@polkadot/types/types';
import { AnyJson } from '@polkadot/types/types';
import { InkConstructorSpec, InkMessageSpec } from '@polkadot/types/interfaces';
import { AbiConstructor, AbiMessage, AbiMessageParam } from './types';
import { Compact, createClass, encodeTypeDef } from '@polkadot/types';
import { assert, isObject, isUndefined, stringCamelCase } from '@polkadot/util';
import { assert, isNumber, isObject, stringCamelCase } from '@polkadot/util';
import ContractRegistry from './ContractRegistry';
function createArgClass (registry: Registry, args: AbiMessageParam[], baseDef: Record<string, string>): Constructor {
return createClass(
registry,
JSON.stringify(
args.reduce((base: Record<string, any>, { name, type }): Record<string, any> => {
base[name] = type.displayName || encodeTypeDef(type);
return base;
}, baseDef)
)
);
}
function extendBase (fn: AbiMessage, add: Partial<AbiMessage> = {}): AbiMessage {
return Object.entries(add).reduce((fn: AbiMessage, [key, value]): AbiMessage => {
// do some magic
fn[key as 'args'] = value as AbiMessageParam[];
return fn;
}, fn);
}
export default class Abi extends ContractRegistry {
public readonly constructors: AbiConstructor[];
@@ -41,16 +18,14 @@ export default class Abi extends ContractRegistry {
super(json);
this.constructors = this.project.spec.constructors.map((spec: InkConstructorSpec, index) =>
this._createBase(spec, {
index,
this._createBase(spec, index, {
isConstructor: true
})
);
this.messages = this.project.spec.messages.map((spec: InkMessageSpec, index): AbiMessage => {
const typeSpec = spec.returnType.unwrapOr(null);
return this._createBase(spec, {
index,
return this._createBase(spec, index, {
isMutating: spec.mutates.isTrue,
isPayable: spec.payable.isTrue,
returnType: typeSpec
@@ -60,7 +35,27 @@ export default class Abi extends ContractRegistry {
});
}
private _createBase (spec: InkMessageSpec | InkConstructorSpec, add: Partial<AbiMessage> = {}): AbiMessage {
public findConstructor (constructorOrIndex: AbiConstructor | number): AbiConstructor {
const message = isNumber(constructorOrIndex)
? this.constructors[constructorOrIndex]
: constructorOrIndex;
assert(message, 'Attempted to call an invalid contract message');
return message;
}
public findMessage (messageOrIndex: AbiMessage | number): AbiMessage {
const message = isNumber(messageOrIndex)
? this.messages[messageOrIndex]
: messageOrIndex;
assert(message, 'Attempted to call an invalid contract message');
return message;
}
private _createBase (spec: InkMessageSpec | InkConstructorSpec, index: number, add: Partial<AbiMessage> = {}): AbiMessage {
const identifier = spec.name.toString();
const args = spec.args.map((arg, index): AbiMessageParam => {
try {
@@ -77,24 +72,13 @@ export default class Abi extends ContractRegistry {
}
});
const Clazz = createArgClass(this.registry, args, isUndefined(spec.selector) ? {} : { __selector: 'u32' });
const baseStruct: { [index: string]: any } = { __selector: this.registry.createType('u32', spec.selector) };
const fn = ((...params: CodecArg[]): Uint8Array => {
assert(params.length === args.length, `Expected ${args.length} arguments to contract message '${identifier}', found ${params.length}`);
return Compact.addLengthPrefix(new Clazz(this.registry, args.reduce((mapped, { name }, index): Record<string, CodecArg> => {
mapped[name] = params[index];
return mapped;
}, { ...baseStruct })).toU8a());
}) as AbiMessage;
return extendBase(fn, {
return {
...add,
args,
docs: spec.docs.map((doc) => doc.toString()),
identifier
});
identifier,
index,
selector: spec.selector
};
}
}
+10 -8
View File
@@ -2,20 +2,20 @@
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod, SignerOptions } from '@polkadot/api/types';
import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import { AccountId, EventRecord, Hash } from '@polkadot/types/interfaces';
import { AnyJson, IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { ApiObject } from '../types';
import { AnyJson, CodecArg, IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { AbiConstructor, ApiObject } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { assert } from '@polkadot/util';
import Abi from '../Abi';
import { applyOnEvent, encodeMessage } from '../util';
import Base from './Base';
import Contract from './Contract';
import { applyOnEvent } from './util';
// eslint-disable-next-line no-use-before-define
type BlueprintCreateResultSubscription<ApiType extends ApiTypes> = Observable<BlueprintCreateResult<ApiType>>;
@@ -38,21 +38,23 @@ class BlueprintCreateResult<ApiType extends ApiTypes> extends SubmittableResult
export default class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly codeHash: Hash;
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>, codeHash: string | Hash) {
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>, codeHash: string | Hash | Uint8Array) {
super(api, abi, decorateMethod);
this.codeHash = this.registry.createType('Hash', codeHash);
}
public deployContract (constructorIndex = 0, endowment: number | BN, maxGas: number | BN, ...params: any[]): BlueprintCreate<ApiType> {
assert(!!this.abi.constructors[constructorIndex], `Specified constructor index ${constructorIndex} does not exist`);
public instantiate (constructorOrIndex: AbiConstructor | number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): SubmittableExtrinsic<ApiType> {
return this.api.tx.contracts.instantiate(endowment, gasLimit, this.codeHash, encodeMessage(this.registry, this.abi.findConstructor(constructorOrIndex), params)) as SubmittableExtrinsic<ApiType>;
}
public createContract (constructorOrIndex: AbiConstructor | number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): BlueprintCreate<ApiType> {
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
signAndSend: this._decorateMethod(
(account: IKeyringPair | string | AccountId, options?: SignerOptions): BlueprintCreateResultSubscription<ApiType> =>
this.api.rx.tx.contracts
.create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params))
.instantiate(endowment, gasLimit, this.codeHash, encodeMessage(this.registry, this.abi.findConstructor(constructorOrIndex), params))
.signAndSend(account, options)
.pipe(
map((result) =>
+1 -1
View File
@@ -12,9 +12,9 @@ import { SubmittableResult } from '@polkadot/api';
import { compactAddLength, u8aToU8a } from '@polkadot/util';
import Abi from '../Abi';
import { applyOnEvent } from '../util';
import Base from './Base';
import Blueprint from './Blueprint';
import { applyOnEvent } from './util';
// eslint-disable-next-line no-use-before-define
type CodePutCodeResultSubscription<ApiType extends ApiTypes> = Observable<CodePutCodeResult<ApiType>>;
+15 -32
View File
@@ -3,7 +3,6 @@
import { ApiTypes, DecorateMethod, ObsInnerType } 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 { ApiObject, AbiMessage, ContractCallOutcome } from '../types';
@@ -12,10 +11,10 @@ import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { assert, isFunction, isNumber } from '@polkadot/util';
import { assert, isFunction } from '@polkadot/util';
import Abi from '../Abi';
import { formatData } from '../util';
import { encodeMessage, formatData } from '../util';
import Base from './Base';
type ContractCallTypes = 'tx' | 'rpc';
@@ -47,44 +46,28 @@ export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
return isFunction(this.api.rx.rpc.contracts?.call);
}
private _createInput (message: AbiMessage | number, params: CodecArg[]): [Uint8Array, AbiMessage] {
const fn = isNumber(message)
? this.abi.messages[message]
: message;
assert(fn, 'Attempted to call an invalid contract message');
return [fn(...params), fn];
public exec (messageOrIndex: AbiMessage | 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(messageOrIndex), params)) as SubmittableExtrinsic<ApiType>;
}
public exec = (message: AbiMessage | number, value: BN | string | number, gasLimit: BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic<ApiType> => {
const [inputData] = this._createInput(message, params);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore I give up
return this.api.tx.contracts.call(this.address, value, gasLimit, inputData);
}
public read = (message: AbiMessage | number, value: BN | string | number, gasLimit: BN | string | number, ...params: CodecArg[]): ContractRead<ApiType> => {
public read (messageOrIndex: AbiMessage | 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');
const [inputData, messageFn] = this._createInput(message, params);
const message = this.abi.findMessage(messageOrIndex);
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((account: IKeyringPair | string | AccountId): ContractCallResult<'rpc'> =>
this.api.rx.rpc.contracts.call(
this.registry.createType('ContractCallRequest', {
dest: this.address,
gasLimit,
inputData,
origin: account,
value
})
).pipe(
this.api.rx.rpc.contracts.call({
dest: this.address,
gasLimit,
inputData: encodeMessage(this.registry, message, params),
origin: account,
value
}).pipe(
map((result: ContractExecResult): ContractCallOutcome => ({
output: result.isSuccess && messageFn.returnType
? formatData(this.registry, result.asSuccess.data, messageFn.returnType)
output: result.isSuccess && message.returnType
? formatData(this.registry, result.asSuccess.data, message.returnType)
: null,
result
}))
-17
View File
@@ -1,17 +0,0 @@
// Copyright 2017-2020 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { SubmittableResult } from '@polkadot/api';
import { EventRecord } from '@polkadot/types/interfaces';
export function applyOnEvent <T> (result: SubmittableResult, type: 'CodeStored' | 'Instantiated', fn: (record: EventRecord) => T): T | undefined {
if (result.isInBlock || result.isFinalized) {
const record = result.findRecord('contract', 'CodeStored');
if (record) {
return fn(record);
}
}
return undefined;
}
+3 -5
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes } from '@polkadot/api/types';
import { ContractExecResult } from '@polkadot/types/interfaces';
import { Codec, CodecArg, TypeDef } from '@polkadot/types/types';
import { ContractExecResult, InkSelector } from '@polkadot/types/interfaces';
import { Codec, TypeDef } from '@polkadot/types/types';
import { ApiPromise, ApiRx } from '@polkadot/api';
import Abi from './Abi';
@@ -34,9 +34,7 @@ export interface AbiMessage {
isMutating?: boolean;
isPayable?: boolean;
returnType?: TypeDef | null;
selector: string;
(...args: CodecArg[]): Uint8Array;
selector: InkSelector;
}
export type AbiConstructor = AbiMessage;
+38 -2
View File
@@ -1,10 +1,46 @@
// Copyright 2017-2020 @polkadot/rpc-core authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { Codec, Registry, TypeDef } from '@polkadot/types/types';
import { SubmittableResult } from '@polkadot/api';
import { EventRecord } from '@polkadot/types/interfaces';
import { Codec, CodecArg, Registry, TypeDef } from '@polkadot/types/types';
import { AbiConstructor, AbiMessage } from './types';
import { Raw, createTypeUnsafe } from '@polkadot/types';
import { Raw, createClass, createTypeUnsafe, encodeTypeDef } from '@polkadot/types';
import { assert, compactAddLength } from '@polkadot/util';
export function formatData (registry: Registry, data: Raw, { type }: TypeDef): Codec {
return createTypeUnsafe(registry, type, [data], true);
}
export function applyOnEvent <T> (result: SubmittableResult, type: 'CodeStored' | 'Instantiated', fn: (record: EventRecord) => T): T | undefined {
if (result.isInBlock || result.isFinalized) {
const record = result.findRecord('contract', type);
if (record) {
return fn(record);
}
}
return undefined;
}
export function encodeMessage (registry: Registry, message: AbiMessage | AbiConstructor, params: CodecArg[]): Uint8Array {
assert(message, 'Attempted to call an invalid contract message');
assert(params.length === message.args.length, `Expected ${message.args.length} arguments to contract message '${message.identifier}', found ${params.length}`);
const Clazz = createClass(registry, JSON.stringify(
message.args.reduce((r: Record<string, any>, { name, type }): Record<string, any> => {
r[name] = type.displayName || encodeTypeDef(type);
return r;
}, { __selector: 'InkSelector' })
));
return compactAddLength(new Clazz(registry, message.args.reduce((r: Record<string, CodecArg>, { name }, index): Record<string, CodecArg> => {
r[name] = params[index];
return r;
}, { __selector: message.selector })).toU8a());
}