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: Macros for initializer checks #4830

Merged
merged 1 commit into from
Feb 29, 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ contract StatefulTest {
}

#[aztec(private)]
#[aztec(initializer)]
fn constructor(owner: AztecAddress, value: Field) {
let selector = FunctionSelector::from_signature("create_note_no_init_check((Field),Field)");
let _res = context.call_private_function(context.this_address(), selector, [owner.to_field(), value]);
mark_as_initialized(&mut context);
}

#[aztec(private)]
#[aztec(initcheck)]
fn create_note(owner: AztecAddress, value: Field) {
assert_is_initialized(&mut context);
if (value != 0) {
let loc = storage.notes.at(owner);
increment(loc, value, owner);
Expand Down
105 changes: 95 additions & 10 deletions noir/noir-repo/aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub enum AztecMacroError {
UnsupportedStorageType { span: Option<Span>, typ: UnresolvedTypeData },
CouldNotAssignStorageSlots { secondary_message: Option<String> },
EventError { span: Span, message: String },
UnsupportedAttributes { span: Span, secondary_message: Option<String> },
}

impl From<AztecMacroError> for MacroError {
Expand Down Expand Up @@ -114,6 +115,11 @@ impl From<AztecMacroError> for MacroError {
secondary_message: None,
span: Some(span),
},
AztecMacroError::UnsupportedAttributes { span, secondary_message } => MacroError {
primary_message: "Unsupported attributes in contract function".to_string(),
secondary_message,
span: Some(span),
},
}
}
}
Expand Down Expand Up @@ -430,22 +436,43 @@ fn transform_module(
}

for func in module.functions.iter_mut() {
let mut is_private = false;
let mut is_public = false;
let mut is_public_vm = false;
let mut is_initializer = false;
let mut skip_init_check = true; // Default to true once we're confident that the approach works

for secondary_attribute in func.def.attributes.secondary.clone() {
let crate_graph = &context.crate_graph[crate_id];
if is_custom_attribute(&secondary_attribute, "aztec(private)") {
transform_function("Private", func, storage_defined)
.map_err(|err| (err, crate_graph.root_file_id))?;
has_transformed_module = true;
is_private = true;
} else if is_custom_attribute(&secondary_attribute, "aztec(initializer)") {
is_initializer = true;
} else if is_custom_attribute(&secondary_attribute, "aztec(initcheck)") {
skip_init_check = false;
} else if is_custom_attribute(&secondary_attribute, "aztec(public)") {
transform_function("Public", func, storage_defined)
.map_err(|err| (err, crate_graph.root_file_id))?;
has_transformed_module = true;
is_public = true;
} else if is_custom_attribute(&secondary_attribute, "aztec(public-vm)") {
transform_vm_function(func, storage_defined)
.map_err(|err| (err, crate_graph.root_file_id))?;
has_transformed_module = true;
is_public_vm = true;
}
}

// Apply transformations to the function based on collected attributes
if is_private || is_public {
transform_function(
if is_private { "Private" } else { "Public" },
func,
storage_defined,
is_initializer,
skip_init_check,
)
.map_err(|err| (err, crate_graph.root_file_id))?;
has_transformed_module = true;
} else if is_public_vm {
transform_vm_function(func, storage_defined)
.map_err(|err| (err, crate_graph.root_file_id))?;
has_transformed_module = true;
}

// Add the storage struct to the beginning of the function if it is unconstrained in an aztec contract
if storage_defined && func.def.is_unconstrained {
transform_unconstrained(func);
Expand Down Expand Up @@ -627,11 +654,28 @@ fn transform_function(
ty: &str,
func: &mut NoirFunction,
storage_defined: bool,
is_initializer: bool,
skip_init_check: bool,
) -> Result<(), AztecMacroError> {
let context_name = format!("{}Context", ty);
let inputs_name = format!("{}ContextInputs", ty);
let return_type_name = format!("{}CircuitPublicInputs", ty);

// Add initialization check
if !skip_init_check {
if ty == "Public" {
let error = AztecMacroError::UnsupportedAttributes {
span: func.def.name.span(),
secondary_message: Some(
"public functions do not yet support initialization check".to_owned(),
),
};
return Err(error);
}
let init_check = create_init_check();
func.def.body.0.insert(0, init_check);
}

// Add access to the storage struct
if storage_defined {
let storage_def = abstract_storage(&ty.to_lowercase(), false);
Expand All @@ -655,6 +699,21 @@ fn transform_function(
func.def.body.0.push(return_values);
}

// 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();
func.def.body.0.push(mark_initialized);
}

// Push the finish method call to the end of the function
let finish_def = create_context_finish();
func.def.body.0.push(finish_def);
Expand Down Expand Up @@ -1091,6 +1150,32 @@ fn create_inputs(ty: &str) -> Param {
Param { pattern: context_pattern, typ: context_type, visibility, span: Span::default() }
}

/// Creates an initialization check to ensure that the contract has been initialized, meant to
/// be injected as the first statement of any function after the context has been created.
///
/// ```noir
/// assert_is_initialized(&mut context);
/// ```
fn create_init_check() -> Statement {
make_statement(StatementKind::Expression(call(
variable_path(chained_path!("aztec", "initializer", "assert_is_initialized")),
vec![mutable_reference("context")],
)))
}

/// Creates a call to mark_as_initialized which emits the initialization nullifier, meant to
/// be injected as the last statement before returning in a constructor.
///
/// ```noir
/// mark_as_initialized(&mut context);
/// ```
fn create_mark_as_initialized() -> Statement {
make_statement(StatementKind::Expression(call(
variable_path(chained_path!("aztec", "initializer", "mark_as_initialized")),
vec![mutable_reference("context")],
)))
}

/// Creates the private context object to be accessed within the function, the parameters need to be extracted to be
/// appended into the args hash object.
///
Expand Down
Loading