From cc88ca06eb31e2184379b5522b59fba4a5b5033f Mon Sep 17 00:00:00 2001 From: Jaco Date: Fri, 21 Jan 2022 15:48:12 +0200 Subject: [PATCH 1/4] Expose contract namespaces under contract.ns.{query, tx}.* --- packages/api-contract/src/base/Blueprint.ts | 18 ++++++----- packages/api-contract/src/base/Code.ts | 18 ++++++----- packages/api-contract/src/base/Contract.ts | 30 ++++++++++++------- packages/api-contract/src/base/types.ts | 4 +++ packages/api-contract/src/base/util.ts | 33 +++++++++++++++++---- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts index e7ef7890a290..c2c2f8fbc813 100644 --- a/packages/api-contract/src/base/Blueprint.ts +++ b/packages/api-contract/src/base/Blueprint.ts @@ -6,17 +6,17 @@ import type { ApiTypes, DecorateMethod } from '@polkadot/api/types'; import type { AccountId, EventRecord, Hash } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; import type { AbiConstructor, BlueprintOptions } from '../types'; -import type { MapConstructorExec } from './types'; +import type { BlueprintDeploy, MapConstructorExec, Namespaced } from './types'; import { SubmittableResult } from '@polkadot/api'; import { ApiBase } from '@polkadot/api/base'; -import { BN_ZERO, isUndefined } from '@polkadot/util'; +import { BN_ZERO } from '@polkadot/util'; import { Abi } from '../Abi'; import { applyOnEvent } from '../util'; import { Base } from './Base'; import { Contract } from './Contract'; -import { createBluePrintTx, encodeSalt } from './util'; +import { encodeSalt, expandConstructors } from './util'; export interface BlueprintConstructor { new(api: ApiBase, abi: string | Record | Abi, codeHash: string | Hash | Uint8Array): Blueprint; @@ -38,6 +38,8 @@ export class Blueprint extends Base { */ public readonly codeHash: Hash; + readonly #ns: { tx: Namespaced> } = { tx: {} }; + readonly #tx: MapConstructorExec = {}; constructor (api: ApiBase, abi: string | Record | Abi, codeHash: string | Hash | Uint8Array, decorateMethod: DecorateMethod) { @@ -45,11 +47,11 @@ export class Blueprint extends Base { this.codeHash = this.registry.createType('Hash', codeHash); - this.abi.constructors.forEach((c): void => { - if (isUndefined(this.#tx[c.method])) { - this.#tx[c.method] = createBluePrintTx((o, p) => this.#deploy(c, o, p)); - } - }); + expandConstructors(this.abi.constructors, this.#ns.tx, this.#tx, this.#deploy); + } + + public get ns (): { tx: Namespaced> } { + return this.#ns; } public get tx (): MapConstructorExec { diff --git a/packages/api-contract/src/base/Code.ts b/packages/api-contract/src/base/Code.ts index 4f54ae7c8839..5f2934c80c59 100644 --- a/packages/api-contract/src/base/Code.ts +++ b/packages/api-contract/src/base/Code.ts @@ -7,18 +7,18 @@ import type { AccountId, EventRecord } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; import type { Codec } from '@polkadot/types-codec/types'; import type { AbiConstructor, BlueprintOptions } from '../types'; -import type { MapConstructorExec } from './types'; +import type { BlueprintDeploy, MapConstructorExec, Namespaced } from './types'; import { SubmittableResult } from '@polkadot/api'; import { ApiBase } from '@polkadot/api/base'; -import { assert, BN_ZERO, compactAddLength, isUndefined, isWasm, u8aToU8a } from '@polkadot/util'; +import { assert, BN_ZERO, compactAddLength, isWasm, u8aToU8a } from '@polkadot/util'; import { Abi } from '../Abi'; import { applyOnEvent } from '../util'; import { Base } from './Base'; import { Blueprint } from './Blueprint'; import { Contract } from './Contract'; -import { createBluePrintTx, encodeSalt } from './util'; +import { encodeSalt, expandConstructors } from './util'; export interface CodeConstructor { new(api: ApiBase, abi: string | Record | Abi, wasm: Uint8Array | string | Buffer | null | undefined): Code; @@ -39,6 +39,8 @@ export class CodeSubmittableResult extends Submittable export class Code extends Base { public readonly code: Uint8Array; + readonly #ns: { tx: Namespaced> } = { tx: {} }; + readonly #tx: MapConstructorExec = {}; constructor (api: ApiBase, abi: string | Record | Abi, wasm: Uint8Array | string | Buffer | null | undefined, decorateMethod: DecorateMethod) { @@ -50,11 +52,11 @@ export class Code extends Base { assert(isWasm(this.code), 'No WASM code provided'); - this.abi.constructors.forEach((c): void => { - if (isUndefined(this.#tx[c.method])) { - this.#tx[c.method] = createBluePrintTx((o, p) => this.#instantiate(c, o, p)); - } - }); + expandConstructors(this.abi.constructors, this.#ns.tx, this.#tx, this.#instantiate); + } + + public get ns (): { tx: Namespaced> } { + return this.#ns; } public get tx (): MapConstructorExec { diff --git a/packages/api-contract/src/base/Contract.ts b/packages/api-contract/src/base/Contract.ts index 17e5f5275de9..e487930ffe46 100644 --- a/packages/api-contract/src/base/Contract.ts +++ b/packages/api-contract/src/base/Contract.ts @@ -7,7 +7,7 @@ import type { Bytes } from '@polkadot/types'; import type { AccountId, ContractExecResult, EventRecord, Weight } from '@polkadot/types/interfaces'; import type { ISubmittableResult } from '@polkadot/types/types'; import type { AbiMessage, ContractCallOutcome, ContractOptions, DecodedEvent } from '../types'; -import type { ContractCallResult, ContractCallSend, ContractQuery, ContractTx, MapMessageQuery, MapMessageTx } from './types'; +import type { ContractCallResult, ContractCallSend, ContractQuery, ContractTx, MapMessageQuery, MapMessageTx, Namespaced } from './types'; import { map } from 'rxjs'; @@ -18,6 +18,12 @@ import { assert, BN, BN_HUNDRED, BN_ONE, BN_ZERO, bnToBn, isFunction, isUndefine import { Abi } from '../Abi'; import { applyOnEvent, extractOptions, isOptions } from '../util'; import { Base } from './Base'; +import { expandNs } from './util'; + +interface NsMessages { + readonly query: Namespaced>; + readonly tx: Namespaced>; +} export interface ContractConstructor { new(api: ApiBase, abi: string | Record | Abi, address: string | AccountId): Contract; @@ -35,19 +41,21 @@ function withMeta (meta: AbiMessage, creator: O return creator as T; } -function createQuery (meta: AbiMessage, fn: (origin: string | AccountId | Uint8Array, options: ContractOptions, params: unknown[]) => ContractCallResult): ContractQuery { +function createQuery (meta: AbiMessage, fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: unknown[]) => ContractCallSend): ContractQuery { return withMeta(meta, (origin: string | AccountId | Uint8Array, options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): ContractCallResult => - isOptions(options) - ? fn(origin, options, params) - : fn(origin, ...extractOptions(options, params)) + ( + isOptions(options) + ? fn(meta, options, params) + : fn(meta, ...extractOptions(options, params)) + ).send(origin) ); } -function createTx (meta: AbiMessage, fn: (options: ContractOptions, params: unknown[]) => SubmittableExtrinsic): ContractTx { +function createTx (meta: AbiMessage, fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: unknown[]) => SubmittableExtrinsic): ContractTx { return withMeta(meta, (options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): SubmittableExtrinsic => isOptions(options) - ? fn(options, params) - : fn(...extractOptions(options, params)) + ? fn(meta, options, params) + : fn(meta, ...extractOptions(options, params)) ); } @@ -67,6 +75,8 @@ export class Contract extends Base { */ public readonly address: AccountId; + readonly #ns: NsMessages = { query: {}, tx: {} }; + readonly #query: MapMessageQuery = {}; readonly #tx: MapMessageTx = {}; @@ -78,11 +88,11 @@ export class Contract extends Base { this.abi.messages.forEach((m): void => { if (isUndefined(this.#tx[m.method])) { - this.#tx[m.method] = createTx(m, (o, p) => this.#exec(m, o, p)); + this.#tx[m.method] = expandNs(this.#ns.tx, m, createTx(m, this.#exec)); } if (isUndefined(this.#query[m.method])) { - this.#query[m.method] = createQuery(m, (f, o, p) => this.#read(m, o, p).send(f)); + this.#query[m.method] = expandNs(this.#ns.query, m, createQuery(m, this.#read)); } }); } diff --git a/packages/api-contract/src/base/types.ts b/packages/api-contract/src/base/types.ts index f1b56c861a43..f82cce0f0013 100644 --- a/packages/api-contract/src/base/types.ts +++ b/packages/api-contract/src/base/types.ts @@ -55,3 +55,7 @@ export interface MapMessageTx { export interface MapMessageQuery { [message: string]: ContractQuery; } + +export interface Namespaced { + [path: string]: T | Namespaced; +} diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts index 9fed63ae83fd..6c5d04647f49 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -4,23 +4,24 @@ import type { SubmittableResult } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; import type { ApiTypes } from '@polkadot/api/types'; +import type { ISubmittableResult } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; -import type { AbiConstructor, BlueprintOptions } from '../types'; -import type { BlueprintDeploy, ContractGeneric } from './types'; +import type { AbiConstructor, AbiMessage, BlueprintOptions } from '../types'; +import type { BlueprintDeploy, ContractGeneric, MapConstructorExec, Namespaced } from './types'; import { Bytes } from '@polkadot/types'; -import { compactAddLength, u8aToU8a } from '@polkadot/util'; +import { compactAddLength, isUndefined, u8aToU8a } from '@polkadot/util'; import { randomAsU8a } from '@polkadot/util-crypto'; import { extractOptions, isOptions } from '../util'; export const EMPTY_SALT = new Uint8Array(); -export function createBluePrintTx (fn: (options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): BlueprintDeploy { +export function createBluePrintTx (meta: AbiMessage, fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): BlueprintDeploy { return (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic => isOptions(options) - ? fn(options, params) - : fn(...extractOptions(options, params)); + ? fn(meta, options, params) + : fn(meta, ...extractOptions(options, params)); } export function createBluePrintWithId (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => T): ContractGeneric { @@ -37,3 +38,23 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U ? compactAddLength(u8aToU8a(salt)) : EMPTY_SALT; } + +export function expandNs (ns: Namespaced, { path }: AbiMessage, call: T): T { + if (path.length > 1) { + for (let i = 0; i < path.length - 2; i++) { + ns = ns[path[i]] = {} as Namespaced; + } + } + + ns[path[path.length - 1]] = call; + + return call; +} + +export function expandConstructors (constructors: AbiMessage[], ns: Namespaced>, tx: MapConstructorExec, creator: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): void { + constructors.forEach((c): void => { + if (isUndefined(tx[c.method])) { + tx[c.method] = expandNs(ns, c, createBluePrintTx(c, creator)); + } + }); +} From f0f18eced52838744dea8c4ebec08417f039b27d Mon Sep 17 00:00:00 2001 From: Jaco Date: Tue, 25 Jan 2022 09:12:39 +0200 Subject: [PATCH 2/4] Expose meta on blueprint --- packages/api-contract/src/base/util.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts index e928bb019b94..7a028bf96220 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -24,10 +24,11 @@ export function withMeta (meta: AbiMessage, cre } export function createBluePrintTx (meta: AbiMessage, fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): BlueprintDeploy { - return (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic => + return withMeta(meta, (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic => isOptions(options) ? fn(meta, options, params) - : fn(meta, ...extractOptions(options, params)); + : fn(meta, ...extractOptions(options, params)) + ); } export function createBluePrintWithId (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => T): ContractGeneric { From 7db210114bb3a598e51d7a60e32b68732e466010 Mon Sep 17 00:00:00 2001 From: Jaco Date: Tue, 25 Jan 2022 09:22:59 +0200 Subject: [PATCH 3/4] Extract duplicated types --- packages/api-contract/src/base/util.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts index 7a028bf96220..37a63c74818f 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -1,7 +1,6 @@ // Copyright 2017-2022 @polkadot/api-contract authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { SubmittableResult } from '@polkadot/api'; import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; import type { ApiTypes } from '@polkadot/api/types'; import type { ISubmittableResult } from '@polkadot/types/types'; @@ -17,13 +16,15 @@ import { extractOptions, isOptions } from '../util'; export const EMPTY_SALT = new Uint8Array(); +type ConstructorTx = (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic; + export function withMeta (meta: AbiMessage, creator: Omit): T { (creator as T).meta = meta; return creator as T; } -export function createBluePrintTx (meta: AbiMessage, fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): BlueprintDeploy { +export function createBluePrintTx (meta: AbiMessage, fn: ConstructorTx): BlueprintDeploy { return withMeta(meta, (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic => isOptions(options) ? fn(meta, options, params) @@ -58,7 +59,7 @@ export function expandNs (ns: Namespaced, { path }: AbiMessage, call: T): return call; } -export function expandConstructors (constructors: AbiMessage[], ns: Namespaced>, tx: MapConstructorExec, creator: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic): void { +export function expandConstructors (constructors: AbiMessage[], ns: Namespaced>, tx: MapConstructorExec, creator: ConstructorTx): void { constructors.forEach((c): void => { if (isUndefined(tx[c.method])) { tx[c.method] = expandNs(ns, c, createBluePrintTx(c, creator)); From 089ba538e5f546f2e3f7e14e57467aa4e7fc0eb1 Mon Sep 17 00:00:00 2001 From: Jaco Date: Thu, 31 Mar 2022 11:56:47 +0300 Subject: [PATCH 4/4] Tests & cleanups --- packages/api-contract/src/base/types.ts | 2 +- packages/api-contract/src/base/util.spec.ts | 40 +++++++++++++++++++++ packages/api-contract/src/base/util.ts | 16 ++++++--- 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/api-contract/src/base/util.spec.ts diff --git a/packages/api-contract/src/base/types.ts b/packages/api-contract/src/base/types.ts index db0d3705f477..07324d9ee206 100644 --- a/packages/api-contract/src/base/types.ts +++ b/packages/api-contract/src/base/types.ts @@ -57,5 +57,5 @@ export interface MapMessageQuery { } export interface Namespaced { - [path: string]: T | Namespaced; + [path: string]: (T & Namespaced) | Namespaced; } diff --git a/packages/api-contract/src/base/util.spec.ts b/packages/api-contract/src/base/util.spec.ts new file mode 100644 index 000000000000..3e2f735ca726 --- /dev/null +++ b/packages/api-contract/src/base/util.spec.ts @@ -0,0 +1,40 @@ +// Copyright 2017-2022 @polkadot/api-contract authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Namespaced } from './types'; + +import { expandNs } from './util'; + +type TestNS = Namespaced; + +describe('expandNs', (): void => { + it('expands into single-namespaced and normal location', (): void => { + const testNs: TestNS = {}; + const test: Record = {}; + + test.a = expandNs(testNs, { path: ['ns_a'] }, 'a'); + + expect(test.a).toEqual('a'); + expect(testNs.ns_a).toEqual('a'); + }); + + it('expands into multi-namespaced and normal location', (): void => { + const testNs: TestNS = {}; + + expect(expandNs(testNs, { path: ['A', 'B', 'a'] }, 'a')).toEqual('a'); + + expect(testNs.A.B.a).toEqual('a'); + }); + + it('it expands multiples', (): void => { + const testNs: TestNS = {}; + + expect(expandNs(testNs, { path: ['A', 'B', 'a'] }, 'a')).toEqual('a'); + expect(expandNs(testNs, { path: ['A', 'B', 'b'] }, 'b')).toEqual('b'); + expect(expandNs(testNs, { path: ['A', 'C', 'c'] }, 'c')).toEqual('c'); + + expect(testNs.A.B.a).toEqual('a'); + expect(testNs.A.B.b).toEqual('b'); + expect(testNs.A.C.c).toEqual('c'); + }); +}); diff --git a/packages/api-contract/src/base/util.ts b/packages/api-contract/src/base/util.ts index 37a63c74818f..d26bab19d56a 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -16,6 +16,10 @@ import { extractOptions, isOptions } from '../util'; export const EMPTY_SALT = new Uint8Array(); +interface WithPath { + path: string[]; +} + type ConstructorTx = (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic; export function withMeta (meta: AbiMessage, creator: Omit): T { @@ -47,14 +51,18 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U : EMPTY_SALT; } -export function expandNs (ns: Namespaced, { path }: AbiMessage, call: T): T { +export function expandNs (ns: Namespaced, { path }: WithPath, call: T): T { if (path.length > 1) { - for (let i = 0; i < path.length - 2; i++) { - ns = ns[path[i]] = {} as Namespaced; + for (let i = 0; i < path.length - 1; i++) { + if (!ns[path[i]]) { + ns[path[i]] = {}; + } + + ns = ns[path[i]]; } } - ns[path[path.length - 1]] = call; + ns[path[path.length - 1]] = call as unknown as Namespaced; return call; }