From 02baa9f4d96e29510ffdbf80f05f36e4bae55a41 Mon Sep 17 00:00:00 2001 From: dinesh11515 Date: Fri, 12 Dec 2025 12:52:03 +0530 Subject: [PATCH 1/2] feat : Add ZeroBuyAmount error handling in CalculateQuoteError --- crates/orderbook/src/api/post_quote.rs | 6 +++ crates/shared/src/order_quoting.rs | 69 ++++++++++++++++++++++++++ crates/shared/src/order_validation.rs | 1 + 3 files changed, 76 insertions(+) diff --git a/crates/orderbook/src/api/post_quote.rs b/crates/orderbook/src/api/post_quote.rs index a2dbfbf2e9..f6b224acea 100644 --- a/crates/orderbook/src/api/post_quote.rs +++ b/crates/orderbook/src/api/post_quote.rs @@ -67,6 +67,12 @@ impl IntoWarpReply for CalculateQuoteErrorWrapper { StatusCode::BAD_REQUEST, ) } + CalculateQuoteError::ZeroBuyAmount => { + warp::reply::with_status( + error("ZeroBuyAmount", "Buy amount is zero."), + StatusCode::BAD_REQUEST, + ) + } CalculateQuoteError::QuoteNotVerified => warp::reply::with_status( error( "QuoteNotVerified", diff --git a/crates/shared/src/order_quoting.rs b/crates/shared/src/order_quoting.rs index e133985f2d..6e1d3c997f 100644 --- a/crates/shared/src/order_quoting.rs +++ b/crates/shared/src/order_quoting.rs @@ -266,6 +266,9 @@ pub enum CalculateQuoteError { #[error("sell amount does not cover fee")] SellAmountDoesNotCoverFee { fee_amount: U256 }, + #[error("buy amount is zero")] + ZeroBuyAmount, + #[error("{estimator_kind:?} estimator failed: {source}")] Price { estimator_kind: EstimatorKind, @@ -628,6 +631,10 @@ impl OrderQuoting for OrderQuoter { quote = quote.with_scaled_sell_amount(sell_amount); } + if quote.buy_amount.is_zero() { + return Err(CalculateQuoteError::ZeroBuyAmount); + } + tracing::debug!(?quote, "computed quote"); Ok(quote) } @@ -1330,6 +1337,68 @@ mod tests { )); } + #[tokio::test] + async fn compute_quote_zero_buy_amount_error() { + let parameters = QuoteParameters { + sell_token: Address::from([1; 20]), + buy_token: Address::from([2; 20]), + side: OrderQuoteSide::Sell { + sell_amount: SellAmount::AfterFee { + value: NonZeroU256::try_from(100).unwrap(), + }, + }, + verification: Verification { + from: Address::from([3; 20]), + ..Default::default() + }, + signing_scheme: QuoteSigningScheme::Eip712, + additional_gas: 0, + timeout: None, + }; + let gas_price = GasPrice1559 { + base_fee_per_gas: 1., + max_fee_per_gas: 2., + max_priority_fee_per_gas: 0., + }; + + let mut price_estimator = MockPriceEstimating::new(); + price_estimator.expect_estimate().returning(|_| { + async { + Ok(price_estimation::Estimate { + out_amount: AlloyU256::ZERO, // Zero buy amount + gas: 200, + solver: H160([1; 20]), + verified: false, + execution: Default::default(), + }) + } + .boxed() + }); + + let mut native_price_estimator = MockNativePriceEstimating::new(); + native_price_estimator + .expect_estimate_native_price() + .returning(|_, _| async { Ok(1.) }.boxed()); + + let gas_estimator = FakeGasPriceEstimator::new(gas_price); + + let quoter = OrderQuoter { + price_estimator: Arc::new(price_estimator), + native_price_estimator: Arc::new(native_price_estimator), + gas_estimator: Arc::new(gas_estimator), + storage: Arc::new(MockQuoteStoring::new()), + now: Arc::new(Utc::now), + validity: Validity::default(), + quote_verification: QuoteVerificationMode::Unverified, + balance_fetcher: mock_balance_fetcher(), + default_quote_timeout: HEALTHY_PRICE_ESTIMATION_TIME, + }; + assert!(matches!( + quoter.calculate_quote(parameters).await.unwrap_err(), + CalculateQuoteError::ZeroBuyAmount, + )); + } + #[tokio::test] async fn require_native_price_for_buy_token() { let parameters = QuoteParameters { diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index e5c51ad7d7..494e03d1d0 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -197,6 +197,7 @@ impl From for ValidationError { | CalculateQuoteError::Other(err) => ValidationError::Other(err), CalculateQuoteError::Price { source, .. } => ValidationError::PriceForQuote(source), CalculateQuoteError::QuoteNotVerified => ValidationError::QuoteNotVerified, + CalculateQuoteError::ZeroBuyAmount => ValidationError::ZeroAmount, // This should never happen because we only calculate quotes with // `SellAmount::AfterFee`, meaning that the sell amount does not // need to be higher than the computed fee amount. Don't bubble up From 278ca0558380befd90f911f704d000f55384b133 Mon Sep 17 00:00:00 2001 From: dinesh11515 Date: Tue, 23 Dec 2025 05:15:56 +0530 Subject: [PATCH 2/2] fix : linting issue --- crates/orderbook/src/api/post_quote.rs | 10 ++++------ crates/shared/src/order_validation.rs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/orderbook/src/api/post_quote.rs b/crates/orderbook/src/api/post_quote.rs index f6b224acea..17066b090d 100644 --- a/crates/orderbook/src/api/post_quote.rs +++ b/crates/orderbook/src/api/post_quote.rs @@ -67,12 +67,10 @@ impl IntoWarpReply for CalculateQuoteErrorWrapper { StatusCode::BAD_REQUEST, ) } - CalculateQuoteError::ZeroBuyAmount => { - warp::reply::with_status( - error("ZeroBuyAmount", "Buy amount is zero."), - StatusCode::BAD_REQUEST, - ) - } + CalculateQuoteError::ZeroBuyAmount => warp::reply::with_status( + error("ZeroBuyAmount", "Buy amount is zero."), + StatusCode::BAD_REQUEST, + ), CalculateQuoteError::QuoteNotVerified => warp::reply::with_status( error( "QuoteNotVerified", diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 081bc04af6..c05d284fc9 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -196,7 +196,7 @@ impl From for ValidationError { | CalculateQuoteError::Other(err) => ValidationError::Other(err), CalculateQuoteError::Price { source, .. } => ValidationError::PriceForQuote(source), CalculateQuoteError::QuoteNotVerified => ValidationError::QuoteNotVerified, - CalculateQuoteError::ZeroBuyAmount => ValidationError::ZeroAmount, + CalculateQuoteError::ZeroBuyAmount => ValidationError::ZeroAmount, // This should never happen because we only calculate quotes with // `SellAmount::AfterFee`, meaning that the sell amount does not // need to be higher than the computed fee amount. Don't bubble up