Skip to content

Commit

Permalink
feat(docs): Add uniswap back in as a reference and fix links (#7074)
Browse files Browse the repository at this point in the history
Co-authored-by: josh crites <[email protected]>
  • Loading branch information
catmcgee and critesjosh authored Jun 18, 2024
1 parent fbd3826 commit a4d1df6
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Hence, it's necessary to add a "randomness" field to your note to prevent such a

### L1 -- L2 interactions

Refer to [Token Portal tutorial on bridging tokens between L1 and L2](../../../../tutorials/contract_tutorials/advanced/token_bridge/index.md). This example shows how to:
Refer to [Token Portal tutorial on bridging tokens between L1 and L2](../../../../tutorials/contract_tutorials/advanced/token_bridge/index.md) and/or [Uniswap smart contract example that shows how to swap on L1 using funds on L2](../../../../reference/smart_contract_reference/examples/uniswap/index.md). Both examples show how to:

1. L1 -> L2 message flow
2. L2 -> L1 message flow
Expand All @@ -131,7 +131,7 @@ To send a note to someone, they need to have a key which we can encrypt the note
There are several patterns here:

1. Give the contract a key and share it amongst all participants. This leaks privacy, as anyone can see all the notes in the contract.
2. `Unshield` funds into the contract. This works like ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys.
2. `Unshield` funds into the contract - this is used in the [Uniswap smart contract example where a user sends private funds into a Uniswap Portal contract which eventually withdraws to L1 to swap on L1 Uniswap](../../../../reference/smart_contract_reference/examples/uniswap/index.md). This works like Ethereum - to achieve contract composability, you move funds into the public domain. This way the contract doesn't even need keys.

There are several other designs we are discussing through [in this discourse post](https://discourse.aztec.network/t/how-to-handle-private-escrows-between-two-parties/2440) but they need some changes in the protocol or in our demo contract. If you are interested in this discussion, please participate in the discourse post!

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Importing Aztec.nr
sidebar_position: 4
sidebar_position: 5
---

On this page you will find information about Aztec.nr libraries and up-to-date paths for use in your `Nargo.toml`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"label": "Smart Contract Examples",
"position": 0,
"collapsible": true,
"collapsed": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"label": "Uniswap Bridge",
"position": 0,
"collapsible": true,
"collapsed": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: e2e tests (TypeScript)
sidebar_position: 3
---

## Private flow test

#include_code uniswap_private yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts typescript

## Public flow test

#include_code uniswap_public yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts typescript

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: Overview
sidebar_position: 0
---

import Image from "@theme/IdealImage";

# Swap on L1 Uniswap from L2

This smart contract example allows someone with funds on L2 to be able to swap using L1 Uniswap and then get the swapped assets back to L2. In this example, L1 will refer to Ethereum and L2 will refer to Aztec.

The flow will be:

1. The user withdraws their “input” assets to L1 (i.e. burn them on L2 and create a L2 to L1 message to withdraw)
2. We create an L2 → L1 message to swap on L1
3. On L1, the user gets their input tokens, consumes the swap message, and executes the swap
4. The user deposits the “output” tokens to the output token portal so it can be deposited into L2
5. We will assume that token portals and token bridges for the input and output tokens must exist. These are what we built in the [token bridge tutorial](../../../../tutorials/contract_tutorials/advanced/token_bridge/0_setup.md).

The execution of swap on L1 should be designed such that any 3rd party can execute the swap on behalf of the user. This helps maintain user privacy by not requiring links between L1 and L2 activity.

This reference will cover:
1. Uniswap Portal - a contract on L1 that talks to the input token portal to withdraw the assets, executes the swap, and deposits the swapped tokens back to L2
2. Uniswap L2 contract - a contract on L2 that creates the needed messages to perform the swap on L1

<Image img={require("/img/tutorials/uniswap_flow.png")} />

This diagram describes the private flow.

This code works alongside a token portal that you can learn to build [in this tutorial](../../../../tutorials/contract_tutorials/advanced/token_bridge/0_setup.md).

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: L1 contracts (EVM)
sidebar_position: 2
---

This page goes over the code in the L1 contract for Uniswap, which works alongside a [token portal](../../../../tutorials/contract_tutorials/advanced/token_bridge/index.md).

## Setup

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

## Public swap

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

1. It fetches the input and output tokens we are swapping. The Uniswap portal only needs to know the portal addresses of the input and output as they store the underlying ERC20 token address.
2. Consumes the `withdraw` message to get input tokens on L1 to itself. This is needed to execute the swap.

Before it actually can swap, it checks if the provided swap parameters were what the user actually wanted by creating a message content hash (similar to what we did in the L2 contract) to ensure the right parameters are used.

3. Executes the swap and receives the output funds to itself.

The deadline by which the funds should be swapped is `block.timestamp` i.e. this block itself. This makes things atomic on the L1 side.

4. The portal must deposit the output funds back to L2 using the output token’s portal. For this we first approve the token portal to move Uniswap funds, and then call the portal’s `depositToAztecPublic()` method to transfer funds to the portal and create a L1 → l2 message to mint the right amount of output tokens on L2.

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

You can find the corresponding function on the [L2 contracts page](l2_contract.md#public-swap).

## Private swap

This works very similarly to the public flow.

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

You can find the corresponding function on the [L2 contracts page](l2_contract.md#private-swap).
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: L2 Contracts (Aztec)
sidebar_position: 1
---

This page goes over the code in the L2 contract for Uniswap, which works alongside a [token bridge](../../../../tutorials/contract_tutorials/advanced/token_bridge/0_setup.md).

## Main.nr

### Setup and constructor

#include_code uniswap_setup noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust
We just need to store the portal address for the token that we want to swap.


### Public swap

#include_code swap_public noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust

1. We check that `msg.sender()` has appropriate approval to call this on behalf of the sender by constructing an authwit message and checking if `from` has given the approval (read more about authwit [here](../../../../aztec/concepts/accounts/authwit.md)).
2. We fetch the underlying aztec token that needs to be swapped.
3. We transfer the user’s funds to the Uniswap contract. Like with Ethereum, the user must have provided approval to the Uniswap contract to do so. The user must provide the nonce they used in the approval for transfer, so that Uniswap can send it to the token contract, to prove it has appropriate approval.
4. Funds are added to the Uniswap contract.
5. Uniswap must exit the input tokens to L1. For this it has to approve the bridge to burn its tokens on its behalf and then actually exit the funds. We call the [`exit_to_l1_public()` method on the token bridge](../../../../tutorials/contract_tutorials/advanced/token_bridge/index.md). We use the public flow for exiting since we are operating on public state.
6. It is not enough for us to simply emit a message to withdraw the funds. We also need to emit a message to display our swap intention. If we do not do this, there is nothing stopping a third party from calling the Uniswap portal with their own parameters and consuming our message.

So the Uniswap portal (on L1) needs to know:

- The token portals for the input and output token (to withdraw the input token to L1 and later deposit the output token to L2)
- The amount of input tokens they want to swap
- The Uniswap fee tier they want to use
- The minimum output amount they can accept (for slippage protection)

The Uniswap portal must first withdraw the input tokens, then check that the swap message exists in the outbox, execute the swap, and then call the output token to deposit the swapped tokens to L2. So the Uniswap portal must also be pass any parameters needed to complete the deposit of swapped tokens to L2. From the tutorial on building token bridges we know these are:

- The address on L2 which must receive the output tokens (remember this is public flow)
- The secret hash for consume the L1 to L2 message. Since this is the public flow the preimage doesn’t need to be a secret.

You can find the corresponding function on the [L1 contracts page](l1_contract.md).

### Private swap

#include_code swap_private noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust

This uses a util function `compute_swap_private_content_hash()` - find that [here](#utils)

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.

### Approve the bridge to burn this contract's funds

Both public and private swap functions call this function:

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

### Assertions

#include_code assert_token_is_same noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr rust

This is a simple function that asserts that the token passed in to the function is the one that the bridge is associated with.

## Utils

### Compute content hash for public

#include_code uniswap_public_content_hash noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr rust

This method computes the L2 to L1 message content hash for the public. To find out how it is consumed on L1, view the [L1 contracts page](./l1_contract.md)

### Compute content hash for private

#include_code compute_swap_private_content_hash noir-projects/noir-contracts/contracts/uniswap_contract/src/util.nr rust

This method computes the L2 to L1 message content hash for the private. To find out how it is consumed on L1, view the [L1 contracts page](./l1_contract.md).

## Redeeming assets

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()`](../../../../tutorials/contract_tutorials/advanced/token_bridge/2_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()`](../../../../tutorials/contract_tutorials/advanced/token_bridge/2_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.
2 changes: 1 addition & 1 deletion docs/docs/reference/smart_contract_reference/globals.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Global Variables
description: Documentation of Aztec's Global Variables in the Public and Private Contexts
sidebar_position: 1
sidebar_position: 2
---

# Global Variables
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: History Reference
sidebar_position: 2
sidebar_position: 3
---

<!-- Note: This will soon be moved into an Aztec.nr reference category under Aztec.nr smart contracts -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Portals",
"position": 3,
"position": 4,
"collapsible": true,
"collapsed": true
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"position": 0,
"position": 1,
"collapsible": true,
"collapsed": true,
"label": "Storage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ contract Uniswap {
struct Storage {
portal_address: SharedImmutable<EthAddress>,
}
// docs:end:uniswap_setup


#[aztec(public)]
#[aztec(initializer)]
fn constructor(portal_address: EthAddress) {
storage.portal_address.initialize(portal_address);
}

// docs:end:uniswap_setup

// docs:start:swap_public
#[aztec(public)]
fn swap_public(
Expand Down

0 comments on commit a4d1df6

Please sign in to comment.