Skip to content

Commit

Permalink
docs: combine messaging and portals
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Sep 19, 2023
1 parent 04b5884 commit e947422
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 60 deletions.
39 changes: 16 additions & 23 deletions docs/docs/dev_docs/contracts/portals/main.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: Portals
description: Documentation of Aztec's Portals and Cross-chain communication.
---

## What is a portal
Expand All @@ -24,32 +25,27 @@ When sending messages, we need to specify quite a bit of information beyond just
| Secret Hash | `field` (~254 bits) | A hash of a secret that is used when consuming the message on L2. Keep this preimage a secret to make the consumption private. To consume the message the caller must know the pre-image (the value that was hashed) - so make sure your app keeps track of the pre-images! Use the [`computeMessageSecretHash`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec.js/src/utils/secrets.ts) to compute it from a secret. |
| Fee | `uint64` | The fee to the sequencer for including the message. This is the amount of ETH that the sequencer will receive for including the message. Note that it is not a full `uint256` but only `uint64`|

:::warning
Should we discard the use of the L2Actor struct and use the individual fields instead? Might make integrations a tiny bit more pleasent to work with.
:::

With all that information at hand, we can call the `sendL2Message` function on the Inbox. The function will return a `field` (inside `bytes32`) that is the hash of the message. This hash can be used as an identifier to spot when your message has been included in a rollup block.

#include_code send_l1_to_l2_message l1-contracts/src/core/interfaces/messagebridge/IInbox.sol solidity

As time passes, a sequencer will see your tx, the juicy fee provided and include it in a rollup block. Upon inclusion, it is removed from L1, and made available to be consumed on L2.

To consume the message, we can use the `consume_l1_to_l2_message` function within the `context` struct.
The `msg_key` is the hash of the message produced from the `sendL2Message` call, the `content` is the content of the message, and the `secret` is the pre-image hashed to compute the `secretHash`.
The `msg_key` is the hash of the message returned by the `sendL2Message` call and is used to help the RPC find the correct message. The `content` is the content of the message, and the `secret` is the pre-image hashed to compute the `secretHash`. Note that while the `secret` and the `content` are both hashed, they are actually hashed with different hash functions! The `secret` is hashed with `pedersen` and the `content` is hashed with `sha256` mapped to a Field element.
If the `content` or `secret` is incorrect, the message will not be consumed, and the transaction will revert.

#include_code context_consume_l1_to_l2_message /yarn-project/aztec-nr/aztec/src/context.nr rust

Computing the `content` might be a little clunky in its current form, as we are still adding a number of bytes utilities. A good example exists within the [Non-native token example](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/hash.nr).
Computing the `content` might be a little clunky in its current form, as we are still adding a number of bytes utilities. A good example exists within the [Token bridge example](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/util.nr).

:::info
The `inputs` that is passed into `consume_l1_to_l2_message` are only temporary, and will be removed in the future when we improve syntax.
:::
An example usage of this flow is a token bridge, in the Aztec contract a `claim_public` function consumes a message from L1 and mints tokens to a user. Note that while we are doing this in public, there is also a private claim in the contract.

An example usage of this flow is a token bridge, in the Aztec contract a `mint` function consumes a message from L1 and mints tokens to a user. Note that we are using a private function, as we don't want to expose the user that is receiving the tokens publicly.
#include_code claim_public /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust

#include_code non_native_token_mint yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr rust
After the transaction has been mined, the message is consumed, a nullifier is emitted and the tokens have been minted on Aztec and are ready for use by the user.

After the transaction has been mined, the message is consumed, and the tokens have been minted on Aztec and are ready for use by the user. A consumed message cannot be consumed again.
Since the message consumption is emitting a nullifier the same message cannot be consumed again. The index in the message tree is used as part of the nullifier computation, ensuring that the same content and secret being inserted will be distinct messages that can each be consumed. Without the index in the nullifier, it would be possible to perform a kind of attack known as fairy gold attacks where two seemingly good messages are inserted, but only one of them can be consumed later.

## Passing data to L1

Expand All @@ -61,7 +57,7 @@ Recall that we mentioned the Aztec contract specifies what portal it is attached

The portal must ensure that the sender is as expected. One way to do this, is to compute the addresses before deployment and store them as constants in the contract, however a more flexible solution is to have an `initialize` function in the portal contract which can be used to set the address of the Aztec contract. In this model, the portal contract can check that the sender matches the value it has in storage.

To send a message to L1 from your Aztec contract, you must use the `message_portal` function on the `context`. When messaging to L1, only the `content` is required (as field).
To send a message to L1 from your Aztec contract, you must use the `message_portal` function on the `context`. When messaging to L1, only the `content` is required (as a `Field`).

#include_code context_message_portal /yarn-project/aztec-nr/aztec/src/context.nr rust

Expand All @@ -74,7 +70,7 @@ Access control on the L1 portal contract is essential to prevent consumption of
As earlier, we can use a token bridge as an example. In this case, we are burning tokens on L2 and sending a message to the portal to free them on L1.

<!-- TODO: update token standard https://github.com/AztecProtocol/aztec-packages/issues/2177 -->
#include_code non_native_token_withdraw yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr rust
#include_code exit_to_l1_private yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust

When the transaction is included in a rollup block the message will be inserted into the `Outbox`, where the recipient portal can consume it from. When consuming, the `msg.sender` must match the `recipient` meaning that only portal can actually consume the message.

Expand Down Expand Up @@ -149,7 +145,7 @@ Building on our token example from earlier, this can be called like:
The example above ensure that the user can cancel their message if it is underpriced.

### Designated caller
Designating a caller grants the ability to specify who should be able to call a function that consumes a message. This is useful for ordering of batched messages, as we will see shortly.
Designating a caller grants the ability to specify who should be able to call a function that consumes a message. This is useful for ordering of batched messages.

When performing multiple cross-chain calls in one action it is important to consider the order of the calls. Say for example, that you want to perform a uniswap trade on L1 because you are a whale and slippage on L2 is too damn high.

Expand All @@ -166,21 +162,18 @@ bytes memory message = abi.encodeWithSignature(
);
```

This way, the message can be consumed by the portal contract, but only if the caller is the designated caller. By being a bit clever when specifying the designated caller, we can ensure that the calls are done in the correct order. For the Uniswap example, say that we have token portals implemented as we have done throughout this page, and n Uniswap portal implementing the designated caller:
This way, the message can be consumed by the portal contract, but only if the caller is the designated caller. By being a bit clever when specifying the designated caller, we can ensure that the calls are done in the correct order. For the Uniswap example, say that we have token portals implemented as we have done throughout this page, and a Uniswap portal implementing the designated caller.

#include_code solidity_uniswap_swap l1-contracts/test/portals/UniswapPortal.sol solidity
Then we simply need to require that the Uniswap portal is the caller of the withdraw, and that the uniswap portal implementation is executing the withdraw before the swap. This can all be validated in the contract, and the user can be sure that the calls are done in the correct order from taking a look at the contracts. Since all of the messages are emitted to L1 from the same transaction, we can be sure that they either are all emitted or none of them are since the transaction is atomic.

We could then have withdraw transactions (on L2) where we are specifying the `UniswapPortal` as the caller. Because the order of the calls are specified in the contract, and that it reverts if any of them fail, we can be sure that it will execute the withdraw first, then the swap and then the deposit. Since only the `UniswapPortal` is able to execute the withdraw, we can be sure that the ordering is ensured. However, note that this means that if it for some reason is impossible to execute the batch (say prices moved greatly), the user will be stuck with the funds on L1 unless the `UniswapPortal` implements proper error handling!
Note however, that crossing the L1/L2 chasm is asynchronous, so there could be a situation where the user have burned the assets on L2 but the swap fails on L1! This could be due to major price movements or the like. In such a case, the user could be stuck with funds on L1 that they cannot get back to L2 unless the portal contract implements a way to handle this (proper handling errors).

:::caution
Designated callers are enforced at the contract level for contracts that are not the rollup itself, and should not be trusted to implement the standard correctly. The user should always be aware that it is possible for the developer to implement something that looks like designated caller without providing the abilities to the user.
:::

## Examples of portals

- Non-native token (asset bridged from L1)
- Token bridge (Portal contract built for L1 -> L2, i.e., a non-native L2 asset)
- [Portal contract](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/test/portals/TokenPortal.sol)
- [Aztec contract](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr)
- Uniswap (swap tokens on L1)
- [Portal contract](https://github.com/AztecProtocol/aztec-packages/blob/master/l1-contracts/test/portals/UniswapPortal.sol)
- [Aztec contract](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr)
- [Aztec contract](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr)
2 changes: 1 addition & 1 deletion docs/docs/dev_docs/contracts/syntax/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ If you recall the `redeem_shield` from back in the [private function section](#p

#include_code redeem_shield /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust

When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the private data tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [messages](./messaging.md) we are going to see this pattern again.
When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the private data tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../portals/main.md) we are going to see this pattern again.

:::danger
Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`).
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/dev_docs/contracts/syntax/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Aztec.nr contains abstractions which remove the need to understand the low-level
- [Oracle functions](./functions.md#oracle-functions) for accessing:
- private state
- secrets
- Functions for communicating with [Ethereum L1](./messaging.md)
- Functions for communicating with [Ethereum L1](../portals/main.md)

To import Aztec.nr into your Aztec contract project, simply include it as a dependency. For example:

Expand Down
34 changes: 0 additions & 34 deletions docs/docs/dev_docs/contracts/syntax/messaging.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ const sidebars = {
"dev_docs/contracts/syntax/functions",
"dev_docs/contracts/syntax/context",
"dev_docs/contracts/syntax/globals",
"dev_docs/contracts/syntax/messaging",
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/test/portals/TokenPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract TokenPortal {
l2TokenAddress = _l2TokenAddress;
}

// docs:start:deposit_public
/**
* @notice Deposit funds into the portal and adds an L2 message which can only be consumed publicly on Aztec
* @param _to - The aztec address of the recipient
Expand Down Expand Up @@ -53,6 +54,7 @@ contract TokenPortal {
// Send message to rollup
return inbox.sendL2Message{value: msg.value}(actor, _deadline, contentHash, _secretHash);
}
// docs:end:deposit_public

/**
* @notice Deposit funds into the portal and adds an L2 message which can only be consumed privately on Aztec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ contract TokenBridge {
// let _callStackItem = context.call_public_function(context.this_address(), selector, [context.msg_sender()]);
}

// docs:start:claim_public
// Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly
#[aztec(public)]
fn claim_public(
Expand All @@ -65,6 +66,7 @@ contract TokenBridge {

1
}
// docs:end:claim_public

// docs:start:exit_to_l1_public
// Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly
Expand Down

0 comments on commit e947422

Please sign in to comment.