diff --git a/yarn-project/pxe/src/database/contracts/contract_artifact_db.ts b/yarn-project/pxe/src/database/contracts/contract_artifact_db.ts index 85f726d0973..3f6e6bb2311 100644 --- a/yarn-project/pxe/src/database/contracts/contract_artifact_db.ts +++ b/yarn-project/pxe/src/database/contracts/contract_artifact_db.ts @@ -9,6 +9,7 @@ export interface ContractArtifactDatabase { * Adds a new contract artifact to the database or updates an existing one. * @param id - Id of the corresponding contract class. * @param contract - Contract artifact to add. + * @throws - If there are duplicate private function selectors. */ addContractArtifact(id: Fr, contract: ContractArtifact): Promise; /** diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index 9421b66231d..bbcc83869c0 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -15,7 +15,7 @@ import { SerializableContractInstance, computePoint, } from '@aztec/circuits.js'; -import { type ContractArtifact } from '@aztec/foundation/abi'; +import { type ContractArtifact, FunctionSelector, FunctionType } from '@aztec/foundation/abi'; import { toBufferBE } from '@aztec/foundation/bigint-buffer'; import { Fr } from '@aztec/foundation/fields'; import { @@ -142,6 +142,19 @@ export class KVPxeDatabase implements PxeDatabase { } public async addContractArtifact(id: Fr, contract: ContractArtifact): Promise { + const privateSelectors = contract.functions + .filter(functionArtifact => functionArtifact.functionType === FunctionType.PRIVATE) + .map(privateFunctionArtifact => + FunctionSelector.fromNameAndParameters( + privateFunctionArtifact.name, + privateFunctionArtifact.parameters, + ).toString(), + ); + + if (privateSelectors.length !== new Set(privateSelectors).size) { + throw new Error('Repeated function selectors of private functions'); + } + await this.#contractArtifacts.set(id.toString(), contractArtifactToBuffer(contract)); } diff --git a/yarn-project/pxe/src/database/pxe_database_test_suite.ts b/yarn-project/pxe/src/database/pxe_database_test_suite.ts index 465aa153d5f..adb494619b3 100644 --- a/yarn-project/pxe/src/database/pxe_database_test_suite.ts +++ b/yarn-project/pxe/src/database/pxe_database_test_suite.ts @@ -8,9 +8,11 @@ import { computePoint, } from '@aztec/circuits.js'; import { makeHeader } from '@aztec/circuits.js/testing'; +import { FunctionType } from '@aztec/foundation/abi'; import { randomInt } from '@aztec/foundation/crypto'; import { Fr, Point } from '@aztec/foundation/fields'; import { BenchmarkingContractArtifact } from '@aztec/noir-contracts.js/Benchmarking'; +import { TestContractArtifact } from '@aztec/noir-contracts.js/Test'; import { IncomingNoteDao } from './incoming_note_dao.js'; import { OutgoingNoteDao } from './outgoing_note_dao.js'; @@ -439,6 +441,19 @@ export function describePxeDatabase(getDatabase: () => PxeDatabase) { await expect(database.getContractArtifact(id)).resolves.toEqual(artifact); }); + it('does not store a contract artifact with a duplicate private function selector', async () => { + const artifact = TestContractArtifact; + const index = artifact.functions.findIndex(fn => fn.functionType === FunctionType.PRIVATE); + + const copiedFn = structuredClone(artifact.functions[index]); + artifact.functions.push(copiedFn); + + const id = Fr.random(); + await expect(database.addContractArtifact(id, artifact)).rejects.toThrow( + 'Repeated function selectors of private functions', + ); + }); + it('stores a contract instance', async () => { const address = AztecAddress.random(); const instance = SerializableContractInstance.random().withAddress(address);