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}