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},
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#[cfg(test)]
1039pub mod fixtures {
1040 use std::str::FromStr;
1041
1042 use rstest::rstest;
1043
1044 use super::*;
1045 use crate::models::ChangeType;
1046
1047 pub fn create_transaction(hash: &str, block: &str, index: u64) -> Transaction {
1050 Transaction::new(
1051 hash.parse().unwrap(),
1052 block.parse().unwrap(),
1053 Bytes::zero(20),
1054 Some(Bytes::zero(20)),
1055 index,
1056 )
1057 }
1058
1059 pub fn tx_with_changes(index: u8) -> TxWithChanges {
1073 let token = Bytes::from(vec![0xaa; 20]);
1074 let token2 = Bytes::from(vec![0xcc; 20]);
1075 let contract = Bytes::from(vec![0xbb; 20]);
1076 let c_id = "pool_0".to_string();
1077
1078 match index {
1079 0 => {
1080 let tx = create_transaction("0x01", "0x00", 1);
1081 TxWithChanges {
1082 tx: tx.clone(),
1083 protocol_components: HashMap::from([(
1084 c_id.clone(),
1085 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1086 )]),
1087 account_deltas: HashMap::from([(
1088 contract.clone(),
1089 AccountDelta::new(
1090 Chain::Ethereum,
1091 contract.clone(),
1092 HashMap::from([
1093 (
1094 Bytes::from(1u64).lpad(32, 0),
1095 Some(Bytes::from(100u64).lpad(32, 0)),
1096 ),
1097 (
1098 Bytes::from(2u64).lpad(32, 0),
1099 Some(Bytes::from(200u64).lpad(32, 0)),
1100 ),
1101 ]),
1102 None,
1103 Some(Bytes::from(vec![0; 4])),
1104 ChangeType::Creation,
1105 ),
1106 )]),
1107 state_updates: HashMap::from([(
1108 c_id.clone(),
1109 ProtocolComponentStateDelta::new(
1110 &c_id,
1111 HashMap::from([
1112 ("reserve".into(), Bytes::from(1000u64).lpad(32, 0)),
1113 ("fee".into(), Bytes::from(50u64).lpad(32, 0)),
1114 ]),
1115 HashSet::new(),
1116 ),
1117 )]),
1118 balance_changes: HashMap::from([(
1119 c_id.clone(),
1120 HashMap::from([
1121 (
1122 token.clone(),
1123 ComponentBalance {
1124 token: token.clone(),
1125 balance: Bytes::from(800_u64).lpad(32, 0),
1126 balance_float: 800.0,
1127 component_id: c_id.clone(),
1128 modify_tx: tx.hash.clone(),
1129 },
1130 ),
1131 (
1132 token2.clone(),
1133 ComponentBalance {
1134 token: token2.clone(),
1135 balance: Bytes::from(300_u64).lpad(32, 0),
1136 balance_float: 300.0,
1137 component_id: c_id.clone(),
1138 modify_tx: tx.hash.clone(),
1139 },
1140 ),
1141 ]),
1142 )]),
1143 account_balance_changes: HashMap::from([(
1144 contract.clone(),
1145 HashMap::from([
1146 (
1147 token.clone(),
1148 AccountBalance {
1149 token: token.clone(),
1150 balance: Bytes::from(500_u64).lpad(32, 0),
1151 modify_tx: tx.hash.clone(),
1152 account: contract.clone(),
1153 },
1154 ),
1155 (
1156 token2,
1157 AccountBalance {
1158 token: Bytes::from(vec![0xcc; 20]),
1159 balance: Bytes::from(150_u64).lpad(32, 0),
1160 modify_tx: tx.hash,
1161 account: contract,
1162 },
1163 ),
1164 ]),
1165 )]),
1166 entrypoints: HashMap::from([(
1167 c_id.clone(),
1168 HashSet::from([EntryPoint::new(
1169 "ep_0".into(),
1170 Bytes::zero(20),
1171 "fn_a()".into(),
1172 )]),
1173 )]),
1174 entrypoint_params: HashMap::from([(
1175 "ep_0".into(),
1176 HashSet::from([(
1177 TracingParams::RPCTracer(RPCTracerParams::new(
1178 None,
1179 Bytes::from(vec![1]),
1180 )),
1181 c_id,
1182 )]),
1183 )]),
1184 }
1185 }
1186 1 => {
1187 let tx = create_transaction("0x02", "0x00", 2);
1188 TxWithChanges {
1189 tx: tx.clone(),
1190 protocol_components: HashMap::from([(
1191 c_id.clone(),
1192 ProtocolComponent { id: c_id.clone(), ..Default::default() },
1193 )]),
1194 account_deltas: HashMap::from([(
1195 contract.clone(),
1196 AccountDelta::new(
1197 Chain::Ethereum,
1198 contract.clone(),
1199 HashMap::from([(
1200 Bytes::from(1u64).lpad(32, 0),
1201 Some(Bytes::from(300u64).lpad(32, 0)),
1202 )]),
1203 None,
1204 None,
1205 ChangeType::Update,
1206 ),
1207 )]),
1208 state_updates: HashMap::from([(
1209 c_id.clone(),
1210 ProtocolComponentStateDelta::new(
1211 &c_id,
1212 HashMap::from([("reserve".into(), Bytes::from(2000u64).lpad(32, 0))]),
1213 HashSet::new(),
1214 ),
1215 )]),
1216 balance_changes: HashMap::from([(
1217 c_id.clone(),
1218 HashMap::from([(
1219 token.clone(),
1220 ComponentBalance {
1221 token: token.clone(),
1222 balance: Bytes::from(1000_u64).lpad(32, 0),
1223 balance_float: 1000.0,
1224 component_id: c_id.clone(),
1225 modify_tx: tx.hash.clone(),
1226 },
1227 )]),
1228 )]),
1229 account_balance_changes: HashMap::from([(
1230 contract.clone(),
1231 HashMap::from([(
1232 token.clone(),
1233 AccountBalance {
1234 token: token.clone(),
1235 balance: Bytes::from(700_u64).lpad(32, 0),
1236 modify_tx: tx.hash,
1237 account: contract,
1238 },
1239 )]),
1240 )]),
1241 entrypoints: HashMap::from([(
1242 c_id.clone(),
1243 HashSet::from([
1244 EntryPoint::new("ep_0".into(), Bytes::zero(20), "fn_a()".into()),
1245 EntryPoint::new("ep_1".into(), Bytes::zero(20), "fn_b()".into()),
1246 ]),
1247 )]),
1248 entrypoint_params: HashMap::from([(
1249 "ep_1".into(),
1250 HashSet::from([(
1251 TracingParams::RPCTracer(RPCTracerParams::new(
1252 None,
1253 Bytes::from(vec![2]),
1254 )),
1255 c_id,
1256 )]),
1257 )]),
1258 }
1259 }
1260 _ => panic!("tx_with_changes: index must be 0 or 1, got {index}"),
1261 }
1262 }
1263
1264 #[test]
1265 fn test_merge_tx_with_changes() {
1266 let mut changes1 = tx_with_changes(0);
1267 let changes2 = tx_with_changes(1);
1268
1269 let token = Bytes::from(vec![0xaa; 20]);
1270 let contract = Bytes::from(vec![0xbb; 20]);
1271 let c_id = "pool_0".to_string();
1272
1273 assert!(changes1.merge(changes2).is_ok());
1274
1275 assert_eq!(
1277 changes1.balance_changes[&c_id][&token].balance,
1278 Bytes::from(1000_u64).lpad(32, 0),
1279 );
1280 assert_eq!(
1281 changes1.account_balance_changes[&contract][&token].balance,
1282 Bytes::from(700_u64).lpad(32, 0),
1283 );
1284 assert_eq!(changes1.tx.hash, Bytes::from(vec![2]));
1286 assert_eq!(changes1.entrypoints[&c_id].len(), 2);
1288 let mut sigs: Vec<_> = changes1.entrypoints[&c_id]
1289 .iter()
1290 .map(|ep| ep.signature.clone())
1291 .collect();
1292 sigs.sort();
1293 assert_eq!(sigs, vec!["fn_a()", "fn_b()"]);
1294 }
1295
1296 #[rstest]
1297 #[case::mismatched_blocks(
1298 fixtures::create_transaction("0x01", "0x0abc", 1),
1299 fixtures::create_transaction("0x02", "0x0def", 2)
1300 )]
1301 #[case::older_transaction(
1302 fixtures::create_transaction("0x02", "0x0abc", 2),
1303 fixtures::create_transaction("0x01", "0x0abc", 1)
1304 )]
1305 fn test_merge_errors(#[case] tx1: Transaction, #[case] tx2: Transaction) {
1306 let mut changes1 = TxWithChanges { tx: tx1, ..Default::default() };
1307
1308 let changes2 = TxWithChanges { tx: tx2, ..Default::default() };
1309
1310 assert!(changes1.merge(changes2).is_err());
1311 }
1312
1313 #[test]
1314 fn test_rpc_tracer_entry_point_serialization_order() {
1315 use std::str::FromStr;
1316
1317 use serde_json;
1318
1319 let entry_point = RPCTracerParams::new(
1320 Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()),
1321 Bytes::from_str("0xabcdef").unwrap(),
1322 );
1323
1324 let serialized = serde_json::to_string(&entry_point).unwrap();
1325
1326 assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"calldata\"").unwrap());
1328
1329 let deserialized: RPCTracerParams = serde_json::from_str(&serialized).unwrap();
1331 assert_eq!(entry_point, deserialized);
1332 }
1333
1334 #[test]
1335 fn test_tracing_result_merge() {
1336 let address1 = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
1337 let address2 = Address::from_str("0x2345678901234567890123456789012345678901").unwrap();
1338 let address3 = Address::from_str("0x3456789012345678901234567890123456789012").unwrap();
1339
1340 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1341 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1342
1343 let mut result1 = TracingResult::new(
1344 HashSet::from([(
1345 address1.clone(),
1346 AddressStorageLocation::new(store_key1.clone(), 12),
1347 )]),
1348 HashMap::from([
1349 (address2.clone(), HashSet::from([store_key1.clone()])),
1350 (address3.clone(), HashSet::from([store_key2.clone()])),
1351 ]),
1352 );
1353
1354 let result2 = TracingResult::new(
1355 HashSet::from([(
1356 address3.clone(),
1357 AddressStorageLocation::new(store_key2.clone(), 12),
1358 )]),
1359 HashMap::from([
1360 (address1.clone(), HashSet::from([store_key1.clone()])),
1361 (address2.clone(), HashSet::from([store_key2.clone()])),
1362 ]),
1363 );
1364
1365 result1.merge(result2);
1366
1367 assert_eq!(result1.retriggers.len(), 2);
1369 assert!(result1
1370 .retriggers
1371 .contains(&(address1.clone(), AddressStorageLocation::new(store_key1.clone(), 12))));
1372 assert!(result1
1373 .retriggers
1374 .contains(&(address3.clone(), AddressStorageLocation::new(store_key2.clone(), 12))));
1375
1376 assert_eq!(result1.accessed_slots.len(), 3);
1378 assert!(result1
1379 .accessed_slots
1380 .contains_key(&address1));
1381 assert!(result1
1382 .accessed_slots
1383 .contains_key(&address2));
1384 assert!(result1
1385 .accessed_slots
1386 .contains_key(&address3));
1387
1388 assert_eq!(
1389 result1
1390 .accessed_slots
1391 .get(&address2)
1392 .unwrap(),
1393 &HashSet::from([store_key1.clone(), store_key2.clone()])
1394 );
1395 }
1396
1397 #[test]
1398 fn test_entry_point_with_tracing_params_display() {
1399 use std::str::FromStr;
1400
1401 let entry_point = EntryPoint::new(
1402 "uniswap_v3_pool_swap".to_string(),
1403 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1404 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1405 );
1406
1407 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1408 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1409 Bytes::from_str("0xabcdef").unwrap(),
1410 ));
1411
1412 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1413
1414 let display_output = entry_point_with_params.to_string();
1415 assert_eq!(display_output, "uniswap_v3_pool_swap [RPC]");
1416 }
1417
1418 #[test]
1419 fn test_traced_entry_point_display() {
1420 use std::str::FromStr;
1421
1422 let entry_point = EntryPoint::new(
1423 "uniswap_v3_pool_swap".to_string(),
1424 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1425 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1426 );
1427
1428 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1429 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1430 Bytes::from_str("0xabcdef").unwrap(),
1431 ));
1432
1433 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1434
1435 let address1 = Address::from_str("0x1111111111111111111111111111111111111111").unwrap();
1437 let address2 = Address::from_str("0x2222222222222222222222222222222222222222").unwrap();
1438 let address3 = Address::from_str("0x3333333333333333333333333333333333333333").unwrap();
1439
1440 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1441 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1442
1443 let tracing_result = TracingResult::new(
1444 HashSet::from([
1445 (address1.clone(), AddressStorageLocation::new(store_key1.clone(), 0)),
1446 (address2.clone(), AddressStorageLocation::new(store_key2.clone(), 12)),
1447 ]),
1448 HashMap::from([
1449 (address1.clone(), HashSet::from([store_key1.clone()])),
1450 (address2.clone(), HashSet::from([store_key2.clone()])),
1451 (address3.clone(), HashSet::from([store_key1.clone()])),
1452 ]),
1453 );
1454
1455 let traced_entry_point = TracedEntryPoint::new(
1456 entry_point_with_params,
1457 Bytes::from_str("0xabcdef1234567890").unwrap(),
1458 tracing_result,
1459 );
1460
1461 let display_output = traced_entry_point.to_string();
1462 assert_eq!(display_output, "[uniswap_v3_pool_swap: 2 retriggers, 3 accessed addresses]");
1463 }
1464}