Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ test-e2e-local: (test-e2e "local_node")
test-e2e-forked: (test-e2e "forked_node")

# Run End-to-end tests with custom filters
test-e2e *filters:
cargo nextest run -p e2e {{filters}} --test-threads 1 --failure-output final --run-ignored ignored-only
test-e2e filters="" *extra="":
cargo nextest run -p e2e '{{filters}}' --test-threads 1 --failure-output final --run-ignored ignored-only {{extra}}

test-driver:
RUST_MIN_STACK=3145728 cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only
Expand Down
4 changes: 4 additions & 0 deletions crates/driver/src/boundary/liquidity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ impl Fetcher {
pairs: &HashSet<liquidity::TokenPair>,
block: infra::liquidity::AtBlock,
) -> Result<Vec<liquidity::Liquidity>> {
if pairs.is_empty() {
return Ok(vec![]);
}

let pairs = pairs
.iter()
.map(|pair| {
Expand Down
33 changes: 12 additions & 21 deletions crates/driver/src/domain/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ use {
},
chrono::Utc,
ethrpc::alloy::conversions::IntoLegacy,
std::{
collections::{HashMap, HashSet},
iter,
},
std::collections::{HashMap, HashSet},
};

/// A quote describing the expected outcome of an order.
Expand Down Expand Up @@ -91,13 +88,13 @@ impl Order {
liquidity: &infra::liquidity::Fetcher,
tokens: &infra::tokens::Fetcher,
) -> Result<Quote, Error> {
let liquidity = match solver.liquidity() {
solver::Liquidity::Fetch => {
let liquidity = match (solver.liquidity(), self.token_liquidity()) {
(solver::Liquidity::Fetch, pairs) if !pairs.is_empty() => {
liquidity
.fetch(&self.liquidity_pairs(), infra::liquidity::AtBlock::Recent)
.fetch(&pairs, infra::liquidity::AtBlock::Recent)
.await
}
solver::Liquidity::Skip => Default::default(),
_ => Default::default(),
};

let auction = self
Expand Down Expand Up @@ -226,29 +223,23 @@ impl Order {
}

/// Returns the token pairs to fetch liquidity for.
fn liquidity_pairs(&self) -> HashSet<liquidity::TokenPair> {
let pair = liquidity::TokenPair::try_new(self.tokens.sell(), self.tokens.buy())
.expect("sell != buy by construction");
iter::once(pair).collect()
fn token_liquidity(&self) -> HashSet<liquidity::TokenPair> {
liquidity::TokenPair::try_new(self.tokens.sell(), self.tokens.buy())
.ok()
.into_iter()
.collect()
}
}

/// The sell and buy tokens to quote for. This type maintains the invariant that
/// the sell and buy tokens are distinct.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Tokens {
sell: eth::TokenAddress,
buy: eth::TokenAddress,
}

impl Tokens {
/// Creates a new instance of [`Tokens`], verifying that the input buy and
/// sell tokens are distinct.
Comment on lines -245 to -246
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to get some feedback from solvers how they would expect this to work. Specifically to ask them about using the regular quoting logic vs. building something into the orderbook which only needs the native price of the tokens and a simulation to get the gas amount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, I will ask them.

pub fn try_new(sell: eth::TokenAddress, buy: eth::TokenAddress) -> Result<Self, SameTokens> {
if sell == buy {
return Err(SameTokens);
}
Ok(Self { sell, buy })
pub fn new(sell: eth::TokenAddress, buy: eth::TokenAddress) -> Self {
Self { sell, buy }
}

pub fn sell(&self) -> eth::TokenAddress {
Expand Down
9 changes: 4 additions & 5 deletions crates/driver/src/infra/api/routes/quote/dto/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ use {
};

impl Order {
pub fn into_domain(self) -> Result<quote::Order, Error> {
Ok(quote::Order {
tokens: quote::Tokens::try_new(self.sell_token.into(), self.buy_token.into())
.map_err(|quote::SameTokens| Error::SameTokens)?,
pub fn into_domain(self) -> quote::Order {
quote::Order {
tokens: quote::Tokens::new(self.sell_token.into(), self.buy_token.into()),
amount: self.amount.into(),
side: match self.kind {
Kind::Sell => competition::order::Side::Sell,
Kind::Buy => competition::order::Side::Buy,
},
deadline: self.deadline,
})
}
}
}

Expand Down
4 changes: 1 addition & 3 deletions crates/driver/src/infra/api/routes/quote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ async fn route(
order: axum::extract::Query<dto::Order>,
) -> Result<axum::Json<dto::Quote>, (hyper::StatusCode, axum::Json<Error>)> {
let handle_request = async {
let order = order.0.into_domain().inspect_err(|err| {
observe::invalid_dto(err, "order");
})?;
let order = order.0.into_domain();
observe::quoting(&order);
let quote = order
.quote(
Expand Down
54 changes: 53 additions & 1 deletion crates/e2e/tests/e2e/place_order_with_quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ use {

#[tokio::test]
#[ignore]
async fn local_node_test() {
async fn local_node_place_order_with_quote_basic() {
run_test(place_order_with_quote).await;
}

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

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

Expand Down Expand Up @@ -114,3 +120,49 @@ async fn place_order_with_quote(web3: Web3) {
assert_eq!(quote_response.verified, order_quote.verified);
assert_eq!(quote_metadata.unwrap().0, order_quote.metadata);
}

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

let [solver] = onchain.make_solvers(to_wei(10).into_alloy()).await;
let [trader] = onchain.make_accounts(to_wei(10).into_alloy()).await;
let [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
.await;

token.mint(trader.address(), eth(10)).await;

token
.approve(onchain.contracts().allowance.into_alloy(), eth(10))
.from(trader.address())
.send_and_watch()
.await
.unwrap();

tracing::info!("Starting services.");
let services = Services::new(&onchain).await;
services.start_protocol(solver.clone()).await;

// Disable auto-mine so we don't accidentally mine a settlement
web3.api::<TestNodeApi<_>>()
.set_automine_enabled(false)
.await
.expect("Must be able to disable automine");

tracing::info!("Quoting");
let quote_sell_amount = to_wei(1);
let quote_request = OrderQuoteRequest {
from: trader.address(),
sell_token: *token.address(),
buy_token: *token.address(),
side: OrderQuoteSide::Sell {
sell_amount: SellAmount::BeforeFee {
value: NonZeroU256::try_from(quote_sell_amount).unwrap(),
},
},
..Default::default()
};
assert!(
matches!(services.submit_quote(&quote_request).await, Err((reqwest::StatusCode::BAD_REQUEST, response)) if response.contains("SameBuyAndSellToken"))
);
}
Loading