Allow salt in contract deploy (#2849)
* Allow salt in contract deploy * Mark deprecated * Shorten * Cleanups * Additional cleanups * Cleanups * Explicit Bytes to decodeEvent * Adjust method creation * Allow null salt
This commit is contained in:
@@ -5,10 +5,13 @@ import { AnyJson, Codec, CodecArg } from '@polkadot/types/types';
|
||||
import { ChainProperties, ContractConstructorSpec, ContractEventSpec, ContractMessageSpec, ContractMessageParamSpec, ContractProject } from '@polkadot/types/interfaces';
|
||||
import { AbiConstructor, AbiEvent, AbiMessage, AbiParam, DecodedEvent, DecodedMessage } from './types';
|
||||
|
||||
import { Bytes } from '@polkadot/types';
|
||||
import { assert, assertReturn, compactAddLength, compactStripLength, isNumber, isObject, isString, logger, stringCamelCase, u8aConcat, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { MetaRegistry } from './MetaRegistry';
|
||||
|
||||
const EMPTY_U8A = new Uint8Array();
|
||||
|
||||
const l = logger('Abi');
|
||||
|
||||
function findMessage <T extends AbiMessage> (list: T[], messageOrId: T | string | number): T {
|
||||
@@ -74,7 +77,7 @@ export class Abi {
|
||||
/**
|
||||
* Warning: Unstable API, bound to change
|
||||
*/
|
||||
public decodeEvent (data: Uint8Array): DecodedEvent {
|
||||
public decodeEvent (data: Bytes | Uint8Array): DecodedEvent {
|
||||
const index = data[0];
|
||||
const event = this.#events[index];
|
||||
|
||||
@@ -151,8 +154,8 @@ export class Abi {
|
||||
identifier: spec.name.toString(),
|
||||
index,
|
||||
selector: spec.selector,
|
||||
toU8a: (params: CodecArg[]) =>
|
||||
this.#encodeArgs(spec, args, params)
|
||||
toU8a: (params: CodecArg[], additional?: Uint8Array) =>
|
||||
this.#encodeArgs(spec, args, params, additional)
|
||||
};
|
||||
|
||||
return message;
|
||||
@@ -182,13 +185,14 @@ export class Abi {
|
||||
return message.fromU8a(trimmed.subarray(4));
|
||||
}
|
||||
|
||||
#encodeArgs = ({ name, selector }: ContractMessageSpec | ContractConstructorSpec, args: AbiParam[], data: CodecArg[]): Uint8Array => {
|
||||
#encodeArgs = ({ name, selector }: ContractMessageSpec | ContractConstructorSpec, args: AbiParam[], data: CodecArg[], additional = EMPTY_U8A): Uint8Array => {
|
||||
assert(data.length === args.length, `Expected ${args.length} arguments to contract message '${name.toString()}', found ${data.length}`);
|
||||
|
||||
return compactAddLength(
|
||||
u8aConcat(
|
||||
this.registry.createType('ContractSelector', selector).toU8a(),
|
||||
...args.map(({ type }, index) => this.registry.createType(type.type as 'Text', data[index]).toU8a())
|
||||
...args.map(({ type }, index) => this.registry.createType(type.type as 'Text', data[index]).toU8a()),
|
||||
additional
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,19 +5,45 @@ import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
|
||||
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 { AbiConstructor, BlueprintOptions } from '../types';
|
||||
import { BlueprintDeploy, ContractGeneric, 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 { Bytes } from '@polkadot/types';
|
||||
import { isUndefined, stringCamelCase, compactAddLength, u8aToU8a } from '@polkadot/util';
|
||||
import { randomAsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
import { Abi } from '../Abi';
|
||||
import { applyOnEvent } from '../util';
|
||||
import { applyOnEvent, extractOptions, isOptions } from '../util';
|
||||
import { Base } from './Base';
|
||||
import { Contract } from './Contract';
|
||||
|
||||
const EMPTY_SALT = new Uint8Array();
|
||||
|
||||
function createTx <ApiType extends ApiTypes> (fn: (options: BlueprintOptions, params: CodecArg[]) => SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>>): BlueprintDeploy<ApiType> {
|
||||
return (options: BigInt | string | number | BN | BlueprintOptions, ...params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> =>
|
||||
isOptions(options)
|
||||
? fn(options, params)
|
||||
: fn(...extractOptions(options, params));
|
||||
}
|
||||
|
||||
function createWithId <T> (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: CodecArg[]) => T): ContractGeneric<BlueprintOptions, T> {
|
||||
return (constructorOrId: AbiConstructor | string | number, options: BigInt | string | number | BN | BlueprintOptions, ...params: CodecArg[]): T =>
|
||||
isOptions(options)
|
||||
? fn(constructorOrId, options, params)
|
||||
: fn(constructorOrId, ...extractOptions(options, params));
|
||||
}
|
||||
|
||||
function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()) {
|
||||
return salt instanceof Bytes
|
||||
? salt
|
||||
: salt && salt.length
|
||||
? compactAddLength(u8aToU8a(salt))
|
||||
: EMPTY_SALT;
|
||||
}
|
||||
|
||||
export class BlueprintSubmittableResult<ApiType extends ApiTypes> extends SubmittableResult {
|
||||
public readonly contract?: Contract<ApiType>;
|
||||
|
||||
@@ -29,21 +55,30 @@ export class BlueprintSubmittableResult<ApiType extends ApiTypes> extends Submit
|
||||
}
|
||||
|
||||
export class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
/**
|
||||
* @description The on-chain code hash for this blueprint
|
||||
*/
|
||||
public readonly codeHash: Hash;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @description Deprecated. Use `.tx.<constructorName>`. Creates a contract in a non-deterministic way.
|
||||
*/
|
||||
public readonly createContract: ContractGeneric<BlueprintOptions, SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>>>;
|
||||
|
||||
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.createContract = createWithId(this.#deploy);
|
||||
|
||||
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);
|
||||
this.#tx[messageName] = createTx((o, p) => this.#deploy(c, o, p));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -52,17 +87,20 @@ export class Blueprint<ApiType extends ApiTypes> extends Base<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, this.abi.findConstructor(constructorOrId).toU8a(params))
|
||||
.withResultTransform((result: ISubmittableResult) =>
|
||||
new BlueprintSubmittableResult(result, applyOnEvent(result, 'Instantiated', ([record]: EventRecord[]) =>
|
||||
new Contract<ApiType>(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod)
|
||||
))
|
||||
);
|
||||
}
|
||||
#deploy = (constructorOrId: AbiConstructor | string| number, { gasLimit = 0, salt, value = 0 }: BlueprintOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> => {
|
||||
const encodedSalt = encodeSalt(salt);
|
||||
const withSalt = this.api.tx.contracts.instantiate.meta.args.length === 5;
|
||||
const encoded = this.abi.findConstructor(constructorOrId).toU8a(params, withSalt ? EMPTY_SALT : encodedSalt);
|
||||
const tx = withSalt
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore new style with salt included
|
||||
? this.api.tx.contracts.instantiate(value, gasLimit, this.codeHash, encoded, encodedSalt)
|
||||
: this.api.tx.contracts.instantiate(value, gasLimit, this.codeHash, encoded);
|
||||
|
||||
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);
|
||||
return tx.withResultTransform((result: ISubmittableResult) =>
|
||||
new BlueprintSubmittableResult(result, applyOnEvent(result, 'Instantiated', ([record]: EventRecord[]) =>
|
||||
new Contract<ApiType>(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { decorateMethod } from '@polkadot/api/promise';
|
||||
|
||||
import contractFlipper from '../../test/contracts/ink/flipper.contract.json';
|
||||
import abiFlipper from '../../test/contracts/ink/flipper.json';
|
||||
import { Code } from './Code';
|
||||
import { mockApi } from './mock';
|
||||
import { Code } from './Code';
|
||||
|
||||
const wasmFlipper = fs.readFileSync(path.join(__dirname, '../../test/contracts/ink/flipper.wasm'));
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ export class Code<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
assert(isWasm(this.code), 'No WASM code provided');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Deploy the code bundle, creating a Blueprint.
|
||||
*/
|
||||
public createBlueprint (): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> {
|
||||
return this.api.tx.contracts
|
||||
.putCode(compactAddLength(this.code))
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
|
||||
import { AccountId, ContractExecResult, EventRecord } from '@polkadot/types/interfaces';
|
||||
import { AnyJson, CodecArg, ISubmittableResult, Registry } from '@polkadot/types/types';
|
||||
import { AbiMessage, ContractCallOutcome, DecodedEvent } from '../types';
|
||||
import { ContractRead, MapMessageExec, MapMessageRead } from './types';
|
||||
import { AbiMessage, ContractCallOutcome, ContractOptions, DecodedEvent } from '../types';
|
||||
import { ContractCallResult, ContractCallSend, ContractGeneric, ContractQuery, ContractTx, MapMessageQuery, MapMessageTx } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { map } from 'rxjs/operators';
|
||||
@@ -16,7 +16,7 @@ import { Bytes } from '@polkadot/types';
|
||||
import { assert, bnToBn, isFunction, isUndefined, logger, stringCamelCase } from '@polkadot/util';
|
||||
|
||||
import { Abi } from '../Abi';
|
||||
import { applyOnEvent, formatData } from '../util';
|
||||
import { applyOnEvent, extractOptions, formatData, isOptions } from '../util';
|
||||
import { Base } from './Base';
|
||||
|
||||
// As per Rust, 5 * GAS_PER_SEC
|
||||
@@ -24,6 +24,27 @@ const MAX_CALL_GAS = new BN(5_000_000_000_000).subn(1);
|
||||
const ERROR_NO_CALL = 'Your node does not expose the contracts.call RPC. This is most probably due to a runtime configuration.';
|
||||
const l = logger('Contract');
|
||||
|
||||
function createQuery <ApiType extends ApiTypes> (fn: (origin: string | AccountId | Uint8Array, options: ContractOptions, params: CodecArg[]) => ContractCallResult<ApiType, ContractCallOutcome>): ContractQuery<ApiType> {
|
||||
return (origin: string | AccountId | Uint8Array, options: BigInt | string | number | BN | ContractOptions, ...params: CodecArg[]): ContractCallResult<ApiType, ContractCallOutcome> =>
|
||||
isOptions(options)
|
||||
? fn(origin, options, params)
|
||||
: fn(origin, ...extractOptions(options, params));
|
||||
}
|
||||
|
||||
function createTx <ApiType extends ApiTypes> (fn: (options: ContractOptions, params: CodecArg[]) => SubmittableExtrinsic<ApiType>): ContractTx<ApiType> {
|
||||
return (options: BigInt | string | number | BN | ContractOptions, ...params: CodecArg[]): SubmittableExtrinsic<ApiType> =>
|
||||
isOptions(options)
|
||||
? fn(options, params)
|
||||
: fn(...extractOptions(options, params));
|
||||
}
|
||||
|
||||
function createWithId <T> (fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: CodecArg[]) => T): ContractGeneric<ContractOptions, T> {
|
||||
return (messageOrId: AbiMessage | string | number, options: BigInt | string | number | BN | ContractOptions, ...params: CodecArg[]): T =>
|
||||
isOptions(options)
|
||||
? fn(messageOrId, options, params)
|
||||
: fn(messageOrId, ...extractOptions(options, params));
|
||||
}
|
||||
|
||||
export class ContractSubmittableResult extends SubmittableResult {
|
||||
public readonly contractEvents?: DecodedEvent[];
|
||||
|
||||
@@ -62,28 +83,43 @@ function mapExecResult (registry: Registry, json: Record<string, AnyJson>): Cont
|
||||
}
|
||||
|
||||
export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
/**
|
||||
* @description The on-chain address for this contract
|
||||
*/
|
||||
public readonly address: AccountId;
|
||||
|
||||
readonly #tx: MapMessageExec<ApiType> = {};
|
||||
/**
|
||||
* @deprecated
|
||||
* @description Deprecated. Use `.tx.<messageName>` to send a transaction.
|
||||
*/
|
||||
public readonly exec: ContractGeneric<ContractOptions, SubmittableExtrinsic<ApiType>>;
|
||||
|
||||
readonly #query: MapMessageRead<ApiType> = {};
|
||||
/**
|
||||
* @deprecated
|
||||
* @description Deprecated. Use `.tx.<messageName>` to send a transaction.
|
||||
*/
|
||||
public readonly read: ContractGeneric<ContractOptions, ContractCallSend<ApiType>>;
|
||||
|
||||
readonly #query: MapMessageQuery<ApiType> = {};
|
||||
|
||||
readonly #tx: MapMessageTx<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.exec = createWithId(this.#exec);
|
||||
this.read = createWithId(this.#read);
|
||||
|
||||
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);
|
||||
this.#tx[messageName] = createTx((o, p) => this.#exec(m, o, p));
|
||||
}
|
||||
|
||||
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);
|
||||
this.#query[messageName] = createQuery((f, o, p) => this.#read(m, o, p).send(f));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -92,13 +128,13 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
return isFunction(this.api.rx.rpc.contracts?.call);
|
||||
}
|
||||
|
||||
public get query (): MapMessageRead<ApiType> {
|
||||
public get query (): MapMessageQuery<ApiType> {
|
||||
assert(this.hasRpcContractsCall, ERROR_NO_CALL);
|
||||
|
||||
return this.#query;
|
||||
}
|
||||
|
||||
public get tx (): MapMessageExec<ApiType> {
|
||||
public get tx (): MapMessageTx<ApiType> {
|
||||
return this.#tx;
|
||||
}
|
||||
|
||||
@@ -112,7 +148,7 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
: gasLimit;
|
||||
}
|
||||
|
||||
#exec = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): SubmittableExtrinsic<ApiType> => {
|
||||
#exec = (messageOrId: AbiMessage | string | number, { gasLimit = 0, value = 0 }: ContractOptions, params: CodecArg[]): SubmittableExtrinsic<ApiType> => {
|
||||
return this.api.tx.contracts
|
||||
.call(this.address, value, this.#getGas(gasLimit), this.abi.findMessage(messageOrId).toU8a(params))
|
||||
.withResultTransform((result: ISubmittableResult) =>
|
||||
@@ -132,7 +168,7 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
|
||||
);
|
||||
}
|
||||
|
||||
#read = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): ContractRead<ApiType> => {
|
||||
#read = (messageOrId: AbiMessage | string | number, { gasLimit = 0, value = 0 }: ContractOptions, params: CodecArg[]): ContractCallSend<ApiType> => {
|
||||
assert(this.hasRpcContractsCall, ERROR_NO_CALL);
|
||||
|
||||
const message = this.abi.findMessage(messageOrId);
|
||||
@@ -161,12 +197,4 @@ export 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,47 @@ 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 { AbiMessage, BlueprintOptions, ContractCallOutcome, ContractOptions } from '../types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
type ContractCallResult<ApiType extends ApiTypes, T> = ApiType extends 'rxjs'
|
||||
export interface BlueprintDeploy<ApiType extends ApiTypes> {
|
||||
(options: BlueprintOptions, ...params: CodecArg[]): SubmittableExtrinsic<ApiType>;
|
||||
(value: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): SubmittableExtrinsic<ApiType>;
|
||||
}
|
||||
|
||||
export interface ContractQuery<ApiType extends ApiTypes> {
|
||||
(origin: AccountId | string | Uint8Array, options: ContractOptions, ...params: CodecArg[]): ContractCallResult<ApiType, ContractCallOutcome>;
|
||||
(origin: AccountId | string | Uint8Array, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): ContractCallResult<ApiType, ContractCallOutcome>;
|
||||
}
|
||||
|
||||
export interface ContractTx<ApiType extends ApiTypes> {
|
||||
(options: ContractOptions, ...params: CodecArg[]): SubmittableExtrinsic<ApiType>;
|
||||
(value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic<ApiType>;
|
||||
}
|
||||
|
||||
export interface ContractGeneric<O, T> {
|
||||
(messageOrId: AbiMessage | string | number, options: O, ...params: CodecArg[]): T;
|
||||
(messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): T;
|
||||
}
|
||||
|
||||
export type ContractCallResult<ApiType extends ApiTypes, T> = ApiType extends 'rxjs'
|
||||
? Observable<Observable<T>>
|
||||
: Promise<ObsInnerType<Observable<T>>>;
|
||||
|
||||
export interface ContractRead<ApiType extends ApiTypes> {
|
||||
export interface ContractCallSend<ApiType extends ApiTypes> {
|
||||
send (account: string | AccountId | Uint8Array): ContractCallResult<ApiType, ContractCallOutcome>;
|
||||
}
|
||||
|
||||
export interface MapConstructorExec<ApiType extends ApiTypes> {
|
||||
[message: string]: (endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]) => SubmittableExtrinsic<ApiType>;
|
||||
[message: string]: BlueprintDeploy<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 MapMessageTx<ApiType extends ApiTypes> {
|
||||
[message: string]: ContractTx<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[]) => ContractCallResult<ApiType, ContractCallOutcome>;
|
||||
export interface MapMessageQuery<ApiType extends ApiTypes> {
|
||||
[message: string]: ContractQuery<ApiType>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2020 @polkadot/api-contract authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Simple non-runnable checks to test type definitions in the editor itself
|
||||
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import { TestKeyringMap, createTestPairs } from '@polkadot/keyring/testingPairs';
|
||||
|
||||
import abiIncrementer from '../test/contracts/ink/incrementer.json';
|
||||
import { BlueprintPromise, ContractPromise } from '.';
|
||||
|
||||
async function checkBlueprint (api: ApiPromise, pairs: TestKeyringMap): Promise<void> {
|
||||
const blueprint = new BlueprintPromise(api, abiIncrementer, '0x1234');
|
||||
|
||||
// new style
|
||||
await blueprint.tx.new({ gasLimit: 456, salt: '0x1234', value: 123 }, 42).signAndSend(pairs.bob);
|
||||
await blueprint.tx.new({ gasLimit: 456, value: 123 }, 42).signAndSend(pairs.bob);
|
||||
|
||||
// old style
|
||||
await blueprint.tx.new(123, 456, 42).signAndSend(pairs.bob);
|
||||
}
|
||||
|
||||
async function checkContract (api: ApiPromise, pairs: TestKeyringMap): Promise<void> {
|
||||
const contract = new ContractPromise(api, abiIncrementer, '0x1234');
|
||||
|
||||
// queries
|
||||
await contract.query.get(pairs.alice.address, {});
|
||||
await contract.query.get(pairs.alice.address, 0, 0);
|
||||
|
||||
// execute
|
||||
await contract.tx.inc({ gasLimit: 1234 }, 123).signAndSend(pairs.eve);
|
||||
await contract.tx.inc(123, 456, 69).signAndSend(pairs.eve);
|
||||
|
||||
// deprecated
|
||||
await contract.read('get', {}).send(pairs.eve.address);
|
||||
await contract.exec('inc', {}, 123).signAndSend(pairs.dave);
|
||||
}
|
||||
|
||||
async function main (): Promise<void> {
|
||||
const api = await ApiPromise.create();
|
||||
const pairs = createTestPairs();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Promise.all([
|
||||
checkBlueprint(api, pairs),
|
||||
checkContract(api, pairs)
|
||||
]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
main().catch(console.error);
|
||||
@@ -5,6 +5,7 @@ import { ApiTypes } from '@polkadot/api/types';
|
||||
import { ContractExecResultResult, ContractSelector } from '@polkadot/types/interfaces';
|
||||
import { Codec, CodecArg, TypeDef } from '@polkadot/types/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { ApiBase } from '@polkadot/api/base';
|
||||
import { Text, u64 } from '@polkadot/types';
|
||||
|
||||
@@ -42,7 +43,7 @@ export interface AbiMessage {
|
||||
isPayable?: boolean;
|
||||
returnType?: TypeDef | null;
|
||||
selector: ContractSelector;
|
||||
toU8a: (params: CodecArg[]) => Uint8Array;
|
||||
toU8a: (params: CodecArg[], additional?: Uint8Array) => Uint8Array;
|
||||
}
|
||||
|
||||
export type AbiConstructor = AbiMessage;
|
||||
@@ -68,3 +69,14 @@ export interface DecodedMessage {
|
||||
args: Codec[];
|
||||
message: AbiMessage;
|
||||
}
|
||||
|
||||
export interface BlueprintOptions {
|
||||
gasLimit?: BigInt | string | number | BN;
|
||||
salt?: Uint8Array | string | null;
|
||||
value?: BigInt | string | number | BN;
|
||||
}
|
||||
|
||||
export interface ContractOptions {
|
||||
gasLimit?: BigInt | BN | string | number;
|
||||
value?: BigInt | BN | string | number;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
|
||||
import { SubmittableResult } from '@polkadot/api';
|
||||
import { EventRecord } from '@polkadot/types/interfaces';
|
||||
import { Codec, Registry, TypeDef } from '@polkadot/types/types';
|
||||
import { Codec, CodecArg, Registry, TypeDef } from '@polkadot/types/types';
|
||||
import { BlueprintOptions, ContractOptions } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Raw } from '@polkadot/types';
|
||||
import { createTypeUnsafe } from '@polkadot/types/create';
|
||||
import { isBn, isBigInt, isNumber, isString } from '@polkadot/util';
|
||||
|
||||
type ContractEvents = 'CodeStored' | 'ContractExecution' | 'Instantiated';
|
||||
|
||||
type TOptions = BlueprintOptions | ContractOptions;
|
||||
|
||||
export function formatData (registry: Registry, data: Raw, { type }: TypeDef): Codec {
|
||||
return createTypeUnsafe(registry, type, [data], true);
|
||||
}
|
||||
@@ -25,3 +30,13 @@ export function applyOnEvent <T> (result: SubmittableResult, type: ContractEvent
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isOptions <T> (options: BigInt | string | number | BN | T): options is T {
|
||||
return !(isBn(options) || isBigInt(options) || isNumber(options) || isString(options));
|
||||
}
|
||||
|
||||
export function extractOptions <T extends TOptions> (value: BigInt | string | number | BN, params: CodecArg[]): [T, CodecArg[]] {
|
||||
const gasLimit = params.shift() as BN;
|
||||
|
||||
return [{ gasLimit, value } as T, params];
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ async function derive (api: ApiPromise): Promise<void> {
|
||||
console.log('fees', fees);
|
||||
}
|
||||
|
||||
async function query (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
async function query (api: ApiPromise, pairs: TestKeyringMap): Promise<void> {
|
||||
const intentions = await api.query.staking.bonded();
|
||||
|
||||
console.log('intentions:', intentions);
|
||||
@@ -47,7 +47,7 @@ async function query (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
const oldBal = await api.query.balances.totalIssuance.at('abcd');
|
||||
// It's hard to correctly type .multi. Expected: `Balance[]`, actual: Codec[].
|
||||
// In the meantime, we can case with `<Balance>` (this is not available on recent chains)
|
||||
const multi = await api.query.balances.freeBalance.multi<Balance>([keyring.alice.address, keyring.bob.address]);
|
||||
const multi = await api.query.balances.freeBalance.multi<Balance>([pairs.alice.address, pairs.bob.address]);
|
||||
|
||||
console.log('query types:', bar, bal, bal2, override, oldBal, multi);
|
||||
|
||||
@@ -63,15 +63,15 @@ async function query (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
|
||||
// check multi , Promise result
|
||||
const multiRes = await api.queryMulti([
|
||||
[api.query.system.account, keyring.eve.address],
|
||||
[api.query.system.account, pairs.eve.address],
|
||||
// older chains only
|
||||
[api.query.system.accountNonce, keyring.eve.address]
|
||||
[api.query.system.accountNonce, pairs.eve.address]
|
||||
]);
|
||||
|
||||
console.log(multiRes);
|
||||
}
|
||||
|
||||
async function queryExtra (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
async function queryExtra (api: ApiPromise, pairs: TestKeyringMap): Promise<void> {
|
||||
// events destructing
|
||||
await api.query.system.events((records): void => {
|
||||
records.forEach(({ event, phase }): void => {
|
||||
@@ -96,7 +96,7 @@ async function queryExtra (api: ApiPromise, keyring: TestKeyringMap): Promise<vo
|
||||
await api.query.staking.nominatorSlashInEra.entries(123); // should take a param
|
||||
|
||||
// check range
|
||||
await api.query.balances.freeBalance.range<Balance>(['0x1234'], keyring.bob.address);
|
||||
await api.query.balances.freeBalance.range<Balance>(['0x1234'], pairs.bob.address);
|
||||
|
||||
// check range types
|
||||
const entries = await api.query.system.events.range(['0x12345', '0x7890']);
|
||||
@@ -139,33 +139,33 @@ function types (api: ApiPromise): void {
|
||||
console.log(balance, gas, compact, gasUnsafe, overriddenUnsafe, api.createType('AccountData'));
|
||||
}
|
||||
|
||||
async function tx (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
async function tx (api: ApiPromise, pairs: TestKeyringMap): Promise<void> {
|
||||
// transfer, also allows for BigInt inputs here
|
||||
const transfer = api.tx.balances.transfer(keyring.bob.address, 123456789n);
|
||||
const transfer = api.tx.balances.transfer(pairs.bob.address, 123456789n);
|
||||
|
||||
console.log('transfer casted', transfer as IMethod, transfer as IExtrinsic);
|
||||
|
||||
// simple "return the hash" variant
|
||||
console.log('hash:', (await transfer.signAndSend(keyring.alice)).toHex());
|
||||
console.log('hash:', (await transfer.signAndSend(pairs.alice)).toHex());
|
||||
|
||||
// passing options, but waiting for hash
|
||||
const nonce = await api.query.system.accountNonce<Index>(keyring.alice.address);
|
||||
const nonce = await api.query.system.accountNonce<Index>(pairs.alice.address);
|
||||
|
||||
(await api.tx.balances
|
||||
.transfer(keyring.bob.address, 12345)
|
||||
.signAndSend(keyring.alice, { nonce })
|
||||
.transfer(pairs.bob.address, 12345)
|
||||
.signAndSend(pairs.alice, { nonce })
|
||||
).toHex();
|
||||
|
||||
// just with the callback
|
||||
await api.tx.balances
|
||||
.transfer(keyring.bob.address, 12345)
|
||||
.signAndSend(keyring.alice, ({ status }: SubmittableResult) => console.log(status.type));
|
||||
.transfer(pairs.bob.address, 12345)
|
||||
.signAndSend(pairs.alice, ({ status }: SubmittableResult) => console.log(status.type));
|
||||
|
||||
// with options and the callback
|
||||
const nonce2 = await api.query.system.accountNonce(keyring.alice.address);
|
||||
const nonce2 = await api.query.system.accountNonce(pairs.alice.address);
|
||||
const unsub2 = await api.tx.balances
|
||||
.transfer(keyring.bob.address, 12345)
|
||||
.signAndSend(keyring.alice, { nonce: nonce2 }, ({ status }: SubmittableResult): void => {
|
||||
.transfer(pairs.bob.address, 12345)
|
||||
.signAndSend(pairs.alice, { nonce: nonce2 }, ({ status }: SubmittableResult): void => {
|
||||
console.log('transfer status:', status.type);
|
||||
|
||||
unsub2();
|
||||
@@ -178,22 +178,22 @@ async function tx (api: ApiPromise, keyring: TestKeyringMap): Promise<void> {
|
||||
await second.signAndSend('123', (result) => console.log(result));
|
||||
|
||||
// it handles enum inputs correctly
|
||||
await api.tx.democracy.proxyVote(123, { Split: { nay: 456, yay: 123 } }).signAndSend(keyring.alice);
|
||||
await api.tx.democracy.proxyVote(123, { Split: { nay: 456, yay: 123 } }).signAndSend(pairs.alice);
|
||||
}
|
||||
|
||||
async function main (): Promise<void> {
|
||||
const api = await ApiPromise.create();
|
||||
const keyring = createTestPairs();
|
||||
const pairs = createTestPairs();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Promise.all([
|
||||
consts(api),
|
||||
derive(api),
|
||||
query(api, keyring),
|
||||
queryExtra(api, keyring),
|
||||
query(api, pairs),
|
||||
queryExtra(api, pairs),
|
||||
rpc(api),
|
||||
types(api),
|
||||
tx(api, keyring)
|
||||
tx(api, pairs)
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,132 +30,14 @@ export enum TypeDefInfo {
|
||||
Null
|
||||
}
|
||||
|
||||
export interface TypeDefExtEnumDiscriminant {
|
||||
discriminant: number;
|
||||
}
|
||||
|
||||
export interface TypeDefExtStructAlias {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface TypeDef {
|
||||
alias?: Map<string, string>;
|
||||
info: TypeDefInfo;
|
||||
index?: number;
|
||||
displayName?: string;
|
||||
ext?: TypeDefExtEnumDiscriminant; // add additional here as required
|
||||
length?: number;
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
sub?: TypeDef | TypeDef[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
export type TypeIndex = number;
|
||||
|
||||
export type StringIndex = number;
|
||||
|
||||
export enum MetaTypeInfo {
|
||||
BuiltinPlain,
|
||||
BuiltinTuple,
|
||||
BuiltinVec,
|
||||
BuiltinVecFixed,
|
||||
Enum,
|
||||
ClikeEnum,
|
||||
Struct,
|
||||
TupleStruct,
|
||||
Null,
|
||||
}
|
||||
|
||||
export type MetaTypeIdPrimitive = string;
|
||||
|
||||
export type MetaTypeIdTuple = TypeIndex[];
|
||||
|
||||
export interface MetaTypeIdCustom {
|
||||
'custom.name': StringIndex;
|
||||
'custom.namespace'?: StringIndex[];
|
||||
'custom.params'?: TypeIndex[];
|
||||
}
|
||||
|
||||
export interface MetaTypeIdVec {
|
||||
'slice.type': TypeIndex;
|
||||
}
|
||||
|
||||
export interface MetaTypeIdVecFixed {
|
||||
'array.len': number;
|
||||
'array.type': TypeIndex;
|
||||
}
|
||||
|
||||
export type MetaTypeId = MetaTypeIdPrimitive | MetaTypeIdTuple | MetaTypeIdVec | MetaTypeIdVecFixed | MetaTypeIdCustom;
|
||||
// MetaTypeIdPrimitive | MetaTypeIdTuple | MetaTypeIdArray | MetaTypeIdCustom;
|
||||
// export type MetaTypeId = Record<string, any>;
|
||||
|
||||
export interface MetaTypeDefClikeEnumVariant {
|
||||
name: StringIndex;
|
||||
discriminant: number;
|
||||
}
|
||||
|
||||
export interface MetaTypeDefClikeEnum {
|
||||
'clike_enum.variants': MetaTypeDefClikeEnumVariant[];
|
||||
}
|
||||
|
||||
export type MetaTypeDefBuiltIn = 'builtin';
|
||||
|
||||
export interface MetaTypeDefEnumVariantUnit {
|
||||
'unit_variant.name': StringIndex;
|
||||
}
|
||||
|
||||
export interface MetaTypeDefEnumVariantTupleStruct {
|
||||
'tuple_struct_variant.name': StringIndex;
|
||||
'tuple_struct_variant.types': TypeIndex[];
|
||||
}
|
||||
|
||||
export interface MetaTypeDefStructField {
|
||||
name: StringIndex;
|
||||
type: TypeIndex;
|
||||
}
|
||||
|
||||
export interface MetaTypeDefEnumVariantStruct {
|
||||
'struct_variant.name': StringIndex;
|
||||
'struct_variant.fields': MetaTypeDefStructField[];
|
||||
}
|
||||
|
||||
export type MetaTypeDefEnumVariant = MetaTypeDefEnumVariantUnit | MetaTypeDefEnumVariantTupleStruct | MetaTypeDefEnumVariantStruct;
|
||||
|
||||
export interface MetaTypeDefEnum {
|
||||
'enum.variants': MetaTypeDefEnumVariant[];
|
||||
}
|
||||
|
||||
export type MetaTypeDefUnionField = MetaTypeDefStructField;
|
||||
|
||||
export interface MetaTypeDefStruct {
|
||||
'struct.fields': MetaTypeDefStructField[];
|
||||
}
|
||||
|
||||
export interface MetaTypeDefTupleStruct {
|
||||
'tuple_struct.types': TypeIndex[];
|
||||
}
|
||||
|
||||
export interface MetaTypeDefUnion {
|
||||
'union.fields': MetaTypeDefUnionField[];
|
||||
}
|
||||
|
||||
export type MetaTypeDef = MetaTypeDefBuiltIn | MetaTypeDefClikeEnum | MetaTypeDefEnum | MetaTypeDefStruct | MetaTypeDefTupleStruct | MetaTypeDefUnion;
|
||||
|
||||
export interface MetaType {
|
||||
def: MetaTypeDef;
|
||||
id: MetaTypeId | null;
|
||||
}
|
||||
|
||||
export enum MetaRegistryItem {
|
||||
String,
|
||||
Type,
|
||||
TypeDef
|
||||
}
|
||||
|
||||
export interface MetaRegistryJson {
|
||||
registry: {
|
||||
strings: string[];
|
||||
types: MetaType[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export interface Codec {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export type CodecArg = Codec | BN | boolean | string | Uint8Array | boolean | number | string | undefined | CodecArgArray | { [index: string]: CodecArg };
|
||||
export type CodecArg = Codec | BigInt | BN | boolean | string | Uint8Array | boolean | number | string | undefined | CodecArgArray | { [index: string]: CodecArg };
|
||||
|
||||
// We cannot inline this into CodecArg, TS throws up when building docs
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
||||
Reference in New Issue
Block a user