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}