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 BlockScoped for BlockAggregatedChanges {
373 fn block(&self) -> Block {
374 self.block.clone()
375 }
376}
377
378impl From<dto::Block> for Block {
379 fn from(value: dto::Block) -> Self {
380 Self {
381 number: value.number,
382 chain: value.chain.into(),
383 hash: value.hash,
384 parent_hash: value.parent_hash,
385 ts: value.ts,
386 }
387 }
388}
389
390impl From<dto::AddressStorageLocation> for AddressStorageLocation {
391 fn from(value: dto::AddressStorageLocation) -> Self {
392 Self { key: value.key, offset: value.offset }
393 }
394}
395
396impl From<dto::TracingResult> for TracingResult {
397 fn from(value: dto::TracingResult) -> Self {
398 Self {
399 retriggers: value
400 .retriggers
401 .into_iter()
402 .map(|(addr, loc)| (addr, loc.into()))
403 .collect(),
404 accessed_slots: value.accessed_slots,
405 }
406 }
407}
408
409impl From<dto::DCIUpdate> for DCIUpdate {
410 fn from(value: dto::DCIUpdate) -> Self {
411 Self {
412 new_entrypoints: value
413 .new_entrypoints
414 .into_iter()
415 .map(|(k, v)| {
416 (
417 k,
418 v.into_iter()
419 .map(EntryPoint::from)
420 .collect(),
421 )
422 })
423 .collect(),
424 new_entrypoint_params: value
425 .new_entrypoint_params
426 .into_iter()
427 .map(|(k, v)| {
428 (
429 k,
430 v.into_iter()
431 .map(|(p, c)| (TracingParams::from(p), c))
432 .collect(),
433 )
434 })
435 .collect(),
436 trace_results: value
437 .trace_results
438 .into_iter()
439 .map(|(k, v)| (k, TracingResult::from(v)))
440 .collect(),
441 }
442 }
443}
444
445impl From<dto::BlockAggregatedChanges> for BlockAggregatedChanges {
446 fn from(value: dto::BlockAggregatedChanges) -> Self {
447 use crate::models::{
448 contract::{AccountBalance, AccountDelta},
449 protocol::{ComponentBalance, ProtocolComponent, ProtocolComponentStateDelta},
450 token::Token,
451 };
452 Self {
453 extractor: value.extractor,
454 chain: value.chain.into(),
455 block: value.block.into(),
456 finalized_block_height: value.finalized_block_height,
457 db_committed_block_height: None,
458 revert: value.revert,
459 state_deltas: value
460 .state_updates
461 .into_iter()
462 .map(|(k, v)| (k, ProtocolComponentStateDelta::from(v)))
463 .collect(),
464 account_deltas: value
465 .account_updates
466 .into_iter()
467 .map(|(k, v)| (k, AccountDelta::from(v)))
468 .collect(),
469 new_tokens: value
470 .new_tokens
471 .into_iter()
472 .map(|(k, v)| (k, Token::from(v)))
473 .collect(),
474 new_protocol_components: value
475 .new_protocol_components
476 .into_iter()
477 .map(|(k, v)| (k, ProtocolComponent::from(v)))
478 .collect(),
479 deleted_protocol_components: value
480 .deleted_protocol_components
481 .into_iter()
482 .map(|(k, v)| (k, ProtocolComponent::from(v)))
483 .collect(),
484 component_balances: value
485 .component_balances
486 .into_iter()
487 .map(|(component_id, token_balances)| {
488 (
489 component_id,
490 token_balances
491 .0
492 .into_iter()
493 .map(|(k, v)| (k, ComponentBalance::from(v)))
494 .collect(),
495 )
496 })
497 .collect(),
498 account_balances: value
499 .account_balances
500 .into_iter()
501 .map(|(account, balances)| {
502 (
503 account,
504 balances
505 .into_iter()
506 .map(|(k, v)| (k, AccountBalance::from(v)))
507 .collect(),
508 )
509 })
510 .collect(),
511 component_tvl: value.component_tvl,
512 dci_update: DCIUpdate::from(value.dci_update),
513 partial_block_index: value.partial_block_index,
514 }
515 }
516}
517
518#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
519pub struct DCIUpdate {
520 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
521 pub new_entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
522 pub trace_results: HashMap<EntryPointId, TracingResult>,
523}
524
525pub type TracedEntryPoints =
530 HashMap<ComponentId, Vec<(EntryPointWithTracingParams, TracingResult)>>;
531
532impl From<TracedEntryPoints> for DCIUpdate {
533 fn from(traced_entry_points: TracedEntryPoints) -> Self {
534 let mut new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>> = HashMap::new();
535 let mut new_entrypoint_params: HashMap<
536 EntryPointId,
537 HashSet<(TracingParams, ComponentId)>,
538 > = HashMap::new();
539 let mut trace_results: HashMap<EntryPointId, TracingResult> = HashMap::new();
540
541 for (component_id, traces) in traced_entry_points {
542 let mut entrypoints = HashSet::new();
543
544 for (ep_with_params, trace) in traces {
545 let ep_id = ep_with_params
546 .entry_point
547 .external_id
548 .clone();
549
550 entrypoints.insert(ep_with_params.entry_point.clone());
551
552 new_entrypoint_params
553 .entry(ep_id.clone())
554 .or_default()
555 .insert((ep_with_params.params, component_id.clone()));
556
557 trace_results
558 .entry(ep_id)
559 .and_modify(|existing: &mut TracingResult| {
560 existing
561 .retriggers
562 .extend(trace.retriggers.clone());
563 for (address, slots) in trace.accessed_slots.clone() {
564 existing
565 .accessed_slots
566 .entry(address)
567 .or_default()
568 .extend(slots);
569 }
570 })
571 .or_insert(trace);
572 }
573
574 if !entrypoints.is_empty() {
575 new_entrypoints.insert(component_id, entrypoints);
576 }
577 }
578
579 DCIUpdate { new_entrypoints, new_entrypoint_params, trace_results }
580 }
581}
582
583#[derive(Debug, Clone, PartialEq, Default, DeepSizeOf)]
585pub struct TxWithChanges {
586 pub tx: Transaction,
587 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
588 pub account_deltas: HashMap<Address, AccountDelta>,
589 pub state_updates: HashMap<ComponentId, ProtocolComponentStateDelta>,
590 pub balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
591 pub account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
592 pub entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
593 pub entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
594}
595
596impl TxWithChanges {
597 #[allow(clippy::too_many_arguments)]
598 pub fn new(
599 tx: Transaction,
600 protocol_components: HashMap<ComponentId, ProtocolComponent>,
601 account_deltas: HashMap<Address, AccountDelta>,
602 protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
603 balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
604 account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
605 entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
606 entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, ComponentId)>>,
607 ) -> Self {
608 Self {
609 tx,
610 account_deltas,
611 protocol_components,
612 state_updates: protocol_states,
613 balance_changes,
614 account_balance_changes,
615 entrypoints,
616 entrypoint_params,
617 }
618 }
619
620 pub fn merge(&mut self, other: TxWithChanges) -> Result<(), MergeError> {
630 if self.tx.block_hash != other.tx.block_hash {
631 return Err(MergeError::BlockMismatch(
632 "TxWithChanges".to_string(),
633 self.tx.block_hash.clone(),
634 other.tx.block_hash,
635 ));
636 }
637 if self.tx.index > other.tx.index {
638 return Err(MergeError::TransactionOrderError(
639 "TxWithChanges".to_string(),
640 self.tx.index,
641 other.tx.index,
642 ));
643 }
644
645 self.tx = other.tx;
646
647 for (key, value) in other.protocol_components {
651 match self.protocol_components.entry(key) {
652 Entry::Occupied(mut entry) => {
653 warn!(
654 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
655 entry.get().id
656 );
657 entry.insert(value);
658 }
659 Entry::Vacant(entry) => {
660 entry.insert(value);
661 }
662 }
663 }
664
665 for (address, update) in other.account_deltas.clone().into_iter() {
667 match self.account_deltas.entry(address) {
668 Entry::Occupied(mut e) => {
669 e.get_mut().merge(update)?;
670 }
671 Entry::Vacant(e) => {
672 e.insert(update);
673 }
674 }
675 }
676
677 for (key, value) in other.state_updates {
679 match self.state_updates.entry(key) {
680 Entry::Occupied(mut entry) => {
681 entry.get_mut().merge(value)?;
682 }
683 Entry::Vacant(entry) => {
684 entry.insert(value);
685 }
686 }
687 }
688
689 for (component_id, balance_changes) in other.balance_changes {
691 let token_balances = self
692 .balance_changes
693 .entry(component_id)
694 .or_default();
695 for (token, balance) in balance_changes {
696 token_balances.insert(token, balance);
697 }
698 }
699
700 for (account_addr, balance_changes) in other.account_balance_changes {
702 let token_balances = self
703 .account_balance_changes
704 .entry(account_addr)
705 .or_default();
706 for (token, balance) in balance_changes {
707 token_balances.insert(token, balance);
708 }
709 }
710
711 for (component_id, entrypoints) in other.entrypoints {
713 self.entrypoints
714 .entry(component_id)
715 .or_default()
716 .extend(entrypoints);
717 }
718
719 for (entrypoint_id, params) in other.entrypoint_params {
721 self.entrypoint_params
722 .entry(entrypoint_id)
723 .or_default()
724 .extend(params);
725 }
726
727 Ok(())
728 }
729}
730
731#[derive(Copy, Clone, Debug, PartialEq)]
732pub enum BlockTag {
733 Finalized,
735 Safe,
737 Latest,
739 Earliest,
741 Pending,
743 Number(u64),
745}
746#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, DeepSizeOf)]
747pub struct EntryPoint {
748 pub external_id: String,
750 pub target: Address,
752 pub signature: String,
754}
755
756impl EntryPoint {
757 pub fn new(external_id: String, target: Address, signature: String) -> Self {
758 Self { external_id, target, signature }
759 }
760}
761
762impl From<dto::EntryPoint> for EntryPoint {
763 fn from(value: dto::EntryPoint) -> Self {
764 Self { external_id: value.external_id, target: value.target, signature: value.signature }
765 }
766}
767
768#[derive(Debug, Clone, PartialEq, Eq, Hash, DeepSizeOf)]
770pub struct EntryPointWithTracingParams {
771 pub entry_point: EntryPoint,
773 pub params: TracingParams,
775}
776
777impl From<dto::EntryPointWithTracingParams> for EntryPointWithTracingParams {
778 fn from(value: dto::EntryPointWithTracingParams) -> Self {
779 match value.params {
780 dto::TracingParams::RPCTracer(ref tracer_params) => Self {
781 entry_point: EntryPoint {
782 external_id: value.entry_point.external_id,
783 target: value.entry_point.target,
784 signature: value.entry_point.signature,
785 },
786 params: TracingParams::RPCTracer(RPCTracerParams {
787 caller: tracer_params.caller.clone(),
788 calldata: tracer_params.calldata.clone(),
789 state_overrides: tracer_params
790 .state_overrides
791 .clone()
792 .map(|s| {
793 s.into_iter()
794 .map(|(k, v)| (k, v.into()))
795 .collect()
796 }),
797 prune_addresses: tracer_params.prune_addresses.clone(),
798 }),
799 },
800 }
801 }
802}
803
804impl EntryPointWithTracingParams {
805 pub fn new(entry_point: EntryPoint, params: TracingParams) -> Self {
806 Self { entry_point, params }
807 }
808}
809
810impl std::fmt::Display for EntryPointWithTracingParams {
811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812 let tracer_type = match &self.params {
813 TracingParams::RPCTracer(_) => "RPC",
814 };
815 write!(f, "{} [{}]", self.entry_point.external_id, tracer_type)
816 }
817}
818
819#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
820pub enum TracingParams {
823 RPCTracer(RPCTracerParams),
825}
826
827impl std::fmt::Display for TracingParams {
828 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
829 match self {
830 TracingParams::RPCTracer(params) => write!(f, "RPC: {params}"),
831 }
832 }
833}
834
835impl From<dto::TracingParams> for TracingParams {
836 fn from(value: dto::TracingParams) -> Self {
837 match value {
838 dto::TracingParams::RPCTracer(tracer_params) => {
839 TracingParams::RPCTracer(tracer_params.into())
840 }
841 }
842 }
843}
844
845#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
846pub enum StorageOverride {
847 Diff(BTreeMap<StoreKey, StoreVal>),
848 Replace(BTreeMap<StoreKey, StoreVal>),
849}
850
851impl From<dto::StorageOverride> for StorageOverride {
852 fn from(value: dto::StorageOverride) -> Self {
853 match value {
854 dto::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
855 dto::StorageOverride::Replace(replace) => StorageOverride::Replace(replace),
856 }
857 }
858}
859
860#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash, DeepSizeOf)]
861pub struct AccountOverrides {
862 pub slots: Option<StorageOverride>,
863 pub native_balance: Option<Balance>,
864 pub code: Option<Code>,
865}
866
867impl From<dto::AccountOverrides> for AccountOverrides {
868 fn from(value: dto::AccountOverrides) -> Self {
869 Self {
870 slots: value.slots.map(|s| s.into()),
871 native_balance: value.native_balance,
872 code: value.code,
873 }
874 }
875}
876
877#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash, DeepSizeOf)]
878pub struct RPCTracerParams {
879 pub caller: Option<Address>,
882 pub calldata: Bytes,
884 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
886 pub prune_addresses: Option<Vec<Address>>,
889}
890
891impl From<dto::RPCTracerParams> for RPCTracerParams {
892 fn from(value: dto::RPCTracerParams) -> Self {
893 Self {
894 caller: value.caller,
895 calldata: value.calldata,
896 state_overrides: value.state_overrides.map(|overrides| {
897 overrides
898 .into_iter()
899 .map(|(address, account_overrides)| (address, account_overrides.into()))
900 .collect()
901 }),
902 prune_addresses: value.prune_addresses,
903 }
904 }
905}
906
907impl RPCTracerParams {
908 pub fn new(caller: Option<Address>, calldata: Bytes) -> Self {
909 Self { caller, calldata, state_overrides: None, prune_addresses: None }
910 }
911
912 pub fn with_state_overrides(mut self, state: BTreeMap<Address, AccountOverrides>) -> Self {
913 self.state_overrides = Some(state);
914 self
915 }
916
917 pub fn with_prune_addresses(mut self, addresses: Vec<Address>) -> Self {
918 self.prune_addresses = Some(addresses);
919 self
920 }
921}
922
923impl std::fmt::Display for RPCTracerParams {
924 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
925 let caller_str = match &self.caller {
926 Some(addr) => format!("caller={addr}"),
927 None => String::new(),
928 };
929
930 let calldata_str = if self.calldata.len() >= 8 {
931 format!(
932 "calldata=0x{}..({} bytes)",
933 hex::encode(&self.calldata[..8]),
934 self.calldata.len()
935 )
936 } else {
937 format!("calldata={}", self.calldata)
938 };
939
940 let overrides_str = match &self.state_overrides {
941 Some(overrides) if !overrides.is_empty() => {
942 format!(", {} state override(s)", overrides.len())
943 }
944 _ => String::new(),
945 };
946
947 write!(f, "{caller_str}, {calldata_str}{overrides_str}")
948 }
949}
950
951impl Serialize for RPCTracerParams {
953 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
954 where
955 S: Serializer,
956 {
957 let mut field_count = 2;
959 if self.state_overrides.is_some() {
960 field_count += 1;
961 }
962 if self.prune_addresses.is_some() {
963 field_count += 1;
964 }
965
966 let mut state = serializer.serialize_struct("RPCTracerEntryPoint", field_count)?;
967 state.serialize_field("caller", &self.caller)?;
968 state.serialize_field("calldata", &self.calldata)?;
969
970 if let Some(ref overrides) = self.state_overrides {
972 state.serialize_field("state_overrides", overrides)?;
973 }
974 if let Some(ref prune_addrs) = self.prune_addresses {
975 state.serialize_field("prune_addresses", prune_addrs)?;
976 }
977
978 state.end()
979 }
980}
981
982#[derive(
983 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, DeepSizeOf,
984)]
985pub struct AddressStorageLocation {
986 pub key: StoreKey,
987 pub offset: u8,
988}
989
990impl AddressStorageLocation {
991 pub fn new(key: StoreKey, offset: u8) -> Self {
992 Self { key, offset }
993 }
994}
995
996#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, DeepSizeOf)]
997pub struct TracingResult {
998 pub retriggers: HashSet<(Address, AddressStorageLocation)>,
1001 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1004}
1005
1006impl TracingResult {
1007 pub fn new(
1008 retriggers: HashSet<(Address, AddressStorageLocation)>,
1009 accessed_slots: HashMap<Address, HashSet<StoreKey>>,
1010 ) -> Self {
1011 Self { retriggers, accessed_slots }
1012 }
1013
1014 pub fn merge(&mut self, other: TracingResult) {
1018 self.retriggers.extend(other.retriggers);
1019 for (address, slots) in other.accessed_slots {
1020 self.accessed_slots
1021 .entry(address)
1022 .or_default()
1023 .extend(slots);
1024 }
1025 }
1026}
1027
1028#[derive(Debug, Clone, PartialEq, DeepSizeOf)]
1029pub struct TracedEntryPoint {
1031 pub entry_point_with_params: EntryPointWithTracingParams,
1033 pub detection_block_hash: BlockHash,
1035 pub tracing_result: TracingResult,
1037}
1038
1039impl TracedEntryPoint {
1040 pub fn new(
1041 entry_point_with_params: EntryPointWithTracingParams,
1042 detection_block_hash: BlockHash,
1043 result: TracingResult,
1044 ) -> Self {
1045 Self { entry_point_with_params, detection_block_hash, tracing_result: result }
1046 }
1047
1048 pub fn entry_point_id(&self) -> String {
1049 self.entry_point_with_params
1050 .entry_point
1051 .external_id
1052 .clone()
1053 }
1054}
1055
1056impl std::fmt::Display for TracedEntryPoint {
1057 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1058 write!(
1059 f,
1060 "[{}: {} retriggers, {} accessed addresses]",
1061 self.entry_point_id(),
1062 self.tracing_result.retriggers.len(),
1063 self.tracing_result.accessed_slots.len()
1064 )
1065 }
1066}
1067
1068#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
1070pub struct TxWithContractChanges {
1071 pub tx: Transaction,
1072 pub contract_changes: AccountToContractChanges,
1073}
1074
1075#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
1076pub struct BlockChanges {
1077 pub extractor: String,
1078 pub chain: Chain,
1079 pub block: Block,
1080 pub finalized_block_height: u64,
1081 pub revert: bool,
1082 pub new_tokens: HashMap<Address, Token>,
1083 pub txs_with_update: Vec<TxWithChanges>,
1085 pub block_contract_changes: Vec<TxWithContractChanges>,
1088 pub trace_results: Vec<TracedEntryPoint>,
1092 pub partial_block_index: Option<u32>,
1094}
1095
1096impl BlockChanges {
1097 pub fn new(
1098 extractor: String,
1099 chain: Chain,
1100 block: Block,
1101 finalized_block_height: u64,
1102 revert: bool,
1103 txs_with_update: Vec<TxWithChanges>,
1104 block_contract_changes: Vec<TxWithContractChanges>,
1105 ) -> Self {
1106 BlockChanges {
1107 extractor,
1108 chain,
1109 block,
1110 finalized_block_height,
1111 revert,
1112 new_tokens: HashMap::new(),
1113 txs_with_update,
1114 block_contract_changes,
1115 trace_results: Vec::new(),
1116 partial_block_index: None,
1117 }
1118 }
1119
1120 pub fn into_aggregated(
1135 self,
1136 db_committed_block_height: Option<u64>,
1137 ) -> Result<BlockAggregatedChanges, MergeError> {
1138 if db_committed_block_height.is_some_and(|h| h > self.finalized_block_height) {
1139 return Err(MergeError::InvalidState(format!(
1140 "Database committed block height {:?} is greater than finalized_block_height {}",
1141 db_committed_block_height, self.finalized_block_height
1142 )));
1143 }
1144
1145 let mut iter = self.txs_with_update.into_iter();
1146
1147 let first_state = iter.next().unwrap_or_default();
1148
1149 let aggregated_changes = iter.try_fold(first_state, |mut acc_state, new_state| {
1150 acc_state.merge(new_state.clone())?;
1151 Ok::<_, MergeError>(acc_state.clone())
1152 })?;
1153
1154 let mut aggregated_trace_results = HashMap::new();
1156 for result in self.trace_results {
1157 let external_id = result.entry_point_id();
1158 aggregated_trace_results
1159 .entry(external_id)
1160 .and_modify(|existing: &mut TracingResult| {
1161 existing.merge(result.tracing_result.clone())
1162 })
1163 .or_insert(result.tracing_result);
1164 }
1165
1166 Ok(BlockAggregatedChanges {
1167 extractor: self.extractor,
1168 chain: self.chain,
1169 block: self.block,
1170 db_committed_block_height,
1171 finalized_block_height: self.finalized_block_height,
1172 revert: self.revert,
1173 new_protocol_components: aggregated_changes.protocol_components,
1174 new_tokens: self.new_tokens,
1175 deleted_protocol_components: HashMap::new(),
1176 state_deltas: aggregated_changes.state_updates,
1177 account_deltas: aggregated_changes.account_deltas,
1178 component_balances: aggregated_changes.balance_changes,
1179 account_balances: aggregated_changes.account_balance_changes,
1180 component_tvl: HashMap::new(),
1181 dci_update: DCIUpdate {
1182 new_entrypoints: aggregated_changes.entrypoints,
1183 new_entrypoint_params: aggregated_changes.entrypoint_params,
1184 trace_results: aggregated_trace_results,
1185 },
1186 partial_block_index: self.partial_block_index,
1187 })
1188 }
1189
1190 pub fn protocol_components(&self) -> Vec<ProtocolComponent> {
1191 self.txs_with_update
1192 .iter()
1193 .flat_map(|tx_u| {
1194 tx_u.protocol_components
1195 .values()
1196 .cloned()
1197 })
1198 .collect()
1199 }
1200
1201 pub fn is_partial_block(&self) -> bool {
1203 self.partial_block_index.is_some()
1204 }
1205
1206 pub fn set_partial_block_index(&mut self, index: Option<u32>) {
1208 self.partial_block_index = index;
1209 }
1210
1211 pub fn normalize_block_hash(&mut self) {
1215 let h = self.block.hash.clone();
1216 for tx_with_changes in self.txs_with_update.iter_mut() {
1217 tx_with_changes.tx.block_hash = h.clone();
1218 }
1219 for tx_with_contract in self.block_contract_changes.iter_mut() {
1220 tx_with_contract.tx.block_hash = h.clone();
1221 }
1222 }
1223
1224 pub fn merge_partial(self, other: Self) -> Result<Self, MergeError> {
1241 let Some(self_index) = self.partial_block_index else {
1242 return Err(MergeError::InvalidState("self is not a partial block".to_string()));
1243 };
1244
1245 let Some(other_index) = other.partial_block_index else {
1246 return Err(MergeError::InvalidState("other is not a partial block".to_string()));
1247 };
1248
1249 if self.extractor != other.extractor {
1250 return Err(MergeError::IdMismatch(
1251 "partial blocks (extractor)".to_string(),
1252 self.extractor.clone(),
1253 other.extractor.clone(),
1254 ));
1255 }
1256
1257 if self.chain != other.chain {
1258 return Err(MergeError::IdMismatch(
1259 "partial blocks (chain)".to_string(),
1260 format!("{:?}", self.chain),
1261 format!("{:?}", other.chain),
1262 ));
1263 }
1264
1265 if self.block.number != other.block.number {
1266 return Err(MergeError::BlockMismatch(
1267 "partial blocks".to_string(),
1268 self.block.hash.clone(),
1269 other.block.hash.clone(),
1270 ));
1271 }
1272
1273 if self.revert != other.revert {
1274 return Err(MergeError::InvalidState(format!(
1275 "different revert status: {} vs {}",
1276 self.revert, other.revert
1277 )));
1278 }
1279
1280 let (mut current, previous) = if self_index > other_index {
1281 (self, other)
1282 } else if self_index < other_index {
1283 (other, self)
1284 } else {
1285 return Err(MergeError::InvalidState(format!("same partial block index: {self_index}")));
1286 };
1287
1288 for (addr, token) in previous.new_tokens {
1289 current
1290 .new_tokens
1291 .entry(addr)
1292 .or_insert(token);
1293 }
1294
1295 current
1296 .txs_with_update
1297 .extend(previous.txs_with_update);
1298 current
1299 .txs_with_update
1300 .sort_by_key(|tx| tx.tx.index);
1301
1302 current
1303 .block_contract_changes
1304 .extend(previous.block_contract_changes);
1305
1306 current.normalize_block_hash();
1307
1308 current
1309 .trace_results
1310 .extend(previous.trace_results);
1311
1312 Ok(current)
1313 }
1314}
1315
1316impl BlockScoped for BlockChanges {
1317 fn block(&self) -> Block {
1318 self.block.clone()
1319 }
1320}
1321
1322#[cfg(test)]
1323pub mod fixtures {
1324 use std::str::FromStr;
1325
1326 use rstest::rstest;
1327
1328 use super::*;
1329 use crate::models::ChangeType;
1330
1331 pub fn create_transaction(hash: &str, block: &str, index: u64) -> Transaction {
1334 Transaction::new(
1335 hash.parse().unwrap(),
1336 block.parse().unwrap(),
1337 Bytes::zero(20),
1338 Some(Bytes::zero(20)),
1339 index,
1340 )
1341 }
1342
1343 pub fn tx_with_changes(index: u8) -> TxWithChanges {
1357 let token = Bytes::from(vec![0xaa; 20]);
1358 let token2 = Bytes::from(vec![0xcc; 20]);
1359 let contract = Bytes::from(vec![0xbb; 20]);
1360 let c_id = "pool_0".to_string();
1361
1362 match index {
1363 0 => {
1364 let tx = create_transaction("0x01", "0x00", 1);
1365 TxWithChanges {
1366 tx: tx.clone(),
1367 protocol_components: HashMap::from([(
1368 c_id.clone(),
1369 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1370 )]),
1371 account_deltas: HashMap::from([(
1372 contract.clone(),
1373 AccountDelta::new(
1374 Chain::Ethereum,
1375 contract.clone(),
1376 HashMap::from([
1377 (
1378 Bytes::from(1u64).lpad(32, 0),
1379 Some(Bytes::from(100u64).lpad(32, 0)),
1380 ),
1381 (
1382 Bytes::from(2u64).lpad(32, 0),
1383 Some(Bytes::from(200u64).lpad(32, 0)),
1384 ),
1385 ]),
1386 None,
1387 Some(Bytes::from(vec![0; 4])),
1388 ChangeType::Creation,
1389 ),
1390 )]),
1391 state_updates: HashMap::from([(
1392 c_id.clone(),
1393 ProtocolComponentStateDelta::new(
1394 &c_id,
1395 HashMap::from([
1396 ("reserve".into(), Bytes::from(1000u64).lpad(32, 0)),
1397 ("fee".into(), Bytes::from(50u64).lpad(32, 0)),
1398 ]),
1399 HashSet::new(),
1400 ),
1401 )]),
1402 balance_changes: HashMap::from([(
1403 c_id.clone(),
1404 HashMap::from([
1405 (
1406 token.clone(),
1407 ComponentBalance {
1408 token: token.clone(),
1409 balance: Bytes::from(800_u64).lpad(32, 0),
1410 balance_float: 800.0,
1411 component_id: c_id.clone(),
1412 modify_tx: tx.hash.clone(),
1413 },
1414 ),
1415 (
1416 token2.clone(),
1417 ComponentBalance {
1418 token: token2.clone(),
1419 balance: Bytes::from(300_u64).lpad(32, 0),
1420 balance_float: 300.0,
1421 component_id: c_id.clone(),
1422 modify_tx: tx.hash.clone(),
1423 },
1424 ),
1425 ]),
1426 )]),
1427 account_balance_changes: HashMap::from([(
1428 contract.clone(),
1429 HashMap::from([
1430 (
1431 token.clone(),
1432 AccountBalance {
1433 token: token.clone(),
1434 balance: Bytes::from(500_u64).lpad(32, 0),
1435 modify_tx: tx.hash.clone(),
1436 account: contract.clone(),
1437 },
1438 ),
1439 (
1440 token2,
1441 AccountBalance {
1442 token: Bytes::from(vec![0xcc; 20]),
1443 balance: Bytes::from(150_u64).lpad(32, 0),
1444 modify_tx: tx.hash,
1445 account: contract,
1446 },
1447 ),
1448 ]),
1449 )]),
1450 entrypoints: HashMap::from([(
1451 c_id.clone(),
1452 HashSet::from([EntryPoint::new(
1453 "ep_0".into(),
1454 Bytes::zero(20),
1455 "fn_a()".into(),
1456 )]),
1457 )]),
1458 entrypoint_params: HashMap::from([(
1459 "ep_0".into(),
1460 HashSet::from([(
1461 TracingParams::RPCTracer(RPCTracerParams::new(
1462 None,
1463 Bytes::from(vec![1]),
1464 )),
1465 c_id,
1466 )]),
1467 )]),
1468 }
1469 }
1470 1 => {
1471 let tx = create_transaction("0x02", "0x00", 2);
1472 TxWithChanges {
1473 tx: tx.clone(),
1474 protocol_components: HashMap::from([(
1475 c_id.clone(),
1476 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1477 )]),
1478 account_deltas: HashMap::from([(
1479 contract.clone(),
1480 AccountDelta::new(
1481 Chain::Ethereum,
1482 contract.clone(),
1483 HashMap::from([(
1484 Bytes::from(1u64).lpad(32, 0),
1485 Some(Bytes::from(300u64).lpad(32, 0)),
1486 )]),
1487 None,
1488 None,
1489 ChangeType::Update,
1490 ),
1491 )]),
1492 state_updates: HashMap::from([(
1493 c_id.clone(),
1494 ProtocolComponentStateDelta::new(
1495 &c_id,
1496 HashMap::from([("reserve".into(), Bytes::from(2000u64).lpad(32, 0))]),
1497 HashSet::new(),
1498 ),
1499 )]),
1500 balance_changes: HashMap::from([(
1501 c_id.clone(),
1502 HashMap::from([(
1503 token.clone(),
1504 ComponentBalance {
1505 token: token.clone(),
1506 balance: Bytes::from(1000_u64).lpad(32, 0),
1507 balance_float: 1000.0,
1508 component_id: c_id.clone(),
1509 modify_tx: tx.hash.clone(),
1510 },
1511 )]),
1512 )]),
1513 account_balance_changes: HashMap::from([(
1514 contract.clone(),
1515 HashMap::from([(
1516 token.clone(),
1517 AccountBalance {
1518 token: token.clone(),
1519 balance: Bytes::from(700_u64).lpad(32, 0),
1520 modify_tx: tx.hash,
1521 account: contract,
1522 },
1523 )]),
1524 )]),
1525 entrypoints: HashMap::from([(
1526 c_id.clone(),
1527 HashSet::from([
1528 EntryPoint::new("ep_0".into(), Bytes::zero(20), "fn_a()".into()),
1529 EntryPoint::new("ep_1".into(), Bytes::zero(20), "fn_b()".into()),
1530 ]),
1531 )]),
1532 entrypoint_params: HashMap::from([(
1533 "ep_1".into(),
1534 HashSet::from([(
1535 TracingParams::RPCTracer(RPCTracerParams::new(
1536 None,
1537 Bytes::from(vec![2]),
1538 )),
1539 c_id,
1540 )]),
1541 )]),
1542 }
1543 }
1544 _ => panic!("tx_with_changes: index must be 0 or 1, got {index}"),
1545 }
1546 }
1547
1548 #[test]
1549 fn test_merge_tx_with_changes() {
1550 let mut changes1 = tx_with_changes(0);
1551 let changes2 = tx_with_changes(1);
1552
1553 let token = Bytes::from(vec![0xaa; 20]);
1554 let contract = Bytes::from(vec![0xbb; 20]);
1555 let c_id = "pool_0".to_string();
1556
1557 assert!(changes1.merge(changes2).is_ok());
1558
1559 assert_eq!(
1561 changes1.balance_changes[&c_id][&token].balance,
1562 Bytes::from(1000_u64).lpad(32, 0),
1563 );
1564 assert_eq!(
1565 changes1.account_balance_changes[&contract][&token].balance,
1566 Bytes::from(700_u64).lpad(32, 0),
1567 );
1568 assert_eq!(changes1.tx.hash, Bytes::from(vec![2]));
1570 assert_eq!(changes1.entrypoints[&c_id].len(), 2);
1572 let mut sigs: Vec<_> = changes1.entrypoints[&c_id]
1573 .iter()
1574 .map(|ep| ep.signature.clone())
1575 .collect();
1576 sigs.sort();
1577 assert_eq!(sigs, vec!["fn_a()", "fn_b()"]);
1578 }
1579
1580 #[rstest]
1581 #[case::mismatched_blocks(
1582 fixtures::create_transaction("0x01", "0x0abc", 1),
1583 fixtures::create_transaction("0x02", "0x0def", 2)
1584 )]
1585 #[case::older_transaction(
1586 fixtures::create_transaction("0x02", "0x0abc", 2),
1587 fixtures::create_transaction("0x01", "0x0abc", 1)
1588 )]
1589 fn test_merge_errors(#[case] tx1: Transaction, #[case] tx2: Transaction) {
1590 let mut changes1 = TxWithChanges { tx: tx1, ..Default::default() };
1591
1592 let changes2 = TxWithChanges { tx: tx2, ..Default::default() };
1593
1594 assert!(changes1.merge(changes2).is_err());
1595 }
1596
1597 #[test]
1598 fn test_rpc_tracer_entry_point_serialization_order() {
1599 use std::str::FromStr;
1600
1601 use serde_json;
1602
1603 let entry_point = RPCTracerParams::new(
1604 Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()),
1605 Bytes::from_str("0xabcdef").unwrap(),
1606 );
1607
1608 let serialized = serde_json::to_string(&entry_point).unwrap();
1609
1610 assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"calldata\"").unwrap());
1612
1613 let deserialized: RPCTracerParams = serde_json::from_str(&serialized).unwrap();
1615 assert_eq!(entry_point, deserialized);
1616 }
1617
1618 #[test]
1619 fn test_tracing_result_merge() {
1620 let address1 = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
1621 let address2 = Address::from_str("0x2345678901234567890123456789012345678901").unwrap();
1622 let address3 = Address::from_str("0x3456789012345678901234567890123456789012").unwrap();
1623
1624 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1625 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1626
1627 let mut result1 = TracingResult::new(
1628 HashSet::from([(
1629 address1.clone(),
1630 AddressStorageLocation::new(store_key1.clone(), 12),
1631 )]),
1632 HashMap::from([
1633 (address2.clone(), HashSet::from([store_key1.clone()])),
1634 (address3.clone(), HashSet::from([store_key2.clone()])),
1635 ]),
1636 );
1637
1638 let result2 = TracingResult::new(
1639 HashSet::from([(
1640 address3.clone(),
1641 AddressStorageLocation::new(store_key2.clone(), 12),
1642 )]),
1643 HashMap::from([
1644 (address1.clone(), HashSet::from([store_key1.clone()])),
1645 (address2.clone(), HashSet::from([store_key2.clone()])),
1646 ]),
1647 );
1648
1649 result1.merge(result2);
1650
1651 assert_eq!(result1.retriggers.len(), 2);
1653 assert!(result1
1654 .retriggers
1655 .contains(&(address1.clone(), AddressStorageLocation::new(store_key1.clone(), 12))));
1656 assert!(result1
1657 .retriggers
1658 .contains(&(address3.clone(), AddressStorageLocation::new(store_key2.clone(), 12))));
1659
1660 assert_eq!(result1.accessed_slots.len(), 3);
1662 assert!(result1
1663 .accessed_slots
1664 .contains_key(&address1));
1665 assert!(result1
1666 .accessed_slots
1667 .contains_key(&address2));
1668 assert!(result1
1669 .accessed_slots
1670 .contains_key(&address3));
1671
1672 assert_eq!(
1673 result1
1674 .accessed_slots
1675 .get(&address2)
1676 .unwrap(),
1677 &HashSet::from([store_key1.clone(), store_key2.clone()])
1678 );
1679 }
1680
1681 #[test]
1682 fn test_entry_point_with_tracing_params_display() {
1683 use std::str::FromStr;
1684
1685 let entry_point = EntryPoint::new(
1686 "uniswap_v3_pool_swap".to_string(),
1687 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1688 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1689 );
1690
1691 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1692 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1693 Bytes::from_str("0xabcdef").unwrap(),
1694 ));
1695
1696 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1697
1698 let display_output = entry_point_with_params.to_string();
1699 assert_eq!(display_output, "uniswap_v3_pool_swap [RPC]");
1700 }
1701
1702 #[test]
1703 fn test_traced_entry_point_display() {
1704 use std::str::FromStr;
1705
1706 let entry_point = EntryPoint::new(
1707 "uniswap_v3_pool_swap".to_string(),
1708 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1709 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1710 );
1711
1712 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1713 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1714 Bytes::from_str("0xabcdef").unwrap(),
1715 ));
1716
1717 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1718
1719 let address1 = Address::from_str("0x1111111111111111111111111111111111111111").unwrap();
1721 let address2 = Address::from_str("0x2222222222222222222222222222222222222222").unwrap();
1722 let address3 = Address::from_str("0x3333333333333333333333333333333333333333").unwrap();
1723
1724 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1725 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1726
1727 let tracing_result = TracingResult::new(
1728 HashSet::from([
1729 (address1.clone(), AddressStorageLocation::new(store_key1.clone(), 0)),
1730 (address2.clone(), AddressStorageLocation::new(store_key2.clone(), 12)),
1731 ]),
1732 HashMap::from([
1733 (address1.clone(), HashSet::from([store_key1.clone()])),
1734 (address2.clone(), HashSet::from([store_key2.clone()])),
1735 (address3.clone(), HashSet::from([store_key1.clone()])),
1736 ]),
1737 );
1738
1739 let traced_entry_point = TracedEntryPoint::new(
1740 entry_point_with_params,
1741 Bytes::from_str("0xabcdef1234567890").unwrap(),
1742 tracing_result,
1743 );
1744
1745 let display_output = traced_entry_point.to_string();
1746 assert_eq!(display_output, "[uniswap_v3_pool_swap: 2 retriggers, 3 accessed addresses]");
1747 }
1748}