Skip to content

Commit

Permalink
feat!: contract interfaces and better function calls (#5687)
Browse files Browse the repository at this point in the history
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 <152162806+sklppy88@users.noreply.github.com>
Co-authored-by: Álvaro Rodríguez <sirasistant@gmail.com>
  • Loading branch information
3 people authored and AztecBot committed Apr 19, 2024
1 parent 5143bae commit 50d5a04
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 42 deletions.
2 changes: 1 addition & 1 deletion authwit/src/auth.nr
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn assert_current_call_valid_authwit_public<TPublicContext>(
context,
on_behalf_of,
function_selector,
[inner_hash],
[inner_hash].as_slice(),
GasOpts::default()
).deserialize_into();
assert(result == IS_VALID_SELECTOR, "Message not authorized by account");
Expand Down
5 changes: 4 additions & 1 deletion aztec/src/context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ mod avm_context;
mod interface;
mod gas;

use interface::ContextInterface;
use interface::{
ContextInterface, PrivateCallInterface, PublicCallInterface, PrivateVoidCallInterface,
PublicVoidCallInterface, AvmCallInterface, AvmVoidCallInterface
};
use private_context::PrivateContext;
use private_context::PackedReturns;
use public_context::PublicContext;
Expand Down
28 changes: 14 additions & 14 deletions aztec/src/context/avm_context.nr
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,32 @@ impl AvmContext {
l1_to_l2_msg_exists(msg_hash, msg_leaf_index) == 1
}

fn call_public_function_raw<ARGS_COUNT, RET_COUNT>(
fn call_public_function_raw<RET_COUNT>(
self: &mut Self,
gas: GasOpts,
contract_address: AztecAddress,
temporary_function_selector: Field,
args: [Field; ARGS_COUNT]
args: [Field]
) -> ([Field; RET_COUNT], u8) {
call(
gas_for_call(gas),
contract_address,
args.as_slice(),
args,
temporary_function_selector
)
}

fn static_call_public_function_raw<ARGS_COUNT, RET_COUNT>(
fn static_call_public_function_raw<RET_COUNT>(
self: &mut Self,
gas: GasOpts,
contract_address: AztecAddress,
temporary_function_selector: Field,
args: [Field; ARGS_COUNT]
args: [Field]
) -> ([Field; RET_COUNT], u8) {
call_static(
gas_for_call(gas),
contract_address,
args.as_slice(),
args,
temporary_function_selector
)
}
Expand Down Expand Up @@ -136,17 +136,17 @@ impl PublicContextInterface for AvmContext {
send_l2_to_l1_msg(recipient, content);
}

fn call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT],
args: [Field],
gas_opts: GasOpts
) -> FunctionReturns<RETURNS_COUNT> {
let results = call(
gas_for_call(gas_opts),
contract_address,
args.as_slice(),
args,
temporary_function_selector.to_field()
);
let data_to_return: [Field; RETURNS_COUNT] = results.0;
Expand All @@ -156,29 +156,29 @@ impl PublicContextInterface for AvmContext {
FunctionReturns::new(data_to_return)
}

fn static_call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn static_call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
temporary_function_selector: FunctionSelector,
args: [Field; ARGS_COUNT],
args: [Field],
gas_opts: GasOpts
) -> FunctionReturns<RETURNS_COUNT> {
let (data_to_return, success): ([Field; RETURNS_COUNT], u8) = call_static(
gas_for_call(gas_opts),
contract_address,
args.as_slice(),
args,
temporary_function_selector.to_field()
);

assert(success == 1, "Nested static call failed!");
FunctionReturns::new(data_to_return)
}

fn delegate_call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn delegate_call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field]
) -> FunctionReturns<RETURNS_COUNT> {
assert(false, "'delegate_call_public_function' not implemented!");
FunctionReturns::new([0; RETURNS_COUNT])
Expand Down
218 changes: 211 additions & 7 deletions aztec/src/context/interface.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, header::Header};
use dep::protocol_types::{
abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, header::Header,
traits::Deserialize
};

use crate::context::private_context::PrivateContext;
use crate::context::public_context::PublicContext;
use crate::context::avm_context::AvmContext;
use crate::context::gas::GasOpts;
use crate::context::public_context::FunctionReturns;

Expand Down Expand Up @@ -30,25 +36,223 @@ trait PublicContextInterface {
fn consume_l1_to_l2_message(&mut self, content: Field, secret: Field, sender: EthAddress);
fn accumulate_encrypted_logs<N>(&mut self, log: [Field; N]);
fn accumulate_unencrypted_logs<T>(&mut self, log: T);
fn call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT],
args: [Field],
gas_opts: GasOpts
) -> FunctionReturns<RETURNS_COUNT>;
fn static_call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn static_call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT],
args: [Field],
gas_opts: GasOpts
) -> FunctionReturns<RETURNS_COUNT>;
fn delegate_call_public_function<ARGS_COUNT, RETURNS_COUNT>(
fn delegate_call_public_function<RETURNS_COUNT>(
self: &mut Self,
contract_address: AztecAddress,
function_selector: FunctionSelector,
args: [Field; ARGS_COUNT]
args: [Field]
) -> FunctionReturns<RETURNS_COUNT>;
fn nullifier_exists(self, unsiloed_nullifier: Field, address: AztecAddress) -> bool;
}

struct PrivateCallInterface<T> {
target_contract: AztecAddress,
selector: FunctionSelector,
args_hash: Field,
}

impl<T> PrivateCallInterface<T> {
pub fn call<N>(self, context: &mut PrivateContext) -> T where T: Deserialize<N> {
let returns = context.call_private_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
);
let unpacked: T = returns.unpack_into();
unpacked
}

pub fn static_call<N>(self, context: &mut PrivateContext) -> T where T: Deserialize<N> {
let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false);
returns.unpack_into()
}

pub fn delegate_call<N>(self, context: &mut PrivateContext) -> T where T: Deserialize<N> {
let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true);
returns.unpack_into()
}
}

struct PrivateVoidCallInterface {
target_contract: AztecAddress,
selector: FunctionSelector,
args_hash: Field,
}

impl PrivateVoidCallInterface {
pub fn call(self, context: &mut PrivateContext) {
context.call_private_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
).assert_empty();
}

pub fn static_call(self, context: &mut PrivateContext) {
context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty();
}

pub fn delegate_call(self, context: &mut PrivateContext) {
context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty();
}
}

struct PublicCallInterface<T> {
target_contract: AztecAddress,
selector: FunctionSelector,
args_hash: Field,
}

impl<T> PublicCallInterface<T> {

pub fn call<N>(self, context: &mut PublicContext) -> T where T: Deserialize<N> {
let returns = context.call_public_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
);
returns.deserialize_into()
}

pub fn static_call<N>(self, context: &mut PublicContext) -> T where T: Deserialize<N> {
let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false);
returns.deserialize_into()
}

pub fn delegate_call<N>(self, context: &mut PublicContext) -> T where T: Deserialize<N> {
let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true);
returns.deserialize_into()
}

pub fn enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
)
}

pub fn static_enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false)
}

pub fn delegate_enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true)
}
}

struct PublicVoidCallInterface {
target_contract: AztecAddress,
selector: FunctionSelector,
args_hash: Field
}

impl PublicVoidCallInterface {
pub fn call(self, context: &mut PublicContext) {
context.call_public_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
).assert_empty()
}

pub fn static_call(self, context: &mut PublicContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty();
}

pub fn delegate_call(self, context: &mut PublicContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty();
}

pub fn enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(
self.target_contract,
self.selector,
self.args_hash,
false,
false
)
}

pub fn static_enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false)
}

pub fn delegate_enqueue(self, context: &mut PrivateContext) {
context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true)
}
}

struct AvmCallInterface<T> {
target_contract: AztecAddress,
selector: FunctionSelector,
args: [Field],
}

impl<T> AvmCallInterface<T> {
pub fn call<N>(self, context: &mut AvmContext, gas_opts: GasOpts) -> T where T: Deserialize<N> {
let returns = context.call_public_function(self.target_contract, self.selector, self.args, gas_opts);
returns.deserialize_into()
}

pub fn static_call<N>(
self,
context: &mut AvmContext,
gas_opts: GasOpts
) -> T where T: Deserialize<N> {
let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, gas_opts);
returns.deserialize_into()
}

pub fn delegate_call<N>(self, context: &mut AvmContext) -> T where T: Deserialize<N> {
let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args);
returns.deserialize_into()
}
}

struct AvmVoidCallInterface {
target_contract: AztecAddress,
selector: FunctionSelector,
args: [Field],
}

impl AvmVoidCallInterface {
pub fn call<N>(self, context: &mut AvmContext, gas_opts: GasOpts) {
let returns = context.call_public_function(self.target_contract, self.selector, self.args, gas_opts);
returns.assert_empty()
}

pub fn static_call<N>(self, context: &mut AvmContext, gas_opts: GasOpts) {
let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, gas_opts);
returns.assert_empty()
}

pub fn delegate_call<N>(self, context: &mut AvmContext) {
let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args);
returns.assert_empty()
}
}
Loading

0 comments on commit 50d5a04

Please sign in to comment.