Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

feat: error unhandling method + decoding #695

Merged
merged 6 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/error_unhandling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { alice, bob } from "capi"
import { Balances } from "westend_dev/mod.ts"

await Balances
.transfer({
value: 1_000_000_000_000_000_000_000_000_000_000_000_000n,
dest: bob.address,
})
.signed({ sender: alice })
.sent()
.dbgStatus()
.finalizedEvents()
.unhandleFailed()
.run()
2 changes: 2 additions & 0 deletions fluent/BlockRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ArrayRune, Rune } from "../rune/mod.ts"
import { ValueRune } from "../rune/ValueRune.ts"
import { Chain, ChainRune } from "./ChainRune.ts"
import { CodecRune } from "./CodecRune.ts"
import { EventsRune } from "./EventsRune.ts"

export class BlockRune<out U, out C extends Chain = Chain> extends Rune<known.SignedBlock, U> {
constructor(
Expand Down Expand Up @@ -50,5 +51,6 @@ export class BlockRune<out U, out C extends Chain = Chain> extends Rune<known.Si
.into(ValueRune)
.unhandle(undefined)
.rehandle(undefined, () => Rune.constant<Chain.Event<C>[]>([]))
.into(EventsRune, this.chain)
}
}
12 changes: 12 additions & 0 deletions fluent/EventsRune.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Rune } from "../rune/mod.ts"
import { Chain, ChainRune } from "./ChainRune.ts"

export class EventsRune<
out U,
out C extends Chain = Chain,
out E extends Chain.Event<C> = Chain.Event<C>,
> extends Rune<E[], U> {
constructor(_prime: EventsRune<U>["_prime"], readonly chain: ChainRune<U, C>) {
super(_prime)
}
}
2 changes: 2 additions & 0 deletions fluent/ExtrinsicStatusRune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { known } from "../rpc/mod.ts"
import { MetaRune, Rune, RunicArgs, ValueRune } from "../rune/mod.ts"
import { BlockRune } from "./BlockRune.ts"
import { Chain } from "./ChainRune.ts"
import { ExtrinsicEventsRune } from "./ExtrinsicsEventsRune.ts"
import { SignedExtrinsicRune } from "./SignedExtrinsicRune.ts"

export class ExtrinsicStatusRune<out U1, out U2, out C extends Chain = Chain>
Expand Down Expand Up @@ -74,6 +75,7 @@ export class ExtrinsicStatusRune<out U1, out U2, out C extends Chain = Chain>
.map(([events, txI]) =>
events.filter((event) => event.phase.type === "ApplyExtrinsic" && event.phase.value === txI)
)
.into(ExtrinsicEventsRune, this.extrinsic.chain)
}
}

Expand Down
50 changes: 50 additions & 0 deletions fluent/ExtrinsicsEventsRune.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { isSystemExtrinsicFailedEvent, SystemExtrinsicFailedEvent } from "../primitives/mod.ts"
import { Rune, ValueRune } from "../rune/mod.ts"
import { Chain } from "./ChainRune.ts"
import { CodecRune } from "./CodecRune.ts"
import { EventsRune } from "./EventsRune.ts"

export class ExtrinsicEventsRune<
out U,
out C extends Chain = Chain,
out E extends Chain.Event<C> = Chain.Event<C>,
> extends EventsRune<U, C, E> {
// TODO: make generic over `Chain.Error` upon T6 metadata rework
unhandleFailed() {
const dispatchError = this
.into(ValueRune)
.map((events): SystemExtrinsicFailedEvent | undefined =>
events.find(isSystemExtrinsicFailedEvent)
)
.unhandle(undefined)
.access("event", "value", "dispatchError")
.map((dispatchError) => {
if (dispatchError.type === "Module") return dispatchError.value
return new ExtrinsicError(dispatchError)
})
.unhandle(ExtrinsicError)
const metadata = this.chain.metadata()
const pallet = Rune
.tuple([
metadata.into(ValueRune).access("pallets"),
dispatchError.access("index"),
])
.map(([pallets, i]) => pallets.find((pallet) => pallet.i === i)!)
return Rune
.tuple([metadata.deriveCodec, pallet])
.map(([deriveCodec, pallet]) => deriveCodec(pallet.error!))
.into(CodecRune)
.decoded(dispatchError.access("error"))
.map((data) => new ExtrinsicError((typeof data === "string" ? { type: data } : data) as any))
.unhandle(ExtrinsicError)
.rehandle(undefined, () => this /* TODO: type-level exclusions? */)
}
}

export class ExtrinsicError<D extends { type: string }> extends Error {
override readonly name = "ExtrinsicError"

constructor(data: D) {
super(data.type)
}
}
2 changes: 2 additions & 0 deletions fluent/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ export * from "./ChainRune.ts"
export * from "./CodecRune.ts"
export * from "./ConnectionRune.ts"
export * from "./ConstRune.ts"
export * from "./EventsRune.ts"
export * from "./ExtrinsicRune.ts"
export * from "./ExtrinsicsEventsRune.ts"
export * from "./ExtrinsicStatusRune.ts"
export * from "./MetadataRune.ts"
export * from "./PalletRune.ts"
Expand Down
21 changes: 21 additions & 0 deletions primitives/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ export interface RuntimeEvent {
value: unknown
}

export interface SystemExtrinsicFailedEvent extends Event {
phase: ApplyExtrinsicEventPhase
event: {
type: "System"
value: {
type: "ExtrinsicFailed"
dispatchError: DispatchError
dispatchInfo: DispatchInfo
}
}
}
export function isSystemExtrinsicFailedEvent(e: Event): e is SystemExtrinsicFailedEvent {
const { event } = e
if (event.type === "System") {
const { value } = event
return typeof value === "object" && value !== null && "type" in value
&& value.type === "ExtrinsicFailed"
}
return false
}

export interface DispatchInfo {
weight: Weight
class: DispatchClass
Expand Down