Skip to content
Closed
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
5 changes: 4 additions & 1 deletion crates/autopilot/src/solvable_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,10 @@ async fn filter_unsupported_tokens(
// async. So either this manual iteration or conversion to stream.
let mut index = 0;
'outer: while index < orders.len() {
for token in orders[index].data.token_pair().unwrap() {
for &token in [orders[index].data.buy_token, orders[index].data.sell_token]
.iter()
.dedup()
{
if !bad_token.detect(token).await?.is_good() {
orders.swap_remove(index);
continue 'outer;
Expand Down
26 changes: 14 additions & 12 deletions crates/driver/src/domain/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ impl Order {
liquidity: &infra::liquidity::Fetcher,
tokens: &infra::tokens::Fetcher,
) -> Result<Quote, Error> {
let pairs = self.liquidity_pairs();

let liquidity = match solver.liquidity() {
solver::Liquidity::Fetch => {
liquidity
.fetch(&self.liquidity_pairs(), infra::liquidity::AtBlock::Recent)
.await
if pairs.is_none() {
Default::default()
} else {
liquidity
.fetch(&pairs.unwrap(), infra::liquidity::AtBlock::Recent)
.await
}
}
solver::Liquidity::Skip => Default::default(),
};
Expand Down Expand Up @@ -204,10 +210,9 @@ impl Order {
}

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

Expand All @@ -222,11 +227,8 @@ pub struct Tokens {
impl Tokens {
/// Creates a new instance of [`Tokens`], verifying that the input buy and
/// sell tokens are distinct.
pub fn 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 @@ -9,17 +9,16 @@ use {
};

impl Order {
pub fn into_domain(self, timeouts: Timeouts) -> Result<quote::Order, Error> {
Ok(quote::Order {
tokens: quote::Tokens::new(self.sell_token.into(), self.buy_token.into())
.map_err(|quote::SameTokens| Error::SameTokens)?,
pub fn into_domain(self, timeouts: Timeouts) -> 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: time::Deadline::new(self.deadline, timeouts),
})
}
}
}

Expand Down
5 changes: 1 addition & 4 deletions crates/driver/src/infra/api/routes/quote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use {
api::{Error, State},
observe,
},
tap::TapFallible,
tracing::Instrument,
};

Expand All @@ -20,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(state.timeouts()).tap_err(|err| {
observe::invalid_dto(err, "order");
})?;
let order = order.0.into_domain(state.timeouts());
observe::quoting(&order);
let quote = order
.quote(
Expand Down
1 change: 0 additions & 1 deletion crates/orderbook/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,6 @@ components:
InsufficientValidTo,
ExcessiveValidTo,
InvalidNativeSellToken,
SameBuyAndSellToken,
UnsupportedToken,
InvalidAppData,
AppDataHashMismatch,
Expand Down
7 changes: 0 additions & 7 deletions crates/orderbook/src/api/post_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,6 @@ impl IntoWarpReply for PartialValidationErrorWrapper {
),
StatusCode::BAD_REQUEST,
),
PartialValidationError::SameBuyAndSellToken => with_status(
error(
"SameBuyAndSellToken",
"Buy token is the same as the sell token.",
),
StatusCode::BAD_REQUEST,
),
PartialValidationError::UnsupportedToken { token, reason } => with_status(
error(
"UnsupportedToken",
Expand Down
63 changes: 0 additions & 63 deletions crates/shared/src/order_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ pub enum PartialValidationError {
Forbidden,
ValidTo(OrderValidToError),
InvalidNativeSellToken,
SameBuyAndSellToken,
UnsupportedBuyTokenDestination(BuyTokenDestination),
UnsupportedSellTokenSource(SellTokenSource),
UnsupportedOrderType,
Expand Down Expand Up @@ -432,9 +431,6 @@ impl OrderValidating for OrderValidator {

self.validity_configuration.validate_period(&order)?;

if has_same_buy_and_sell_token(&order, &self.native_token) {
return Err(PartialValidationError::SameBuyAndSellToken);
}
if order.sell_token == BUY_ETH_ADDRESS {
return Err(PartialValidationError::InvalidNativeSellToken);
}
Expand Down Expand Up @@ -806,14 +802,6 @@ pub enum OrderValidToError {
Excessive,
}

/// Returns true if the orders have same buy and sell tokens.
///
/// This also checks for orders selling wrapped native token for native token.
fn has_same_buy_and_sell_token(order: &PreOrderData, native_token: &WETH9) -> bool {
order.sell_token == order.buy_token
|| (order.sell_token == native_token.address() && order.buy_token == BUY_ETH_ADDRESS)
}

/// Min balance user must have in sell token for order to be accepted.
///
/// None when addition overflows.
Expand Down Expand Up @@ -993,46 +981,6 @@ mod tests {
assert_eq!(minimum_balance(&order), Some(U256::from(2)));
}

#[test]
fn detects_orders_with_same_buy_and_sell_token() {
let native_token = dummy_contract!(WETH9, [0xef; 20]);
assert!(has_same_buy_and_sell_token(
&PreOrderData {
sell_token: H160([0x01; 20]),
buy_token: H160([0x01; 20]),
..Default::default()
},
&native_token,
));
assert!(has_same_buy_and_sell_token(
&PreOrderData {
sell_token: native_token.address(),
buy_token: BUY_ETH_ADDRESS,
..Default::default()
},
&native_token,
));

assert!(!has_same_buy_and_sell_token(
&PreOrderData {
sell_token: H160([0x01; 20]),
buy_token: H160([0x02; 20]),
..Default::default()
},
&native_token,
));
// Sell token set to 0xeee...eee has no special meaning, so it isn't
// considered buying and selling the same token.
assert!(!has_same_buy_and_sell_token(
&PreOrderData {
sell_token: BUY_ETH_ADDRESS,
buy_token: native_token.address(),
..Default::default()
},
&native_token,
));
}

#[tokio::test]
async fn pre_validate_err() {
let native_token = dummy_contract!(WETH9, [0xef; 20]);
Expand Down Expand Up @@ -1150,17 +1098,6 @@ mod tests {
OrderValidToError::Excessive,
))
));
assert!(matches!(
validator
.partial_validate(PreOrderData {
valid_to: legit_valid_to,
buy_token: H160::from_low_u64_be(2),
sell_token: H160::from_low_u64_be(2),
..Default::default()
})
.await,
Err(PartialValidationError::SameBuyAndSellToken)
));
assert!(matches!(
validator
.partial_validate(PreOrderData {
Expand Down
2 changes: 1 addition & 1 deletion crates/shared/src/price_estimation/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ fn pools_vec_to_map(pools: Vec<Pool>) -> Pools {
fn estimate_gas(path_len: usize) -> u64 {
let hops = match path_len.checked_sub(1) {
Some(len) => len,
None => return 0,
None => 0,
};
// Can be reduced to one erc20 transfer when #675 is fixed.
let per_hop = gas::ERC20_TRANSFER * 2 + 40_000;
Expand Down
20 changes: 10 additions & 10 deletions crates/shared/src/price_estimation/sanitized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ impl PriceEstimating for SanitizedPriceEstimator {
self.handle_bad_tokens(&query).await?;

// buy_token == sell_token => 1 to 1 conversion
if query.buy_token == query.sell_token {
let estimation = Estimate {
out_amount: query.in_amount.get(),
gas: 0,
solver: Default::default(),
verified: true,
};
tracing::debug!(?query, ?estimation, "generate trivial price estimation");
return Ok(estimation);
}
//if query.buy_token == query.sell_token {
// let estimation = Estimate {
// out_amount: query.in_amount.get(),
// gas: 0,
// solver: Default::default(),
// verified: true,
// };
// tracing::debug!(?query, ?estimation, "generate trivial price estimation");
// return Ok(estimation);
//}

// sell WETH for ETH => 1 to 1 conversion with cost for unwrapping
if query.sell_token == self.native_token && query.buy_token == BUY_ETH_ADDRESS {
Expand Down
5 changes: 4 additions & 1 deletion crates/solvers/src/boundary/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ impl<'a> Solver<'a> {
request: baseline::Request,
max_hops: usize,
) -> Option<baseline::Route<'a>> {
if request.sell.token == request.buy.token {
return Some(baseline::Route::new(vec![]));
}
let candidates = self.base_tokens.path_candidates_with_hops(
request.sell.token.0,
request.buy.token.0,
Expand Down Expand Up @@ -95,7 +98,7 @@ impl<'a> Solver<'a> {
.max_by_key(|(_, buy)| buy.value)?,
};

baseline::Route::new(segments)
Some(baseline::Route::new(segments))
}

fn traverse_path(
Expand Down
33 changes: 26 additions & 7 deletions crates/solvers/src/domain/solver/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use {
},
ethereum_types::U256,
std::{cmp, collections::HashSet, sync::Arc},
tracing::debug,
};

pub struct Baseline(Arc<Inner>);
Expand Down Expand Up @@ -148,7 +149,16 @@ impl Inner {
// can buy slightly more than intended. Fix this by
// capping the output amount to the order's buy amount
// for buy orders.
let mut output = route.output();
let mut output = if route.is_empty() {
order.sell
} else {
route.output()
};
let input = if route.is_empty() {
order.sell
} else {
route.input()
};
if let order::Side::Buy = order.side {
output.amount = cmp::min(output.amount, order.buy.amount);
}
Expand All @@ -158,10 +168,11 @@ impl Inner {
.success_probability(route.gas(), auction.gas_price, 1),
));

tracing::trace!("before single solution");
Some(
solution::Single {
order: order.clone(),
input: route.input(),
input,
output,
interactions,
gas: route.gas(),
Expand All @@ -171,6 +182,7 @@ impl Inner {
.with_buffers_internalizations(&auction.tokens),
)
});

if let Some(solution) = solution {
if sender.send(solution).is_err() {
tracing::debug!("deadline hit, receiver dropped");
Expand Down Expand Up @@ -240,18 +252,25 @@ pub struct Segment<'a> {
}

impl<'a> Route<'a> {
pub fn new(segments: Vec<Segment<'a>>) -> Option<Self> {
if segments.is_empty() {
return None;
}
Some(Self { segments })
pub fn new(segments: Vec<Segment<'a>>) -> Self {
Self { segments }
}

fn is_empty(&self) -> bool {
self.segments.is_empty()
}

fn input(&self) -> eth::Asset {
if self.is_empty() {
unreachable!("Input empty segment");
}
self.segments[0].input
}

fn output(&self) -> eth::Asset {
if self.is_empty() {
unreachable!("Output empty segment");
}
self.segments
.last()
.expect("route has at least one segment by construction")
Expand Down
4 changes: 2 additions & 2 deletions playground/driver.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ relative-slippage = "0.1" # Percentage in the [0, 1] range
account = "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6" # Known test private key

[submission]
gas-price-cap = 1e12
additional-tip-percentage = 0.05
gas-price-cap = "1000000000000"
#additional-tip-percentage = 0.05

[[submission.mempool]]
mempool = "public"
Expand Down