diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts index 59f16d4562c1..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(c, (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 cde342de07bd..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(c, (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 fea389445996..cc7e1a111f1e 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,7 +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 { withMeta } from './util'; +import { expandNs, withMeta } from './util'; + +interface NsMessages { + readonly query: Namespaced>; + readonly tx: Namespaced>; +} export interface ContractConstructor { new(api: ApiBase, abi: string | Record | Abi, address: string | AccountId): Contract; @@ -30,19 +35,21 @@ const ERROR_NO_CALL = 'Your node does not expose the contracts.call RPC. This is const l = logger('Contract'); -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)) ); } @@ -62,6 +69,8 @@ export class Contract extends Base { */ public readonly address: AccountId; + readonly #ns: NsMessages = { query: {}, tx: {} }; + readonly #query: MapMessageQuery = {}; readonly #tx: MapMessageTx = {}; @@ -73,11 +82,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 251bbccebfb7..07324d9ee206 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) | 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 57a3a4ad253b..d26bab19d56a 100644 --- a/packages/api-contract/src/base/util.ts +++ b/packages/api-contract/src/base/util.ts @@ -1,32 +1,38 @@ // 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'; import type { BN } from '@polkadot/util'; import type { AbiConstructor, AbiMessage, BlueprintOptions } from '../types'; -import type { BlueprintDeploy, ContractGeneric } 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(); +interface WithPath { + path: string[]; +} + +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: (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(options, params) - : fn(...extractOptions(options, params)) + ? fn(meta, options, params) + : fn(meta, ...extractOptions(options, params)) ); } @@ -44,3 +50,27 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U ? compactAddLength(u8aToU8a(salt)) : EMPTY_SALT; } + +export function expandNs (ns: Namespaced, { path }: WithPath, call: T): T { + if (path.length > 1) { + 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 as unknown as Namespaced; + + return call; +} + +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)); + } + }); +}