Updated ABI definitions & parsing (#1662)

* Updated ABI definitions & parsing

* Run type generation

* Definitions (should be ok-ish)

* Cleanup names

* Allow aliasses on structs (JSON parsing)

* Cleanup json mapping passing

* s/types/lookup/

* Parsing as per current output

* InkSelector

* MtRegistryIndex

* String lookup tests

* Remove serd ovverride comments (it works as-is)

* inkRegistry tests

* Extract primitive types

* s/ty/id/

* Start of parsing into types (actually to be TypeDef)

* Parsing

* stringify struct fields

* Small cleanups

* Add type index to project lookup

* Prefix with index

* Fix interface generation

* Latest contract metadata (#2118)

* Add new (provisional) incrementer contract metadata

* Update and regenerate type definitions

* WIP: generate new format scale-info types

* WIP: implementing variants

* Implement variants

* Fix up type resolution

* Fix registry spec tests

* Fix lints and some tests

* Fix inkTypes tests

* Fix some lints

* Make imports same line like original

* Comment out (temporarily) sanitize import to make CI pass

* Sanitize

* Remove indices

* Remove index

* Adjust ContractAbi interface (empty rpc)

* linting

* Pull in Andrew's changes

* Cleanups

* Some more cleanups

* Update with v3 contracts

* Adjust with actual types

* Regenerate

* Update ABI parsing (#2695)

* Adjust ABI code

* Cleanup

* Merge non-code

* Type naming

* Simplify Base

* Rename 1

* rename 2

* Rename 3

* Simplify

* Add index to AbiMessage

* Pass actual message or index for call

* Rename param

* Simplify

* Expose call & exec

* s/call/read/

* Cleanup asserts

* AccountId as per spec

* displayName is path

* Update definitions

* Flatten ABIType into TypeDef

* Rewrite

* Fix tests

* Updates

Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
Jaco Greeff
2020-10-09 18:33:31 +02:00
committed by GitHub
co-authored by Andrew Jones
parent 89e0548092
commit 453dd9e49a
61 changed files with 6978 additions and 11163 deletions
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2017-2020 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod, SubmittableModuleExtrinsics } from '@polkadot/api/types';
import { AnyJson, Registry } from '@polkadot/types/types';
import { ApiObject } from '../types';
import { assert } from '@polkadot/util';
import Abi from '../Abi';
export default abstract class Base<ApiType extends ApiTypes> {
public readonly abi: Abi;
public readonly api: ApiObject<ApiType>;
public readonly registry: Registry;
protected readonly _decorateMethod: DecorateMethod<ApiType>;
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>) {
this.abi = abi instanceof Abi
? abi
: new Abi(api.registry, abi);
this.api = api;
this.registry = api.registry;
this._decorateMethod = 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');
}
protected get _apiContracts (): SubmittableModuleExtrinsics<'rxjs'> {
return this.api.rx.tx.contracts;
}
}
+18 -26
View File
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { ApiObject, ContractABIPre } from '../types';
import { AccountId, EventRecord, Hash } from '@polkadot/types/interfaces';
import { AnyJson, IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { ApiObject } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
@@ -13,14 +13,15 @@ import { SubmittableResult } from '@polkadot/api';
import { assert } from '@polkadot/util';
import Abi from '../Abi';
import Base from './Base';
import Contract from './Contract';
import { BaseWithTx } from './util';
import { applyOnEvent } from './util';
// eslint-disable-next-line no-use-before-define
type BlueprintCreateResultSubscription<ApiType extends ApiTypes> = Observable<BlueprintCreateResult<ApiType>>;
export interface BlueprintCreate<ApiType extends ApiTypes> {
signAndSend (account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription<ApiType>;
signAndSend (account: IKeyringPair | string | AccountId): BlueprintCreateResultSubscription<ApiType>;
}
class BlueprintCreateResult<ApiType extends ApiTypes> extends SubmittableResult {
@@ -34,10 +35,10 @@ class BlueprintCreateResult<ApiType extends ApiTypes> extends SubmittableResult
}
// NOTE Experimental, POC, bound to change
export default class Blueprint<ApiType extends ApiTypes> extends BaseWithTx<ApiType> {
export default class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly codeHash: Hash;
constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, codeHash: string | Hash) {
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>, codeHash: string | Hash) {
super(api, abi, decorateMethod);
this.codeHash = this.registry.createType('Hash', codeHash);
@@ -48,28 +49,19 @@ export default class Blueprint<ApiType extends ApiTypes> extends BaseWithTx<ApiT
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
signAndSend: this.decorateMethod(
(account: IKeyringPair | string | AccountId | Address): BlueprintCreateResultSubscription<ApiType> => {
return this._apiContracts
signAndSend: this._decorateMethod(
(account: IKeyringPair | string | AccountId): BlueprintCreateResultSubscription<ApiType> =>
this._apiContracts
.create(endowment, maxGas, this.codeHash, this.abi.constructors[constructorIndex](...params))
.signAndSend(account)
.pipe(map(this._createResult));
}
.pipe(
map((result) =>
new BlueprintCreateResult(result, applyOnEvent(result, 'Instantiated', (record: EventRecord) =>
new Contract<ApiType>(this.api, this.abi, this._decorateMethod, record.event.data[1] as AccountId)
))
)
)
)
};
}
private _createResult = (result: SubmittableResult): BlueprintCreateResult<ApiType> => {
let contract: Contract<ApiType> | undefined;
if (result.isInBlock) {
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);
}
}
+16 -23
View File
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import { AccountId, Address, Hash } from '@polkadot/types/interfaces';
import { IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { ApiObject, ContractABIPre } from '../types';
import { AccountId, Address, EventRecord, Hash } from '@polkadot/types/interfaces';
import { AnyJson, IKeyringPair, ISubmittableResult } from '@polkadot/types/types';
import { ApiObject } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
@@ -13,8 +13,9 @@ import { SubmittableResult } from '@polkadot/api';
import { compactAddLength, u8aToU8a } from '@polkadot/util';
import Abi from '../Abi';
import Base from './Base';
import Blueprint from './Blueprint';
import { BaseWithTx } from './util';
import { applyOnEvent } from './util';
// eslint-disable-next-line no-use-before-define
type CodePutCodeResultSubscription<ApiType extends ApiTypes> = Observable<CodePutCodeResult<ApiType>>;
@@ -34,10 +35,10 @@ class CodePutCodeResult<ApiType extends ApiTypes> extends SubmittableResult {
}
// NOTE Experimental, POC, bound to change
export default class Code<ApiType extends ApiTypes> extends BaseWithTx<ApiType> {
export default class Code<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly code: Uint8Array;
constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, wasm: string | Uint8Array) {
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>, wasm: Uint8Array | string) {
super(api, abi, decorateMethod);
this.code = u8aToU8a(wasm);
@@ -46,27 +47,19 @@ export default class Code<ApiType extends ApiTypes> extends BaseWithTx<ApiType>
public createBlueprint = (maxGas: number | BN): CodePutCode<ApiType> => {
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
signAndSend: this.decorateMethod(
(account: IKeyringPair | string | AccountId | Address): CodePutCodeResultSubscription<ApiType> =>
signAndSend: this._decorateMethod(
(account: IKeyringPair | string | AccountId): CodePutCodeResultSubscription<ApiType> =>
this._apiContracts
.putCode(maxGas, compactAddLength(this.code))
.signAndSend(account)
.pipe(map(this._createResult))
.pipe(
map((result) =>
new CodePutCodeResult(result, applyOnEvent(result, 'CodeStored', (record: EventRecord) =>
new Blueprint<ApiType>(this.api, this.abi, this._decorateMethod, record.event.data[0] as Hash)
))
)
)
)
};
}
private _createResult = (result: ISubmittableResult): CodePutCodeResult<ApiType> => {
let blueprint: Blueprint<ApiType> | undefined;
if (result.isInBlock) {
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);
}
}
+75 -48
View File
@@ -2,17 +2,20 @@
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod, ObsInnerType } from '@polkadot/api/types';
import { AccountId, Address, ContractExecResult } from '@polkadot/types/interfaces';
import { Codec, CodecArg, IKeyringPair } from '@polkadot/types/types';
import { ApiObject, ContractABIMessage, ContractABIPre, ContractCallOutcome } from '../types';
import { AccountId, ContractExecResult } from '@polkadot/types/interfaces';
import { AnyJson, Codec, CodecArg, IKeyringPair } from '@polkadot/types/types';
import { ApiObject, AbiMessage, ContractCallOutcome } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SubmittableResult } from '@polkadot/api';
import { assert, isFunction, isNumber } from '@polkadot/util';
import Abi from '../Abi';
import { formatData } from '../util';
import { BaseWithTxAndRpcCall } from './util';
import Base from './Base';
import { SignerOptions } from '@polkadot/api/submittable/types';
type ContractCallTypes = 'tx' | 'rpc';
@@ -22,71 +25,95 @@ type ContractCallResultSubscription<ApiType extends ApiTypes, CallType extends C
// eslint-disable-next-line no-use-before-define
: Promise<ObsInnerType<ContractCallResult<CallType>>>;
export interface ContractCall<ApiType extends ApiTypes, CallType extends ContractCallTypes> {
send (account: IKeyringPair | string | AccountId | Address): ContractCallResultSubscription<ApiType, CallType>;
export interface ContractExec<ApiType extends ApiTypes> {
signAndSend (account: IKeyringPair | string | AccountId, options?: Partial<SignerOptions>): ContractCallResultSubscription<ApiType, 'tx'>;
}
export interface ContractRead<ApiType extends ApiTypes> {
send (account: IKeyringPair | string | AccountId): ContractCallResultSubscription<ApiType, 'rpc'>;
}
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;
export default class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
public readonly address: AccountId;
constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>, address: string | AccountId | Address) {
constructor (api: ApiObject<ApiType>, abi: AnyJson | Abi, decorateMethod: DecorateMethod<ApiType>, address: string | AccountId) {
super(api, abi, decorateMethod);
this.address = this.registry.createType('Address', address);
this.address = this.registry.createType('AccountId', address);
}
public call (as: 'rpc', message: string, value: BN | number, gasLimit: BN | number, ...params: CodecArg[]): ContractCall<ApiType, 'rpc'>;
public call (as: 'tx', message: string, value: BN | number, gasLimit: BN | number, ...params: CodecArg[]): ContractCall<ApiType, 'tx'>;
public call<CallType extends ContractCallTypes> (as: CallType, message: string, value: BN | number, gasLimit: BN | number, ...params: CodecArg[]): ContractCall<ApiType, CallType> {
const { def, fn } = this.getMessage(message);
public get hasRpcContractsCall (): boolean {
return isFunction(this.api.rx.rpc.contracts?.call);
}
private _createInput (message: AbiMessage | number, params: CodecArg[]): [Uint8Array, AbiMessage] {
const fn = isNumber(message)
? this.abi.messages[message]
: message;
assert(fn, 'Attempted to call an invalid contract message');
return [fn(...params), fn];
}
public exec = (message: AbiMessage | number, value: BN | string | number, gasLimit: BN | string | number, ...params: CodecArg[]): ContractExec<ApiType> => {
const [inputData] = this._createInput(message, params);
return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this.decorateMethod(
as === 'rpc' && this.hasRpcContractsCall
? (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'rpc'> =>
this._rpcContractsCall(
this.registry.createType('ContractCallRequest', {
dest: this.address.toString(),
gasLimit,
inputData: fn(...params),
origin: account,
value
})
).pipe(map((result: ContractExecResult): ContractCallOutcome =>
this._createOutcome(result, this.registry.createType('AccountId', account), def, params)
))
: (account: IKeyringPair | string | AccountId | Address): ContractCallResult<'tx'> =>
this._apiContracts
.call(this.address, value, gasLimit, fn(...params))
.signAndSend(account)
signAndSend: this._decorateMethod((account: IKeyringPair | string | AccountId, options?: Partial<SignerOptions>): ContractCallResult<'tx'> =>
this._apiContracts
.call(this.address, value, gasLimit, inputData)
.signAndSend(account, options)
)
};
}
private _createOutcome (result: ContractExecResult, origin: AccountId, message: ContractABIMessage, params: CodecArg[]): ContractCallOutcome {
let output: Codec | null = null;
public read = (message: AbiMessage | number, value: BN | string | number, gasLimit: BN | string | number, ...params: CodecArg[]): ContractRead<ApiType> => {
assert(this.hasRpcContractsCall, 'Your node does not support contract RPC read calls');
if (result.isSuccess) {
const { data } = result.asSuccess;
output = message.returnType
? formatData(this.registry, data, message.returnType)
: this.registry.createType('Raw', data);
}
const [inputData, messageFn] = this._createInput(message, params);
return {
isSuccess: result.isSuccess,
message,
origin,
output,
params,
result,
time: Date.now()
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((account: IKeyringPair | string | AccountId): ContractCallResult<'rpc'> =>
this.api.rx.rpc.contracts.call(
this.registry.createType('ContractCallRequest', {
dest: this.address,
gasLimit,
inputData,
origin: account,
value
})
).pipe(
map((result: ContractExecResult): ContractCallOutcome => {
let output: Codec | null = null;
if (result.isSuccess) {
const { data } = result.asSuccess;
output = messageFn.returnType
? formatData(this.registry, data, messageFn.returnType)
: this.registry.createType('Raw', data);
}
return {
isSuccess: result.isSuccess,
message: messageFn,
origin: this.registry.createType('AccountId', account),
output,
params,
result,
time: Date.now(),
type: messageFn.returnType || null
};
})
)
)
};
}
}
+9 -70
View File
@@ -1,78 +1,17 @@
// Copyright 2017-2020 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiTypes, DecorateMethod, DecoratedRpc, SubmittableModuleExtrinsics } from '@polkadot/api/types';
import { RpcInterface } from '@polkadot/rpc-core/types';
import { Registry } from '@polkadot/types/types';
import { ApiObject, ContractABIMessage, ContractABIPre, ContractBase, ContractMessage } from '../types';
import { SubmittableResult } from '@polkadot/api';
import { EventRecord } from '@polkadot/types/interfaces';
import { assert, isFunction, stringCamelCase } from '@polkadot/util';
import Abi from '../Abi';
export function applyOnEvent <T> (result: SubmittableResult, type: 'CodeStored' | 'Instantiated', fn: (record: EventRecord) => T): T | undefined {
if (result.isInBlock || result.isFinalized) {
const record = result.findRecord('contract', 'CodeStored');
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 readonly registry: Registry;
constructor (api: ApiObject<ApiType>, abi: ContractABIPre | Abi, decorateMethod: DecorateMethod<ApiType>) {
this.abi = abi instanceof Abi
? abi
: new Abi(api.registry, abi);
this.registry = api.registry;
this.api = api;
this.decorateMethod = decorateMethod;
if (record) {
return fn(record);
}
}
public get messages (): ContractMessage[] {
return this.abi.abi.contract.messages.map(
(def: ContractABIMessage, index): ContractMessage => ({
def,
fn: this.abi.messages[def.name] || this.abi.messages[stringCamelCase(name)],
index
})
);
}
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: ${typeof nameOrIndex === 'number' ? `index ${nameOrIndex}` : (nameOrIndex || 'unknown')}`);
const fn = this.abi.messages[def.name] || this.abi.messages[stringCamelCase(def.name)];
return { def, fn, index };
}
}
export abstract class BaseWithTx<ApiType extends ApiTypes> extends Base<ApiType> {
protected get _apiContracts (): SubmittableModuleExtrinsics<'rxjs'> {
return this.api.rx.tx.contracts;
}
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> {
public get hasRpcContractsCall (): boolean {
return isFunction(this.api.rx.rpc.contracts?.call);
}
protected get _rpcContractsCall (): DecoratedRpc<'rxjs', RpcInterface>['contracts']['call'] {
assert(this.hasRpcContractsCall, 'You need to connect to a node with the contracts.call RPC method.');
return this.api.rx.rpc.contracts.call;
}
return undefined;
}