Skip to content

Commit

Permalink
Small readability edits
Browse files Browse the repository at this point in the history
  • Loading branch information
nventuro committed Nov 25, 2024
1 parent e2851e4 commit ae5065a
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use dep::aztec::protocol_types::{address::AztecAddress, traits::{Deserialize, Serialize}};

global STATE_LENGTH: u32 = 3;
global CONFIG_LENGTH: u32 = 3;

/// We store the tokens of the pool in a struct such that to load it from SharedImmutable asserts only a single
/// merkle proof.
/// (Once we actually do the optimization. WIP in https://github.com/AztecProtocol/aztec-packages/pull/8022).
pub struct State {
pub struct Config {
pub token0: AztecAddress,
pub token1: AztecAddress,
pub liquidity_token: AztecAddress,
}

// Note: I could not get #[derive(Serialize)] to work so I had to implement it manually.
impl Serialize<STATE_LENGTH> for State {
fn serialize(self: Self) -> [Field; STATE_LENGTH] {
impl Serialize<CONFIG_LENGTH> for Config {
fn serialize(self: Self) -> [Field; CONFIG_LENGTH] {
[self.token0.to_field(), self.token1.to_field(), self.liquidity_token.to_field()]
}
}

impl Deserialize<STATE_LENGTH> for State {
fn deserialize(fields: [Field; STATE_LENGTH]) -> Self {
State {
impl Deserialize<CONFIG_LENGTH> for Config {
fn deserialize(fields: [Field; CONFIG_LENGTH]) -> Self {
Self {
token0: AztecAddress::from_field(fields[0]),
token1: AztecAddress::from_field(fields[1]),
liquidity_token: AztecAddress::from_field(fields[2]),
Expand Down
27 changes: 15 additions & 12 deletions noir-projects/noir-contracts/contracts/amm_contract/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
/// Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset.
/// copy of https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L36
fn get_quote(amountA: U128, reserveA: U128, reserveB: U128) -> U128 {
assert(amountA > U128::zero(), "INSUFFICIENT_AMOUNT");
assert((reserveA > U128::zero()) & (reserveB > U128::zero()), "INSUFFICIENT_LIQUIDITY");
(amountA * reserveB) / reserveA
}

/// Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset.
/// copy of https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L43
pub fn get_amount_out(amount_in: U128, reserve_in: U128, reserve_out: U128) -> U128 {
// Based on Uniswap v2: https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L43
assert(amount_in > U128::zero(), "INSUFFICIENT_INPUT_AMOUNT");
assert((reserve_in > U128::zero()) & (reserve_out > U128::zero()), "INSUFFICIENT_LIQUIDITY");
let amount_in_with_fee = amount_in * U128::from_integer(997);
Expand All @@ -18,8 +10,8 @@ pub fn get_amount_out(amount_in: U128, reserve_in: U128, reserve_out: U128) -> U
}

/// Given an output amount of an asset and pair reserves, returns a required input amount of the other asset.
/// copy of https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L53
pub fn get_amount_in(amount_out: U128, reserve_in: U128, reserve_out: U128) -> U128 {
// Based on Uniswap v2: https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L53
assert(amount_out > U128::zero(), "INSUFFICIENT_OUTPUT_AMOUNT");
assert((reserve_in > U128::zero()) & (reserve_out > U128::zero()), "INSUFFICIENT_LIQUIDITY");
let numerator = reserve_in * amount_out * U128::from_integer(1000);
Expand All @@ -40,7 +32,7 @@ pub fn get_amounts_to_add(
let mut amount1 = amount1_desired;
if ((reserve0 != U128::zero()) | (reserve1 != U128::zero())) {
// First calculate the optimal amount of token1 based on the desired amount of token0.
let amount1_optimal = get_quote(amount0_desired, reserve0, reserve1);
let amount1_optimal = get_equivalent_amount(amount0_desired, reserve0, reserve1);
if (amount1_optimal <= amount1_desired) {
// Revert if the optimal amount of token1 is less than the desired amount of token1.
assert(amount1_optimal >= amount1_min, "INSUFFICIENT_1_AMOUNT");
Expand All @@ -49,7 +41,7 @@ pub fn get_amounts_to_add(
} else {
// We got more amount of token1 than desired so we try repeating the process but this time by quoting
// based on token1.
let amount0_optimal = get_quote(amount1_desired, reserve1, reserve0);
let amount0_optimal = get_equivalent_amount(amount1_desired, reserve1, reserve0);
assert(amount0_optimal <= amount0_desired);
assert(amount0_optimal >= amount0_min, "INSUFFICIENT_0_AMOUNT");
amount0 = amount0_optimal;
Expand All @@ -59,3 +51,14 @@ pub fn get_amounts_to_add(

(amount0, amount1)
}

/// Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset. Tokens should be
/// added and removed from the Pool respecting this ratio.
fn get_equivalent_amount(amount0: U128, reserve0: U128, reserve1: U128) -> U128 {
// Based on Uniswap v2: https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/libraries/UniswapV2Library.sol#L36
assert((reserve0 > U128::zero()) & (reserve1 > U128::zero()), "INSUFFICIENT_LIQUIDITY");

// This is essentially the Rule of Three, since we're computing proportional ratios. Note we divide at the end to
// avoid introducing too much error due to truncation.
(amount0 * reserve1) / reserve0
}
95 changes: 49 additions & 46 deletions noir-projects/noir-contracts/contracts/amm_contract/src/main.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod lib;
mod state;
mod config;
mod test;

use dep::aztec::macros::aztec;
Expand All @@ -10,6 +10,10 @@ use dep::aztec::macros::aztec;
/// - Anyone can observe **what actions** were performed.
/// - All amounts involved are visible, but **who** performed the action remains private.
///
/// Unlike most Ethereum AMMs, the AMM contract is not itself the token that tracks participation of liquidity
/// providers, mostly due to Noir lacking inheritance as a feature. Instead, the AMM is expected to have mint and burn
/// permission over an external token contract.
///
/// **Note:**
/// This is purely a demonstration. The **Aztec team** does not consider this the optimal design for building a DEX.
///
Expand All @@ -31,12 +35,9 @@ use dep::aztec::macros::aztec;
/// `AMM.private_fn --> AMM.public_fn --> ExternalContract.fn --> AMM.public_fn`.
#[aztec]
contract AMM {
use crate::{lib::{get_amount_in, get_amount_out, get_amounts_to_add}, state::State};
use crate::{lib::{get_amount_in, get_amount_out, get_amounts_to_add}, config::Config};
use dep::aztec::{
macros::{
functions::{initializer, internal, private, public},
storage::storage,
},
macros::{functions::{initializer, internal, private, public}, storage::storage},
prelude::{AztecAddress, SharedImmutable},
};
use dep::token::Token;
Expand All @@ -46,23 +47,21 @@ contract AMM {
// The following is only needed in private but we use ShareImmutable here instead of PrivateImmutable because
// the value can be publicly known and SharedImmutable provides us with a better devex here because we don't
// have to bother with sharing the note between pixies of users.
state: SharedImmutable<State, Context>,
config: SharedImmutable<Config, Context>,
}

/// Amount of liquidity which gets locked token_contract0l when liquidity is provided for the first ttoken_contract0purpose
/// is to prevent the pool from ever emptying which could lead to undefined behavior.
/// Amount of liquidity which gets locked when liquidity is provided for the first time. Its purpose is to prevent
/// the pool from ever emptying which could lead to undefined behavior.
global MINIMUM_LIQUIDITY = U128::from_integer(1000);
// We set it to 9 times the minimum liquidity. That way the first LP gets 90% of the value of his deposit.
/// We set it to 9 times the minimum liquidity. That way the first LP gets 90% of the value of their deposit.
global INITIAL_LIQUIDITY = U128::from_integer(9000);

// Note: Since we don't have inheritance it seems the easiest to deploy the standard token and use it as
// a liquidity tracking contract. This contract would be an admin of the liquidity contract.
// TODO(#9480): Either deploy the liquidity contract in the constructor or verify it that it corresponds to what
// this contract expects.
// this contract expects (i.e. that the AMM has permission to mint and burn).
#[public]
#[initializer]
fn constructor(token0: AztecAddress, token1: AztecAddress, liquidity_token: AztecAddress) {
storage.state.initialize(State { token0, token1, liquidity_token });
storage.config.initialize(Config { token0, token1, liquidity_token });
}

/// Privately adds liquidity to the pool (identity of liquidity provider not revealed). `amount0_desired`
Expand All @@ -86,11 +85,11 @@ contract AMM {
"INSUFFICIENT_INPUT_AMOUNTS",
);

let state = storage.state.read_private();
let config = storage.config.read_private();

let token0 = Token::at(state.token0);
let token1 = Token::at(state.token1);
let liquidity_token = Token::at(state.liquidity_token);
let token0 = Token::at(config.token0);
let token1 = Token::at(config.token1);
let liquidity_token = Token::at(config.liquidity_token);

// We transfer the desired amounts of tokens to this contract.
token0
Expand All @@ -117,12 +116,13 @@ contract AMM {
let refund_token1_hiding_point_slot =
token1.prepare_private_balance_increase(context.msg_sender()).call(&mut context);
// We prepare a partial note for the liquidity tokens.
let liquidity_hiding_point_slot =
liquidity_token.prepare_private_balance_increase(context.msg_sender()).call(&mut context);
let liquidity_hiding_point_slot = liquidity_token
.prepare_private_balance_increase(context.msg_sender())
.call(&mut context);

AMM::at(context.this_address())
._add_liquidity(
state,
config,
refund_token0_hiding_point_slot,
refund_token1_hiding_point_slot,
liquidity_hiding_point_slot,
Expand All @@ -138,7 +138,7 @@ contract AMM {
#[internal]
fn _add_liquidity(
// We pass the state as an argument in order to not have to read it from public storage again.
state: State,
config: Config,
refund_token0_hiding_point_slot: Field,
refund_token1_hiding_point_slot: Field,
liquidity_hiding_point_slot: Field,
Expand All @@ -153,9 +153,9 @@ contract AMM {
let amount0_min = U128::from_integer(amount0_min);
let amount1_min = U128::from_integer(amount1_min);

let token0 = Token::at(state.token0);
let token1 = Token::at(state.token1);
let liquidity_token = Token::at(state.liquidity_token);
let token0 = Token::at(config.token0);
let token1 = Token::at(config.token1);
let liquidity_token = Token::at(config.liquidity_token);

let reserve0_with_amount0_desired = U128::from_integer(token0
.balance_of_public(context.this_address())
Expand Down Expand Up @@ -227,11 +227,11 @@ contract AMM {
/// TODO(#8271): Type the args as U128
#[private]
fn remove_liquidity(liquidity: Field, amount0_min: Field, amount1_min: Field, nonce: Field) {
let state = storage.state.read_private();
let config = storage.config.read_private();

let liquidity_token = Token::at(state.liquidity_token);
let token0 = Token::at(state.token0);
let token1 = Token::at(state.token1);
let liquidity_token = Token::at(config.liquidity_token);
let token0 = Token::at(config.token0);
let token1 = Token::at(config.token1);

// We transfer the liquidity tokens to this contract and prepare partial notes for the output tokens. We are
// forced to first transfer into the AMM because it is not possible to burn in private - the enqueued public
Expand All @@ -246,7 +246,7 @@ contract AMM {

AMM::at(context.this_address())
._remove_liquidity(
state,
config,
token0_hiding_point_slot,
token1_hiding_point_slot,
liquidity,
Expand All @@ -260,7 +260,7 @@ contract AMM {
#[internal]
fn _remove_liquidity(
// We pass the state as an argument in order to not have to read it from public storage again.
state: State,
config: Config,
token0_hiding_point_slot: Field,
token1_hiding_point_slot: Field,
liquidity: Field,
Expand All @@ -272,9 +272,9 @@ contract AMM {
let amount0_min = U128::from_integer(amount0_min);
let amount1_min = U128::from_integer(amount1_min);

let token0 = Token::at(state.token0);
let token1 = Token::at(state.token1);
let liquidity_token = Token::at(state.liquidity_token);
let token0 = Token::at(config.token0);
let token1 = Token::at(config.token1);
let liquidity_token = Token::at(config.liquidity_token);

// We get the reserves and the liquidity token total supply.
let reserve0 = U128::from_integer(token0.balance_of_public(context.this_address()).view(
Expand Down Expand Up @@ -318,12 +318,12 @@ contract AMM {
amount_out_min: Field,
nonce: Field,
) {
let state = storage.state.read_private();
let config = storage.config.read_private();

// We check the tokens are valid
assert(token_in != token_out);
assert((token_in == state.token0) | (token_in == state.token1));
assert((token_out == state.token0) | (token_out == state.token1));
assert((token_in == config.token0) | (token_in == config.token1));
assert((token_out == config.token0) | (token_out == config.token1));

let token_in_contract = Token::at(token_in);
let token_out_contract = Token::at(token_out);
Expand All @@ -332,8 +332,9 @@ contract AMM {
token_in_contract
.transfer_to_public(context.msg_sender(), context.this_address(), amount_in, nonce)
.call(&mut context);
let token_out_hiding_point_slot =
token_out_contract.prepare_private_balance_increase(context.msg_sender()).call(&mut context);
let token_out_hiding_point_slot = token_out_contract
.prepare_private_balance_increase(context.msg_sender())
.call(&mut context);

AMM::at(context.this_address())
._swap_exact_tokens_for_tokens(
Expand Down Expand Up @@ -395,12 +396,12 @@ contract AMM {
// TODO(#8271): Type the args as U128 and nuke these ugly casts
let amount_out = U128::from_integer(amount_out);

let state = storage.state.read_private();
let config = storage.config.read_private();

// We check the tokens are valid
assert(token_in != token_out);
assert((token_in == state.token0) | (token_in == state.token1));
assert((token_out == state.token0) | (token_out == state.token1));
assert((token_in == config.token0) | (token_in == config.token1));
assert((token_out == config.token0) | (token_out == config.token1));

let token_in_contract = Token::at(token_in);
let token_out_contract = Token::at(token_out);
Expand All @@ -410,10 +411,12 @@ contract AMM {
token_in_contract
.transfer_to_public(context.msg_sender(), context.this_address(), amount_in_max, nonce)
.call(&mut context);
let refund_token_in_hiding_point_slot =
token_in_contract.prepare_private_balance_increase(context.msg_sender()).call(&mut context);
let token_out_hiding_point_slot =
token_out_contract.prepare_private_balance_increase(context.msg_sender()).call(&mut context);
let refund_token_in_hiding_point_slot = token_in_contract
.prepare_private_balance_increase(context.msg_sender())
.call(&mut context);
let token_out_hiding_point_slot = token_out_contract
.prepare_private_balance_increase(context.msg_sender())
.call(&mut context);

AMM::at(context.this_address())
._swap_tokens_for_exact_tokens(
Expand Down

0 comments on commit ae5065a

Please sign in to comment.