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 2 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>;
}
34 changes: 27 additions & 7 deletions packages/api-contract/src/base/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
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';
Expand All @@ -22,12 +23,11 @@ export function withMeta <T extends { meta: AbiMessage }> (meta: AbiMessage, cre
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> {
return withMeta(meta, (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic<ApiType, R> =>
export function createBluePrintTx <ApiType extends ApiTypes, R extends SubmittableResult> (meta: AbiMessage, fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic<ApiType, R>): BlueprintDeploy<ApiType> {
return (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));
}

export function createBluePrintWithId <T> (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => T): ContractGeneric<BlueprintOptions, T> {
Expand All @@ -44,3 +44,23 @@ export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): U
? compactAddLength(u8aToU8a(salt))
: EMPTY_SALT;
}

export function expandNs <T> (ns: Namespaced<T>, { path }: AbiMessage, call: T): T {
if (path.length > 1) {
for (let i = 0; i < path.length - 2; i++) {
ns = ns[path[i]] = {} as Namespaced<T>;
}
}

ns[path[path.length - 1]] = call;

return call;
}

export function expandConstructors <ApiType extends ApiTypes, R extends ISubmittableResult> (constructors: AbiMessage[], ns: Namespaced<BlueprintDeploy<ApiType>>, tx: MapConstructorExec<ApiType>, creator: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => SubmittableExtrinsic<ApiType, R>): void {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment ink! doesn't plan to support namespaced constructors, so maybe we can remove that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency. It is easier when it is exactly the same everywhere from a maintenance perspective.

If in this case the namespace is single level, so be it. If it changes, it is catered for. Importantly- there are no implementation differences.

constructors.forEach((c): void => {
if (isUndefined(tx[c.method])) {
tx[c.method] = expandNs(ns, c, createBluePrintTx(c, creator));
}
});
}