diff --git a/docs/docs/dev_docs/contracts/portals/main.md b/docs/docs/dev_docs/contracts/portals/main.md index c94d4950a1fa..11cdf34b1ae6 100644 --- a/docs/docs/dev_docs/contracts/portals/main.md +++ b/docs/docs/dev_docs/contracts/portals/main.md @@ -1,5 +1,6 @@ --- title: Portals +description: Documentation of Aztec's Portals and Cross-chain communication. --- ## What is a portal @@ -24,10 +25,6 @@ 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 @@ -35,21 +32,20 @@ With all that information at hand, we can call the `sendL2Message` function on t 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 @@ -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 @@ -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. -#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. @@ -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. @@ -166,11 +162,11 @@ 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. @@ -178,9 +174,6 @@ Designated callers are enforced at the contract level for contracts that are not ## 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) \ No newline at end of file + - [Aztec contract](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr) \ No newline at end of file diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index 97249f8a9659..45f8ac410f9f 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -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`). diff --git a/docs/docs/dev_docs/contracts/syntax/main.md b/docs/docs/dev_docs/contracts/syntax/main.md index 7dfa37376c76..ff85d8a5b65f 100644 --- a/docs/docs/dev_docs/contracts/syntax/main.md +++ b/docs/docs/dev_docs/contracts/syntax/main.md @@ -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: diff --git a/docs/docs/dev_docs/contracts/syntax/messaging.md b/docs/docs/dev_docs/contracts/syntax/messaging.md deleted file mode 100644 index f83fc725c10c..000000000000 --- a/docs/docs/dev_docs/contracts/syntax/messaging.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Messaging -description: Documentation of Aztec's Messaging system ---- - -# Messaging - -## L1 --> L2 -The context available within functions includes the ability to send messages to l1. For more information on how cross chain communication works in Aztec, see the [documentation on communication.](../../../concepts/foundation/communication/cross_chain_calls.md) - -#include_code non_native_token_withdraw /yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/main.nr rust - -### What happens behind the scenes? -When a user sends a message from a [portal contract](../../../concepts/foundation/communication/cross_chain_calls.md#portal) to the rollup's inbox it gets processed and added to the `l1 to l2 messages tree`. - - <-- TODO(Maddiaa): INCLUDE LINK TO WHERE the messages tree is discussed elsewhere in the docs. --> - -The l1 to l2 messages tree contains all messages that have been sent from l1 to the l2. The good thing about this tree is that it does not reveal when it's messages have been spent, as consuming a message from the l1 to l2 messages tree is done by nullifing a message. - -When calling the `consume_l1_to_l2_message` function on a contract; a number of actions are performed by `Aztec.nr`. - -1. The `msgKey` value (passed to the consume message function) is used to look up the contents of the l1 message. -2. Check that the message recipient is the contract of the current calling context. -3. Check that the message content matches the content reproduced earlier on. -4. Validate that caller know's the preimage to the message's `secretHash`. See more information [here](../../../concepts/foundation/communication/cross_chain_calls.md#messages). -5. We compute the nullifier for the message. -#include_code l1_to_l2_message_compute_nullifier /yarn-project/aztec-nr/aztec/src/messaging/l1_to_l2_message.nr rust -6. Finally we push the nullifier to the context. Allowing it to be checked for validity by the kernel and rollup circuits. - -#include_code consume_l1_to_l2_message /yarn-project/aztec-nr/aztec/src/context.nr rust - -As the same nullifier cannot be created twice. We cannot consume the message again. - -## L2 ->> L1 diff --git a/docs/sidebars.js b/docs/sidebars.js index 099c6b0a2300..9b4ac8d3111b 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -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", ], }, { diff --git a/l1-contracts/test/portals/TokenPortal.sol b/l1-contracts/test/portals/TokenPortal.sol index 3b74d240ec86..e9deaa92596c 100644 --- a/l1-contracts/test/portals/TokenPortal.sol +++ b/l1-contracts/test/portals/TokenPortal.sol @@ -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 @@ -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 diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr index 748ab5d97391..0b32db5f7275 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr @@ -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( @@ -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