diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 2abd64ae19..149e6366c3 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -561,6 +561,7 @@ pub async fn run(args: Arguments) { signature_validator.clone(), args.auction_update_interval, args.ethflow_contract, + native_token.address(), args.limit_order_price_factor .try_into() .expect("limit order price factor can't be converted to BigDecimal"), diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 9ee4c56936..7589b82603 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -18,12 +18,15 @@ use { shared::{ account_balances::{BalanceFetching, Query}, bad_token::BadTokenDetecting, - price_estimation::native_price_cache::CachingNativePriceEstimator, + price_estimation::{ + native::NativePriceEstimating, + native_price_cache::CachingNativePriceEstimator, + }, remaining_amounts, signature_validator::{SignatureCheck, SignatureValidating}, }, std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{btree_map::Entry, BTreeMap, HashMap, HashSet}, sync::{Arc, Mutex, Weak}, time::Duration, }, @@ -74,6 +77,7 @@ pub struct SolvableOrdersCache { signature_validator: Arc, metrics: &'static Metrics, ethflow_contract_address: Option, + weth: H160, limit_order_price_factor: BigDecimal, // Will be obsolete when the new autopilot run loop takes over the competition. store_in_db: bool, @@ -108,6 +112,7 @@ impl SolvableOrdersCache { signature_validator: Arc, update_interval: Duration, ethflow_contract_address: Option, + weth: H160, limit_order_price_factor: BigDecimal, store_in_db: bool, fee_objective_scaling_factor: f64, @@ -131,6 +136,7 @@ impl SolvableOrdersCache { signature_validator, metrics: Metrics::instance(observe::metrics::get_storage_registry()).unwrap(), ethflow_contract_address, + weth, limit_order_price_factor, store_in_db, fee_objective_scaling_factor: BigRational::from_f64(fee_objective_scaling_factor) @@ -202,11 +208,24 @@ impl SolvableOrdersCache { order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); // create auction - let (orders, prices) = get_orders_with_native_prices( + let (orders, mut prices) = get_orders_with_native_prices( orders.clone(), &self.native_price_estimator, self.metrics, ); + // Add WETH price if it's not already there to support ETH wrap when required. + if let Entry::Vacant(entry) = prices.entry(self.weth) { + let weth_price = self + .native_price_estimator + .estimate_native_price(self.weth) + .await + .expect("weth price fetching can never fail"); + let weth_price = to_normalized_price(weth_price) + .expect("weth price can never be outside of U256 range"); + + entry.insert(weth_price); + } + let removed = counter.checkpoint("missing_price", &orders); order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); diff --git a/crates/e2e/tests/e2e/colocation_eth_safe.rs b/crates/e2e/tests/e2e/colocation_eth_safe.rs new file mode 100644 index 0000000000..0bd5f42132 --- /dev/null +++ b/crates/e2e/tests/e2e/colocation_eth_safe.rs @@ -0,0 +1,87 @@ +use { + e2e::{ + setup::{safe::Safe, *}, + tx, + }, + ethcontract::U256, + model::{ + order::{OrderCreation, OrderKind, BUY_ETH_ADDRESS}, + signature::EcdsaSigningScheme, + }, + secp256k1::SecretKey, + shared::ethrpc::Web3, + web3::signing::SecretKeyRef, +}; + +#[tokio::test] +#[ignore] +async fn local_node_test() { + run_test(test).await; +} + +async fn test(web3: Web3) { + tracing::info!("Setting up chain state."); + let mut onchain = OnchainComponents::deploy(web3.clone()).await; + + let [solver] = onchain.make_solvers(to_wei(10)).await; + let [trader] = onchain.make_accounts(to_wei(10)).await; + let safe = Safe::deploy(trader.clone(), &web3).await; + let [token] = onchain + .deploy_tokens_with_weth_uni_v2_pools(to_wei(1000), to_wei(1000)) + .await; + + token.mint(trader.address(), to_wei(4)).await; + tx!( + trader.account(), + token.approve(onchain.contracts().allowance, to_wei(4)) + ); + + tracing::info!("Starting services."); + let solver_endpoint = colocation::start_solver(onchain.contracts().weth.address()).await; + colocation::start_driver(onchain.contracts(), &solver_endpoint, &solver); + + let services = Services::new(onchain.contracts()).await; + services.start_autopilot(vec![ + "--enable-colocation=true".to_string(), + "--drivers=test_solver|http://localhost:11088/test_solver".to_string(), + ]); + services + .start_api(vec!["--enable-eth-smart-contract-payments=true".to_string()]) + .await; + + tracing::info!("Placing order"); + let balance = onchain + .contracts() + .weth + .balance_of(safe.address()) + .call() + .await + .unwrap(); + assert_eq!(balance, 0.into()); + let order = OrderCreation { + sell_token: token.address(), + sell_amount: to_wei(4), + buy_token: BUY_ETH_ADDRESS, + buy_amount: to_wei(3), + valid_to: model::time::now_in_epoch_seconds() + 300, + partially_fillable: true, + kind: OrderKind::Sell, + receiver: Some(safe.address()), + ..Default::default() + } + .sign( + EcdsaSigningScheme::Eip712, + &onchain.contracts().domain_separator, + SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()), + ); + services.create_order(&order).await.unwrap(); + + tracing::info!("Waiting for trade."); + let trade_happened = || async { + let safe_balance = web3.eth().balance(safe.address(), None).await.unwrap(); + // the balance is slightly less because of the fee + (3_899_000_000_000_000_000_u128..4_000_000_000_000_000_000_u128) + .contains(&safe_balance.as_u128()) + }; + wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); +} diff --git a/crates/e2e/tests/e2e/main.rs b/crates/e2e/tests/e2e/main.rs index 5a991b1fcd..b42cb7f926 100644 --- a/crates/e2e/tests/e2e/main.rs +++ b/crates/e2e/tests/e2e/main.rs @@ -7,6 +7,7 @@ mod app_data; mod app_data_signer; mod colocation_buffers; +mod colocation_eth_safe; mod colocation_ethflow; mod colocation_hooks; mod colocation_partial_fill;