Contract API for Rx/promises (#1520)

* wip with rxjs/promise

* add outcome process

* contract call decoding solved

* restore messages as dict, cleanup

* linting

* linting

* type fixes
This commit is contained in:
Keith Ingram
2019-10-31 19:47:14 +02:00
committed by GitHub
parent 64bc3fbffa
commit cf01c41b33
39 changed files with 1080 additions and 386 deletions
+5 -5
View File
@@ -2,21 +2,21 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import erc20Abi from '../test/contracts/Erc20.json';
import incrementerAbi from '../test/contracts/incrementer.json';
import { Abi } from '.';
describe('Abi', (): void => {
describe('erc20', (): void => {
describe('incrementer', (): void => {
let abi: Abi;
beforeEach((): void => {
abi = new Abi(erc20Abi);
abi = new Abi(incrementerAbi);
});
it('has the attached methods', (): void => {
expect(Object.keys(abi.messages)).toEqual(
['totalSupply', 'balanceOf', 'allowance', 'transfer', 'approve', 'transferFrom']
expect(Object.values(abi.abi.contract.messages).map(({ name }): string => name)).toEqual(
['inc', 'get', 'compare']
);
});
});
+14 -9
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ContractABI, ContractABIPre, ContractABIFn, InterfaceAbi, AbiMessages } from './types';
import { AbiConstructors, AbiMessages, ContractABI, ContractABIPre, ContractABIFn, InterfaceAbi } from './types';
import { stringCamelCase } from '@polkadot/util';
import ContractRegistry from './ContractRegistry';
@@ -10,9 +10,9 @@ import ContractRegistry from './ContractRegistry';
export default class ContractAbi extends ContractRegistry implements InterfaceAbi {
public readonly abi: ContractABI;
public readonly constructors: ContractABIFn[];
public readonly constructors: AbiConstructors;
public readonly messages: AbiMessages = {};
public readonly messages: AbiMessages;
public constructor (abi: ContractABIPre) {
super(abi);
@@ -25,16 +25,21 @@ export default class ContractAbi extends ContractRegistry implements InterfaceAb
const abi = this.convertAbi(abiPre);
const constructors = abi.contract.constructors.map(
(constructor, index): ContractABIFn => {
return this.createMethod(`constructor ${index}`, constructor);
return this.createMessage(`constructor ${index}`, constructor);
}
);
const messages: AbiMessages = {};
abi.contract.messages.forEach((method): void => {
const name = stringCamelCase(method.name);
const messages: AbiMessages = abi.contract.messages.reduce(
(result: AbiMessages, message): AbiMessages => {
const name = stringCamelCase(message.name);
messages[name] = this.createMethod(`messages.${name}`, method);
});
return {
...result,
[name]: this.createMessage(`messages.${name}`, message)
};
},
{}
);
return [abi, constructors, messages];
}
+53 -36
View File
@@ -2,38 +2,55 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { CodecArg, TypeDef } from '@polkadot/types/types';
import { ContractABIArgBasePre, ContractABIContract, ContractABIContractPre, ContractABIEvent, ContractABIEventPre, ContractABIFn, ContractABIFnArg, ContractABIMethod, ContractABIMethodBase, ContractABIMethodPre, ContractABI, ContractABIPre, ContractABIRange, ContractABIRangePre, ContractABIStorage, ContractABIStorageLayout, ContractABIStorageLayoutPre, ContractABIStoragePre, ContractABIStorageStruct, ContractABIStorageStructPre, ContractABITypePre } from './types';
import { Compact, u32, createType } from '@polkadot/types';
import { CodecArg, Constructor, TypeDef } from '@polkadot/types/types';
import { ContractABIArgBasePre, ContractABIContract, ContractABIContractPre, ContractABIEvent, ContractABIEventPre, ContractABIFn, ContractABIFnArg, ContractABIMessage, ContractABIMessageBase, ContractABIMessagePre, ContractABI, ContractABIMessageCommon, ContractABIPre, ContractABIRange, ContractABIRangePre, ContractABIStorage, ContractABIStorageLayout, ContractABIStorageLayoutPre, ContractABIStoragePre, ContractABIStorageStruct, ContractABIStorageStructPre, ContractABITypePre } from './types';
import { Compact, u32, createClass, createType, encodeType } from '@polkadot/types';
import { assert, hexToU8a, isNumber, isString, isNull, isObject, isUndefined, stringCamelCase, isHex, hexToNumber } from '@polkadot/util';
import MetaRegistry from './MetaRegistry';
import { createArgClass } from './method';
// parse a selector, this can be a number (older) or of [<hex>, <hex>, ...]. However,
// just catch everything (since this is now non-standard for u32 anyway)
function parseSelector (fnname: string, input: number | string | number[] | string[]): u32 {
function parseSelector (fnname: string, input: ContractABIMessageCommon['selector']): u32 {
if (isNumber(input)) {
return createType('u32', input);
} else if (isHex(input)) {
return createType('u32', hexToU8a(input));
} else if (Array.isArray(input)) {
assert(input.length === 4, `${fnname}: Invalid selector length`);
} else if (typeof input === 'string') {
try {
const array = JSON.parse(input);
return createType('u32', Uint8Array.from(
// the as number[] is here to pacify TS, it doesn't quite know how to handle the cb
(input as number[]).map((value: string | number): number =>
isHex(value)
? hexToNumber(value.toString())
: value
)
));
assert(array.length === 4, `${fnname}: Invalid selector length`);
return createType('u32', Uint8Array.from(
// the as number[] is here to pacify TS, it doesn't quite know how to handle the cb
(array as number[]).map((value: string | number): number =>
isHex(value)
? hexToNumber(value.toString())
: value
)
));
} catch (e) {
console.error(e);
}
}
throw new Error(`${fnname}: Unable to parse selector`);
}
function createArgClass (args: ContractABIFnArg[], baseDef: Record<string, string>): Constructor {
return createClass(
JSON.stringify(
args.reduce((base: Record<string, any>, { name, type }): Record<string, any> => {
base[name] = encodeType(type);
return base;
}, baseDef)
)
);
}
export default class ContractRegistry extends MetaRegistry {
// Contract ABI helpers
public validateArgs (name: string, args: ContractABIArgBasePre[]): void {
@@ -49,7 +66,7 @@ export default class ContractRegistry extends MetaRegistry {
}
public validateConstructors ({ contract: { constructors } }: ContractABIPre): void {
constructors.forEach((constructor: ContractABIMethodPre, index): void => {
constructors.forEach((constructor: ContractABIMessagePre, index): void => {
const unknownKeys = Object.keys(constructor).filter((key): boolean => !['args', 'docs', 'name', 'selector'].includes(key));
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI constructor`);
@@ -58,20 +75,20 @@ export default class ContractRegistry extends MetaRegistry {
});
}
public validateMethods ({ contract: { messages } }: ContractABIPre): void {
messages.forEach((method): void => {
const unknownKeys = Object.keys(method).filter((key): boolean => !['args', 'docs', 'mutates', 'name', 'selector', 'return_type'].includes(key));
const fnname = `messages.${method.name}`;
public validateMessages ({ contract: { messages } }: ContractABIPre): void {
messages.forEach((message): void => {
const unknownKeys = Object.keys(message).filter((key): boolean => !['args', 'docs', 'mutates', 'name', 'selector', 'return_type'].includes(key));
const fnname = `messages.${message.name}`;
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${method.name}`);
assert(unknownKeys.length === 0, `Unknown keys ${unknownKeys.join(', ')} found in ABI args for messages.${message.name}`);
const { name, selector, return_type: returnType } = method;
const { name, selector, return_type: returnType } = message;
assert(isNumber(name) && isString(this.stringAt(name)), `Expected name for ${fnname}`);
assert(isNull(returnType) || (isNumber(returnType.ty) && isObject(this.typeDefAt(returnType.ty))), `Expected return_type for ${fnname}`);
parseSelector(fnname, selector);
this.validateArgs(fnname, method.args);
this.validateArgs(fnname, message.args);
});
}
@@ -85,19 +102,19 @@ export default class ContractRegistry extends MetaRegistry {
assert(constructors && messages && isString(this.stringAt(name)), 'ABI should have constructors, messages & name sections');
this.validateConstructors(abi);
this.validateMethods(abi);
this.validateMessages(abi);
}
public createMethod (name: string, method: Partial<ContractABIMethod> & ContractABIMethodBase): ContractABIFn {
const args = method.args.map(({ name, type }): ContractABIFnArg => {
public createMessage (name: string, message: Partial<ContractABIMessage> & ContractABIMessageBase): ContractABIFn {
const args = message.args.map(({ name, type }): ContractABIFnArg => {
assert(isObject(type), `Invalid type at index ${type}`);
return {
name: stringCamelCase(name),
type
};
});
const Clazz = createArgClass(args, isUndefined(method.selector) ? {} : { __selector: 'u32' });
const baseStruct: { [index: string]: any } = { __selector: isUndefined(method.selector) ? undefined : parseSelector(name, method.selector) };
const Clazz = createArgClass(args, isUndefined(message.selector) ? {} : { __selector: 'u32' });
const baseStruct: { [index: string]: any } = { __selector: isUndefined(message.selector) ? undefined : parseSelector(name, message.selector) };
const encoder = (...params: CodecArg[]): Uint8Array => {
assert(params.length === args.length, `Expected ${args.length} arguments to contract ${name}, found ${params.length}`);
@@ -115,8 +132,8 @@ export default class ContractRegistry extends MetaRegistry {
const fn = (encoder as ContractABIFn);
fn.args = args;
fn.isConstant = !(method.mutates || false);
fn.type = method.returnType || null;
fn.isConstant = !(message.mutates || false);
fn.type = message.returnType || null;
return fn;
}
@@ -140,7 +157,7 @@ export default class ContractRegistry extends MetaRegistry {
public convertContract ({ constructors, messages, name, events, ...contract }: ContractABIContractPre): ContractABIContract {
return {
constructors: this.convertConstructors(constructors),
messages: messages.map(method => this.convertMethod(method)),
messages: messages.map(message => this.convertMessage(message)),
name: this.stringAt(name),
...(events
? { events: events.map(event => this.convertEvent(event)) }
@@ -149,17 +166,17 @@ export default class ContractRegistry extends MetaRegistry {
};
}
public convertConstructors (constructors: ContractABIMethodPre[]): ContractABIMethod[] {
public convertConstructors (constructors: ContractABIMessagePre[]): ContractABIMessage[] {
return constructors.map(
(constructor: ContractABIMethodPre): ContractABIMethod => {
return this.convertMethod(constructor);
(constructor: ContractABIMessagePre): ContractABIMessage => {
return this.convertMessage(constructor);
}
);
}
public convertMethod ({ args, name, return_type: returnType, ...method }: ContractABIMethodPre): ContractABIMethod {
public convertMessage ({ args, name, return_type: returnType, ...message }: ContractABIMessagePre): ContractABIMessage {
return {
...method,
...message,
args: this.convertArgs(args),
name: this.stringAt(name),
returnType: returnType ? this.convertType(returnType) : null
-31
View File
@@ -1,31 +0,0 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { SubmittableModuleExtrinsics } from '@polkadot/api/types';
import { ApiObject, ContractABIPre, ContractBase } from './types';
import { assert } from '@polkadot/util';
import Abi from './Abi';
// NOTE Experimental, POC, bound to change
export default abstract class RxBase implements ContractBase<'rxjs'> {
public readonly abi: Abi;
public readonly api: ApiObject<'rxjs'>;
public readonly apiContracts: SubmittableModuleExtrinsics<'rxjs'>;
public constructor (api: ApiObject<'rxjs'>, abi: ContractABIPre | Abi) {
this.abi = abi instanceof Abi
? abi
: new Abi(abi);
this.api = api;
// cater for substrate 2.x & 1.x (in order)
this.apiContracts = api.tx.contracts || api.tx.contract;
assert(this.apiContracts && this.apiContracts.putCode, 'You need to connect to a node with the contracts module, the metadata does not enable api.tx.contracts on this instance');
}
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ContractABIPre } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import { assert } from '@polkadot/util';
import Abi from './Abi';
import RxBase from './RxBase';
import RxContract from './RxContract';
type BlueprintCreateResultSubscription = Observable<BlueprintCreateResult>;
export interface BlueprintCreate {
signAndSend (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription;
}
class BlueprintCreateResult extends SubmittableResult {
public readonly contract?: RxContract;
public constructor (result: SubmittableResultImpl, contract?: RxContract) {
super(result);
this.contract = contract;
}
}
// NOTE Experimental, POC, bound to change
export default class Blueprint extends RxBase {
public readonly codeHash: Hash;
public constructor (api: ApiRx, abi: ContractABIPre | Abi, codeHash: string | Hash) {
super(api, abi);
this.codeHash = createType('Hash', codeHash);
}
public deployContract (constructorIndex = 0, endowment: number | BN, maxGas: number | BN, ...params: any[]): BlueprintCreate {
assert(!!this.abi.constructors[constructorIndex], `Specified constructor index ${constructorIndex} does not exist`);
const signAndSend = (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription => {
return this.apiContracts
.create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params))
.signAndSend(account)
.pipe(map(this.createResult));
};
return { signAndSend };
}
private createResult = (result: SubmittableResult): BlueprintCreateResult => {
let contract: RxContract | undefined;
if (result.isFinalized) {
const record = result.findRecord('contract', 'Instantiated');
if (record) {
contract = new RxContract(this.api, this.abi, record.event.data[1] as Address);
}
}
return new BlueprintCreateResult(result, contract);
}
}
-83
View File
@@ -1,83 +0,0 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ContractABIPre } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { compactAddLength, u8aToU8a } from '@polkadot/util';
import Abi from './Abi';
import RxBase from './RxBase';
import RxBlueprint from './RxBlueprint';
// Ok, tried, failed, eventually ... well, we are only trying with RxJs as a
// start, so take a big fat shortcut with this version, real intended version
// follows this and is commented out
type CodePutCodeResultSubscription = Observable<CodePutCodeResult>;
// type ICodePutCodeResultSubscription<ApiType> =
// ApiType extends 'rxjs'
// ? Observable<CodePutCodeResult>
// : Promise<() => void>;
// unlike the ISubmittableExtrinsic, it doesn't extend IExtrinsic and only
// implements one var iant of signAndSend - this is purely done to see what
// is hanging where, with the bare-mainimum from a simplicity perspective
// Very obvious is that it supports only 1 version of signAndSend
// (we need to revisit this, but probably ok for initial?)
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface CodePutCode {
signAndSend (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription;
}
class CodePutCodeResult extends SubmittableResult {
public readonly blueprint?: RxBlueprint;
public constructor (result: SubmittableResultImpl, blueprint?: RxBlueprint) {
super(result);
this.blueprint = blueprint;
}
}
// NOTE Experimental, POC, bound to change
export default class RxCode extends RxBase {
public readonly code: Uint8Array;
public constructor (api: ApiRx, abi: ContractABIPre | Abi, wasm: string | Uint8Array) {
super(api, abi);
this.code = u8aToU8a(wasm);
}
public createBlueprint (maxGas: number | BN): CodePutCode {
const signAndSend = (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription => {
return this.apiContracts
.putCode(maxGas, compactAddLength(this.code))
.signAndSend(account)
.pipe(map(this.createResult));
};
return { signAndSend };
}
private createResult = (result: SubmittableResultImpl): CodePutCodeResult => {
let blueprint: RxBlueprint | undefined;
if (result.isFinalized) {
const record = result.findRecord('contract', 'CodeStored');
if (record) {
blueprint = new RxBlueprint(this.api, this.abi, record.event.data[0] as Hash);
}
}
return new CodePutCodeResult(result, blueprint);
}
}
-41
View File
@@ -1,41 +0,0 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId, Address } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ContractABIPre, ContractABIFn, InterfaceContract, InterfaceContractCalls } from './types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { ApiRx, SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import Abi from './Abi';
import RxBase from './RxBase';
export type ContractCallResultSubscription = Observable<SubmittableResult>;
export interface ContractCall {
signAndSend (account: IKeyringPair | string | AccountId | Address): ContractCallResultSubscription;
}
type CallResult = (value: BN | number, maxGas: BN | number, ...params: any[]) => ContractCall;
// NOTE Experimental, POC, bound to change
export default class RxContract extends RxBase implements InterfaceContract {
public readonly address: Address;
public readonly calls: InterfaceContractCalls = {};
public constructor (api: ApiRx, abi: ContractABIPre | Abi, address: string | AccountId | Address) {
super(api, abi);
this.address = createType('Address', address);
Object.entries(this.abi.messages).forEach(([name]): void => {
this.calls[name] = (fn: ContractABIFn): CallResult =>
(value: BN | number, maxGas: BN | number, ...params: any[]): ContractCall =>
this.apiContracts.call(this.address, value, maxGas, fn(...params));
});
}
}
@@ -0,0 +1,75 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiTypes, DecorateMethod, SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ApiObject, ContractABIPre } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import { assert } from '@polkadot/util';
import Abi from '../Abi';
import Contract from './Contract';
import { BaseWithTx } from './util';
type BlueprintCreateResultSubscription<ApiType extends ApiTypes> = Observable<BlueprintCreateResult<ApiType>>;
export interface BlueprintCreate<ApiType extends ApiTypes> {
signAndSend (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription<ApiType>;
}
class BlueprintCreateResult<ApiType extends ApiTypes> extends SubmittableResult {
public readonly contract?: Contract<ApiType>;
public constructor (result: SubmittableResultImpl, contract?: Contract<ApiType>) {
super(result);
this.contract = contract;
}
}
// NOTE Experimental, POC, bound to change
export default class Blueprint<ApiType extends ApiTypes> extends BaseWithTx<ApiType> {
public readonly codeHash: Hash;
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, codeHash: string | Hash) {
super(api, abi, decorateMethod);
this.codeHash = createType('Hash', codeHash);
}
public deployContract (constructorIndex = 0, endowment: number | BN, maxGas: number | BN, ...params: any[]): BlueprintCreate<ApiType> {
assert(!!this.abi.constructors[constructorIndex], `Specified constructor index ${constructorIndex} does not exist`);
return {
signAndSend: this.decorateMethod(
(account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription<ApiType> => {
return this.apiContracts
.create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params))
.signAndSend(account)
.pipe(map(this.createResult));
}
)
};
}
private createResult = (result: SubmittableResult): BlueprintCreateResult<ApiType> => {
let contract: Contract<ApiType> | undefined;
if (result.isFinalized) {
const record = result.findRecord('contract', 'Instantiated');
if (record) {
contract = new Contract<ApiType>(this.api, this.abi, this.decorateMethod, record.event.data[1] as Address);
}
}
return new BlueprintCreateResult(result, contract);
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiTypes, DecorateMethod, SubmittableResultImpl } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ApiObject, ContractABIPre } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { compactAddLength, u8aToU8a } from '@polkadot/util';
import Abi from '../Abi';
import Blueprint from './Blueprint';
import { BaseWithTx } from './util';
type CodePutCodeResultSubscription<ApiType extends ApiTypes> = Observable<CodePutCodeResult<ApiType>>;
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface CodePutCode<ApiType extends ApiTypes> {
signAndSend (account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription<ApiType>;
}
class CodePutCodeResult<ApiType extends ApiTypes> extends SubmittableResult {
public readonly blueprint?: Blueprint<ApiType>;
public constructor (result: SubmittableResultImpl, blueprint?: Blueprint<ApiType>) {
super(result);
this.blueprint = blueprint;
}
}
// NOTE Experimental, POC, bound to change
export default class Code<ApiType extends ApiTypes> extends BaseWithTx<ApiType> {
public readonly code: Uint8Array;
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, wasm: string | Uint8Array) {
super(api, abi, decorateMethod);
this.code = u8aToU8a(wasm);
}
public createBlueprint = (maxGas: number | BN): CodePutCode<ApiType> => {
return {
signAndSend: this.decorateMethod(
(account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription<ApiType> => this.apiContracts
.putCode(maxGas, compactAddLength(this.code))
.signAndSend(account)
.pipe(map(this.createResult))
)
};
}
private createResult = (result: SubmittableResultImpl): CodePutCodeResult<ApiType> => {
let blueprint: Blueprint<ApiType> | undefined;
if (result.isFinalized) {
const record = result.findRecord('contract', 'CodeStored');
if (record) {
blueprint = new Blueprint<ApiType>(this.api, this.abi, this.decorateMethod, record.event.data[0] as Hash);
}
}
return new CodePutCodeResult(result, blueprint);
}
}
@@ -0,0 +1,99 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiTypes, DecorateMethod, ObsInnerType } from '@polkadot/api/types';
import { AccountId, Address, ContractExecResult } from '@polkadot/types/interfaces';
import { IKeyringPair } from '@polkadot/types/types';
import { ApiObject, ContractABIMessage, ContractABIPre, ContractCallOutcome } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { createType } from '@polkadot/types';
import Abi from '../Abi';
import { formatData } from '../util';
import { BaseWithTxAndRpcCall } from './util';
type ContractCallTypes = 'tx' | 'rpc';
type ContractCallResultSubscription<ApiType extends ApiTypes, CallType extends ContractCallTypes> = ApiType extends 'rxjs'
? Observable<ContractCallResult<CallType>>
: Promise<ObsInnerType<ContractCallResult<CallType>>>;
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface ContractCall<ApiType extends ApiTypes, CallType extends ContractCallTypes> {
send (account: IKeyringPair | string | AccountId | Address): ContractCallResultSubscription<ApiType, CallType>;
}
export type ContractCallResult<CallType extends ContractCallTypes> = CallType extends 'rpc'
? Observable<ContractCallOutcome>
: Observable<SubmittableResult>;
export default class Contract<ApiType extends ApiTypes> extends BaseWithTxAndRpcCall<ApiType> {
public readonly address: Address;
public call (as: 'rpc', message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall<ApiType, 'rpc'>;
public call (as: 'tx', message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall<ApiType, 'tx'>;
public call<CallType extends ContractCallTypes> (as: CallType, message: string, value: BN | number, gasLimit: BN | number, ...params: any[]): ContractCall<ApiType, CallType> {
const { fn, def } = this.getMessage(message);
return {
send: this.decorateMethod(
as === 'rpc'
? (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'rpc'> => {
return this.rpcContractsCall(
createType('ContractCallRequest', {
origin: account,
dest: this.address.toString(),
value,
gasLimit,
inputData: fn(...params)
})
)
.pipe(
map((result: ContractExecResult): ContractCallOutcome =>
this.createOutcome(result, createType('AccountId', account), def, params)
)
);
}
: (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'tx'> => {
return this.apiContracts
.call(this.address, value, gasLimit, fn(...params))
.signAndSend(account);
}
)
};
}
private createOutcome (result: ContractExecResult, origin: AccountId, message: ContractABIMessage, params: any[]): ContractCallOutcome {
let output: string;
if (result.isSuccess) {
const { data } = result.asSuccess;
output = message.returnType
? formatData(data, message.returnType).toString()
: data.toHex();
} else {
output = 'Error';
}
const outcome = {
time: Date.now(),
message,
origin,
params,
result: result,
success: result.isSuccess,
output: output && output.length ? output : '()'
};
return outcome;
}
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, address: string | AccountId | Address) {
super(api, abi, decorateMethod);
this.address = createType('Address', address);
}
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2017-2019 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as Blueprint } from './Blueprint';
export { default as Code } from './Code';
export { default as Contract } from './Contract';
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2017-2019 @polkadot/api authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiTypes, DecorateMethod, DecoratedRpc, SubmittableModuleExtrinsics } from '@polkadot/api/types';
import { ApiObject, ContractABIMessage, ContractABIPre, ContractBase, ContractMessage } from '../types';
import { RpcInterface } from '@polkadot/rpc-core/jsonrpc.types';
import { assert, stringCamelCase } from '@polkadot/util';
import Abi from '../Abi';
export abstract class Base<ApiType extends ApiTypes> implements ContractBase<ApiType> {
public readonly abi: Abi;
public readonly api: ApiObject<ApiType>;
public readonly decorateMethod: DecorateMethod<ApiType>;
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>) {
this.abi = abi instanceof Abi
? abi
: new Abi(abi);
this.api = api;
this.decorateMethod = decorateMethod;
}
public get messages (): ContractMessage[] {
return this.abi.abi.contract.messages
.map(
(def: ContractABIMessage, index): ContractMessage => ({
index,
def,
fn: this.abi.messages[def.name] || this.abi.messages[stringCamelCase(name)]
})
);
}
public getMessage (nameOrIndex?: string | number): ContractMessage {
const index = nameOrIndex
? typeof nameOrIndex === 'number'
? nameOrIndex
: this.abi.abi.contract.messages.findIndex((message): boolean => nameOrIndex === message.name || nameOrIndex === stringCamelCase(message.name))
: 0;
const def = this.abi.abi.contract.messages[index];
assert(!!def, `Attempted to access a contract message that does not exist: ${name}`);
const fn = this.abi.messages[def.name] || this.abi.messages[stringCamelCase(def.name)];
return { index, def, fn };
}
}
export abstract class BaseWithTx<ApiType extends ApiTypes> extends Base<ApiType> {
protected get apiContracts (): SubmittableModuleExtrinsics<'rxjs'> {
return this.api.rx.tx.contracts;
}
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>) {
super(api, abi, decorateMethod);
assert(this.api.rx.tx.contracts && this.api.rx.tx.contracts.putCode, 'You need to connect to a node with the contracts module, the metadata does not enable api.tx.contracts on this instance');
}
}
export abstract class BaseWithTxAndRpcCall<ApiType extends ApiTypes> extends BaseWithTx<ApiType> {
protected get rpcContractsCall (): DecoratedRpc<'rxjs', RpcInterface>['contracts']['call'] {
return this.api.rx.rpc.contracts.call;
}
public constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>) {
super(api, abi, decorateMethod);
assert(this.api.rx.rpc.contracts && this.api.rx.tx.contracts.call, 'You need to connect to a node with the contracts module, the metadata does not enable api.rpc.contracts.call on this instance');
}
}
+2 -3
View File
@@ -3,6 +3,5 @@
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as Abi } from './Abi';
export { default as RxBlueprint } from './RxBlueprint';
export { default as RxCode } from './RxCode';
export { default as RxContract } from './RxContract';
export * from './rx';
export * from './promise';
-20
View File
@@ -1,20 +0,0 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Constructor } from '@polkadot/types/types';
import { ContractABIFnArg } from './types';
import { createClass, encodeType } from '@polkadot/types';
export function createArgClass (args: ContractABIFnArg[], baseDef: Record<string, string>): Constructor {
return createClass(
JSON.stringify(
args.reduce((base: Record<string, any>, { name, type }): Record<string, any> => {
base[name] = encodeType(type);
return base;
}, baseDef)
)
);
}
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Hash } from '@polkadot/types/interfaces';
import { ContractABIPre } from '../types';
import { ApiPromise } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/promise';
import Abi from '../Abi';
import Blueprint from '../base/Blueprint';
export default class PromiseBlueprint extends Blueprint<'promise'> {
public constructor (api: ApiPromise, abi: ContractABIPre | Abi, codeHash: string | Hash) {
super(api, abi, decorateMethod, codeHash);
}
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ContractABIPre } from '../types';
import { ApiPromise } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/promise';
import Abi from '../Abi';
import Code from '../base/Code';
export default class PromiseCode extends Code<'promise'> {
public constructor (api: ApiPromise, abi: ContractABIPre | Abi, wasm: string | Uint8Array) {
super(api, abi, decorateMethod, wasm);
}
}
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { ContractABIPre } from '../types';
import { ApiPromise } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/promise';
import Abi from '../Abi';
import Contract from '../base/Contract';
export default class PromiseContract extends Contract<'promise'> {
public constructor (api: ApiPromise, abi: ContractABIPre | Abi, address: string | AccountId) {
super(api, abi, decorateMethod, address);
}
}
@@ -0,0 +1,7 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as PromiseBlueprint } from './PromiseBlueprint';
export { default as PromiseCode } from './PromiseCode';
export { default as PromiseContract } from './PromiseContract';
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Hash } from '@polkadot/types/interfaces';
import { ContractABIPre } from '../types';
import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Blueprint from '../base/Blueprint';
export default class RxBlueprint extends Blueprint<'rxjs'> {
public constructor (api: ApiRx, abi: ContractABIPre | Abi, codeHash: string | Hash) {
super(api, abi, decorateMethod, codeHash);
}
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ContractABIPre } from '../types';
import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Code from '../base/Code';
export default class RxCode extends Code<'rxjs'> {
public constructor (api: ApiRx, abi: ContractABIPre | Abi, wasm: string | Uint8Array) {
super(api, abi, decorateMethod, wasm);
}
}
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId } from '@polkadot/types/interfaces';
import { ContractABIPre } from '../types';
import { ApiRx } from '@polkadot/api';
import { decorateMethod } from '@polkadot/api/rx';
import Abi from '../Abi';
import Contract from '../base/Contract';
export default class RxContract extends Contract<'rxjs'> {
public constructor (api: ApiRx, abi: ContractABIPre | Abi, address: string | AccountId) {
super(api, abi, decorateMethod, address);
}
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2017-2019 @polkadot/api-contract authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as RxBlueprint } from './RxBlueprint';
export { default as RxCode } from './RxCode';
export { default as RxContract } from './RxContract';
+46 -25
View File
@@ -2,17 +2,26 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { SubmittableModuleExtrinsics } from '@polkadot/api/types';
import { Address } from '@polkadot/types/interfaces';
import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import { AccountId, Address, ContractExecResult } from '@polkadot/types/interfaces';
import { CodecArg } from '@polkadot/types/types';
import { MetaRegistryJson, StringIndex, TypeIndex, TypeDef } from '@polkadot/types/codec/create/types';
// import { Observable } from 'rxjs';
import { ApiPromise, ApiRx } from '@polkadot/api';
export type ApiObject<ApiType> = ApiType extends 'rxjs'
export type ApiObject<ApiType extends ApiTypes> = ApiType extends 'rxjs'
? ApiRx
: ApiPromise;
export interface ContractBase<ApiType extends ApiTypes> {
readonly abi: InterfaceAbi;
readonly api: ApiObject<ApiType>;
readonly decorateMethod: DecorateMethod<ApiType>;
getMessage: (name: string) => ContractMessage;
messages: ContractMessage[];
}
export interface ContractABITypePre {
ty: TypeIndex;
display_name: StringIndex[];
@@ -28,31 +37,31 @@ export interface ContractABIArgBase {
type: TypeDef;
}
export type ContractABIMethodArgPre = ContractABIArgBasePre;
export type ContractABIMessageArgPre = ContractABIArgBasePre;
export type ContractABIMethodArg = ContractABIArgBase;
export type ContractABIMessageArg = ContractABIArgBase;
export interface ContractABIMethodBase {
args: ContractABIMethodArg[];
export interface ContractABIMessageBase {
args: ContractABIMessageArg[];
}
export interface ContractABIMethodBasePre {
args: ContractABIMethodArgPre[];
export interface ContractABIMessageBasePre {
args: ContractABIMessageArgPre[];
}
export interface ContractABIMethodCommon {
export interface ContractABIMessageCommon {
docs?: ContractABIDocs;
mutates?: boolean;
selector: number;
selector: string | number;
}
export interface ContractABIMethodPre extends ContractABIMethodCommon, ContractABIMethodBasePre {
export interface ContractABIMessagePre extends ContractABIMessageCommon, ContractABIMessageBasePre {
name: StringIndex;
return_type: ContractABITypePre | null;
}
export interface ContractABIMethod extends ContractABIMethodCommon {
args: ContractABIMethodArg[];
export interface ContractABIMessage extends ContractABIMessageCommon {
args: ContractABIMessageArg[];
name: string;
returnType: TypeDef | null;
}
@@ -62,16 +71,16 @@ export interface ContractABIContractCommon {
}
export interface ContractABIContractPre extends ContractABIContractCommon {
constructors: ContractABIMethodPre[];
messages: ContractABIMethodPre[];
constructors: ContractABIMessagePre[];
messages: ContractABIMessagePre[];
name: StringIndex;
events?: ContractABIEventPre[];
docs?: ContractABIDocs;
}
export interface ContractABIContract extends ContractABIContractCommon {
constructors: ContractABIMethod[];
messages: ContractABIMethod[];
constructors: ContractABIMessage[];
messages: ContractABIMessage[];
name: string;
events?: ContractABIEvent[];
docs?: ContractABIDocs;
@@ -162,20 +171,22 @@ export type ContractABIStoragePre = ContractABIStorageStructPre;
export type ContractABIStorage = ContractABIStorageStruct;
export interface ContractMessage {
index: number;
fn: ContractABIFn;
def: ContractABIMessage;
}
export type AbiConstructors = ContractABIFn[];
export type AbiMessages = Record<string, ContractABIFn>;
export interface InterfaceAbi {
readonly abi: ContractABI;
readonly constructors: ContractABIFn[];
readonly constructors: AbiConstructors;
readonly messages: AbiMessages;
}
export interface ContractBase<ApiType> {
readonly abi: InterfaceAbi;
readonly api: ApiObject<ApiType>;
readonly apiContracts: SubmittableModuleExtrinsics<ApiType>;
}
export interface InterfaceContractCalls {
[index: string]: Function;
}
@@ -184,3 +195,13 @@ export interface InterfaceContract {
readonly address: Address;
readonly calls: InterfaceContractCalls;
}
export interface ContractCallOutcome {
time: number;
result: ContractExecResult;
origin: AccountId;
output: string;
params: any[];
success: boolean;
message: ContractABIMessage;
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2017-2019 @polkadot/rpc-core authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Codec, TypeDefInfo, TypeDef } from '@polkadot/types/types';
import { Bytes, Option, createClass } from '@polkadot/types';
import { createType, createTypeUnsafe } from '@polkadot/types/codec';
import { compactStripLength } from '@polkadot/util';
export function formatData (value: Bytes, { info, type }: TypeDef): Codec {
const input = compactStripLength(value.toU8a(true))[1];
const data = createType('Data', input);
if (info === TypeDefInfo.Option) {
return new Option(
createClass(type),
createTypeUnsafe(type, [data], true)
);
}
return createTypeUnsafe(type, [data], true);
}
@@ -0,0 +1,188 @@
[
{
"info": 9,
"type": "{ \"value\": \"{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"type": "u8"
},
"index": 0
}
],
"name": "cell",
"namespace": "ink_core::storage::key"
}
],
"name": "cell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"type": "u32"
}
]
}
],
"name": "value",
"namespace": "ink_core::storage::value",
"params": [
{
"info": 6,
"type": "u32"
}
]
}
],
"name": "Incrementer",
"namespace": "incrementer::normal"
},
{
"info": 9,
"type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"type": "u8"
},
"index": 0
}
],
"name": "cell",
"namespace": "ink_core::storage::key"
}
],
"name": "cell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"type": "u32"
}
]
}
],
"name": "Value",
"namespace": "ink_core::storage::value",
"params": [
{
"info": 6,
"type": "u32"
}
]
},
{
"info": 6,
"type": "u32"
},
{
"info": 9,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"type": "u8"
},
"index": 0
}
],
"name": "cell",
"namespace": "ink_core::storage::key"
}
],
"name": "SyncCell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"type": "u32"
}
]
},
{
"info": 10,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"type": "u8"
},
"index": 0
}
],
"name": "Key",
"namespace": "ink_core::storage::key"
},
{
"info": 12,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"type": "u8"
}
},
{
"info": 6,
"type": "u8"
},
{
"info": 6,
"type": "bool"
}
]
@@ -1 +0,0 @@
{"name":"Incrementer","deploy":{"args":[{"name":"init_value","type":"u32"}]},"messages":[{"name":"inc","selector":257544423,"mutates":true,"args":[{"name":"by","type":"u32"}],"return_type":null},{"name":"get","selector":4266279973,"mutates":false,"args":[],"return_type":"u32"},{"name":"compare","selector":363906316,"mutates":false,"args":[{"name":"with","type":"u32"}],"return_type":"bool"}]}
@@ -0,0 +1,233 @@
{
"registry": {
"strings": [
"Incrementer",
"incrementer",
"normal",
"value",
"Value",
"ink_core",
"storage",
"cell",
"SyncCell",
"sync_cell",
"Key",
"key",
"on_deploy",
"init_value",
"u32",
"inc",
"by",
"get",
"compare",
"with",
"bool"
],
"types": [
{
"id": {
"custom.name": 1,
"custom.namespace": [
2,
3
],
"custom.params": []
},
"def": {
"struct.fields": [
{
"name": 4,
"type": 2
}
]
}
},
{
"id": {
"custom.name": 5,
"custom.namespace": [
6,
7,
4
],
"custom.params": [
3
]
},
"def": {
"struct.fields": [
{
"name": 8,
"type": 4
}
]
}
},
{
"id": "u32",
"def": "builtin"
},
{
"id": {
"custom.name": 9,
"custom.namespace": [
6,
7,
8,
10
],
"custom.params": [
3
]
},
"def": {
"struct.fields": [
{
"name": 8,
"type": 5
}
]
}
},
{
"id": {
"custom.name": 11,
"custom.namespace": [
6,
7,
12
],
"custom.params": []
},
"def": {
"tuple_struct.types": [
6
]
}
},
{
"id": {
"array.len": 32,
"array.type": 7
},
"def": "builtin"
},
{
"id": "u8",
"def": "builtin"
},
{
"id": "bool",
"def": "builtin"
}
]
},
"storage": {
"struct.type": 1,
"struct.fields": [
{
"name": 4,
"layout": {
"struct.type": 2,
"struct.fields": [
{
"name": 8,
"layout": {
"range.offset": "0x0000000000000000000000000000000000000000000000000000000000000000",
"range.len": 1,
"range.elem_type": 3
}
}
]
}
}
]
},
"contract": {
"name": 1,
"constructors": [
{
"name": 13,
"selector": "[\"0x00\",\"0x00\",\"0x00\",\"0x00\"]",
"args": [
{
"name": 14,
"type": {
"ty": 3,
"display_name": [
15
]
}
}
],
"docs": [
"Initializes the value to the initial value."
]
}
],
"messages": [
{
"name": 16,
"selector": "[\"0x0F\",\"0x59\",\"0xD0\",\"0xE7\"]",
"mutates": true,
"args": [
{
"name": 17,
"type": {
"ty": 3,
"display_name": [
15
]
}
}
],
"return_type": null,
"docs": [
"Flips the current state of our smart contract."
]
},
{
"name": 18,
"selector": "[\"0xFE\",\"0x4A\",\"0x44\",\"0x25\"]",
"mutates": false,
"args": [],
"return_type": {
"ty": 3,
"display_name": [
15
]
},
"docs": [
"Returns the current state."
]
},
{
"name": 19,
"selector": "[\"0x15\",\"0xB0\",\"0xC5\",\"0x0C\"]",
"mutates": false,
"args": [
{
"name": 20,
"type": {
"ty": 3,
"display_name": [
15
]
}
}
],
"return_type": {
"ty": 8,
"display_name": [
21
]
},
"docs": [
"Returns `true` if the internal value is greater than or equal to the provided value."
]
}
],
"events": [],
"docs": []
}
}