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(aztec-noir): abstract storage #2750

Merged
merged 3 commits into from
Sep 19, 2023
Merged
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
109 changes: 102 additions & 7 deletions compiler/noirc_frontend/src/hir/def_map/aztec_library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
ParsedModule, Path, PathKind, Pattern, Statement, UnresolvedType, UnresolvedTypeData,
Visibility,
};
use crate::{PrefixExpression, UnaryOp};
use noirc_errors::FileDiagnostic;

//
Expand Down Expand Up @@ -55,8 +56,12 @@ fn call(func: Expression, arguments: Vec<Expression>) -> Expression {
expression(ExpressionKind::Call(Box::new(CallExpression { func: Box::new(func), arguments })))
}

fn mutable(pattern: &str) -> Pattern {
Pattern::Mutable(Box::new(Pattern::Identifier(ident(pattern))), Span::default())
fn pattern(name: &str) -> Pattern {
Pattern::Identifier(ident(name))
}

fn mutable(name: &str) -> Pattern {
Pattern::Mutable(Box::new(pattern(name)), Span::default())
}

fn mutable_assignment(name: &str, assigned_to: Expression) -> Statement {
Expand All @@ -67,6 +72,21 @@ fn mutable_assignment(name: &str, assigned_to: Expression) -> Statement {
})
}

fn mutable_reference(variable_name: &str) -> Expression {
expression(ExpressionKind::Prefix(Box::new(PrefixExpression {
operator: UnaryOp::MutableReference,
rhs: variable(variable_name),
})))
}

fn assignment(name: &str, assigned_to: Expression) -> Statement {
Statement::Let(LetStatement {
pattern: pattern(name),
r#type: make_type(UnresolvedTypeData::Unspecified),
expression: assigned_to,
})
}

fn member_access(lhs: &str, rhs: &str) -> Expression {
expression(ExpressionKind::MemberAccess(Box::new(MemberAccessExpression {
lhs: variable(lhs),
Expand Down Expand Up @@ -141,7 +161,9 @@ pub(crate) fn transform(

// Covers all functions in the ast
for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) {
if transform_module(&mut submodule.contents.functions) {
let storage_defined = check_for_storage_definition(&submodule.contents);

if transform_module(&mut submodule.contents.functions, storage_defined) {
check_for_aztec_dependency(crate_id, context, errors);
include_relevant_imports(&mut submodule.contents);
}
Expand Down Expand Up @@ -183,27 +205,37 @@ fn check_for_aztec_dependency(
}
}

// Check to see if the user has defined a storage struct
fn check_for_storage_definition(module: &ParsedModule) -> bool {
module.types.iter().any(|function| function.name.0.contents == "Storage")
}

/// Determines if the function is annotated with `aztec(private)` or `aztec(public)`
/// If it is, it calls the `transform` function which will perform the required transformations.
/// Returns true if an annotated function is found, false otherwise
fn transform_module(functions: &mut [NoirFunction]) -> bool {
fn transform_module(functions: &mut [NoirFunction], storage_defined: bool) -> bool {
let mut has_annotated_functions = false;
for func in functions.iter_mut() {
for secondary_attribute in func.def.attributes.secondary.clone() {
if let SecondaryAttribute::Custom(custom_attribute) = secondary_attribute {
match custom_attribute.as_str() {
"aztec(private)" => {
transform_function("Private", func);
transform_function("Private", func, storage_defined);
has_annotated_functions = true;
}
"aztec(public)" => {
transform_function("Public", func);
transform_function("Public", func, storage_defined);
has_annotated_functions = true;
}
_ => continue,
}
}
}
// 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);
has_annotated_functions = true;
}
}
has_annotated_functions
}
Expand All @@ -212,11 +244,17 @@ fn transform_module(functions: &mut [NoirFunction]) -> bool {
/// - A new Input that is provided for a kernel app circuit, named: {Public/Private}ContextInputs
/// - Hashes all of the function input variables
/// - This instantiates a helper function
fn transform_function(ty: &str, func: &mut NoirFunction) {
fn transform_function(ty: &str, func: &mut NoirFunction, storage_defined: bool) {
let context_name = format!("{}Context", ty);
let inputs_name = format!("{}ContextInputs", ty);
let return_type_name = format!("{}CircuitPublicInputs", ty);

// Add access to the storage struct
if storage_defined {
let storage_def = abstract_storage(&ty.to_lowercase(), false);
func.def.body.0.insert(0, storage_def);
}

// Insert the context creation as the first action
let create_context = create_context(&context_name, &func.def.parameters);
func.def.body.0.splice(0..0, (create_context).iter().cloned());
Expand Down Expand Up @@ -247,6 +285,18 @@ fn transform_function(ty: &str, func: &mut NoirFunction) {
}
}

/// Transform Unconstrained
///
/// Inserts the following code at the beginning of an unconstrained function
/// ```noir
/// let storage = Storage::init(Context::none());
/// ```
///
/// This will allow developers to access their contract' storage struct in unconstrained functions
fn transform_unconstrained(func: &mut NoirFunction) {
func.def.body.0.insert(0, abstract_storage("Unconstrained", true));
}

/// Helper function that returns what the private context would look like in the ast
/// This should make it available to be consumed within aztec private annotated functions.
///
Expand Down Expand Up @@ -413,6 +463,51 @@ fn abstract_return_values(func: &NoirFunction) -> Option<Statement> {
}
}

/// Abstract storage
///
/// For private functions:
/// ```noir
/// #[aztec(private)]
/// fn lol() {
/// let storage = Storage::init(Context::private(context));
/// }
/// ```
///
/// For public functions:
/// ```noir
/// #[aztec(public)]
/// fn lol() {
/// let storage = Storage::init(Context::public(context));
/// }
/// ```
///
/// For unconstrained functions:
/// ```noir
/// unconstrained fn lol() {
/// let storage = Storage::init(Context::none());
/// }
fn abstract_storage(typ: &str, unconstrained: bool) -> Statement {
let init_context_call = if unconstrained {
call(
variable_path(chained_path!("aztec", "context", "Context", "none")), // Path
vec![], // args
)
} else {
call(
variable_path(chained_path!("aztec", "context", "Context", typ)), // Path
vec![mutable_reference("context")], // args
)
};

assignment(
"storage", // Assigned to
call(
variable_path(chained_path!("Storage", "init")), // Path
vec![init_context_call], // args
),
)
}

/// Context Return Values
///
/// Creates an instance to the context return values
Expand Down