1use std::{
2 any::Any,
3 collections::{hash_map::Entry, HashMap, HashSet},
4 sync::Arc,
5};
6
7use chrono::NaiveDateTime;
8use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
9use tracing::warn;
10
11use crate::{
12 dto,
13 models::{
14 contract::{AccountBalance, AccountChangesWithTx, AccountDelta},
15 protocol::{
16 ComponentBalance, ProtocolChangesWithTx, ProtocolComponent, ProtocolComponentStateDelta,
17 },
18 token::Token,
19 Address, BlockHash, Chain, ComponentId, EntryPointId, ExtractorIdentity, MergeError,
20 NormalisedMessage, StoreKey,
21 },
22 Bytes,
23};
24
25#[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)]
26pub struct Block {
27 pub number: u64,
28 pub chain: Chain,
29 pub hash: Bytes,
30 pub parent_hash: Bytes,
31 pub ts: NaiveDateTime,
32}
33
34impl Block {
35 pub fn new(
36 number: u64,
37 chain: Chain,
38 hash: Bytes,
39 parent_hash: Bytes,
40 ts: NaiveDateTime,
41 ) -> Self {
42 Block { hash, parent_hash, number, chain, ts }
43 }
44}
45
46#[derive(Clone, Default, PartialEq, Debug, Eq, Hash)]
47pub struct Transaction {
48 pub hash: Bytes,
49 pub block_hash: Bytes,
50 pub from: Bytes,
51 pub to: Option<Bytes>,
52 pub index: u64,
53}
54
55impl Transaction {
56 pub fn new(hash: Bytes, block_hash: Bytes, from: Bytes, to: Option<Bytes>, index: u64) -> Self {
57 Transaction { hash, block_hash, from, to, index }
58 }
59}
60
61pub struct BlockTransactionDeltas<T> {
62 pub extractor: String,
63 pub chain: Chain,
64 pub block: Block,
65 pub revert: bool,
66 pub deltas: Vec<TransactionDeltaGroup<T>>,
67}
68
69#[allow(dead_code)]
70pub struct TransactionDeltaGroup<T> {
71 changes: T,
72 protocol_component: HashMap<String, ProtocolComponent>,
73 component_balances: HashMap<String, ComponentBalance>,
74 component_tvl: HashMap<String, f64>,
75 tx: Transaction,
76}
77
78#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
79pub struct BlockAggregatedChanges {
80 pub extractor: String,
81 pub chain: Chain,
82 pub block: Block,
83 pub finalized_block_height: u64,
84 pub revert: bool,
85 pub state_deltas: HashMap<String, ProtocolComponentStateDelta>,
86 pub account_deltas: HashMap<Bytes, AccountDelta>,
87 pub new_tokens: HashMap<Address, Token>,
88 pub new_protocol_components: HashMap<String, ProtocolComponent>,
89 pub deleted_protocol_components: HashMap<String, ProtocolComponent>,
90 pub component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
91 pub account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
92 pub component_tvl: HashMap<String, f64>,
93 pub dci_update: DCIUpdate,
94}
95
96impl BlockAggregatedChanges {
97 #[allow(clippy::too_many_arguments)]
98 pub fn new(
99 extractor: &str,
100 chain: Chain,
101 block: Block,
102 finalized_block_height: u64,
103 revert: bool,
104 state_deltas: HashMap<String, ProtocolComponentStateDelta>,
105 account_deltas: HashMap<Bytes, AccountDelta>,
106 new_tokens: HashMap<Address, Token>,
107 new_components: HashMap<String, ProtocolComponent>,
108 deleted_components: HashMap<String, ProtocolComponent>,
109 component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
110 account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
111 component_tvl: HashMap<String, f64>,
112 dci_update: DCIUpdate,
113 ) -> Self {
114 Self {
115 extractor: extractor.to_string(),
116 chain,
117 block,
118 finalized_block_height,
119 revert,
120 state_deltas,
121 account_deltas,
122 new_tokens,
123 new_protocol_components: new_components,
124 deleted_protocol_components: deleted_components,
125 component_balances,
126 account_balances,
127 component_tvl,
128 dci_update,
129 }
130 }
131}
132
133impl std::fmt::Display for BlockAggregatedChanges {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 write!(f, "block_number: {}, extractor: {}", self.block.number, self.extractor)
136 }
137}
138
139#[typetag::serde]
140impl NormalisedMessage for BlockAggregatedChanges {
141 fn source(&self) -> ExtractorIdentity {
142 ExtractorIdentity::new(self.chain, &self.extractor)
143 }
144
145 fn drop_state(&self) -> Arc<dyn NormalisedMessage> {
146 Arc::new(Self {
147 extractor: self.extractor.clone(),
148 chain: self.chain,
149 block: self.block.clone(),
150 finalized_block_height: self.finalized_block_height,
151 revert: self.revert,
152 account_deltas: HashMap::new(),
153 state_deltas: HashMap::new(),
154 new_tokens: self.new_tokens.clone(),
155 new_protocol_components: self.new_protocol_components.clone(),
156 deleted_protocol_components: self.deleted_protocol_components.clone(),
157 component_balances: self.component_balances.clone(),
158 account_balances: self.account_balances.clone(),
159 component_tvl: self.component_tvl.clone(),
160 dci_update: self.dci_update.clone(),
161 })
162 }
163
164 fn as_any(&self) -> &dyn Any {
165 self
166 }
167}
168
169pub trait BlockScoped {
170 fn block(&self) -> Block;
171}
172
173impl BlockScoped for BlockAggregatedChanges {
174 fn block(&self) -> Block {
175 self.block.clone()
176 }
177}
178
179#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
180pub struct DCIUpdate {
181 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
182 pub new_entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
183 pub trace_results: HashMap<EntryPointId, TracingResult>,
184}
185
186#[derive(Debug, Clone, PartialEq, Default)]
188pub struct TxWithChanges {
189 pub tx: Transaction,
190 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
191 pub account_deltas: HashMap<Address, AccountDelta>,
192 pub state_updates: HashMap<ComponentId, ProtocolComponentStateDelta>,
193 pub balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
194 pub account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
195 pub entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
196 pub entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
197}
198
199impl TxWithChanges {
200 #[allow(clippy::too_many_arguments)]
201 pub fn new(
202 tx: Transaction,
203 protocol_components: HashMap<ComponentId, ProtocolComponent>,
204 account_deltas: HashMap<Address, AccountDelta>,
205 protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
206 balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
207 account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
208 entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
209 entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
210 ) -> Self {
211 Self {
212 tx,
213 account_deltas,
214 protocol_components,
215 state_updates: protocol_states,
216 balance_changes,
217 account_balance_changes,
218 entrypoints,
219 entrypoint_params,
220 }
221 }
222
223 pub fn merge(&mut self, other: TxWithChanges) -> Result<(), MergeError> {
233 if self.tx.block_hash != other.tx.block_hash {
234 return Err(MergeError::BlockMismatch(
235 "TxWithChanges".to_string(),
236 self.tx.block_hash.clone(),
237 other.tx.block_hash,
238 ));
239 }
240 if self.tx.index > other.tx.index {
241 return Err(MergeError::TransactionOrderError(
242 "TxWithChanges".to_string(),
243 self.tx.index,
244 other.tx.index,
245 ));
246 }
247
248 self.tx = other.tx;
249
250 for (key, value) in other.protocol_components {
254 match self.protocol_components.entry(key) {
255 Entry::Occupied(mut entry) => {
256 warn!(
257 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
258 entry.get().id
259 );
260 entry.insert(value);
261 }
262 Entry::Vacant(entry) => {
263 entry.insert(value);
264 }
265 }
266 }
267
268 for (address, update) in other.account_deltas.clone().into_iter() {
270 match self.account_deltas.entry(address) {
271 Entry::Occupied(mut e) => {
272 e.get_mut().merge(update)?;
273 }
274 Entry::Vacant(e) => {
275 e.insert(update);
276 }
277 }
278 }
279
280 for (key, value) in other.state_updates {
282 match self.state_updates.entry(key) {
283 Entry::Occupied(mut entry) => {
284 entry.get_mut().merge(value)?;
285 }
286 Entry::Vacant(entry) => {
287 entry.insert(value);
288 }
289 }
290 }
291
292 for (component_id, balance_changes) in other.balance_changes {
294 let token_balances = self
295 .balance_changes
296 .entry(component_id)
297 .or_default();
298 for (token, balance) in balance_changes {
299 token_balances.insert(token, balance);
300 }
301 }
302
303 for (account_addr, balance_changes) in other.account_balance_changes {
305 let token_balances = self
306 .account_balance_changes
307 .entry(account_addr)
308 .or_default();
309 for (token, balance) in balance_changes {
310 token_balances.insert(token, balance);
311 }
312 }
313
314 for (component_id, entrypoints) in other.entrypoints {
316 self.entrypoints
317 .entry(component_id)
318 .or_default()
319 .extend(entrypoints);
320 }
321
322 for (entrypoint_id, params) in other.entrypoint_params {
324 self.entrypoint_params
325 .entry(entrypoint_id)
326 .or_default()
327 .extend(params);
328 }
329
330 Ok(())
331 }
332}
333
334impl From<AccountChangesWithTx> for TxWithChanges {
335 fn from(value: AccountChangesWithTx) -> Self {
336 Self {
337 tx: value.tx,
338 protocol_components: value.protocol_components,
339 account_deltas: value.account_deltas,
340 balance_changes: value.component_balances,
341 account_balance_changes: value.account_balances,
342 ..Default::default()
343 }
344 }
345}
346
347impl From<ProtocolChangesWithTx> for TxWithChanges {
348 fn from(value: ProtocolChangesWithTx) -> Self {
349 Self {
350 tx: value.tx,
351 protocol_components: value.new_protocol_components,
352 state_updates: value.protocol_states,
353 balance_changes: value.balance_changes,
354 ..Default::default()
355 }
356 }
357}
358
359#[derive(Copy, Clone, Debug, PartialEq)]
360pub enum BlockTag {
361 Finalized,
363 Safe,
365 Latest,
367 Earliest,
369 Pending,
371 Number(u64),
373}
374#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
375pub struct EntryPoint {
376 pub external_id: String,
378 pub target: Address,
380 pub signature: String,
382}
383
384impl EntryPoint {
385 pub fn new(external_id: String, target: Address, signature: String) -> Self {
386 Self { external_id, target, signature }
387 }
388}
389
390impl From<dto::EntryPoint> for EntryPoint {
391 fn from(value: dto::EntryPoint) -> Self {
392 Self { external_id: value.external_id, target: value.target, signature: value.signature }
393 }
394}
395
396#[derive(Debug, Clone, PartialEq, Eq, Hash)]
398pub struct EntryPointWithTracingParams {
399 pub entry_point: EntryPoint,
401 pub params: TracingParams,
403}
404
405impl From<dto::EntryPointWithTracingParams> for EntryPointWithTracingParams {
406 fn from(value: dto::EntryPointWithTracingParams) -> Self {
407 match value.params {
408 dto::TracingParams::RPCTracer(ref tracer_params) => Self {
409 entry_point: EntryPoint {
410 external_id: value.entry_point.external_id,
411 target: value.entry_point.target,
412 signature: value.entry_point.signature,
413 },
414 params: TracingParams::RPCTracer(RPCTracerParams {
415 caller: tracer_params.caller.clone(),
416 calldata: tracer_params.calldata.clone(),
417 }),
418 },
419 }
420 }
421}
422
423impl EntryPointWithTracingParams {
424 pub fn new(entry_point: EntryPoint, params: TracingParams) -> Self {
425 Self { entry_point, params }
426 }
427}
428
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
430pub enum TracingParams {
433 RPCTracer(RPCTracerParams),
435}
436
437impl From<dto::TracingParams> for TracingParams {
438 fn from(value: dto::TracingParams) -> Self {
439 match value {
440 dto::TracingParams::RPCTracer(tracer_params) => {
441 TracingParams::RPCTracer(tracer_params.into())
442 }
443 }
444 }
445}
446
447#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
448pub struct RPCTracerParams {
449 pub caller: Option<Address>,
452 pub calldata: Bytes,
454}
455
456impl From<dto::RPCTracerParams> for RPCTracerParams {
457 fn from(value: dto::RPCTracerParams) -> Self {
458 Self { caller: value.caller, calldata: value.calldata }
459 }
460}
461
462impl RPCTracerParams {
463 pub fn new(caller: Option<Address>, calldata: Bytes) -> Self {
464 Self { caller, calldata }
465 }
466}
467
468impl Serialize for RPCTracerParams {
470 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
471 where
472 S: Serializer,
473 {
474 let mut state = serializer.serialize_struct("RPCTracerEntryPoint", 2)?;
475 state.serialize_field("caller", &self.caller)?;
476 state.serialize_field("calldata", &self.calldata)?;
477 state.end()
478 }
479}
480
481#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
482pub struct TracingResult {
483 pub retriggers: HashSet<(Address, StoreKey)>,
486 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
489}
490
491impl TracingResult {
492 pub fn new(
493 retriggers: HashSet<(Address, StoreKey)>,
494 accessed_slots: HashMap<Address, HashSet<StoreKey>>,
495 ) -> Self {
496 Self { retriggers, accessed_slots }
497 }
498
499 pub fn merge(&mut self, other: TracingResult) {
503 self.retriggers.extend(other.retriggers);
504 for (address, slots) in other.accessed_slots {
505 self.accessed_slots
506 .entry(address)
507 .or_default()
508 .extend(slots);
509 }
510 }
511}
512
513#[derive(Debug, Clone, PartialEq)]
514pub struct TracedEntryPoint {
516 pub entry_point_with_params: EntryPointWithTracingParams,
518 pub detection_block_hash: BlockHash,
520 pub tracing_result: TracingResult,
522}
523
524impl TracedEntryPoint {
525 pub fn new(
526 entry_point_with_params: EntryPointWithTracingParams,
527 detection_block_hash: BlockHash,
528 result: TracingResult,
529 ) -> Self {
530 Self { entry_point_with_params, detection_block_hash, tracing_result: result }
531 }
532
533 pub fn entry_point_id(&self) -> String {
534 self.entry_point_with_params
535 .entry_point
536 .external_id
537 .clone()
538 }
539}
540
541#[cfg(test)]
542pub mod fixtures {
543 use std::str::FromStr;
544
545 use rstest::rstest;
546
547 use super::*;
548 use crate::models::ChangeType;
549
550 pub fn transaction01() -> Transaction {
551 Transaction::new(
552 Bytes::zero(32),
553 Bytes::zero(32),
554 Bytes::zero(20),
555 Some(Bytes::zero(20)),
556 10,
557 )
558 }
559
560 pub fn create_transaction(hash: &str, block: &str, index: u64) -> Transaction {
561 Transaction::new(
562 hash.parse().unwrap(),
563 block.parse().unwrap(),
564 Bytes::zero(20),
565 Some(Bytes::zero(20)),
566 index,
567 )
568 }
569
570 #[test]
571 fn test_merge_tx_with_changes() {
572 let base_token = Bytes::from_str("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
573 let quote_token = Bytes::from_str("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap();
574 let contract_addr = Bytes::from_str("aaaaaaaaa24eeeb8d57d431224f73832bc34f688").unwrap();
575 let tx_hash0 = "0x2f6350a292c0fc918afe67cb893744a080dacb507b0cea4cc07437b8aff23cdb";
576 let tx_hash1 = "0x0d9e0da36cf9f305a189965b248fc79c923619801e8ab5ef158d4fd528a291ad";
577 let block = "0x0000000000000000000000000000000000000000000000000000000000000000";
578 let component = ProtocolComponent::new(
579 "ambient_USDC_ETH",
580 "test",
581 "vm:pool",
582 Chain::Ethereum,
583 vec![base_token.clone(), quote_token.clone()],
584 vec![contract_addr.clone()],
585 Default::default(),
586 Default::default(),
587 Bytes::from_str(tx_hash0).unwrap(),
588 Default::default(),
589 );
590 let account_delta = AccountDelta::new(
591 Chain::Ethereum,
592 contract_addr.clone(),
593 HashMap::new(),
594 None,
595 Some(vec![0, 0, 0, 0].into()),
596 ChangeType::Creation,
597 );
598
599 let mut changes1 = TxWithChanges::new(
600 create_transaction(tx_hash0, block, 1),
601 HashMap::from([(component.id.clone(), component.clone())]),
602 HashMap::from([(contract_addr.clone(), account_delta.clone())]),
603 HashMap::new(),
604 HashMap::from([(
605 component.id.clone(),
606 HashMap::from([(
607 base_token.clone(),
608 ComponentBalance {
609 token: base_token.clone(),
610 balance: Bytes::from(800_u64).lpad(32, 0),
611 balance_float: 800.0,
612 component_id: component.id.clone(),
613 modify_tx: Bytes::from_str(tx_hash0).unwrap(),
614 },
615 )]),
616 )]),
617 HashMap::from([(
618 contract_addr.clone(),
619 HashMap::from([(
620 base_token.clone(),
621 AccountBalance {
622 token: base_token.clone(),
623 balance: Bytes::from(800_u64).lpad(32, 0),
624 modify_tx: Bytes::from_str(tx_hash0).unwrap(),
625 account: contract_addr.clone(),
626 },
627 )]),
628 )]),
629 HashMap::from([(
630 component.id.clone(),
631 HashSet::from([EntryPoint::new(
632 "test".to_string(),
633 contract_addr.clone(),
634 "function()".to_string(),
635 )]),
636 )]),
637 HashMap::from([(
638 "test".to_string(),
639 HashSet::from([(
640 TracingParams::RPCTracer(RPCTracerParams::new(
641 None,
642 Bytes::from_str("0x000001ef").unwrap(),
643 )),
644 Some(component.id.clone()),
645 )]),
646 )]),
647 );
648 let changes2 = TxWithChanges::new(
649 create_transaction(tx_hash1, block, 2),
650 HashMap::from([(
651 component.id.clone(),
652 ProtocolComponent {
653 creation_tx: Bytes::from_str(tx_hash1).unwrap(),
654 ..component.clone()
655 },
656 )]),
657 HashMap::from([(
658 contract_addr.clone(),
659 AccountDelta {
660 slots: HashMap::from([(
661 vec![0, 0, 0, 0].into(),
662 Some(vec![0, 0, 0, 0].into()),
663 )]),
664 change: ChangeType::Update,
665 ..account_delta
666 },
667 )]),
668 HashMap::new(),
669 HashMap::from([(
670 component.id.clone(),
671 HashMap::from([(
672 base_token.clone(),
673 ComponentBalance {
674 token: base_token.clone(),
675 balance: Bytes::from(1000_u64).lpad(32, 0),
676 balance_float: 1000.0,
677 component_id: component.id.clone(),
678 modify_tx: Bytes::from_str(tx_hash1).unwrap(),
679 },
680 )]),
681 )]),
682 HashMap::from([(
683 contract_addr.clone(),
684 HashMap::from([(
685 base_token.clone(),
686 AccountBalance {
687 token: base_token.clone(),
688 balance: Bytes::from(1000_u64).lpad(32, 0),
689 modify_tx: Bytes::from_str(tx_hash1).unwrap(),
690 account: contract_addr.clone(),
691 },
692 )]),
693 )]),
694 HashMap::from([(
695 component.id.clone(),
696 HashSet::from([
697 EntryPoint::new(
698 "test".to_string(),
699 contract_addr.clone(),
700 "function()".to_string(),
701 ),
702 EntryPoint::new(
703 "test2".to_string(),
704 contract_addr.clone(),
705 "function_2()".to_string(),
706 ),
707 ]),
708 )]),
709 HashMap::from([(
710 "test2".to_string(),
711 HashSet::from([(
712 TracingParams::RPCTracer(RPCTracerParams::new(
713 None,
714 Bytes::from_str("0x000001").unwrap(),
715 )),
716 None,
717 )]),
718 )]),
719 );
720
721 assert!(changes1.merge(changes2).is_ok());
722 assert_eq!(
723 changes1
724 .account_balance_changes
725 .get(&contract_addr)
726 .unwrap()
727 .get(&base_token)
728 .unwrap()
729 .balance,
730 Bytes::from(1000_u64).lpad(32, 0),
731 );
732 assert_eq!(
733 changes1
734 .balance_changes
735 .get(&component.id)
736 .unwrap()
737 .get(&base_token)
738 .unwrap()
739 .balance,
740 Bytes::from(1000_u64).lpad(32, 0),
741 );
742 assert_eq!(changes1.tx.hash, Bytes::from_str(tx_hash1).unwrap(),);
743 assert_eq!(changes1.entrypoints.len(), 1);
744 assert_eq!(
745 changes1
746 .entrypoints
747 .get(&component.id)
748 .unwrap()
749 .len(),
750 2
751 );
752 let mut expected_entry_points = changes1
753 .entrypoints
754 .values()
755 .flat_map(|ep| ep.iter())
756 .map(|ep| ep.signature.clone())
757 .collect::<Vec<_>>();
758 expected_entry_points.sort();
759 assert_eq!(
760 expected_entry_points,
761 vec!["function()".to_string(), "function_2()".to_string()],
762 );
763 }
764
765 #[rstest]
766 #[case::mismatched_blocks(
767 fixtures::create_transaction("0x01", "0x0abc", 1),
768 fixtures::create_transaction("0x02", "0x0def", 2)
769 )]
770 #[case::older_transaction(
771 fixtures::create_transaction("0x02", "0x0abc", 2),
772 fixtures::create_transaction("0x01", "0x0abc", 1)
773 )]
774 fn test_merge_errors(#[case] tx1: Transaction, #[case] tx2: Transaction) {
775 let mut changes1 = TxWithChanges { tx: tx1, ..Default::default() };
776
777 let changes2 = TxWithChanges { tx: tx2, ..Default::default() };
778
779 assert!(changes1.merge(changes2).is_err());
780 }
781
782 #[test]
783 fn test_rpc_tracer_entry_point_serialization_order() {
784 use std::str::FromStr;
785
786 use serde_json;
787
788 let entry_point = RPCTracerParams::new(
789 Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()),
790 Bytes::from_str("0xabcdef").unwrap(),
791 );
792
793 let serialized = serde_json::to_string(&entry_point).unwrap();
794
795 assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"calldata\"").unwrap());
797
798 let deserialized: RPCTracerParams = serde_json::from_str(&serialized).unwrap();
800 assert_eq!(entry_point, deserialized);
801 }
802
803 #[test]
804 fn test_tracing_result_merge() {
805 let address1 = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
806 let address2 = Address::from_str("0x2345678901234567890123456789012345678901").unwrap();
807 let address3 = Address::from_str("0x3456789012345678901234567890123456789012").unwrap();
808
809 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
810 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
811
812 let mut result1 = TracingResult::new(
813 HashSet::from([(address1.clone(), store_key1.clone())]),
814 HashMap::from([
815 (address2.clone(), HashSet::from([store_key1.clone()])),
816 (address3.clone(), HashSet::from([store_key2.clone()])),
817 ]),
818 );
819
820 let result2 = TracingResult::new(
821 HashSet::from([(address3.clone(), store_key2.clone())]),
822 HashMap::from([
823 (address1.clone(), HashSet::from([store_key1.clone()])),
824 (address2.clone(), HashSet::from([store_key2.clone()])),
825 ]),
826 );
827
828 result1.merge(result2);
829
830 assert_eq!(result1.retriggers.len(), 2);
832 assert!(result1
833 .retriggers
834 .contains(&(address1.clone(), store_key1.clone())));
835 assert!(result1
836 .retriggers
837 .contains(&(address3.clone(), store_key2.clone())));
838
839 assert_eq!(result1.accessed_slots.len(), 3);
841 assert!(result1
842 .accessed_slots
843 .contains_key(&address1));
844 assert!(result1
845 .accessed_slots
846 .contains_key(&address2));
847 assert!(result1
848 .accessed_slots
849 .contains_key(&address3));
850
851 assert_eq!(
852 result1
853 .accessed_slots
854 .get(&address2)
855 .unwrap(),
856 &HashSet::from([store_key1.clone(), store_key2.clone()])
857 );
858 }
859}