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:
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user