Skip to content

Commit

Permalink
finish uniswap portal
Browse files Browse the repository at this point in the history
  • Loading branch information
rahul-kothari committed Oct 12, 2023
1 parent 0e650af commit c033a51
Show file tree
Hide file tree
Showing 17 changed files with 222 additions and 104 deletions.
22 changes: 11 additions & 11 deletions docs/docs/dev_docs/tutorials/token_portal/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ However if you’d rather skip this part, our dev-rels repo contains the starter
- [docker](https://docs.docker.com/)
- [Aztec sandbox](https://docs.aztec.network/dev_docs/getting_started/sandbox) - you should have this running before starting the tutorial

```bash
/bin/bash -c "$(curl -fsSL 'https://sandbox.aztec.network')"
```sh
/bin/sh -c "$(curl -fsSL 'https://sandbox.aztec.network')"
```

- Nargo

```bash
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
```sh
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | sh
noirup -v #include_noir_version
```

# Create a yarn project

Our root yarn project will house everything ✨

```bash
```sh
mkdir aztec-token-bridge && cd aztec-token-bridge && yarn init
```

Expand Down Expand Up @@ -92,7 +92,7 @@ Now inside `aztec-token-bridge` create a new directory called `aztec-contracts`

Inside `aztec-contracts`, create a nargo contract project by running

```bash
```sh
mkdir aztec-contracts
cd aztec-contracts
nargo new --contract token_bridge
Expand Down Expand Up @@ -135,20 +135,20 @@ aztec-contracts

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
```sh
mkdir l1-contracts
cd l1-contracts
npx hardhat init
```
Once you have a hardhat project set up, delete the existing contracts and create a `TokenPortal.sol`:

```bash
```sh
cd contracts
rm *.sol
touch TokenPortal.sol
```

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.
Also add Aztec's L1-contracts that includes the interfaces to Aztec Inbox, Outbox and Registry smart contracts, which we will need to send messages between L1 and L2.

```
yarn add @aztec/l1-contracts
Expand All @@ -174,7 +174,7 @@ We will use `viem` instead of `ethers.js` although ethers works fine too! We wil

Inside the root directory, run

```bash
```sh
mkdir src && cd src && yarn init -yp
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 Expand Up @@ -238,7 +238,7 @@ In this package we will also add a jest config file: `jest.config.json`

Finally, we will create a test file, in the `src` package:

```bash
```sh
mkdir test && cd test
touch cross_chain_messaging.test.ts
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ 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);
jest.setTimeout(90_000);

let logger: DebugLogger;
// include code:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
---
title: Executing Private Swap on L1
---

This works very similarly to the public flow.

In the public flow, you can call `claim_public()` on the output token bridge which consumes the deposit message and mints your assets.

In the private flow, you can choose to leak your secret for L1 → L2 message consumption to let someone mint the notes on L2 and then you can later redeem these notes to yourself by presenting the preimage to `secret_hash_for_redeeming_minted_notes` and calling the `redeem_shield()` method on the token contract.

In your `UniswapPortal.sol`, paste this:
To execute the swaps on L1, go back to the `UniswapPortal.sol` we [created earlier](./l1_portal.md) in `packages/l1-contracts`.

#include_code solidity_uniswap_swap_private l1-contracts/test/portals/UniswapPortal.sol solidity

This works very similarly to the public flow.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: Executing Swap on L1
title: Solidity Code to Execute Swap on L1
---

To execute the swaps on L1, go back to the `TokenPortal.sol` we [created earlier](./l1_portal.md).
To execute the swaps on L1, go back to the `UniswapPortal.sol` we [created earlier](./l1_portal.md) in `packages/l1-contracts`.

Under the struct, paste this code that will manage the public flow:

#include_code solidity_uniswap_swap l1-contracts/test/portals/UniswapPortal.sol solidity
#include_code solidity_uniswap_swap_public l1-contracts/test/portals/UniswapPortal.sol solidity

**What’s happening here?**

Expand All @@ -23,4 +23,6 @@ Under the struct, paste this code that will manage the public flow:

To incentivize the sequencer to pick up this message, we pass a fee to the deposit message.

This concludes the public flow.

In the next step, we will code a private flow in the Aztec.nr contract.
17 changes: 14 additions & 3 deletions docs/docs/dev_docs/tutorials/uniswap/l1_portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@ In this step we will set up our Solidity portal contract.
In `l1-tokens` create a new file called `UniswapPortal.sol`

```bash
cd l1-tokens && touch UniswapPortal.sol
cd packages/l1-tokens && touch UniswapPortal.sol
```

and paste this inside:
```solidity
pragma solidity >=0.8.18;
#include_code setup l1-contracts/test/portals/UniswapPortal.sol solidity
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
In this set up we define the `initialize()` function and a struct (`LocalSwapVars`) to manage assets being swapped.
import {IRegistry} from "@aztec/l1-contracts/src/core/interfaces/messagebridge/IRegistry.sol";
import {DataStructures} from "@aztec/l1-contracts/src/core/libraries/DataStructures.sol";
import {Hash} from "@aztec/l1-contracts/src/core/libraries/Hash.sol";
#include_code setup l1-contracts/test/portals/UniswapPortal.sol solidity raw
```

In this set up we defined the `initialize()` function and a struct (`LocalSwapVars`) to manage assets being swapped.

Like we saw in the [TokenPortal](../token_portal/depositing_to_aztec.md), we initialize this portal with the registry contract address (to fetch the appropriate inbox and outbox) and the portal’s sister contract address on L2.

In the next step we will set up the appropriate L2 Uniswap contract!
13 changes: 11 additions & 2 deletions docs/docs/dev_docs/tutorials/uniswap/l2_contract_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,23 @@ Our main contract will live inside `uniswap/src/main.nr`. In `main.nr`, paste th

**What’s happening here?**

Because Uniswap is the one approving, it stores a map of all the actions that are approved. The approval message is hashed to a field and stored in the contract’s storage in the `approved_action` map.
Because Uniswap has to approve the bridge to withdraw funds, it has to handle the approvals. So it stores a map of all the actions that are approved. The approval message is hashed to a field and stored in the contract’s storage in the `approved_action` map.

To ensure there are no collisions (i.e. when the contract wants to approve the bridge of the exact same amount, the message hash would be the same), we also keep a nonce that gets incremented each time after use in a message.

Under the storage struct, paste this function:
## Building the approval flow
Next, paste this function:

#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.

This is similar to the [Authwit flow](../../contracts/resources/common_patterns/authwit.md).

However we don't have a function that actually creates the approved message and stores the action. This method should be responsible for creating the approval and then calling the token bridge to withdraw the funds to L1:

#include_code authwit_uniswap_set yarn-project/noir-contracts/src/contracts/uniswap_contract/src/main.nr rust

Notice how the nonce also gets incremented.

In the next step we’ll go through a public swapping flow.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: Redeeming Swapped Assets on L2
---
So you emitted a message to withdraw input tokens to L1 and a message to swap. Then you or someone on your behalf can swap on L1 and emit a message to deposit swapped assets to L2,

You still need to "claim" these swapped funds on L2.

In the public flow, you can call [`claim_public()`](../token_portal/minting_on_aztec.md) on the output token bridge which consumes the deposit message and mints your assets.

In the private flow, you can choose to leak your secret for L1 → L2 message consumption to let someone mint the notes on L2 (by calling [`claim_private()`](../token_portal/minting_on_aztec.md) on the output token bridge) and then you can later redeem these notes to yourself by presenting the preimage to `secret_hash_for_redeeming_minted_notes` and calling the `redeem_shield()` method on the token contract.

In the next step we will write the typescript code that interacts with all these contracts on the sandbox to actually execute the swaps!
32 changes: 13 additions & 19 deletions docs/docs/dev_docs/tutorials/uniswap/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,25 @@ This tutorial builds on top of the project created in the previous tutorial. It
If you don’t have this, you can find the code for it [here].
// TODO full code in dev-rels rouper

# L1 contracts

We will need one more L1 contract - _ISwapRouter_ - which you can find [here](https://github.com/AztecProtocol/aztec-packages/blob/c794533754a9706d362d0374209df9eb5b6bfdc7/l1-contracts/test/external/ISwapRouter.sol). Add this to `l1-contracts/external`:
# Uniswap contract
To interact with uniswap we need to add it's interface:

```bash
cd l1-contracts && mkdir external && touch ISwapRouter.sol
cd packages/l1-contracts && mkdir external && touch ISwapRouter.sol
```

Inside `ISwapRouter.sol` paste this:

"#include_code iswaprouter l1-contracts/test/external/ISwapRouter.sol solidity
#include_code iswaprouter /l1-contracts/test/external/ISwapRouter.sol solidity

This is an interface for the Uniswap V3 Router, providing token swapping functionality. The contract defines methods for token swaps, both between two tokens or via a multi-hop path. Our portal will interact with the Uniswap V3 Router via this interface to perform token swaps on L1. We’ll see more about this in the next step.

# Create nargo project
# Create another nargo project

In `aztec-packages` create a new nargo project.

```bash
cd aztec-packages && nargo new --contract uniswap
cd packages/aztec-packages && nargo new --contract uniswap
```

Now your `aztec-contracts` will look like this:
Expand All @@ -43,7 +42,7 @@ aztec-contracts
├── main.nr
```

Inside the new `Nargo.toml` paste this in `[dependencies]`:
Inside `uniswap/Nargo.toml` paste this in `[dependencies]`:

```json
[dependencies]
Expand All @@ -60,21 +59,14 @@ The `main.nr` will utilize a few helper functions that are outside the scope of
cd uniswap/src && touch util.nr && touch interface.nr
```

Inside `util.nr` paste this:

#include_code uniswap_util
yarn-project/noir-contracts/src/contracts/uniswap_contract/src/util.nr rust

This file contains two functions, `compute_swap_private_content_hash` and `compute_swap_public_content_hash`, which generate content hashes for L2 to L1 messages representing swap transactions.

and inside `interface.nr` paste this:
Inside `interface.nr` paste this:

#include_code interfaces yarn-project/noir-contracts/src/contracts/uniswap_contract/src/interfaces.nr rust

This defines two structs: `Token` and `TokenBridge.`
This creates interfaces for the `Token` contract and `TokenBridge` contract

- `Token` represents an Aztec token, allowing for public transfers (`transfer_public`) and private-to-public conversions (`unshield`).
- The `TokenBridge` struct facilitates interactions with our bridge contract, enabling getting the associated token (`token`), claiming tokens in a public context (`claim_public`), and exiting tokens to L1. (`exit_to_l1_public`).
- `Token` is a reference implementation for a token on Aztec. Here we just need two methods - [`transfer_public`](../writing_token_contract.md#transfer_public) and [`unshield()`](../writing_token_contract.md#unshield).
- The `TokenBridge` facilitates interactions with our [bridge contract](../token_portal/main.md). Here we just need its [`exit_to_l1_public`](../token_portal/withdrawing_to_l1.md)

# Run Aztec sandbox

Expand All @@ -83,3 +75,5 @@ You will need a running sandbox.
```bash
/bin/bash -c "$(curl -fsSL 'https://sandbox.aztec.network')"
```

Next we will write the L1 Uniswap Portal
19 changes: 13 additions & 6 deletions docs/docs/dev_docs/tutorials/uniswap/swap_privately.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@
title: Swapping Privately
---

In the `main.nr` contract we created [previously](./l2_contract_setup.md), paste these functions:
In the `uniswap/src/main.nr` contract we created [previously](./l2_contract_setup.md) in `packages/aztec-contracts/uniswap`, paste these functions:

#include_code swap_private 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:
This uses a util function `compute_swap_private_content_hash()` - let's add that.

- Contracts on Aztec can’t directly hold notes. Since private tokens are basically notes, it isn’t possible for the Uniiswap contract to hold the notes and then approve the token bridge to burn them (since the Uniswap contract would then need to have a private key associated with it that can sign the payload for approval.)
- To work around this, the user can unshield their private tokens into Uniswap L2 contract. Unshielding is a private method on the token contract that reduces a user’s private balance and then calls a public method to increase the recipient’s (ie Uniswap) public balance. Remember that first all private methods are executed and then later all public methods will be - so the Uniswap contract won’t have the funds until public execution begins.
- As a result `swap_private()` calls the internal public method which approves the input token bridge to burn Uniswap’s tokens and creates an L2 → L1 message to exit to L1.
In `util.nr`, add:
#include_code compute_swap_private_content_hash yarn-project/noir-contracts/src/contracts/uniswap_contract/src/util.nr rust

This flow works similarly to the public flow with a few notable changes:

- Notice how in the `swap_private()`, user has to pass in `token` address which they didn't in the public flow? Since `swap_private()` is a private method, it can't read what token is publicly stored on the token bridge, 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. Note that 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 done. So first the burn would happen and only later the sequencer asserts that the token is same. Note that 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.
- In the public flow, the user calls `transfer_public()`. Here instead, the user calls `unshield()`. Why? The user can't directly transfer their private tokens, their notes to the uniswap contract, because later the Uniswap contract has to approve the bridge to burn these notes and withdraw to L1. The authwit flow for the private domain requires a signature from the `sender`, which in this case would be the Uniswap contract. For the contract to sign, it would need a private key associated to it. But who would operate this key?
- To work around this, the user can unshield their private tokens into Uniswap L2 contract. Unshielding would convert user's private notes to public balance. It is a private method on the token contract that reduces a user’s private balance and then calls a public method to increase the recipient’s (ie Uniswap) public balance. **Remember that first all private methods are executed and then later all public methods will be - so the Uniswap contract won’t have the funds until public execution begins.**
- Now uniswap has public balance (like with the public flow). Hence, `swap_private()` calls the internal public method which approves the input token bridge to burn Uniswap’s tokens and calls `exit_to_l1_public` to create an L2 → L1 message to exit to L1.
- Constructing the message content for swapping works exactly as the public flow except instead of specifying who would be the Aztec address that receives the swapped funds, we specify a secret hash (`secret_hash_for_redeeming_minted_notes`). Only those who know the preimage to the secret can later redeem the minted notes to themselves.

In the next step we will write the code to execute this swap on L1.
Loading

0 comments on commit c033a51

Please sign in to comment.