diff --git a/cspell.json b/cspell.json index d964b9afe22..dd5586ddbc8 100644 --- a/cspell.json +++ b/cspell.json @@ -283,6 +283,7 @@ "Validium", "vals", "viem", + "vpks", "Vyper", "wasms", "webassembly", diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 0cd90c9fd60..8f84ccf43e5 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -116,6 +116,7 @@ export { EncryptedNoteL2BlockL2Logs, EpochProofQuote, EpochProofQuotePayload, + EventMetadata, EventType, ExtendedNote, FunctionCall, diff --git a/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts b/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts index 34ce3848390..0e7a81a0714 100644 --- a/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts +++ b/yarn-project/aztec.js/src/rpc_clients/pxe_client.ts @@ -5,6 +5,7 @@ import { EncryptedL2Log, EncryptedL2NoteLog, EncryptedNoteL2BlockL2Logs, + EventMetadata, ExtendedNote, ExtendedUnencryptedL2Log, L2Block, @@ -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'; @@ -55,6 +56,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false) CompleteAddress, FunctionSelector, EthAddress, + EventSelector, ExtendedNote, UniqueNote, ExtendedUnencryptedL2Log, @@ -75,6 +77,7 @@ export const createPXEClient = (url: string, fetch = makeFetch([1, 2, 3], false) EncryptedNoteL2BlockL2Logs, EncryptedL2NoteLog, EncryptedL2Log, + EventMetadata, UnencryptedL2Log, NoteSelector, NullifierMembershipWitness, diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index e64396187a7..2ec4fe257b8 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -1,6 +1,5 @@ import { type AuthWitness, - type EventMetadata, type EventType, type ExtendedNote, type GetUnencryptedLogsResponse, @@ -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'; @@ -199,15 +198,22 @@ export abstract class BaseWallet implements Wallet { } getEvents( type: EventType, - eventMetadata: EventMetadata, + 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 { + return this.pxe.getEvents(type, event, from, limit, vpks); } public getL1ToL2MembershipWitness( contractAddress: AztecAddress, diff --git a/yarn-project/builder/src/contract-interface-gen/typescript.ts b/yarn-project/builder/src/contract-interface-gen/typescript.ts index 200337cb22b..455378a3d13 100644 --- a/yarn-project/builder/src/contract-interface-gen/typescript.ts +++ b/yarn-project/builder/src/contract-interface-gen/typescript.ts @@ -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}], }`; @@ -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( - 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')} diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 26721aee915..9b8da020962 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -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'; @@ -409,7 +404,7 @@ 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. @@ -417,7 +412,7 @@ export interface PXE { */ getEvents( type: EventType, - eventMetadata: EventMetadata, + eventMetadata: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] }, from: number, limit: number, vpks: Point[], @@ -425,15 +420,6 @@ export interface PXE { } // docs:end:pxe-interface -/** - * The shape of the event generated on the Contract. - */ -export interface EventMetadata { - decode(payload: L1EventPayload | UnencryptedL2Log): T | undefined; - eventSelector: EventSelector; - fieldNames: string[]; -} - /** * This is used in getting events via the filter */ diff --git a/yarn-project/circuit-types/src/logs/event_metadata.ts b/yarn-project/circuit-types/src/logs/event_metadata.ts new file mode 100644 index 00000000000..9e2e44f7abf --- /dev/null +++ b/yarn-project/circuit-types/src/logs/event_metadata.ts @@ -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 { + 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(event.eventSelector, event.abiType); + } + + public static decodeEvent( + 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 { + 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, + }); + } +} diff --git a/yarn-project/circuit-types/src/logs/index.ts b/yarn-project/circuit-types/src/logs/index.ts index 14cb33aa006..af29a4c9677 100644 --- a/yarn-project/circuit-types/src/logs/index.ts +++ b/yarn-project/circuit-types/src/logs/index.ts @@ -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'; diff --git a/yarn-project/end-to-end/src/e2e_event_logs.test.ts b/yarn-project/end-to-end/src/e2e_event_logs.test.ts index e508c603aff..c678873bb49 100644 --- a/yarn-project/end-to-end/src/e2e_event_logs.test.ts +++ b/yarn-project/end-to-end/src/e2e_event_logs.test.ts @@ -1,6 +1,7 @@ import { type AccountWalletWithSecretKey, type AztecNode, + EventMetadata, EventType, Fr, L1EventPayload, @@ -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( + 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())!; @@ -78,7 +83,12 @@ 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( + 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]); @@ -86,7 +96,7 @@ describe('Logs', () => { 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); }); @@ -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( EventType.Encrypted, TestLogContract.events.ExampleEvent0, firstTx.blockNumber!, lastTx.blockNumber! - firstTx.blockNumber! + 1, ); - const collectedEvent0sWithIncoming = await wallets[0].getEvents( + const collectedEvent0sWithIncoming = await wallets[0].getEvents( EventType.Encrypted, TestLogContract.events.ExampleEvent0, firstTx.blockNumber!, @@ -129,7 +140,7 @@ describe('Logs', () => { [wallets[0].getCompleteAddress().publicKeys.masterIncomingViewingPublicKey], ); - const collectedEvent0sWithOutgoing = await wallets[0].getEvents( + const collectedEvent0sWithOutgoing = await wallets[0].getEvents( EventType.Encrypted, TestLogContract.events.ExampleEvent0, firstTx.blockNumber!, @@ -137,7 +148,7 @@ describe('Logs', () => { [wallets[0].getCompleteAddress().publicKeys.masterOutgoingViewingPublicKey], ); - const collectedEvent1s = await wallets[0].getEvents( + const collectedEvent1s = await wallets[0].getEvents( EventType.Encrypted, TestLogContract.events.ExampleEvent1, firstTx.blockNumber!, @@ -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( EventType.Encrypted, TestLogContract.events.ExampleEvent1, firstTx.blockNumber!, @@ -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( EventType.Unencrypted, TestLogContract.events.ExampleEvent0, firstTx.blockNumber!, lastTx.blockNumber! - firstTx.blockNumber! + 1, ); - const collectedEvent1s = await wallets[0].getEvents( + const collectedEvent1s = await wallets[0].getEvents( EventType.Unencrypted, TestLogContract.events.ExampleEvent1, firstTx.blockNumber!, diff --git a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts index fd7f8df6a22..1425c0b16f0 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts @@ -1,5 +1,5 @@ import { BatchCall, EventType } from '@aztec/aztec.js'; -import { TokenContract } from '@aztec/noir-contracts.js'; +import { TokenContract, type Transfer } from '@aztec/noir-contracts.js'; import { TokenContractTest } from './token_contract_test.js'; @@ -42,7 +42,12 @@ describe('e2e_token_contract private transfer recursion', () => { // We should have created a single new note, for the recipient expect(tx.debugInfo?.noteHashes.length).toBe(1); - const events = await wallets[1].getEvents(EventType.Encrypted, TokenContract.events.Transfer, tx.blockNumber!, 1); + const events = await wallets[1].getEvents( + EventType.Encrypted, + TokenContract.events.Transfer, + tx.blockNumber!, + 1, + ); expect(events[0]).toEqual({ from: accounts[0].address, diff --git a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts index 5865ac3343a..ced087e5320 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/transfer_private.test.ts @@ -6,7 +6,7 @@ import { computeAuthWitMessageHash, computeInnerAuthWitHashFromAction, } from '@aztec/aztec.js'; -import { TokenContract } from '@aztec/noir-contracts.js'; +import { TokenContract, type Transfer } from '@aztec/noir-contracts.js'; import { DUPLICATE_NULLIFIER_ERROR } from '../fixtures/fixtures.js'; import { TokenContractTest } from './token_contract_test.js'; @@ -41,7 +41,12 @@ describe('e2e_token_contract transfer private', () => { const tx = await asset.methods.transfer(accounts[1].address, amount).send().wait(); tokenSim.transferPrivate(accounts[0].address, accounts[1].address, amount); - const events = await wallets[1].getEvents(EventType.Encrypted, TokenContract.events.Transfer, tx.blockNumber!, 1); + const events = await wallets[1].getEvents( + EventType.Encrypted, + TokenContract.events.Transfer, + tx.blockNumber!, + 1, + ); expect(events[0]).toEqual({ from: accounts[0].address, diff --git a/yarn-project/pxe/src/pxe_http/pxe_http_server.ts b/yarn-project/pxe/src/pxe_http/pxe_http_server.ts index 7f1f814278b..373b908a270 100644 --- a/yarn-project/pxe/src/pxe_http/pxe_http_server.ts +++ b/yarn-project/pxe/src/pxe_http/pxe_http_server.ts @@ -6,6 +6,7 @@ import { EncryptedL2Log, EncryptedL2NoteLog, EncryptedNoteL2BlockL2Logs, + EventMetadata, ExtendedNote, ExtendedUnencryptedL2Log, L2Block, @@ -27,7 +28,7 @@ import { UniqueNote, } from '@aztec/circuit-types'; import { FunctionSelector, PrivateCircuitPublicInputs, PublicKeys } from '@aztec/circuits.js'; -import { NoteSelector } from '@aztec/foundation/abi'; +import { EventSelector, NoteSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Buffer32 } from '@aztec/foundation/buffer'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -44,43 +45,45 @@ export function createPXERpcServer(pxeService: PXE): JsonRpcServer { return new JsonRpcServer( pxeService, { - CompleteAddress, + AuthWitness, AztecAddress, - TxExecutionRequest, - ExtendedUnencryptedL2Log, - FunctionSelector, - TxHash, Buffer32, + CompleteAddress, EthAddress, - Point, + EventSelector, + ExtendedNote, + ExtendedUnencryptedL2Log, Fr, + FunctionSelector, GrumpkinScalar, + L2Block, + LogId, Note, - ExtendedNote, + Point, PublicKeys, - UniqueNote, SiblingPath, - AuthWitness, - L2Block, TxEffect, - LogId, + TxExecutionRequest, + TxHash, + UniqueNote, }, { - EncryptedNoteL2BlockL2Logs, - EncryptedL2NoteLog, + CountedPublicExecutionRequest, + CountedNoteLog, EncryptedL2Log, - UnencryptedL2Log, + EncryptedL2NoteLog, + EncryptedNoteL2BlockL2Logs, + EventMetadata, NoteSelector, NullifierMembershipWitness, - TxSimulationResult, - TxProvingResult, PrivateCircuitPublicInputs, PrivateExecutionResult, - CountedPublicExecutionRequest, - CountedNoteLog, + TxSimulationResult, + TxProvingResult, Tx, TxReceipt, UnencryptedL2BlockL2Logs, + UnencryptedL2Log, }, ['start', 'stop'], ); diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index c2bea391e5d..0f5bf72ae8b 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -1,7 +1,7 @@ import { type AuthWitness, type AztecNode, - type EventMetadata, + EventMetadata, EventType, type ExtendedNote, type FunctionCall, @@ -49,6 +49,7 @@ import { import { computeNoteHashNonce, siloNullifier } from '@aztec/circuits.js/hash'; import { type AbiDecoded, + type AbiType, type ContractArtifact, EventSelector, FunctionSelector, @@ -830,24 +831,25 @@ export class PXEService implements PXE { public getEvents( type: EventType.Encrypted, - eventMetadata: EventMetadata, + event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] }, from: number, limit: number, vpks: Point[], ): Promise; public getEvents( type: EventType.Unencrypted, - eventMetadata: EventMetadata, + event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] }, from: number, limit: number, ): Promise; public getEvents( type: EventType, - eventMetadata: EventMetadata, + event: { eventSelector: EventSelector; abiType: AbiType; fieldNames: string[] }, from: number, limit: number, vpks: Point[] = [], ): Promise { + const eventMetadata = new EventMetadata(type, event); if (type.includes(EventType.Encrypted)) { return this.getEncryptedEvents(from, limit, eventMetadata, vpks); }