Skip to content

Commit

Permalink
finish token portal tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
rahul-kothari committed Oct 12, 2023
1 parent e59ff00 commit d8cc5ef
Show file tree
Hide file tree
Showing 16 changed files with 268 additions and 91 deletions.
27 changes: 23 additions & 4 deletions docs/docs/dev_docs/tutorials/token_portal/depositing_to_aztec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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:
Expand All @@ -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.
Expand Down
35 changes: 25 additions & 10 deletions docs/docs/dev_docs/tutorials/token_portal/minting_on_aztec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
51 changes: 12 additions & 39 deletions docs/docs/dev_docs/tutorials/token_portal/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.
Expand All @@ -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
```

Expand Down
140 changes: 139 additions & 1 deletion docs/docs/dev_docs/tutorials/token_portal/typescript_glue_code.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,142 @@
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 index.test.ts
```

Open index.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 `index.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
## 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.
Loading

0 comments on commit d8cc5ef

Please sign in to comment.