1use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
2
3use chrono::NaiveDateTime;
4use deepsize::DeepSizeOf;
5use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
6use tracing::warn;
7
8use crate::{
9 dto,
10 models::{
11 contract::{AccountBalance, AccountDelta, AccountToContractChanges},
12 protocol::{ComponentBalance, ProtocolComponent, ProtocolComponentStateDelta},
13 token::Token,
14 Address, Balance, BlockHash, Chain, Code, ComponentId, EntryPointId, MergeError, StoreKey,
15 StoreVal,
16 },
17 Bytes,
18};
19
20#[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)]
21pub struct Block {
22 pub number: u64,
23 pub chain: Chain,
24 pub hash: Bytes,
25 pub parent_hash: Bytes,
26 pub ts: NaiveDateTime,
27}
28
29impl Block {
30 pub fn new(
31 number: u64,
32 chain: Chain,
33 hash: Bytes,
34 parent_hash: Bytes,
35 ts: NaiveDateTime,
36 ) -> Self {
37 Block { hash, parent_hash, number, chain, ts }
38 }
39}
40
41impl DeepSizeOf for Block {
43 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
44 self.chain
45 .deep_size_of_children(context) +
46 self.hash.deep_size_of_children(context) +
47 self.parent_hash
48 .deep_size_of_children(context)
49 }
50}
51
52#[derive(Clone, Default, PartialEq, Debug, Eq, Hash, DeepSizeOf)]
53pub struct Transaction {
54 pub hash: Bytes,
55 pub block_hash: Bytes,
56 pub from: Bytes,
57 pub to: Option<Bytes>,
58 pub index: u64,
59}
60
61impl Transaction {
62 pub fn new(hash: Bytes, block_hash: Bytes, from: Bytes, to: Option<Bytes>, index: u64) -> Self {
63 Transaction { hash, block_hash, from, to, index }
64 }
65}
66
67#[derive(Debug, Clone)]
73pub struct LogInput {
74 address: Bytes,
75 topics: Vec<Bytes>,
76 data: Bytes,
77 log_index: u32,
78}
79
80impl LogInput {
81 pub fn new(address: Bytes, topics: Vec<Bytes>, data: Bytes, log_index: u32) -> Self {
82 Self { address, topics, data, log_index }
83 }
84
85 pub fn address(&self) -> &Bytes {
86 &self.address
87 }
88
89 pub fn topics(&self) -> &[Bytes] {
90 &self.topics
91 }
92
93 pub fn data(&self) -> &Bytes {
94 &self.data
95 }
96
97 pub fn log_index(&self) -> u32 {
98 self.log_index
99 }
100}
101
102#[derive(Debug, Clone)]
107pub struct TxInput {
108 hash: Bytes,
109 from: Bytes,
110 to: Bytes,
111 index: u64,
112 logs: Vec<LogInput>,
113 succeeded: bool,
114}
115
116impl TxInput {
117 pub fn new(
118 hash: Bytes,
119 from: Bytes,
120 to: Bytes,
121 index: u64,
122 logs: Vec<LogInput>,
123 succeeded: bool,
124 ) -> Self {
125 Self { hash, from, to, index, logs, succeeded }
126 }
127
128 pub fn hash(&self) -> &Bytes {
129 &self.hash
130 }
131
132 pub fn from(&self) -> &Bytes {
133 &self.from
134 }
135
136 pub fn to(&self) -> &Bytes {
137 &self.to
138 }
139
140 pub fn index(&self) -> u64 {
141 self.index
142 }
143
144 pub fn logs(&self) -> &[LogInput] {
145 &self.logs
146 }
147
148 pub fn succeeded(&self) -> bool {
149 self.succeeded
150 }
151}
152
153#[derive(Debug, Clone, Default)]
155pub struct PendingBlock {
156 block: Block,
157 txs: Vec<TxInput>,
158 accounts: HashMap<Address, AccountDelta>,
159}
160
161impl PendingBlock {
162 pub fn new(block: Block, txs: Vec<TxInput>, accounts: HashMap<Address, AccountDelta>) -> Self {
163 Self { block, txs, accounts }
164 }
165
166 pub fn block(&self) -> &Block {
168 &self.block
169 }
170
171 pub fn txs(&self) -> &[TxInput] {
173 &self.txs
174 }
175
176 pub fn accounts(&self) -> &HashMap<Address, AccountDelta> {
179 &self.accounts
180 }
181}
182
183pub struct BlockTransactionDeltas<T> {
184 pub extractor: String,
185 pub chain: Chain,
186 pub block: Block,
187 pub revert: bool,
188 pub deltas: Vec<TransactionDeltaGroup<T>>,
189}
190
191#[allow(dead_code)]
192pub struct TransactionDeltaGroup<T> {
193 changes: T,
194 protocol_component: HashMap<String, ProtocolComponent>,
195 component_balances: HashMap<String, ComponentBalance>,
196 component_tvl: HashMap<String, f64>,
197 tx: Transaction,
198}
199
200#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
201pub struct BlockAggregatedChanges {
202 pub extractor: String,
203 pub chain: Chain,
204 pub block: Block,
205 pub finalized_block_height: u64,
206 pub db_committed_block_height: Option<u64>,
207 pub revert: bool,
208 pub state_deltas: HashMap<String, ProtocolComponentStateDelta>,
209 pub account_deltas: HashMap<Bytes, AccountDelta>,
210 pub new_tokens: HashMap<Address, Token>,
211 pub new_protocol_components: HashMap<String, ProtocolComponent>,
212 pub deleted_protocol_components: HashMap<String, ProtocolComponent>,
213 pub component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
214 pub account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
215 pub component_tvl: HashMap<String, f64>,
216 pub dci_update: DCIUpdate,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
219 pub partial_block_index: Option<u32>,
220}
221
222impl BlockAggregatedChanges {
223 #[allow(clippy::too_many_arguments)]
224 pub fn new(
225 extractor: &str,
226 chain: Chain,
227 block: Block,
228 db_committed_block_height: Option<u64>,
229 finalized_block_height: u64,
230 revert: bool,
231 state_deltas: HashMap<String, ProtocolComponentStateDelta>,
232 account_deltas: HashMap<Bytes, AccountDelta>,
233 new_tokens: HashMap<Address, Token>,
234 new_components: HashMap<String, ProtocolComponent>,
235 deleted_components: HashMap<String, ProtocolComponent>,
236 component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
237 account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
238 component_tvl: HashMap<String, f64>,
239 dci_update: DCIUpdate,
240 ) -> Self {
241 Self {
242 extractor: extractor.to_string(),
243 chain,
244 block,
245 db_committed_block_height,
246 finalized_block_height,
247 revert,
248 state_deltas,
249 account_deltas,
250 new_tokens,
251 new_protocol_components: new_components,
252 deleted_protocol_components: deleted_components,
253 component_balances,
254 account_balances,
255 component_tvl,
256 dci_update,
257 partial_block_index: None,
258 }
259 }
260
261 pub fn drop_state(&self) -> Self {
262 Self {
263 extractor: self.extractor.clone(),
264 chain: self.chain,
265 block: self.block.clone(),
266 db_committed_block_height: self.db_committed_block_height,
267 finalized_block_height: self.finalized_block_height,
268 revert: self.revert,
269 account_deltas: HashMap::new(),
270 state_deltas: HashMap::new(),
271 new_tokens: self.new_tokens.clone(),
272 new_protocol_components: self.new_protocol_components.clone(),
273 deleted_protocol_components: self.deleted_protocol_components.clone(),
274 component_balances: self.component_balances.clone(),
275 account_balances: self.account_balances.clone(),
276 component_tvl: self.component_tvl.clone(),
277 dci_update: self.dci_update.clone(),
278 partial_block_index: self.partial_block_index,
279 }
280 }
281
282 pub fn is_partial(&self) -> bool {
283 self.partial_block_index.is_some()
284 }
285
286 pub fn get_block(&self) -> &Block {
287 &self.block
288 }
289
290 pub fn n_changes(&self) -> usize {
291 self.account_deltas.len() + self.state_deltas.len()
292 }
293
294 pub fn filter_by_component<F: Fn(&str) -> bool>(&mut self, keep: F) {
295 self.state_deltas.retain(|k, _| keep(k));
296 self.component_balances
297 .retain(|k, _| keep(k));
298 self.component_tvl
299 .retain(|k, _| keep(k));
300 }
301
302 pub fn filter_by_contract<F: Fn(&Bytes) -> bool>(&mut self, keep: F) {
303 self.account_deltas
304 .retain(|k, _| keep(k));
305 self.account_balances
306 .retain(|k, _| keep(k));
307 }
308
309 pub fn merge(mut self, other: Self) -> Self {
313 for (k, v) in other.account_deltas {
314 match self.account_deltas.entry(k) {
315 Entry::Occupied(mut e) => {
316 let _ = e.get_mut().merge(v);
318 }
319 Entry::Vacant(e) => {
320 e.insert(v);
321 }
322 }
323 }
324
325 for (k, v) in other.state_deltas {
326 match self.state_deltas.entry(k) {
327 Entry::Occupied(mut e) => {
328 let _ = e.get_mut().merge(v);
329 }
330 Entry::Vacant(e) => {
331 e.insert(v);
332 }
333 }
334 }
335
336 for (component_id, balances) in other.component_balances {
337 self.component_balances
338 .entry(component_id)
339 .or_default()
340 .extend(balances);
341 }
342
343 for (account, balances) in other.account_balances {
344 self.account_balances
345 .entry(account)
346 .or_default()
347 .extend(balances);
348 }
349
350 self.component_tvl
351 .extend(other.component_tvl);
352 self.new_protocol_components
353 .extend(other.new_protocol_components);
354 self.deleted_protocol_components
355 .extend(other.deleted_protocol_components);
356 self.revert = other.revert;
357 self.block = other.block;
358 self
359 }
360}
361
362impl std::fmt::Display for BlockAggregatedChanges {
363 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364 write!(f, "block_number: {}, extractor: {}", self.block.number, self.extractor)
365 }
366}
367
368pub trait BlockScoped {
369 fn block(&self) -> Block;
370}
371
372impl<T: BlockScoped> BlockScoped for std::sync::Arc<T> {
373 fn block(&self) -> Block {
374 (**self).block()
375 }
376}
377
378impl BlockScoped for BlockAggregatedChanges {
379 fn block(&self) -> Block {
380 self.block.clone()
381 }
382}
383
384impl From<dto::Block> for Block {
385 fn from(value: dto::Block) -> Self {
386 Self {
387 number: value.number,
388 chain: value.chain.into(),
389 hash: value.hash,
390 parent_hash: value.parent_hash,
391 ts: value.ts,
392 }
393 }
394}
395
396impl From<dto::AddressStorageLocation> for AddressStorageLocation {
397 fn from(value: dto::AddressStorageLocation) -> Self {
398 Self { key: value.key, offset: value.offset }
399 }
400}
401
402impl From<dto::TracingResult> for TracingResult {
403 fn from(value: dto::TracingResult) -> Self {
404 Self {
405 retriggers: value
406 .retriggers
407 .into_iter()
408 .map(|(addr, loc)| (addr, loc.into()))
409 .collect(),
410 accessed_slots: value.accessed_slots,
411 }
412 }
413}
414
415impl From<dto::DCIUpdate> for DCIUpdate {
416 fn from(value: dto::DCIUpdate) -> Self {
417 Self {
418 new_entrypoints: value
419 .new_entrypoints
420 .into_iter()
421 .map(|(k, v)| {
422 (
423 k,
424 v.into_iter()
425 .map(EntryPoint::from)
426 .collect(),
427 )
428 })
429 .collect(),
430 new_entrypoint_params: value
431 .new_entrypoint_params
432 .into_iter()
433 .map(|(k, v)| {
434 (
435 k,
436 v.into_iter()
437 .map(|(p, c)| (TracingParams::from(p), c))
438 .collect(),
439 )
440 })
441 .collect(),
442 trace_results: value
443 .trace_results
444 .into_iter()
445 .map(|(k, v)| (k, TracingResult::from(v)))
446 .collect(),
447 }
448 }
449}
450
451impl From<dto::BlockAggregatedChanges> for BlockAggregatedChanges {
452 fn from(value: dto::BlockAggregatedChanges) -> Self {
453 use crate::models::{
454 contract::{AccountBalance, AccountDelta},
455 protocol::{ComponentBalance, ProtocolComponent, ProtocolComponentStateDelta},
456 token::Token,
457 };
458 Self {
459 extractor: value.extractor,
460 chain: value.chain.into(),
461 block: value.block.into(),
462 finalized_block_height: value.finalized_block_height,
463 db_committed_block_height: None,
464 revert: value.revert,
465 state_deltas: value
466 .state_updates
467 .into_iter()
468 .map(|(k, v)| (k, ProtocolComponentStateDelta::from(v)))
469 .collect(),
470 account_deltas: value
471 .account_updates
472 .into_iter()
473 .map(|(k, v)| (k, AccountDelta::from(v)))
474 .collect(),
475 new_tokens: value
476 .new_tokens
477 .into_iter()
478 .map(|(k, v)| (k, Token::from(v)))
479 .collect(),
480 new_protocol_components: value
481 .new_protocol_components
482 .into_iter()
483 .map(|(k, v)| (k, ProtocolComponent::from(v)))
484 .collect(),
485 deleted_protocol_components: value
486 .deleted_protocol_components
487 .into_iter()
488 .map(|(k, v)| (k, ProtocolComponent::from(v)))
489 .collect(),
490 component_balances: value
491 .component_balances
492 .into_iter()
493 .map(|(component_id, token_balances)| {
494 (
495 component_id,
496 token_balances
497 .0
498 .into_iter()
499 .map(|(k, v)| (k, ComponentBalance::from(v)))
500 .collect(),
501 )
502 })
503 .collect(),
504 account_balances: value
505 .account_balances
506 .into_iter()
507 .map(|(account, balances)| {
508 (
509 account,
510 balances
511 .into_iter()
512 .map(|(k, v)| (k, AccountBalance::from(v)))
513 .collect(),
514 )
515 })
516 .collect(),
517 component_tvl: value.component_tvl,
518 dci_update: DCIUpdate::from(value.dci_update),
519 partial_block_index: value.partial_block_index,
520 }
521 }
522}
523
524#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
525pub struct DCIUpdate {
526 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
527 pub new_entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
528 pub trace_results: HashMap<EntryPointId, TracingResult>,
529}
530
531pub type TracedEntryPoints =
536 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>;
537
538impl From<TracedEntryPoints> for DCIUpdate {
539 fn from(traced_entry_points: TracedEntryPoints) -> Self {
540 let mut new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>> = HashMap::new();
541 let mut new_entrypoint_params: HashMap<
542 EntryPointId,
543 HashSet<(TracingParams, ComponentId)>,
544 > = HashMap::new();
545 let mut trace_results: HashMap<EntryPointId, TracingResult> = HashMap::new();
546
547 for (component_id, traces) in traced_entry_points {
548 let mut entrypoints = HashSet::new();
549
550 for (ep_with_params, trace) in traces {
551 let ep_id = ep_with_params
552 .entry_point
553 .external_id
554 .clone();
555
556 entrypoints.insert(ep_with_params.entry_point.clone());
557
558 new_entrypoint_params
559 .entry(ep_id.clone())
560 .or_default()
561 .insert((ep_with_params.params, component_id.clone()));
562
563 trace_results
564 .entry(ep_id)
565 .and_modify(|existing: &mut TracingResult| {
566 existing
567 .retriggers
568 .extend(trace.retriggers.clone());
569 for (address, slots) in trace.accessed_slots.clone() {
570 existing
571 .accessed_slots
572 .entry(address)
573 .or_default()
574 .extend(slots);
575 }
576 })
577 .or_insert(trace);
578 }
579
580 if !entrypoints.is_empty() {
581 new_entrypoints.insert(component_id, entrypoints);
582 }
583 }
584
585 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
586 }
587}
588
589#[derive(Debug, Clone, PartialEq, Default, DeepSizeOf)]
591pub struct TxWithChanges {
592 pub tx: Transaction,
593 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
594 pub account_deltas: HashMap<Address, AccountDelta>,
595 pub state_updates: HashMap<ComponentId, ProtocolComponentStateDelta>,
596 pub balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
597 pub account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
598 pub entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
599 pub entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
600}
601
602impl TxWithChanges {
603 #[allow(clippy::too_many_arguments)]
604 pub fn new(
605 tx: Transaction,
606 protocol_components: HashMap<ComponentId, ProtocolComponent>,
607 account_deltas: HashMap<Address, AccountDelta>,
608 protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
609 balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
610 account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
611 entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
612 entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
613 ) -> Self {
614 Self {
615 tx,
616 account_deltas,
617 protocol_components,
618 state_updates: protocol_states,
619 balance_changes,
620 account_balance_changes,
621 entrypoints,
622 entrypoint_params,
623 }
624 }
625
626 pub fn merge(&mut self, other: TxWithChanges) -> Result<(), MergeError> {
636 if self.tx.block_hash != other.tx.block_hash {
637 return Err(MergeError::BlockMismatch(
638 "TxWithChanges".to_string(),
639 self.tx.block_hash.clone(),
640 other.tx.block_hash,
641 ));
642 }
643 if self.tx.index > other.tx.index {
644 return Err(MergeError::TransactionOrderError(
645 "TxWithChanges".to_string(),
646 self.tx.index,
647 other.tx.index,
648 ));
649 }
650
651 self.tx = other.tx;
652
653 for (key, value) in other.protocol_components {
657 match self.protocol_components.entry(key) {
658 Entry::Occupied(mut entry) => {
659 warn!(
660 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
661 entry.get().id
662 );
663 entry.insert(value);
664 }
665 Entry::Vacant(entry) => {
666 entry.insert(value);
667 }
668 }
669 }
670
671 for (address, update) in other.account_deltas.clone().into_iter() {
673 match self.account_deltas.entry(address) {
674 Entry::Occupied(mut e) => {
675 e.get_mut().merge(update)?;
676 }
677 Entry::Vacant(e) => {
678 e.insert(update);
679 }
680 }
681 }
682
683 for (key, value) in other.state_updates {
685 match self.state_updates.entry(key) {
686 Entry::Occupied(mut entry) => {
687 entry.get_mut().merge(value)?;
688 }
689 Entry::Vacant(entry) => {
690 entry.insert(value);
691 }
692 }
693 }
694
695 for (component_id, balance_changes) in other.balance_changes {
697 let token_balances = self
698 .balance_changes
699 .entry(component_id)
700 .or_default();
701 for (token, balance) in balance_changes {
702 token_balances.insert(token, balance);
703 }
704 }
705
706 for (account_addr, balance_changes) in other.account_balance_changes {
708 let token_balances = self
709 .account_balance_changes
710 .entry(account_addr)
711 .or_default();
712 for (token, balance) in balance_changes {
713 token_balances.insert(token, balance);
714 }
715 }
716
717 for (component_id, entrypoints) in other.entrypoints {
719 self.entrypoints
720 .entry(component_id)
721 .or_default()
722 .extend(entrypoints);
723 }
724
725 for (entrypoint_id, params) in other.entrypoint_params {
727 self.entrypoint_params
728 .entry(entrypoint_id)
729 .or_default()
730 .extend(params);
731 }
732
733 Ok(())
734 }
735}
736
737#[derive(Copy, Clone, Debug, PartialEq)]
738pub enum BlockTag {
739 Finalized,
741 Safe,
743 Latest,
745 Earliest,
747 Pending,
749 Number(u64),
751}
752#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, DeepSizeOf)]
753pub struct EntryPoint {
754 pub external_id: String,
756 pub target: Address,
758 pub signature: String,
760}
761
762impl EntryPoint {
763 pub fn new(external_id: String, target: Address, signature: String) -> Self {
764 Self { external_id, target, signature }
765 }
766}
767
768impl From<dto::EntryPoint> for EntryPoint {
769 fn from(value: dto::EntryPoint) -> Self {
770 Self { external_id: value.external_id, target: value.target, signature: value.signature }
771 }
772}
773
774#[derive(Debug, Clone, PartialEq, Eq, Hash, DeepSizeOf)]
776pub struct EntryPointWithTracingParams {
777 pub entry_point: EntryPoint,
779 pub params: TracingParams,
781}
782
783impl From<dto::EntryPointWithTracingParams> for EntryPointWithTracingParams {
784 fn from(value: dto::EntryPointWithTracingParams) -> Self {
785 match value.params {
786 dto::TracingParams::RPCTracer(ref tracer_params) => Self {
787 entry_point: EntryPoint {
788 external_id: value.entry_point.external_id,
789 target: value.entry_point.target,
790 signature: value.entry_point.signature,
791 },
792 params: TracingParams::RPCTracer(RPCTracerParams {
793 caller: tracer_params.caller.clone(),
794 calldata: tracer_params.calldata.clone(),
795 state_overrides: tracer_params
796 .state_overrides
797 .clone()
798 .map(|s| {
799 s.into_iter()
800 .map(|(k, v)| (k, v.into()))
801 .collect()
802 }),
803 prune_addresses: tracer_params.prune_addresses.clone(),
804 }),
805 },
806 }
807 }
808}
809
810impl EntryPointWithTracingParams {
811 pub fn new(entry_point: EntryPoint, params: TracingParams) -> Self {
812 Self { entry_point, params }
813 }
814}
815
816impl std::fmt::Display for EntryPointWithTracingParams {
817 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818 let tracer_type = match &self.params {
819 TracingParams::RPCTracer(_) => "RPC",
820 };
821 write!(f, "{} [{}]", self.entry_point.external_id, tracer_type)
822 }
823}
824
825#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
826pub enum TracingParams {
829 RPCTracer(RPCTracerParams),
831}
832
833impl std::fmt::Display for TracingParams {
834 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
835 match self {
836 TracingParams::RPCTracer(params) => write!(f, "RPC: {params}"),
837 }
838 }
839}
840
841impl From<dto::TracingParams> for TracingParams {
842 fn from(value: dto::TracingParams) -> Self {
843 match value {
844 dto::TracingParams::RPCTracer(tracer_params) => {
845 TracingParams::RPCTracer(tracer_params.into())
846 }
847 }
848 }
849}
850
851#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
852pub enum StorageOverride {
853 Diff(BTreeMap<StoreKey, StoreVal>),
854 Replace(BTreeMap<StoreKey, StoreVal>),
855}
856
857impl From<dto::StorageOverride> for StorageOverride {
858 fn from(value: dto::StorageOverride) -> Self {
859 match value {
860 dto::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
861 dto::StorageOverride::Replace(replace) => StorageOverride::Replace(replace),
862 }
863 }
864}
865
866#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
867pub struct AccountOverrides {
868 pub slots: Option<StorageOverride>,
869 pub native_balance: Option<Balance>,
870 pub code: Option<Code>,
871}
872
873impl From<dto::AccountOverrides> for AccountOverrides {
874 fn from(value: dto::AccountOverrides) -> Self {
875 Self {
876 slots: value.slots.map(|s| s.into()),
877 native_balance: value.native_balance,
878 code: value.code,
879 }
880 }
881}
882
883#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash, DeepSizeOf)]
884pub struct RPCTracerParams {
885 pub caller: Option<Address>,
888 pub calldata: Bytes,
890 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
892 pub prune_addresses: Option<Vec<Address>>,
895}
896
897impl From<dto::RPCTracerParams> for RPCTracerParams {
898 fn from(value: dto::RPCTracerParams) -> Self {
899 Self {
900 caller: value.caller,
901 calldata: value.calldata,
902 state_overrides: value.state_overrides.map(|overrides| {
903 overrides
904 .into_iter()
905 .map(|(address, account_overrides)| (address, account_overrides.into()))
906 .collect()
907 }),
908 prune_addresses: value.prune_addresses,
909 }
910 }
911}
912
913impl RPCTracerParams {
914 pub fn new(caller: Option<Address>, calldata: Bytes) -> Self {
915 Self { caller, calldata, state_overrides: None, prune_addresses: None }
916 }
917
918 pub fn with_state_overrides(mut self, state: BTreeMap<Address, AccountOverrides>) -> Self {
919 self.state_overrides = Some(state);
920 self
921 }
922
923 pub fn with_prune_addresses(mut self, addresses: Vec<Address>) -> Self {
924 self.prune_addresses = Some(addresses);
925 self
926 }
927}
928
929impl std::fmt::Display for RPCTracerParams {
930 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
931 let caller_str = match &self.caller {
932 Some(addr) => format!("caller={addr}"),
933 None => String::new(),
934 };
935
936 let calldata_str = if self.calldata.len() >= 8 {
937 format!(
938 "calldata=0x{}..({} bytes)",
939 hex::encode(&self.calldata[..8]),
940 self.calldata.len()
941 )
942 } else {
943 format!("calldata={}", self.calldata)
944 };
945
946 let overrides_str = match &self.state_overrides {
947 Some(overrides) if !overrides.is_empty() => {
948 format!(", {} state override(s)", overrides.len())
949 }
950 _ => String::new(),
951 };
952
953 write!(f, "{caller_str}, {calldata_str}{overrides_str}")
954 }
955}
956
957impl Serialize for RPCTracerParams {
959 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
960 where
961 S: Serializer,
962 {
963 let mut field_count = 2;
965 if self.state_overrides.is_some() {
966 field_count += 1;
967 }
968 if self.prune_addresses.is_some() {
969 field_count += 1;
970 }
971
972 let mut state = serializer.serialize_struct("RPCTracerEntryPoint", field_count)?;
973 state.serialize_field("caller", &self.caller)?;
974 state.serialize_field("calldata", &self.calldata)?;
975
976 if let Some(ref overrides) = self.state_overrides {
978 state.serialize_field("state_overrides", overrides)?;
979 }
980 if let Some(ref prune_addrs) = self.prune_addresses {
981 state.serialize_field("prune_addresses", prune_addrs)?;
982 }
983
984 state.end()
985 }
986}
987
988#[derive(
989 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, DeepSizeOf,
990)]
991pub struct AddressStorageLocation {
992 pub key: StoreKey,
993 pub offset: u8,
994}
995
996impl AddressStorageLocation {
997 pub fn new(key: StoreKey, offset: u8) -> Self {
998 Self { key, offset }
999 }
1000}
1001
1002#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, DeepSizeOf)]
1003pub struct TracingResult {
1004 pub retriggers: HashSet<(Address, AddressStorageLocation)>,
1007 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1010}
1011
1012impl TracingResult {
1013 pub fn new(
1014 retriggers: HashSet<(Address, AddressStorageLocation)>,
1015 accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1016 ) -> Self {
1017 Self { retriggers, accessed_slots }
1018 }
1019
1020 pub fn merge(&mut self, other: TracingResult) {
1024 self.retriggers.extend(other.retriggers);
1025 for (address, slots) in other.accessed_slots {
1026 self.accessed_slots
1027 .entry(address)
1028 .or_default()
1029 .extend(slots);
1030 }
1031 }
1032}
1033
1034#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
1035pub struct TracedEntryPoint {
1037 pub entry_point_with_params: EntryPointWithTracingParams,
1039 pub detection_block_hash: BlockHash,
1041 pub tracing_result: TracingResult,
1043}
1044
1045impl TracedEntryPoint {
1046 pub fn new(
1047 entry_point_with_params: EntryPointWithTracingParams,
1048 detection_block_hash: BlockHash,
1049 result: TracingResult,
1050 ) -> Self {
1051 Self { entry_point_with_params, detection_block_hash, tracing_result: result }
1052 }
1053
1054 pub fn entry_point_id(&self) -> String {
1055 self.entry_point_with_params
1056 .entry_point
1057 .external_id
1058 .clone()
1059 }
1060}
1061
1062impl std::fmt::Display for TracedEntryPoint {
1063 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1064 write!(
1065 f,
1066 "[{}: {} retriggers, {} accessed addresses]",
1067 self.entry_point_id(),
1068 self.tracing_result.retriggers.len(),
1069 self.tracing_result.accessed_slots.len()
1070 )
1071 }
1072}
1073
1074#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
1076pub struct TxWithContractChanges {
1077 pub tx: Transaction,
1078 pub contract_changes: AccountToContractChanges,
1079}
1080
1081#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
1082pub struct BlockChanges {
1083 pub extractor: String,
1084 pub chain: Chain,
1085 pub block: Block,
1086 pub finalized_block_height: u64,
1087 pub revert: bool,
1088 pub new_tokens: HashMap<Address, Token>,
1089 pub txs_with_update: Vec<TxWithChanges>,
1091 pub block_contract_changes: Vec<TxWithContractChanges>,
1094 pub trace_results: Vec<TracedEntryPoint>,
1098 pub partial_block_index: Option<u32>,
1100}
1101
1102impl BlockChanges {
1103 pub fn new(
1104 extractor: String,
1105 chain: Chain,
1106 block: Block,
1107 finalized_block_height: u64,
1108 revert: bool,
1109 txs_with_update: Vec<TxWithChanges>,
1110 block_contract_changes: Vec<TxWithContractChanges>,
1111 ) -> Self {
1112 BlockChanges {
1113 extractor,
1114 chain,
1115 block,
1116 finalized_block_height,
1117 revert,
1118 new_tokens: HashMap::new(),
1119 txs_with_update,
1120 block_contract_changes,
1121 trace_results: Vec::new(),
1122 partial_block_index: None,
1123 }
1124 }
1125
1126 pub fn into_aggregated(
1141 self,
1142 db_committed_block_height: Option<u64>,
1143 ) -> Result<BlockAggregatedChanges, MergeError> {
1144 if db_committed_block_height.is_some_and(|h| h > self.finalized_block_height) {
1145 return Err(MergeError::InvalidState(format!(
1146 "Database committed block height {:?} is greater than finalized_block_height {}",
1147 db_committed_block_height, self.finalized_block_height
1148 )));
1149 }
1150
1151 let mut iter = self.txs_with_update.into_iter();
1152
1153 let first_state = iter.next().unwrap_or_default();
1154
1155 let aggregated_changes = iter.try_fold(first_state, |mut acc_state, new_state| {
1156 acc_state.merge(new_state.clone())?;
1157 Ok::<_, MergeError>(acc_state.clone())
1158 })?;
1159
1160 let mut aggregated_trace_results = HashMap::new();
1162 for result in self.trace_results {
1163 let external_id = result.entry_point_id();
1164 aggregated_trace_results
1165 .entry(external_id)
1166 .and_modify(|existing: &mut TracingResult| {
1167 existing.merge(result.tracing_result.clone())
1168 })
1169 .or_insert(result.tracing_result);
1170 }
1171
1172 Ok(BlockAggregatedChanges {
1173 extractor: self.extractor,
1174 chain: self.chain,
1175 block: self.block,
1176 db_committed_block_height,
1177 finalized_block_height: self.finalized_block_height,
1178 revert: self.revert,
1179 new_protocol_components: aggregated_changes.protocol_components,
1180 new_tokens: self.new_tokens,
1181 deleted_protocol_components: HashMap::new(),
1182 state_deltas: aggregated_changes.state_updates,
1183 account_deltas: aggregated_changes.account_deltas,
1184 component_balances: aggregated_changes.balance_changes,
1185 account_balances: aggregated_changes.account_balance_changes,
1186 component_tvl: HashMap::new(),
1187 dci_update: DCIUpdate {
1188 new_entrypoints: aggregated_changes.entrypoints,
1189 new_entrypoint_params: aggregated_changes.entrypoint_params,
1190 trace_results: aggregated_trace_results,
1191 },
1192 partial_block_index: self.partial_block_index,
1193 })
1194 }
1195
1196 pub fn protocol_components(&self) -> Vec<ProtocolComponent> {
1197 self.txs_with_update
1198 .iter()
1199 .flat_map(|tx_u| {
1200 tx_u.protocol_components
1201 .values()
1202 .cloned()
1203 })
1204 .collect()
1205 }
1206
1207 pub fn is_partial_block(&self) -> bool {
1209 self.partial_block_index.is_some()
1210 }
1211
1212 pub fn set_partial_block_index(&mut self, index: Option<u32>) {
1214 self.partial_block_index = index;
1215 }
1216
1217 pub fn normalize_block_hash(&mut self) {
1221 let h = self.block.hash.clone();
1222 for tx_with_changes in self.txs_with_update.iter_mut() {
1223 tx_with_changes.tx.block_hash = h.clone();
1224 }
1225 for tx_with_contract in self.block_contract_changes.iter_mut() {
1226 tx_with_contract.tx.block_hash = h.clone();
1227 }
1228 }
1229
1230 pub fn merge_partial(self, other: Self) -> Result<Self, MergeError> {
1247 let Some(self_index) = self.partial_block_index else {
1248 return Err(MergeError::InvalidState("self is not a partial block".to_string()));
1249 };
1250
1251 let Some(other_index) = other.partial_block_index else {
1252 return Err(MergeError::InvalidState("other is not a partial block".to_string()));
1253 };
1254
1255 if self.extractor != other.extractor {
1256 return Err(MergeError::IdMismatch(
1257 "partial blocks (extractor)".to_string(),
1258 self.extractor.clone(),
1259 other.extractor.clone(),
1260 ));
1261 }
1262
1263 if self.chain != other.chain {
1264 return Err(MergeError::IdMismatch(
1265 "partial blocks (chain)".to_string(),
1266 format!("{:?}", self.chain),
1267 format!("{:?}", other.chain),
1268 ));
1269 }
1270
1271 if self.block.number != other.block.number {
1272 return Err(MergeError::BlockMismatch(
1273 "partial blocks".to_string(),
1274 self.block.hash.clone(),
1275 other.block.hash.clone(),
1276 ));
1277 }
1278
1279 if self.revert != other.revert {
1280 return Err(MergeError::InvalidState(format!(
1281 "different revert status: {} vs {}",
1282 self.revert, other.revert
1283 )));
1284 }
1285
1286 let (mut current, previous) = if self_index > other_index {
1287 (self, other)
1288 } else if self_index < other_index {
1289 (other, self)
1290 } else {
1291 return Err(MergeError::InvalidState(format!("same partial block index: {self_index}")));
1292 };
1293
1294 for (addr, token) in previous.new_tokens {
1295 current
1296 .new_tokens
1297 .entry(addr)
1298 .or_insert(token);
1299 }
1300
1301 current
1302 .txs_with_update
1303 .extend(previous.txs_with_update);
1304 current
1305 .txs_with_update
1306 .sort_by_key(|tx| tx.tx.index);
1307
1308 current
1309 .block_contract_changes
1310 .extend(previous.block_contract_changes);
1311
1312 current.normalize_block_hash();
1313
1314 current
1315 .trace_results
1316 .extend(previous.trace_results);
1317
1318 Ok(current)
1319 }
1320}
1321
1322impl BlockScoped for BlockChanges {
1323 fn block(&self) -> Block {
1324 self.block.clone()
1325 }
1326}
1327
1328#[cfg(test)]
1329pub mod fixtures {
1330 use std::str::FromStr;
1331
1332 use rstest::rstest;
1333
1334 use super::*;
1335 use crate::models::ChangeType;
1336
1337 pub fn create_transaction(hash: &str, block: &str, index: u64) -> Transaction {
1340 Transaction::new(
1341 hash.parse().unwrap(),
1342 block.parse().unwrap(),
1343 Bytes::zero(20),
1344 Some(Bytes::zero(20)),
1345 index,
1346 )
1347 }
1348
1349 pub fn tx_with_changes(index: u8) -> TxWithChanges {
1363 let token = Bytes::from(vec![0xaa; 20]);
1364 let token2 = Bytes::from(vec![0xcc; 20]);
1365 let contract = Bytes::from(vec![0xbb; 20]);
1366 let c_id = "pool_0".to_string();
1367
1368 match index {
1369 0 => {
1370 let tx = create_transaction("0x01", "0x00", 1);
1371 TxWithChanges {
1372 tx: tx.clone(),
1373 protocol_components: HashMap::from([(
1374 c_id.clone(),
1375 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1376 )]),
1377 account_deltas: HashMap::from([(
1378 contract.clone(),
1379 AccountDelta::new(
1380 Chain::Ethereum,
1381 contract.clone(),
1382 HashMap::from([
1383 (
1384 Bytes::from(1u64).lpad(32, 0),
1385 Some(Bytes::from(100u64).lpad(32, 0)),
1386 ),
1387 (
1388 Bytes::from(2u64).lpad(32, 0),
1389 Some(Bytes::from(200u64).lpad(32, 0)),
1390 ),
1391 ]),
1392 None,
1393 Some(Bytes::from(vec![0; 4])),
1394 ChangeType::Creation,
1395 ),
1396 )]),
1397 state_updates: HashMap::from([(
1398 c_id.clone(),
1399 ProtocolComponentStateDelta::new(
1400 &c_id,
1401 HashMap::from([
1402 ("reserve".into(), Bytes::from(1000u64).lpad(32, 0)),
1403 ("fee".into(), Bytes::from(50u64).lpad(32, 0)),
1404 ]),
1405 HashSet::new(),
1406 ),
1407 )]),
1408 balance_changes: HashMap::from([(
1409 c_id.clone(),
1410 HashMap::from([
1411 (
1412 token.clone(),
1413 ComponentBalance {
1414 token: token.clone(),
1415 balance: Bytes::from(800_u64).lpad(32, 0),
1416 balance_float: 800.0,
1417 component_id: c_id.clone(),
1418 modify_tx: tx.hash.clone(),
1419 },
1420 ),
1421 (
1422 token2.clone(),
1423 ComponentBalance {
1424 token: token2.clone(),
1425 balance: Bytes::from(300_u64).lpad(32, 0),
1426 balance_float: 300.0,
1427 component_id: c_id.clone(),
1428 modify_tx: tx.hash.clone(),
1429 },
1430 ),
1431 ]),
1432 )]),
1433 account_balance_changes: HashMap::from([(
1434 contract.clone(),
1435 HashMap::from([
1436 (
1437 token.clone(),
1438 AccountBalance {
1439 token: token.clone(),
1440 balance: Bytes::from(500_u64).lpad(32, 0),
1441 modify_tx: tx.hash.clone(),
1442 account: contract.clone(),
1443 },
1444 ),
1445 (
1446 token2,
1447 AccountBalance {
1448 token: Bytes::from(vec![0xcc; 20]),
1449 balance: Bytes::from(150_u64).lpad(32, 0),
1450 modify_tx: tx.hash,
1451 account: contract,
1452 },
1453 ),
1454 ]),
1455 )]),
1456 entrypoints: HashMap::from([(
1457 c_id.clone(),
1458 HashSet::from([EntryPoint::new(
1459 "ep_0".into(),
1460 Bytes::zero(20),
1461 "fn_a()".into(),
1462 )]),
1463 )]),
1464 entrypoint_params: HashMap::from([(
1465 "ep_0".into(),
1466 HashSet::from([(
1467 TracingParams::RPCTracer(RPCTracerParams::new(
1468 None,
1469 Bytes::from(vec![1]),
1470 )),
1471 c_id,
1472 )]),
1473 )]),
1474 }
1475 }
1476 1 => {
1477 let tx = create_transaction("0x02", "0x00", 2);
1478 TxWithChanges {
1479 tx: tx.clone(),
1480 protocol_components: HashMap::from([(
1481 c_id.clone(),
1482 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1483 )]),
1484 account_deltas: HashMap::from([(
1485 contract.clone(),
1486 AccountDelta::new(
1487 Chain::Ethereum,
1488 contract.clone(),
1489 HashMap::from([(
1490 Bytes::from(1u64).lpad(32, 0),
1491 Some(Bytes::from(300u64).lpad(32, 0)),
1492 )]),
1493 None,
1494 None,
1495 ChangeType::Update,
1496 ),
1497 )]),
1498 state_updates: HashMap::from([(
1499 c_id.clone(),
1500 ProtocolComponentStateDelta::new(
1501 &c_id,
1502 HashMap::from([("reserve".into(), Bytes::from(2000u64).lpad(32, 0))]),
1503 HashSet::new(),
1504 ),
1505 )]),
1506 balance_changes: HashMap::from([(
1507 c_id.clone(),
1508 HashMap::from([(
1509 token.clone(),
1510 ComponentBalance {
1511 token: token.clone(),
1512 balance: Bytes::from(1000_u64).lpad(32, 0),
1513 balance_float: 1000.0,
1514 component_id: c_id.clone(),
1515 modify_tx: tx.hash.clone(),
1516 },
1517 )]),
1518 )]),
1519 account_balance_changes: HashMap::from([(
1520 contract.clone(),
1521 HashMap::from([(
1522 token.clone(),
1523 AccountBalance {
1524 token: token.clone(),
1525 balance: Bytes::from(700_u64).lpad(32, 0),
1526 modify_tx: tx.hash,
1527 account: contract,
1528 },
1529 )]),
1530 )]),
1531 entrypoints: HashMap::from([(
1532 c_id.clone(),
1533 HashSet::from([
1534 EntryPoint::new("ep_0".into(), Bytes::zero(20), "fn_a()".into()),
1535 EntryPoint::new("ep_1".into(), Bytes::zero(20), "fn_b()".into()),
1536 ]),
1537 )]),
1538 entrypoint_params: HashMap::from([(
1539 "ep_1".into(),
1540 HashSet::from([(
1541 TracingParams::RPCTracer(RPCTracerParams::new(
1542 None,
1543 Bytes::from(vec![2]),
1544 )),
1545 c_id,
1546 )]),
1547 )]),
1548 }
1549 }
1550 _ => panic!("tx_with_changes: index must be 0 or 1, got {index}"),
1551 }
1552 }
1553
1554 #[test]
1555 fn test_merge_tx_with_changes() {
1556 let mut changes1 = tx_with_changes(0);
1557 let changes2 = tx_with_changes(1);
1558
1559 let token = Bytes::from(vec![0xaa; 20]);
1560 let contract = Bytes::from(vec![0xbb; 20]);
1561 let c_id = "pool_0".to_string();
1562
1563 assert!(changes1.merge(changes2).is_ok());
1564
1565 assert_eq!(
1567 changes1.balance_changes[&c_id][&token].balance,
1568 Bytes::from(1000_u64).lpad(32, 0),
1569 );
1570 assert_eq!(
1571 changes1.account_balance_changes[&contract][&token].balance,
1572 Bytes::from(700_u64).lpad(32, 0),
1573 );
1574 assert_eq!(changes1.tx.hash, Bytes::from(vec![2]));
1576 assert_eq!(changes1.entrypoints[&c_id].len(), 2);
1578 let mut sigs: Vec<_> = changes1.entrypoints[&c_id]
1579 .iter()
1580 .map(|ep| ep.signature.clone())
1581 .collect();
1582 sigs.sort();
1583 assert_eq!(sigs, vec!["fn_a()", "fn_b()"]);
1584 }
1585
1586 #[rstest]
1587 #[case::mismatched_blocks(
1588 fixtures::create_transaction("0x01", "0x0abc", 1),
1589 fixtures::create_transaction("0x02", "0x0def", 2)
1590 )]
1591 #[case::older_transaction(
1592 fixtures::create_transaction("0x02", "0x0abc", 2),
1593 fixtures::create_transaction("0x01", "0x0abc", 1)
1594 )]
1595 fn test_merge_errors(#[case] tx1: Transaction, #[case] tx2: Transaction) {
1596 let mut changes1 = TxWithChanges { tx: tx1, ..Default::default() };
1597
1598 let changes2 = TxWithChanges { tx: tx2, ..Default::default() };
1599
1600 assert!(changes1.merge(changes2).is_err());
1601 }
1602
1603 #[test]
1604 fn test_rpc_tracer_entry_point_serialization_order() {
1605 use std::str::FromStr;
1606
1607 use serde_json;
1608
1609 let entry_point = RPCTracerParams::new(
1610 Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()),
1611 Bytes::from_str("0xabcdef").unwrap(),
1612 );
1613
1614 let serialized = serde_json::to_string(&entry_point).unwrap();
1615
1616 assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"calldata\"").unwrap());
1618
1619 let deserialized: RPCTracerParams = serde_json::from_str(&serialized).unwrap();
1621 assert_eq!(entry_point, deserialized);
1622 }
1623
1624 #[test]
1625 fn test_tracing_result_merge() {
1626 let address1 = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
1627 let address2 = Address::from_str("0x2345678901234567890123456789012345678901").unwrap();
1628 let address3 = Address::from_str("0x3456789012345678901234567890123456789012").unwrap();
1629
1630 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1631 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1632
1633 let mut result1 = TracingResult::new(
1634 HashSet::from([(
1635 address1.clone(),
1636 AddressStorageLocation::new(store_key1.clone(), 12),
1637 )]),
1638 HashMap::from([
1639 (address2.clone(), HashSet::from([store_key1.clone()])),
1640 (address3.clone(), HashSet::from([store_key2.clone()])),
1641 ]),
1642 );
1643
1644 let result2 = TracingResult::new(
1645 HashSet::from([(
1646 address3.clone(),
1647 AddressStorageLocation::new(store_key2.clone(), 12),
1648 )]),
1649 HashMap::from([
1650 (address1.clone(), HashSet::from([store_key1.clone()])),
1651 (address2.clone(), HashSet::from([store_key2.clone()])),
1652 ]),
1653 );
1654
1655 result1.merge(result2);
1656
1657 assert_eq!(result1.retriggers.len(), 2);
1659 assert!(result1
1660 .retriggers
1661 .contains(&(address1.clone(), AddressStorageLocation::new(store_key1.clone(), 12))));
1662 assert!(result1
1663 .retriggers
1664 .contains(&(address3.clone(), AddressStorageLocation::new(store_key2.clone(), 12))));
1665
1666 assert_eq!(result1.accessed_slots.len(), 3);
1668 assert!(result1
1669 .accessed_slots
1670 .contains_key(&address1));
1671 assert!(result1
1672 .accessed_slots
1673 .contains_key(&address2));
1674 assert!(result1
1675 .accessed_slots
1676 .contains_key(&address3));
1677
1678 assert_eq!(
1679 result1
1680 .accessed_slots
1681 .get(&address2)
1682 .unwrap(),
1683 &HashSet::from([store_key1.clone(), store_key2.clone()])
1684 );
1685 }
1686
1687 #[test]
1688 fn test_entry_point_with_tracing_params_display() {
1689 use std::str::FromStr;
1690
1691 let entry_point = EntryPoint::new(
1692 "uniswap_v3_pool_swap".to_string(),
1693 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1694 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1695 );
1696
1697 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1698 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1699 Bytes::from_str("0xabcdef").unwrap(),
1700 ));
1701
1702 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1703
1704 let display_output = entry_point_with_params.to_string();
1705 assert_eq!(display_output, "uniswap_v3_pool_swap [RPC]");
1706 }
1707
1708 #[test]
1709 fn test_traced_entry_point_display() {
1710 use std::str::FromStr;
1711
1712 let entry_point = EntryPoint::new(
1713 "uniswap_v3_pool_swap".to_string(),
1714 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1715 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1716 );
1717
1718 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1719 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1720 Bytes::from_str("0xabcdef").unwrap(),
1721 ));
1722
1723 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1724
1725 let address1 = Address::from_str("0x1111111111111111111111111111111111111111").unwrap();
1727 let address2 = Address::from_str("0x2222222222222222222222222222222222222222").unwrap();
1728 let address3 = Address::from_str("0x3333333333333333333333333333333333333333").unwrap();
1729
1730 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1731 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1732
1733 let tracing_result = TracingResult::new(
1734 HashSet::from([
1735 (address1.clone(), AddressStorageLocation::new(store_key1.clone(), 0)),
1736 (address2.clone(), AddressStorageLocation::new(store_key2.clone(), 12)),
1737 ]),
1738 HashMap::from([
1739 (address1.clone(), HashSet::from([store_key1.clone()])),
1740 (address2.clone(), HashSet::from([store_key2.clone()])),
1741 (address3.clone(), HashSet::from([store_key1.clone()])),
1742 ]),
1743 );
1744
1745 let traced_entry_point = TracedEntryPoint::new(
1746 entry_point_with_params,
1747 Bytes::from_str("0xabcdef1234567890").unwrap(),
1748 tracing_result,
1749 );
1750
1751 let display_output = traced_entry_point.to_string();
1752 assert_eq!(display_output, "[uniswap_v3_pool_swap: 2 retriggers, 3 accessed addresses]");
1753 }
1754}