diff --git a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr index 6c90b6d4cf5..ba8478b6f46 100644 --- a/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_class_registerer_contract/src/main.nr @@ -210,4 +210,9 @@ contract ContractClassRegisterer { LogHash { value: log_hash, counter, length: 40 + (N as Field) * 32 }, ); } + + #[private] + fn assert_class_id_is_registered(contract_class_id: ContractClassId) { + context.push_nullifier_read_request(contract_class_id.to_field()); + } } diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/Nargo.toml index e1fcbb12317..ca745f5639e 100644 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/Nargo.toml @@ -6,3 +6,4 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } +contract_class_registerer = { path = "../contract_class_registerer_contract" } diff --git a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr index 12a591d70d3..9a242a23258 100644 --- a/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/contract_instance_deployer_contract/src/main.nr @@ -5,12 +5,13 @@ contract ContractInstanceDeployer { use dep::aztec::macros::{events::event, functions::private}; use dep::aztec::protocol_types::{ address::{AztecAddress, PartialAddress}, - constants::DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, + constants::{DEPLOYER_CONTRACT_INSTANCE_DEPLOYED_MAGIC_VALUE, REGISTERER_CONTRACT_ADDRESS}, contract_class_id::ContractClassId, public_keys::PublicKeys, traits::Serialize, utils::arrays::array_concat, }; + use dep::contract_class_registerer::ContractClassRegisterer; use std::meta::derive; #[derive(Serialize)] @@ -85,7 +86,11 @@ contract ContractInstanceDeployer { public_keys: PublicKeys, universal_deploy: bool, ) { - // TODO(@spalladino): assert nullifier_exists silo(contract_class_id, ContractClassRegisterer) + // contract class must be registered to deploy an instance + ContractClassRegisterer::at(REGISTERER_CONTRACT_ADDRESS) + .assert_class_id_is_registered(contract_class_id) + .call(&mut context); + let deployer = if universal_deploy { AztecAddress::zero() } else { diff --git a/yarn-project/accounts/src/testing/create_account.ts b/yarn-project/accounts/src/testing/create_account.ts index 15f9a620057..3dc7568bbb7 100644 --- a/yarn-project/accounts/src/testing/create_account.ts +++ b/yarn-project/accounts/src/testing/create_account.ts @@ -38,13 +38,23 @@ export async function createAccounts( // Prepare deployments const accountsAndDeployments = await Promise.all( - secrets.map(async secret => { + secrets.map(async (secret, index) => { const signingKey = deriveSigningKey(secret); const account = getSchnorrAccount(pxe, secret, signingKey); + + // only register the contract class once + let skipClassRegistration = true; + if (index === 0) { + // for the first account, check if the contract class is already registered, otherwise we should register now + if (!(await pxe.isContractClassPubliclyRegistered(account.getInstance().contractClassId))) { + skipClassRegistration = false; + } + } + const deployMethod = await account.getDeployMethod(); const provenTx = await deployMethod.prove({ contractAddressSalt: account.salt, - skipClassRegistration: true, + skipClassRegistration, skipPublicDeployment: true, universalDeploy: true, }); diff --git a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts index da4af23cdcc..152cb475163 100644 --- a/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts @@ -97,7 +97,7 @@ export class BlacklistTokenContractTest { this.admin = this.wallets[0]; this.other = this.wallets[1]; this.blacklisted = this.wallets[2]; - this.accounts = await pxe.getRegisteredAccounts(); + this.accounts = accountManagers.map(a => a.getCompleteAddress()); }); await this.snapshotManager.snapshot( diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts index b0d724f44f5..966137857cd 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts @@ -88,8 +88,8 @@ export class CrossChainMessagingTest { async ({ accountKeys }, { pxe, aztecNodeConfig, aztecNode, deployL1ContractsValues }) => { const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], 1)); this.wallets = await Promise.all(accountManagers.map(a => a.getWallet())); + this.accounts = accountManagers.map(a => a.getCompleteAddress()); this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`)); - this.accounts = await pxe.getRegisteredAccounts(); this.rollup = getContract({ address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), diff --git a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts index d0695e4200b..881a587c89c 100644 --- a/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts +++ b/yarn-project/end-to-end/src/e2e_deploy_contract/deploy_method.test.ts @@ -31,6 +31,15 @@ describe('e2e_deploy_contract deploy method', () => { afterAll(() => t.teardown()); + it('refused to deploy a contract instance whose contract class is not yet registered', async () => { + const owner = wallet.getAddress(); + const opts = { skipClassRegistration: true }; + logger.debug(`Trying to deploy contract instance without registering its contract class`); + await expect(StatefulTestContract.deploy(wallet, owner, owner, 42).send(opts).wait()).rejects.toThrow( + /Cannot find the leaf for nullifier/, + ); + }); + it('publicly deploys and initializes a contract', async () => { const owner = wallet.getAddress(); logger.debug(`Deploying stateful test contract`); diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index 11a67f90b41..4f971207761 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -115,7 +115,7 @@ export class FullProverTest { this.keys = accountKeys; const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], SALT)); this.wallets = await Promise.all(accountManagers.map(a => a.getWallet())); - this.accounts = await pxe.getRegisteredAccounts(); + this.accounts = accountManagers.map(a => a.getCompleteAddress()); this.wallets.forEach((w, i) => this.logger.verbose(`Wallet ${i} address: ${w.getAddress()}`)); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index 98b117652c7..fa71ade208d 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -4,6 +4,7 @@ import { DocsExampleContract } from '@aztec/noir-contracts.js/DocsExample'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { jest } from '@jest/globals'; +import { strict as assert } from 'assert'; import { type ISnapshotManager, @@ -48,7 +49,20 @@ export class TokenContractTest { await this.snapshotManager.snapshot('3_accounts', addAccounts(3, this.logger), async ({ accountKeys }, { pxe }) => { const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], 1)); this.wallets = await Promise.all(accountManagers.map(a => a.getWallet())); - this.accounts = await pxe.getRegisteredAccounts(); + + // This Map and loop ensure that this.accounts has the same order as this.wallets and this.keys + const registeredAccounts: Map = new Map( + (await pxe.getRegisteredAccounts()).map(acc => [acc.address.toString(), acc]), + ); + for (let i = 0; i < this.wallets.length; i++) { + const wallet = this.wallets[i]; + const walletAddr = wallet.getAddress().toString(); + assert( + registeredAccounts.has(walletAddr), + `Test account ${walletAddr} not registered, but it should have been`, + ); + this.accounts.push(registeredAccounts.get(walletAddr)!); + } }); await this.snapshotManager.snapshot( diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index 44e677aa46a..f3a34aab523 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -8,10 +8,12 @@ import { type CompleteAddress, type DeployL1Contracts, Fr, + type FunctionCall, GrumpkinScalar, type Logger, type PXE, type Wallet, + getContractClassFromArtifact, } from '@aztec/aztec.js'; import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; import { type BlobSinkServer, createBlobSinkServer } from '@aztec/blob-sink/server'; @@ -550,13 +552,22 @@ export const addAccounts = logger.verbose('Simulating account deployment...'); const provenTxs = await Promise.all( - accountKeys.map(async ([secretKey, signPk]) => { + accountKeys.map(async ([secretKey, signPk], index) => { const account = getSchnorrAccount(pxe, secretKey, signPk, 1); - const deployMethod = await account.getDeployMethod(); + // only register the contract class once + let skipClassRegistration = true; + if (index === 0) { + // for the first account, check if the contract class is already registered, otherwise we should register now + if (!(await pxe.isContractClassPubliclyRegistered(account.getInstance().contractClassId))) { + skipClassRegistration = false; + } + } + + const deployMethod = await account.getDeployMethod(); const provenTx = await deployMethod.prove({ contractAddressSalt: account.salt, - skipClassRegistration: true, + skipClassRegistration, skipPublicDeployment: true, universalDeploy: true, }); @@ -589,9 +600,16 @@ export async function publicDeployAccounts( ) { const accountAddressesToDeploy = accountsToDeploy.map(a => ('address' in a ? a.address : a)); const instances = await Promise.all(accountAddressesToDeploy.map(account => sender.getContractInstance(account))); - const batch = new BatchCall(sender, [ - (await registerContractClass(sender, SchnorrAccountContractArtifact)).request(), - ...instances.map(instance => deployInstance(sender, instance!).request()), - ]); + + const contractClass = getContractClassFromArtifact(SchnorrAccountContractArtifact); + const alreadyRegistered = await sender.isContractClassPubliclyRegistered(contractClass.id); + + const calls: FunctionCall[] = []; + if (!alreadyRegistered) { + calls.push((await registerContractClass(sender, SchnorrAccountContractArtifact)).request()); + } + calls.push(...instances.map(instance => deployInstance(sender, instance!).request())); + + const batch = new BatchCall(sender, calls); await batch.send().wait({ proven: waitUntilProven }); }