From 9f60a8cd5f44d6f0e275d9f2f508178f58ccf02a Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 31 Oct 2020 13:19:57 +0100 Subject: [PATCH] Add decoded contract events to execution (#2812) * Cleanup assignments * Small adjustments * Add decoded contract events to execution * Adjust all applyOnEvent --- packages/api-contract/src/base/Blueprint.ts | 2 +- packages/api-contract/src/base/Code.ts | 2 +- packages/api-contract/src/base/Contract.ts | 40 +++++++++++++++++---- packages/api-contract/src/util.ts | 10 +++--- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/api-contract/src/base/Blueprint.ts b/packages/api-contract/src/base/Blueprint.ts index 04d58154c1..a01545b18f 100644 --- a/packages/api-contract/src/base/Blueprint.ts +++ b/packages/api-contract/src/base/Blueprint.ts @@ -56,7 +56,7 @@ export default class Blueprint extends Base { return this.api.tx.contracts .instantiate(endowment, gasLimit, this.codeHash, this.abi.findConstructor(constructorOrId).toU8a(params)) .withResultTransform((result: ISubmittableResult) => - new BlueprintSubmittableResult(result, applyOnEvent(result, 'Instantiated', (record: EventRecord) => + new BlueprintSubmittableResult(result, applyOnEvent(result, 'Instantiated', ([record]: EventRecord[]) => new Contract(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod) )) ); diff --git a/packages/api-contract/src/base/Code.ts b/packages/api-contract/src/base/Code.ts index e0a02c4301..ef61241f70 100644 --- a/packages/api-contract/src/base/Code.ts +++ b/packages/api-contract/src/base/Code.ts @@ -38,7 +38,7 @@ export default class Code extends Base { return this.api.tx.contracts .putCode(compactAddLength(this.code)) .withResultTransform((result: ISubmittableResult) => - new CodeSubmittableResult(result, applyOnEvent(result, 'CodeStored', (record: EventRecord) => + new CodeSubmittableResult(result, applyOnEvent(result, 'CodeStored', ([record]: EventRecord[]) => new Blueprint(this.api, this.abi, record.event.data[0] as Hash, this._decorateMethod) )) ); diff --git a/packages/api-contract/src/base/Contract.ts b/packages/api-contract/src/base/Contract.ts index 4bf2e2f2cd..4287c1cdc9 100644 --- a/packages/api-contract/src/base/Contract.ts +++ b/packages/api-contract/src/base/Contract.ts @@ -3,21 +3,33 @@ import { ApiTypes, DecorateMethod } from '@polkadot/api/types'; import { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; -import { AccountId, ContractExecResult } from '@polkadot/types/interfaces'; -import { AnyJson, CodecArg, Registry } from '@polkadot/types/types'; -import { AbiMessage, ContractCallOutcome } from '../types'; +import { AccountId, ContractExecResult, EventRecord } from '@polkadot/types/interfaces'; +import { AnyJson, CodecArg, ISubmittableResult, Registry } from '@polkadot/types/types'; +import { AbiMessage, ContractCallOutcome, DecodedEvent } from '../types'; import { ContractRead, MapMessageExec, MapMessageRead } from './types'; import BN from 'bn.js'; import { map } from 'rxjs/operators'; +import { SubmittableResult } from '@polkadot/api'; import ApiBase from '@polkadot/api/base'; -import { assert, bnToBn, isFunction, isUndefined, stringCamelCase } from '@polkadot/util'; +import { assert, bnToBn, isFunction, isUndefined, logger, stringCamelCase } from '@polkadot/util'; import Abi from '../Abi'; -import { formatData } from '../util'; +import { applyOnEvent, 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.'; +const l = logger('Contract'); + +export class ContractSubmittableResult extends SubmittableResult { + public readonly contractEvents?: DecodedEvent[]; + + constructor (result: ISubmittableResult, contractEvents?: DecodedEvent[]) { + super(result); + + this.contractEvents = contractEvents; + } +} // map from a JSON result to current-style ContractExecResult function mapExecResult (registry: Registry, json: Record): ContractExecResult { @@ -96,7 +108,23 @@ export default class Contract extends Base { } #exec = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): SubmittableExtrinsic => { - return this.api.tx.contracts.call(this.address, value, this.#getGas(gasLimit), this.abi.findMessage(messageOrId).toU8a(params)); + return this.api.tx.contracts + .call(this.address, value, this.#getGas(gasLimit), this.abi.findMessage(messageOrId).toU8a(params)) + .withResultTransform((result: ISubmittableResult) => + new ContractSubmittableResult(result, applyOnEvent(result, 'ContractExecution', (records: EventRecord[]) => + records + .map(({ event: { data: [, data] } }): DecodedEvent | null => { + try { + return this.abi.decodeEvent(data.toU8a()); + } catch (error) { + l.error(`Unable to decode contract event: ${(error as Error).message}`); + + return null; + } + }) + .filter((decoded): decoded is DecodedEvent => !!decoded) + )) + ); } #read = (messageOrId: AbiMessage | string | number, value: BigInt | BN | string | number, gasLimit: BigInt | BN | string | number, params: CodecArg[]): ContractRead => { diff --git a/packages/api-contract/src/util.ts b/packages/api-contract/src/util.ts index e7de80ba78..5c56721978 100644 --- a/packages/api-contract/src/util.ts +++ b/packages/api-contract/src/util.ts @@ -7,16 +7,18 @@ import { Codec, Registry, TypeDef } from '@polkadot/types/types'; import { Raw, createTypeUnsafe } from '@polkadot/types'; +type ContractEvents = 'CodeStored' | 'ContractExecution' | 'Instantiated'; + export function formatData (registry: Registry, data: Raw, { type }: TypeDef): Codec { return createTypeUnsafe(registry, type, [data], true); } -export function applyOnEvent (result: SubmittableResult, type: 'CodeStored' | 'Instantiated', fn: (record: EventRecord) => T): T | undefined { +export function applyOnEvent (result: SubmittableResult, type: ContractEvents, fn: (records: EventRecord[]) => T): T | undefined { if (result.isInBlock || result.isFinalized) { - const record = result.findRecord('contracts', type); + const records = result.filterRecords('contracts', type); - if (record) { - return fn(record); + if (records.length) { + return fn(records); } }