Skip to content

Commit

Permalink
refactor: replacing unshield naming with transfer_to_public
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan committed Oct 30, 2024
1 parent e7c8bd4 commit f3d867a
Show file tree
Hide file tree
Showing 25 changed files with 216 additions and 196 deletions.
3 changes: 0 additions & 3 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,6 @@
"unexcluded",
"unfinalised",
"unprefixed",
"unshield",
"unshielding",
"unshields",
"unzipit",
"updateable",
"upperfirst",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ contract FPC {
#[private]
fn fee_entrypoint_private(amount: Field, asset: AztecAddress, secret_hash: Field, nonce: Field) {
assert(asset == storage.other_asset.read_private());
Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context);
Token::at(asset).transfer_to_public(context.msg_sender(), context.this_address(), amount, nonce).call(&mut context);
FPC::at(context.this_address()).pay_fee_with_shielded_rebate(amount, asset, secret_hash).enqueue(&mut context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ In the snippet below, this is done as a separate contract call, but can also be

We have cases where we need a non-wallet contract to approve an action to be executed by another contract. One of the cases could be when making more complex defi where funds are passed along. When doing so, we need the intermediate contracts to support approving of actions on their behalf.

This is fairly straight forward to do using the `auth` library which includes logic for updating values in the public auth registry. Namely, you can prepare the `message_hash` using `compute_authwit_message_hash_from_call` and then simply feed it into the `set_authorized` function (both are in `auth` library) to update the value.
This is fairly straight forward to do using the `auth` library which includes logic for updating values in the public auth registry. Namely, you can prepare the `message_hash` using `compute_authwit_message_hash_from_call` and then simply feed it into the `set_authorized` function (both are in `auth` library) to update the value.

When another contract later is consuming the authwit using `assert_current_call_valid_authwit_public` it will be calling the registry, and spend that authwit.

Expand All @@ -197,7 +197,7 @@ sequenceDiagram
activate AC;
AC->>CC: Swap 1000 token A to B
activate CC;
CC->>T: unshield 1000 tokens from Alice Account to CCS
CC->>T: transfer_to_public 1000 tokens from Alice Account to CCS
activate T;
T->>AC: Have you approved this??
AC-->>A: Please give me an AuthWit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 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](../../../../../tutorials/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.
2. `transfer_to_public` 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](../../../../../tutorials/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
Expand Up @@ -37,7 +37,7 @@ Consider a user, Alice, who does not have FPA but wishes to interact with the ne

Suppose there is a Fee Payment Contract (FPC) that has been deployed by another user to the network. Alice can structure her transaction as follows:

0. Before the transaction, Alice creates a private authwit in her wallet, allowing the FPC to unshield a specified amount of BananaCoin from Alice's private balance to the FPC's public balance.
0. Before the transaction, Alice creates a private authwit in her wallet, allowing the FPC to transfer to public a specified amount of BananaCoin from Alice's private balance to the FPC's public balance.
1. Private setup:
- Alice calls a private function on the FPC which is exposed for public fee payment in BananaCoin.
- The FPC checks that the amount of teardown gas Alice has allocated is sufficient to cover the gas associated with the teardown function it will use to provide a refund to Alice.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ These are functions that have transparent logic, will execute in a publicly veri
These are functions that have private logic and will be executed on user devices to maintain privacy. The only data that is submitted to the network is a proof of correct execution, new data commitments and nullifiers, so users will not reveal which contract they are interacting with or which function they are executing. The only information that will be revealed publicly is that someone executed a private transaction on Aztec.

- `redeem_shield` enables accounts to claim tokens that have been made private via `mint_private` or `shield` by providing the secret
- `unshield` enables an account to send tokens from their private balance to any other account's public balance
- `transfer_to_public` enables an account to send tokens from their private balance to any other account's public balance
- `transfer` enables an account to send tokens from their private balance to another account's private balance
- `transferFrom` enables an account to send tokens from another account's private balance to another account's private balance
- `transfer_from` enables an account to send tokens from another account's private balance to another account's private balance
- `cancel_authwit` enables an account to cancel an authorization to spend tokens
- `burn` enables tokens to be burned privately

### Internal functions

Internal functions are functions that can only be called by the contract itself. These can be used when the contract needs to call one of it's public functions from one of it's private functions.

- `_increase_public_balance` increases the public balance of an account when `unshield` is called
- `_increase_public_balance` increases the public balance of an account when `transfer_to_public` is called
- `_reduce_total_supply` reduces the total supply of tokens when a token is privately burned

To clarify, let's review some details of the Aztec transaction lifecycle, particularly how a transaction "moves through" these contexts.
Expand Down Expand Up @@ -133,7 +133,6 @@ We are importing:

- `CompressedString` to hold the token symbol
- Types from `aztec::prelude`
- `compute_secret_hash` that will help with the shielding and unshielding, allowing someone to claim a token from private to public
- Types for storing note types

### Types files
Expand Down Expand Up @@ -206,7 +205,7 @@ First, storage is initialized. Then the function checks that the `msg_sender` is

This public function allows an account approved in the public `minters` mapping to create new private tokens that can be claimed by anyone that has the pre-image to the `secret_hash`.

First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `PrivateSet` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function.
First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here (GitHub link)](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/noir-contracts/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `PrivateSet` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function.

#include_code mint_private /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

Expand Down Expand Up @@ -251,7 +250,7 @@ Private functions are declared with the `#[private]` macro above the function na
fn redeem_shield(
```

As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`unshield`](#unshield) function).
As described in the [execution contexts section above](#execution-contexts), private function logic and transaction information is hidden from the world and is executed on user devices. Private functions update private state, but can pass data to the public execution context (e.g. see the [`transfer_to_public`](#transfer_to_public) function).

Storage is referenced as `storage.variable`.

Expand All @@ -265,15 +264,15 @@ The function returns `1` to indicate successful execution.

#include_code redeem_shield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `unshield`
#### `transfer_to_public`

This private function enables un-shielding of private `UintNote`s stored in `balances` to any Aztec account's `public_balance`.

After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After the authorization check, the sender's private balance is decreased using the `decrement` helper function for the `value_note` library. Then it stages a public function call on this contract ([`_increase_public_balance`](#_increase_public_balance)) to be executed in the [public execution phase](#execution-contexts) of transaction execution. `_increase_public_balance` is marked as an `internal` function, so can only be called by this token contract.

The function returns `1` to indicate successful execution.

#include_code unshield /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust
#include_code transfer_to_public /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

#### `transfer`

Expand Down Expand Up @@ -303,7 +302,7 @@ Internal functions are functions that can only be called by this contract. The f

#### `_increase_public_balance`

This function is called from [`unshield`](#unshield). The account's private balance is decremented in `shield` and the public balance is increased in this function.
This function is called from [`transfer_to_public`](#transfer_to_public). The account's private balance is decremented in `shield` and the public balance is increased in this function.

#include_code increase_public_balance /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/tutorials/examples/uniswap/l2_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 (codealong tutorial)](../../codealong/contract_tutorials/advanced/token_bridge/index.md).
This page goes over the code in the L2 contract for Uniswap, which works alongside a [token bridge (codealong tutorial)](../../codealong/contract_tutorials/advanced/token_bridge/index.md).

## Main.nr

Expand Down Expand Up @@ -47,8 +47,8 @@ This uses a util function `compute_swap_private_content_hash()` - find that [her
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.**
- In the public flow, the user calls `transfer_public()`. Here instead, the user calls `transfer_to_public()`. 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 transfer to public their private tokens into Uniswap L2 contract. Transferring to public 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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ contract FPC {
nonce: Field,
) {
assert(asset == storage.other_asset.read_private());
Token::at(asset).unshield(context.msg_sender(), context.this_address(), amount, nonce).call(
&mut context,
);
Token::at(asset)
.transfer_to_public(context.msg_sender(), context.this_address(), amount, nonce)
.call(&mut context);
context.set_as_fee_payer();
// Would like to get back to
// FPC::at(context.this_address()).pay_refund_with_shielded_rebate(amount, asset, secret_hash).set_public_teardown_function(&mut context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ contract Lending {
let on_behalf_of =
compute_identifier(secret, on_behalf_of, context.msg_sender().to_field());
let _res = Token::at(collateral_asset)
.unshield(from, context.this_address(), amount, nonce)
.transfer_to_public(from, context.this_address(), amount, nonce)
.call(&mut context);
// docs:start:enqueue_public
Lending::at(context.this_address())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ contract Token {
));
}
// docs:end:redeem_shield
// docs:start:unshield
// docs:start:transfer_to_public
#[private]
fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
fn transfer_to_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) {
if (!from.eq(context.msg_sender())) {
assert_current_call_valid_authwit(&mut context, from);
} else {
Expand All @@ -331,7 +331,7 @@ contract Token {
);
Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context);
}
// docs:end:unshield
// docs:end:transfer_to_public
// docs:start:transfer
#[private]
fn transfer(to: AztecAddress, amount: Field) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ mod utils;
mod transfer_public;
mod transfer_private;
mod transfer_to_private;
mod transfer_to_public;
mod refunds;
mod unshielding;
mod minting;
mod reading_constants;
mod shielding;
Loading

0 comments on commit f3d867a

Please sign in to comment.