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 MergeError, 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<(), MergeError> {
93 if self.component_id != delta.component_id {
94 return Err(MergeError::IdMismatch(
95 "ProtocolComponentStates".to_string(),
96 self.component_id.clone(),
97 delta.component_id.clone(),
98 ));
99 }
100 self.attributes
101 .extend(delta.updated_attributes.clone());
102
103 self.attributes
104 .retain(|attr, _| !delta.deleted_attributes.contains(attr));
105
106 Ok(())
107 }
108
109 pub fn apply_balance_delta(
113 &mut self,
114 delta: &HashMap<Bytes, ComponentBalance>,
115 ) -> Result<(), MergeError> {
116 self.balances.extend(
117 delta
118 .iter()
119 .map(|(k, v)| (k.clone(), v.balance.clone())),
120 );
121
122 Ok(())
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
127pub struct ProtocolComponentStateDelta {
128 pub component_id: ComponentId,
129 pub updated_attributes: HashMap<AttrStoreKey, StoreVal>,
130 pub deleted_attributes: HashSet<AttrStoreKey>,
131}
132
133impl ProtocolComponentStateDelta {
134 pub fn new(
135 component_id: &str,
136 updated_attributes: HashMap<AttrStoreKey, StoreVal>,
137 deleted_attributes: HashSet<AttrStoreKey>,
138 ) -> Self {
139 Self { component_id: component_id.to_string(), updated_attributes, deleted_attributes }
140 }
141
142 pub fn merge(&mut self, other: ProtocolComponentStateDelta) -> Result<(), MergeError> {
154 if self.component_id != other.component_id {
155 return Err(MergeError::IdMismatch(
156 "ProtocolComponentStateDeltas".to_string(),
157 self.component_id.clone(),
158 other.component_id.clone(),
159 ));
160 }
161 for attr in &other.deleted_attributes {
162 self.updated_attributes.remove(attr);
163 }
164 for attr in other.updated_attributes.keys() {
165 self.deleted_attributes.remove(attr);
166 }
167 self.updated_attributes
168 .extend(other.updated_attributes);
169 self.deleted_attributes
170 .extend(other.deleted_attributes);
171 Ok(())
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
176pub struct ComponentBalance {
177 pub token: Address,
178 pub balance: Balance,
179 pub balance_float: f64,
180 pub modify_tx: TxHash,
181 pub component_id: ComponentId,
182}
183
184impl ComponentBalance {
185 pub fn new(
186 token: Address,
187 new_balance: Balance,
188 balance_float: f64,
189 modify_tx: TxHash,
190 component_id: &str,
191 ) -> Self {
192 Self {
193 token,
194 balance: new_balance,
195 balance_float,
196 modify_tx,
197 component_id: component_id.to_string(),
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
206pub struct QualityRange {
207 pub min: Option<i32>,
208 pub max: Option<i32>,
209}
210
211impl QualityRange {
212 pub fn new(min: i32, max: i32) -> Self {
213 Self { min: Some(min), max: Some(max) }
214 }
215
216 pub fn min_only(min: i32) -> Self {
217 Self { min: Some(min), max: None }
218 }
219
220 #[allow(non_snake_case)]
221 pub fn None() -> Self {
222 Self { min: None, max: None }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Default)]
228pub struct ProtocolChangesWithTx {
229 pub new_protocol_components: HashMap<ComponentId, ProtocolComponent>,
230 pub protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
231 pub balance_changes: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
232 pub tx: Transaction,
233}
234
235impl ProtocolChangesWithTx {
236 pub fn merge(&mut self, other: ProtocolChangesWithTx) -> Result<(), MergeError> {
253 if self.tx.block_hash != other.tx.block_hash {
254 return Err(MergeError::BlockMismatch(
255 "ProtocolChangesWithTx".to_string(),
256 self.tx.block_hash.clone(),
257 other.tx.block_hash,
258 ));
259 }
260 if self.tx.hash == other.tx.hash {
261 return Err(MergeError::SameTransaction(
262 "ProtocolChangesWithTx".to_string(),
263 other.tx.hash,
264 ));
265 }
266 if self.tx.index > other.tx.index {
267 return Err(MergeError::TransactionOrderError(
268 "ProtocolChangesWithTx".to_string(),
269 self.tx.index,
270 other.tx.index,
271 ));
272 }
273 self.tx = other.tx;
274 for (key, value) in other.protocol_states {
276 match self.protocol_states.entry(key) {
277 Entry::Occupied(mut entry) => {
278 entry.get_mut().merge(value)?;
279 }
280 Entry::Vacant(entry) => {
281 entry.insert(value);
282 }
283 }
284 }
285
286 for (component_id, balance_changes) in other.balance_changes {
288 let token_balances = self
289 .balance_changes
290 .entry(component_id)
291 .or_default();
292 for (token, balance) in balance_changes {
293 token_balances.insert(token, balance);
294 }
295 }
296
297 for (key, value) in other.new_protocol_components {
301 match self.new_protocol_components.entry(key) {
302 Entry::Occupied(mut entry) => {
303 warn!(
304 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
305 entry.get().id
306 );
307 entry.insert(value);
308 }
309 Entry::Vacant(entry) => {
310 entry.insert(value);
311 }
312 }
313 }
314
315 Ok(())
316 }
317}
318
319#[cfg(test)]
320mod test {
321 use rstest::rstest;
322
323 use super::*;
324 use crate::models::blockchain::fixtures as block_fixtures;
325
326 const HASH_256_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
327 const HASH_256_1: &str = "0x0000000000000000000000000000000000000000000000000000000000000001";
328
329 fn create_state(id: String) -> ProtocolComponentStateDelta {
330 let attributes1: HashMap<String, Bytes> = vec![
331 ("reserve1".to_owned(), Bytes::from(1000u64).lpad(32, 0)),
332 ("reserve2".to_owned(), Bytes::from(500u64).lpad(32, 0)),
333 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
334 ]
335 .into_iter()
336 .collect();
337 ProtocolComponentStateDelta {
338 component_id: id,
339 updated_attributes: attributes1,
340 deleted_attributes: HashSet::new(),
341 }
342 }
343
344 #[test]
345 fn test_merge_protocol_state_updates() {
346 let mut state_1 = create_state("State1".to_owned());
347 state_1
348 .updated_attributes
349 .insert("to_be_removed".to_owned(), Bytes::from(1u64).lpad(32, 0));
350 state_1.deleted_attributes = vec!["to_add_back".to_owned()]
351 .into_iter()
352 .collect();
353
354 let attributes2: HashMap<String, Bytes> = vec![
355 ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
356 ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
357 ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
358 ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
359 ]
360 .into_iter()
361 .collect();
362 let del_attributes2: HashSet<String> = vec!["to_be_removed".to_owned()]
363 .into_iter()
364 .collect();
365 let mut state_2 = create_state("State1".to_owned());
366 state_2.updated_attributes = attributes2;
367 state_2.deleted_attributes = del_attributes2;
368
369 let res = state_1.merge(state_2);
370
371 assert!(res.is_ok());
372 let expected_attributes: HashMap<String, Bytes> = vec![
373 ("reserve1".to_owned(), Bytes::from(900u64).lpad(32, 0)),
374 ("reserve2".to_owned(), Bytes::from(550u64).lpad(32, 0)),
375 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
376 ("new_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
377 ("to_add_back".to_owned(), Bytes::from(200u64).lpad(32, 0)),
378 ]
379 .into_iter()
380 .collect();
381 assert_eq!(state_1.updated_attributes, expected_attributes);
382 let expected_del_attributes: HashSet<String> = vec!["to_be_removed".to_owned()]
383 .into_iter()
384 .collect();
385 assert_eq!(state_1.deleted_attributes, expected_del_attributes);
386 }
387
388 fn protocol_state_with_tx() -> ProtocolChangesWithTx {
389 let state_1 = create_state("State1".to_owned());
390 let state_2 = create_state("State2".to_owned());
391 let states: HashMap<String, ProtocolComponentStateDelta> =
392 vec![(state_1.component_id.clone(), state_1), (state_2.component_id.clone(), state_2)]
393 .into_iter()
394 .collect();
395 ProtocolChangesWithTx {
396 protocol_states: states,
397 tx: block_fixtures::transaction01(),
398 ..Default::default()
399 }
400 }
401
402 #[test]
403 fn test_merge_protocol_state_update_with_tx() {
404 let mut base_state = protocol_state_with_tx();
405
406 let new_attributes: HashMap<String, Bytes> = vec![
407 ("reserve1".to_owned(), Bytes::from(600u64).lpad(32, 0)),
408 ("new_attribute".to_owned(), Bytes::from(10u64).lpad(32, 0)),
409 ]
410 .into_iter()
411 .collect();
412 let new_tx = block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 11);
413 let new_states: HashMap<String, ProtocolComponentStateDelta> = vec![(
414 "State1".to_owned(),
415 ProtocolComponentStateDelta {
416 component_id: "State1".to_owned(),
417 updated_attributes: new_attributes,
418 deleted_attributes: HashSet::new(),
419 },
420 )]
421 .into_iter()
422 .collect();
423
424 let tx_update =
425 ProtocolChangesWithTx { protocol_states: new_states, tx: new_tx, ..Default::default() };
426
427 let res = base_state.merge(tx_update);
428
429 assert!(res.is_ok());
430 assert_eq!(base_state.protocol_states.len(), 2);
431 let expected_attributes: HashMap<String, Bytes> = vec![
432 ("reserve1".to_owned(), Bytes::from(600u64).lpad(32, 0)),
433 ("reserve2".to_owned(), Bytes::from(500u64).lpad(32, 0)),
434 ("static_attribute".to_owned(), Bytes::from(1u64).lpad(32, 0)),
435 ("new_attribute".to_owned(), Bytes::from(10u64).lpad(32, 0)),
436 ]
437 .into_iter()
438 .collect();
439 assert_eq!(
440 base_state
441 .protocol_states
442 .get("State1")
443 .unwrap()
444 .updated_attributes,
445 expected_attributes
446 );
447 }
448
449 #[rstest]
450 #[case::diff_block(
451 block_fixtures::create_transaction(HASH_256_1, HASH_256_1, 11),
452 Err(MergeError::BlockMismatch(
453 "ProtocolChangesWithTx".to_string(),
454 Bytes::zero(32),
455 HASH_256_1.into(),
456 ))
457 )]
458 #[case::same_tx(
459 block_fixtures::create_transaction(HASH_256_0, HASH_256_0, 11),
460 Err(MergeError::SameTransaction(
461 "ProtocolChangesWithTx".to_string(),
462 Bytes::zero(32),
463 ))
464 )]
465 #[case::lower_idx(
466 block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 1),
467 Err(MergeError::TransactionOrderError(
468 "ProtocolChangesWithTx".to_string(),
469 10,
470 1,
471 ))
472 )]
473 fn test_merge_pool_state_update_with_tx_errors(
474 #[case] tx: Transaction,
475 #[case] exp: Result<(), MergeError>,
476 ) {
477 let mut base_state = protocol_state_with_tx();
478
479 let mut new_state = protocol_state_with_tx();
480 new_state.tx = tx;
481
482 let res = base_state.merge(new_state);
483
484 assert_eq!(res, exp);
485 }
486
487 #[test]
488 fn test_merge_protocol_state_update_wrong_id() {
489 let mut state1 = create_state("State1".to_owned());
490 let state2 = create_state("State2".to_owned());
491
492 let res = state1.merge(state2);
493
494 assert_eq!(
495 res,
496 Err(MergeError::IdMismatch(
497 "ProtocolComponentStateDeltas".to_string(),
498 "State1".to_string(),
499 "State2".to_string(),
500 ))
501 );
502 }
503}