Skip to main content

tycho_common/simulation/
swap.rs

1use std::{collections::HashMap, fmt, fmt::Debug, sync::Arc};
2
3use itertools::Itertools;
4use num_bigint::BigUint;
5
6use crate::{
7    dto::ProtocolStateDelta,
8    models::{protocol::ProtocolComponent, token::Token},
9    simulation::{
10        errors::{SimulationError, TransitionError},
11        indicatively_priced::IndicativelyPriced,
12        protocol_sim::{Balances, Price, ProtocolSim},
13    },
14    Bytes,
15};
16
17/// Result type for swap simulation operations that may fail with a `SimulationError`.
18pub type SimulationResult<T> = Result<T, SimulationError>;
19
20/// Type alias for token addresses, represented as raw bytes.
21pub type TokenAddress = Bytes;
22
23/// Macro that generates parameter structs with embedded blockchain context.
24///
25/// This macro creates structs that automatically include a `Context` field and provides
26/// methods for managing blockchain context (block number and timestamp). All parameter
27/// structs used in swap simulations should be created with this macro to ensure consistent
28/// context handling.
29///
30/// # Generated Methods
31/// - `with_context(context: Context) -> Self` - Sets the blockchain context
32/// - `context() -> &Context` - Gets a reference to the current context
33///
34/// # Example
35/// ```rust
36/// params_with_context! {
37///     pub struct MyParams {
38///         token: TokenAddress,
39///         amount: BigUint,
40///     }
41/// }
42/// ```
43macro_rules! params_with_context {
44    (
45        $(#[$meta:meta])*
46        $vis:vis struct $name:ident $(<$($gen:tt),*>)? {
47            $($field:ident: $ty:ty),* $(,)?
48        }
49    ) => {
50        $(#[$meta])*
51        #[derive(Debug, Clone)]
52        $vis struct $name $(<$($gen),*>)? {
53            context: Context,
54            $($field: $ty,)*
55        }
56
57        impl $(<$($gen),*>)? $name $(<$($gen),*>)? {
58
59            pub fn with_context(mut self, context: Context) -> Self {
60                self.context = context;
61                self
62            }
63
64            pub fn context(&self) -> &Context {
65                &self.context
66            }
67        }
68    };
69}
70
71/// Blockchain context information used in swap simulations.
72///
73/// Contains optional future block information that can be used for time-sensitive
74/// simulations or to simulate swaps at specific future blockchain states.
75#[derive(Debug, Clone)]
76pub struct Context {}
77
78impl Default for Context {
79    /// Creates a new `Context` with no future block information.
80    fn default() -> Self {
81        Self {}
82    }
83}
84
85params_with_context! {
86/// Parameters for requesting a swap quote from a pool.
87///
88/// Contains the tokens to swap between, the input amount, and whether the
89/// simulation should return a modified state to reflect the swap execution.
90pub struct QuoteParams<'a>{
91        token_in: &'a TokenAddress,
92        token_out: &'a TokenAddress,
93        amount: QuoteAmount,
94        return_new_state: bool,
95    }
96}
97
98#[derive(Debug, Clone)]
99pub enum QuoteAmount {
100    FixedIn(BigUint),
101    FixedOut(BigUint),
102}
103
104impl<'a> QuoteParams<'a> {
105    /// Creates new fixed input parameters with default settings (no state modification).
106    ///
107    /// # Arguments
108    /// * `token_in` - The token to sell
109    /// * `token_out` - The token to buy
110    /// * `amount` - The amount of input token to sell
111    pub fn fixed_in(
112        token_in: &'a TokenAddress,
113        token_out: &'a TokenAddress,
114        amount: BigUint,
115    ) -> SimulationResult<Self> {
116        if token_out == token_in {
117            return Err(SimulationError::InvalidInput(
118                "Quote tokens have to differ!".to_string(),
119                None,
120            ))
121        }
122        Ok(Self {
123            context: Context::default(),
124            token_in,
125            token_out,
126            amount: QuoteAmount::FixedIn(amount),
127            return_new_state: false,
128        })
129    }
130
131    /// Creates new fixed output parameters with default settings (no state modification).
132    ///
133    /// # Arguments
134    /// * `token_in` - The token to sell
135    /// * `token_out` - The token to buy
136    /// * `amount` - The amount of output token to buy
137    pub fn fixed_out(
138        token_in: &'a TokenAddress,
139        token_out: &'a TokenAddress,
140        amount: BigUint,
141    ) -> SimulationResult<Self> {
142        if token_out == token_in {
143            return Err(SimulationError::InvalidInput(
144                "Quote tokens have to differ!".to_string(),
145                None,
146            ))
147        }
148
149        Ok(Self {
150            context: Context::default(),
151            token_in,
152            token_out,
153            amount: QuoteAmount::FixedOut(amount),
154            return_new_state: false,
155        })
156    }
157
158    pub fn amount(&self) -> &QuoteAmount {
159        &self.amount
160    }
161
162    /// Configures the quote to modify the state during simulation.
163    ///
164    /// When enabled, the quote simulation will return an updated state
165    /// as if the swap was actually executed.
166    pub fn with_new_state(mut self) -> Self {
167        self.return_new_state = true;
168        self
169    }
170
171    /// Returns the input token address.
172    pub fn token_in(&self) -> &TokenAddress {
173        self.token_in
174    }
175
176    /// Returns the output token address.
177    pub fn token_out(&self) -> &TokenAddress {
178        self.token_out
179    }
180
181    /// Returns whether the simulation should return the post-swap state.
182    pub fn should_return_new_state(&self) -> bool {
183        self.return_new_state
184    }
185}
186
187params_with_context! {
188/// Parameters for querying swap limits from a pool.
189///
190/// Used to determine the minimum and maximum amounts that can be traded
191/// between two tokens in the pool.
192pub struct LimitsParams<'a> {
193    token_in: &'a TokenAddress,
194    token_out: &'a TokenAddress,
195}
196}
197
198impl<'a> LimitsParams<'a> {
199    /// Creates new parameters for querying swap limits.
200    ///
201    /// # Arguments
202    /// * `token_in` - The input token address
203    /// * `token_out` - The output token address
204    pub fn new(token_in: &'a TokenAddress, token_out: &'a TokenAddress) -> Self {
205        Self { context: Context::default(), token_in, token_out }
206    }
207
208    pub fn token_in(&self) -> &TokenAddress {
209        self.token_in
210    }
211
212    pub fn token_out(&self) -> &TokenAddress {
213        self.token_out
214    }
215}
216
217params_with_context! {
218/// Parameters for querying the spot price between two tokens.
219///
220/// Used to get the current marginal price for infinitesimally small trades
221/// between two tokens in a pool.
222pub struct MarginalPriceParams<'a> {
223    token_in: &'a TokenAddress,
224    token_out: &'a TokenAddress,
225}
226}
227
228impl<'a> MarginalPriceParams<'a> {
229    /// Creates new parameters for querying marginal price.
230    ///
231    /// # Arguments
232    /// * `token_in` - The input token address
233    /// * `token_out` - The output token address
234    pub fn new(token_in: &'a TokenAddress, token_out: &'a TokenAddress) -> Self {
235        Self { context: Context::default(), token_in, token_out }
236    }
237    pub fn token_in(&self) -> &TokenAddress {
238        self.token_in
239    }
240
241    pub fn token_out(&self) -> &TokenAddress {
242        self.token_out
243    }
244}
245
246/// Represents a swap fee as a fraction.
247///
248/// The fee is typically expressed as a decimal (e.g., 0.003 for 0.3%).
249pub struct SwapFee {
250    fee: f64,
251}
252
253impl SwapFee {
254    /// Creates a new swap fee.
255    ///
256    /// # Arguments
257    /// * `fee` - The fee as a decimal fraction (e.g., 0.003 for 0.3%)
258    pub fn new(fee: f64) -> Self {
259        Self { fee }
260    }
261
262    /// Returns the fee as a decimal fraction.
263    pub fn fee(&self) -> f64 {
264        self.fee
265    }
266}
267
268/// Represents the marginal price at which the next infinitesimal trade would execute.
269///
270/// This is the instantaneous price for very small trades at the current pool state,
271/// often different from the effective price of larger trades due to slippage.
272pub struct MarginalPrice {
273    price: f64,
274}
275
276impl MarginalPrice {
277    /// Creates a new marginal price.
278    ///
279    /// # Arguments
280    /// * `price` - The marginal price as token_out/token_in ratio
281    pub fn new(price: f64) -> Self {
282        Self { price }
283    }
284
285    /// Returns the marginal price value.
286    pub fn price(&self) -> f64 {
287        self.price
288    }
289}
290
291/// Result of a swap quote calculation.
292///
293/// Contains the expected output amount, gas cost, and optionally the new pool state
294/// if the quote was requested with state modification enabled.
295pub struct Quote {
296    amount_out: BigUint,
297    gas: BigUint,
298    new_state: Option<Arc<dyn SwapQuoter>>,
299}
300
301impl Quote {
302    /// Creates a new quote result.
303    ///
304    /// # Arguments
305    /// * `amount_out` - The amount of output tokens that would be received
306    /// * `gas` - The estimated gas cost for executing this swap, excluding token transfers cost
307    /// * `new_state` - The new pool state after the swap (if state modification was requested)
308    pub fn new(amount_out: BigUint, gas: BigUint, new_state: Option<Arc<dyn SwapQuoter>>) -> Self {
309        Self { amount_out, gas, new_state }
310    }
311
312    /// Returns the amount of output tokens.
313    pub fn amount_out(&self) -> &BigUint {
314        &self.amount_out
315    }
316
317    /// Returns the estimated swap gas cost excluding including token transfer cost.
318    pub fn gas(&self) -> &BigUint {
319        &self.gas
320    }
321
322    /// Returns the new pool state after the swap, if available.
323    pub fn new_state(&self) -> Option<Arc<dyn SwapQuoter>> {
324        self.new_state.clone()
325    }
326}
327
328/// Represents a numeric range with lower and upper bounds.
329///
330/// Used for specifying trading limits and constraints.
331pub struct Range {
332    lower: BigUint,
333    upper: BigUint,
334}
335
336impl Range {
337    /// Creates a new range with validation.
338    ///
339    /// # Arguments
340    /// * `lower` - The lower bound (must be <= upper)
341    /// * `upper` - The upper bound (must be >= lower)
342    ///
343    /// # Errors
344    /// Returns `SimulationError::InvalidInput` if lower > upper.
345    pub fn new(lower: BigUint, upper: BigUint) -> SimulationResult<Self> {
346        if lower > upper {
347            return Err(SimulationError::InvalidInput(
348                "Invalid range! Argument lower > upper".to_string(),
349                None,
350            ))
351        }
352        Ok(Self { lower, upper })
353    }
354
355    /// Returns the lower bound.
356    pub fn lower(&self) -> &BigUint {
357        &self.lower
358    }
359
360    /// Returns the upper bound.
361    pub fn upper(&self) -> &BigUint {
362        &self.upper
363    }
364}
365
366/// Defines the trading limits for input and output amounts in a swap.
367///
368/// Specifies the minimum and maximum amounts that can be traded through a pool
369/// for both input and output tokens.
370pub struct SwapLimits {
371    range_in: Range,
372    range_out: Range,
373}
374
375impl SwapLimits {
376    /// Creates new swap limits.
377    ///
378    /// # Arguments
379    /// * `range_in` - The valid range for input token amounts
380    /// * `range_out` - The valid range for output token amounts
381    pub fn new(range_in: Range, range_out: Range) -> Self {
382        Self { range_in, range_out }
383    }
384
385    /// Returns the input amount limits.
386    pub fn range_in(&self) -> &Range {
387        &self.range_in
388    }
389
390    /// Returns the output amount limits.
391    pub fn range_out(&self) -> &Range {
392        &self.range_out
393    }
394}
395
396params_with_context! {
397/// Parameters for applying protocol state transitions.
398///
399/// Contains the state delta and associated data needed to transition
400/// a pool's state in response to blockchain events.
401pub struct TransitionParams<'a> {
402        delta: ProtocolStateDelta,
403        tokens: &'a HashMap<Bytes, Token>,
404        balances: &'a Balances,
405    }
406}
407
408impl<'a> TransitionParams<'a> {
409    /// Creates new parameters for state transition.
410    ///
411    /// # Arguments
412    /// * `delta` - The protocol state change to apply
413    /// * `tokens` - Map of token addresses to token metadata
414    /// * `balances` - Current token balances in the system
415    pub fn new(
416        delta: ProtocolStateDelta,
417        tokens: &'a HashMap<Bytes, Token>,
418        balances: &'a Balances,
419    ) -> Self {
420        Self { context: Context::default(), delta, tokens, balances }
421    }
422
423    pub fn delta(&self) -> &ProtocolStateDelta {
424        &self.delta
425    }
426
427    pub fn tokens(&self) -> &HashMap<Bytes, Token> {
428        self.tokens
429    }
430
431    pub fn balances(&self) -> &Balances {
432        self.balances
433    }
434}
435
436/// Result of applying a state transition to a pool.
437///
438/// Currently, a placeholder struct that may be extended in the future
439/// to contain transition metadata or validation results.
440pub struct Transition {}
441
442impl Default for Transition {
443    /// Creates a new transition result.
444    fn default() -> Self {
445        Self {}
446    }
447}
448
449/// Defines constraints for advanced quote calculations.
450///
451/// These constraints allow sophisticated trading strategies by limiting swaps
452/// based on price thresholds or targeting specific pool states.
453#[derive(Debug, Clone, PartialEq)]
454pub enum SwapConstraint {
455    /// This mode will calculate the maximum trade that this pool can execute while respecting a
456    /// trade limit price.
457    #[non_exhaustive]
458    TradeLimitPrice {
459        /// The minimum acceptable price for the resulting trade, as a [Price] struct. The
460        /// resulting amount_out / amount_in must be >= trade_limit_price
461        limit: Price,
462        /// The tolerance as a fraction to be applied on top of (increasing) the trade
463        /// limit price, raising the acceptance threshold. This is used to loosen the acceptance
464        /// criteria for implementations of this method, but will never allow violating the trade
465        /// limit price itself.
466        tolerance: f64,
467        /// The minimum amount of token_in that must be used for this trade.
468        min_amount_in: Option<BigUint>,
469        /// The maximum amount of token_in that can be used for this trade.
470        max_amount_in: Option<BigUint>,
471    },
472
473    /// This mode will calculate the amount of token_in required to move the pool's marginal price
474    /// down to a target price, and the amount of token_out received.
475    ///
476    /// # Edge Cases and Limitations
477    ///
478    /// Computing the exact amount to move a pool's marginal price to a target has several
479    /// challenges:
480    /// - The definition of marginal price varies between protocols. It is usually not an attribute
481    ///   of the pool but a consequence of its liquidity distribution and current state.
482    /// - For protocols with concentrated liquidity, the marginal price is discrete, meaning we
483    ///   can't always find an exact trade amount to reach the target price.
484    /// - Not all protocols support analytical solutions for this problem, requiring numerical
485    ///   methods.
486    #[non_exhaustive]
487    PoolTargetPrice {
488        /// The marginal price we want the pool to be after the trade, as a [Price] struct. The
489        /// pool's price will move down to this level as token_in is sold into it
490        target: Price,
491        /// The tolerance as a fraction of the resulting pool marginal price. After trading, the
492        /// pool's price will decrease to the interval `[target, target * (1 +
493        /// tolerance)]`.
494        tolerance: f64,
495        /// The lower bound for searching algorithms.
496        min_amount_in: Option<BigUint>,
497        /// The upper bound for searching algorithms.
498        max_amount_in: Option<BigUint>,
499    },
500}
501
502impl SwapConstraint {
503    /// Creates a trade limit price constraint.
504    ///
505    /// This constraint finds the maximum trade size while respecting a minimum price
506    /// threshold. See `SwapConstraint::TradeLimitPrice` for details.
507    ///
508    /// # Arguments
509    /// * `limit` - The minimum acceptable price for the trade
510    /// * `tolerance` - Additional tolerance as a fraction to loosen the constraint
511    pub fn trade_limit_price(limit: Price, tolerance: f64) -> Self {
512        SwapConstraint::TradeLimitPrice {
513            limit,
514            tolerance,
515            min_amount_in: None,
516            max_amount_in: None,
517        }
518    }
519
520    /// Creates a pool target price constraint.
521    ///
522    /// This constraint calculates the trade needed to move the pool's price to a
523    /// target level. See `SwapConstraint::PoolTargetPrice` for details.`
524    ///
525    /// # Arguments
526    /// * `target` - The desired final marginal price of the pool
527    /// * `tolerance` - Acceptable variance from the target price as a fraction
528    pub fn pool_target_price(target: Price, tolerance: f64) -> Self {
529        SwapConstraint::PoolTargetPrice {
530            target,
531            tolerance,
532            min_amount_in: None,
533            max_amount_in: None,
534        }
535    }
536
537    /// Adds a lower bound to the constraint's search range.
538    ///
539    /// # Arguments
540    /// * `lower` - The minimum amount_in to consider
541    ///
542    /// # Returns
543    /// The modified constraint with the lower bound applied.
544    pub fn with_lower_bound(mut self, lower: BigUint) -> SimulationResult<Self> {
545        match &mut self {
546            SwapConstraint::PoolTargetPrice { min_amount_in, .. } => {
547                *min_amount_in = Some(lower);
548                Ok(self)
549            }
550            SwapConstraint::TradeLimitPrice { min_amount_in, .. } => {
551                *min_amount_in = Some(lower);
552                Ok(self)
553            }
554        }
555    }
556
557    /// Adds an upper bound to the constraint's search range.
558    ///
559    /// # Arguments
560    /// * `upper` - The maximum amount_in to consider
561    ///
562    /// # Returns
563    /// The modified constraint with the upper bound applied.
564    pub fn with_upper_bound(mut self, upper: BigUint) -> SimulationResult<Self> {
565        match &mut self {
566            SwapConstraint::PoolTargetPrice { max_amount_in, .. } => {
567                *max_amount_in = Some(upper);
568                Ok(self)
569            }
570            SwapConstraint::TradeLimitPrice { max_amount_in, .. } => {
571                *max_amount_in = Some(upper);
572                Ok(self)
573            }
574        }
575    }
576}
577
578params_with_context! {
579/// Parameters for advanced swap queries with constraints.
580///
581/// Used for sophisticated swap calculations that respect price limits or target
582/// prices instead of given amount values.
583pub struct QuerySwapParams<'a> {
584    token_in: &'a TokenAddress,
585    token_out: &'a TokenAddress,
586    swap_constraint: SwapConstraint,
587}
588}
589
590impl<'a> QuerySwapParams<'a> {
591    /// Creates new parameters for constrained swap queries.
592    ///
593    /// # Arguments
594    /// * `token_in` - The input token address
595    /// * `token_out` - The output token metadata
596    /// * `swap_constraint` - The constraint to apply to the swap calculation
597    pub fn new(
598        token_in: &'a TokenAddress,
599        token_out: &'a TokenAddress,
600        swap_constraint: SwapConstraint,
601    ) -> Self {
602        Self { context: Context::default(), token_in, token_out, swap_constraint }
603    }
604
605    pub fn token_in(&self) -> &'a TokenAddress {
606        self.token_in
607    }
608
609    pub fn token_out(&self) -> &'a TokenAddress {
610        self.token_out
611    }
612
613    pub fn swap_constraint(&self) -> &SwapConstraint {
614        &self.swap_constraint
615    }
616}
617
618/// Result of an advanced swap calculation with constraints.
619///
620/// Contains the calculated swap amounts, optionally the new pool state,
621/// and price points traversed during calculation for optimization purposes.
622pub struct Swap {
623    /// The amount of token_in sold to the component
624    amount_in: BigUint,
625    /// The amount of token_out bought from the component
626    amount_out: BigUint,
627    /// The new state of the component after the swap
628    new_state: Option<Arc<dyn SwapQuoter>>,
629    /// Optional price points that the pool was transitioned through while computing this swap.
630    /// The values are tuples of (amount_in, amount_out, price). This is useful for repeated calls
631    /// by providing good bounds for the next call.
632    price_points: Option<Vec<PricePoint>>,
633}
634
635/// A point on the AMM price curve.
636///
637/// Collected during iterative numerical search algorithms.
638/// These points can be reused as bounds for subsequent searches, improving convergence speed.
639#[derive(Debug, Clone)]
640pub struct PricePoint {
641    /// The amount of token_in in atomic units (wei).
642    amount_in: BigUint,
643    /// The amount of token_out in atomic units (wei).
644    amount_out: BigUint,
645    /// The price in units of `[token_out/token_in]` scaled by decimals.
646    ///
647    /// Computed as `(amount_out / 10^token_out_decimals) / (amount_in / 10^token_in_decimals)`.
648    price: f64,
649}
650
651impl PricePoint {
652    pub fn amount_in(&self) -> &BigUint {
653        &self.amount_in
654    }
655
656    pub fn amount_out(&self) -> &BigUint {
657        &self.amount_out
658    }
659
660    pub fn price(&self) -> f64 {
661        self.price
662    }
663}
664
665impl Swap {
666    /// Creates a new swap result.
667    ///
668    /// # Arguments
669    /// * `amount_in` - The amount of input tokens used
670    /// * `amount_out` - The amount of output tokens received
671    /// * `new_state` - The new pool state after the swap (if calculated)
672    /// * `price_points` - Optional price trajectory data for optimization
673    pub fn new(
674        amount_in: BigUint,
675        amount_out: BigUint,
676        new_state: Option<Arc<dyn SwapQuoter>>,
677        price_points: Option<Vec<PricePoint>>,
678    ) -> Self {
679        Self { amount_in, amount_out, new_state, price_points }
680    }
681
682    /// Returns the amount of input tokens used.
683    pub fn amount_in(&self) -> &BigUint {
684        &self.amount_in
685    }
686
687    /// Returns the amount of output tokens received.
688    pub fn amount_out(&self) -> &BigUint {
689        &self.amount_out
690    }
691
692    /// Returns the new pool state after the swap, if calculated.
693    pub fn new_state(&self) -> Option<Arc<dyn SwapQuoter>> {
694        self.new_state.clone()
695    }
696
697    /// Returns the price points traversed during calculation.
698    ///
699    /// Each tuple contains (amount_in, amount_out, price) at various points
700    /// during the swap calculation, useful for optimizing subsequent calls.
701    pub fn price_points(&self) -> &Option<Vec<PricePoint>> {
702        &self.price_points
703    }
704}
705
706/// Core trait for implementing swap quote functionality.
707///
708/// This trait defines the interface that all liquidity sources must implement
709/// to participate in swap quotes. It provides methods for price discovery,
710/// quote calculation, state transitions, and advanced swap queries.
711///
712/// Implementations should be thread-safe and support cloning for parallel simulations.
713#[typetag::serde(tag = "protocol", content = "state")]
714pub trait SwapQuoter: fmt::Debug + Send + Sync + 'static {
715    /// Returns the [`ProtocolComponent`] describing the protocol instance this quoter
716    /// is associated with.
717    ///
718    /// The component provides **structural and descriptive metadata** about the protocol,
719    /// such as the set of involved tokens and protocol-specific configuration, but does not
720    /// represent a mutable simulation state.
721    ///
722    /// # Semantics
723    ///
724    /// - The returned component is expected to be **stable for the lifetime of the quoter**.
725    /// - Multiple quoter instances may share the same component instance; callers should not assume
726    ///   unique ownership (e.g. quoter instances may represent the state at different points in
727    ///   time)
728    /// - The component is used for discovery and introspection (e.g. determining supported tokens
729    ///   or deriving default quotable pairs), not for executing swaps.
730    ///
731    /// # Ownership
732    ///
733    /// This method returns an `Arc` to allow cheap cloning and shared access without
734    /// constraining the internal storage strategy of the implementation.
735    ///
736    /// # Intended use
737    ///
738    /// Typical uses include:
739    /// - Inspecting the tokens and configuration exposed by the protocol
740    /// - Deriving default [`quotable_pairs`](Self::quotable_pairs)
741    /// - Identifying or grouping quoters by protocol metadata
742    fn component(&self) -> Arc<ProtocolComponent<Arc<Token>>>;
743
744    /// Returns the set of **directed token pairs** for which this quoter can produce swap quotes.
745    ///
746    /// Each `(base, quote)` pair indicates that a swap from `base` to `quote` is supported.
747    /// Direction matters: `(A, B)` and `(B, A)` are considered distinct pairs and might not
748    /// both be present.
749    ///
750    /// # Semantics
751    ///
752    /// - The returned set represents **capability**, not liquidity or pricing guarantees. A pair
753    ///   being present does not imply that a quote will be favorable or even currently executable,
754    ///   only that the quoter understands how to price it.
755    /// - The set may be **computed dynamically** and is not required to be stable across calls,
756    ///   though most implementations are expected to return the same result unless the underlying
757    ///   protocol configuration changes.
758    ///
759    /// # Default behavior
760    ///
761    /// The default implementation derives the pairs from the tokens exposed by
762    /// [`component()`], returning all ordered pairs `(a, b)` where `a != b`.
763    /// Protocols with restricted or asymmetric support (e.g. RFQ-based or single-sided
764    /// designs) should override this method.
765    ///
766    ///
767    /// # Intended use
768    ///
769    /// This method is primarily intended for routing, discovery, and validation logic,
770    /// allowing callers to determine whether a quote request is meaningful before invoking
771    /// [`quote`].
772    fn quotable_pairs(&self) -> Vec<(Arc<Token>, Arc<Token>)> {
773        let component = self.component();
774        component
775            .tokens
776            .iter()
777            .permutations(2)
778            .map(|token| (token[0].clone(), token[1].clone()))
779            .collect()
780    }
781
782    /// Computes the protocol fee applicable to a prospective swap described by `params`.
783    ///
784    /// This method evaluates the fee that would be charged by the protocol for the given
785    /// swap parameters, without performing the swap or mutating any internal state.
786    ///
787    /// # Semantics
788    ///
789    /// - The returned [`SwapFee`] represents the **protocol-defined fee component** of the swap
790    ///   (e.g. LP fee, protocol fee, or RFQ spread), as understood by this quoter.
791    /// - Fee computation is **pure and side-effect free**; calling this method must not modify the
792    ///   internal state of the quoter.
793    /// - The fee may depend on the full set of quote parameters (including direction, amount, or
794    ///   other protocol-specific inputs).
795    ///
796    /// # Relation to quoting
797    ///
798    /// Implementations may internally reuse logic from [`quote`], but this method exists to
799    /// allow callers to:
800    /// - Inspect or decompose pricing components
801    /// - Perform fee-aware routing or optimization
802    /// - Estimate costs without requesting a full quote
803    ///
804    /// # Errors
805    ///
806    /// Returns an error if the fee cannot be determined for the given parameters (e.g. the
807    /// pair is not quotable or required inputs are missing)
808    fn fee(&self, params: QuoteParams) -> SimulationResult<SwapFee>;
809
810    /// Computes the **marginal (infinitesimal) price** for a swap described by `params`,
811    /// with **all protocol fees included**.
812    ///
813    /// The marginal price represents the instantaneous exchange rate at the current
814    /// protocol state, evaluated at an infinitesimally small trade size. It reflects the
815    /// derivative of output amount with respect to input amount for the specified swap
816    /// direction, inclusive of any protocol-defined fees or spreads.
817    ///
818    /// # Semantics
819    ///
820    /// - Fees are **always included** in the returned [`MarginalPrice`].
821    /// - The price is evaluated **at the margin** and does not represent an executable price for a
822    ///   finite trade.
823    /// - This method is **pure and side-effect free**; it must not mutate internal state.
824    /// - For sufficiently small trade sizes, the marginal price should be consistent with
825    ///   [`quote`], up to numerical precision.
826    ///
827    /// # Use cases
828    ///
829    /// Typical uses include:
830    /// - Price display and monitoring
831    /// - Slippage estimation and sensitivity analysis
832    /// - Routing heuristics and initial path selection
833    ///
834    /// # Errors
835    ///
836    /// Returns an error if the marginal price is undefined or cannot be computed for the
837    /// given parameters (e.g. unsupported pair, zero liquidity, or missing inputs).
838    fn marginal_price(&self, params: MarginalPriceParams) -> SimulationResult<MarginalPrice>;
839
840    /// Produces a swap quote for the trade described by `params`, with **all protocol fees
841    /// included**.
842    ///
843    /// The returned [`Quote`] represents the effective execution terms of the swap as
844    /// understood by this quoter, including any protocol-defined fees, spreads, or
845    /// adjustments. Calling this method does not perform the swap and does not mutate
846    /// internal state.
847    ///
848    /// # Semantics
849    ///
850    /// - Fees are **always included** in the quoted price and amounts. Callers should not apply
851    ///   additional protocol fees on top of the returned quote.
852    /// - Fees mentioned above **do not include** chain fees such as gas costs.
853    /// - The quote reflects a **finite-size trade** and therefore accounts for price impact where
854    ///   applicable.
855    /// - This method is **pure and side-effect free**; it must not mutate internal state.
856    /// - For sufficiently small trade sizes, the quote should be consistent with
857    ///   [`marginal_price`], up to numerical precision.
858    ///
859    /// # Intended use
860    ///
861    /// This method is the primary entry point for consumers of [`SwapQuoter`] and is
862    /// intended for:
863    /// - User-facing price discovery
864    /// - Routing and optimization across multiple quoters
865    /// - Simulation and what-if analysis
866    ///
867    /// # Errors
868    ///
869    /// Returns an error if a quote cannot be produced for the given parameters (e.g. the
870    /// pair is not quotable, required inputs are missing, or the quote is undefined).
871    fn quote(&self, params: QuoteParams) -> SimulationResult<Quote>;
872
873    /// Returns the valid execution limits for a prospective quote
874    ///
875    /// The returned [`SwapLimits`] describes the bounds within which a swap can be quoted or
876    /// simulated, such as minimum and maximum input or output amounts, given the current
877    /// protocol state.
878    ///
879    /// # Semantics
880    ///
881    /// - Limits are evaluated **at the current state** of the quoter and do not imply that a quote
882    ///   will succeed outside the returned bounds.
883    /// - The limits may depend on swap direction, fees, liquidity constraints, or protocol-specific
884    ///   rules.
885    /// - This method is **pure and side-effect free**; it must not mutate internal state.
886    /// - Limits are expressed in **fee-inclusive terms**, consistent with [`quote`] and
887    ///   [`marginal_price`].
888    ///
889    /// # Intended use
890    ///
891    /// Typical uses include:
892    /// - Pre-validating quote requests before quoting
893    /// - Bounding search spaces for routing and optimization
894    /// - UI validation and input clamping
895    ///
896    /// # Errors
897    ///
898    /// Returns an error if limits cannot be determined for the given parameters (e.g. the
899    /// pair is not quotable or required inputs are missing).
900    fn swap_limits(&self, params: LimitsParams) -> SimulationResult<SwapLimits>;
901
902    /// Produces an **advanced, price-constraint-based quote**
903    ///
904    /// Unlike [`quote`], which prices a swap for a fixed input or output amount,
905    /// `query_swap` solves for a swap that satisfies a higher-level [`SwapConstraint`],
906    /// such as a minimum execution price or a target post-trade pool price.
907    ///
908    /// The returned [`Swap`] describes the swap that best satisfies the given constraint
909    /// under the current protocol state, with **all protocol fees included**.
910    ///
911    /// # Semantics
912    ///
913    /// - The method is **read-only** and does not mutate internal state.
914    /// - All amounts and prices in the returned [`Swap`] are **fee-inclusive**, consistent with
915    ///   [`quote`] and [`marginal_price`].
916    /// - The constraint defines *what is solved for* (e.g. maximum trade size, target price),
917    ///   rather than supplying an explicit trade amount.
918    /// - Implementations may use analytical or numerical methods to satisfy the constraint, subject
919    ///   to the provided tolerances and bounds.
920    ///
921    /// # Supported constraints
922    ///
923    /// The behavior of this method is defined by the [`SwapConstraint`] provided in
924    /// `params`, including:
925    ///
926    /// - **Trade limit price**: computes the maximum executable trade size whose effective price
927    ///   (amount_out / amount_in) meets or exceeds a specified limit.
928    /// - **Pool target price**: computes the trade required to move the pool’s marginal price down
929    ///   to a target level, within a specified tolerance.
930    ///
931    /// Bounds on the search space (minimum and maximum `amount_in`) are respected when
932    /// provided.
933    ///
934    /// # Intended use
935    ///
936    /// Typical uses include:
937    /// - Price-impact-aware execution planning
938    /// - Strategy-driven routing (e.g. price-capped or price-targeting trades)
939    /// - Liquidity probing and pool sensitivity analysis
940    ///
941    /// # Errors
942    ///
943    /// Returns an error if:
944    /// - The constraint cannot be satisfied within the provided bounds or tolerances
945    /// - The token pair is not quotable
946    /// - The swap is undefined under the current protocol state
947    fn query_swap(&self, params: QuerySwapParams) -> SimulationResult<Swap>;
948
949    /// Applies a **protocol state delta**
950    ///
951    /// This method updates the internal protocol state of the quoter by applying the
952    /// incremental changes described by `params`, typically derived from an external
953    /// indexer or on-chain data source. It is used to keep the quoter’s local view of the
954    /// protocol state in sync with the latest block.
955    ///
956    /// # Semantics
957    ///
958    /// - The provided delta is assumed to represent a **valid, externally observed state
959    ///   transition** (e.g. from an indexer service) and is not re-validated as a swap.
960    /// - Calling this method **mutates internal state**; all subsequent quotes, prices, and limits
961    ///   will reflect the updated state.
962    /// - The transition is applied **incrementally** and is expected to be composable with previous
963    ///   deltas.
964    ///
965    /// # Intended use
966    ///
967    /// Typical uses include:
968    /// - Applying per-block protocol updates received from an indexer
969    /// - Advancing local state during historical replay or backfilling
970    /// - Keeping multiple quoters synchronized with chain state
971    ///
972    /// # Errors
973    ///
974    /// Returns an error if the delta cannot be applied to the current state (e.g. it is
975    /// incompatible, malformed, or violates protocol invariants).
976    fn delta_transition(&mut self, params: TransitionParams)
977        -> Result<Transition, TransitionError>;
978
979    /// Clones the protocol state as a trait object.
980    ///
981    /// This method enables cloning when the pool is used as a boxed trait object,
982    /// which is necessary for parallel simulations and state management.
983    ///
984    /// # Returns
985    /// A new boxed instance with the same state as this pool.
986    fn clone_box(&self) -> Box<dyn SwapQuoter>;
987
988    /// Attempts to cast this pool to an indicatively priced pool.
989    ///
990    /// This is used for RFQ (Request for Quote) protocols that provide
991    /// indicative pricing rather than deterministic calculations.
992    ///
993    /// # Returns
994    /// A reference to the `IndicativelyPriced` implementation, or an error
995    /// if this pool type doesn't support indicative pricing.
996    ///
997    /// # Default Implementation
998    /// Returns an error indicating that indicative pricing is not supported.
999    fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
1000        Err(SimulationError::FatalError("Pool State does not implement IndicativelyPriced".into()))
1001    }
1002
1003    #[deprecated(note = "ProtocolSim is deprecated. This method will be removed in v1.0.0")]
1004    fn to_protocol_sim(&self) -> Box<dyn ProtocolSim>;
1005}
1006
1007/// Testing extension trait for SwapQuoter implementations.
1008///
1009/// Provides additional methods needed for testing and validation
1010/// that are not part of the main SwapQuoter interface.
1011#[cfg(test)]
1012pub trait SwapQuoterTestExt {
1013    /// Compares this pool state with another for equality.
1014    ///
1015    /// This method is used in tests to verify that pool states
1016    /// are equivalent after various operations.
1017    ///
1018    /// # Arguments
1019    /// * `other` - Another SwapQuoter to compare against
1020    ///
1021    /// # Returns
1022    /// `true` if the pool states are equivalent, `false` otherwise.
1023    fn eq(&self, other: &dyn SwapQuoter) -> bool;
1024}