Skip to main content

tycho_simulation/protocol/
models.rs

1//! Pair Properties and ProtocolState
2//!
3//! This module contains the `ProtocolComponent` struct, which represents the
4//! properties of a trading pair. It also contains the `Pair` struct, which
5//! represents a trading pair with its properties and corresponding state.
6//!
7//! Additionally, it contains the `GetAmountOutResult` struct, which
8//! represents the result of getting the amount out of a trading pair.
9//!
10//! The `ProtocolComponent` struct has two fields: `address` and `tokens`.
11//! `address` is the address of the trading pair and `tokens` is a vector
12//! of `ERC20Token` representing the tokens of the trading pair.
13//!
14//! Generally this struct contains immutable properties of the pair. These
15//! are attributes that will never change - not even through governance.
16//!
17//! This is in contrast to `ProtocolState`, which includes ideally only
18//! attributes that can change.
19//!
20//! The `Pair` struct combines the former two: `ProtocolComponent` and
21//! `ProtocolState` into a single struct.
22//!
23//! # Note:
24//! It's worth emphasizing that although the term "pair" used in this
25//! module refers to a trading pair, it does not necessarily imply two
26//! tokens only. Some pairs might have more than two tokens.
27use std::{collections::HashMap, default::Default, future::Future};
28
29use chrono::NaiveDateTime;
30use serde::{Deserialize, Serialize};
31use tokio::sync::watch;
32use tycho_client::feed::{HeaderLike, SynchronizerState};
33use tycho_common::{
34    models::{token::Token, Chain},
35    simulation::protocol_sim::ProtocolSim,
36    Bytes,
37};
38
39use crate::evm::override_stream::OverrideSnapshot;
40
41/// What a quote may assume about the swap's position within its execution block, for protocols
42/// whose pricing depends on that position.
43#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum BlockPositionAssumption {
45    /// No assumption: price the swap for the least favourable position it could take, so the
46    /// output is never over-quoted. Loses fills where a better position would in fact have held.
47    #[default]
48    WorstCase,
49    /// Bet the swap lands before any other on the same pool. Better quotes wherever the protocol
50    /// favours that position; a lost bet surfaces as reverts or negative slippage at execution
51    /// time. Enable only if the submission path can realistically win that race.
52    First,
53}
54
55/// Context struct containing attributes for decoders
56///
57/// This struct can be extended to include additional attributes for other decoders in the future
58#[derive(Debug, Clone)]
59pub struct DecoderContext {
60    pub adapter_path: Option<String>,
61    pub vm_traces: Option<bool>,
62    /// What quotes may assume about the swap's position within its execution block.
63    ///
64    /// Only consumed by protocols that price the first swap of a block differently (currently
65    /// `aerodrome_slipstreams`). Once the pool has been touched in the execution block, position
66    /// is a known fact and the assumption has no effect.
67    pub block_position: BlockPositionAssumption,
68    /// Live per-block VM state override channel, wired into the pool at construction time.
69    ///
70    /// Set internally by the decoder from its registered override providers; not part of the
71    /// public API. External consumers never set this — overrides are fully handled by the library.
72    pub(crate) live_override: Option<watch::Receiver<OverrideSnapshot>>,
73    /// Chain the decoded components live on.
74    ///
75    /// Stamped by [`TychoStreamDecoder`](crate::evm::decoder::TychoStreamDecoder) at registration,
76    /// so it is `None` only for a context that never went through a decoder. Decoders whose
77    /// behaviour depends on the chain (currently the Uniswap V4 hook handler registry) reject the
78    /// snapshot when it is `None` rather than assume one.
79    pub chain: Option<Chain>,
80}
81
82impl DecoderContext {
83    pub fn new() -> Self {
84        Self {
85            adapter_path: None,
86            vm_traces: None,
87            block_position: BlockPositionAssumption::default(),
88            live_override: None,
89            chain: None,
90        }
91    }
92
93    /// Declares the chain the decoded components live on.
94    ///
95    /// Registering the context with a
96    /// [`TychoStreamDecoder`](crate::evm::decoder::TychoStreamDecoder) overrides whatever is
97    /// set here with the decoder's own chain.
98    pub fn chain(mut self, chain: Chain) -> Self {
99        self.chain = Some(chain);
100        self
101    }
102
103    pub fn block_position_assumption(mut self, assumption: BlockPositionAssumption) -> Self {
104        self.block_position = assumption;
105        self
106    }
107
108    pub fn vm_adapter_path<S: Into<String>>(mut self, path: S) -> Self {
109        self.adapter_path = Some(path.into());
110        self
111    }
112
113    pub fn vm_traces(mut self, trace: bool) -> Self {
114        self.vm_traces = Some(trace);
115        self
116    }
117}
118
119impl Default for DecoderContext {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125/// ProtocolComponent struct represents the properties of a trading pair
126///
127/// # Fields
128///
129/// * `address`: String, the address of the trading pair
130/// * `tokens`: `Vec<ERC20Token>`, the tokens of the trading pair
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct ProtocolComponent {
133    #[deprecated(since = "0.73.0", note = "Use `id` instead")]
134    pub address: Bytes,
135    pub id: Bytes,
136    pub tokens: Vec<Token>,
137    pub protocol_system: String,
138    pub protocol_type_name: String,
139    pub chain: Chain,
140    pub contract_ids: Vec<Bytes>,
141    pub static_attributes: HashMap<String, Bytes>,
142    pub creation_tx: Bytes,
143    pub created_at: NaiveDateTime,
144}
145
146impl ProtocolComponent {
147    #[allow(deprecated)]
148    #[allow(clippy::too_many_arguments)]
149    pub fn new(
150        id: Bytes,
151        protocol_system: String,
152        protocol_type_name: String,
153        chain: Chain,
154        tokens: Vec<Token>,
155        contract_ids: Vec<Bytes>,
156        static_attributes: HashMap<String, Bytes>,
157        creation_tx: Bytes,
158        created_at: NaiveDateTime,
159    ) -> Self {
160        ProtocolComponent {
161            address: Default::default(),
162            id,
163            tokens,
164            protocol_system,
165            protocol_type_name,
166            chain,
167            contract_ids,
168            static_attributes,
169            creation_tx,
170            created_at,
171        }
172    }
173
174    pub fn from_with_tokens(
175        core_model: tycho_common::models::protocol::ProtocolComponent,
176        tokens: Vec<Token>,
177    ) -> Self {
178        let id = Bytes::from(core_model.id.as_str());
179        ProtocolComponent::new(
180            id.clone(),
181            core_model.protocol_system,
182            core_model.protocol_type_name,
183            core_model.chain,
184            tokens,
185            core_model.contract_addresses,
186            core_model.static_attributes,
187            core_model.creation_tx,
188            core_model.created_at,
189        )
190    }
191}
192
193impl From<ProtocolComponent> for tycho_common::models::protocol::ProtocolComponent {
194    fn from(component: ProtocolComponent) -> Self {
195        tycho_common::models::protocol::ProtocolComponent {
196            id: hex::encode(component.id),
197            protocol_system: component.protocol_system,
198            protocol_type_name: component.protocol_type_name,
199            chain: component.chain,
200            tokens: component
201                .tokens
202                .into_iter()
203                .map(|t| t.address)
204                .collect(),
205            static_attributes: component.static_attributes,
206            change: Default::default(),
207            creation_tx: component.creation_tx,
208            created_at: component.created_at,
209            contract_addresses: component.contract_ids,
210        }
211    }
212}
213
214pub trait TryFromWithBlock<T, H>
215where
216    H: HeaderLike,
217{
218    type Error;
219
220    fn try_from_with_header(
221        value: T,
222        block: H,
223        account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
224        all_tokens: &HashMap<Bytes, Token>,
225        decoder_context: &DecoderContext,
226    ) -> impl Future<Output = Result<Self, Self::Error>> + Send + Sync
227    where
228        Self: Sized;
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct Update {
233    pub block_number_or_timestamp: u64,
234    /// True when this update is for a partial (pre-confirmation) block, false for full blocks.
235    #[serde(default)]
236    pub is_partial: bool,
237    /// Synchronization state per protocol
238    pub sync_states: HashMap<String, SynchronizerState>,
239    /// The new and updated states of this block.
240    /// VM-backed states that can't be serialized are silently skipped during
241    /// serialization and will be absent after a roundtrip.
242    #[serde(with = "crate::serde_helpers::protocol_states")]
243    pub states: HashMap<String, Box<dyn ProtocolSim>>,
244    /// The new pairs that were added in this block
245    pub new_pairs: HashMap<String, ProtocolComponent>,
246    /// The pairs that were removed in this block
247    pub removed_pairs: HashMap<String, ProtocolComponent>,
248}
249
250impl Update {
251    pub fn new(
252        block_number: u64,
253        states: HashMap<String, Box<dyn ProtocolSim>>,
254        new_pairs: HashMap<String, ProtocolComponent>,
255    ) -> Self {
256        Update {
257            block_number_or_timestamp: block_number,
258            is_partial: false,
259            sync_states: HashMap::new(),
260            states,
261            new_pairs,
262            removed_pairs: HashMap::new(),
263        }
264    }
265
266    pub fn set_is_partial(mut self, is_partial: bool) -> Self {
267        self.is_partial = is_partial;
268        self
269    }
270
271    pub fn set_removed_pairs(mut self, pairs: HashMap<String, ProtocolComponent>) -> Self {
272        self.removed_pairs = pairs;
273        self
274    }
275
276    pub fn set_sync_states(mut self, sync_states: HashMap<String, SynchronizerState>) -> Self {
277        self.sync_states = sync_states;
278        self
279    }
280
281    pub fn merge(mut self, other: Update) -> Self {
282        self.states.extend(other.states);
283        self.new_pairs.extend(other.new_pairs);
284        self.removed_pairs
285            .extend(other.removed_pairs);
286        self
287    }
288}