From 0e650affb6fe21384736f28c1238373919e1951a Mon Sep 17 00:00:00 2001 From: Rahul Kothari Date: Thu, 12 Oct 2023 12:11:35 +0000 Subject: [PATCH] finish token portal tutorial --- .../token_portal/depositing_to_aztec.md | 27 +++- .../token_portal/minting_on_aztec.md | 35 +++-- .../dev_docs/tutorials/token_portal/setup.md | 55 ++----- .../token_portal/typescript_glue_code.md | 146 +++++++++++++++++- ...rawing_from_l1.md => withdrawing_to_l1.md} | 22 ++- .../tutorials/uniswap/l2_contract_setup.md | 2 +- .../tutorials/uniswap/swap_privately.md | 2 +- docs/sidebars.js | 2 +- .../src/e2e_cross_chain_messaging.test.ts | 19 ++- .../e2e_public_cross_chain_messaging.test.ts | 36 ++--- .../src/fixtures/cross_chain_test_harness.ts | 6 +- yarn-project/end-to-end/src/fixtures/utils.ts | 4 + .../ethereum/src/deploy_l1_contracts.ts | 2 + .../token_bridge_contract/src/main.nr | 8 +- .../src/token_interface.nr | 2 + .../token_portal_content_hash_lib/src/lib.nr | 2 +- .../contracts/uniswap_contract/src/main.nr | 3 +- 17 files changed, 278 insertions(+), 95 deletions(-) rename docs/docs/dev_docs/tutorials/token_portal/{withdrawing_from_l1.md => withdrawing_to_l1.md} (89%) diff --git a/docs/docs/dev_docs/tutorials/token_portal/depositing_to_aztec.md b/docs/docs/dev_docs/tutorials/token_portal/depositing_to_aztec.md index 582cfdc41af..81d8625e3c5 100644 --- a/docs/docs/dev_docs/tutorials/token_portal/depositing_to_aztec.md +++ b/docs/docs/dev_docs/tutorials/token_portal/depositing_to_aztec.md @@ -15,10 +15,10 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // Messaging -import {IRegistry} from "./aztec/interfaces/messagebridge/IRegistry.sol"; -import {IInbox} from "./aztec/interfaces/messagebridge/IInbox.sol"; -import {DataStructures} from "./aztec/libraries/DataStructures.sol"; -import {Hash} from "./aztec/libraries/Hash.sol"; +import {IRegistry} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol"; +import {IInbox} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol"; +import {DataStructures} from "@aztec/l1-contracts/src/core/libraries/DataStructures.sol"; +import {Hash} from "@aztec/l1-contracts/src/core/libraries/Hash.sol"; contract TokenPortal { using SafeERC20 for IERC20; @@ -32,6 +32,7 @@ contract TokenPortal { underlying = IERC20(_underlying); l2TokenAddress = _l2TokenAddress; } +} ``` This imports relevant files including the interfaces used by the Aztec rollup. And initializes the contract with the following parameters: @@ -40,6 +41,24 @@ This imports relevant files including the interfaces used by the Aztec rollup. A - The erc20 token the portal corresponds to - The address of the sister contract on Aztec to where the token will send messages to (for depositing tokens or from where to withdraw the tokens) +Let's also create a dummy ERC20 contract that can mint tokens to anyone. This will make it easier to test our code: + +Let's create a file `PortalERC20.sol` in the same folder and add: + +```solidity +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract PortalERC20 is ERC20 { + constructor() ERC20("Portal", "PORTAL") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} +``` ## Depositing tokens to Aztec publicly Next, we will write a function that is used to deposit funds on L1 that a user may have into an Aztec portal and send a message to the Aztec rollup to mint tokens _publicly_ on Aztec. diff --git a/docs/docs/dev_docs/tutorials/token_portal/minting_on_aztec.md b/docs/docs/dev_docs/tutorials/token_portal/minting_on_aztec.md index bb77dd56ff6..5b63c74e749 100644 --- a/docs/docs/dev_docs/tutorials/token_portal/minting_on_aztec.md +++ b/docs/docs/dev_docs/tutorials/token_portal/minting_on_aztec.md @@ -2,21 +2,36 @@ title: Minting tokens on Aztec --- -In this step we will start writing our Aztec.nr bridge smart contract! +In this step we will start writing our Aztec.nr bridge smart contract and write a function to consume the message from the token portal to mint funds on Aztec -## Consume the L1 message - -We have now 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. +## Initial contract setup In our `token-bridge` nargo project in `aztec-contracts`, under `src` there is an example `main.nr` file. Delete all the code in here and paste this to define imports and initialize the constructor: -#include_code imports_and_constructor /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust +```rust +mod util; +#include_code token_bridge_imports /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust + use crate::token_interface::Token; + use crate::util::{get_mint_public_content_hash, get_mint_private_content_hash, get_withdraw_content_hash}; + +#include_code token_bridge_storage_and_constructor /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust +``` + +This imports aztec related dependencies and also our two helper files `token_interface.nr` and `util.nr`. + +In `token_interface.nr`, add the follows: +#include_code token_brodge_token_interface /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr rust + +We will write `util.nr` as needed. +## 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. -Then paste this `claim_public` function: +In `main.nr`, now paste this `claim_public` function: #include_code claim_public /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust In your `util.nr` paste this `mint_public_content_hash` function: -#include_code mint_public_content_hash_nr /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/util.nr rust +#include_code mint_public_content_hash_nr /yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr rust The `claim_public` function enables anyone to consume the message on the user's behalf and mint tokens for them on L2. This is fine as the minting of tokens is done publicly anyway. @@ -39,14 +54,14 @@ Now we will create a function to mint the amount privately. Paste this into your #include_code call_mint_on_token /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr rust -Then inside your `util.nr` that you created in [setup](./setup.md), paste this: +Then inside your `util.nr`, paste this: -#include_code get_mint_private_content_hash /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/util.nr rust +#include_code get_mint_private_content_hash /yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr rust If the content hashes were constructed similarly for `mint_private` and `mint_publicly`, then content intended for private execution could have been consumed by calling the `claim_public` method. By making these two content hashes distinct, we prevent this scenario. While we mint the tokens on L2, we _still don’t actually mint them to a certain address_. Instead we continue to pass the `secret_hash_for_redeeming_minted_notes` like we did on L1. This means that a user could reveal their secret for L2 message consumption for anyone to mint tokens on L2 but they can redeem these notes at a later time. **This enables a paradigm where an app can manage user’s secrets for L2 message consumption on their behalf**. **The app or any external party can also mint tokens on the user’s behalf should they be comfortable with leaking the secret for L2 Message consumption.** This doesn’t leak any new information to the app because their smart contract on L1 knew that a user wanted to move some amount of tokens to L2. The app still doesn’t know which address on L2 the user wants these notes to be in, but they can mint tokens nevertheless on their behalf. -To mint tokens privately, `claim_private` calls an internal function [\_call_mint_on_token()](https://github.com/AztecProtocol/dev-rel/tree/main/tutorials/token-bridge#_call_mint_on_token) which then calls [token.mint_private()](https://github.com/AztecProtocol/dev-rel/blob/main/tutorials/token-contract/README.md#mint_private) which is a public method since it operates on public storage. Note that mint_private (on the token contract) is public because it too reads from public storage. Since the `secret_hash_for_redeeming_minted_notes` is passed publicly (and not the secret), nothing that should be leaked is, and the only the person that knows the secret can actually redeem their notes at a later time by calling `Token.redeem_shield(secret, amount)`. +To mint tokens privately, `claim_private` calls an internal function `_call_mint_on_token()` which then calls [token.mint_private()](../writing_token_contract.md#mint_private) which is a public method since it operates on public storage. Note that mint_private (on the token contract) is public because it too reads from public storage. Since the `secret_hash_for_redeeming_minted_notes` is passed publicly (and not the secret), nothing that should be leaked is, and the only the person that knows the secret can actually redeem their notes at a later time by calling [`Token.redeem_shield(secret, amount)`](../writing_token_contract.md#redeem_shield). In the next step we will see how we can cancel a message. diff --git a/docs/docs/dev_docs/tutorials/token_portal/setup.md b/docs/docs/dev_docs/tutorials/token_portal/setup.md index 6b60aa49d9c..487aeee4fe0 100644 --- a/docs/docs/dev_docs/tutorials/token_portal/setup.md +++ b/docs/docs/dev_docs/tutorials/token_portal/setup.md @@ -127,32 +127,32 @@ aztec-contracts ├── Nargo.toml ├── src ├── main.nr + ├── token_interface.nr ├── util.nr ``` -# Create a hardhat project +# Create a JS hardhat project -In the root dir `aztec-token-bridge`, create a new directory called `l1-contracts` and run `npx hardhat init` inside of it. Keep hitting enter so you get the default setup. +In the root dir `aztec-token-bridge`, create a new directory called `l1-contracts` and run `npx hardhat init` inside of it. Keep hitting enter so you get the default setup (Javascript project) ```bash mkdir l1-contracts cd l1-contracts npx hardhat init ``` - -Once you have a hardhat project set up, delete the `contracts` directory inside `l1-contracts`. We will be cloning a new `contracts` dir in the next step. +Once you have a hardhat project set up, delete the existing contracts and create a `TokenPortal.sol`: ```bash -rm -rf contracts +cd contracts +rm *.sol +touch TokenPortal.sol ``` -## Download Ethereum contracts - -We will write the `TokenPortal.sol` contract in this tutorial, but it has many imports that we will need to have locally. - -To make this easier we have a standalone submodule on GItHub with all the smart contracts with relative paths - [find it in the devrels repo under utils](https://github.com/AztecProtocol/dev-rel/tree/main/utils). You can clone this directly into `l1-contracts` (recommended to then `rm -rf .git` in the `contracts` dir). +Also add Aztec's L1-contracts that includes the interfaces to Aztec Inbox, Outbox and Registry smart contracts, which we will need to send L1<>L2 messages. -<-- TODO(#2453) redirect people to the l1-contracts repo --> +``` +yarn add @aztec/l1-contracts +``` This is what your `l1-contracts` should look like: @@ -166,33 +166,6 @@ This is what your `l1-contracts` should look like: └── package.json ``` -And inside `contracts`: - -```json -contracts -├── Outbox.sol -├── PortalERC20.sol -└── aztec - ├── Rollup.sol - ├── interfaces - │   ├── IRollup.sol - │   └── messagebridge - │   ├── IInbox.sol - │   ├── IOutbox.sol - │   └── IRegistry.sol - ├── libraries - │   ├── ConstantsGen.sol - │   ├── DataStructures.sol - │   ├── Decoder.sol - │   ├── Errors.sol - │   ├── Hash.sol - │   └── MessageBox.sol - └── mock - ├── MockVerifier.sol - └── interfaces - └── IVerifier.sol -``` - # Create src yarn project In this package, we will write TS code that will interact with our ethereum and aztec-nr contracts and run them against the sandbox. @@ -203,7 +176,7 @@ Inside the root directory, run ```bash mkdir src && cd src && yarn init -yp -yarn add @aztec/aztec.js @aztec/noir-contracts @aztec/types viem "@types/node@^20.8.2" +yarn add @aztec/aztec.js @aztec/noir-contracts @aztec/types @aztec/foundation @aztec/l1-artifacts viem "@types/node@^20.8.2" yarn add -D jest @jest/globals ts-jest ``` @@ -267,7 +240,7 @@ Finally, we will create a test file, in the `src` package: ```bash mkdir test && cd test -touch index.test.ts +touch cross_chain_messaging.test.ts ``` Your `src` package should look like: @@ -276,7 +249,7 @@ Your `src` package should look like: src ├── node_modules └── test - └── index.test.ts + └── cross_chain_messaging.test.ts ├── jest.config.json ├── package.json ``` diff --git a/docs/docs/dev_docs/tutorials/token_portal/typescript_glue_code.md b/docs/docs/dev_docs/tutorials/token_portal/typescript_glue_code.md index 3af85c528d4..0662e589643 100644 --- a/docs/docs/dev_docs/tutorials/token_portal/typescript_glue_code.md +++ b/docs/docs/dev_docs/tutorials/token_portal/typescript_glue_code.md @@ -2,4 +2,148 @@ title: Deploy & Call Contracts with Typescript --- -Still todo +So far we have written our solidity and aztec-nr functions to deposit and withdraw tokens between L1 and L2. But we haven't yet interacted with the sandbox to actually execute the code and see the tokens being bridged. We will now write a test to interact with the sandbox and see the expected results! + +In the root folder, go to `packages/src` folder where we added `jest`: +```bash +cd packages/src +mkdir test && cd test +touch cross_chain_messaging.test.ts +``` + +Open cross_chain_messaging.test.ts: + +We will write two tests: +1. Test the deposit and withdraw in the private flow +2. Do the same in the public flow + +## Test imports and setup +We need some helper files that can keep our code clean: + +```bash +mkdir fixtures +touch utils.ts +touch cross_chain_test_harness.ts +``` + +In `utils.ts`, put: +```typescript +import * as fs from 'fs'; +import { AztecAddress, EthAddress, TxStatus, Wallet } from "@aztec/aztec.js"; +import { TokenBridgeContract, TokenContract } from "@aztec/noir-contracts/types"; +import { Account, Chain, Hex, HttpTransport, PublicClient, WalletClient, getContract } from "viem"; +import type { Abi, Narrow } from 'abitype'; + +const PATH = "../../packages/l1-contracts/artifacts/contracts"; +const EXT = ".sol" +function getL1ContractABIAndBytecode(contractName: string) { + const pathToArtifact = `${PATH}/${contractName}${EXT}/${contractName}.json`; + const artifacts = JSON.parse(fs.readFileSync(pathToArtifact, 'utf-8')); + return [artifacts.abi, artifacts.bytecode]; +} + +const [PortalERC20Abi, PortalERC20Bytecode] = getL1ContractABIAndBytecode("PortalERC20"); +const [TokenPortalAbi, TokenPortalBytecode] = getL1ContractABIAndBytecode("TokenPortal"); + +#include_code deployL1Contract /yarn-project/ethereum/src/deploy_l1_contracts.ts typescript raw +#include_code deployAndInitializeTokenAndBridgeContracts /yarn-project/end-to-end/src/fixtures/utils.ts typescript raw +#include_code delay /yarn-project/end-to-end/src/fixtures/utils.ts typescript raw +``` + +This code +- gets your solidity contract ABIs, +- uses viem to deploy them to Ethereum, +- uses aztec.js to deploy the token and token bridge contract on L2, sets the bridge's portal address to `tokenPortalAddress` and initialises all the contracts + +Now let's create another util file to can handle interaction with these contracts to mint/deposit the functions: + +In `cross_chain_test_harness.ts`, put: + +#include_code cross_chain_test_harness /yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts typescript + +This is just a class that holds all contracts as objects and exposes easy to use helper methods to interact with our contracts. Take a moment of review everything here! + +Okay now we are ready to write our tests: + +open `cross_chain_messaging.test.ts` and lets do the initial description of the test: +```typescript +import { expect, jest} from '@jest/globals' +import { AccountWallet, AztecAddress, DebugLogger, EthAddress, Fr, computeAuthWitMessageHash, createDebugLogger, createPXEClient, getSandboxAccountsWallets, waitForSandbox } from '@aztec/aztec.js'; +import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types'; + +import { CrossChainTestHarness } from './fixtures/cross_chain_test_harness.js'; +import { delay } from './fixtures/utils.js'; +import { mnemonicToAccount } from 'viem/accounts'; +import { createPublicClient, createWalletClient, http } from 'viem'; +import { foundry } from 'viem/chains'; + +const { PXE_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; +const MNEMONIC = 'test test test test test test test test test test test junk'; +const hdAccount = mnemonicToAccount(MNEMONIC); + +describe('e2e_cross_chain_messaging', () => { + jest.setTimeout(120_000); + + let logger: DebugLogger; + // include code: + let user1Wallet: AccountWallet; + let user2Wallet: AccountWallet; + let ethAccount: EthAddress; + let ownerAddress: AztecAddress; + + let crossChainTestHarness: CrossChainTestHarness; + let l2Token: TokenContract; + let l2Bridge: TokenBridgeContract; + let outbox: any; + + beforeEach(async () => { + logger = createDebugLogger('aztec:canary_uniswap'); + const pxe = createPXEClient(PXE_URL); + await waitForSandbox(pxe); + const wallets = await getSandboxAccountsWallets(pxe); + + const walletClient = createWalletClient({ + account: hdAccount, + chain: foundry, + transport: http(ETHEREUM_HOST), + }); + const publicClient = createPublicClient({ + chain: foundry, + transport: http(ETHEREUM_HOST), + }); + + crossChainTestHarness = await CrossChainTestHarness.new( + pxe, + publicClient, + walletClient, + wallets[0], + logger, + ); + + l2Token = crossChainTestHarness.l2Token; + l2Bridge = crossChainTestHarness.l2Bridge; + ethAccount = crossChainTestHarness.ethAccount; + ownerAddress = crossChainTestHarness.ownerAddress; + outbox = crossChainTestHarness.outbox; + user1Wallet = wallets[0]; + user2Wallet = wallets[1]; + logger = logger; + logger('Successfully deployed contracts and initialized portal'); + }); +``` + +This fetches the wallets from the sandbox and deploys our cross chain harness on the sandbox! +## Private flow test +#include_code e2e_private_cross_chain /yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts + +## Public flow test +#include_code e2e_public_cross_chain /yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts + +## Running the test +```bash +cd packages/src +yarn test +``` + +## Jest error +Note - you might have a jest error at the end of each test saying "expected 1-2 arguments but got 3". In case case simply remove the "120_000" at the end of each test. We have already set the timeout at the top so this shouldn't be a problem. \ No newline at end of file diff --git a/docs/docs/dev_docs/tutorials/token_portal/withdrawing_from_l1.md b/docs/docs/dev_docs/tutorials/token_portal/withdrawing_to_l1.md similarity index 89% rename from docs/docs/dev_docs/tutorials/token_portal/withdrawing_from_l1.md rename to docs/docs/dev_docs/tutorials/token_portal/withdrawing_to_l1.md index ae485a8f84d..ed5338172ca 100644 --- a/docs/docs/dev_docs/tutorials/token_portal/withdrawing_from_l1.md +++ b/docs/docs/dev_docs/tutorials/token_portal/withdrawing_to_l1.md @@ -1,5 +1,5 @@ --- -title: Withdrawing from L1 +title: Withdrawing to L1 --- This is where we have tokens on Aztec and want to withdraw them back to L1 (i.e. burn them on L2 and mint on L1). Withdrawing from L1 will be public. @@ -12,7 +12,7 @@ Go back to your `main.nr` and paste this: For this to work we will need this helper function, in `util.nr`: -#include_code get_withdraw_content_hash /yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/util.nr rust +#include_code get_withdraw_content_hash /yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr rust **What’s happening here?** @@ -20,7 +20,7 @@ The `exit_to_l1_public` function enables anyone to withdraw their L2 tokens back 1. Like with our deposit function, we need to create the L2 to L1 message. The content is the _amount_ to burn, the recipient address, and who can execute the withdraw on the L1 portal on behalf of the user. It can be `0x0` for anyone, or a specified address. 2. `context.message_portal()` passes this content to the [kernel circuit](../../../concepts/advanced/circuits/kernels/public_kernel.md) which creates the proof for the transaction. The kernel circuit then adds the sender (the L2 address of the bridge + version of aztec) and the recipient (the portal to the L2 address + the chain ID of L1) under the hood, to create the message which gets added as rollup calldata by the sequencer and is stored in the outbox for consumption. -3. Finally, you also burn the tokens on L2! Note that it burning is done at the end to follow the check effects interaction pattern. Note that the caller has to first approve the bridge contract to burn tokens on its behalf. Refer to [burn_public function on the token contract](https://github.com/AztecProtocol/dev-rel/blob/main/tutorials/token-contract/README.md#burn_public). The nonce parameter refers to the approval message that the user creates - also refer to [authorizing token spends here](https://github.com/AztecProtocol/dev-rel/blob/main/tutorials/token-contract/README.md#authorizing-token-spends). +3. Finally, you also burn the tokens on L2! Note that it burning is done at the end to follow the check effects interaction pattern. Note that the caller has to first approve the bridge contract to burn tokens on its behalf. Refer to [burn_public function on the token contract](../writing_token_contract.md#burn_public). The nonce parameter refers to the approval message that the user creates - also refer to [authorizing token spends here](../writing_token_contract.md#authorizing-token-spends). - We burn the tokens from the `msg_sender()`. Otherwise, a malicious user could burn someone else’s tokens and mint tokens on L1 to themselves. One could add another approval flow on the bridge but that might make it complex for other applications to call the bridge. ## Withdrawing Privately @@ -53,4 +53,20 @@ We also use a `_withCaller` parameter to determine the appropriate party that ca We call this pattern _designed caller_ which enables a new paradigm **where we can construct other such portals that talk to the token portal and therefore create more seamless crosschain legos** between L1 and L2. +## Compile code + +We are done with our contracts! Let's compile them: + +Compile your solidity contracts to ensure everything is okay: +```bash +cd packages/l1-contracts +npx hardhat compile +``` + +Compile your aztec-nr contracts: +```bash +cd packages/aztec-contracts +aztec-cli compile token_bridge +``` + In the next step we will write the TypeScript code to deploy our contracts and call on both L1 and L2 so we can see how everything works together. diff --git a/docs/docs/dev_docs/tutorials/uniswap/l2_contract_setup.md b/docs/docs/dev_docs/tutorials/uniswap/l2_contract_setup.md index 211df9f2723..ecf7af820d4 100644 --- a/docs/docs/dev_docs/tutorials/uniswap/l2_contract_setup.md +++ b/docs/docs/dev_docs/tutorials/uniswap/l2_contract_setup.md @@ -18,7 +18,7 @@ To ensure there are no collisions (i.e. when the contract wants to approve the b Under the storage struct, paste this function: -#include_code is_valid_public yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust +#include_code authwit_uniswap_get yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust In this function, the token contract calls the Uniswap contract to check if Uniswap has indeed done the approval. The token contract expects a `is_valid()` function to exit for private approvals and `is_valid_public()` for public approvals. If the action is indeed approved, it expects that the contract would return the function selector for `is_valid()`  in both cases. The Aztec.nr library exposes this constant for ease of use. The token contract also emits a nullifier for this message so that this approval (with the nonce) can’t be used again. diff --git a/docs/docs/dev_docs/tutorials/uniswap/swap_privately.md b/docs/docs/dev_docs/tutorials/uniswap/swap_privately.md index ee78d95a8cd..155a32cea32 100644 --- a/docs/docs/dev_docs/tutorials/uniswap/swap_privately.md +++ b/docs/docs/dev_docs/tutorials/uniswap/swap_privately.md @@ -5,7 +5,7 @@ title: Swapping Privately In the `main.nr` contract we created [previously](./l2_contract_setup.md), paste these functions: #include_code swap_private yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust -#include_code approve_bridge yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust +#include_code authwit_uniswap_set yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust #include_code assert_token_is_same yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust This flow works very similarly to the public flow except: diff --git a/docs/sidebars.js b/docs/sidebars.js index bec4f02a5c1..eec51acf848 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -240,7 +240,7 @@ const sidebars = { "dev_docs/tutorials/token_portal/depositing_to_aztec", "dev_docs/tutorials/token_portal/minting_on_aztec", "dev_docs/tutorials/token_portal/cancelling_deposits", - "dev_docs/tutorials/token_portal/withdrawing_from_l1", + "dev_docs/tutorials/token_portal/withdrawing_to_l1", "dev_docs/tutorials/token_portal/typescript_glue_code", ], }, diff --git a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts index 35e152271e6..3f4fbd352f6 100644 --- a/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_cross_chain_messaging.test.ts @@ -1,9 +1,13 @@ -import { AccountWallet, AztecAddress, computeAuthWitMessageHash } from '@aztec/aztec.js'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { Fr } from '@aztec/foundation/fields'; -import { DebugLogger } from '@aztec/foundation/log'; +import { + AccountWallet, + AztecAddress, + DebugLogger, + EthAddress, + Fr, + TxStatus, + computeAuthWitMessageHash, +} from '@aztec/aztec.js'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types'; -import { TxStatus } from '@aztec/types'; import { CrossChainTestHarness } from './fixtures/cross_chain_test_harness.js'; import { delay, setup } from './fixtures/utils.js'; @@ -48,8 +52,8 @@ describe('e2e_cross_chain_messaging', () => { afterEach(async () => { await teardown(); }); - - it('Milestone 2: Deposit funds from L1 -> L2 and withdraw back to L1', async () => { + // docs:start:e2e_private_cross_chain + it('Privately deposit funds from L1 -> L2 and withdraw back to L1', async () => { // Generate a claim secret using pedersen const l1TokenBalance = 1000000n; const bridgeAmount = 100n; @@ -113,6 +117,7 @@ describe('e2e_cross_chain_messaging', () => { expect(await outbox.read.contains([entryKey.toString(true)])).toBeFalsy(); }, 120_000); + // docs:end:e2e_private_cross_chain // Unit tests for TokenBridge's private methods. it('Someone else can mint funds to me on my behalf (privately)', async () => { diff --git a/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts b/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts index 1135d44ba7c..082a6aad120 100644 --- a/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts +++ b/yarn-project/end-to-end/src/e2e_public_cross_chain_messaging.test.ts @@ -12,9 +12,9 @@ describe('e2e_public_cross_chain_messaging', () => { let logger: DebugLogger; let teardown: () => Promise; - let ownerWallet: AccountWallet; + let user1Wallet: AccountWallet; let user2Wallet: AccountWallet; - let ownerEthAddress: EthAddress; + let ethAccount: EthAddress; let ownerAddress: AztecAddress; let crossChainTestHarness: CrossChainTestHarness; @@ -33,11 +33,11 @@ describe('e2e_public_cross_chain_messaging', () => { ); l2Token = crossChainTestHarness.l2Token; l2Bridge = crossChainTestHarness.l2Bridge; - ownerEthAddress = crossChainTestHarness.ethAccount; + ethAccount = crossChainTestHarness.ethAccount; ownerAddress = crossChainTestHarness.ownerAddress; outbox = crossChainTestHarness.outbox; teardown = teardown_; - ownerWallet = wallets[0]; + user1Wallet = wallets[0]; user2Wallet = wallets[1]; logger = logger_; @@ -48,7 +48,8 @@ describe('e2e_public_cross_chain_messaging', () => { await teardown(); }); - it('Milestone 2: Deposit funds from L1 -> L2 and withdraw back to L1', async () => { + // docs:start:e2e_public_cross_chain + it('Publicly deposit funds from L1 -> L2 and withdraw back to L1', async () => { // Generate a claim secret using pedersen const l1TokenBalance = 1000000n; const bridgeAmount = 100n; @@ -60,7 +61,7 @@ describe('e2e_public_cross_chain_messaging', () => { // 2. Deposit tokens to the TokenPortal const messageKey = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); - expect(await crossChainTestHarness.getL1BalanceOf(ownerEthAddress)).toBe(l1TokenBalance - bridgeAmount); + expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); // Wait for the archiver to process the message await delay(5000); /// waiting 5 seconds. @@ -86,7 +87,7 @@ describe('e2e_public_cross_chain_messaging', () => { l2Bridge.address, l2Token.methods.burn_public(ownerAddress, withdrawAmount, nonce).request(), ); - await ownerWallet.setPublicAuth(burnMessageHash, true).send().wait(); + await user1Wallet.setPublicAuth(burnMessageHash, true).send().wait(); // 5. Withdraw owner's funds from L2 to L1 const entryKey = await crossChainTestHarness.checkEntryIsNotInOutbox(withdrawAmount); @@ -94,14 +95,13 @@ describe('e2e_public_cross_chain_messaging', () => { await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, afterBalance - withdrawAmount); // Check balance before and after exit. - expect(await crossChainTestHarness.getL1BalanceOf(ownerEthAddress)).toBe(l1TokenBalance - bridgeAmount); + expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); await crossChainTestHarness.withdrawFundsFromBridgeOnL1(withdrawAmount, entryKey); - expect(await crossChainTestHarness.getL1BalanceOf(ownerEthAddress)).toBe( - l1TokenBalance - bridgeAmount + withdrawAmount, - ); + expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount + withdrawAmount); expect(await outbox.read.contains([entryKey.toString(true)])).toBeFalsy(); }, 120_000); + // docs:end:e2e_public_cross_chain // Unit tests for TokenBridge's public methods. @@ -114,7 +114,7 @@ describe('e2e_public_cross_chain_messaging', () => { await crossChainTestHarness.mintTokensOnL1(l1TokenBalance); const messageKey = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); - expect(await crossChainTestHarness.getL1BalanceOf(ownerEthAddress)).toBe(l1TokenBalance - bridgeAmount); + expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount); // Wait for the archiver to process the message await delay(5000); /// waiting 5 seconds. @@ -128,7 +128,7 @@ describe('e2e_public_cross_chain_messaging', () => { await expect( l2Bridge .withWallet(user2Wallet) - .methods.claim_public(user2Wallet.getAddress(), bridgeAmount, ownerEthAddress, messageKey, secret) + .methods.claim_public(user2Wallet.getAddress(), bridgeAmount, ethAccount, messageKey, secret) .simulate(), ).rejects.toThrow(); @@ -136,7 +136,7 @@ describe('e2e_public_cross_chain_messaging', () => { logger("user2 consumes owner's message on L2 Publicly"); const tx = l2Bridge .withWallet(user2Wallet) - .methods.claim_public(ownerAddress, bridgeAmount, ownerEthAddress, messageKey, secret) + .methods.claim_public(ownerAddress, bridgeAmount, ethAccount, messageKey, secret) .send(); const receipt = await tx.wait(); expect(receipt.status).toBe(TxStatus.MINED); @@ -154,8 +154,8 @@ describe('e2e_public_cross_chain_messaging', () => { // Should fail as owner has not given approval to bridge burn their funds. await expect( l2Bridge - .withWallet(ownerWallet) - .methods.exit_to_l1_public(ownerEthAddress, withdrawAmount, EthAddress.ZERO, nonce) + .withWallet(user1Wallet) + .methods.exit_to_l1_public(ethAccount, withdrawAmount, EthAddress.ZERO, nonce) .simulate(), ).rejects.toThrowError('Assertion failed: Message not authorized by account'); }); @@ -166,7 +166,7 @@ describe('e2e_public_cross_chain_messaging', () => { await crossChainTestHarness.mintTokensOnL1(bridgeAmount); const messageKey = await crossChainTestHarness.sendTokensToPortalPublic(bridgeAmount, secretHash); - expect(await crossChainTestHarness.getL1BalanceOf(ownerEthAddress)).toBe(0n); + expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(0n); // Wait for the archiver to process the message await delay(5000); /// waiting 5 seconds. @@ -177,7 +177,7 @@ describe('e2e_public_cross_chain_messaging', () => { await expect( l2Bridge .withWallet(user2Wallet) - .methods.claim_private(secretHash, bridgeAmount, ownerEthAddress, messageKey, secret) + .methods.claim_private(secretHash, bridgeAmount, ethAccount, messageKey, secret) .simulate(), ).rejects.toThrowError("Cannot satisfy constraint 'l1_to_l2_message_data.message.content == content"); }); diff --git a/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts index 66c61f6aa68..2a173c1e597 100644 --- a/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/fixtures/cross_chain_test_harness.ts @@ -1,7 +1,6 @@ -import { TxHash, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; -import { AztecAddress, EthAddress, Fr } from '@aztec/circuits.js'; +// docs:start:cross_chain_test_harness +import { AztecAddress, DebugLogger, EthAddress, Fr, TxHash, Wallet, computeMessageSecretHash } from '@aztec/aztec.js'; import { sha256ToField } from '@aztec/foundation/crypto'; -import { DebugLogger } from '@aztec/foundation/log'; import { OutboxAbi } from '@aztec/l1-artifacts'; import { TokenBridgeContract, TokenContract } from '@aztec/noir-contracts/types'; import { NotePreimage, PXE, TxStatus } from '@aztec/types'; @@ -323,3 +322,4 @@ export class CrossChainTestHarness { expect(unshieldReceipt.status).toBe(TxStatus.MINED); } } +// docs:end:cross_chain_test_harness diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 48c5179190b..23eb4004f22 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -338,6 +338,7 @@ export function getLogger() { return createDebugLogger('aztec:' + describeBlockName); } +// docs:start:deployAndInitializeTokenAndBridgeContracts /** * Deploy L1 token and portal, initialize portal, deploy a non native l2 token contract, its L2 bridge contract and attach is to the portal. * @param wallet - the wallet instance @@ -429,7 +430,9 @@ export async function deployAndInitializeTokenAndBridgeContracts( return { token, bridge, tokenPortalAddress, tokenPortal, underlyingERC20 }; } +// docs:end:deployAndInitializeTokenAndBridgeContracts +// docs:start:delay /** * Sleep for a given number of milliseconds. * @param ms - the number of milliseconds to sleep for @@ -437,6 +440,7 @@ export async function deployAndInitializeTokenAndBridgeContracts( export function delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } +// docs:end:delay /** * Checks the number of encrypted logs in the last block is as expected. diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 49933e2477c..02f4b0c05ae 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -192,6 +192,7 @@ export const deployL1Contracts = async ( }; }; +// docs:start:deployL1Contract /** * Helper function to deploy ETH contracts. * @param walletClient - A viem WalletClient. @@ -222,3 +223,4 @@ export async function deployL1Contract( return EthAddress.fromString(receipt.contractAddress!); } +// docs:end:deployL1Contract 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 41fde87980a..18c4cc849ef 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 @@ -1,4 +1,4 @@ -// docs:start:imports_and_constructor +// docs:start:token_bridge_imports mod token_interface; // Minimal implementation of the token bridge that can move funds between L1 <> L2. @@ -17,10 +17,13 @@ contract TokenBridge { types::address::{AztecAddress, EthereumAddress}, selector::compute_selector, }; + // docs:end:token_bridge_imports + use dep::token_portal_content_hash_lib::{get_mint_public_content_hash, get_mint_private_content_hash, get_withdraw_content_hash}; use crate::token_interface::Token; + // docs:start:token_bridge_storage_and_constructor // Storage structure, containing all storage, and specifying what slots they use. struct Storage { token: PublicState, @@ -44,7 +47,8 @@ contract TokenBridge { let selector = compute_selector("_initialize((Field))"); context.call_public_function(context.this_address(), selector, [token.address]); } - // docs:end:imports_and_constructor + // docs:end:token_bridge_storage_and_constructor + // docs:start:claim_public // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount publicly diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr index f4d89558415..6c44755b8a4 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr @@ -1,3 +1,4 @@ +// docs:start:token_brodge_token_interface use dep::aztec::{ context::{ PrivateContext, PublicContext, Context }, selector::compute_selector, @@ -49,3 +50,4 @@ impl Token { } // docs:end:private_burn_interface } +// docs:end:token_brodge_token_interface diff --git a/yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr b/yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr index aeadac9d974..98cf094aabc 100644 --- a/yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr +++ b/yarn-project/noir-contracts/src/contracts/token_portal_content_hash_lib/src/lib.nr @@ -1,5 +1,5 @@ -use dep::std::hash::pedersen_with_separator; // docs:start:mint_public_content_hash_nr +use dep::std::hash::pedersen_with_separator; use dep::aztec::hash::{sha256_to_field}; // Computes a content hash of a deposit/mint_public message. diff --git a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr index 7c2c8b3f22f..35472e07ff4 100644 --- a/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr @@ -233,6 +233,7 @@ contract Uniswap { nonce_for_burn_approval, ); } + // docs:end:authwit_uniswap_set // docs:start:assert_token_is_same #[aztec(public)] @@ -241,12 +242,10 @@ contract Uniswap { } // docs:end:assert_token_is_same - // /// Unconstrained /// // this method exists solely for e2e tests to test that nonce gets incremented each time. unconstrained fn nonce_for_burn_approval() -> Field { storage.nonce_for_burn_approval.read() } - // docs:end:assert_token_is_same } \ No newline at end of file