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