From 0ae866e077ba1b9e470bd32d23bb55ade25f2c9a Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 24 Jan 2024 18:04:47 +0000 Subject: [PATCH] Baseline LimitOrder support --- Cargo.lock | 2 + crates/solvers/Cargo.toml | 2 + crates/solvers/src/boundary/baseline.rs | 15 +++ .../src/boundary/liquidity/limit_order.rs | 126 ++++++++++++++++++ crates/solvers/src/boundary/liquidity/mod.rs | 1 + 5 files changed, 146 insertions(+) create mode 100644 crates/solvers/src/boundary/liquidity/limit_order.rs diff --git a/Cargo.lock b/Cargo.lock index 662094db6f..7738eed0f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4212,11 +4212,13 @@ dependencies = [ "chrono", "clap", "contracts", + "ethcontract", "ethereum-types", "ethrpc", "futures", "glob", "hex", + "hex-literal", "humantime-serde", "hyper", "itertools 0.11.0", diff --git a/crates/solvers/Cargo.toml b/crates/solvers/Cargo.toml index a20d616c99..d134691993 100644 --- a/crates/solvers/Cargo.toml +++ b/crates/solvers/Cargo.toml @@ -53,3 +53,5 @@ tracing = { workspace = true } [dev-dependencies] glob = "0.3" tempfile = "3" +hex-literal = { workspace = true } +ethcontract = { workspace = true } diff --git a/crates/solvers/src/boundary/baseline.rs b/crates/solvers/src/boundary/baseline.rs index 32434a4c7a..d321b2f914 100644 --- a/crates/solvers/src/boundary/baseline.rs +++ b/crates/solvers/src/boundary/baseline.rs @@ -187,6 +187,17 @@ fn to_boundary_amms(liquidity: &[liquidity::Liquidity]) -> HashMap { + if let Some(token_pair) = + TokenPair::new(limit_order.maker.token.0, limit_order.taker.token.0) + { + amms.entry(token_pair).or_default().push(Amm { + id: liquidity.id.clone(), + token_pair, + pool: Pool::LimitOrder(limit_order.clone()), + }) + } + } // The baseline solver does not currently support other AMMs. _ => {} }; @@ -206,6 +217,7 @@ enum Pool { ConstantProduct(boundary::liquidity::constant_product::Pool), WeightedProduct(boundary::liquidity::weighted_product::Pool), Stable(boundary::liquidity::stable::Pool), + LimitOrder(liquidity::limit_order::LimitOrder), } impl BaselineSolvable for Amm { @@ -214,6 +226,7 @@ impl BaselineSolvable for Amm { Pool::ConstantProduct(pool) => pool.get_amount_out(out_token, input), Pool::WeightedProduct(pool) => pool.get_amount_out(out_token, input), Pool::Stable(pool) => pool.get_amount_out(out_token, input), + Pool::LimitOrder(limit_order) => limit_order.get_amount_out(out_token, input), } } @@ -222,6 +235,7 @@ impl BaselineSolvable for Amm { Pool::ConstantProduct(pool) => pool.get_amount_in(in_token, out), Pool::WeightedProduct(pool) => pool.get_amount_in(in_token, out), Pool::Stable(pool) => pool.get_amount_in(in_token, out), + Pool::LimitOrder(limit_order) => limit_order.get_amount_in(in_token, out), } } @@ -230,6 +244,7 @@ impl BaselineSolvable for Amm { Pool::ConstantProduct(pool) => pool.gas_cost(), Pool::WeightedProduct(pool) => pool.gas_cost(), Pool::Stable(pool) => pool.gas_cost(), + Pool::LimitOrder(limit_order) => limit_order.gas_cost(), } } } diff --git a/crates/solvers/src/boundary/liquidity/limit_order.rs b/crates/solvers/src/boundary/liquidity/limit_order.rs new file mode 100644 index 0000000000..e8a7ac6351 --- /dev/null +++ b/crates/solvers/src/boundary/liquidity/limit_order.rs @@ -0,0 +1,126 @@ +use { + crate::domain::liquidity::limit_order::{LimitOrder, TakerAmount}, + contracts::ethcontract::{H160, U256}, + shared::{baseline_solver::BaselineSolvable, sources::balancer_v2::swap::fixed_point::Bfp}, +}; + +impl BaselineSolvable for LimitOrder { + fn get_amount_out(&self, out_token: H160, (in_amount, in_token): (U256, H160)) -> Option { + if in_token == self.taker.token.0 && out_token == self.maker.token.0 { + calculate_amount_out(in_amount, self.maker.amount, self.taker.amount, &self.fee) + } else { + None + } + } + + fn get_amount_in(&self, in_token: H160, (out_amount, out_token): (U256, H160)) -> Option { + if out_token == self.maker.token.0 && in_token == self.taker.token.0 { + calculate_amount_in(out_amount, self.maker.amount, self.taker.amount, &self.fee) + } else { + None + } + } + + fn gas_cost(&self) -> usize { + 0 + } +} + +fn calculate_amount_out( + in_amount: U256, + maker_amount: U256, + taker_amount: U256, + fee: &TakerAmount, +) -> Option { + let fee_adjusted_amount = in_amount.checked_sub(fee.0)?; + let fee_adjusted_amount_bfp = Bfp::from_wei(fee_adjusted_amount); + let scaled_maker_amount = Bfp::from_wei(maker_amount).mul_down(Bfp::exp10(18)).ok()?; + let scaled_price_ratio = scaled_maker_amount + .div_down(Bfp::from_wei(taker_amount)) + .ok()?; + let scaled_out_amount_bfp = fee_adjusted_amount_bfp.mul_down(scaled_price_ratio).ok()?; + scaled_out_amount_bfp + .div_down(Bfp::exp10(18)) + .ok() + .map(|amount| amount.as_uint256()) +} + +fn calculate_amount_in( + out_amount: U256, + maker_amount: U256, + taker_amount: U256, + fee: &TakerAmount, +) -> Option { + let scaled_taker_amount = Bfp::from_wei(taker_amount).mul_down(Bfp::exp10(18)).ok()?; + let maker_bfp = Bfp::from_wei(maker_amount); + let scaled_price_ratio = scaled_taker_amount.div_down(maker_bfp).ok()?; + let required_amount_before_scaling = Bfp::from_wei(out_amount) + .mul_down(scaled_price_ratio) + .ok()?; + let required_amount = required_amount_before_scaling + .div_down(Bfp::exp10(18)) + .ok()? + .as_uint256(); + required_amount.checked_add(fee.0) +} + +#[cfg(test)] +mod tests { + use {super::*, crate::domain::eth, contracts::ethcontract::U256, shared::addr}; + + fn create_limit_order(maker_amount: u32, taker_amount: u32, fee_amount: u32) -> LimitOrder { + let maker = eth::Asset { + amount: U256::from(maker_amount), + token: eth::TokenAddress(addr!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")), + }; + let taker = eth::Asset { + amount: U256::from(taker_amount), + token: eth::TokenAddress(addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")), + }; + let fee = TakerAmount(U256::from(fee_amount)); + + LimitOrder { maker, taker, fee } + } + + #[test] + fn test_amount_out_in_round_trip() { + let maker_amount: u32 = 200; + let taker_amount: u32 = 100; + let fee_amount: u32 = 10; + let desired_in_amount: u32 = 50; + + let order = create_limit_order(maker_amount, taker_amount, fee_amount); + let out_token = order.maker.token.0; + let in_token = order.taker.token.0; + + let amount_out = order + .get_amount_out(out_token, (U256::from(desired_in_amount), in_token)) + .unwrap(); + let amount_in = order + .get_amount_in(in_token, (amount_out, out_token)) + .unwrap(); + + assert_eq!(amount_in, U256::from(desired_in_amount)); + } + + #[test] + fn test_amount_in_out_round_trip() { + let maker_amount: u32 = 100; + let taker_amount: u32 = 200; + let fee_amount: u32 = 10; + let desired_out_amount: u32 = 50; + + let order = create_limit_order(maker_amount, taker_amount, fee_amount); + let out_token = order.maker.token.0; + let in_token = order.taker.token.0; + + let amount_in = order + .get_amount_in(in_token, (U256::from(desired_out_amount), out_token)) + .unwrap(); + let amount_out = order + .get_amount_out(out_token, (amount_in, in_token)) + .unwrap(); + + assert_eq!(amount_out, U256::from(desired_out_amount)); + } +} diff --git a/crates/solvers/src/boundary/liquidity/mod.rs b/crates/solvers/src/boundary/liquidity/mod.rs index 1d9b6dc151..8f01c25545 100644 --- a/crates/solvers/src/boundary/liquidity/mod.rs +++ b/crates/solvers/src/boundary/liquidity/mod.rs @@ -1,3 +1,4 @@ pub mod constant_product; +mod limit_order; pub mod stable; pub mod weighted_product;