tycho_common/simulation/
protocol_sim.rs

1use std::{any::Any, collections::HashMap, fmt};
2
3use num_bigint::BigUint;
4
5use crate::{
6    dto::ProtocolStateDelta,
7    models::token::Token,
8    simulation::{
9        errors::{SimulationError, TransitionError},
10        indicatively_priced::IndicativelyPriced,
11    },
12    Bytes,
13};
14
15#[derive(Default)]
16pub struct Balances {
17    pub component_balances: HashMap<String, HashMap<Bytes, Bytes>>,
18    pub account_balances: HashMap<Bytes, HashMap<Bytes, Bytes>>,
19}
20
21/// GetAmountOutResult struct represents the result of getting the amount out of a trading pair
22///
23/// # Fields
24///
25/// * `amount`: BigUint, the amount of the trading pair
26/// * `gas`: BigUint, the gas of the trading pair
27#[derive(Debug)]
28pub struct GetAmountOutResult {
29    pub amount: BigUint,
30    pub gas: BigUint,
31    pub new_state: Box<dyn ProtocolSim>,
32}
33
34impl GetAmountOutResult {
35    /// Constructs a new GetAmountOutResult struct with the given amount and gas
36    pub fn new(amount: BigUint, gas: BigUint, new_state: Box<dyn ProtocolSim>) -> Self {
37        GetAmountOutResult { amount, gas, new_state }
38    }
39
40    /// Aggregates the given GetAmountOutResult struct to the current one.
41    /// It updates the amount with the other's amount and adds the other's gas to the current one's
42    /// gas.
43    pub fn aggregate(&mut self, other: &Self) {
44        self.amount = other.amount.clone();
45        self.gas += &other.gas;
46    }
47}
48
49impl fmt::Display for GetAmountOutResult {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        write!(f, "amount = {}, gas = {}", self.amount, self.gas)
52    }
53}
54
55/// ProtocolSim trait
56/// This trait defines the methods that a protocol state must implement in order to be used
57/// in the trade simulation.
58pub trait ProtocolSim: fmt::Debug + Send + Sync + 'static {
59    /// Returns the fee of the protocol as ratio
60    ///
61    /// E.g. if the fee is 1%, the value returned would be 0.01.
62    fn fee(&self) -> f64;
63
64    /// Returns the protocol's current spot price of two tokens
65    ///
66    /// Currency pairs are meant to be compared against one another in
67    /// order to understand how much of the quote currency is required
68    /// to buy one unit of the base currency.
69    ///
70    /// E.g. if ETH/USD is trading at 1000, we need 1000 USD (quote)
71    /// to buy 1 ETH (base currency).
72    ///
73    /// # Arguments
74    ///
75    /// * `a` - Base Token: refers to the token that is the quantity of a pair. For the pair
76    ///   BTC/USDT, BTC would be the base asset.
77    /// * `b` - Quote Token: refers to the token that is the price of a pair. For the symbol
78    ///   BTC/USDT, USDT would be the quote asset.
79    fn spot_price(&self, base: &Token, quote: &Token) -> Result<f64, SimulationError>;
80
81    /// Returns the amount out given an amount in and input/output tokens.
82    ///
83    /// # Arguments
84    ///
85    /// * `amount_in` - The amount in of the input token.
86    /// * `token_in` - The input token ERC20 token.
87    /// * `token_out` - The output token ERC20 token.
88    ///
89    /// # Returns
90    ///
91    /// A `Result` containing a `GetAmountOutResult` struct on success or a
92    ///  `SimulationError` on failure.
93    fn get_amount_out(
94        &self,
95        amount_in: BigUint,
96        token_in: &Token,
97        token_out: &Token,
98    ) -> Result<GetAmountOutResult, SimulationError>;
99
100    /// Computes the maximum amount that can be traded between two tokens.
101    ///
102    /// This function calculates the maximum possible trade amount between two tokens,
103    /// taking into account the protocol's specific constraints and mechanics.
104    /// The implementation details vary by protocol - for example:
105    /// - For constant product AMMs (like Uniswap V2), this is based on available reserves
106    /// - For concentrated liquidity AMMs (like Uniswap V3), this considers liquidity across tick
107    ///   ranges
108    ///
109    /// Note: if there are no limits, the returned amount will be a "soft" limit,
110    ///       meaning that the actual amount traded could be higher but it's advised to not
111    ///       exceed it.
112    ///
113    /// # Arguments
114    /// * `sell_token` - The address of the token being sold
115    /// * `buy_token` - The address of the token being bought
116    ///
117    /// # Returns
118    /// * `Ok((Option<BigUint>, Option<BigUint>))` - A tuple containing:
119    ///   - First element: The maximum input amount
120    ///   - Second element: The maximum output amount
121    ///
122    /// This means that for `let res = get_limits(...)` the amount input domain for `get_amount_out`
123    /// would be `[0, res.0]` and the amount input domain for `get_amount_in` would be `[0,
124    /// res.1]`
125    ///
126    /// * `Err(SimulationError)` - If any unexpected error occurs
127    fn get_limits(
128        &self,
129        sell_token: Bytes,
130        buy_token: Bytes,
131    ) -> Result<(BigUint, BigUint), SimulationError>;
132
133    /// Decodes and applies a protocol state delta to the state
134    ///
135    /// Will error if the provided delta is missing any required attributes or if any of the
136    /// attribute values cannot be decoded.
137    ///
138    /// # Arguments
139    ///
140    /// * `delta` - A `ProtocolStateDelta` from the tycho indexer
141    ///
142    /// # Returns
143    ///
144    /// * `Result<(), TransitionError<String>>` - A `Result` containing `()` on success or a
145    ///   `TransitionError` on failure.
146    fn delta_transition(
147        &mut self,
148        delta: ProtocolStateDelta,
149        tokens: &HashMap<Bytes, Token>,
150        balances: &Balances,
151    ) -> Result<(), TransitionError<String>>;
152
153    /// Clones the protocol state as a trait object.
154    /// This allows the state to be cloned when it is being used as a `Box<dyn ProtocolSim>`.
155    fn clone_box(&self) -> Box<dyn ProtocolSim>;
156
157    /// Allows downcasting of the trait object to its underlying type.
158    fn as_any(&self) -> &dyn Any;
159
160    /// Allows downcasting of the trait object to its mutable underlying type.
161    fn as_any_mut(&mut self) -> &mut dyn Any;
162
163    /// Compares two protocol states for equality.
164    /// This method must be implemented to define how two protocol states are considered equal
165    /// (used for tests).
166    fn eq(&self, other: &dyn ProtocolSim) -> bool;
167
168    /// Cast as IndicativelyPriced. This is necessary for RFQ protocols
169    fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
170        Err(SimulationError::FatalError("Pool State does not implement IndicativelyPriced".into()))
171    }
172}
173
174impl Clone for Box<dyn ProtocolSim> {
175    fn clone(&self) -> Box<dyn ProtocolSim> {
176        self.clone_box()
177    }
178}