Skip to content
Merged
51 changes: 42 additions & 9 deletions packages/api-contract/src/base/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,50 @@
import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import { AccountId, ContractExecResult } from '@polkadot/types/interfaces';
import { AnyJson, CodecArg } from '@polkadot/types/types';
import { AnyJson, CodecArg, Registry } from '@polkadot/types/types';
import { AbiMessage, ContractCallOutcome } from '../types';
import { ContractRead, MapMessageExec, MapMessageRead } from './types';

import BN from 'bn.js';
import { map } from 'rxjs/operators';
import ApiBase from '@polkadot/api/base';
import { assert, bnToBn, isFunction, isUndefined, stringCamelCase } from '@polkadot/util';
import { assert, bnToBn, isFunction, isObject, isUndefined, stringCamelCase } from '@polkadot/util';

import Abi from '../Abi';
import { formatData } from '../util';
import Base from './Base';

const ERROR_NO_CALL = 'Your node does not expose the contracts.call RPC. This is most probably due to a runtime configuration.';

// map from a JSON result to current-style ContractExecResult
function mapExecResult (registry: Registry, json: AnyJson): ContractExecResult {
assert(isObject(json) && !Array.isArray(json), 'Invalid JSON result retrieved');

if (!Object.keys(json).some((key) => ['error', 'success'].includes(key))) {
return registry.createType('ContractExecResult', json);
}

const from = registry.createType('ContractExecResultTo260', json);

if (from.isSuccess) {
const s = from.asSuccess;

return registry.createType('ContractExecResult', {
gasConsumed: s.gasConsumed,
result: {
ok: {
data: s.data,
flags: s.flags
}
}
});
}

// in the old format error has no additional information,
// map it as-is with an "unknown" error
return registry.createType('ContractExecResult', { result: { err: { other: 'unknown' } } });
}

export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly address: AccountId;

Expand Down Expand Up @@ -80,20 +109,24 @@ export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((origin: string | AccountId | Uint8Array) =>
this.api.rx.rpc.contracts.call({
this.api.rx.rpc.contracts.call.json({
dest: this.address,
gasLimit: this.#getGas(gasLimit),
inputData: message.toU8a(params),
origin,
value
}).pipe(
map((result: ContractExecResult): ContractCallOutcome => ({
output: result.isSuccess && message.returnType
? formatData(this.registry, result.asSuccess.data, message.returnType)
}).pipe(map((json): ContractCallOutcome => {
const { debugMessage, gasConsumed, result } = mapExecResult(this.registry, json.toJSON());

return {
debugMessage,
gasConsumed,
output: result.isOk && message.returnType
? formatData(this.registry, result.asOk.data, message.returnType)
: null,
result
}))
)
};
}))
)
};
}
Expand Down
8 changes: 6 additions & 2 deletions packages/api-contract/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

import { ApiTypes } from '@polkadot/api/types';
import { ContractExecResult, ContractSelector } from '@polkadot/types/interfaces';
import { ContractExecResultResult, ContractSelector } from '@polkadot/types/interfaces';
import { Codec, CodecArg, TypeDef } from '@polkadot/types/types';

import ApiBase from '@polkadot/api/base';
import { Text, u64 } from '@polkadot/types';

import Abi from './Abi';

export interface ContractBase<ApiType extends ApiTypes> {
Expand Down Expand Up @@ -51,8 +53,10 @@ export interface InterfaceContractCalls {
}

export interface ContractCallOutcome {
debugMessage: Text;
gasConsumed: u64;
output: Codec | null;
result: ContractExecResult;
result: ContractExecResultResult;
}

export interface DecodedEvent {
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/base/Decorate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
if (this.hasSubscriptions || !(methodName.startsWith('subscribe') || methodName.startsWith('unsubscribe'))) {
(section as Record<string, unknown>)[methodName] = decorateMethod(method, { methodName }) as unknown;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(section as Record<string, { json: unknown }>)[methodName].json = decorateMethod(method.json, { methodName }) as unknown;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(section as Record<string, { raw: unknown }>)[methodName].raw = decorateMethod(method.raw, { methodName }) as unknown;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/checkTypes.manual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ async function rpc (api: ApiPromise): Promise<void> {
console.log('current balance:', balance.toString());
});

// using raw
// using json & raw
await api.rpc.chain.getBlock.json('0x123456');
await api.rpc.chain.getBlock.raw('0x123456');

// using raw subs
Expand Down
14 changes: 8 additions & 6 deletions packages/api/src/types/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
// Copyright 2017-2020 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { AnyFunction, Callback, Codec } from '@polkadot/types/types';
import { AnyFunction, Callback } from '@polkadot/types/types';

import { Observable } from 'rxjs';
import { Json, Raw } from '@polkadot/types/codec';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { ApiTypes, Push, PromiseResult, RxResult, UnsubscribePromise } from './base';

export interface RpcRxResult<F extends AnyFunction> extends RxResult<F> {
raw (...args: Parameters<F>): Observable<Uint8Array & Codec>;
json (...args: Parameters<F>): Observable<Json>;
raw (...args: Parameters<F>): Observable<Raw>;
}

export interface RpcPromiseResult<F extends AnyFunction> extends PromiseResult<F> {
raw (...args: Parameters<F>): Promise<Uint8Array & Codec>;
raw (...args: Push<Parameters<F>, Callback<Uint8Array & Codec>>): UnsubscribePromise;
json (...args: Parameters<F>): Promise<Json>;
json (...args: Push<Parameters<F>, Callback<Json>>): UnsubscribePromise;
raw (...args: Parameters<F>): Promise<Raw>;
raw (...args: Push<Parameters<F>, Callback<Raw>>): UnsubscribePromise;
}

// FIXME The day TS has higher-kinded types, we can remove this hardcoded stuff
export type RpcMethodResult<ApiType extends ApiTypes, F extends AnyFunction> = ApiType extends 'rxjs'
? RpcRxResult<F>
: RpcPromiseResult<F>;
Expand Down
52 changes: 26 additions & 26 deletions packages/rpc-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface StorageChangeSetJSON {
changes: [string, string | null][];
}

type OutputType = 'json' | 'raw' | 'scale';

const l = logger('rpc-core');

const EMPTY_META = {
Expand Down Expand Up @@ -211,12 +213,20 @@ export default class Rpc implements RpcInterface {
}, {} as RpcInterface[Section]);
}

private _createMethodWithRaw (creator: (isRaw: boolean) => (...values: any[]) => Observable<any>): RpcInterfaceMethod {
const call = creator(false) as Partial<RpcInterfaceMethod>;
private _memomize (creator: (outputAs: OutputType) => (...values: any[]) => Observable<any>): RpcInterfaceMethod & memoizee.Memoized<RpcInterfaceMethod> {
const memoized = memoizee(creator('scale') as RpcInterfaceMethod, {
// Dynamic length for argument
length: false,
// Normalize args so that different args that should be cached
// together are cached together.
// E.g.: `query.my.method('abc') === query.my.method(createType('AccountId', 'abc'));`
normalizer: normalizer(this.#instanceId)
});

call.raw = creator(true);
memoized.json = creator('json');
memoized.raw = creator('raw');

return call as RpcInterfaceMethod;
return memoized;
}

private _createMethodSend (section: string, method: string, def: DefinitionRpc): RpcInterfaceMethod {
Expand All @@ -226,7 +236,7 @@ export default class Rpc implements RpcInterface {
let memoized: null | RpcInterfaceMethod & memoizee.Memoized<RpcInterfaceMethod> = null;

// execute the RPC call, doing a registry swap for historic as applicable
const callWithRegistry = async (isRaw: boolean, values: any[]): Promise<Codec | Codec[]> => {
const callWithRegistry = async (outputAs: OutputType, values: any[]): Promise<Codec | Codec[]> => {
const hash = hashIndex === -1
? undefined
: values[hashIndex] as Uint8Array;
Expand All @@ -236,16 +246,16 @@ export default class Rpc implements RpcInterface {
const params = this._formatInputs(registry, def, values);
const data = await this.provider.send(rpcName, params.map((param): AnyJson => param.toJSON())) as AnyJson;

return isRaw
? registry.createType('Raw', data)
: this._formatOutput(registry, method, def, params, data);
return outputAs === 'scale'
? this._formatOutput(registry, method, def, params, data)
: registry.createType(outputAs === 'raw' ? 'Raw' : 'Json', data);
};

const creator = (isRaw: boolean) => (...values: any[]): Observable<any> => {
const creator = (outputAs: OutputType) => (...values: any[]): Observable<any> => {
const isDelayed = (hashIndex !== -1 && !!values[hashIndex]) || (cacheIndex !== -1 && !!values[cacheIndex]);

return new Observable((observer: Observer<any>): VoidCallback => {
callWithRegistry(isRaw, values)
callWithRegistry(outputAs, values)
.then((value): void => {
observer.next(value);
observer.complete();
Expand All @@ -269,10 +279,7 @@ export default class Rpc implements RpcInterface {
);
};

memoized = memoizee(this._createMethodWithRaw(creator), {
length: false,
normalizer: normalizer(this.#instanceId)
});
memoized = this._memomize(creator);

return memoized;
}
Expand All @@ -297,7 +304,7 @@ export default class Rpc implements RpcInterface {
const subType = `${section}_${updateType}`;
let memoized: null | RpcInterfaceMethod & memoizee.Memoized<RpcInterfaceMethod> = null;

const creator = (isRaw: boolean) => (...values: unknown[]): Observable<any> => {
const creator = (outputAs: OutputType) => (...values: unknown[]): Observable<any> => {
return new Observable((observer: Observer<any>): VoidCallback => {
// Have at least an empty promise, as used in the unsubscribe
let subscriptionPromise: Promise<number | string | null> = Promise.resolve(null);
Expand All @@ -322,9 +329,9 @@ export default class Rpc implements RpcInterface {

try {
observer.next(
isRaw
? registry.createType('Raw', result)
: this._formatOutput(registry, method, def, params, result)
outputAs === 'scale'
? this._formatOutput(registry, method, def, params, result)
: registry.createType(outputAs === 'raw' ? 'Raw' : 'Json', result)
);
} catch (error) {
observer.error(error);
Expand Down Expand Up @@ -353,14 +360,7 @@ export default class Rpc implements RpcInterface {
}).pipe(drr());
};

memoized = memoizee(this._createMethodWithRaw(creator), {
// Dynamic length for argument
length: false,
// Normalize args so that different args that should be cached
// together are cached together.
// E.g.: `query.my.method('abc') === query.my.method(createType('AccountId', 'abc'));`
normalizer: normalizer(this.#instanceId)
});
memoized = this._memomize(creator);

return memoized;
}
Expand Down
1 change: 1 addition & 0 deletions packages/rpc-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from './types.jsonrpc';

export interface RpcInterfaceMethod {
(...params: any[]): Observable<any>;
json (...params: any[]): Observable<any>;
raw (...params: any[]): Observable<any>;
}
2 changes: 2 additions & 0 deletions packages/typegen/src/generate/interfaceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import Handlebars from 'handlebars';

import Json from '@polkadot/types/codec/Json';
import Raw from '@polkadot/types/codec/Raw';
import { TypeRegistry } from '@polkadot/types/create';
import * as defaultDefinitions from '@polkadot/types/interfaces/definitions';
Expand All @@ -13,6 +14,7 @@ import { ModuleTypes } from '../util/imports';

const primitiveClasses = {
...defaultPrimitives,
Json,
Raw
};

Expand Down
28 changes: 23 additions & 5 deletions packages/types/src/augment/registry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Auto-generated via `yarn polkadot-types-from-defs`, do not edit
/* eslint-disable */

import { Compact, Option, Raw, Vec } from '@polkadot/types/codec';
import { Compact, Json, Option, Raw, Vec } from '@polkadot/types/codec';
import { BitVec, Bytes, Data, DoNotConstruct, Null, StorageKey, Text, Type, U256, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types/primitive';
import { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
import { RawAuraPreDigest } from '@polkadot/types/interfaces/aura';
Expand All @@ -14,7 +14,7 @@ import { PrefixedStorageKey } from '@polkadot/types/interfaces/childstate';
import { EthereumAddress, StatementKind } from '@polkadot/types/interfaces/claims';
import { CollectiveOrigin, MemberCount, ProposalIndex, Votes, VotesTo230 } from '@polkadot/types/interfaces/collective';
import { AuthorityId, RawVRFOutput } from '@polkadot/types/interfaces/consensus';
import { AliveContractInfo, CodeHash, ContractCallRequest, ContractExecResult, ContractExecResultSuccess, ContractExecResultSuccessTo255, ContractExecResultTo255, ContractInfo, ContractStorageKey, Gas, HostFnWeights, InstructionWeights, PrefabWasmModule, PrefabWasmModuleReserved, Schedule, ScheduleTo212, ScheduleTo258, SeedOf, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts';
import { AliveContractInfo, CodeHash, ContractCallRequest, ContractExecResult, ContractExecResultErr, ContractExecResultErrModule, ContractExecResultOk, ContractExecResultResult, ContractExecResultSuccessTo255, ContractExecResultSuccessTo260, ContractExecResultTo255, ContractExecResultTo260, ContractInfo, ContractStorageKey, Gas, HostFnWeights, InstructionWeights, PrefabWasmModule, PrefabWasmModuleReserved, Schedule, ScheduleTo212, ScheduleTo258, SeedOf, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts';
import { ContractConstructorSpec, ContractContractSpec, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEventParamSpec, ContractEventSpec, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpec, ContractMessageSpec, ContractProject, ContractProjectContract, ContractProjectSource, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi';
import { AccountVote, AccountVoteSplit, AccountVoteStandard, Conviction, Delegations, PreimageStatus, PreimageStatusAvailable, PriorLock, PropIndex, Proposal, ProxyState, ReferendumIndex, ReferendumInfo, ReferendumInfoFinished, ReferendumInfoTo239, ReferendumStatus, Tally, Voting, VotingDelegating, VotingDirect, VotingDirectVote } from '@polkadot/types/interfaces/democracy';
import { ApprovalFlag, DefunctVoter, Renouncing, SetIndex, Vote, VoteIndex, VoteThreshold, VoterInfo } from '@polkadot/types/interfaces/elections';
Expand Down Expand Up @@ -129,6 +129,9 @@ declare module '@polkadot/types/types/registry' {
'Compact<usize>': Compact<usize>;
'Option<usize>': Option<usize>;
'Vec<usize>': Vec<usize>;
Json: Json;
'Option<Json>': Option<Json>;
'Vec<Json>': Vec<Json>;
Raw: Raw;
'Option<Raw>': Option<Raw>;
'Vec<Raw>': Vec<Raw>;
Expand Down Expand Up @@ -487,9 +490,24 @@ declare module '@polkadot/types/types/registry' {
ContractExecResultTo255: ContractExecResultTo255;
'Option<ContractExecResultTo255>': Option<ContractExecResultTo255>;
'Vec<ContractExecResultTo255>': Vec<ContractExecResultTo255>;
ContractExecResultSuccess: ContractExecResultSuccess;
'Option<ContractExecResultSuccess>': Option<ContractExecResultSuccess>;
'Vec<ContractExecResultSuccess>': Vec<ContractExecResultSuccess>;
ContractExecResultSuccessTo260: ContractExecResultSuccessTo260;
'Option<ContractExecResultSuccessTo260>': Option<ContractExecResultSuccessTo260>;
'Vec<ContractExecResultSuccessTo260>': Vec<ContractExecResultSuccessTo260>;
ContractExecResultTo260: ContractExecResultTo260;
'Option<ContractExecResultTo260>': Option<ContractExecResultTo260>;
'Vec<ContractExecResultTo260>': Vec<ContractExecResultTo260>;
ContractExecResultErrModule: ContractExecResultErrModule;
'Option<ContractExecResultErrModule>': Option<ContractExecResultErrModule>;
'Vec<ContractExecResultErrModule>': Vec<ContractExecResultErrModule>;
ContractExecResultErr: ContractExecResultErr;
'Option<ContractExecResultErr>': Option<ContractExecResultErr>;
'Vec<ContractExecResultErr>': Vec<ContractExecResultErr>;
ContractExecResultOk: ContractExecResultOk;
'Option<ContractExecResultOk>': Option<ContractExecResultOk>;
'Vec<ContractExecResultOk>': Vec<ContractExecResultOk>;
ContractExecResultResult: ContractExecResultResult;
'Option<ContractExecResultResult>': Option<ContractExecResultResult>;
'Vec<ContractExecResultResult>': Vec<ContractExecResultResult>;
ContractExecResult: ContractExecResult;
'Option<ContractExecResult>': Option<ContractExecResult>;
'Vec<ContractExecResult>': Vec<ContractExecResult>;
Expand Down
Loading