Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose contract namespaces under contract.ns.{query, tx}.* #4487

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions packages/api-contract/src/base/Blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiType extends ApiTypes> {
new(api: ApiBase<ApiType>, abi: string | Record<string, unknown> | Abi, codeHash: string | Hash | Uint8Array): Blueprint<ApiType>;
Expand All @@ -38,18 +38,20 @@ export class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
*/
public readonly codeHash: Hash;

readonly #ns: { tx: Namespaced<BlueprintDeploy<ApiType>> } = { tx: {} };

readonly #tx: MapConstructorExec<ApiType> = {};

constructor (api: ApiBase<ApiType>, abi: string | Record<string, unknown> | Abi, codeHash: string | Hash | Uint8Array, decorateMethod: DecorateMethod<ApiType>) {
super(api, abi, decorateMethod);

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<BlueprintDeploy<ApiType>> } {
return this.#ns;
}

public get tx (): MapConstructorExec<ApiType> {
Expand Down
18 changes: 10 additions & 8 deletions packages/api-contract/src/base/Code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiType extends ApiTypes> {
new(api: ApiBase<ApiType>, abi: string | Record<string, unknown> | Abi, wasm: Uint8Array | string | Buffer | null | undefined): Code<ApiType>;
Expand All @@ -39,6 +39,8 @@ export class CodeSubmittableResult<ApiType extends ApiTypes> extends Submittable
export class Code<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly code: Uint8Array;

readonly #ns: { tx: Namespaced<BlueprintDeploy<ApiType>> } = { tx: {} };

readonly #tx: MapConstructorExec<ApiType> = {};

constructor (api: ApiBase<ApiType>, abi: string | Record<string, unknown> | Abi, wasm: Uint8Array | string | Buffer | null | undefined, decorateMethod: DecorateMethod<ApiType>) {
Expand All @@ -50,11 +52,11 @@ export class Code<ApiType extends ApiTypes> extends Base<ApiType> {

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<BlueprintDeploy<ApiType>> } {
return this.#ns;
}

public get tx (): MapConstructorExec<ApiType> {
Expand Down
31 changes: 20 additions & 11 deletions packages/api-contract/src/base/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<ApiType extends ApiTypes> {
readonly query: Namespaced<ContractQuery<ApiType>>;
readonly tx: Namespaced<ContractTx<ApiType>>;
}

export interface ContractConstructor<ApiType extends ApiTypes> {
new(api: ApiBase<ApiType>, abi: string | Record<string, unknown> | Abi, address: string | AccountId): Contract<ApiType>;
Expand All @@ -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 <ApiType extends ApiTypes> (meta: AbiMessage, fn: (origin: string | AccountId | Uint8Array, options: ContractOptions, params: unknown[]) => ContractCallResult<ApiType, ContractCallOutcome>): ContractQuery<ApiType> {
function createQuery <ApiType extends ApiTypes> (meta: AbiMessage, fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: unknown[]) => ContractCallSend<ApiType>): ContractQuery<ApiType> {
return withMeta(meta, (origin: string | AccountId | Uint8Array, options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): ContractCallResult<ApiType, ContractCallOutcome> =>
isOptions(options)
? fn(origin, options, params)
: fn(origin, ...extractOptions<ContractOptions>(options, params))
(
isOptions(options)
? fn(meta, options, params)
: fn(meta, ...extractOptions<ContractOptions>(options, params))
).send(origin)
);
}

function createTx <ApiType extends ApiTypes> (meta: AbiMessage, fn: (options: ContractOptions, params: unknown[]) => SubmittableExtrinsic<ApiType>): ContractTx<ApiType> {
function createTx <ApiType extends ApiTypes> (meta: AbiMessage, fn: (messageOrId: AbiMessage | string | number, options: ContractOptions, params: unknown[]) => SubmittableExtrinsic<ApiType>): ContractTx<ApiType> {
return withMeta(meta, (options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): SubmittableExtrinsic<ApiType> =>
isOptions(options)
? fn(options, params)
: fn(...extractOptions<ContractOptions>(options, params))
? fn(meta, options, params)
: fn(meta, ...extractOptions<ContractOptions>(options, params))
);
}

Expand All @@ -62,6 +69,8 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
*/
public readonly address: AccountId;

readonly #ns: NsMessages<ApiType> = { query: {}, tx: {} };

readonly #query: MapMessageQuery<ApiType> = {};

readonly #tx: MapMessageTx<ApiType> = {};
Expand All @@ -73,11 +82,11 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {

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));
}
});
}
Expand Down
4 changes: 4 additions & 0 deletions packages/api-contract/src/base/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ export interface MapMessageTx<ApiType extends ApiTypes> {
export interface MapMessageQuery<ApiType extends ApiTypes> {
[message: string]: ContractQuery<ApiType>;
}

export interface Namespaced <T> {
[path: string]: (T & Namespaced<T>) | Namespaced<T>;
}
40 changes: 40 additions & 0 deletions packages/api-contract/src/base/util.spec.ts
Original file line number Diff line number Diff line change
@@ -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<string>;

describe('expandNs', (): void => {
it('expands into single-namespaced and normal location', (): void => {
const testNs: TestNS = {};
const test: Record<string, string> = {};

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');
});
});
42 changes: 36 additions & 6 deletions packages/api-contract/src/base/util.ts
Original file line number Diff line number Diff line change
@@ -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 <ApiType extends ApiTypes, R extends ISubmittableResult> = (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic<ApiType, R>;

export function withMeta <T extends { meta: AbiMessage }> (meta: AbiMessage, creator: Omit<T, 'meta'>): T {
(creator as T).meta = meta;

return creator as T;
}

export function createBluePrintTx <ApiType extends ApiTypes, R extends SubmittableResult> (meta: AbiMessage, fn: (options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic<ApiType, R>): BlueprintDeploy<ApiType> {
export function createBluePrintTx <ApiType extends ApiTypes, R extends ISubmittableResult> (meta: AbiMessage, fn: ConstructorTx<ApiType, R>): BlueprintDeploy<ApiType> {
return withMeta(meta, (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic<ApiType, R> =>
isOptions(options)
? fn(options, params)
: fn(...extractOptions<BlueprintOptions>(options, params))
? fn(meta, options, params)
: fn(meta, ...extractOptions<BlueprintOptions>(options, params))
);
}

Expand All @@ -44,3 +50,27 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U
? compactAddLength(u8aToU8a(salt))
: EMPTY_SALT;
}

export function expandNs <T> (ns: Namespaced<T>, { 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<T>;

return call;
}

export function expandConstructors <ApiType extends ApiTypes, R extends ISubmittableResult> (constructors: AbiMessage[], ns: Namespaced<BlueprintDeploy<ApiType>>, tx: MapConstructorExec<ApiType>, creator: ConstructorTx<ApiType, R>): void {
constructors.forEach((c): void => {
if (isUndefined(tx[c.method])) {
tx[c.method] = expandNs(ns, c, createBluePrintTx(c, creator));
}
});
}