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 #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
#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 Apr 17, 2024
1 parent be9f24c commit 274f7d9
Show file tree
Hide file tree
Showing 100 changed files with 1,338 additions and 2,205 deletions.
2 changes: 1 addition & 1 deletion boxes/boxes/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"main": "./dist/index.js",
"scripts": {
"compile": "cd src/contracts && ${AZTEC_NARGO:-aztec-nargo} compile",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen src/contracts/target -o artifacts --ts",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen src/contracts/target -o artifacts",
"clean": "rm -rf ./dist .tsbuildinfo ./artifacts ./src/contracts/target",
"prep": "yarn clean && yarn compile && yarn codegen",
"dev": "yarn prep && webpack serve --mode development",
Expand Down
2 changes: 1 addition & 1 deletion boxes/boxes/vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"scripts": {
"compile": "cd src/contracts && ${AZTEC_NARGO:-aztec-nargo} compile",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen src/contracts/target -o artifacts --ts",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen src/contracts/target -o artifacts",
"clean": "rm -rf ./dest .tsbuildinfo ./artifacts ./src/contracts/target",
"prep": "yarn clean && yarn compile && yarn codegen && tsc -b",
"dev": "yarn prep && webpack serve --mode development",
Expand Down
2 changes: 1 addition & 1 deletion boxes/contract-only/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"scripts": {
"compile": "cd src && ${AZTEC_NARGO:-aztec-nargo} compile",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen target -o artifacts --ts",
"codegen": "${AZTEC_CLI:-aztec-cli} codegen target -o artifacts",
"clean": "rm -rf ./dest .tsbuildinfo ./artifacts ./target",
"prep": "yarn clean && yarn compile && yarn codegen && tsc -b",
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --runInBand",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ This will output a JSON [artifact](./artifacts.md) for each contract in the proj

You can use the code generator to autogenerate type-safe typescript classes for each of your contracts. These classes define type-safe methods for deploying and interacting with your contract based on their artifact.

To generate them, include a `--ts` option in the `codegen` command with a path to the target folder for the typescript files:

```bash
aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts --ts
aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts
```

Below is typescript code generated from the [Token](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/token_contract/src/main.nr) contract:
Expand Down Expand Up @@ -119,94 +117,34 @@ Read more about interacting with contracts using `aztec.js` [here](../../getting

An Aztec.nr contract can [call a function](../writing_contracts/functions/call_functions.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe.

To make this easier, the compiler can generate contract interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one. For each contract, two interface structs are generated: one to be used from private functions with a `PrivateContext`, and one to be used from open functions with a `PublicContext`.

To generate them, include a `--nr` option in the `codegen` command with a path to the target folder for the generated Aztec.nr interface files:

```bash
aztec-cli codegen ./aztec-nargo/output/target/path -o ./path/to/output/folder --nr
```
To make this easier, the compiler automatically generates interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one.

Below is an example interface, also generated from the [Token](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/token_contract/src/main.nr) contract:
Below is an example of interface usage generated from the [Token](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/token_contract/src/main.nr) contract, used from the [FPC](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr):

```rust
impl TokenPrivateContextInterface {
pub fn at(address: Field) -> Self {
Self {
address,
}
}

pub fn burn(
self,
context: &mut PrivateContext,
from: FromBurnStruct,
amount: Field,
nonce: Field
) -> [Field; RETURN_VALUES_LENGTH] {
let mut serialized_args = [0; 3];
serialized_args[0] = from.address;
serialized_args[1] = amount;
serialized_args[2] = nonce;

context.call_private_function(self.address, 0xd4fcc96e, serialized_args)
}


pub fn burn_public(
self,
context: &mut PrivateContext,
from: FromBurnPublicStruct,
amount: Field,
nonce: Field
) {
let mut serialized_args = [0; 3];
serialized_args[0] = from.address;
serialized_args[1] = amount;
serialized_args[2] = nonce;
contract FPC {

context.call_public_function(self.address, 0xb0e964d5, serialized_args)
}
...

}
...

impl TokenPublicContextInterface {
pub fn at(address: Field) -> Self {
Self {
address,
}
}
use dep::token::Token;

pub fn burn_public(
self,
context: PublicContext,
from: FromBurnPublicStruct,
amount: Field,
nonce: Field
) -> [Field; RETURN_VALUES_LENGTH] {
let mut serialized_args = [0; 3];
serialized_args[0] = from.address;
serialized_args[1] = amount;
serialized_args[2] = nonce;

context.call_public_function(self.address, 0xb0e964d5, serialized_args)
}
...


pub fn mint_private(
self,
context: PublicContext,
amount: Field,
secret_hash: Field
) -> [Field; RETURN_VALUES_LENGTH] {
let mut serialized_args = [0; 2];
serialized_args[0] = amount;
serialized_args[1] = secret_hash;
#[aztec(private)]
fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) {
assert(asset == storage.other_asset.read_private());
Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context);
FPC::at(context.this_address()).pay_fee_with_shielded_rebate(amount, asset, secret_hash).enqueue(&mut context);
}

context.call_public_function(self.address, 0x10763932, serialized_args)
}
#[aztec(private)]
fn fee_entrypoint_public(amount: Field, asset: AztecAddress, nonce: Field) {
FPC::at(context.this_address()).prepare_fee(context.msg_sender(), amount, asset, nonce).enqueue(&mut context);
FPC::at(context.this_address()).pay_fee(context.msg_sender(), amount, asset).enqueue(&mut context);
}

...

}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ aztec-nargo compile
Generate the typescript class:

```bash
aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts --ts
aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts
```

This would create a typescript file like `Example.ts` in `./src/artifacts`. Read more on the [compiling page](../compiling_contracts/how_to_compile_contract.md).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,19 @@ On this page you will learn how to implement a slow updates tree into your contr

# How to implement a slow updates tree

1. Copy the _SlowTree.nr_ example and its dependencies, found [here](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/slow_tree_contract). Replace the constants with whatever you like and deploy it to your sandbox
2. Copy the _SlowMap interface_ for easy interaction with your deployed SlowTree. Find it [here](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/interfaces.nr)
3. Import this interface into your contract

#include_code interface noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

5. Store the SlowTree address in private storage as a FieldNote
1. Store the SlowTree address in private storage as a FieldNote

#include_code constructor noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

6. Store the SlowTree address in public storage and initialize an instance of SlowMap using this address
2. Store the SlowTree address in public storage and initialize an instance of SlowMap using this address

#include_code write_slow_update_public noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

7. Now you can read and update from private functions:
3. Now you can read and update from private functions:

#include_code get_and_update_private noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

8. Or from public functions:
4. Or from public functions:

#include_code get_public noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr rust

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ There are generally 4 main components involved to make it easier to use a slow u

This is the primary smart contract that will use the slow updates tree. In the example we use a [token with blacklisting features](./implement_slow_updates.md#exploring-an-example-integration-through-a-tokenblacklist-smart-contract).

## Interface

This interface of the slow updates tree contract allows your contract to interact with the Slow Updates Tree contract. It provides methods for reading and updating values in the tree in both public and private contexts. You can find it [here](https://github.com/AztecProtocol/aztec-packages/blob/master/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/interfaces.nr).

## SlowTree.nr contract

This is a smart contract developed by Aztec that establishes and manages a slow updates tree structure. It allows developers to access and interact with the tree, such as reading and updating data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ This will compile the smart contract and create a `target` folder with a `.json`
After compiling, you can generate a typescript class. In the same directory, run this:

```bash
aztec-cli codegen target -o src/artifacts --ts
aztec-cli codegen target -o src/artifacts
```

### Deploy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ In our `token-bridge` Aztec project in `aztec-contracts`, under `src` there is a

#include_code token_bridge_storage_and_constructor /noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr rust

This imports Aztec-related dependencies and our helper file `token_interface.nr`.
(The code above will give errors right now - this is because we haven't implemented util and token_interface yet.)

In `token_interface.nr`, add this:

#include_code token_bridge_token_interface /noir-projects/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust

## Consume the L1 message

In the previous step, we have moved our funds to the portal and created a L1->L2 message. Upon building the next rollup, the sequencer asks the inbox for any incoming messages and adds them to Aztec’s L1->L2 message tree, so an application on L2 can prove that the message exists and consumes it.
Expand Down
3 changes: 1 addition & 2 deletions docs/docs/developers/tutorials/token_portal/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_
token_portal_content_hash_lib = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/noir-contracts/contracts/token_portal_content_hash_lib" }
```

We will also be writing some helper functions that should exist elsewhere so we don't overcomplicated our contract. In `src` create two more files - one called `util.nr` and one called `token_interface` - so your dir structure should now look like this:
We will also be writing some helper functions that should exist elsewhere so we don't overcomplicated our contract. In `src` create one more file called `util.nr` - so your dir structure should now look like this:

```tree
aztec-contracts
└── token_bridge
├── Nargo.toml
├── src
├── main.nr
├── token_interface.nr
├── util.nr
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ aztec-nargo compile
And generate the TypeScript interface for the contract and add it to the test dir:

```bash
aztec-cli codegen target -o ../../src/test/fixtures --ts
aztec-cli codegen target -o ../../src/test/fixtures
```

This will create a TS interface in our `src/test` folder!
Expand Down
13 changes: 3 additions & 10 deletions docs/docs/developers/tutorials/uniswap/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,18 @@ Inside `uniswap/Nargo.toml` paste this in `[dependencies]`:
[dependencies]
aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/aztec" }
authwit = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/authwit"}
token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/noir-contracts/token_contract" }
token_bridge = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/noir-contracts/token_bridge_contract" }
```

## L2 contracts

The `main.nr` will utilize a few helper functions that are outside the scope of this tutorial. Inside `uniswap/src` create two new files:

```bash
cd uniswap/src && touch util.nr && touch interfaces.nr
cd uniswap/src && touch util.nr
```

Inside `interfaces.nr` paste this:

#include_code interfaces noir-projects/noir-contracts/contracts/uniswap_contract/src/interfaces.nr rust

This creates interfaces for the `Token` contract and `TokenBridge` contract

- `Token` is a reference implementation for a token on Aztec. Here we just need two methods - [`transfer_public`](../writing_token_contract.md#transfer_public) and [`unshield()`](../writing_token_contract.md#unshield).
- The `TokenBridge` facilitates interactions with our [bridge contract](../token_portal/main.md). Here we just need its [`exit_to_l1_public`](../token_portal/withdrawing_to_l1.md)

## Run Aztec sandbox

You will need a running sandbox.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ aztec-nargo compile
And then generate the typescript interface:

```bash
aztec-cli codegen ./target/ -o ../../../src/test/fixtures uniswap --ts
aztec-cli codegen ./target/ -o ../../../src/test/fixtures uniswap
```

This will create a TS interface in our `src/test` folder that will help us write our test.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ aztec-nargo compile
This will create a new directory called `target` and a JSON artifact inside it. To optionally create a typescript interface, run:

```bash
aztec-cli codegen target -o src/artifacts --ts
aztec-cli codegen target -o src/artifacts
```

Once it is compiled you can [deploy](../contracts/deploying_contracts/how_to_deploy_contract.md) it to the sandbox. Ensure your [sandbox is running](../sandbox/references/sandbox-reference.md) and run this in the same dir as before:
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/developers/tutorials/writing_token_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ aztec-nargo compile
Once your contract is compiled, optionally generate a typescript interface with the following command:

```bash
aztec-cli codegen target -o src/artifacts --ts
aztec-cli codegen target -o src/artifacts
```

## Next Steps
Expand Down
48 changes: 48 additions & 0 deletions docs/docs/misc/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,54 @@ Aztec is in full-speed development. Literally every version breaks compatibility

## TBD

### [Aztec.nr] Contract interfaces

It is now possible to import contracts on another contracts and use their automatic interfaces to perform calls. The interfaces have the same name as the contract, and are automatically exported. Parameters are automatically serialized (using the `Serialize<N>` trait) and return values are automatically deserialized (using the `Deserialize<N>` trait). Serialize and Deserialize methods have to conform to the standard ACVM serialization schema for the interface to work!

1. Only fixed length types are supported
2. All numeric types become Fields
3. Strings become arrays of Fields, one per char
4. Arrays become arrays of Fields following rules 2 and 3
5. Structs become arrays of Fields, with every item defined in the same order as they are in Noir code, following rules 2, 3, 4 and 5 (recursive)


```diff
- context.call_public_function(
- storage.gas_token_address.read_private(),
- FunctionSelector::from_signature("pay_fee(Field)"),
- [42]
- );
-
- context.call_public_function(
- storage.gas_token_address.read_private(),
- FunctionSelector::from_signature("pay_fee(Field)"),
- [42]
- );
-
- let _ = context.call_private_function(
- storage.subscription_token_address.read_private(),
- FunctionSelector::from_signature("transfer((Field),(Field),Field,Field)"),
- [
- context.msg_sender().to_field(),
- storage.subscription_recipient_address.read_private().to_field(),
- storage.subscription_price.read_private(),
- nonce
- ]
- );
+ use dep::gas_token::GasToken;
+ use dep::token::Token;
+
+ ...
+ // Public call from public land
+ GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).call(&mut context);
+ // Public call from private land
+ GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context);
+ // Private call from private land
+ Token::at(asset).transfer(context.msg_sender(), storage.subscription_recipient_address.read_private(), amount, nonce).call(&mut context);
```

It is also possible to use these automatic interfaces from the local contract, and thus enqueue public calls from private without having to rely on low level `context` calls.

### [Aztec.nr] Rename max block number setter

The `request_max_block_number` function has been renamed to `set_tx_max_block_number` to better reflect that it is not a getter, and that the setting is transaction-wide.
Expand Down
2 changes: 1 addition & 1 deletion noir-projects/aztec-nr/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 noir-projects/aztec-nr/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
Loading

0 comments on commit 274f7d9

Please sign in to comment.