Skip to content

Commit

Permalink
Driver tests refactoring for protocol fees (#2404)
Browse files Browse the repository at this point in the history
# Description
Refactors driver tests to be able to use them in UT fashion by providing
custom quotes and explicit expected results without replicating original
protocol fees logic in the tests.

The easiest approach is to continue using Uniswap on-chain interactions,
but it turned out that it has some limitations where the pool's executed
amounts ratio should be the same as the pool's reserves ratio. The
proposed approach addresses this limitation by adjusting the pool's
reserves by providing precalculated executed amounts using a reversed
function. The precision issue with calculations without a floating point
adds complexity to the implemented functions.

# Changes
- [ ] Adds ability to provide custom pool's executed amounts
- [ ] Results are now compared with precalculated values
- [ ] Minor refactoring to operate with WEI values for better
readability
- [ ] Pool's reserve token `a` or `b` adjustment functions could be used
interchangeably

## Related Issues
Fixed #2267
  • Loading branch information
squadgazzz authored Feb 23, 2024
1 parent 4d571b9 commit 8f8ef93
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 64 deletions.
5 changes: 3 additions & 2 deletions crates/driver/src/tests/cases/buy_eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ use crate::tests::{
#[tokio::test]
#[ignore]
async fn test() {
let order = eth_order();
let test = setup()
.pool(weth_pool())
.order(eth_order())
.order(order.clone())
.solution(eth_solution())
.done()
.await;

test.solve().await.ok().orders(&[eth_order().name]);
test.solve().await.ok().orders(&[order]);
test.settle().await.ok().await.eth_order_executed().await;
}
18 changes: 9 additions & 9 deletions crates/driver/src/tests/cases/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,35 +29,35 @@ async fn rejects_unwarranted_solver_fee() {
#[ignore]
async fn solver_fee() {
for side in [order::Side::Buy, order::Side::Sell] {
let order = ab_order()
.kind(order::Kind::Limit)
.side(side)
.solver_fee(Some(500.into()));
let test = tests::setup()
.name(format!("Solver Fee: {side:?}"))
.pool(ab_pool())
.order(
ab_order()
.kind(order::Kind::Limit)
.side(side)
.solver_fee(Some(500.into())),
)
.order(order.clone())
.solution(ab_solution())
.done()
.await;

test.solve().await.ok().orders(&[ab_order().name]);
test.solve().await.ok().orders(&[order]);
}
}

#[tokio::test]
#[ignore]
async fn user_fee() {
for side in [order::Side::Buy, order::Side::Sell] {
let order = ab_order().side(side).user_fee(1000.into());
let test = tests::setup()
.name(format!("User Fee: {side:?}"))
.pool(ab_pool())
.order(ab_order().side(side).user_fee(1000.into()))
.order(order.clone())
.solution(ab_solution())
.done()
.await;

test.solve().await.ok().orders(&[ab_order().name]);
test.solve().await.ok().orders(&[order]);
}
}
18 changes: 9 additions & 9 deletions crates/driver/src/tests/cases/merge_settlements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ use crate::tests::{
#[tokio::test]
#[ignore]
async fn possible() {
let ab_order = ab_order();
let cd_order = cd_order();
let test = setup()
.pool(cd_pool())
.pool(ab_pool())
.order(ab_order())
.order(cd_order())
.order(ab_order.clone())
.order(cd_order.clone())
.solution(cd_solution())
.solution(ab_solution())
.done()
.await;

test.solve()
.await
.ok()
.orders(&[ab_order().name, cd_order().name]);
test.solve().await.ok().orders(&[ab_order, cd_order]);
test.reveal().await.ok().calldata();
test.settle()
.await
Expand All @@ -38,10 +37,11 @@ async fn possible() {
#[tokio::test]
#[ignore]
async fn impossible() {
let order = ab_order();
let test = setup()
.pool(ab_pool())
.order(ab_order())
.order(ab_order().rename("reduced order").reduce_amount(1000000000000000u128.into()))
.order(order.clone())
.order(order.clone().rename("reduced order").reduce_amount(1000000000000000u128.into()))
// These two solutions result in different clearing prices (due to different surplus),
// so they can't be merged.
.solution(ab_solution())
Expand All @@ -54,7 +54,7 @@ async fn impossible() {

// Only the first A-B order gets settled.

test.solve().await.ok().orders(&[ab_order().name]);
test.solve().await.ok().orders(&[order]);
test.reveal().await.ok().calldata();
test.settle().await.ok().await.ab_order_executed().await;
}
1 change: 1 addition & 0 deletions crates/driver/src/tests/cases/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod multiple_drivers;
pub mod multiple_solutions;
pub mod negative_scores;
pub mod order_prioritization;
pub mod protocol_fees;
pub mod quote;
pub mod score_competition;
pub mod settle;
Expand Down
18 changes: 6 additions & 12 deletions crates/driver/src/tests/cases/multiple_solutions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ use crate::tests::{
#[tokio::test]
#[ignore]
async fn valid() {
let order = ab_order();
let test = setup()
.pool(ab_pool())
.order(ab_order())
.order(order.clone())
.solution(ab_solution())
.solution(ab_solution().reduce_score())
.done()
.await;

test.solve()
.await
.ok()
.default_score()
.orders(&[ab_order().name]);
test.solve().await.ok().default_score().orders(&[order]);
test.reveal().await.ok().calldata();
}

Expand All @@ -29,18 +26,15 @@ async fn valid() {
#[tokio::test]
#[ignore]
async fn invalid() {
let order = ab_order();
let test = setup()
.pool(ab_pool())
.order(ab_order())
.order(order.clone())
.solution(ab_solution().reduce_score())
.solution(ab_solution().invalid())
.done()
.await;

test.solve()
.await
.ok()
.default_score()
.orders(&[ab_order().name]);
test.solve().await.ok().default_score().orders(&[order]);
test.reveal().await.ok().calldata();
}
11 changes: 4 additions & 7 deletions crates/driver/src/tests/cases/negative_scores.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ async fn no_valid_solutions() {
#[tokio::test]
#[ignore]
async fn one_valid_solution() {
let order = ab_order();
let test = setup()
.pool(ab_pool())
.order(ab_order())
.order(ab_order().rename("no surplus").no_surplus())
.order(order.clone())
.order(order.clone().rename("no surplus").no_surplus())
.solution(ab_solution())
// This solution has no surplus, and hence a negative score, so it gets skipped.
.solution(Solution {
Expand All @@ -36,10 +37,6 @@ async fn one_valid_solution() {
})
.done()
.await;
test.solve()
.await
.ok()
.default_score()
.orders(&[ab_order().name]);
test.solve().await.ok().default_score().orders(&[order]);
test.reveal().await.ok().calldata();
}
189 changes: 189 additions & 0 deletions crates/driver/src/tests/cases/protocol_fees.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use crate::{
domain::{competition::order, eth},
tests::{
self,
setup::{
ab_adjusted_pool,
ab_liquidity_quote,
ab_order,
ab_solution,
ExpectedOrderAmounts,
FeePolicy,
Test,
},
},
};

struct TestCase {
order_side: order::Side,
fee_policy: FeePolicy,
order_sell_amount: eth::U256,
solver_fee: Option<eth::U256>,
quote_sell_amount: eth::U256,
quote_buy_amount: eth::U256,
executed: eth::U256,
executed_sell_amount: eth::U256,
executed_buy_amount: eth::U256,
}

async fn protocol_fee_test_case(test_case: TestCase) {
let test_name = format!(
"Protocol Fee: {:?} {:?}",
test_case.order_side, test_case.fee_policy
);
let quote = ab_liquidity_quote()
.sell_amount(test_case.quote_sell_amount)
.buy_amount(test_case.quote_buy_amount);
let pool = ab_adjusted_pool(quote);
let expected_amounts = ExpectedOrderAmounts {
sell: test_case.executed_sell_amount,
buy: test_case.executed_buy_amount,
};
let order = ab_order()
.kind(order::Kind::Limit)
.sell_amount(test_case.order_sell_amount)
.side(test_case.order_side)
.solver_fee(test_case.solver_fee)
.fee_policy(test_case.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() {
let fee_policy = FeePolicy::Surplus {
factor: 0.5,
// high enough so we don't get capped by volume fee
max_volume_factor: 1.0,
};
let test_case = TestCase {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 100000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
};

protocol_fee_test_case(test_case).await;
}

#[tokio::test]
#[ignore]
async fn surplus_protocol_fee_sell_order_not_capped() {
let fee_policy = FeePolicy::Surplus {
factor: 0.5,
// high enough so we don't get capped by volume fee
max_volume_factor: 1.0,
};
let test_case = TestCase {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 20000000002000000000u128.into(),
};

protocol_fee_test_case(test_case).await;
}

#[tokio::test]
#[ignore]
async fn surplus_protocol_fee_buy_order_capped() {
let fee_policy = FeePolicy::Surplus {
factor: 0.5,
// low enough so we get capped by volume fee
max_volume_factor: 0.1,
};
let test_case = TestCase {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 55000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
};

protocol_fee_test_case(test_case).await;
}

#[tokio::test]
#[ignore]
async fn surplus_protocol_fee_sell_order_capped() {
let fee_policy = FeePolicy::Surplus {
factor: 0.5,
// low enough so we get capped by volume fee
max_volume_factor: 0.1,
};
let test_case = TestCase {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 35000000000000000000u128.into(),
};

protocol_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 {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 75000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
};

protocol_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 {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 15000000000000000000u128.into(),
};

protocol_fee_test_case(test_case).await;
}
Loading

0 comments on commit 8f8ef93

Please sign in to comment.