Skip to content

Commit

Permalink
Merge branch 'main' into fees-breakdown
Browse files Browse the repository at this point in the history
  • Loading branch information
sunce86 authored Aug 13, 2024
2 parents a1f9c84 + 0412ffa commit 875f1cd
Show file tree
Hide file tree
Showing 18 changed files with 955 additions and 211 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ ANVIL_IP_ADDR=0.0.0.0 anvil \

### Profiling

The most important binaries support [tokio-console](https://github.com/tokio-rs/console) to allow you a could look inside the tokio runtime.
All binaries are compiled with support for [tokio-console](https://github.com/tokio-rs/console) by default to allow you to look inside the tokio runtime.
However, this feature is not enabled at runtime by default because it comes with a pretty significant memory overhead. To enable it you just have to set the environment variable `TOKIO_CONSOLE=true` and run the binary you want to instrument.

Simply enable the feature by passing `--enable-tokio-console true` when running a binary and then in another shell, run

```
You can install and run `tokio-console` with:
```bash
cargo install --locked tokio-console
tokio-console
```
Expand Down
17 changes: 14 additions & 3 deletions crates/autopilot/src/domain/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use {
std::{collections::HashSet, str::FromStr},
};

#[derive(Debug)]
enum OrderClass {
Market,
Limit,
Expand Down Expand Up @@ -81,7 +82,7 @@ impl ProtocolFees {
pub fn apply(
&self,
order: boundary::Order,
quote: &domain::Quote,
quote: Option<domain::Quote>,
surplus_capturing_jit_order_owners: &[eth::Address],
) -> domain::Order {
let partner_fee = order
Expand Down Expand Up @@ -114,16 +115,26 @@ impl ProtocolFees {
buy: order.data.buy_amount,
fee: order.data.fee_amount,
};

// In case there is no quote, we assume 0 buy amount so that the order ends up
// being considered out of market price.
let quote = quote.unwrap_or(domain::Quote {
order_uid: order.metadata.uid.into(),
sell_amount: order.data.sell_amount.into(),
buy_amount: U256::zero().into(),
fee: order.data.fee_amount.into(),
});

let quote_ = boundary::Amounts {
sell: quote.sell_amount.into(),
buy: quote.buy_amount.into(),
fee: quote.fee.into(),
};

if self.enable_protocol_fees {
self.apply_multiple_policies(order, quote, order_, quote_, partner_fee)
self.apply_multiple_policies(order, &quote, order_, quote_, partner_fee)
} else {
self.apply_single_policy(order, quote, order_, quote_, partner_fee)
self.apply_single_policy(order, &quote, order_, quote_, partner_fee)
}
}

Expand Down
32 changes: 19 additions & 13 deletions crates/autopilot/src/solvable_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,17 @@ impl SolvableOrdersCache {
let surplus_capturing_jit_order_owners = cow_amms
.iter()
.filter(|cow_amm| {
cow_amm
.traded_tokens()
.iter()
.all(|token| prices.contains_key(token))
cow_amm.traded_tokens().iter().all(|token| {
let price_exist = prices.contains_key(token);
if !price_exist {
tracing::debug!(
cow_amm = ?cow_amm.address(),
?token,
"omitted from auction due to missing prices"
);
}
price_exist
})
})
.map(|cow_amm| cow_amm.address())
.cloned()
Expand All @@ -267,17 +274,16 @@ impl SolvableOrdersCache {
latest_settlement_block: db_solvable_orders.latest_settlement_block,
orders: orders
.into_iter()
.filter_map(|order| {
if let Some(quote) = db_solvable_orders.quotes.get(&order.metadata.uid.into()) {
Some(self.protocol_fees.apply(order, quote, &surplus_capturing_jit_order_owners))
} else {
tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing");
None
}
.map(|order| {
let quote = db_solvable_orders
.quotes
.get(&order.metadata.uid.into())
.cloned();
self.protocol_fees
.apply(order, quote, &surplus_capturing_jit_order_owners)
})
.collect(),
prices:
prices
prices: prices
.into_iter()
.map(|(key, value)| {
Price::new(value.into()).map(|price| (eth::TokenAddress(key), price))
Expand Down
2 changes: 1 addition & 1 deletion crates/e2e/src/setup/colocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl LiquidityProvider {
[[liquidity.uniswap-v2]]
router = "{:?}"
pool-code = "{:?}"
missing-pool-cache-time = "1h"
missing-pool-cache-time = "0s"
"#,
contracts.uniswap_v2_router.address(),
contracts.default_pool_code()
Expand Down
75 changes: 75 additions & 0 deletions crates/e2e/src/setup/fee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
pub struct ProtocolFeesConfig(pub Vec<ProtocolFee>);

#[derive(Clone)]
pub struct ProtocolFee {
pub policy: FeePolicyKind,
pub policy_order_class: FeePolicyOrderClass,
}

#[derive(Clone)]
pub enum FeePolicyOrderClass {
Market,
Limit,
Any,
}

impl std::fmt::Display for FeePolicyOrderClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FeePolicyOrderClass::Market => write!(f, "market"),
FeePolicyOrderClass::Limit => write!(f, "limit"),
FeePolicyOrderClass::Any => write!(f, "any"),
}
}
}

#[derive(Clone)]
pub enum FeePolicyKind {
/// How much of the order's surplus should be taken as a protocol fee.
Surplus { factor: f64, max_volume_factor: f64 },
/// How much of the order's volume should be taken as a protocol fee.
Volume { factor: f64 },
/// How much of the order's price improvement should be taken as a protocol
/// fee where price improvement is a difference between the executed price
/// and the best quote.
PriceImprovement { factor: f64, max_volume_factor: f64 },
}

impl std::fmt::Display for ProtocolFee {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let order_class_str = &self.policy_order_class.to_string();
match &self.policy {
FeePolicyKind::Surplus {
factor,
max_volume_factor,
} => write!(
f,
"surplus:{}:{}:{}",
factor, max_volume_factor, order_class_str
),
FeePolicyKind::Volume { factor } => {
write!(f, "volume:{}:{}", factor, order_class_str)
}
FeePolicyKind::PriceImprovement {
factor,
max_volume_factor,
} => write!(
f,
"priceImprovement:{}:{}:{}",
factor, max_volume_factor, order_class_str
),
}
}
}

impl std::fmt::Display for ProtocolFeesConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let fees_str = self
.0
.iter()
.map(|fee| fee.to_string())
.collect::<Vec<_>>()
.join(",");
write!(f, "--fee-policies={}", fees_str)
}
}
1 change: 1 addition & 0 deletions crates/e2e/src/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod colocation;
mod deploy;
#[macro_use]
pub mod onchain_components;
pub mod fee;
mod services;
mod solver;

Expand Down
20 changes: 15 additions & 5 deletions crates/e2e/src/setup/onchain_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,12 @@ impl OnchainComponents {
solvers
}

async fn deploy_tokens<const N: usize>(&self, minter: Account) -> [MintableToken; N] {
/// Deploy `N` tokens without any onchain liquidity
pub async fn deploy_tokens<const N: usize>(&self, minter: &Account) -> [MintableToken; N] {
let mut res = Vec::with_capacity(N);
for _ in 0..N {
let contract = ERC20Mintable::builder(&self.web3)
.from(minter.clone())
.deploy()
.await
.expect("MintableERC20 deployment failed");
Expand All @@ -333,9 +335,19 @@ impl OnchainComponents {
.expect("getting accounts failed")[0],
None,
);
let tokens = self.deploy_tokens::<N>(minter).await;
let tokens = self.deploy_tokens::<N>(&minter).await;
self.seed_weth_uni_v2_pools(tokens.iter(), token_amount, weth_amount)
.await;
tokens
}

for MintableToken { contract, minter } in &tokens {
pub async fn seed_weth_uni_v2_pools(
&self,
tokens: impl IntoIterator<Item = &MintableToken>,
token_amount: U256,
weth_amount: U256,
) {
for MintableToken { contract, minter } in tokens {
tx!(minter, contract.mint(minter.address(), token_amount));
tx_value!(minter, weth_amount, self.contracts.weth.deposit());

Expand Down Expand Up @@ -369,8 +381,6 @@ impl OnchainComponents {
)
);
}

tokens
}

/// Mints `amount` tokens to its `token`-WETH Uniswap V2 pool.
Expand Down
3 changes: 2 additions & 1 deletion crates/e2e/tests/e2e/cow_amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,8 @@ async fn cow_amm_driver_support(web3: Web3) {
addr!("301076c36e034948a747bb61bab9cd03f62672e3"),
addr!("d7cb8cc1b56356bb7b78d02e785ead28e2158660"),
addr!("9941fd7db2003308e7ee17b04400012278f12ac6"),
addr!("beef5afe88ef73337e5070ab2855d37dbf5493a4"),
// no native prices for the tokens traded by this AMM (COW token price)
// addr!("beef5afe88ef73337e5070ab2855d37dbf5493a4"),
addr!("c6b13d5e662fa0458f03995bcb824a1934aa895f"),
];

Expand Down
116 changes: 116 additions & 0 deletions crates/e2e/tests/e2e/limit_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use {
driver::domain::eth::NonZeroU256,
e2e::{nodes::forked_node::ForkedNodeApi, setup::*, tx},
ethcontract::{prelude::U256, H160},
fee::{FeePolicyOrderClass, ProtocolFee, ProtocolFeesConfig},
model::{
order::{OrderClass, OrderCreation, OrderKind},
quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount},
Expand Down Expand Up @@ -37,6 +38,12 @@ async fn local_node_limit_does_not_apply_to_in_market_orders_test() {
run_test(limit_does_not_apply_to_in_market_orders_test).await;
}

#[tokio::test]
#[ignore]
async fn local_node_no_liquidity_limit_order() {
run_test(no_liquidity_limit_order).await;
}

/// The block number from which we will fetch state for the forked tests.
const FORK_BLOCK_MAINNET: u64 = 18477910;
/// USDC whale address as per [FORK_BLOCK_MAINNET].
Expand Down Expand Up @@ -695,3 +702,112 @@ async fn forked_gnosis_single_limit_order_test(web3: Web3) {
assert!(sell_token_balance_before > sell_token_balance_after);
assert!(buy_token_balance_after >= buy_token_balance_before + to_wei(500));
}

async fn no_liquidity_limit_order(web3: Web3) {
let mut onchain = OnchainComponents::deploy(web3.clone()).await;

let [solver] = onchain.make_solvers(to_wei(10_000)).await;
let [trader_a] = onchain.make_accounts(to_wei(1)).await;
let [token_a] = onchain.deploy_tokens(solver.account()).await;

// Fund trader accounts
token_a.mint(trader_a.address(), to_wei(10)).await;

// Approve GPv2 for trading
tx!(
trader_a.account(),
token_a.approve(onchain.contracts().allowance, to_wei(10))
);

// Setup services
let protocol_fees_config = ProtocolFeesConfig(vec![
ProtocolFee {
policy: fee::FeePolicyKind::Surplus {
factor: 0.5,
max_volume_factor: 0.01,
},
policy_order_class: FeePolicyOrderClass::Limit,
},
ProtocolFee {
policy: fee::FeePolicyKind::PriceImprovement {
factor: 0.5,
max_volume_factor: 0.01,
},
policy_order_class: FeePolicyOrderClass::Market,
},
])
.to_string();

let services = Services::new(onchain.contracts()).await;
services
.start_protocol_with_args(
ExtraServiceArgs {
autopilot: vec![
protocol_fees_config,
"--enable-multiple-fees=true".to_string(),
],
..Default::default()
},
solver,
)
.await;

// Place order
let order = OrderCreation {
sell_token: token_a.address(),
sell_amount: to_wei(10),
buy_token: onchain.contracts().weth.address(),
buy_amount: to_wei(1),
valid_to: model::time::now_in_epoch_seconds() + 300,
kind: OrderKind::Sell,
..Default::default()
}
.sign(
EcdsaSigningScheme::Eip712,
&onchain.contracts().domain_separator,
SecretKeyRef::from(&SecretKey::from_slice(trader_a.private_key()).unwrap()),
);
let order_id = services.create_order(&order).await.unwrap();
let limit_order = services.get_order(&order_id).await.unwrap();
assert_eq!(limit_order.metadata.class, OrderClass::Limit);

// Create liquidity
onchain
.seed_weth_uni_v2_pools([&token_a].iter().copied(), to_wei(1000), to_wei(1000))
.await;

// Drive solution
tracing::info!("Waiting for trade.");
let balance_before = onchain
.contracts()
.weth
.balance_of(trader_a.address())
.call()
.await
.unwrap();
wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 1 })
.await
.unwrap();

wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 0 })
.await
.unwrap();

let balance_after = onchain
.contracts()
.weth
.balance_of(trader_a.address())
.call()
.await
.unwrap();
assert!(balance_after.checked_sub(balance_before).unwrap() >= to_wei(5));

let trades = services.get_trades(&order_id).await.unwrap();
assert_eq!(
trades.first().unwrap().fee_policies,
vec![model::fee_policy::FeePolicy::Surplus {
factor: 0.5,
max_volume_factor: 0.01
}],
);
}
Loading

0 comments on commit 875f1cd

Please sign in to comment.