Skip to content
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

fix: EventMetadata class implementation for serialisation #9574

Merged
merged 7 commits into from
Oct 30, 2024
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"Validium",
"vals",
"viem",
"vpks",
"Vyper",
"wasms",
"webassembly",
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export {
EncryptedNoteL2BlockL2Logs,
EpochProofQuote,
EpochProofQuotePayload,
EventMetadata,
EventType,
ExtendedNote,
FunctionCall,
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/aztec.js/src/rpc_clients/pxe_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EncryptedL2Log,
EncryptedL2NoteLog,
EncryptedNoteL2BlockL2Logs,
EventMetadata,
ExtendedNote,
ExtendedUnencryptedL2Log,
L2Block,
Expand Down Expand Up @@ -36,7 +37,7 @@ import {
PrivateCircuitPublicInputs,
PublicKeys,
} from '@aztec/circuits.js';
import { NoteSelector } from '@aztec/foundation/abi';
import { EventSelector, NoteSelector } from '@aztec/foundation/abi';
import { Buffer32 } from '@aztec/foundation/buffer';
import { createJsonRpcClient, makeFetch } from '@aztec/foundation/json-rpc/client';

Expand All @@ -55,6 +56,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false)
CompleteAddress,
FunctionSelector,
EthAddress,
EventSelector,
ExtendedNote,
UniqueNote,
ExtendedUnencryptedL2Log,
Expand All @@ -75,6 +77,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false)
EncryptedNoteL2BlockL2Logs,
EncryptedL2NoteLog,
EncryptedL2Log,
EventMetadata,
UnencryptedL2Log,
NoteSelector,
NullifierMembershipWitness,
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/aztec.js/src/wallet/base_wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
type AuthWitness,
type EventMetadata,
type EventType,
type ExtendedNote,
type GetUnencryptedLogsResponse,
Expand Down Expand Up @@ -34,7 +33,7 @@ import {
type PartialAddress,
type Point,
} from '@aztec/circuits.js';
import { type ContractArtifact } from '@aztec/foundation/abi';
import type { AbiType, ContractArtifact, EventSelector } from '@aztec/foundation/abi';

import { type Wallet } from '../account/wallet.js';
import { type ExecutionRequestInit } from '../entrypoint/entrypoint.js';
Expand Down Expand Up @@ -199,15 +198,22 @@ export abstract class BaseWallet implements Wallet {
}
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
event: {
/** The event selector */
eventSelector: EventSelector;
/** The event's abi type */
abiType: AbiType;
/** The field names */
fieldNames: string[];
},
from: number,
limit: number,
vpks: Point[] = [
this.getCompleteAddress().publicKeys.masterIncomingViewingPublicKey,
this.getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey,
],
) {
return this.pxe.getEvents(type, eventMetadata, from, limit, vpks);
): Promise<T[]> {
return this.pxe.getEvents(type, event, from, limit, vpks);
}
public getL1ToL2MembershipWitness(
contractAddress: AztecAddress,
Expand Down
30 changes: 2 additions & 28 deletions yarn-project/builder/src/contract-interface-gen/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,12 @@ function generateEvents(events: any[] | undefined) {
`;

const fieldNames = event.fields.map((field: any) => `"${field.name}"`);
const eventType = `${eventName}: {decode: (payload: L1EventPayload | UnencryptedL2Log | undefined) => ${eventName} | undefined, eventSelector: EventSelector, fieldNames: string[] }`;
const eventType = `${eventName}: {abiType: AbiType, eventSelector: EventSelector, fieldNames: string[] }`;
// Reusing the decodeFunctionSignature
const eventSignature = decodeFunctionSignature(eventName, event.fields);
const eventSelector = `EventSelector.fromSignature('${eventSignature}')`;
const eventImpl = `${eventName}: {
decode: this.decodeEvent(${eventSelector}, ${JSON.stringify(event, null, 4)}),
abiType: ${JSON.stringify(event, null, 4)},
eventSelector: ${eventSelector},
fieldNames: [${fieldNames}],
}`;
Expand All @@ -277,32 +277,6 @@ function generateEvents(events: any[] | undefined) {
return {
eventDefs: eventsMetadata.map(({ eventDef }) => eventDef).join('\n'),
events: `
// Partial application is chosen is to avoid the duplication of so much codegen.
private static decodeEvent<T>(
eventSelector: EventSelector,
eventType: AbiType,
): (payload: L1EventPayload | UnencryptedL2Log | undefined) => T | undefined {
return (payload: L1EventPayload | UnencryptedL2Log | undefined): T | undefined => {
if (payload === undefined) {
return undefined;
}

if (payload instanceof L1EventPayload) {
if (!eventSelector.equals(payload.eventTypeId)) {
return undefined;
}
return decodeFromAbi([eventType], payload.event.items) as T;
} else {
let items = [];
for (let i = 0; i < payload.data.length; i += 32) {
items.push(new Fr(payload.data.subarray(i, i + 32)));
}

return decodeFromAbi([eventType], items) as T;
}
};
}

public static get events(): { ${eventsMetadata.map(({ eventType }) => eventType).join(', ')} } {
return {
${eventsMetadata.map(({ eventImpl }) => eventImpl).join(',\n')}
Expand Down
22 changes: 4 additions & 18 deletions yarn-project/circuit-types/src/interfaces/pxe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import {
type Point,
type ProtocolContractAddresses,
} from '@aztec/circuits.js';
import { type ContractArtifact, type EventSelector } from '@aztec/foundation/abi';
import type { AbiType, ContractArtifact, EventSelector } from '@aztec/foundation/abi';

import { type AuthWitness } from '../auth_witness.js';
import { type L2Block } from '../l2_block.js';
import {
type GetUnencryptedLogsResponse,
type L1EventPayload,
type LogFilter,
type UnencryptedL2Log,
} from '../logs/index.js';
import { type GetUnencryptedLogsResponse, type LogFilter } from '../logs/index.js';
import { type IncomingNotesFilter } from '../notes/incoming_notes_filter.js';
import { type ExtendedNote, type OutgoingNotesFilter, type UniqueNote } from '../notes/index.js';
import { type PrivateExecutionResult } from '../private_execution_result.js';
Expand Down Expand Up @@ -409,31 +404,22 @@ export interface PXE {
/**
* Returns the events of a specified type given search parameters.
* @param type - The type of the event to search for—Encrypted, or Unencrypted.
* @param eventMetadata - Identifier of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param eventMetadata - Metadata of the event. This should be the class generated from the contract. e.g. Contract.events.Event
* @param from - The block number to search from.
* @param limit - The amount of blocks to search.
* @param vpks - (Used for encrypted logs only) The viewing (incoming and outgoing) public keys that correspond to the viewing secret keys that can decrypt the log.
* @returns - The deserialized events.
*/
getEvents<T>(
type: EventType,
eventMetadata: EventMetadata<T>,
eventMetadata: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] },
from: number,
limit: number,
vpks: Point[],
): Promise<T[]>;
}
// docs:end:pxe-interface

/**
* The shape of the event generated on the Contract.
*/
export interface EventMetadata<T> {
decode(payload: L1EventPayload | UnencryptedL2Log): T | undefined;
eventSelector: EventSelector;
fieldNames: string[];
}

/**
* This is used in getting events via the filter
*/
Expand Down
77 changes: 77 additions & 0 deletions yarn-project/circuit-types/src/logs/event_metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { EventType, L1EventPayload, type UnencryptedL2Log } from '@aztec/circuit-types';
import { type AbiType } from '@aztec/foundation/abi';
import { EventSelector, decodeFromAbi } from '@aztec/foundation/abi';
import { Fr } from '@aztec/foundation/fields';

/**
* Represents metadata for an event decoder, including all information needed to reconstruct it.
*/
export class EventMetadata<T> {
public readonly decode: (payload: L1EventPayload | UnencryptedL2Log) => T | undefined;

public readonly eventSelector: EventSelector;
public readonly abiType: AbiType;
public readonly fieldNames: string[];

constructor(
public readonly eventType: EventType,
event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] },
) {
this.eventSelector = event.eventSelector;
this.abiType = event.abiType;
this.fieldNames = event.fieldNames;
this.decode = EventMetadata.decodeEvent<T>(event.eventSelector, event.abiType);
}

public static decodeEvent<T>(
eventSelector: EventSelector,
abiType: AbiType,
): (payload: L1EventPayload | UnencryptedL2Log | undefined) => T | undefined {
return (payload: L1EventPayload | UnencryptedL2Log | undefined): T | undefined => {
if (payload === undefined) {
return undefined;
}

if (payload instanceof L1EventPayload) {
if (!eventSelector.equals(payload.eventTypeId)) {
return undefined;
}
return decodeFromAbi([abiType], payload.event.items) as T;
} else {
const items = [];
for (let i = 0; i < payload.data.length; i += 32) {
items.push(new Fr(payload.data.subarray(i, i + 32)));
}

return decodeFromAbi([abiType], items) as T;
}
};
}

/**
* Serializes the metadata to a JSON-friendly format
*/
public toJSON() {
return {
type: 'event_metadata',
eventSelector: this.eventSelector.toString(),
eventType: this.eventType,
fieldNames: this.fieldNames,
};
}

/**
* Creates an EventMetadata instance from a JSON representation
*/
public static fromJSON(json: any): EventMetadata<any> {
if (json?.type !== 'event_metadata') {
throw new Error('Invalid event metadata format');
}

return new EventMetadata(EventType.Encrypted, {
eventSelector: EventSelector.fromString(json.eventSelector),
abiType: json.abiType,
fieldNames: json.fieldNames,
});
}
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/logs/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './encrypted_l2_note_log.js';
export * from './encrypted_l2_log.js';
export * from './event_metadata.js';
export * from './get_unencrypted_logs_response.js';
export * from './function_l2_logs.js';
export * from './l2_block_l2_logs.js';
Expand Down
33 changes: 22 additions & 11 deletions yarn-project/end-to-end/src/e2e_event_logs.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type AccountWalletWithSecretKey,
type AztecNode,
EventMetadata,
EventType,
Fr,
L1EventPayload,
Expand Down Expand Up @@ -61,14 +62,18 @@ describe('Logs', () => {
expect(decryptedEvent0.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent0(Field,Field)'));

// We decode our event into the event type
const event0 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent0);
const event0Metadata = new EventMetadata<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
);
const event0 = event0Metadata.decode(decryptedEvent0);

// We check that the event was decoded correctly
expect(event0?.value0).toStrictEqual(preimage[0].toBigInt());
expect(event0?.value1).toStrictEqual(preimage[1].toBigInt());

// We check that an event that does not match, is not decoded correctly due to an event type id mismatch
const badEvent0 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent0);
const badEvent0 = event0Metadata.decode(decryptedEvent0);
expect(badEvent0).toBe(undefined);

const decryptedEvent1 = L1EventPayload.decryptAsIncoming(encryptedLogs[2], wallets[0].getEncryptionSecret())!;
Expand All @@ -78,15 +83,20 @@ describe('Logs', () => {
expect(decryptedEvent1.eventTypeId).toStrictEqual(EventSelector.fromSignature('ExampleEvent1((Field),u8)'));

// We check our second event, which is a different type
const event1 = TestLogContract.events.ExampleEvent1.decode(decryptedEvent1);
const event1Metadata = new EventMetadata<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
);

const event1 = event1Metadata.decode(decryptedEvent1);

// We expect the fields to have been populated correctly
expect(event1?.value2).toStrictEqual(preimage[2]);
// We get the last byte here because value3 is of type u8
expect(event1?.value3).toStrictEqual(BigInt(preimage[3].toBuffer().subarray(31).readUint8()));

// Again, trying to decode another event with mismatching data does not yield anything
const badEvent1 = TestLogContract.events.ExampleEvent0.decode(decryptedEvent1);
const badEvent1 = event1Metadata.decode(decryptedEvent1);
expect(badEvent1).toBe(undefined);
});

Expand All @@ -113,14 +123,15 @@ describe('Logs', () => {
.wait();

// We get all the events we can decrypt with either our incoming or outgoing viewing keys
const collectedEvent0s = await wallets[0].getEvents(

const collectedEvent0s = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent0sWithIncoming = await wallets[0].getEvents(
const collectedEvent0sWithIncoming = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
Expand All @@ -129,15 +140,15 @@ describe('Logs', () => {
[wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey],
);

const collectedEvent0sWithOutgoing = await wallets[0].getEvents(
const collectedEvent0sWithOutgoing = await wallets[0].getEvents<ExampleEvent0>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
[wallets[0].getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey],
);

const collectedEvent1s = await wallets[0].getEvents(
const collectedEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand All @@ -150,7 +161,7 @@ describe('Logs', () => {
expect(collectedEvent0s.length).toBe(10);
expect(collectedEvent1s.length).toBe(5);

const emptyEvent1s = await wallets[0].getEvents(
const emptyEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Encrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand Down Expand Up @@ -199,14 +210,14 @@ describe('Logs', () => {
);
const lastTx = await testLogContract.methods.emit_unencrypted_events(preimage[++i]).send().wait();

const collectedEvent0s = await wallets[0].getEvents(
const collectedEvent0s = await wallets[0].getEvents<ExampleEvent0>(
EventType.Unencrypted,
TestLogContract.events.ExampleEvent0,
firstTx.blockNumber!,
lastTx.blockNumber! - firstTx.blockNumber! + 1,
);

const collectedEvent1s = await wallets[0].getEvents(
const collectedEvent1s = await wallets[0].getEvents<ExampleEvent1>(
EventType.Unencrypted,
TestLogContract.events.ExampleEvent1,
firstTx.blockNumber!,
Expand Down
Loading
Loading