-
Notifications
You must be signed in to change notification settings - Fork 265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: AMM #10153
Merged
feat: AMM #10153
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
81d7607
draft: amm
nventuro e2851e4
Fix warnings
nventuro c80f926
Small readability edits
nventuro 1edf613
Merge branch 'master' into nv/amm
nventuro 0fde9ba
Migrate to publicimmutable
nventuro 71a1266
Multiple tweaks
nventuro 32f72e5
Simplify test fns
nventuro 5567f2f
Fix deps
nventuro f584795
Remove txe tests
nventuro 118ea3a
Merge branch 'master' into nv/amm
nventuro 59e7b57
Fix formatting
nventuro d31edae
Type globals
nventuro d174448
Link issues
nventuro 9af1fdc
Add CI job
nventuro e381962
Fix flamegraph to account for the AMM name
nventuro 9d5f0db
Revert "Fix flamegraph to account for the AMM name"
nventuro c962c7c
Undo 'addr' struct renaming, keep param
nventuro 4bda20c
Merge branch 'master' into nv/amm
nventuro bb8c2b1
Merge branch 'master' into nv/amm
nventuro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
noir-projects/noir-contracts/contracts/amm_contract/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "amm_contract" | ||
authors = [""] | ||
compiler_version = ">=0.25.0" | ||
type = "contract" | ||
|
||
[dependencies] | ||
aztec = { path = "../../../aztec-nr/aztec" } | ||
token = { path = "../token_contract" } |
29 changes: 29 additions & 0 deletions
29
noir-projects/noir-contracts/contracts/amm_contract/src/config.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use dep::aztec::protocol_types::{address::AztecAddress, traits::{Deserialize, Serialize}}; | ||
|
||
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 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<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<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]), | ||
} | ||
} | ||
} |
96 changes: 96 additions & 0 deletions
96
noir-projects/noir-contracts/contracts/amm_contract/src/lib.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/// Given an input amount of an asset and pair balances, returns the maximum output amount of the other asset. | ||
pub fn get_amount_out(amount_in: U128, balance_in: U128, balance_out: U128) -> U128 { | ||
assert(amount_in > U128::zero(), "INSUFFICIENT_INPUT_AMOUNT"); | ||
assert((balance_in > U128::zero()) & (balance_out > U128::zero()), "INSUFFICIENT_LIQUIDITY"); | ||
|
||
// The expression below is: | ||
// (amount_in * 997 * balance_out) / (balance_in * 10000 + amount_in * 997) | ||
// which is equivalent to: | ||
// balance_out * ((amount_in * 0.997) / (balance_in + amount_in * 0.997)) | ||
// resulting in an implicit 0.3% fee on the amount in, as the fee tokens are not taken into consideration. | ||
|
||
let amount_in_with_fee = amount_in * U128::from_integer(997); | ||
let numerator = amount_in_with_fee * balance_out; | ||
let denominator = balance_in * U128::from_integer(1000) + amount_in_with_fee; | ||
numerator / denominator | ||
} | ||
|
||
/// Given an output amount of an asset and pair balances, returns a required input amount of the other asset. | ||
pub fn get_amount_in(amount_out: U128, balance_in: U128, balance_out: U128) -> U128 { | ||
assert(amount_out > U128::zero(), "INSUFFICIENT_OUTPUT_AMOUNT"); | ||
assert((balance_in > U128::zero()) & (balance_out > U128::zero()), "INSUFFICIENT_LIQUIDITY"); | ||
|
||
// The expression below is: | ||
// (balance_in * amount_out * 1000) / (balance_out - amout_out * 997) + 1 | ||
// which is equivalent to: | ||
// balance_in * (amount_out / (balance_in + amount_in)) * 1/0.997 + 1 | ||
// resulting in an implicit 0.3% fee on the amount in, as the fee tokens are not taken into consideration. The +1 | ||
// at the end ensures the rounding error favors the pool. | ||
|
||
let numerator = balance_in * amount_out * U128::from_integer(1000); | ||
let denominator = (balance_out - amount_out) * U128::from_integer(997); | ||
(numerator / denominator) + U128::from_integer(1) | ||
} | ||
|
||
/// Given the desired amounts and balances of token0 and token1 returns the optimal amount of token0 and token1 to be added to the pool. | ||
pub fn get_amounts_to_add( | ||
amount0_max: U128, | ||
amount1_max: U128, | ||
amount0_min: U128, | ||
amount1_min: U128, | ||
balance0: U128, | ||
balance1: U128, | ||
) -> (U128, U128) { | ||
// When adding tokens, both balances must grow by the same ratio, which means that their spot price is unchanged. | ||
// Since any swaps would affect these ratios, liquidity providers supply a range of minimum and maximum balances | ||
// they are willing to supply for each token (which translates to minimum and maximum relative prices of the | ||
// tokens, preventing loss of value outside of this range due to e.g. front-running). | ||
|
||
if (balance0 == U128::zero()) | (balance1 == U128::zero()) { | ||
// The token balances should only be zero when initializing the pool. In this scenario there is no prior ratio | ||
// to follow so we simply transfer the full maximum balance - it is up to the caller to make sure that the ratio | ||
// they've chosen results in a a reasonable spot price. | ||
(amount0_max, amount1_max) | ||
} else { | ||
// There is a huge number of amount combinations that respect the minimum and maximum for each token, but we'll | ||
// only consider the two scenarios in which one of the amounts is the maximum amount. | ||
|
||
// First we calculate the token1 amount that'd need to be supplied if we used the maximum amount for token0. | ||
let amount1_equivalent = get_equivalent_amount(amount0_max, balance0, balance1); | ||
if (amount1_equivalent <= amount1_max) { | ||
assert(amount1_equivalent >= amount1_min, "AMOUNT_1_BELOW_MINIMUM"); | ||
(amount0_max, amount1_equivalent) | ||
} else { | ||
// If the max amount for token0 results in a token1 amount larger than the maximum, then we try with the | ||
// maximum token1 amount, hoping that it'll result in a token0 amount larger than the minimum. | ||
let amount0_equivalent = get_equivalent_amount(amount1_max, balance1, balance0); | ||
// This should never happen, as it'd imply that the maximum is lower than the minimum. | ||
assert(amount0_equivalent <= amount0_max); | ||
|
||
assert(amount0_equivalent >= amount0_min, "AMOUNT_0_BELOW_MINIMUM"); | ||
(amount0_equivalent, amount1_max) | ||
} | ||
} | ||
} | ||
|
||
/// Returns the amount of tokens to return to a liquidity provider when they remove liquidity from the pool. | ||
pub fn get_amounts_on_remove( | ||
to_burn: U128, | ||
total_supply: U128, | ||
balance0: U128, | ||
balance1: U128, | ||
) -> (U128, U128) { | ||
// Since the liquidity token tracks ownership of the pool, the liquidity provider gets a proportional share of each | ||
// token. | ||
(to_burn * balance0 / total_supply, to_burn * balance1 / total_supply) | ||
} | ||
|
||
/// Given some amount of an asset and pair balances, 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, balance0: U128, balance1: U128) -> U128 { | ||
assert((balance0 > U128::zero()) & (balance1 > 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 * balance1) / balance0 | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to increase this limit since adding liquidity involves:
Implementing #10286 would reduce this to three calls.