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