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: Enable public constructor functions #4896

Merged
merged 2 commits into from
Mar 5, 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
10 changes: 9 additions & 1 deletion noir-projects/aztec-nr/aztec/src/initializer.nr
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
use dep::protocol_types::hash::silo_nullifier;
use crate::context::{PrivateContext, ContextInterface};
use crate::context::{PrivateContext, PublicContext, 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): Using the trait here fails with "No matching impl found for `&mut TContext: ContextInterface`"
// on the `push_new_nullifier` call. Remove this method in favor of a single method that uses the trait (and update
// the noir compiler macro accordingly) once we sort it out.
pub fn mark_as_initialized_public(context: &mut PublicContext) {
let init_nullifier = compute_unsiloed_contract_initialization_nullifier(*context);
context.push_new_nullifier(init_nullifier, 0);
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ contract StatefulTest {
let _res = context.call_private_function(context.this_address(), selector, [owner.to_field(), value]);
}

#[aztec(public)]
#[aztec(initializer)]
fn public_constructor(owner: AztecAddress, value: Field) {
let selector = FunctionSelector::from_signature("increment_public_value_no_init_check((Field),Field)");
let _res = context.call_public_function(context.this_address(), selector, [owner.to_field(), value]);
}

#[aztec(private)]
fn create_note(owner: AztecAddress, value: Field) {
if (value != 0) {
Expand Down
16 changes: 4 additions & 12 deletions noir/noir-repo/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,16 +711,7 @@ fn transform_function(

// Before returning mark the contract as initialized
if is_initializer {
if ty == "Public" {
let error = AztecMacroError::UnsupportedAttributes {
span: func.def.name.span(),
secondary_message: Some(
"public functions cannot yet be used as initializers".to_owned(),
),
};
return Err(error);
}
let mark_initialized = create_mark_as_initialized();
let mark_initialized = create_mark_as_initialized(ty);
func.def.body.0.push(mark_initialized);
}

Expand Down Expand Up @@ -1179,9 +1170,10 @@ fn create_init_check() -> Statement {
/// ```noir
/// mark_as_initialized(&mut context);
/// ```
fn create_mark_as_initialized() -> Statement {
fn create_mark_as_initialized(ty: &str) -> Statement {
let name = if ty == "Public" { "mark_as_initialized_public" } else { "mark_as_initialized" };
make_statement(StatementKind::Expression(call(
variable_path(chained_dep!("aztec", "initializer", "mark_as_initialized")),
variable_path(chained_dep!("aztec", "initializer", name)),
vec![mutable_reference("context")],
)))
}
Expand Down
49 changes: 37 additions & 12 deletions yarn-project/end-to-end/src/e2e_deploy_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
deployInstance,
registerContractClass,
} from '@aztec/aztec.js/deployment';
import { ContractClassIdPreimage, Point, PublicKey } from '@aztec/circuits.js';
import { ContractClassIdPreimage, Point } from '@aztec/circuits.js';
import { siloNullifier } from '@aztec/circuits.js/hash';
import { FunctionSelector, FunctionType } from '@aztec/foundation/abi';
import { StatefulTestContract } from '@aztec/noir-contracts.js';
Expand Down Expand Up @@ -258,10 +258,6 @@ describe('e2e_deploy_contract', () => {
/nullifier witness not found/i,
);
});

it('refuses to call a public function that requires initialization', async () => {
// TODO(@spalladino)
});
});

describe('registering a contract class', () => {
Expand Down Expand Up @@ -309,16 +305,14 @@ describe('e2e_deploy_contract', () => {
describe(`deploying a contract instance ${how}`, () => {
let instance: ContractInstanceWithAddress;
let initArgs: StatefulContractCtorArgs;
let publicKey: PublicKey;
let contract: StatefulTestContract;

beforeAll(async () => {
initArgs = [accounts[0].address, 42];
const deployInstance = async () => {
const initArgs = [accounts[0].address, 42] as StatefulContractCtorArgs;
const salt = Fr.random();
const portalAddress = EthAddress.random();
publicKey = Point.random();

instance = getContractInstanceFromDeployParams(artifact, initArgs, salt, publicKey, portalAddress);
const publicKey = Point.random();
const instance = getContractInstanceFromDeployParams(artifact, initArgs, salt, publicKey, portalAddress);
const { address, contractClassId } = instance;
logger(`Deploying contract instance at ${address.toString()} class id ${contractClassId.toString()}`);
await deployFn(instance);
Expand All @@ -338,7 +332,12 @@ describe('e2e_deploy_contract', () => {
publicKey,
});
expect(registered.address).toEqual(instance.address);
contract = await StatefulTestContract.at(instance.address, wallet);
const contract = await StatefulTestContract.at(instance.address, wallet);
return { contract, initArgs, instance, publicKey };
};

beforeAll(async () => {
({ instance, initArgs, contract } = await deployInstance());
}, 60_000);

it('stores contract instance in the aztec node', async () => {
Expand Down Expand Up @@ -381,6 +380,32 @@ describe('e2e_deploy_contract', () => {
const stored = await contract.methods.get_public_value(whom).view();
expect(stored).toEqual(10n);
}, 30_000);

it('refuses to reinitialize the contract', async () => {
await expect(
contract.methods
.public_constructor(...initArgs)
.send({ skipPublicSimulation: true })
.wait(),
).rejects.toThrow(/dropped/i);
}, 30_000);

it('initializes a new instance of the contract via a public function', async () => {
const { contract, initArgs } = await deployInstance();
const whom = initArgs[0];
logger.info(`Initializing contract at ${contract.address} via a public function`);
await contract.methods
.public_constructor(...initArgs)
.send({ skipPublicSimulation: true })
.wait();
expect(await contract.methods.get_public_value(whom).view()).toEqual(42n);
logger.info(`Calling a public function that requires initialization on ${contract.address}`);
await contract.methods.increment_public_value(whom, 10).send().wait();
expect(await contract.methods.get_public_value(whom).view()).toEqual(52n);
logger.info(`Calling a private function that requires initialization on ${contract.address}`);
await contract.methods.create_note(whom, 10).send().wait();
expect(await contract.methods.summed_values(whom).view()).toEqual(10n);
}, 90_000);
});

testDeployingAnInstance('from a wallet', async instance => {
Expand Down
Loading