From 942b49687235c3c0399acbc1a2d43459d2a351c1 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 1 Mar 2024 17:04:10 -0300 Subject: [PATCH] feat: Public initializer check --- .../aztec-nr/aztec/src/initializer.nr | 9 ++- .../stateful_test_contract/src/main.nr | 7 +++ .../src/e2e_deploy_contract.test.ts | 55 ++++++++++++++----- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/noir-projects/aztec-nr/aztec/src/initializer.nr b/noir-projects/aztec-nr/aztec/src/initializer.nr index 73301dd0950..bb39faacca9 100644 --- a/noir-projects/aztec-nr/aztec/src/initializer.nr +++ b/noir-projects/aztec-nr/aztec/src/initializer.nr @@ -1,5 +1,5 @@ use dep::protocol_types::hash::silo_nullifier; -use crate::context::PrivateContext; +use crate::context::{PrivateContext, ContextInterface}; use crate::history::nullifier_inclusion::prove_nullifier_inclusion; pub fn mark_as_initialized(context: &mut PrivateContext) { @@ -7,17 +7,16 @@ pub fn mark_as_initialized(context: &mut PrivateContext) { context.push_new_nullifier(init_nullifier, 0); } -// TODO(@spalladino): Add a variant using PublicContext once we can read nullifiers or note hashes from public-land. -pub fn assert_is_initialized(context: &mut PrivateContext) { +pub fn assert_is_initialized(context: &mut TContext) where TContext: ContextInterface { let init_nullifier = compute_contract_initialization_nullifier(*context); prove_nullifier_inclusion(init_nullifier, *context); } -pub fn compute_contract_initialization_nullifier(context: PrivateContext) -> Field { +pub fn compute_contract_initialization_nullifier(context: TContext) -> Field where TContext: ContextInterface { let address = context.this_address(); silo_nullifier(address, compute_unsiloed_contract_initialization_nullifier(context)) } -pub fn compute_unsiloed_contract_initialization_nullifier(context: PrivateContext) -> Field { +pub fn compute_unsiloed_contract_initialization_nullifier(context: TContext) -> Field where TContext: ContextInterface{ context.this_address().to_field() } diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index ed0b66ee923..587f7713fd8 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -68,6 +68,13 @@ contract StatefulTest { #[aztec(public)] fn increment_public_value(owner: AztecAddress, value: Field) { + assert_is_initialized(&mut context); + let loc = storage.public_values.at(owner); + loc.write(loc.read() + value); + } + + #[aztec(public)] + fn increment_public_value_no_init_check(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); } 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 cabdcacd7f9..d11d4f1a6e9 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 @@ -201,7 +201,7 @@ describe('e2e_deploy_contract', () => { .wait(); logger.info(`Checking if the constructor was run for ${contract.address}`); expect(await contract.methods.summed_values(owner).view()).toEqual(42n); - logger.info(`Calling a function that requires initialization on ${contract.address}`); + logger.info(`Calling a private function that requires initialization on ${contract.address}`); await contract.methods.create_note(owner, 10).send().wait(); expect(await contract.methods.summed_values(owner).view()).toEqual(52n); }, @@ -309,6 +309,7 @@ describe('e2e_deploy_contract', () => { let instance: ContractInstanceWithAddress; let initArgs: StatefulContractCtorArgs; let publicKey: PublicKey; + let contract: StatefulTestContract; beforeAll(async () => { initArgs = [accounts[0].address, 42]; @@ -320,20 +321,7 @@ describe('e2e_deploy_contract', () => { const { address, contractClassId } = instance; logger(`Deploying contract instance at ${address.toString()} class id ${contractClassId.toString()}`); await deployFn(instance); - }, 60_000); - - it('stores contract instance in the aztec node', async () => { - const deployed = await aztecNode.getContract(instance.address); - expect(deployed).toBeDefined(); - expect(deployed!.address).toEqual(instance.address); - expect(deployed!.contractClassId).toEqual(contractClass.id); - expect(deployed!.initializationHash).toEqual(instance.initializationHash); - expect(deployed!.portalContractAddress).toEqual(instance.portalContractAddress); - expect(deployed!.publicKeysHash).toEqual(instance.publicKeysHash); - expect(deployed!.salt).toEqual(instance.salt); - }); - it('calls a public function on the deployed instance', async () => { // TODO(@spalladino) We should **not** need the whole instance, including initArgs and salt, // in order to interact with a public function for the contract. We may even not need // all of it for running a private function. Consider removing `instance` as a required @@ -349,7 +337,44 @@ describe('e2e_deploy_contract', () => { publicKey, }); expect(registered.address).toEqual(instance.address); - const contract = await StatefulTestContract.at(instance.address, wallet); + contract = await StatefulTestContract.at(instance.address, wallet); + }, 60_000); + + it('stores contract instance in the aztec node', async () => { + const deployed = await aztecNode.getContract(instance.address); + expect(deployed).toBeDefined(); + expect(deployed!.address).toEqual(instance.address); + expect(deployed!.contractClassId).toEqual(contractClass.id); + expect(deployed!.initializationHash).toEqual(instance.initializationHash); + expect(deployed!.portalContractAddress).toEqual(instance.portalContractAddress); + expect(deployed!.publicKeysHash).toEqual(instance.publicKeysHash); + expect(deployed!.salt).toEqual(instance.salt); + }); + + it('calls a public function with no init check on the deployed instance', async () => { + const whom = AztecAddress.random(); + await contract.methods + .increment_public_value_no_init_check(whom, 10) + .send({ skipPublicSimulation: true }) + .wait(); + const stored = await contract.methods.get_public_value(whom).view(); + expect(stored).toEqual(10n); + }, 30_000); + + it('refuses to call a public function with init check if the instance is not initialized', async () => { + await expect( + contract.methods + .increment_public_value(AztecAddress.random(), 10) + .send({ skipPublicSimulation: true }) + .wait(), + ).rejects.toThrow(/dropped/i); + }, 30_000); + + it('calls a public function with init check after initialization', async () => { + await contract.methods + .constructor(...initArgs) + .send() + .wait(); const whom = AztecAddress.random(); await contract.methods.increment_public_value(whom, 10).send({ skipPublicSimulation: true }).wait(); const stored = await contract.methods.get_public_value(whom).view();