Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Public initializer check #4894

Merged
merged 1 commit into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions noir-projects/aztec-nr/aztec/src/initializer.nr
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
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) {
let init_nullifier = compute_unsiloed_contract_initialization_nullifier(*context);
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<TContext>(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<TContext>(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<TContext>(context: TContext) -> Field where TContext: ContextInterface{
context.this_address().to_field()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
55 changes: 40 additions & 15 deletions yarn-project/end-to-end/src/e2e_deploy_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down Expand Up @@ -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];
Expand All @@ -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
Expand All @@ -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();
Expand Down
Loading