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