From d3b13bceb0cf5c784d89db2320a33999846d1fbc Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 16 Nov 2020 07:35:02 +0100 Subject: [PATCH] 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 --- packages/api-contract/src/Abi.ts | 14 ++- packages/api-contract/src/base/Blueprint.ts | 72 ++++++++--- packages/api-contract/src/base/Code.spec.ts | 2 +- packages/api-contract/src/base/Code.ts | 3 + packages/api-contract/src/base/Contract.ts | 70 +++++++---- packages/api-contract/src/base/types.ts | 36 ++++-- .../api-contract/src/checkTypes.manual.ts | 51 ++++++++ packages/api-contract/src/types.ts | 14 ++- packages/api-contract/src/util.ts | 17 ++- packages/api/src/checkTypes.manual.ts | 44 +++---- packages/types/src/create/types.ts | 118 ------------------ packages/types/src/types/codec.ts | 2 +- 12 files changed, 248 insertions(+), 195 deletions(-) create mode 100644 packages/api-contract/src/checkTypes.manual.ts diff --git a/packages/api-contract/src/Abi.ts b/packages/api-contract/src/Abi.ts index a3a5ecc60a..8172d4b7af 100644 --- a/packages/api-contract/src/Abi.ts +++ b/packages/api-contract/src/Abi.ts @@ -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 (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 ) ); } diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts index 164b526bd9..dab58f6ccb 100644 --- a/packages/api-contract/src/base/Blueprint.ts +++ b/packages/api-contract/src/base/Blueprint.ts @@ -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 (fn: (options: BlueprintOptions, params: CodecArg[]) => SubmittableExtrinsic>): BlueprintDeploy { + return (options: BigInt | string | number | BN | BlueprintOptions, ...params: CodecArg[]): SubmittableExtrinsic> => + isOptions(options) + ? fn(options, params) + : fn(...extractOptions(options, params)); +} + +function createWithId (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: CodecArg[]) => T): ContractGeneric { + 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 extends SubmittableResult { public readonly contract?: Contract; @@ -29,21 +55,30 @@ export class BlueprintSubmittableResult extends Submit } export class Blueprint extends Base { + /** + * @description The on-chain code hash for this blueprint + */ public readonly codeHash: Hash; + /** + * @deprecated + * @description Deprecated. Use `.tx.`. Creates a contract in a non-deterministic way. + */ + public readonly createContract: ContractGeneric>>; + readonly #tx: MapConstructorExec = {}; constructor (api: ApiBase, abi: AnyJson | Abi, codeHash: string | Hash | Uint8Array, decorateMethod: DecorateMethod) { 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 extends Base { return this.#tx; } - #deploy = (constructorOrId: AbiConstructor | string| number, endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, params: CodecArg[]): SubmittableExtrinsic> => { - 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(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> => { + 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> { - return this.#deploy(constructorOrId, endowment, gasLimit, params); + return tx.withResultTransform((result: ISubmittableResult) => + new BlueprintSubmittableResult(result, applyOnEvent(result, 'Instantiated', ([record]: EventRecord[]) => + new Contract(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod) + )) + ); } } diff --git a/packages/api-contract/src/base/Code.spec.ts b/packages/api-contract/src/base/Code.spec.ts index b341613107..043dcaf09d 100644 --- a/packages/api-contract/src/base/Code.spec.ts +++ b/packages/api-contract/src/base/Code.spec.ts @@ -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')); diff --git a/packages/api-contract/src/base/Code.ts b/packages/api-contract/src/base/Code.ts index 0ad6953b6b..fd4d8e50b6 100644 --- a/packages/api-contract/src/base/Code.ts +++ b/packages/api-contract/src/base/Code.ts @@ -38,6 +38,9 @@ export class Code extends Base { assert(isWasm(this.code), 'No WASM code provided'); } + /** + * @description Deploy the code bundle, creating a Blueprint. + */ public createBlueprint (): SubmittableExtrinsic> { return this.api.tx.contracts .putCode(compactAddLength(this.code)) diff --git a/packages/api-contract/src/base/Contract.ts b/packages/api-contract/src/base/Contract.ts index 582c43e881..3064f05293 100644 --- a/packages/api-contract/src/base/Contract.ts +++ b/packages/api-contract/src/base/Contract.ts @@ -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 (fn: (origin: string | AccountId | Uint8Array, options: ContractOptions, params: CodecArg[]) => ContractCallResult): ContractQuery { + return (origin: string | AccountId | Uint8Array, options: BigInt | string | number | BN | ContractOptions, ...params: CodecArg[]): ContractCallResult => + isOptions(options) + ? fn(origin, options, params) + : fn(origin, ...extractOptions(options, params)); +} + +function createTx (fn: (options: ContractOptions, params: CodecArg[]) => SubmittableExtrinsic): ContractTx { + return (options: BigInt | string | number | BN | ContractOptions, ...params: CodecArg[]): SubmittableExtrinsic => + isOptions(options) + ? fn(options, params) + : fn(...extractOptions(options, params)); +} + +function createWithId (fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: CodecArg[]) => T): ContractGeneric { + 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): Cont } export class Contract extends Base { + /** + * @description The on-chain address for this contract + */ public readonly address: AccountId; - readonly #tx: MapMessageExec = {}; + /** + * @deprecated + * @description Deprecated. Use `.tx.` to send a transaction. + */ + public readonly exec: ContractGeneric>; - readonly #query: MapMessageRead = {}; + /** + * @deprecated + * @description Deprecated. Use `.tx.` to send a transaction. + */ + public readonly read: ContractGeneric>; + + readonly #query: MapMessageQuery = {}; + + readonly #tx: MapMessageTx = {}; constructor (api: ApiBase, abi: AnyJson | Abi, address: string | AccountId, decorateMethod: DecorateMethod) { 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 extends Base { return isFunction(this.api.rx.rpc.contracts?.call); } - public get query (): MapMessageRead { + public get query (): MapMessageQuery { assert(this.hasRpcContractsCall, ERROR_NO_CALL); return this.#query; } - public get tx (): MapMessageExec { + public get tx (): MapMessageTx { return this.#tx; } @@ -112,7 +148,7 @@ export class Contract extends Base { : gasLimit; } - #exec = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): SubmittableExtrinsic => { + #exec = (messageOrId: AbiMessage | string | number, { gasLimit = 0, value = 0 }: ContractOptions, params: CodecArg[]): SubmittableExtrinsic => { 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 extends Base { ); } - #read = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): ContractRead => { + #read = (messageOrId: AbiMessage | string | number, { gasLimit = 0, value = 0 }: ContractOptions, params: CodecArg[]): ContractCallSend => { assert(this.hasRpcContractsCall, ERROR_NO_CALL); const message = this.abi.findMessage(messageOrId); @@ -161,12 +197,4 @@ export class Contract extends Base { ) }; } - - public exec (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic { - 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 { - return this.#read(messageOrId, value, gasLimit, params); - } } diff --git a/packages/api-contract/src/base/types.ts b/packages/api-contract/src/base/types.ts index 6fa30eba77..53234255ae 100644 --- a/packages/api-contract/src/base/types.ts +++ b/packages/api-contract/src/base/types.ts @@ -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 'rxjs' +export interface BlueprintDeploy { + (options: BlueprintOptions, ...params: CodecArg[]): SubmittableExtrinsic; + (value: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]): SubmittableExtrinsic; +} + +export interface ContractQuery { + (origin: AccountId | string | Uint8Array, options: ContractOptions, ...params: CodecArg[]): ContractCallResult; + (origin: AccountId | string | Uint8Array, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): ContractCallResult; +} + +export interface ContractTx { + (options: ContractOptions, ...params: CodecArg[]): SubmittableExtrinsic; + (value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]): SubmittableExtrinsic; +} + +export interface ContractGeneric { + (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 'rxjs' ? Observable> : Promise>>; -export interface ContractRead { +export interface ContractCallSend { send (account: string | AccountId | Uint8Array): ContractCallResult; } export interface MapConstructorExec { - [message: string]: (endowment: BigInt | string | number | BN, gasLimit: BigInt | string | number | BN, ...params: CodecArg[]) => SubmittableExtrinsic; + [message: string]: BlueprintDeploy; } -export interface MapMessageExec { - [message: string]: (value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) => SubmittableExtrinsic; +export interface MapMessageTx { + [message: string]: ContractTx; } -export interface MapMessageRead { - [message: string]: (origin: AccountId | string | Uint8Array, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, ...params: CodecArg[]) => ContractCallResult; +export interface MapMessageQuery { + [message: string]: ContractQuery; } diff --git a/packages/api-contract/src/checkTypes.manual.ts b/packages/api-contract/src/checkTypes.manual.ts new file mode 100644 index 0000000000..06671f4d9c --- /dev/null +++ b/packages/api-contract/src/checkTypes.manual.ts @@ -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 { + 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 { + 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 { + 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); diff --git a/packages/api-contract/src/types.ts b/packages/api-contract/src/types.ts index bc206e59d8..76ad2b7157 100644 --- a/packages/api-contract/src/types.ts +++ b/packages/api-contract/src/types.ts @@ -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; +} diff --git a/packages/api-contract/src/util.ts b/packages/api-contract/src/util.ts index 2d9a9c0783..3629773809 100644 --- a/packages/api-contract/src/util.ts +++ b/packages/api-contract/src/util.ts @@ -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 (result: SubmittableResult, type: ContractEvent return undefined; } + +export function isOptions (options: BigInt | string | number | BN | T): options is T { + return !(isBn(options) || isBigInt(options) || isNumber(options) || isString(options)); +} + +export function extractOptions (value: BigInt | string | number | BN, params: CodecArg[]): [T, CodecArg[]] { + const gasLimit = params.shift() as BN; + + return [{ gasLimit, value } as T, params]; +} diff --git a/packages/api/src/checkTypes.manual.ts b/packages/api/src/checkTypes.manual.ts index 3acb321764..fcc804055a 100644 --- a/packages/api/src/checkTypes.manual.ts +++ b/packages/api/src/checkTypes.manual.ts @@ -34,7 +34,7 @@ async function derive (api: ApiPromise): Promise { console.log('fees', fees); } -async function query (api: ApiPromise, keyring: TestKeyringMap): Promise { +async function query (api: ApiPromise, pairs: TestKeyringMap): Promise { const intentions = await api.query.staking.bonded(); console.log('intentions:', intentions); @@ -47,7 +47,7 @@ async function query (api: ApiPromise, keyring: TestKeyringMap): Promise { 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 `` (this is not available on recent chains) - const multi = await api.query.balances.freeBalance.multi([keyring.alice.address, keyring.bob.address]); + const multi = await api.query.balances.freeBalance.multi([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 { // 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 { +async function queryExtra (api: ApiPromise, pairs: TestKeyringMap): Promise { // 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(['0x1234'], keyring.bob.address); + await api.query.balances.freeBalance.range(['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 { +async function tx (api: ApiPromise, pairs: TestKeyringMap): Promise { // 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(keyring.alice.address); + const nonce = await api.query.system.accountNonce(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 { 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 { 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) ]); } diff --git a/packages/types/src/create/types.ts b/packages/types/src/create/types.ts index 2a85b73106..e593dc335e 100644 --- a/packages/types/src/create/types.ts +++ b/packages/types/src/create/types.ts @@ -30,132 +30,14 @@ export enum TypeDefInfo { Null } -export interface TypeDefExtEnumDiscriminant { - discriminant: number; -} - -export interface TypeDefExtStructAlias { - [key: string]: string; -} - export interface TypeDef { alias?: Map; 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; - -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[]; - }; -} diff --git a/packages/types/src/types/codec.ts b/packages/types/src/types/codec.ts index 5393c2b65f..d058b90444 100644 --- a/packages/types/src/types/codec.ts +++ b/packages/types/src/types/codec.ts @@ -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