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