-
Notifications
You must be signed in to change notification settings - Fork 614
fix(docs): Update the token bridge tutorial #11578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 23 commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
3cc85fe
add test
critesjosh 310b3eb
update
critesjosh 5d066b1
rm old
critesjosh 546d083
fix links
critesjosh 48536f1
update comments
critesjosh 8df63f6
write tutorial
critesjosh 903887a
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 7220398
clarify
critesjosh a276228
Merge branch 'jc/docs-bridge-tutorial' of https://github.com/AztecPro…
critesjosh cb2e4c4
use compose for test
critesjosh 84528d3
fix link to test file
critesjosh 2353b49
fmt
critesjosh 402a54e
run test w/ compose
critesjosh 37a88e4
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh cf5cc72
fmt
critesjosh c2f8201
fix filename
critesjosh 61f46b1
add token bridge test to ci
critesjosh a4a7cf0
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh e884962
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh d7c93fa
passing
critesjosh 9732e5f
try bump test timeout
critesjosh 4890cbf
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh d742f5a
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 74f107f
cats comments
critesjosh f8dba2a
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh a3855c7
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 4ffb66d
fix links
critesjosh 450ac31
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 80acf5c
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 47036ce
add cat's suggestions
critesjosh da23343
update rootDir
critesjosh 685c44f
simp tsconfig, specify node version
critesjosh 531540b
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 41c5188
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh eedceea
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh 8037bae
james comments
critesjosh 9ce59b4
Merge branch 'master' into jc/docs-bridge-tutorial
critesjosh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
docs/docs/tutorials/codealong/contract_tutorials/token_bridge.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| --- | ||
| title: "Token Bridge Tutorial" | ||
| --- | ||
|
|
||
| This tutorial goes over how to create the contracts necessary to create a portal (aka token bridge) and how a developer can use it. | ||
|
|
||
| In this tutorial, we will go over the components of a token bridge and how to deploy them, as well as show how to bridge tokens publicly from L1 to L2 and back, using aztec.js. | ||
|
|
||
| ## Components | ||
|
|
||
| Bridges in Aztec involve several components across L1 and L2: | ||
|
|
||
| - L1 contracts: | ||
| - `ERC20.sol`: An ERC20 contract that represents assets on L1 | ||
| - `TokenPortal.sol`: Manages the passing of messages from L1 to L2 | ||
| - L2 contracts: | ||
| - `Token`: Manages the tokens on L2 | ||
| - `TokenBridge`: Manages the bridging of tokens between L2 and L1 | ||
|
|
||
| ### `TokenPortal.sol` | ||
|
|
||
| `TokenPortal.sol` is the contract that manages the passing of messages from L1 to L2. It is deployed on L1, is linked to a specific token on L1 and a corresponding contract on L2. The `registry` is used to find the rollup and the corresponding `inbox` and `outbox` contracts. | ||
|
|
||
| ### Deposit to Aztec | ||
|
|
||
| Messages content that is passed to Aztec is limited to a single field element (~254 bits), so if the message content is larger than that, it is hashed, and the message hash is passed an verified on the receiving contract. There is a utility function in the `Hash` library to hash messages (using `sha256`) to field elements. | ||
|
|
||
| It is a good practice to include all of the parameters used by the L2 contract in the message content, so that the receiving contract can verify the message and malicious actors cannot modify the message. | ||
|
|
||
| The Aztec message Inbox expects a recipient Aztec address that can consume the message (the corresponding L2 bridge contract), the Aztec version (similar to Ethereum's `chainId`), the message content hash (which includes the token recipient and amount in this case), and a `secretHash`, where the corresponding `secret` is used to consume the message on the receiving contract. | ||
|
|
||
| So in summary, it deposits tokens to the portal, encodes a mint message, hashes it, and sends it to the Aztec rollup via the Inbox. The L2 token contract can then mint the tokens when it processes the message. | ||
|
|
||
| Note that because L1 is public, everyone can inspect and figure out the contentHash and the recipient contract address. | ||
|
|
||
| #### `depositToAztecPublic` | ||
|
|
||
| #include_code deposit_public l1-contracts/test/portals/TokenPortal.sol solidity | ||
|
|
||
| #### `depositToAztecPrivate` | ||
|
|
||
| #include_code deposit_private l1-contracts/test/portals/TokenPortal.sol solidity | ||
|
|
||
| **So how do we privately consume the message on Aztec?** | ||
|
|
||
| On Aztec, anytime something is consumed (i.e. deleted), we emit a nullifier hash and add it to the nullifier tree. This prevents double-spends. The nullifier hash is a hash of the message that is consumed. So without the secret, one could reverse engineer the expected nullifier hash that might be emitted on L2 upon message consumption. To consume the message on L2, the user provides a secret to the private function, which computes the hash and asserts that it matches to what was provided in the L1->L2 message. This secret is included in the nullifier hash computation and the nullifier is added to the nullifier tree. Anyone inspecting the blockchain won’t know which nullifier hash corresponds to the L1->L2 message consumption. | ||
|
|
||
| ### Minting on Aztec | ||
|
|
||
| In the previous step, we moved our funds to the bridge and created a L1->L2 message. Upon building the next rollup block, the sequencer asks the L1 inbox contract for any incoming messages and adds them to the Aztec block's L1->L2 message tree, so an application on L2 can prove that the message exists and can consume it. | ||
|
|
||
| #include_code claim_public /noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr rust | ||
|
|
||
| What's happening here? | ||
|
|
||
| 1. compute the content hash of the message | ||
| 2. consume the message | ||
| 3. mint the tokens | ||
|
|
||
| :::note | ||
|
|
||
| The Aztec `TokenBridge` contract should be an authorized minter in the corresponding Aztec `Token` contract so that it is able to complete mints to the intended recipient. | ||
|
|
||
| ::: | ||
|
|
||
| ### Withdraw to L1 | ||
|
|
||
| Now we have tokens on L2, we can withdraw them back to L1. You can withdraw part of a public or private balance to L1, but the amount and the recipient on L1 will be public. | ||
|
|
||
| Sending tokens to L1 involves burning the tokens on L2 and creating a L2->L1 message. The message 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. | ||
|
|
||
| For both the public and private flow, we use the same mechanism to determine the content hash. This is because on L1, things are public anyway. The only different between the two functions is that in the private domain we have to nullify user’s notes where as in the public domain we subtract the balance from the user. | ||
|
|
||
| ### Aztec `TokenBridge` | ||
|
|
||
| #### `exit_to_L1_public` | ||
|
|
||
| #include_code exit_to_l1_public /noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr rust | ||
|
|
||
| #### `exit_to_L1_private` | ||
|
|
||
| This function works very similarly to the public version, except here we burn user’s private notes. | ||
|
|
||
| #include_code exit_to_l1_private /noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr rust | ||
|
|
||
| Since this is a private method, it can't read what token is publicly stored. So instead the user passes a token address, and `_assert_token_is_same()` checks that this user provided address is same as the one in storage. | ||
|
|
||
| Because public functions are executed by the sequencer while private methods are executed locally, all public calls are always done _after_ all private calls are completed. So first the burn would happen and only later the sequencer asserts that the token is same. The sequencer just sees a request to `execute_assert_token_is_same` and therefore has no context on what the appropriate private method was. If the assertion fails, then the kernel circuit will fail to create a proof and hence the transaction will be dropped. | ||
|
|
||
| A user must sign an approval message to let the contract burn tokens on their behalf. The nonce refers to this approval message. | ||
|
|
||
| ### L1 `TokenPortal.sol` | ||
|
|
||
| After the transaction is completed on L2, the portal must call the outbox to successfully transfer funds to the user on L1. Like with deposits, things can be complex here. For example, what happens if the transaction was done on L2 to burn tokens but can’t be withdrawn to L1? Then the funds are lost forever! How do we prevent this? | ||
|
|
||
| #include_code token_portal_withdraw /l1-contracts/test/portals/TokenPortal.sol solidity | ||
|
|
||
| Here we reconstruct the L2 to L1 message and check that this message exists on the outbox. If so, we consume it and transfer the funds to the recipient. As part of the reconstruction, the content hash looks similar to what we did in our bridge contract on aztec where we pass the amount and recipient to the hash. This way a malicious actor can’t change the recipient parameter to the address and withdraw funds to themselves. | ||
|
|
||
| We also use a `_withCaller` parameter to determine the appropriate party that can execute this function on behalf of the recipient. If `withCaller` is false, then anyone can call the method and hence we use address(0), otherwise only msg.sender should be able to execute. This address should match the `callerOnL1` address we passed in aztec when withdrawing from L2. | ||
|
|
||
| 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. | ||
|
|
||
| ## Running with Aztec.js | ||
|
|
||
| Let's run through the entire process of depositing, minting and withdrawing tokens in Typescript, so you can see how it works in practice. | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| Same prerequisites as the [getting started guide](../../../guides/getting_started.md#prerequisites) and the sandbox. | ||
|
|
||
| ### ProjectSetup | ||
|
|
||
| Create a new directory for the tutorial and install the dependencies: | ||
|
|
||
| ```bash | ||
| mkdir token-bridge-tutorial | ||
| cd token-bridge-tutorial | ||
| yarn init -y | ||
| yarn add @aztec/aztec.js @aztec/noir-contracts.js @aztec/l1-artifacts @aztec/accounts @aztec/ethereum @aztec/types @types/node typescript@^5.0.4 viem@^2.22.8 tsx | ||
| touch tsconfig.json | ||
| touch index.ts | ||
| ``` | ||
|
|
||
| Add this to your `tsconfig.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "compilerOptions": { | ||
| "rootDir": "../", | ||
| "outDir": "./dest", | ||
| "target": "es2020", | ||
| "lib": ["dom", "esnext", "es2017.object"], | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "strict": true, | ||
| "declaration": true, | ||
| "allowSyntheticDefaultImports": true, | ||
| "esModuleInterop": true, | ||
| "downlevelIteration": true, | ||
| "inlineSourceMap": true, | ||
| "declarationMap": true, | ||
| "importHelpers": true, | ||
| "resolveJsonModule": true, | ||
| "composite": true, | ||
| "skipLibCheck": true | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| and add this to your `package.json`: | ||
|
|
||
| ```json | ||
| // ... | ||
| "type": "module", | ||
| "scripts": { | ||
| "start": "node --import tsx index.ts" | ||
| }, | ||
| // ... | ||
| ``` | ||
|
|
||
| You can run the script we will build in `index.ts` at any point with `yarn start`. | ||
|
|
||
| ### Imports | ||
|
|
||
| Add the following imports to your `index.ts`: | ||
|
|
||
| #include_code imports /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| ### Utility functions | ||
|
|
||
| Add the following utility functions to your `index.ts` below the imports: | ||
|
|
||
| #include_code utils /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| ### Sandbox Setup | ||
|
|
||
| Start the sandbox with: | ||
|
|
||
| ```bash | ||
| aztec start --sandbox | ||
| ``` | ||
|
|
||
| And add the following code to your `index.ts`: | ||
|
|
||
| ```ts | ||
| async function main() { | ||
| #include_code setup /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts raw | ||
| } | ||
|
|
||
| main(); | ||
| ``` | ||
|
|
||
| The rest of the code in the tutorial will go inside the `main()` function. | ||
|
|
||
| Run the script with `yarn start` and you should see the L1 contract addresses printed out. | ||
|
|
||
| ### Deploying the contracts | ||
|
|
||
| Add the following code to `index.ts` to deploy the L2 token contract: | ||
|
|
||
| #include_code deploy-l2-token /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Add the following code to `index.ts` to deploy the L1 token contract and set up the `L1TokenManager` (a utility class to interact with the L1 token contract): | ||
|
|
||
| #include_code deploy-l1-token /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Add the following code to `index.ts` to deploy the L1 portal contract: | ||
|
|
||
| #include_code deploy-portal /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Add the following code to `index.ts` to deploy the L2 bridge contract: | ||
|
|
||
| #include_code deploy-l2-bridge /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| ### Setup contracts | ||
|
|
||
| Add the following code to `index.ts` to authorize the L2 bridge contract to mint tokens on the L2 token contract: | ||
|
|
||
| #include_code authorize-l2-bridge /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Add the following code to `index.ts` to set up the L1 portal contract and `L1TokenPortalManager` (a utility class to interact with the L1 portal contract): | ||
|
|
||
| #include_code setup-portal /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| ### Bridge tokens | ||
|
|
||
| Add the following code to `index.ts` to bridge tokens from L1 to L2: | ||
|
|
||
| #include_code l1-bridge-public /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| We have to send two additional transactions because the network must process 2 blocks for the message to be processed by the archiver. We need to progress by 2 because there is a 1 block lag between when the message is sent to Inbox and when the subtree containing the message is included in the block. Then when it's included it becomes available for consumption in the next block. | ||
|
|
||
| ### Claim on Aztec | ||
|
|
||
| Add the following code to `index.ts` to claim the tokens publicly on Aztec: | ||
|
|
||
| #include_code claim /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| ### Withdraw | ||
|
|
||
| Add the following code to `index.ts` to start the withdraw the tokens to L1: | ||
|
|
||
| #include_code setup-withdrawal /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| We have to send a public authwit to allow the bridge contract to burn tokens on behalf of the user. | ||
|
|
||
| Add the following code to `index.ts` to start the withdraw process on Aztec: | ||
|
|
||
| #include_code l2-withdraw /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Add the following code to `index.ts` to complete the withdraw process on L1: | ||
|
|
||
| #include_code l1-withdraw /yarn-project/end-to-end/src/e2e_token_bridge_tutorial_test.test.ts typescript | ||
|
|
||
| Run `yarn start` to run the script and see the entire process in action. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.