Skip to main content

tycho_common/models/
protocol.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::Arc,
4};
5
6use chrono::NaiveDateTime;
7use deepsize::{Context, DeepSizeOf};
8use num_bigint::BigUint;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12    models::{
13        token::Token, Address, AttrStoreKey, Balance, Chain, ChangeType, ComponentId, MergeError,
14        StoreVal, TxHash,
15    },
16    Bytes,
17};
18
19/// `ProtocolComponent` provides detailed descriptions of a component of a protocol,
20/// for example, swap pools that enables the exchange of two tokens.
21///
22/// A `ProtocolComponent` can be associated with an `Account`, and it has an identifier (`id`) that
23/// can be either the on-chain address or a custom one. It belongs to a specific `ProtocolSystem`
24/// and has a `ProtocolTypeID` that associates it with a `ProtocolType` that describes its behaviour
25/// e.g., swap, lend, bridge. The component is associated with a specific `Chain` and holds
26/// information about tradable tokens, related contract IDs, and static attributes.
27///
28/// Every values of a `ProtocolComponent` must be static, they can't ever be changed after creation.
29/// The dynamic values associated to a component must be given using `ProtocolComponentState`.
30#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
31pub struct ProtocolComponent<Token: Into<Address> + Clone = Address> {
32    pub id: ComponentId,
33    pub protocol_system: String,
34    pub protocol_type_name: String,
35    pub chain: Chain,
36    pub tokens: Vec<Token>,
37    pub contract_addresses: Vec<Address>,
38    pub static_attributes: HashMap<AttrStoreKey, StoreVal>,
39    pub change: ChangeType,
40    pub creation_tx: TxHash,
41    pub created_at: NaiveDateTime,
42}
43
44impl<T> ProtocolComponent<T>
45where
46    T: Into<Address> + Clone,
47{
48    #[allow(clippy::too_many_arguments)]
49    pub fn new(
50        id: &str,
51        protocol_system: &str,
52        protocol_type_name: &str,
53        chain: Chain,
54        tokens: Vec<T>,
55        contract_addresses: Vec<Address>,
56        static_attributes: HashMap<AttrStoreKey, StoreVal>,
57        change: ChangeType,
58        creation_tx: TxHash,
59        created_at: NaiveDateTime,
60    ) -> Self {
61        Self {
62            id: id.to_string(),
63            protocol_system: protocol_system.to_string(),
64            protocol_type_name: protocol_type_name.to_string(),
65            chain,
66            tokens,
67            contract_addresses,
68            static_attributes,
69            change,
70            creation_tx,
71            created_at,
72        }
73    }
74}
75
76impl ProtocolComponent<Arc<Token>> {
77    pub fn get_token(&self, address: &Address) -> Option<Arc<Token>> {
78        self.tokens
79            .iter()
80            .find(|t| &t.address == address)
81            .map(Arc::clone)
82    }
83}
84
85impl DeepSizeOf for ProtocolComponent {
86    fn deep_size_of_children(&self, ctx: &mut Context) -> usize {
87        self.id.deep_size_of_children(ctx) +
88            self.protocol_system
89                .deep_size_of_children(ctx) +
90            self.protocol_type_name
91                .deep_size_of_children(ctx) +
92            self.chain.deep_size_of_children(ctx) +
93            self.tokens.deep_size_of_children(ctx) +
94            self.contract_addresses
95                .deep_size_of_children(ctx) +
96            self.static_attributes
97                .deep_size_of_children(ctx) +
98            self.change.deep_size_of_children(ctx) +
99            self.creation_tx
100                .deep_size_of_children(ctx)
101    }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub struct ProtocolComponentState {
106    pub component_id: ComponentId,
107    pub attributes: HashMap<AttrStoreKey, StoreVal>,
108    // used during snapshots retrieval by the gateway
109    pub balances: HashMap<Address, Balance>,
110}
111
112impl ProtocolComponentState {
113    pub fn new(
114        component_id: &str,
115        attributes: HashMap<AttrStoreKey, StoreVal>,
116        balances: HashMap<Address, Balance>,
117    ) -> Self {
118        Self { component_id: component_id.to_string(), attributes, balances }
119    }
120
121    /// Applies state deltas to this state.
122    ///
123    /// This method assumes that the passed delta is "newer" than the current state.
124    pub fn apply_state_delta(
125        &mut self,
126        delta: &ProtocolComponentStateDelta,
127    ) -> Result<(), MergeError> {
128        if self.component_id != delta.component_id {
129            return Err(MergeError::IdMismatch(
130                "ProtocolComponentStates".to_string(),
131                self.component_id.clone(),
132                delta.component_id.clone(),
133            ));
134        }
135        self.attributes
136            .extend(delta.updated_attributes.clone());
137
138        self.attributes
139            .retain(|attr, _| !delta.deleted_attributes.contains(attr));
140
141        Ok(())
142    }
143
144    /// Applies balance deltas to this state.
145    ///
146    /// This method assumes that the passed delta is "newer" than the current state.
147    pub fn apply_balance_delta(
148        &mut self,
149        delta: &HashMap<Bytes, ComponentBalance>,
150    ) -> Result<(), MergeError> {
151        self.balances.extend(
152            delta
153                .iter()
154                .map(|(k, v)| (k.clone(), v.balance.clone())),
155        );
156
157        Ok(())
158    }
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
162pub struct ProtocolComponentStateDelta {
163    pub component_id: ComponentId,
164    pub updated_attributes: HashMap<AttrStoreKey, StoreVal>,
165    pub deleted_attributes: HashSet<AttrStoreKey>,
166}
167
168impl ProtocolComponentStateDelta {
169    pub fn new(
170        component_id: &str,
171        updated_attributes: HashMap<AttrStoreKey, StoreVal>,
172        deleted_attributes: HashSet<AttrStoreKey>,
173    ) -> Self {
174        Self { component_id: component_id.to_string(), updated_attributes, deleted_attributes }
175    }
176
177    /// Merges this update with another one.
178    ///
179    /// The method combines two `ProtocolComponentStateDelta` instances if they are for the same
180    /// protocol component.
181    ///
182    /// NB: It is assumed that `other` is a more recent update than `self` is and the two are
183    /// combined accordingly.
184    ///
185    /// # Errors
186    /// This method will return `CoreError::MergeError` if any of the above
187    /// conditions is violated.
188    pub fn merge(&mut self, other: ProtocolComponentStateDelta) -> Result<(), MergeError> {
189        if self.component_id != other.component_id {
190            return Err(MergeError::IdMismatch(
191                "ProtocolComponentStateDeltas".to_string(),
192                self.component_id.clone(),
193                other.component_id.clone(),
194            ));
195        }
196        for attr in &other.deleted_attributes {
197            self.updated_attributes.remove(attr);
198        }
199        for attr in other.updated_attributes.keys() {
200            self.deleted_attributes.remove(attr);
201        }
202        self.updated_attributes
203            .extend(other.updated_attributes);
204        self.deleted_attributes
205            .extend(other.deleted_attributes);
206        Ok(())
207    }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
211pub struct ComponentBalance {
212    pub token: Address,
213    pub balance: Balance,
214    pub balance_float: f64,
215    pub modify_tx: TxHash,
216    pub component_id: ComponentId,
217}
218
219impl ComponentBalance {
220    pub fn new(
221        token: Address,
222        new_balance: Balance,
223        balance_float: f64,
224        modify_tx: TxHash,
225        component_id: &str,
226    ) -> Self {
227        Self {
228            token,
229            balance: new_balance,
230            balance_float,
231            modify_tx,
232            component_id: component_id.to_string(),
233        }
234    }
235}
236
237/// Token quality range filter
238///
239/// The quality range is considered inclusive and used as a filter, will be applied as such.
240#[derive(Debug, Clone)]
241pub struct QualityRange {
242    pub min: Option<i32>,
243    pub max: Option<i32>,
244}
245
246impl QualityRange {
247    pub fn new(min: i32, max: i32) -> Self {
248        Self { min: Some(min), max: Some(max) }
249    }
250
251    pub fn min_only(min: i32) -> Self {
252        Self { min: Some(min), max: None }
253    }
254
255    #[allow(non_snake_case)]
256    pub fn None() -> Self {
257        Self { min: None, max: None }
258    }
259}
260
261pub struct GetAmountOutParams {
262    pub amount_in: BigUint,
263    pub token_in: Bytes,
264    pub token_out: Bytes,
265    pub sender: Bytes,
266    pub receiver: Bytes,
267}
268
269#[cfg(test)]
270mod test {
271    use super::*;
272
273    fn create_state(id: String) -> ProtocolComponentStateDelta {
274        let attributes1: HashMap<String, Bytes> = vec![
275            ("reserve1".to_owned(), Bytes::from(1000u64).lpad(32, 0)),
276            ("reserve2".to_owned(), Bytes::from(500u64).lpad(32, 0)),
277            ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
278        ]
279        .into_iter()
280        .collect();
281        ProtocolComponentStateDelta {
282            component_id: id,
283            updated_attributes: attributes1,
284            deleted_attributes: HashSet::new(),
285        }
286    }
287
288    #[test]
289    fn test_merge_protocol_state_updates() {
290        let mut state_1 = create_state("State1".to_owned());
291        state_1
292            .updated_attributes
293            .insert("to_be_removed".to_owned(), Bytes::from(1u64).lpad(32, 0));
294        state_1.deleted_attributes = vec!["to_add_back".to_owned()]
295            .into_iter()
296            .collect();
297
298        let attributes2: HashMap<String, Bytes> = vec![
299            ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
300            ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
301            ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
302            ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
303        ]
304        .into_iter()
305        .collect();
306        let del_attributes2: HashSet<String> = vec!["to_be_removed".to_owned()]
307            .into_iter()
308            .collect();
309        let mut state_2 = create_state("State1".to_owned());
310        state_2.updated_attributes = attributes2;
311        state_2.deleted_attributes = del_attributes2;
312
313        let res = state_1.merge(state_2);
314
315        assert!(res.is_ok());
316        let expected_attributes: HashMap<String, Bytes> = vec![
317            ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
318            ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
319            ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
320            ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
321            ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
322        ]
323        .into_iter()
324        .collect();
325        assert_eq!(state_1.updated_attributes, expected_attributes);
326        let expected_del_attributes: HashSet<String> = vec!["to_be_removed".to_owned()]
327            .into_iter()
328            .collect();
329        assert_eq!(state_1.deleted_attributes, expected_del_attributes);
330    }
331
332    #[test]
333    fn test_merge_protocol_state_update_wrong_id() {
334        let mut state1 = create_state("State1".to_owned());
335        let state2 = create_state("State2".to_owned());
336
337        let res = state1.merge(state2);
338
339        assert_eq!(
340            res,
341            Err(MergeError::IdMismatch(
342                "ProtocolComponentStateDeltas".to_string(),
343                "State1".to_string(),
344                "State2".to_string(),
345            ))
346        );
347    }
348}