From 22fc411644c847dfa5d39069f9bcf47e3c0ce17b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 5 Feb 2024 19:45:05 -0300 Subject: [PATCH] Track instance deployed events in node --- .../archiver/src/archiver/archiver.ts | 44 +++++++++++- .../aztec-node/src/aztec-node/server.ts | 6 +- .../circuit-types/src/contract_data.ts | 8 ++- .../src/interfaces/aztec-node.ts | 8 ++- .../contract_instance_deployed_event.ts | 68 +++++++++++++++++++ .../circuits.js/src/contract/index.ts | 1 + .../src/e2e_deploy_contract.test.ts | 17 ++++- yarn-project/foundation/src/fields/fields.ts | 4 ++ 8 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 473c3da3abd..e7010057310 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -22,6 +22,7 @@ import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, REGISTERER_CONTRACT_CLASS_REGISTERED_MAGIC_VALUE, } from '@aztec/circuits.js'; +import { ContractInstanceDeployedEvent } from '@aztec/circuits.js/contract'; import { createEthereumChain } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; @@ -73,12 +74,19 @@ export class Archiver implements ArchiveSource { // TODO(@spalladino): Calculate this on the fly somewhere else. // Today this is printed in the logs for end-to-end test at // end-to-end/src/e2e_deploy_contract.test.ts -t 'registering a new contract class' - // as "Added contract ContractClassRegisterer ADDRESS" + // "Added contract ContractClassRegisterer ADDRESS" + // "Added contract ContractInstanceDeployer ADDRESS" + /** Address of the ClassRegisterer contract with a salt=1 */ private classRegistererAddress = AztecAddress.fromString( '0x29c0cd0000951bba8af520ad5513cc53d9f0413c5a24a72a4ba8c17894c0bef9', ); + /** Address of the InstanceDeployer contract with a salt=1 */ + private instanceDeployerAddress = AztecAddress.fromString( + '0x1799c61aa10430bf6fec46679c4cb76c3ed12cd8b6e73ed7389d5ae296ad1b97', + ); + /** * Creates a new instance of the Archiver. * @param publicClient - A client for interacting with the Ethereum node. @@ -288,13 +296,14 @@ export class Archiver implements ArchiveSource { ), ); - // Unroll all logs emitted during the retrieved blocks and extract any contract classes from them + // Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them await Promise.all( retrievedBlocks.retrievedData.map(async block => { const blockLogs = (block.newUnencryptedLogs?.txLogs ?? []) .flatMap(txLog => txLog.unrollLogs()) .map(log => UnencryptedL2Log.fromBuffer(log)); await this.storeRegisteredContractClasses(blockLogs, block.number); + await this.storeDeployedContractInstances(blockLogs, block.number); }), ); @@ -355,6 +364,33 @@ export class Archiver implements ArchiveSource { } } + /** + * Extracts and stores contract instances out of ContractInstanceDeployed events emitted by the canonical deployer contract. + * @param allLogs - All logs emitted in a bunch of blocks. + */ + private async storeDeployedContractInstances(allLogs: UnencryptedL2Log[], blockNum: number) { + const contractInstances: ContractInstanceWithAddress[] = []; + for (const log of allLogs) { + try { + if ( + !log.contractAddress.equals(this.instanceDeployerAddress) || + !ContractInstanceDeployedEvent.isContractInstanceDeployedEvent(log.data) + ) { + continue; + } + const event = ContractInstanceDeployedEvent.fromLogData(log.data); + contractInstances.push(event.toContractInstance()); + } catch (err) { + this.log.warn(`Error processing log ${log.toHumanReadable()}: ${err}`); + } + } + + if (contractInstances.length > 0) { + contractInstances.forEach(c => this.log(`Storing contract instance at ${c.address.toString()}`)); + await this.store.addContractInstances(contractInstances, blockNum); + } + } + /** * Stores extended contract data as classes and instances. * Temporary solution until we source this data from the contract class registerer and instance deployer. @@ -505,6 +541,10 @@ export class Archiver implements ArchiveSource { return this.store.getContractClass(id); } + public getContract(address: AztecAddress): Promise { + return this.store.getContractInstance(address); + } + /** * Gets up to `limit` amount of pending L1 to L2 messages. * @param limit - The number of messages to return. diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index a6a5fb16b41..0cea843f9c2 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -47,7 +47,7 @@ import { SequencerClient, getGlobalVariableBuilder, } from '@aztec/sequencer-client'; -import { ContractClassPublic } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { MerkleTrees, ServerWorldStateSynchronizer, @@ -242,6 +242,10 @@ export class AztecNodeService implements AztecNode { return this.contractDataSource.getContractClass(id); } + public getContract(address: AztecAddress): Promise { + return this.contractDataSource.getContract(address); + } + /** * Gets up to `limit` amount of logs starting from `from`. * @param from - Number of the L2 block to which corresponds the first logs to be returned. diff --git a/yarn-project/circuit-types/src/contract_data.ts b/yarn-project/circuit-types/src/contract_data.ts index aac4f4ded85..fbc6ac12ffe 100644 --- a/yarn-project/circuit-types/src/contract_data.ts +++ b/yarn-project/circuit-types/src/contract_data.ts @@ -8,7 +8,7 @@ import { serializeBufferArrayToVector, serializeToBuffer, } from '@aztec/foundation/serialize'; -import { ContractClassPublic } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; /** * Used for retrieval of contract data (A3 address, portal contract address, bytecode). @@ -62,6 +62,12 @@ export interface ContractDataSource { * @param id - Contract class id. */ getContractClass(id: Fr): Promise; + + /** + * Returns a publicly deployed contract instance given its address. + * @param address - Address of the deployed contract. + */ + getContract(address: AztecAddress): Promise; } /** diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index 85809f4d5d2..10123d17b7d 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -10,7 +10,7 @@ import { import { L1ContractAddresses } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; -import { ContractClassPublic } from '@aztec/types/contracts'; +import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts'; import { ContractData, ExtendedContractData } from '../contract_data.js'; import { L1ToL2MessageAndIndex } from '../l1_to_l2_message.js'; @@ -286,4 +286,10 @@ export interface AztecNode { * @param id - Id of the contract class. */ getContractClass(id: Fr): Promise; + + /** + * Returns a publicly deployed contract instance given its address. + * @param address - Address of the deployed contract. + */ + getContract(address: AztecAddress): Promise; } diff --git a/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts b/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts new file mode 100644 index 00000000000..134b24034b8 --- /dev/null +++ b/yarn-project/circuits.js/src/contract/contract_instance_deployed_event.ts @@ -0,0 +1,68 @@ +import { toBigIntBE } from '@aztec/foundation/bigint-buffer'; +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader } from '@aztec/foundation/serialize'; +import { ContractInstanceWithAddress } from '@aztec/types/contracts'; + +import { DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE } from '../constants.gen.js'; +import { AztecAddress, EthAddress } from '../index.js'; + +/** Event emitted from the ContractInstanceDeployer. */ +export class ContractInstanceDeployedEvent { + constructor( + public readonly address: AztecAddress, + public readonly version: number, + public readonly salt: Fr, + public readonly contractClassId: Fr, + public readonly initializationHash: Fr, + public readonly portalContractAddress: EthAddress, + public readonly publicKeysHash: Fr, + public readonly universalDeploy: boolean, + ) {} + + static isContractInstanceDeployedEvent(log: Buffer) { + return toBigIntBE(log.subarray(0, 32)) == DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE; + } + + static fromLogData(log: Buffer) { + if (!this.isContractInstanceDeployedEvent(log)) { + const magicValue = DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE.toString(16); + throw new Error(`Log data for ContractInstanceDeployedEvent is not prefixed with magic value 0x${magicValue}`); + } + const reader = new BufferReader(log.subarray(32)); + const address = reader.readObject(AztecAddress); + const version = reader.readObject(Fr).toNumber(); + const salt = reader.readObject(Fr); + const contractClassId = reader.readObject(Fr); + const initializationHash = reader.readObject(Fr); + const portalContractAddress = reader.readObject(EthAddress); + const publicKeysHash = reader.readObject(Fr); + const universalDeploy = reader.readObject(Fr).toBool(); + + return new ContractInstanceDeployedEvent( + address, + version, + salt, + contractClassId, + initializationHash, + portalContractAddress, + publicKeysHash, + universalDeploy, + ); + } + + toContractInstance(): ContractInstanceWithAddress { + if (this.version !== 1) { + throw new Error(`Unexpected contract instance version ${this.version}`); + } + + return { + address: this.address, + version: this.version, + contractClassId: this.contractClassId, + initializationHash: this.initializationHash, + portalContractAddress: this.portalContractAddress, + publicKeysHash: this.publicKeysHash, + salt: this.salt, + }; + } +} diff --git a/yarn-project/circuits.js/src/contract/index.ts b/yarn-project/circuits.js/src/contract/index.ts index 5ffe4c961b9..40ce0f0d8a2 100644 --- a/yarn-project/circuits.js/src/contract/index.ts +++ b/yarn-project/circuits.js/src/contract/index.ts @@ -7,3 +7,4 @@ export * from './contract_address.js'; export * from './private_function.js'; export * from './public_bytecode.js'; export * from './contract_class_registered_event.js'; +export * from './contract_instance_deployed_event.js'; diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts index cbe2aeab39d..d5e0dc3acd5 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract.test.ts @@ -388,18 +388,29 @@ describe('e2e_deploy_contract', () => { const deployer = await registerContract(wallet, ContractInstanceDeployerContract, [], new Fr(1)); const salt = Fr.random(); - const initArgs = Fr.ZERO; const portalAddress = EthAddress.random(); - const publicKeysHash = computePublicKeysHash(Point.random()); + const publicKey = Point.random(); + const publicKeysHash = computePublicKeysHash(publicKey); + + const expected = getContractInstanceFromDeployParams(artifact, [], salt, publicKey, portalAddress); const tx = await deployer.methods - .deploy(salt, contractClass.id, initArgs, portalAddress, publicKeysHash, false) + .deploy(salt, contractClass.id, expected.initializationHash, portalAddress, publicKeysHash, false) .send() .wait(); const logs = await pxe.getUnencryptedLogs({ txHash: tx.txHash }); const deployedLog = logs.logs[0].log; expect(deployedLog.contractAddress).toEqual(deployer.address); + + const instance = await aztecNode.getContract(expected.address); + expect(instance).toBeDefined(); + expect(instance!.address).toEqual(expected.address); + expect(instance!.contractClassId).toEqual(contractClass.id); + expect(instance!.initializationHash).toEqual(expected.initializationHash); + expect(instance!.portalContractAddress).toEqual(portalAddress); + expect(instance!.publicKeysHash).toEqual(publicKeysHash); + expect(instance!.salt).toEqual(salt); }); }); }); diff --git a/yarn-project/foundation/src/fields/fields.ts b/yarn-project/foundation/src/fields/fields.ts index 0aebb8fa3cc..28e0a64dad8 100644 --- a/yarn-project/foundation/src/fields/fields.ts +++ b/yarn-project/foundation/src/fields/fields.ts @@ -95,6 +95,10 @@ abstract class BaseField { return this.asBigInt; } + toBool(): boolean { + return Boolean(this.toBigInt()); + } + toNumber(): number { const value = this.toBigInt(); if (value > Number.MAX_SAFE_INTEGER) {