From 42e260c9a2b4c1d15974db528ab72a1fa0bdf09d Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 8 Feb 2023 17:51:37 -0500 Subject: [PATCH] typed events from client and misc --- _tasks/star.ts | 2 + codegen/frame/FrameCodegen.ts | 11 ++- codegen/frame/type.ts | 5 ++ examples/block_author.ts | 4 +- examples/block_events.ts | 5 +- examples/block_extrinsics.ts | 5 +- examples/ink_e2e/main.ts | 16 ++--- examples/watch.ts | 36 +++++----- examples/xcm/asset_teleportation.ts | 39 +++++------ fluent/ClientRune.ts | 14 ++-- fluent/SignedExtrinsicRune.ts | 2 +- patterns/consensus/babeBlockAuthor.ts | 2 +- patterns/consensus/preRuntimeDigest.ts | 2 +- patterns/ink/InkRune.ts | 47 +++++-------- patterns/ink/events.ts | 97 ++++---------------------- primitives/Event.ts | 60 +++------------- 16 files changed, 110 insertions(+), 237 deletions(-) diff --git a/_tasks/star.ts b/_tasks/star.ts index 66f211e8b..ae6df2370 100755 --- a/_tasks/star.ts +++ b/_tasks/star.ts @@ -83,3 +83,5 @@ for (const [file, deps] of entries.slice(1)) { done.add(d) } } + +console.log("Checked successfully") diff --git a/codegen/frame/FrameCodegen.ts b/codegen/frame/FrameCodegen.ts index 0293971b6..4e30c31f2 100644 --- a/codegen/frame/FrameCodegen.ts +++ b/codegen/frame/FrameCodegen.ts @@ -1,5 +1,5 @@ import { Metadata } from "../../frame_metadata/mod.ts" -import { Ty } from "../../scale_info/mod.ts" +import { SequenceTyDef, Ty } from "../../scale_info/mod.ts" import { codecs } from "./codecs.ts" import { File } from "./File.ts" import { pallet } from "./pallet.ts" @@ -39,7 +39,10 @@ export class FrameCodegen { .fromEntries(this.metadata.extrinsic.ty.params.map((x) => [x.name.toLowerCase(), x.ty])) .call! - const callTySrcPath = this.typeVisitor.visit(callTy) + const eventTy = (this.metadata + .pallets.find((x) => x.name === "System") + ?.storage?.entries.find((x) => x.name === "Events") + ?.value! as SequenceTyDef).typeParam let palletNamespaceExports = "" for (const p of this.metadata.pallets) { @@ -53,7 +56,9 @@ export class FrameCodegen { new File(` import * as C from "./capi.ts" import * as types from "./types/mod.ts" - export type Chain = C.Chain<${callTySrcPath}> + export type Chain = C.Chain<${this.typeVisitor.visit(callTy)}, ${ + this.typeVisitor.visit(eventTy) + }> export * from "./client.ts" export * as types from "./types/mod.ts" diff --git a/codegen/frame/type.ts b/codegen/frame/type.ts index a4f504959..e82b9bdca 100644 --- a/codegen/frame/type.ts +++ b/codegen/frame/type.ts @@ -135,6 +135,11 @@ function createTypeDecl(ctx: FrameCodegen, visitor: TyVisitor, path: str }): C.ValueRune<${memberPath}, C.RunicArgs.U>` + `{ return C.Rune.rec({ type: ${S.string(type)}, ${factory[1]} }) }`, ) + factories.push( + `export function is${type}(value: ${path}): value is ${memberPath} { return value.type === ${ + S.string(type) + } }`, + ) types.push( makeDocComment(docs) + `export interface ${type}` diff --git a/examples/block_author.ts b/examples/block_author.ts index 1b8f5853d..dc184cec0 100644 --- a/examples/block_author.ts +++ b/examples/block_author.ts @@ -1,6 +1,8 @@ import { consensus } from "capi/patterns" import { client } from "polkadot/mod.ts" -const result = await consensus.babeBlockAuthor(client).run() +const result = await consensus + .babeBlockAuthor(client, client.latestBlock.hash) + .run() console.log(result) diff --git a/examples/block_events.ts b/examples/block_events.ts index ee0d700bc..5cd707d91 100644 --- a/examples/block_events.ts +++ b/examples/block_events.ts @@ -1,8 +1,5 @@ import { client } from "polkadot/mod.ts" -const result = await client - .block() - .events() - .run() +const result = await client.latestBlock.events().run() console.log(result) diff --git a/examples/block_extrinsics.ts b/examples/block_extrinsics.ts index 6a9ee8d0d..9dbb2ba7c 100644 --- a/examples/block_extrinsics.ts +++ b/examples/block_extrinsics.ts @@ -1,8 +1,5 @@ import { client } from "polkadot/mod.ts" -const result = await client - .block() - .extrinsics() - .run() +const result = await client.latestBlock.extrinsics().run() console.log(result) diff --git a/examples/ink_e2e/main.ts b/examples/ink_e2e/main.ts index 4da148836..f992936dd 100644 --- a/examples/ink_e2e/main.ts +++ b/examples/ink_e2e/main.ts @@ -3,7 +3,7 @@ import { ink } from "capi/patterns" import { client } from "zombienet/examples/ink_e2e/zombienet.toml/collator/@latest/mod.ts" import { parse } from "../../deps/std/flags.ts" -export const contract = ink.InkMetadataRune.from( +export const metadata = ink.InkMetadataRune.from( client, Deno.readTextFileSync("examples/ink_e2e/metadata.json"), ) @@ -13,10 +13,10 @@ const sender = alice.publicKey let { address } = parse(Deno.args, { string: ["address"] }) if (!address) { class FailedToFindContractInstantiatedError extends Error { - override readonly name = "FailedToFindContractAddressError" + override readonly name = "FailedToFindContractInstantiatedError" } - address = await contract + address = await metadata .instantiate({ sender, code: Deno.readFileSync("examples/ink_e2e/code.wasm"), @@ -38,19 +38,19 @@ console.log(`Contract address: ${address}`) const publicKey = AddressRune.from(address, client).publicKey() console.log("Contract public key:", await publicKey.run()) -const instance = contract.instance(client, publicKey) +const contract = metadata.instance(client, publicKey) -console.log("Get:", await instance.call({ sender, method: "get" }).run()) +console.log("Get:", await contract.call({ sender, method: "get" }).run()) console.log( - await instance + await contract .tx({ sender, method: "flip" }) .signed({ sender: alice }) .sent() .logStatus("Flip status:") .txEvents() - .pipe(instance.filterContractEvents) + .pipe(contract.filterContractEvents) .run(), ) -console.log("Get:", await instance.call({ sender, method: "get" }).run()) +console.log("Get:", await contract.call({ sender, method: "get" }).run()) diff --git a/examples/watch.ts b/examples/watch.ts index f98940ff8..59fd918cd 100644 --- a/examples/watch.ts +++ b/examples/watch.ts @@ -1,25 +1,21 @@ -import { Rune, ValueRune } from "capi" +import { Rune } from "capi" import { client, Timestamp } from "polkadot/mod.ts" -const { blockHash } = client - -const block = client.block(blockHash) +const block = client.latestBlock const extrinsics = block.extrinsics() const events = block.events() -const now = Timestamp.Now.entry([], blockHash) +const now = Timestamp.Now.entry([], block.hash) + +const root = Rune.rec({ + hash: block.hash, + block, + extrinsics, + events, + now, +}) -await Rune - .rec({ - blockHash, - block, - extrinsics, - events, - now, - }) - .into(ValueRune) - .reduce(0, (i, values) => { - console.log(i, values) - return i + 1 - }) - .filter((i) => i === 3) - .run() +let i = 0 +for await (const values of root.watch()) { + console.log(values) + if (++i === 3) break +} diff --git a/examples/xcm/asset_teleportation.ts b/examples/xcm/asset_teleportation.ts index a187aca7f..4a42e7bf3 100644 --- a/examples/xcm/asset_teleportation.ts +++ b/examples/xcm/asset_teleportation.ts @@ -1,8 +1,10 @@ -import { alice, ApplyExtrinsicEvent, applyExtrinsicGuard, Rune, ValueRune } from "capi" +import { alice, Rune, ValueRune } from "capi" import { types, XcmPallet } from "zombienet/examples/xcm/zombienet.toml/alice/@latest/mod.ts" import { Event as XcmPalletEvent } from "zombienet/examples/xcm/zombienet.toml/alice/@latest/types/pallet_xcm/pallet.ts" +import { RuntimeEvent as AliceRuntimeEvent } from "zombienet/examples/xcm/zombienet.toml/alice/@latest/types/rococo_runtime/mod.ts" import { client, System } from "zombienet/examples/xcm/zombienet.toml/collator/@latest/mod.ts" import { Event as ParachainSystemEvent } from "zombienet/examples/xcm/zombienet.toml/collator/@latest/types/cumulus_pallet_parachain_system/pallet.ts" +import { RuntimeEvent as CollatorRuntimeEvent } from "zombienet/examples/xcm/zombienet.toml/collator/@latest/types/statemine_runtime.ts" // TODO: have the recipient associate the downward message with the sender @@ -55,13 +57,25 @@ const initiatedEvent = XcmPallet .sent() .logStatus("Teleportation status:") .txEvents() - .map((events) => events.find(isXcmPalletAttemptedEvent) ?? new CannotFindAttemptError()) + .map((events) => + events.find((e) => + AliceRuntimeEvent.isXcmPallet(e.event) + && XcmPalletEvent.isAttempted(e.event.value) + ) + ?? new CannotFindAttemptError() + ) .unhandle(CannotFindAttemptError) const processedEvent = System.Events - .entry([], client.blockHash) + .entry([], client.latestBlock.hash) .into(ValueRune) - .map((events) => events.find(isParachainSystemDownwardMessageProcessedEvent)) + .map((events) => + events + .find((e) => + CollatorRuntimeEvent.isParachainSystem(e.event) + && ParachainSystemEvent.isDownwardMessagesProcessed(e.event.value) + ) + ) .filter((event) => !!event) console.log( @@ -70,20 +84,3 @@ console.log( .run(), ) console.log("Final balance:", await aliceFree.run()) - -const isXcmPalletAttemptedEvent = applyExtrinsicGuard( - "XcmPallet", - "Attempted", -) -type AttemptedXcmPalletEvent = ApplyExtrinsicEvent<"XcmPallet", XcmPalletEvent.Attempted> - -const isParachainSystemDownwardMessageProcessedEvent = applyExtrinsicGuard< - ParachainSystemDownwardMessageProcessedEvent ->( - "ParachainSystem", - "DownwardMessagesProcessed", -) -type ParachainSystemDownwardMessageProcessedEvent = ApplyExtrinsicEvent< - "ParachainSystem", - ParachainSystemEvent.DownwardMessagesProcessed -> diff --git a/fluent/ClientRune.ts b/fluent/ClientRune.ts index 2fd59e4d3..df0292ced 100644 --- a/fluent/ClientRune.ts +++ b/fluent/ClientRune.ts @@ -16,7 +16,7 @@ export interface Chain { } export class ClientRune extends Rune { - blockHash = chain.getBlockHash( + latestBlock = this.block(chain.getBlockHash( this, chain .subscribeNewHeads(this.as(ClientRune)) @@ -24,16 +24,12 @@ export class ClientRune extends Rune headers.into(ValueRune)) .access("number"), - ) + )) - block(...[_maybeHash]: RunicArgs) { - const maybeHash = Rune.resolve(_maybeHash) - const blockHash = maybeHash - .unhandle(undefined) - .rehandle(undefined, () => chain.getFinalizedHead(this.as(Rune))) + block(...[blockHash]: RunicArgs) { return chain .getBlock(this.as(Rune), blockHash) - .into(BlockRune, this, blockHash) + .into(BlockRune, this, Rune.resolve(blockHash)) } metadata(...[blockHash]: RunicArgs) { @@ -46,7 +42,7 @@ export class ClientRune extends Rune(...args: RunicArgs) { const [call] = RunicArgs.resolve(args) - return call.into(ExtrinsicRune, this.as(ClientRune)) + return call.into(ExtrinsicRune, this.as(ClientRune)) } addressPrefix() { diff --git a/fluent/SignedExtrinsicRune.ts b/fluent/SignedExtrinsicRune.ts index a46cb9852..fc979c4bf 100644 --- a/fluent/SignedExtrinsicRune.ts +++ b/fluent/SignedExtrinsicRune.ts @@ -16,7 +16,7 @@ export class SignedExtrinsicRune extends Run sent() { return this .hex() - .map((hex) => author.submitAndWatchExtrinsic(this.client, hex)) + .map((hex) => author.submitAndWatchExtrinsic(this.client as ClientRune, hex)) .into(ExtrinsicStatusRune, this) } } diff --git a/patterns/consensus/babeBlockAuthor.ts b/patterns/consensus/babeBlockAuthor.ts index 39f433a39..6071de374 100644 --- a/patterns/consensus/babeBlockAuthor.ts +++ b/patterns/consensus/babeBlockAuthor.ts @@ -6,7 +6,7 @@ import { Rune, RunicArgs, ValueRune } from "../../rune/mod.ts" import { HexHash } from "../../util/branded.ts" import { preRuntimeDigest } from "./preRuntimeDigest.ts" -export function babeBlockAuthor(...args: RunicArgs) { +export function babeBlockAuthor(...args: RunicArgs) { const [client, at] = RunicArgs.resolve(args) const validators = client .into(ClientRune) diff --git a/patterns/consensus/preRuntimeDigest.ts b/patterns/consensus/preRuntimeDigest.ts index 8714aa255..90a10ab50 100644 --- a/patterns/consensus/preRuntimeDigest.ts +++ b/patterns/consensus/preRuntimeDigest.ts @@ -5,7 +5,7 @@ import { RunicArgs, ValueRune } from "../../rune/mod.ts" import { HexHash } from "../../util/branded.ts" import { hex } from "../../util/mod.ts" -export function preRuntimeDigest(...args: RunicArgs) { +export function preRuntimeDigest(...args: RunicArgs) { const [client, at] = RunicArgs.resolve(args) return client .into(ClientRune) diff --git a/patterns/ink/InkRune.ts b/patterns/ink/InkRune.ts index c09348dc0..2855a1692 100644 --- a/patterns/ink/InkRune.ts +++ b/patterns/ink/InkRune.ts @@ -1,4 +1,3 @@ -import * as $ from "../../deps/scale.ts" import { equals } from "../../deps/std/bytes/mod.ts" import { Chain, @@ -8,10 +7,10 @@ import { MultiAddressRune, state, } from "../../fluent/mod.ts" -import { Event, ExtrinsicFailEvent } from "../../primitives/mod.ts" +import { Event } from "../../primitives/mod.ts" import { Rune, RunicArgs, ValueRune } from "../../rune/mod.ts" import { hex } from "../../util/mod.ts" -import { ContractEvent, isContractEmittedEvent } from "./events.ts" +import { isInstantiatedEvent } from "./events.ts" import { InkMetadataRune } from "./InkMetadataRune.ts" import { $contractsApiCallArgs, $contractsApiCallResult, Weight } from "./known.ts" @@ -95,30 +94,18 @@ export class InkRune extends Rune(...[events]: RunicArgs) => { + // TODO: return all relevant events, not just instantiated return Rune .tuple([Rune.resolve(events), this]) .map(([events, publicKey]) => events.filter((e) => { - console.log(e) - if (e.event.type !== "Contracts") return false - const { event } = e as ContractEvent - switch (event.value.type) { - case "Called": - case "ContractCodeUpdated": - case "ContractEmitted": - case "DelegateCalled": - case "Terminated": - case "Instantiated": - return equals(event.value.contract, publicKey) - case "CodeRemoved": - case "CodeStored": - return false - } + return isInstantiatedEvent(e) && equals(e.event.value.contract, publicKey) }) ) } - decodeErrorEvent = (...[failRuntimeEvent]: RunicArgs) => { + // TODO: improve + decodeErrorEvent = (...[failRuntimeEvent]: RunicArgs) => { const metadata = this.client.metadata() const $error = metadata.codec( metadata @@ -139,17 +126,17 @@ export class InkRune extends Rune(...[events]: RunicArgs) { - const $event: $.Codec = null! - return Rune - .tuple([Rune.resolve(events), $event]) - .map(([events, $event]) => - events - .filter(isContractEmittedEvent) - .map((event) => $event.decode(event.event.value.data)) - ) - } + // // TODO: finish this + // emissions(...[events]: RunicArgs) { + // const $event: $.Codec = null! + // return Rune + // .tuple([Rune.resolve(events), $event]) + // .map(([events, $event]) => + // events + // .filter(isContractEmittedEvent) + // .map((event) => $event.decode(event.event.value.data)) + // ) + // } } export class MethodNotFoundError extends Error { diff --git a/patterns/ink/events.ts b/patterns/ink/events.ts index 2ce6a7931..7d806fee5 100644 --- a/patterns/ink/events.ts +++ b/patterns/ink/events.ts @@ -1,89 +1,22 @@ import { PublicKeyRune } from "../../fluent/mod.ts" -import { ApplyExtrinsicEvent, applyExtrinsicGuard, RuntimeEventData } from "../../primitives/mod.ts" +import { ApplyExtrinsicEventPhase, Event } from "../../primitives/mod.ts" import { Rune, RunicArgs } from "../../rune/mod.ts" -import { HexHash } from "../../util/branded.ts" -export type ContractEvent = - | InstantiatedEvent - | TerminatedEvent - | CodeStoredEvent - | ContractEmittedEvent - | CodeRemovedEvent - | ContractCodeUpdatedEvent - | CalledEvent - | DelegateCalledEvent - -export const isInstantiatedEvent = applyExtrinsicGuard( - "Contracts", - "Instantiated", -) -export type InstantiatedEvent = ContractEventBase<{ - type: "Instantiated" - deployer: Uint8Array - contract: Uint8Array -}> - -export const isTerminatedEvent = applyExtrinsicGuard("Contracts", "Terminated") -export type TerminatedEvent = ContractEventBase<{ - type: "Terminated" - contract: Uint8Array - beneficiary: Uint8Array -}> - -export const isCodeStoredEvent = applyExtrinsicGuard("Contracts", "CodeStored") -export type CodeStoredEvent = ContractEventBase<{ - type: "CodeStored" - code_hash: HexHash -}> - -export const isContractEmittedEvent = applyExtrinsicGuard( - "Contracts", - "ContractEmitted", -) -export type ContractEmittedEvent = ContractEventBase<{ - type: "ContractEmitted" - contract: Uint8Array - data: Uint8Array -}> - -export const isCodeRemovedEvent = applyExtrinsicGuard("Contracts", "CodeRemoved") -export type CodeRemovedEvent = ContractEventBase<{ - type: "CodeRemoved" - code_hash: Uint8Array -}> - -export const isContractCodeUpdatedEvent = applyExtrinsicGuard( - "Contracts", - "ContractCodeUpdated", -) -export type ContractCodeUpdatedEvent = ContractEventBase<{ - type: "ContractCodeUpdated" - contract: Uint8Array - new_code_hash: Uint8Array - old_code_hash: Uint8Array -}> - -export const isCalledEvent = applyExtrinsicGuard("Contracts", "Called") -export type CalledEvent = ContractEventBase<{ - type: "Called" - caller: Uint8Array - contract: Uint8Array -}> - -export const isDelegateCalledEvent = applyExtrinsicGuard( - "Contracts", - "DelegateCalled", -) -export type DelegateCalledEvent = ContractEventBase<{ - type: "DelegateCalled" - contract: Uint8Array - code_hash: Uint8Array -}> +export interface InstantiatedEvent extends Event { + phase: ApplyExtrinsicEventPhase + event: { + type: "Contracts" + value: { + type: "Instantiated" + deployer: Uint8Array + contract: Uint8Array + } + } +} -type ContractEventBase = ApplyExtrinsicEvent< - "Contracts", - Data -> +export function isInstantiatedEvent(event: Event): event is InstantiatedEvent { + return event.event.type === "Contracts" && (event.event.value as any).type === "Instantiated" +} export function instantiationEventIntoPublicKey( ...[event]: RunicArgs diff --git a/primitives/Event.ts b/primitives/Event.ts index 909fdfeaf..f4572d894 100644 --- a/primitives/Event.ts +++ b/primitives/Event.ts @@ -1,27 +1,16 @@ import { Weight } from "./Weight.ts" -export interface Event

{ - phase: P - event: E +export interface Event { + phase: EventPhase + event: RuntimeEvent topics: Uint8Array[] } -export function applyExtrinsicGuard( - pallet: E["event"]["type"], - label: E["event"]["value"]["type"], -) { - return (event: Event): event is E => - event.event.type === pallet && event.event.value.type === label -} - -export type ApplyExtrinsicEvent< - Pallet extends string = string, - Value extends RuntimeEventData = RuntimeEventData, -> = Event> export type EventPhase = | ApplyExtrinsicEventPhase | FinalizationEventPhase | InitializationEventPhase + export interface ApplyExtrinsicEventPhase { type: "ApplyExtrinsic" value: number @@ -33,43 +22,9 @@ export interface InitializationEventPhase { type: "Initialization" } -export interface RuntimeEvent< - Pallet extends string = string, - Value extends RuntimeEventData = RuntimeEventData, -> { - type: Pallet - value: Value -} - -export interface RuntimeEventData { - type: Type -} - -export type ExtrinsicSuccessEvent = Event< - ApplyExtrinsicEventPhase, - RuntimeEvent<"System", { - type: "ExtrinsicSuccess" - dispatchInfo: DispatchInfo - }> -> -export function isExtrinsicSuccessEvent(e: Event): e is ExtrinsicSuccessEvent { - const { event } = e - return event.type === "System" && event.value.type === "ExtrinsicSuccess" -} - -export type ExtrinsicFailEvent = Event< - ApplyExtrinsicEventPhase, - RuntimeEvent<"System", { - type: "ExtrinsicFailed" - dispatchInfo: DispatchInfo - dispatchError: DispatchError - }> -> -export function isExtrinsicFailEvent( - e: Event, -): e is ExtrinsicFailEvent { - const { event } = e - return event.type === "System" && event.value.type === "ExtrinsicFailed" +export interface RuntimeEvent { + type: string + value: unknown } export interface DispatchInfo { @@ -92,6 +47,7 @@ export type DispatchError = | ExhaustedDispatchError | CorruptionDispatchError | UnavailableDispatchError + export interface OtherDispatchError { type: "Other" }