diff --git a/crates/driver/src/tests/cases/protocol_fees.rs b/crates/driver/src/tests/cases/protocol_fees.rs index 15f3fef1c8..4df8aaa6ea 100644 --- a/crates/driver/src/tests/cases/protocol_fees.rs +++ b/crates/driver/src/tests/cases/protocol_fees.rs @@ -10,16 +10,19 @@ use crate::{ ab_solution, ExpectedOrderAmounts, FeePolicy, + PriceImprovementQuote, Test, }, }, }; -struct TestCase { +const DEFAULT_SURPLUS_FACTOR: u64 = 2; + +struct CommonTestCase { order_side: order::Side, fee_policy: FeePolicy, order_sell_amount: eth::U256, - solver_fee: Option, + network_fee: Option, quote_sell_amount: eth::U256, quote_buy_amount: eth::U256, executed: eth::U256, @@ -27,7 +30,20 @@ struct TestCase { executed_buy_amount: eth::U256, } -async fn protocol_fee_test_case(test_case: TestCase) { +struct PriceImprovementTestCase { + order_side: order::Side, + policy_factor: f64, + policy_max_volume_factor: f64, + quote: PriceImprovementQuote, + order_sell_amount: eth::U256, + order_buy_amount: eth::U256, + network_fee: Option, + executed: eth::U256, + executed_sell_amount: eth::U256, + executed_buy_amount: eth::U256, +} + +async fn common_fee_test_case(test_case: CommonTestCase) { let test_name = format!( "Protocol Fee: {:?} {:?}", test_case.order_side, test_case.fee_policy @@ -41,10 +57,11 @@ async fn protocol_fee_test_case(test_case: TestCase) { buy: test_case.executed_buy_amount, }; let order = ab_order() + .surplus(DEFAULT_SURPLUS_FACTOR.into()) .kind(order::Kind::Limit) .sell_amount(test_case.order_sell_amount) .side(test_case.order_side) - .solver_fee(test_case.solver_fee) + .solver_fee(test_case.network_fee) .fee_policy(test_case.fee_policy) .executed(test_case.executed) .expected_amounts(expected_amounts); @@ -59,6 +76,41 @@ async fn protocol_fee_test_case(test_case: TestCase) { test.solve().await.ok().orders(&[order]); } +async fn price_improvement_fee_test_case(test_case: PriceImprovementTestCase) { + let test_name = format!("Protocol Fee: {:?} PriceImprovement", test_case.order_side); + let liquidity_quote = ab_liquidity_quote() + .sell_amount(test_case.order_sell_amount) + .buy_amount(test_case.order_buy_amount); + let pool = ab_adjusted_pool(liquidity_quote); + let expected_amounts = ExpectedOrderAmounts { + sell: test_case.executed_sell_amount, + buy: test_case.executed_buy_amount, + }; + let fee_policy = FeePolicy::PriceImprovement { + factor: test_case.policy_factor, + max_volume_factor: test_case.policy_max_volume_factor, + quote: test_case.quote, + }; + let order = ab_order() + .no_surplus() + .kind(order::Kind::Limit) + .sell_amount(test_case.order_sell_amount) + .side(test_case.order_side) + .solver_fee(test_case.network_fee) + .fee_policy(fee_policy) + .executed(test_case.executed) + .expected_amounts(expected_amounts); + let test: Test = tests::setup() + .name(test_name) + .pool(pool) + .order(order.clone()) + .solution(ab_solution()) + .done() + .await; + + test.solve().await.ok().orders(&[order]); +} + #[tokio::test] #[ignore] async fn surplus_protocol_fee_buy_order_not_capped() { @@ -67,19 +119,21 @@ async fn surplus_protocol_fee_buy_order_not_capped() { // high enough so we don't get capped by volume fee max_volume_factor: 1.0, }; - let test_case = TestCase { + let order_buy_amount = 40.ether().into_wei(); + let order_sell_amount = 50.ether().into_wei(); + let test_case = CommonTestCase { order_side: order::Side::Buy, fee_policy, - order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), - quote_sell_amount: 50.ether().into_wei(), - quote_buy_amount: 40.ether().into_wei(), - executed: 40.ether().into_wei(), - executed_sell_amount: 100.ether().into_wei(), - executed_buy_amount: 40.ether().into_wei(), + order_sell_amount, + network_fee: Some(10.ether().into_wei()), + quote_sell_amount: order_sell_amount, + quote_buy_amount: order_buy_amount, + executed: order_buy_amount, + executed_sell_amount: 75.ether().into_wei(), + executed_buy_amount: order_buy_amount, }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; } #[tokio::test] @@ -90,19 +144,21 @@ async fn surplus_protocol_fee_sell_order_not_capped() { // high enough so we don't get capped by volume fee max_volume_factor: 1.0, }; - let test_case = TestCase { + let order_sell_amount = 50.ether().into_wei(); + let order_buy_amount = 40.ether().into_wei(); + let test_case = CommonTestCase { order_side: order::Side::Sell, fee_policy, - order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), - quote_sell_amount: 50.ether().into_wei(), - quote_buy_amount: 40.ether().into_wei(), - executed: 40.ether().into_wei(), - executed_sell_amount: 50.ether().into_wei(), - executed_buy_amount: "20.000000002".ether().into_wei(), + order_sell_amount, + network_fee: Some(10.ether().into_wei()), + quote_sell_amount: order_sell_amount, + quote_buy_amount: order_buy_amount, + executed: order_buy_amount, + executed_sell_amount: order_sell_amount, + executed_buy_amount: 30.ether().into_wei(), }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; } #[tokio::test] @@ -113,11 +169,11 @@ async fn surplus_protocol_fee_buy_order_capped() { // low enough so we get capped by volume fee max_volume_factor: 0.1, }; - let test_case = TestCase { + let test_case = CommonTestCase { order_side: order::Side::Buy, fee_policy, order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), + network_fee: Some(10.ether().into_wei()), quote_sell_amount: 50.ether().into_wei(), quote_buy_amount: 40.ether().into_wei(), executed: 40.ether().into_wei(), @@ -125,7 +181,7 @@ async fn surplus_protocol_fee_buy_order_capped() { executed_buy_amount: 40.ether().into_wei(), }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; } #[tokio::test] @@ -136,11 +192,11 @@ async fn surplus_protocol_fee_sell_order_capped() { // low enough so we get capped by volume fee max_volume_factor: 0.1, }; - let test_case = TestCase { + let test_case = CommonTestCase { order_side: order::Side::Sell, fee_policy, order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), + network_fee: Some(10.ether().into_wei()), quote_sell_amount: 50.ether().into_wei(), quote_buy_amount: 40.ether().into_wei(), executed: 40.ether().into_wei(), @@ -148,18 +204,18 @@ async fn surplus_protocol_fee_sell_order_capped() { executed_buy_amount: 36.ether().into_wei(), }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; } #[tokio::test] #[ignore] async fn volume_protocol_fee_buy_order() { let fee_policy = FeePolicy::Volume { factor: 0.5 }; - let test_case = TestCase { + let test_case = CommonTestCase { order_side: order::Side::Buy, fee_policy, order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), + network_fee: Some(10.ether().into_wei()), quote_sell_amount: 50.ether().into_wei(), quote_buy_amount: 40.ether().into_wei(), executed: 40.ether().into_wei(), @@ -167,18 +223,18 @@ async fn volume_protocol_fee_buy_order() { executed_buy_amount: 40.ether().into_wei(), }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; } #[tokio::test] #[ignore] async fn volume_protocol_fee_sell_order() { let fee_policy = FeePolicy::Volume { factor: 0.5 }; - let test_case = TestCase { + let test_case = CommonTestCase { order_side: order::Side::Sell, fee_policy, order_sell_amount: 50.ether().into_wei(), - solver_fee: Some(10.ether().into_wei()), + network_fee: Some(10.ether().into_wei()), quote_sell_amount: 50.ether().into_wei(), quote_buy_amount: 40.ether().into_wei(), executed: 40.ether().into_wei(), @@ -186,5 +242,123 @@ async fn volume_protocol_fee_sell_order() { executed_buy_amount: 20.ether().into_wei(), }; - protocol_fee_test_case(test_case).await; + common_fee_test_case(test_case).await; +} + +// Price Improvement policy fee tests. +// Out of market order could be defined as: +// (order.sell + order.fee) * quote.buy < (quote.sell + quote.fee) * order.buy +// In the following tests Limit orders are used only, where order fee is 0. The +// amount values are adjusted to respect the definition. + +#[tokio::test] +#[ignore] +async fn price_improvement_fee_buy_out_of_market_order() { + let order_sell_amount = 50.ether().into_wei(); + let order_buy_amount = 40.ether().into_wei(); + let quote_network_fee = 20.ether().into_wei(); + let quote = PriceImprovementQuote { + sell_amount: order_sell_amount, + buy_amount: order_buy_amount, + network_fee: quote_network_fee, + }; + let test_case = PriceImprovementTestCase { + order_side: order::Side::Buy, + policy_factor: 0.5, + policy_max_volume_factor: 1.0, + quote, + order_sell_amount, + order_buy_amount, + network_fee: Some(1.ether().into_wei()), + executed: order_buy_amount, + // order sell amount + quote network fee * factor + executed_sell_amount: 60.ether().into_wei(), + executed_buy_amount: order_buy_amount, + }; + + price_improvement_fee_test_case(test_case).await; +} + +#[tokio::test] +#[ignore] +async fn price_improvement_fee_sell_out_of_market_order() { + let order_sell_amount = 50.ether().into_wei(); + let order_buy_amount = 40.ether().into_wei(); + let quote = PriceImprovementQuote { + sell_amount: order_sell_amount, + buy_amount: order_buy_amount, + network_fee: 20.ether().into_wei(), + }; + let network_fee = 10.ether().into_wei(); + let test_case = PriceImprovementTestCase { + order_side: order::Side::Sell, + policy_factor: 0.5, + policy_max_volume_factor: 1.0, + quote, + order_sell_amount, + order_buy_amount, + network_fee: Some(network_fee), + executed: order_sell_amount - network_fee, + executed_sell_amount: order_sell_amount, + // todo: how to prove the value? + executed_buy_amount: "34.285714285714285714".ether().into_wei(), + }; + + price_improvement_fee_test_case(test_case).await; +} + +#[tokio::test] +#[ignore] +async fn price_improvement_fee_buy_in_market_order() { + let order_sell_amount = 100.ether().into_wei(); + let order_buy_amount = 40.ether().into_wei(); + let quote = PriceImprovementQuote { + sell_amount: 50.ether().into_wei(), + buy_amount: 40.ether().into_wei(), + network_fee: 20.ether().into_wei(), + }; + let network_fee = 10.ether().into_wei(); + let test_case = PriceImprovementTestCase { + order_side: order::Side::Buy, + policy_factor: 0.5, + policy_max_volume_factor: 1.0, + quote, + order_sell_amount, + order_buy_amount, + network_fee: Some(network_fee), + executed: order_buy_amount, + // no price improvement since quote provides better conditions + executed_sell_amount: order_sell_amount, + executed_buy_amount: order_buy_amount, + }; + + price_improvement_fee_test_case(test_case).await; +} + +#[tokio::test] +#[ignore] +async fn price_improvement_fee_sell_in_market_order() { + let order_sell_amount: eth::U256 = 50.ether().into_wei(); + let order_buy_amount: eth::U256 = 10.ether().into_wei(); + let quote = PriceImprovementQuote { + sell_amount: 50.ether().into_wei(), + buy_amount: 40.ether().into_wei(), + network_fee: 20.ether().into_wei(), + }; + let network_fee = 10.ether().into_wei(); + let test_case = PriceImprovementTestCase { + order_side: order::Side::Sell, + policy_factor: 0.5, + policy_max_volume_factor: 1.0, + quote, + order_sell_amount, + order_buy_amount, + network_fee: Some(network_fee), + executed: order_sell_amount - network_fee, + // no price improvement since quote provides better conditions + executed_sell_amount: order_sell_amount, + executed_buy_amount: order_buy_amount, + }; + + price_improvement_fee_test_case(test_case).await; } diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index 74f259808b..fb38e1e223 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -6,7 +6,7 @@ use { eth::{self, ContractAddress}, }, infra::time, - tests::{self, boundary, cases::EtherExt}, + tests::{self, boundary, cases::EtherExt, setup::FeePolicy}, }, ethcontract::{dyns::DynWeb3, transport::DynTransport, Web3}, futures::Future, @@ -97,14 +97,34 @@ impl QuotedOrder { pub fn buy_amount(&self) -> eth::U256 { match self.order.side { order::Side::Buy => self.buy, - order::Side::Sell => self.buy / self.order.surplus_factor, + order::Side::Sell => { + let fee = match &self.order.fee_policy { + FeePolicy::PriceImprovement { + factor: _, + max_volume_factor: _, + quote, + } => quote.network_fee, + _ => eth::U256::zero(), + }; + (self.buy * (self.sell - fee) / self.sell) / self.order.surplus_factor + } } } /// The sell amount with the surplus factor. pub fn sell_amount(&self) -> eth::U256 { match self.order.side { - order::Side::Buy => self.sell * self.order.surplus_factor, + order::Side::Buy => { + let fee = match &self.order.fee_policy { + FeePolicy::PriceImprovement { + factor: _, + max_volume_factor: _, + quote, + } => quote.network_fee, + _ => eth::U256::zero(), + }; + (self.sell + fee) * self.order.surplus_factor + } order::Side::Sell => self.sell, } } diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index c1e517f095..6cdfa3fe15 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -73,6 +73,15 @@ pub enum Score { RiskAdjusted { success_probability: f64 }, } +#[serde_as] +#[derive(Debug, Clone, PartialEq, serde::Serialize)] +#[serde(rename_all = "camelCase", tag = "kind")] +pub struct PriceImprovementQuote { + pub buy_amount: eth::U256, + pub sell_amount: eth::U256, + pub network_fee: eth::U256, +} + #[serde_as] #[derive(Debug, Clone, PartialEq, serde::Serialize)] #[serde(rename_all = "camelCase", tag = "kind")] @@ -80,6 +89,12 @@ pub enum FeePolicy { #[serde(rename_all = "camelCase")] Surplus { factor: f64, max_volume_factor: f64 }, #[serde(rename_all = "camelCase")] + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: PriceImprovementQuote, + }, + #[serde(rename_all = "camelCase")] Volume { factor: f64 }, } @@ -95,6 +110,21 @@ impl FeePolicy { "maxVolumeFactor": max_volume_factor } }), + FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => json!({ + "priceImprovement": { + "factor": factor, + "maxVolumeFactor": max_volume_factor, + "quote": { + "sellAmount": quote.sell_amount, + "buyAmount": quote.buy_amount, + "fee": quote.network_fee, + } + } + }), FeePolicy::Volume { factor } => json!({ "volume": { "factor": factor @@ -207,6 +237,13 @@ impl Order { } } + pub fn surplus(self, surplus_factor: eth::U256) -> Self { + Self { + surplus_factor, + ..self + } + } + /// Mark this order as internalizable. pub fn internalize(self) -> Self { Self {