forked from etherspot/skandha
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: private event utilities #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zeluisping
wants to merge
2
commits into
feat/update-sd-dependency-match-lock-viem
Choose a base branch
from
feat/private-event-utilities
base: feat/update-sd-dependency-match-lock-viem
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { AbiEvent, toEventHash } from 'viem' | ||
|
|
||
| export const AccountDeployedAbi = <const>{ | ||
| anonymous: false, | ||
| inputs: [ | ||
| { | ||
| indexed: true, | ||
| internalType: "bytes32", | ||
| name: "userOpHash", | ||
| type: "bytes32", | ||
| }, | ||
| { | ||
| indexed: true, | ||
| internalType: "address", | ||
| name: "sender", | ||
| type: "address", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "address", | ||
| name: "factory", | ||
| type: "address", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "address", | ||
| name: "paymaster", | ||
| type: "address", | ||
| }, | ||
| ], | ||
| name: "AccountDeployed", | ||
| type: "event", | ||
| } satisfies AbiEvent | ||
| export type AccountDeployedAbi = typeof AccountDeployedAbi | ||
|
|
||
| export const ACCOUNT_DEPLOYED_EVENT_HASH = toEventHash(AccountDeployedAbi) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { AbiEvent } from 'viem' | ||
|
|
||
| export const PrivateEventAbi = <const>{ | ||
| anonymous: false, | ||
| inputs: [ | ||
| { | ||
| indexed: false, | ||
| internalType: "address[]", | ||
| name: "allowedViewers", | ||
| type: "address[]", | ||
| }, | ||
| { | ||
| indexed: true, | ||
| internalType: "bytes32", | ||
| name: "eventType", | ||
| type: "bytes32", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "bytes", | ||
| name: "payload", | ||
| type: "bytes", | ||
| }, | ||
| ], | ||
| name: "PrivateEvent", | ||
| type: "event", | ||
| } satisfies AbiEvent | ||
| export type PrivateEventAbi = typeof PrivateEventAbi |
16 changes: 16 additions & 0 deletions
16
packages/executor/src/utils/abi-events/SignatureAggregatorChanged.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { AbiEvent } from 'viem' | ||
|
|
||
| export const SignatureAggregatorChangedAbi = <const>{ | ||
| anonymous: false, | ||
| inputs: [ | ||
| { | ||
| indexed: true, | ||
| internalType: "address", | ||
| name: "aggregator", | ||
| type: "address", | ||
| }, | ||
| ], | ||
| name: "SignatureAggregatorChanged", | ||
| type: "event", | ||
| } satisfies AbiEvent | ||
| export type SignatureAggregatorChangedAbi = typeof SignatureAggregatorChangedAbi |
54 changes: 54 additions & 0 deletions
54
packages/executor/src/utils/abi-events/UserOperationEvent.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import { AbiEvent, toEventHash } from 'viem' | ||
|
|
||
| export const UserOperationEventAbi = <const>{ | ||
| anonymous: false, | ||
| inputs: [ | ||
| { | ||
| indexed: true, | ||
| internalType: "bytes32", | ||
| name: "userOpHash", | ||
| type: "bytes32", | ||
| }, | ||
| { | ||
| indexed: true, | ||
| internalType: "address", | ||
| name: "sender", | ||
| type: "address", | ||
| }, | ||
| { | ||
| indexed: true, | ||
| internalType: "address", | ||
| name: "paymaster", | ||
| type: "address", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "uint256", | ||
| name: "nonce", | ||
| type: "uint256", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "bool", | ||
| name: "success", | ||
| type: "bool", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "uint256", | ||
| name: "actualGasCost", | ||
| type: "uint256", | ||
| }, | ||
| { | ||
| indexed: false, | ||
| internalType: "uint256", | ||
| name: "actualGasUsed", | ||
| type: "uint256", | ||
| }, | ||
| ], | ||
| name: "UserOperationEvent", | ||
| type: "event", | ||
| } satisfies AbiEvent | ||
| export type UserOperationEventAbi = typeof UserOperationEventAbi | ||
|
|
||
| export const USER_OPERATION_EVENT_HASH = toEventHash(UserOperationEventAbi) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export * from './AccountDeployed' | ||
| export * from './PrivateEvent' | ||
| export * from './SignatureAggregatorChanged' | ||
| export * from './UserOperationEvent' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import { AbiParameter } from "abitype" | ||
| import { AbiEvent, decodeAbiParameters, encodeEventTopics, Hex, Log } from "viem" | ||
|
|
||
|
|
||
| export class AbiEventTopicsCountMismatchError extends Error { | ||
| indexedParams: readonly AbiParameter[] | ||
| expectedCount: number | ||
| actualCount: number | ||
|
|
||
| constructor({ | ||
| indexedParams, | ||
| expectedCount, | ||
| actualCount, | ||
| }: { | ||
| indexedParams: readonly AbiParameter[] | ||
| expectedCount: number | ||
| actualCount: number | ||
| }) { | ||
| const indexedParamsStr = indexedParams | ||
| .map(p => `${p.type}${p.name ? ` ${p.name}` : ''}`) | ||
| .join(', ') | ||
|
|
||
| const message = [ | ||
| `Topics count mismatch: expected ${expectedCount}, got ${actualCount}`, | ||
| `Indexed params: (${indexedParamsStr})`, | ||
| ].join('\n') | ||
|
|
||
| super(message) | ||
|
|
||
| // Restore prototype chain for instanceof checks | ||
| Object.setPrototypeOf(this, AbiEventTopicsCountMismatchError.prototype) | ||
| this.name = 'AbiEventTopicsCountMismatchError' | ||
| this.indexedParams = indexedParams | ||
| this.expectedCount = expectedCount | ||
| this.actualCount = actualCount | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extracts indexed parameters into a fixed length array of hex | ||
| * | ||
| * @param Inputs - the inputs of the event (the indexed parameters) | ||
| * @param Acc - the accumulator (the array of hex) | ||
| * @returns the indexed parameters as a fixed length array of hex | ||
| */ | ||
| type ExtractIndexedAsHex< | ||
| Inputs extends readonly any[], | ||
| Acc extends Hex[] = [] | ||
| > = Inputs extends readonly [infer First, ...infer Rest] | ||
| ? First extends { indexed: true } | ||
| ? ExtractIndexedAsHex<Rest, [...Acc, Hex]> | ||
| : ExtractIndexedAsHex<Rest, Acc> | ||
| : Acc; | ||
|
|
||
| /** | ||
| * Builds the topics fixed-length array type | ||
| * | ||
| * @param ABI - the abi of the event to extract the topics from | ||
| * @returns the topics fixed-length array type | ||
| */ | ||
| type AbiEventTopicsArray<ABI extends AbiEvent> = | ||
| ABI['inputs'] extends readonly [...infer Inputs] | ||
| ? [Hex, ...ExtractIndexedAsHex<Inputs>] | ||
| : [Hex]; | ||
|
|
||
| /** | ||
| * Extracts the topics from an event | ||
| * | ||
| * @param abi - the abi of the event to extract the topics from | ||
| * @param payload - the payload of the event | ||
| * @returns fixed-length array with the encoded event topics | ||
| * @throws an {@link AbiEventTopicsCountMismatchError} if the topics count mismatch | ||
| */ | ||
| export function extractEventAbiTopics< | ||
| ABI extends AbiEvent | ||
| >( | ||
| abi: ABI, | ||
| payload: Hex, | ||
| ): AbiEventTopicsArray<ABI> { | ||
| const allParams = decodeAbiParameters<ABI['inputs']>(abi.inputs, payload) | ||
|
|
||
| const indexedValues: unknown[] = [] | ||
| abi.inputs.forEach((input, index) => { | ||
| if (input.indexed) { | ||
| indexedValues.push(allParams[index]) | ||
| } | ||
| }) | ||
|
|
||
| // @ts-expect-error - dynamically extracted indexedValues cannot be proven to match generic tuple type | ||
| const topics = encodeEventTopics({ | ||
| abi: [abi], | ||
| args: indexedValues | ||
| }) | ||
|
|
||
| return topics as AbiEventTopicsArray<ABI> | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AbiEvent, decodeEventLog, Log } from 'viem' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PrivateEventAbi } from './abi-events' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { extractEventAbiTopics } from './extractEventAbiTopics' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type PrivateEventLog = Log<bigint, number, false, PrivateEventAbi> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type PrivateEventLogWithArgs = PrivateEventLog & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: Required<PrivateEventLog['args']> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Checks if a private event log has all the args defined. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param x - the private event log to check if it has args | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns true if the private event log has args, false otherwise | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const isPrivateEventLogWithArgs = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x: PrivateEventLog, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): x is PrivateEventLogWithArgs => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x.args.allowedViewers !== undefined && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x.args.eventType !== undefined && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| x.args.payload !== undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Unwraps a private event log into a regular event log. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param abi - the abi of the event to unwrap | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param ev - the private event log to unwrap | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns the unwrapped event log | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @throws if there is an error decoding the event log or extracting the topics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function unwrapPrivateEvent<ABI extends AbiEvent>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| abi: ABI, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ev: PrivateEventLogWithArgs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Log<bigint, number, false, ABI> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const decoded = decodeEventLog({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| abi: [abi], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: ev.args.payload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| topics: [], // empty so viem decodes all params from the payload | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const topics = extractEventAbiTopics(abi, ev.args.payload) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: decoded.args has type `readonly unknown[] | ...` because viem can't | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // narrow the type when topics is empty, but at runtime it is correctly decoded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...ev, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventName: abi.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: decoded.args, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| topics, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } as unknown as Log<bigint, number, false, ABI> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+51
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | |
| ...ev, | |
| eventName: abi.name, | |
| args: decoded.args, | |
| topics, | |
| } as unknown as Log<bigint, number, false, ABI> | |
| } | |
| // Runtime validation: ensure decoded.args is an object and matches ABI inputs | |
| if ( | |
| typeof decoded.args !== 'object' || | |
| decoded.args === null | |
| ) { | |
| throw new Error('Decoded args is not an object'); | |
| } | |
| // Optionally, check that all ABI inputs are present in decoded.args | |
| if ( | |
| !abi.inputs.every(input => input.name in (decoded.args as object)) | |
| ) { | |
| throw new Error('Decoded args does not match ABI inputs'); | |
| } | |
| return { | |
| ...ev, | |
| eventName: abi.name, | |
| args: decoded.args, | |
| topics, | |
| }; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
@ts-expect-errordirective suppresses type checking without validation. Consider using@ts-ignorewith a more specific explanation, or better yet, add runtime validation to ensureindexedValuesmatches the expected structure before encoding.