Skip to content

Commit

Permalink
feat!: contract interfaces and better function calls (AztecProtocol/a…
Browse files Browse the repository at this point in the history
…ztec-packages#5687)

Closes AztecProtocol/aztec-packages#5081

This PR introduces autogenerated contract interfaces for easy intra and
inter contract interactions. The `aztec-macro` crate is used to stub
every non-internal private and public function and inject them into a
ghost struct which has the same name as the contract that generated
them. After that, they can be called like this:

```rust

contract ImportTest {

  use dep::my_imported_contract::MyImportedContract;

  #[aztec(private)]
  fn a_private_fn() {
    let deserialized_return = MyImportedContract::at(some_address).another_private_fn(arg1, arg2).call(&mut context);
    MyImportedContract::at(some_address).a_public_fn(arg1).enqueue(&mut context);
  }

  #[aztec(public)]
  fn a_public_fn() {
    let deserialized_return = MyImportedContract::at(some_address).a_public_fn(arg1).call(&mut context);
  }

  #[aztec(private)]
  fn calling_my_own_fns() {
    ImportTest::at(context.this_address).a_private_fn().call(&mut context);
    ImportTest::at(context.this_address).a_public_fn().enqueue(&mut context);
  }

}
```

Return values are `deserialized_into()` automatically, providing "real"
return values thanks to
AztecProtocol/aztec-packages#5633

Also, some general cleanup was required to allow importing contracts in
another contracts. Main changes:

- `HirContext.fully_qualified_struct_path` now uses BFS to avoid
returning the longest path when looking for a struct in a crate. This is
required to avoid pulling structs usually imported in top-level
dependencies (usually notes from our main contract) from other imported
contracts.
- `pack_args_oracle` now has a slice mode in addition to its usual array
mode.

PENDING:

~~AvmContext. The AVM team is discussing supporting args as slices. In
case it's decided not to do that, a workaround could possibly be
implemented using the macro, but it would be fairly complex.~~

Thanks to @fcarreiro and the amazing AVM team, this is now supported for
the AvmContext!

---------

Co-authored-by: esau <[email protected]>
Co-authored-by: Álvaro Rodríguez <[email protected]>
  • Loading branch information
3 people committed Apr 17, 2024
2 parents 961ca28 + 4f32473 commit b25ca49
Show file tree
Hide file tree
Showing 14 changed files with 587 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .aztec-sync-commit
Original file line number Diff line number Diff line change
@@ -1 +1 @@
be9f24c16484b26a1eb88bcf35b785553160995d
274f7d935230ce21d062644f6ec5f7cd0f58ae62
50 changes: 39 additions & 11 deletions aztec_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ mod utils;

use transforms::{
compute_note_hash_and_nullifier::inject_compute_note_hash_and_nullifier,
contract_interface::{
generate_contract_interface, stub_function, update_fn_signatures_in_contract_interface,
},
events::{generate_selector_impl, transform_events},
functions::{export_fn_abi, transform_function, transform_unconstrained},
note_interface::{generate_note_interface_impl, inject_note_exports},
Expand Down Expand Up @@ -59,7 +62,14 @@ fn transform(
// Usage -> mut ast -> aztec_library::transform(&mut ast)
// Covers all functions in the ast
for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) {
if transform_module(&mut submodule.contents).map_err(|err| (err.into(), file_id))? {
if transform_module(
crate_id,
context,
&mut submodule.contents,
submodule.name.0.contents.as_str(),
)
.map_err(|err| (err.into(), file_id))?
{
check_for_aztec_dependency(crate_id, context)?;
}
}
Expand All @@ -72,7 +82,12 @@ fn transform(
/// Determines if ast nodes are annotated with aztec attributes.
/// For annotated functions it calls the `transform` function which will perform the required transformations.
/// Returns true if an annotated node is found, false otherwise
fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError> {
fn transform_module(
crate_id: &CrateId,
context: &HirContext,
module: &mut SortedModule,
module_name: &str,
) -> Result<bool, AztecMacroError> {
let mut has_transformed_module = false;

// Check for a user defined storage struct
Expand All @@ -84,7 +99,12 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>
if !check_for_storage_implementation(module, &storage_struct_name) {
generate_storage_implementation(module, &storage_struct_name)?;
}
generate_storage_layout(module, storage_struct_name)?;
// Make sure we're only generating the storage layout for the root crate
// In case we got a contract importing other contracts for their interface, we
// don't want to generate the storage layout for them
if crate_id == context.root_crate_id() {
generate_storage_layout(module, storage_struct_name)?;
}
}

for structure in module.types.iter_mut() {
Expand All @@ -102,6 +122,8 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>
.any(|attr| is_custom_attribute(attr, "aztec(initializer)"))
});

let mut stubs: Vec<_> = vec![];

for func in module.functions.iter_mut() {
let mut is_private = false;
let mut is_public = false;
Expand Down Expand Up @@ -129,15 +151,18 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>

// Apply transformations to the function based on collected attributes
if is_private || is_public || is_public_vm {
let fn_type = if is_private {
"Private"
} else if is_public_vm {
"Avm"
} else {
"Public"
};
stubs.push(stub_function(fn_type, func));

export_fn_abi(&mut module.types, func)?;
transform_function(
if is_private {
"Private"
} else if is_public_vm {
"Avm"
} else {
"Public"
},
fn_type,
func,
storage_defined,
is_initializer,
Expand Down Expand Up @@ -171,6 +196,8 @@ fn transform_module(module: &mut SortedModule) -> Result<bool, AztecMacroError>
span: Span::default(),
});
}

generate_contract_interface(module, module_name, &stubs)?;
}

Ok(has_transformed_module)
Expand All @@ -189,7 +216,8 @@ fn transform_hir(
transform_events(crate_id, context)?;
inject_compute_note_hash_and_nullifier(crate_id, context)?;
assign_storage_slots(crate_id, context)?;
inject_note_exports(crate_id, context)
inject_note_exports(crate_id, context)?;
update_fn_signatures_in_contract_interface(crate_id, context)
} else {
Ok(())
}
Expand Down
25 changes: 16 additions & 9 deletions aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,20 @@ pub fn inject_compute_note_hash_and_nullifier(
crate_id: &CrateId,
context: &mut HirContext,
) -> Result<(), (AztecMacroError, FileId)> {
if let Some((module_id, file_id)) = get_contract_module_data(context, crate_id) {
if let Some((_, module_id, file_id)) = get_contract_module_data(context, crate_id) {
// If compute_note_hash_and_nullifier is already defined by the user, we skip auto-generation in order to provide an
// escape hatch for this mechanism.
// TODO(#4647): improve this diagnosis and error messaging.
if check_for_compute_note_hash_and_nullifier_definition(crate_id, context) {
if context.crate_graph.root_crate_id() != crate_id
|| check_for_compute_note_hash_and_nullifier_definition(crate_id, context)
{
return Ok(());
}

// In order to implement compute_note_hash_and_nullifier, we need to know all of the different note types the
// contract might use. These are the types that are marked as #[aztec(note)].
let note_types = fetch_notes(context)
.iter()
.map(|(_, note)| note.borrow().name.0.contents.clone())
.collect::<Vec<_>>();
let note_types =
fetch_notes(context).iter().map(|(path, _)| path.to_string()).collect::<Vec<_>>();

// We can now generate a version of compute_note_hash_and_nullifier tailored for the contract in this crate.
let func = generate_compute_note_hash_and_nullifier(&note_types);
Expand All @@ -73,7 +73,14 @@ pub fn inject_compute_note_hash_and_nullifier(
// pass an empty span. This function should not produce errors anyway so this should not matter.
let location = Location::new(Span::empty(0), file_id);

inject_fn(crate_id, context, func, location, module_id, file_id);
inject_fn(crate_id, context, func, location, module_id, file_id).map_err(|err| {
(
AztecMacroError::CouldNotImplementComputeNoteHashAndNullifier {
secondary_message: err.secondary_message,
},
file_id,
)
})?;
}
Ok(())
}
Expand All @@ -100,7 +107,7 @@ fn generate_compute_note_hash_and_nullifier_source(note_types: &[String]) -> Str
// so we include a dummy version.
"
unconstrained fn compute_note_hash_and_nullifier(
contract_address: AztecAddress,
contract_address: dep::aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
Expand Down Expand Up @@ -130,7 +137,7 @@ fn generate_compute_note_hash_and_nullifier_source(note_types: &[String]) -> Str
format!(
"
unconstrained fn compute_note_hash_and_nullifier(
contract_address: AztecAddress,
contract_address: dep::aztec::protocol_types::address::AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
Expand Down
Loading

0 comments on commit b25ca49

Please sign in to comment.