1use std::collections::{hash_map::Entry, HashMap, HashSet};
2
3use chrono::NaiveDateTime;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7use crate::{
8 models::{
9 blockchain::Transaction, Address, AttrStoreKey, Balance, Chain, ChangeType, ComponentId,
10 DeltaError, StoreVal, TxHash,
11 },
12 Bytes,
13};
14
15#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
27pub struct ProtocolComponent {
28 pub id: ComponentId,
29 pub protocol_system: String,
30 pub protocol_type_name: String,
31 pub chain: Chain,
32 pub tokens: Vec<Address>,
33 pub contract_addresses: Vec<Address>,
34 pub static_attributes: HashMap<AttrStoreKey, StoreVal>,
35 pub change: ChangeType,
36 pub creation_tx: TxHash,
37 pub created_at: NaiveDateTime,
38}
39
40impl ProtocolComponent {
41 #[allow(clippy::too_many_arguments)]
42 pub fn new(
43 id: &str,
44 protocol_system: &str,
45 protocol_type_name: &str,
46 chain: Chain,
47 tokens: Vec<Address>,
48 contract_addresses: Vec<Address>,
49 static_attributes: HashMap<AttrStoreKey, StoreVal>,
50 change: ChangeType,
51 creation_tx: TxHash,
52 created_at: NaiveDateTime,
53 ) -> Self {
54 Self {
55 id: id.to_string(),
56 protocol_system: protocol_system.to_string(),
57 protocol_type_name: protocol_type_name.to_string(),
58 chain,
59 tokens,
60 contract_addresses,
61 static_attributes,
62 change,
63 creation_tx,
64 created_at,
65 }
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
70pub struct ProtocolComponentState {
71 pub component_id: ComponentId,
72 pub attributes: HashMap<AttrStoreKey, StoreVal>,
73 pub balances: HashMap<Address, Balance>,
75}
76
77impl ProtocolComponentState {
78 pub fn new(
79 component_id: &str,
80 attributes: HashMap<AttrStoreKey, StoreVal>,
81 balances: HashMap<Address, Balance>,
82 ) -> Self {
83 Self { component_id: component_id.to_string(), attributes, balances }
84 }
85
86 pub fn apply_state_delta(
90 &mut self,
91 delta: &ProtocolComponentStateDelta,
92 ) -> Result<(), DeltaError> {
93 if self.component_id != delta.component_id {
94 return Err(DeltaError::IdMismatch(
95 self.component_id.clone(),
96 delta.component_id.clone(),
97 ));
98 }
99 self.attributes
100 .extend(delta.updated_attributes.clone());
101
102 self.attributes
103 .retain(|attr, _| !delta.deleted_attributes.contains(attr));
104
105 Ok(())
106 }
107
108 pub fn apply_balance_delta(
112 &mut self,
113 delta: &HashMap<Bytes, ComponentBalance>,
114 ) -> Result<(), DeltaError> {
115 self.balances.extend(
116 delta
117 .iter()
118 .map(|(k, v)| (k.clone(), v.balance.clone())),
119 );
120
121 Ok(())
122 }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct ProtocolComponentStateDelta {
127 pub component_id: ComponentId,
128 pub updated_attributes: HashMap<AttrStoreKey, StoreVal>,
129 pub deleted_attributes: HashSet<AttrStoreKey>,
130}
131
132impl ProtocolComponentStateDelta {
133 pub fn new(
134 component_id: &str,
135 updated_attributes: HashMap<AttrStoreKey, StoreVal>,
136 deleted_attributes: HashSet<AttrStoreKey>,
137 ) -> Self {
138 Self { component_id: component_id.to_string(), updated_attributes, deleted_attributes }
139 }
140
141 pub fn merge(&mut self, other: ProtocolComponentStateDelta) -> Result<(), String> {
153 if self.component_id != other.component_id {
154 return Err(format!(
155 "Can't merge ProtocolStates from differing identities; Expected {}, got {}",
156 self.component_id, other.component_id
157 ));
158 }
159 for attr in &other.deleted_attributes {
160 self.updated_attributes.remove(attr);
161 }
162 for attr in other.updated_attributes.keys() {
163 self.deleted_attributes.remove(attr);
164 }
165 self.updated_attributes
166 .extend(other.updated_attributes);
167 self.deleted_attributes
168 .extend(other.deleted_attributes);
169 Ok(())
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
174pub struct ComponentBalance {
175 pub token: Address,
176 pub balance: Balance,
177 pub balance_float: f64,
178 pub modify_tx: TxHash,
179 pub component_id: ComponentId,
180}
181
182impl ComponentBalance {
183 pub fn new(
184 token: Address,
185 new_balance: Balance,
186 balance_float: f64,
187 modify_tx: TxHash,
188 component_id: &str,
189 ) -> Self {
190 Self {
191 token,
192 balance: new_balance,
193 balance_float,
194 modify_tx,
195 component_id: component_id.to_string(),
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
204pub struct QualityRange {
205 pub min: Option<i32>,
206 pub max: Option<i32>,
207}
208
209impl QualityRange {
210 pub fn new(min: i32, max: i32) -> Self {
211 Self { min: Some(min), max: Some(max) }
212 }
213
214 pub fn min_only(min: i32) -> Self {
215 Self { min: Some(min), max: None }
216 }
217
218 #[allow(non_snake_case)]
219 pub fn None() -> Self {
220 Self { min: None, max: None }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Default)]
226pub struct ProtocolChangesWithTx {
227 pub new_protocol_components: HashMap<ComponentId, ProtocolComponent>,
228 pub protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
229 pub balance_changes: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
230 pub tx: Transaction,
231}
232
233impl ProtocolChangesWithTx {
234 pub fn merge(&mut self, other: ProtocolChangesWithTx) -> Result<(), String> {
251 if self.tx.block_hash != other.tx.block_hash {
252 return Err(format!(
253 "Can't merge ProtocolStates from different blocks: {:x} != {:x}",
254 self.tx.block_hash, other.tx.block_hash,
255 ));
256 }
257 if self.tx.hash == other.tx.hash {
258 return Err(format!(
259 "Can't merge ProtocolStates from the same transaction: {:x}",
260 self.tx.hash
261 ));
262 }
263 if self.tx.index > other.tx.index {
264 return Err(format!(
265 "Can't merge ProtocolStates with lower transaction index: {} > {}",
266 self.tx.index, other.tx.index
267 ));
268 }
269 self.tx = other.tx;
270 for (key, value) in other.protocol_states {
272 match self.protocol_states.entry(key) {
273 Entry::Occupied(mut entry) => {
274 entry.get_mut().merge(value)?;
275 }
276 Entry::Vacant(entry) => {
277 entry.insert(value);
278 }
279 }
280 }
281
282 for (component_id, balance_changes) in other.balance_changes {
284 let token_balances = self
285 .balance_changes
286 .entry(component_id)
287 .or_default();
288 for (token, balance) in balance_changes {
289 token_balances.insert(token, balance);
290 }
291 }
292
293 for (key, value) in other.new_protocol_components {
297 match self.new_protocol_components.entry(key) {
298 Entry::Occupied(mut entry) => {
299 warn!(
300 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
301 entry.get().id
302 );
303 entry.insert(value);
304 }
305 Entry::Vacant(entry) => {
306 entry.insert(value);
307 }
308 }
309 }
310
311 Ok(())
312 }
313}
314
315#[cfg(test)]
316mod test {
317 use rstest::rstest;
318
319 use super::*;
320 use crate::models::blockchain::fixtures as block_fixtures;
321
322 const HASH_256_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
323 const HASH_256_1: &str = "0x0000000000000000000000000000000000000000000000000000000000000001";
324
325 fn create_state(id: String) -> ProtocolComponentStateDelta {
326 let attributes1: HashMap<String, Bytes> = vec![
327 ("reserve1".to_owned(), Bytes::from(1000u64).lpad(32, 0)),
328 ("reserve2".to_owned(), Bytes::from(500u64).lpad(32, 0)),
329 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
330 ]
331 .into_iter()
332 .collect();
333 ProtocolComponentStateDelta {
334 component_id: id,
335 updated_attributes: attributes1,
336 deleted_attributes: HashSet::new(),
337 }
338 }
339
340 #[test]
341 fn test_merge_protocol_state_updates() {
342 let mut state_1 = create_state("State1".to_owned());
343 state_1
344 .updated_attributes
345 .insert("to_be_removed".to_owned(), Bytes::from(1u64).lpad(32, 0));
346 state_1.deleted_attributes = vec!["to_add_back".to_owned()]
347 .into_iter()
348 .collect();
349
350 let attributes2: HashMap<String, Bytes> = vec![
351 ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
352 ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
353 ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
354 ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
355 ]
356 .into_iter()
357 .collect();
358 let del_attributes2: HashSet<String> = vec!["to_be_removed".to_owned()]
359 .into_iter()
360 .collect();
361 let mut state_2 = create_state("State1".to_owned());
362 state_2.updated_attributes = attributes2;
363 state_2.deleted_attributes = del_attributes2;
364
365 let res = state_1.merge(state_2);
366
367 assert!(res.is_ok());
368 let expected_attributes: HashMap<String, Bytes> = vec![
369 ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
370 ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
371 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
372 ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
373 ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
374 ]
375 .into_iter()
376 .collect();
377 assert_eq!(state_1.updated_attributes, expected_attributes);
378 let expected_del_attributes: HashSet<String> = vec!["to_be_removed".to_owned()]
379 .into_iter()
380 .collect();
381 assert_eq!(state_1.deleted_attributes, expected_del_attributes);
382 }
383
384 fn protocol_state_with_tx() -> ProtocolChangesWithTx {
385 let state_1 = create_state("State1".to_owned());
386 let state_2 = create_state("State2".to_owned());
387 let states: HashMap<String, ProtocolComponentStateDelta> =
388 vec![(state_1.component_id.clone(), state_1), (state_2.component_id.clone(), state_2)]
389 .into_iter()
390 .collect();
391 ProtocolChangesWithTx {
392 protocol_states: states,
393 tx: block_fixtures::transaction01(),
394 ..Default::default()
395 }
396 }
397
398 #[test]
399 fn test_merge_protocol_state_update_with_tx() {
400 let mut base_state = protocol_state_with_tx();
401
402 let new_attributes: HashMap<String, Bytes> = vec![
403 ("reserve1".to_owned(), Bytes::from(600u64).lpad(32, 0)),
404 ("new_attribute".to_owned(), Bytes::from(10u64).lpad(32, 0)),
405 ]
406 .into_iter()
407 .collect();
408 let new_tx = block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 11);
409 let new_states: HashMap<String, ProtocolComponentStateDelta> = vec![(
410 "State1".to_owned(),
411 ProtocolComponentStateDelta {
412 component_id: "State1".to_owned(),
413 updated_attributes: new_attributes,
414 deleted_attributes: HashSet::new(),
415 },
416 )]
417 .into_iter()
418 .collect();
419
420 let tx_update =
421 ProtocolChangesWithTx { protocol_states: new_states, tx: new_tx, ..Default::default() };
422
423 let res = base_state.merge(tx_update);
424
425 assert!(res.is_ok());
426 assert_eq!(base_state.protocol_states.len(), 2);
427 let expected_attributes: HashMap<String, Bytes> = vec![
428 ("reserve1".to_owned(), Bytes::from(600u64).lpad(32, 0)),
429 ("reserve2".to_owned(), Bytes::from(500u64).lpad(32, 0)),
430 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
431 ("new_attribute".to_owned(), Bytes::from(10u64).lpad(32, 0)),
432 ]
433 .into_iter()
434 .collect();
435 assert_eq!(
436 base_state
437 .protocol_states
438 .get("State1")
439 .unwrap()
440 .updated_attributes,
441 expected_attributes
442 );
443 }
444
445 #[rstest]
446 #[case::diff_block(
447 block_fixtures::create_transaction(HASH_256_1, HASH_256_1, 11),
448 Err(format ! ("Can't merge ProtocolStates from different blocks: {:x} != {}", Bytes::zero(32), HASH_256_1))
449 )]
450 #[case::same_tx(
451 block_fixtures::create_transaction(HASH_256_0, HASH_256_0, 11),
452 Err(format ! ("Can't merge ProtocolStates from the same transaction: {:x}", Bytes::zero(32)))
453 )]
454 #[case::lower_idx(
455 block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 1),
456 Err("Can't merge ProtocolStates with lower transaction index: 10 > 1".to_owned())
457 )]
458 fn test_merge_pool_state_update_with_tx_errors(
459 #[case] tx: Transaction,
460 #[case] exp: Result<(), String>,
461 ) {
462 let mut base_state = protocol_state_with_tx();
463
464 let mut new_state = protocol_state_with_tx();
465 new_state.tx = tx;
466
467 let res = base_state.merge(new_state);
468
469 assert_eq!(res, exp);
470 }
471
472 #[test]
473 fn test_merge_protocol_state_update_wrong_id() {
474 let mut state1 = create_state("State1".to_owned());
475 let state2 = create_state("State2".to_owned());
476
477 let res = state1.merge(state2);
478
479 assert_eq!(
480 res,
481 Err(
482 "Can't merge ProtocolStates from differing identities; Expected State1, got State2"
483 .to_owned()
484 )
485 );
486 }
487}