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:
Jaco Greeff
2020-11-16 07:35:02 +01:00
committed by GitHub
parent 0fc5e1d387
commit d3b13bceb0
12 changed files with 248 additions and 195 deletions
+9 -5
View File
@@ -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
)
);
}
+55 -17
View File
@@ -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)
))
);
}
}
+1 -1
View File
@@ -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'));
+3
View File
@@ -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))
+49 -21
View File
@@ -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);
}
}
+28 -8
View File
@@ -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);
+13 -1
View File
@@ -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;
}
+16 -1
View File
@@ -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];
}
+22 -22
View File
@@ -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)
]);
}
-118
View File
@@ -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[];
};
}
+1 -1
View File
@@ -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