@@ -234,13 +234,25 @@ pub fn decide_quote(
234234 let Some ( capacity) = capacity else {
235235 return Err ( RejectReason :: Inventory ) ;
236236 } ;
237- if input > capacity {
238- return Err ( RejectReason :: Size ) ;
239- }
240237 let reserved = if bid { reserved_bid } else { reserved_ask } ;
241- if input > capacity. saturating_sub ( reserved) {
238+ let available = capacity. saturating_sub ( reserved) ;
239+ if available. is_zero ( ) {
242240 return Err ( RejectReason :: Inventory ) ;
243241 }
242+ // The venue may ask for more than we can fill (stale levels, or a slice
243+ // of a larger RFQ). Quote what remains instead of rejecting — the API
244+ // bundles several makers into one taker quote.
245+ let ( input, output, fee, sell_amount) = if input > available {
246+ let input = available;
247+ let output = output_for_input ( input) ;
248+ let fee = fee_on ( output, req. fee_bps ) ;
249+ if input. is_zero ( ) || output. is_zero ( ) || fee. is_zero ( ) {
250+ return Err ( RejectReason :: Size ) ;
251+ }
252+ ( input, output, fee, output + fee)
253+ } else {
254+ ( input, output, fee, sell_amount)
255+ } ;
244256
245257 Ok ( QuotePlan {
246258 bid,
@@ -436,23 +448,28 @@ mod tests {
436448 }
437449
438450 #[ test]
439- fn capacity_and_reservations_gate_with_distinct_reasons ( ) {
440- // Bigger than configured capacity outright → size .
451+ fn capacity_scales_down_instead_of_rejecting_size ( ) {
452+ // Bigger than configured capacity: quote the cap, don't reject .
441453 let mut req = request ( COLLATERAL , DEBT ) ;
442454 req. sell_amount = Some ( "100000000000" . into ( ) ) ; // maker would pay ~98e9 > 5e9
443- assert_eq ! (
444- decide( & book( ) , & req, 1.0 , U256 :: ZERO , U256 :: ZERO ) ,
445- Err ( RejectReason :: Size )
446- ) ;
455+ let plan = decide ( & book ( ) , & req, 1.0 , U256 :: ZERO , U256 :: ZERO ) . unwrap ( ) ;
456+ assert_eq ! ( plan. input, U256 :: from( 5_000_000_000u64 ) ) ;
457+ assert ! ( plan. sell_amount < U256 :: from( 100000000000u64 ) ) ;
447458
448- // Fits capacity but not what's left after in-flight quotes → inventory.
459+ // Fits capacity but nothing left after in-flight quotes → inventory.
449460 let mut req = request ( COLLATERAL , DEBT ) ;
450- req. sell_amount = Some ( "1000000000" . into ( ) ) ; // maker pays ~0.98e9
451- let reserved_bid = U256 :: from ( 4_500_000_000u64 ) ; // 4.5e9 of 5e9 claimed
461+ req. sell_amount = Some ( "1000000000" . into ( ) ) ;
462+ let fully_reserved = U256 :: from ( 5_000_000_000u64 ) ;
452463 assert_eq ! (
453- decide( & book( ) , & req, 1.0 , reserved_bid , U256 :: ZERO ) ,
464+ decide( & book( ) , & req, 1.0 , fully_reserved , U256 :: ZERO ) ,
454465 Err ( RejectReason :: Inventory )
455466 ) ;
467+
468+ // Partial reservation: quote what's left rather than inventory-reject.
469+ let reserved_bid = U256 :: from ( 4_500_000_000u64 ) ; // 0.5e9 left
470+ let plan = decide ( & book ( ) , & req, 1.0 , reserved_bid, U256 :: ZERO ) . unwrap ( ) ;
471+ assert_eq ! ( plan. input, U256 :: from( 500_000_000u64 ) ) ;
472+
456473 // The ask side's reservations don't bleed into the bid check.
457474 assert ! ( decide( & book( ) , & req, 1.0 , U256 :: ZERO , reserved_bid) . is_ok( ) ) ;
458475 }
@@ -641,13 +658,25 @@ mod tests {
641658 ] ) ) ;
642659 assert ! ( decide_quote( & wallet_book, & req, 1.0 , U256 :: ZERO , U256 :: ZERO , & inv) . is_ok( ) ) ;
643660
644- // Reservations still eat the live balance.
661+ // Reservations eat the live balance; leftover is still quoted.
662+ let plan = decide_quote (
663+ & wallet_book,
664+ & req,
665+ 1.0 ,
666+ U256 :: from ( 1_500_000_000u64 ) ,
667+ U256 :: ZERO ,
668+ & inv,
669+ )
670+ . unwrap ( ) ;
671+ assert_eq ! ( plan. input, U256 :: from( 500_000_000u64 ) ) ;
672+
673+ // Nothing left → inventory.
645674 assert_eq ! (
646675 decide_quote(
647676 & wallet_book,
648677 & req,
649678 1.0 ,
650- U256 :: from( 1_500_000_000u64 ) ,
679+ U256 :: from( 2_000_000_000u64 ) ,
651680 U256 :: ZERO ,
652681 & inv
653682 ) ,
@@ -676,9 +705,11 @@ mod tests {
676705 ( debt, U256 :: from ( 100_000u64 ) ) ,
677706 ( collateral, U256 :: from ( 100_000u64 ) ) ,
678707 ] ) ) ;
708+ let thin_plan = decide_quote ( & exact, & req, 1.0 , U256 :: ZERO , U256 :: ZERO , & thin) . unwrap ( ) ;
679709 assert_eq ! (
680- decide_quote( & exact, & req, 1.0 , U256 :: ZERO , U256 :: ZERO , & thin) ,
681- Err ( RejectReason :: Size )
710+ thin_plan. input,
711+ U256 :: from( 100_000u64 ) ,
712+ "thin wallet is quoted as a leftover slice, not size-rejected"
682713 ) ;
683714 assert ! ( decide_quote( & exact, & req, 1.0 , U256 :: ZERO , U256 :: ZERO , & inv) . is_ok( ) ) ;
684715 }
0 commit comments