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}
74
75impl DecoderContext {
76    pub fn new() -> Self {
77        Self {
78            adapter_path: None,
79            vm_traces: None,
80            block_position: BlockPositionAssumption::default(),
81            live_override: None,
82        }
83    }
84
85    pub fn block_position_assumption(mut self, assumption: BlockPositionAssumption) -> Self {
86        self.block_position = assumption;
87        self
88    }
89
90    pub fn vm_adapter_path<S: Into<String>>(mut self, path: S) -> Self {
91        self.adapter_path = Some(path.into());
92        self
93    }
94
95    pub fn vm_traces(mut self, trace: bool) -> Self {
96        self.vm_traces = Some(trace);
97        self
98    }
99}
100
101impl Default for DecoderContext {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107/// ProtocolComponent struct represents the properties of a trading pair
108///
109/// # Fields
110///
111/// * `address`: String, the address of the trading pair
112/// * `tokens`: `Vec<ERC20Token>`, the tokens of the trading pair
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub struct ProtocolComponent {
115    #[deprecated(since = "0.73.0", note = "Use `id` instead")]
116    pub address: Bytes,
117    pub id: Bytes,
118    pub tokens: Vec<Token>,
119    pub protocol_system: String,
120    pub protocol_type_name: String,
121    pub chain: Chain,
122    pub contract_ids: Vec<Bytes>,
123    pub static_attributes: HashMap<String, Bytes>,
124    pub creation_tx: Bytes,
125    pub created_at: NaiveDateTime,
126}
127
128impl ProtocolComponent {
129    #[allow(deprecated)]
130    #[allow(clippy::too_many_arguments)]
131    pub fn new(
132        id: Bytes,
133        protocol_system: String,
134        protocol_type_name: String,
135        chain: Chain,
136        tokens: Vec<Token>,
137        contract_ids: Vec<Bytes>,
138        static_attributes: HashMap<String, Bytes>,
139        creation_tx: Bytes,
140        created_at: NaiveDateTime,
141    ) -> Self {
142        ProtocolComponent {
143            address: Default::default(),
144            id,
145            tokens,
146            protocol_system,
147            protocol_type_name,
148            chain,
149            contract_ids,
150            static_attributes,
151            creation_tx,
152            created_at,
153        }
154    }
155
156    pub fn from_with_tokens(
157        core_model: tycho_common::models::protocol::ProtocolComponent,
158        tokens: Vec<Token>,
159    ) -> Self {
160        let id = Bytes::from(core_model.id.as_str());
161        ProtocolComponent::new(
162            id.clone(),
163            core_model.protocol_system,
164            core_model.protocol_type_name,
165            core_model.chain,
166            tokens,
167            core_model.contract_addresses,
168            core_model.static_attributes,
169            core_model.creation_tx,
170            core_model.created_at,
171        )
172    }
173}
174
175impl From<ProtocolComponent> for tycho_common::models::protocol::ProtocolComponent {
176    fn from(component: ProtocolComponent) -> Self {
177        tycho_common::models::protocol::ProtocolComponent {
178            id: hex::encode(component.id),
179            protocol_system: component.protocol_system,
180            protocol_type_name: component.protocol_type_name,
181            chain: component.chain,
182            tokens: component
183                .tokens
184                .into_iter()
185                .map(|t| t.address)
186                .collect(),
187            static_attributes: component.static_attributes,
188            change: Default::default(),
189            creation_tx: component.creation_tx,
190            created_at: component.created_at,
191            contract_addresses: component.contract_ids,
192        }
193    }
194}
195
196pub trait TryFromWithBlock<T, H>
197where
198    H: HeaderLike,
199{
200    type Error;
201
202    fn try_from_with_header(
203        value: T,
204        block: H,
205        account_balances: &HashMap<Bytes, HashMap<Bytes, Bytes>>,
206        all_tokens: &HashMap<Bytes, Token>,
207        decoder_context: &DecoderContext,
208    ) -> impl Future<Output = Result<Self, Self::Error>> + Send + Sync
209    where
210        Self: Sized;
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct Update {
215    pub block_number_or_timestamp: u64,
216    /// True when this update is for a partial (pre-confirmation) block, false for full blocks.
217    #[serde(default)]
218    pub is_partial: bool,
219    /// Synchronization state per protocol
220    pub sync_states: HashMap<String, SynchronizerState>,
221    /// The new and updated states of this block.
222    /// VM-backed states that can't be serialized are silently skipped during
223    /// serialization and will be absent after a roundtrip.
224    #[serde(with = "crate::serde_helpers::protocol_states")]
225    pub states: HashMap<String, Box<dyn ProtocolSim>>,
226    /// The new pairs that were added in this block
227    pub new_pairs: HashMap<String, ProtocolComponent>,
228    /// The pairs that were removed in this block
229    pub removed_pairs: HashMap<String, ProtocolComponent>,
230}
231
232impl Update {
233    pub fn new(
234        block_number: u64,
235        states: HashMap<String, Box<dyn ProtocolSim>>,
236        new_pairs: HashMap<String, ProtocolComponent>,
237    ) -> Self {
238        Update {
239            block_number_or_timestamp: block_number,
240            is_partial: false,
241            sync_states: HashMap::new(),
242            states,
243            new_pairs,
244            removed_pairs: HashMap::new(),
245        }
246    }
247
248    pub fn set_is_partial(mut self, is_partial: bool) -> Self {
249        self.is_partial = is_partial;
250        self
251    }
252
253    pub fn set_removed_pairs(mut self, pairs: HashMap<String, ProtocolComponent>) -> Self {
254        self.removed_pairs = pairs;
255        self
256    }
257
258    pub fn set_sync_states(mut self, sync_states: HashMap<String, SynchronizerState>) -> Self {
259        self.sync_states = sync_states;
260        self
261    }
262
263    pub fn merge(mut self, other: Update) -> Self {
264        self.states.extend(other.states);
265        self.new_pairs.extend(other.new_pairs);
266        self.removed_pairs
267            .extend(other.removed_pairs);
268        self
269    }
270}