diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index f056d687296f..fd4f10b6549d 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -14,7 +14,7 @@ import { protocolContractNames } from '@aztec/protocol-contracts'; import { BundledProtocolContractsProvider } from '@aztec/protocol-contracts/providers/bundle'; import { FunctionType, decodeFunctionSignature } from '@aztec/stdlib/abi'; import type { ArchiverEmitter } from '@aztec/stdlib/block'; -import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; +import { type ContractClassPublicWithCommitment, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers'; import { getTelemetryClient } from '@aztec/telemetry-client'; @@ -187,8 +187,10 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) { continue; } - const contractClassPublic: ContractClassPublic = { + const publicBytecodeCommitment = await computePublicBytecodeCommitment(contract.contractClass.packedBytecode); + const contractClassPublic: ContractClassPublicWithCommitment = { ...contract.contractClass, + publicBytecodeCommitment, privateFunctions: [], utilityFunctions: [], }; @@ -198,8 +200,7 @@ export async function registerProtocolContracts(store: KVArchiverDataStore) { .map(fn => decodeFunctionSignature(fn.name, fn.parameters)); await store.registerContractFunctionSignatures(publicFunctionSignatures); - const bytecodeCommitment = await computePublicBytecodeCommitment(contractClassPublic.packedBytecode); - await store.addContractClasses([contractClassPublic], [bytecodeCommitment], BlockNumber(blockNumber)); + await store.addContractClasses([contractClassPublic], BlockNumber(blockNumber)); await store.addContractInstances([contract.instance], BlockNumber(blockNumber)); } } diff --git a/yarn-project/archiver/src/modules/data_store_updater.ts b/yarn-project/archiver/src/modules/data_store_updater.ts index 6bf2975b3c3f..a490a80e84f9 100644 --- a/yarn-project/archiver/src/modules/data_store_updater.ts +++ b/yarn-project/archiver/src/modules/data_store_updater.ts @@ -13,9 +13,10 @@ import { import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block'; import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint'; import { + type ContractClassPublicWithCommitment, type ExecutablePrivateFunctionWithMembershipProof, type UtilityFunctionWithMembershipProof, - computePublicBytecodeCommitment, + computeContractClassId, isValidPrivateFunctionMembershipProof, isValidUtilityFunctionMembershipProof, } from '@aztec/stdlib/contract'; @@ -321,18 +322,37 @@ export class ArchiverDataStoreUpdater { .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log)) .map(log => ContractClassPublishedEvent.fromLog(log)); - const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic())); - if (contractClasses.length > 0) { - contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); - if (operation == Operation.Store) { - // TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive - const commitments = await Promise.all( - contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)), - ); - return await this.store.addContractClasses(contractClasses, commitments, blockNum); - } else if (operation == Operation.Delete) { + if (operation == Operation.Delete) { + const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic()); + if (contractClasses.length > 0) { + contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); return await this.store.deleteContractClasses(contractClasses, blockNum); } + return true; + } + + // Compute bytecode commitments and validate class IDs in a single pass. + const contractClasses: ContractClassPublicWithCommitment[] = []; + for (const event of contractClassPublishedEvents) { + const contractClass = await event.toContractClassPublicWithBytecodeCommitment(); + const computedClassId = await computeContractClassId({ + artifactHash: contractClass.artifactHash, + privateFunctionsRoot: contractClass.privateFunctionsRoot, + publicBytecodeCommitment: contractClass.publicBytecodeCommitment, + }); + if (!computedClassId.equals(contractClass.id)) { + this.log.warn( + `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`, + { blockNum, contractClassId: event.contractClassId.toString() }, + ); + continue; + } + contractClasses.push(contractClass); + } + + if (contractClasses.length > 0) { + contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`)); + return await this.store.addContractClasses(contractClasses, blockNum); } return true; } diff --git a/yarn-project/archiver/src/store/kv_archiver_store.test.ts b/yarn-project/archiver/src/store/kv_archiver_store.test.ts index 5b7c188ccf78..ca9558c67c73 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.test.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.test.ts @@ -25,6 +25,7 @@ import { import { Checkpoint, PublishedCheckpoint, randomCheckpointInfo } from '@aztec/stdlib/checkpoint'; import { type ContractClassPublic, + type ContractClassPublicWithCommitment, type ContractInstanceWithAddress, SerializableContractInstance, computePublicBytecodeCommitment, @@ -78,6 +79,13 @@ async function addProposedBlocks( return result; } +async function withCommitment(contractClass: ContractClassPublic): Promise { + return { + ...contractClass, + publicBytecodeCommitment: await computePublicBytecodeCommitment(contractClass.packedBytecode), + }; +} + describe('KVArchiverDataStore', () => { let store: KVArchiverDataStore; let publishedCheckpoints: PublishedCheckpoint[]; @@ -2185,11 +2193,7 @@ describe('KVArchiverDataStore', () => { beforeEach(async () => { contractClass = await makeContractClassPublic(); - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum), - ); + await store.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum)); }); it('returns previously stored contract class', async () => { @@ -2203,14 +2207,15 @@ describe('KVArchiverDataStore', () => { it('throws if the same contract class is added again', async () => { await expect( - store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum + 1), - ), + store.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum + 1)), ).rejects.toThrow(/already exists/); }); + it('returns contract class if deleted at a later block number', async () => { + await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1)); + await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); + }); + it('returns undefined if contract class is not found', async () => { await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined(); }); @@ -3091,21 +3096,17 @@ describe('KVArchiverDataStore', () => { it('throws when adding the same contract class twice', async () => { const contractClass = await makeContractClassPublic(); - const commitment = await computePublicBytecodeCommitment(contractClass.packedBytecode); + const contractClassWithCommitment = await withCommitment(contractClass); - await store.addContractClasses([contractClass], [commitment], BlockNumber(1)); - await expect(store.addContractClasses([contractClass], [commitment], BlockNumber(2))).rejects.toThrow( + await store.addContractClasses([contractClassWithCommitment], BlockNumber(1)); + await expect(store.addContractClasses([contractClassWithCommitment], BlockNumber(2))).rejects.toThrow( /already exists/, ); }); it('throws when adding the same contract instance twice', async () => { const contractClass = await makeContractClassPublic(); - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(1), - ); + await store.addContractClasses([await withCommitment(contractClass)], BlockNumber(1)); const instance = { ...(await SerializableContractInstance.random({ diff --git a/yarn-project/archiver/src/store/kv_archiver_store.ts b/yarn-project/archiver/src/store/kv_archiver_store.ts index 7db1342c198b..3279db17ef63 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.ts @@ -16,6 +16,7 @@ import { import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint'; import type { ContractClassPublic, + ContractClassPublicWithCommitment, ContractDataSource, ContractInstanceUpdateWithAddress, ContractInstanceWithAddress, @@ -168,19 +169,14 @@ export class KVArchiverDataStore implements ContractDataSource { /** * Add new contract classes from an L2 block to the store's list. - * @param data - List of contract classes to be added. - * @param bytecodeCommitments - Bytecode commitments for the contract classes. + * @param data - List of contract classes (with bytecode commitments) to be added. * @param blockNumber - Number of the L2 block the contracts were registered in. * @returns True if the operation is successful. */ - async addContractClasses( - data: ContractClassPublic[], - bytecodeCommitments: Fr[], - blockNumber: BlockNumber, - ): Promise { + async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise { return ( await Promise.all( - data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)), + data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)), ) ).every(Boolean); } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts index 2820c3a108b6..3f3c4ae17d00 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.test.ts @@ -1,14 +1,18 @@ import { CONTRACT_CLASS_LOG_SIZE_IN_FIELDS, + CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS, } from '@aztec/constants'; import { timesParallel } from '@aztec/foundation/collection'; import { randomInt } from '@aztec/foundation/crypto/random'; import { Fr } from '@aztec/foundation/curves/bn254'; +import { ProtocolContractAddress } from '@aztec/protocol-contracts'; +import { bufferAsFields } from '@aztec/stdlib/abi'; import { AztecAddress } from '@aztec/stdlib/aztec-address'; +import { computeContractClassId, computePublicBytecodeCommitment } from '@aztec/stdlib/contract'; import { LogHash, ScopedLogHash } from '@aztec/stdlib/kernel'; -import { ContractClassLogFields } from '@aztec/stdlib/logs'; +import { ContractClassLog, ContractClassLogFields } from '@aztec/stdlib/logs'; import { mockTx } from '@aztec/stdlib/testing'; import { TX_ERROR_CALLDATA_COUNT_MISMATCH, @@ -17,6 +21,8 @@ import { TX_ERROR_CONTRACT_CLASS_LOG_COUNT, TX_ERROR_CONTRACT_CLASS_LOG_LENGTH, TX_ERROR_INCORRECT_CALLDATA, + TX_ERROR_INCORRECT_CONTRACT_CLASS_ID, + TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG, type Tx, } from '@aztec/stdlib/tx'; @@ -243,4 +249,119 @@ describe('TxDataValidator', () => { await expectInvalid(badTxs[0], TX_ERROR_CONTRACT_CLASS_LOG_LENGTH); }); + + describe('contract class id validation', () => { + /** + * Builds a ContractClassLog encoding a ContractClassPublishedEvent. + * Layout: [magic, contractClassId, version, artifactHash, privateFunctionsRoot, ...bytecodeAsFields] + */ + async function buildContractClassLog(opts?: { contractClassId?: Fr }): Promise<{ + log: ContractClassLog; + emittedLength: number; + }> { + const artifactHash = Fr.random(); + const privateFunctionsRoot = Fr.random(); + const packedBytecode = Buffer.from('aabbccdd', 'hex'); + + const bytecodeCommitment = await computePublicBytecodeCommitment(packedBytecode); + const correctClassId = await computeContractClassId({ + artifactHash, + privateFunctionsRoot, + publicBytecodeCommitment: bytecodeCommitment, + }); + const contractClassId = opts?.contractClassId ?? correctClassId; + + const bytecodeFields = bufferAsFields(packedBytecode, CONTRACT_CLASS_LOG_SIZE_IN_FIELDS); + let lastNonZero = bytecodeFields.length - 1; + while (lastNonZero >= 0 && bytecodeFields[lastNonZero].isZero()) { + lastNonZero--; + } + const bytecodeEmittedFields = bytecodeFields.slice(0, lastNonZero + 1); + + const headerFields = [ + new Fr(CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE), + contractClassId, + new Fr(1), // version + artifactHash, + privateFunctionsRoot, + ]; + + const emittedFields = [...headerFields, ...bytecodeEmittedFields]; + const emittedLength = emittedFields.length; + + const allFields = [ + ...emittedFields, + ...Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - emittedFields.length).fill(Fr.ZERO), + ]; + + const fields = new ContractClassLogFields(allFields); + const log = new ContractClassLog(ProtocolContractAddress.ContractClassRegistry, fields, emittedLength); + return { log, emittedLength }; + } + + async function injectContractClassLog(tx: Tx, log: ContractClassLog, emittedLength: number) { + tx.contractClassLogFields.push(log.fields); + const logHashes = tx.data.forPublic!.nonRevertibleAccumulatedData.contractClassLogsHashes; + const emptyIdx = logHashes.findIndex(h => h.isEmpty()); + if (emptyIdx >= 0) { + logHashes[emptyIdx] = LogHash.from({ + value: await log.fields.hash(), + length: emittedLength, + }).scope(log.contractAddress); + } + } + + it('allows transactions with correct contract class ids', async () => { + const tx = await mockTx(2, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const { log, emittedLength } = await buildContractClassLog(); + await injectContractClassLog(tx, log, emittedLength); + await tx.recomputeHash(); + await expect(validator.validateTx(tx)).resolves.toEqual({ result: 'valid' }); + }); + + it('rejects transactions with incorrect contract class ids', async () => { + const tx = await mockTx(3, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const { log, emittedLength } = await buildContractClassLog({ contractClassId: Fr.random() }); + await injectContractClassLog(tx, log, emittedLength); + await tx.recomputeHash(); + await expect(validator.validateTx(tx)).resolves.toEqual({ + result: 'invalid', + reason: [TX_ERROR_INCORRECT_CONTRACT_CLASS_ID], + }); + }); + + it('rejects transactions with malformed contract class logs', async () => { + const tx = await mockTx(4, { + numberOfNonRevertiblePublicCallRequests: 1, + numberOfRevertiblePublicCallRequests: 0, + }); + const headerFields = [ + new Fr(CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE), + Fr.random(), + new Fr(1), + Fr.random(), + Fr.random(), + new Fr(999999), // bogus bytecode length + ]; + const allFields = [ + ...headerFields, + ...Array(CONTRACT_CLASS_LOG_SIZE_IN_FIELDS - headerFields.length).fill(Fr.ZERO), + ]; + const fields = new ContractClassLogFields(allFields); + const log = new ContractClassLog(ProtocolContractAddress.ContractClassRegistry, fields, headerFields.length); + await injectContractClassLog(tx, log, headerFields.length); + await tx.recomputeHash(); + const result = await validator.validateTx(tx); + expect(result.result).toBe('invalid'); + expect(result.result === 'invalid' && result.reason[0]).toMatch( + new RegExp(`${TX_ERROR_INCORRECT_CONTRACT_CLASS_ID}|${TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG}`), + ); + }); + }); }); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts index 692e4705e186..7c284b6d0ce3 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/data_validator.ts @@ -1,5 +1,7 @@ import { MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS } from '@aztec/constants'; import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log'; +import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry'; +import { computeContractClassId } from '@aztec/stdlib/contract'; import { computeCalldataHash } from '@aztec/stdlib/hash'; import { TX_ERROR_CALLDATA_COUNT_MISMATCH, @@ -9,7 +11,9 @@ import { TX_ERROR_CONTRACT_CLASS_LOG_LENGTH, TX_ERROR_CONTRACT_CLASS_LOG_SORTING, TX_ERROR_INCORRECT_CALLDATA, + TX_ERROR_INCORRECT_CONTRACT_CLASS_ID, TX_ERROR_INCORRECT_HASH, + TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG, Tx, type TxValidationResult, type TxValidator, @@ -26,7 +30,8 @@ export class DataTxValidator implements TxValidator { const reason = (await this.#hasCorrectHash(tx)) ?? (await this.#hasCorrectCalldata(tx)) ?? - (await this.#hasCorrectContractClassLogs(tx)); + (await this.#hasCorrectContractClassLogs(tx)) ?? + (await this.#hasCorrectContractClassIds(tx)); return reason ? { result: 'invalid', reason: [reason] } : { result: 'valid' }; } @@ -127,4 +132,40 @@ export class DataTxValidator implements TxValidator { return undefined; } + + async #hasCorrectContractClassIds(tx: Tx): Promise { + const contractClassLogs = tx.getContractClassLogs(); + for (const log of contractClassLogs) { + if (!ContractClassPublishedEvent.isContractClassPublishedEvent(log)) { + continue; + } + + let event; + try { + event = ContractClassPublishedEvent.fromLog(log); + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to parse contract class event: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG; + } + + try { + const { publicBytecodeCommitment } = await event.toContractClassPublicWithBytecodeCommitment(); + const computedClassId = await computeContractClassId({ + artifactHash: event.artifactHash, + privateFunctionsRoot: event.privateFunctionsRoot, + publicBytecodeCommitment, + }); + if (!computedClassId.equals(event.contractClassId)) { + this.#log.warn( + `Rejecting tx ${tx.getTxHash()}: contract class id mismatch. Claimed ${event.contractClassId}, computed ${computedClassId}`, + ); + return TX_ERROR_INCORRECT_CONTRACT_CLASS_ID; + } + } catch (e) { + this.#log.warn(`Rejecting tx ${tx.getTxHash()}: failed to compute contract class id: ${e}`); + return TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG; + } + } + return undefined; + } } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts index 69d5bd9f0cab..39362bd39dd2 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts @@ -40,7 +40,7 @@ export class PhasesTxValidator implements TxValidator { // which are needed for public FPC flows, but fail if the account contract hasnt been deployed yet, // which is what we're trying to do as part of the current txs. // We only need to create/revert checkpoint here because of this addNewContracts call. - await this.contractsDB.addNewContracts(tx); + this.contractsDB.addNewContracts(tx); if (!tx.data.forPublic) { this.#log.debug( diff --git a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts index 73234f5cc6a3..a7aea045ff8d 100644 --- a/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts +++ b/yarn-project/protocol-contracts/src/class-registry/contract_class_published_event.ts @@ -4,7 +4,7 @@ import { FieldReader } from '@aztec/foundation/serialize'; import { bufferFromFields } from '@aztec/stdlib/abi'; import { type ContractClassPublic, - computeContractClassId, + type ContractClassPublicWithCommitment, computePublicBytecodeCommitment, } from '@aztec/stdlib/contract'; import type { ContractClassLog } from '@aztec/stdlib/logs'; @@ -47,34 +47,25 @@ export class ContractClassPublishedEvent { ); } - async toContractClassPublic(): Promise { - const computedClassId = await computeContractClassId({ - artifactHash: this.artifactHash, - privateFunctionsRoot: this.privateFunctionsRoot, - publicBytecodeCommitment: await computePublicBytecodeCommitment(this.packedPublicBytecode), - }); - - if (!computedClassId.equals(this.contractClassId)) { - throw new Error( - `Invalid contract class id: computed ${computedClassId.toString()} but event broadcasted ${this.contractClassId.toString()}`, - ); - } - - if (this.version !== 1) { - throw new Error(`Unexpected contract class version ${this.version}`); - } - + /** Converts the event to a contract class, without computing or validating the bytecode commitment. */ + toContractClassPublic(): ContractClassPublic { return { id: this.contractClassId, artifactHash: this.artifactHash, packedBytecode: this.packedPublicBytecode, privateFunctionsRoot: this.privateFunctionsRoot, - version: this.version, + version: this.version as 1, privateFunctions: [], utilityFunctions: [], }; } + /** Converts the event to a contract class with its bytecode commitment (expensive). */ + async toContractClassPublicWithBytecodeCommitment(): Promise { + const publicBytecodeCommitment = await computePublicBytecodeCommitment(this.packedPublicBytecode); + return { ...this.toContractClassPublic(), publicBytecodeCommitment }; + } + public static extractContractClassEvents(logs: ContractClassLog[]): ContractClassPublishedEvent[] { return logs .filter((log: ContractClassLog) => ContractClassPublishedEvent.isContractClassPublishedEvent(log)) diff --git a/yarn-project/simulator/src/public/public_db_sources.ts b/yarn-project/simulator/src/public/public_db_sources.ts index 445949934694..144a39182563 100644 --- a/yarn-project/simulator/src/public/public_db_sources.ts +++ b/yarn-project/simulator/src/public/public_db_sources.ts @@ -55,10 +55,10 @@ export class PublicContractsDB implements PublicContractsDBInterface { this.log = createLogger('simulator:contracts-data-source', bindings); } - public async addContracts(contractDeploymentData: ContractDeploymentData): Promise { + public addContracts(contractDeploymentData: ContractDeploymentData): void { const currentState = this.getCurrentState(); - await this.addContractClassesFromEvents( + this.addContractClassesFromEvents( ContractClassPublishedEvent.extractContractClassEvents(contractDeploymentData.getContractClassLogs()), currentState, ); @@ -69,10 +69,10 @@ export class PublicContractsDB implements PublicContractsDBInterface { ); } - public async addNewContracts(tx: Tx): Promise { + public addNewContracts(tx: Tx): void { const contractDeploymentData = AllContractDeploymentData.fromTx(tx); - await this.addContracts(contractDeploymentData.getNonRevertibleContractDeploymentData()); - await this.addContracts(contractDeploymentData.getRevertibleContractDeploymentData()); + this.addContracts(contractDeploymentData.getNonRevertibleContractDeploymentData()); + this.addContracts(contractDeploymentData.getRevertibleContractDeploymentData()); } /** @@ -174,17 +174,15 @@ export class PublicContractsDB implements PublicContractsDBInterface { return await this.dataSource.getDebugFunctionName(address, selector); } - private async addContractClassesFromEvents( + private addContractClassesFromEvents( contractClassEvents: ContractClassPublishedEvent[], state: ContractsDbCheckpoint, ) { - await Promise.all( - contractClassEvents.map(async (event: ContractClassPublishedEvent) => { - this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`); - const contractClass = await event.toContractClassPublic(); - state.addClass(event.contractClassId, contractClass); - }), - ); + for (const event of contractClassEvents) { + this.log.debug(`Adding class ${event.contractClassId.toString()} to contract state`); + const contractClass = event.toContractClassPublic(); + state.addClass(event.contractClassId, contractClass); + } } private addContractInstancesFromEvents( diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts index 23a019bb6080..abc3aedf918e 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.test.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.test.ts @@ -361,8 +361,8 @@ describe('public_processor', () => { // we want to confirm that even non-revertibles get cleared const contractClassId = await mockContractClassForTx(tx, /*revertible=*/ false); - publicTxSimulator.simulate.mockImplementation(async (simulatedTx: Tx) => { - await contractsDB.addNewContracts(simulatedTx); + publicTxSimulator.simulate.mockImplementation((simulatedTx: Tx) => { + contractsDB.addNewContracts(simulatedTx); throw new Error('Uncaught error'); }); diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.ts b/yarn-project/simulator/src/public/public_processor/public_processor.ts index f8b708d0a568..29a4f6aaadbf 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.ts @@ -548,7 +548,7 @@ export class PublicProcessor implements Traceable { // Fee payment insertion has already been done. Do the rest. await this.doTreeInsertionsForPrivateOnlyTx(processedTx); - await this.contractsDB.addNewContracts(tx); + this.contractsDB.addNewContracts(tx); return [processedTx, undefined, []]; } diff --git a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts index 6568a47228e9..9077b8676c1e 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/contract_provider_for_cpp.ts @@ -52,6 +52,7 @@ export class ContractProviderForCpp implements ContractProvider { return serializeWithMessagePack(contractClass); }; + // eslint-disable-next-line require-await public addContracts = async (contractDeploymentDataBuffer: Buffer): Promise => { this.log.trace(`Contract provider callback: addContracts`); @@ -62,7 +63,7 @@ export class ContractProviderForCpp implements ContractProvider { // Add contracts to the contracts DB this.log.trace(`Calling contractsDB.addContracts`); - await this.contractsDB.addContracts(contractDeploymentData); + this.contractsDB.addContracts(contractDeploymentData); }; public getBytecodeCommitment = async (classId: string): Promise => { diff --git a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts index c07f0d04945c..4e2b95c21c7c 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator/public_tx_simulator.ts @@ -401,7 +401,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { // However, things work as expected because later calls to getters on the hintingContractsDB // will pick up the new contracts and will generate the necessary hints. // So, a consumer of the hints will always see the new contracts. - await this.contractsDB.addContracts(context.nonRevertibleContractDeploymentData); + this.contractsDB.addContracts(context.nonRevertibleContractDeploymentData); } /** @@ -486,7 +486,7 @@ export class PublicTxSimulator implements PublicTxSimulatorInterface { // However, things work as expected because later calls to getters on the hintingContractsDB // will pick up the new contracts and will generate the necessary hints. // So, a consumer of the hints will always see the new contracts. - await this.contractsDB.addContracts(context.revertibleContractDeploymentData); + this.contractsDB.addContracts(context.revertibleContractDeploymentData); } private async payFee(context: PublicTxContext) { diff --git a/yarn-project/stdlib/src/contract/contract_class_id.test.ts b/yarn-project/stdlib/src/contract/contract_class_id.test.ts index 224957aa6431..04df3321ab02 100644 --- a/yarn-project/stdlib/src/contract/contract_class_id.test.ts +++ b/yarn-project/stdlib/src/contract/contract_class_id.test.ts @@ -1,6 +1,10 @@ import { Fr } from '@aztec/foundation/curves/bn254'; +import { createLogger } from '@aztec/foundation/log'; +import { elapsed } from '@aztec/foundation/timer'; import { FunctionSelector } from '../abi/function_selector.js'; +import { getBenchmarkContractArtifact, getTestContractArtifact, getTokenContractArtifact } from '../tests/fixtures.js'; +import { getContractClassFromArtifact } from './contract_class.js'; import { computeContractClassId } from './contract_class_id.js'; import type { ContractClass } from './interfaces/contract_class.js'; @@ -18,5 +22,20 @@ describe('ContractClass', () => { `"0x2926577ccab09f8e4600550792066ed9d6ce530a973ac2b81a36eaebee56ad44"`, ); }); + + it('calculates the contract class id for a real contract artifact', async () => { + const artifacts = [getBenchmarkContractArtifact(), getTokenContractArtifact(), getTestContractArtifact()]; + const logger = createLogger('stdlib:contract_class_id:test'); + + for (const artifact of artifacts) { + const contractClass = await getContractClassFromArtifact(artifact); + + const [ms, contractClassId] = await elapsed(computeContractClassId(contractClass)); + logger.info(`Computed contract class id ${contractClassId} in ${ms}ms`); + + expect(contractClassId.toString()).toHaveLength(66); // 0x + 64 hex chars + expect(contractClassId.toBigInt()).toBeGreaterThan(0n); + } + }); }); }); diff --git a/yarn-project/stdlib/src/tx/validator/error_texts.ts b/yarn-project/stdlib/src/tx/validator/error_texts.ts index 6a8326f032a8..bcc100c2d9b0 100644 --- a/yarn-project/stdlib/src/tx/validator/error_texts.ts +++ b/yarn-project/stdlib/src/tx/validator/error_texts.ts @@ -41,5 +41,13 @@ export const TX_ERROR_SIZE_ABOVE_LIMIT = 'Transaction size above size limit'; // Block header export const TX_ERROR_BLOCK_HEADER = 'Block header not found'; +// Contract instance +export const TX_ERROR_INCORRECT_CONTRACT_ADDRESS = 'Incorrect contract instance deployment address'; +export const TX_ERROR_MALFORMED_CONTRACT_INSTANCE_LOG = 'Failed to parse contract instance deployment log'; + +// Contract class +export const TX_ERROR_INCORRECT_CONTRACT_CLASS_ID = 'Incorrect contract class id'; +export const TX_ERROR_MALFORMED_CONTRACT_CLASS_LOG = 'Failed to parse contract class registration log'; + // General export const TX_ERROR_DURING_VALIDATION = 'Unexpected error during validation';