1use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
2
3use chrono::NaiveDateTime;
4use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
5use tracing::warn;
6
7use crate::{
8 dto,
9 models::{
10 contract::{AccountBalance, AccountChangesWithTx, AccountDelta},
11 protocol::{
12 ComponentBalance, ProtocolChangesWithTx, ProtocolComponent, ProtocolComponentStateDelta,
13 },
14 token::Token,
15 Address, Balance, BlockHash, Chain, Code, ComponentId, EntryPointId, MergeError, StoreKey,
16 StoreVal,
17 },
18 Bytes,
19};
20
21#[derive(Clone, Default, PartialEq, Serialize, Deserialize, Debug)]
22pub struct Block {
23 pub number: u64,
24 pub chain: Chain,
25 pub hash: Bytes,
26 pub parent_hash: Bytes,
27 pub ts: NaiveDateTime,
28}
29
30impl Block {
31 pub fn new(
32 number: u64,
33 chain: Chain,
34 hash: Bytes,
35 parent_hash: Bytes,
36 ts: NaiveDateTime,
37 ) -> Self {
38 Block { hash, parent_hash, number, chain, ts }
39 }
40}
41
42#[derive(Clone, Default, PartialEq, Debug, Eq, Hash)]
43pub struct Transaction {
44 pub hash: Bytes,
45 pub block_hash: Bytes,
46 pub from: Bytes,
47 pub to: Option<Bytes>,
48 pub index: u64,
49}
50
51impl Transaction {
52 pub fn new(hash: Bytes, block_hash: Bytes, from: Bytes, to: Option<Bytes>, index: u64) -> Self {
53 Transaction { hash, block_hash, from, to, index }
54 }
55}
56
57pub struct BlockTransactionDeltas<T> {
58 pub extractor: String,
59 pub chain: Chain,
60 pub block: Block,
61 pub revert: bool,
62 pub deltas: Vec<TransactionDeltaGroup<T>>,
63}
64
65#[allow(dead_code)]
66pub struct TransactionDeltaGroup<T> {
67 changes: T,
68 protocol_component: HashMap<String, ProtocolComponent>,
69 component_balances: HashMap<String, ComponentBalance>,
70 component_tvl: HashMap<String, f64>,
71 tx: Transaction,
72}
73
74#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
75pub struct BlockAggregatedChanges {
76 pub extractor: String,
77 pub chain: Chain,
78 pub block: Block,
79 pub finalized_block_height: u64,
80 pub db_committed_upto_block_height: u64,
81 pub revert: bool,
82 pub state_deltas: HashMap<String, ProtocolComponentStateDelta>,
83 pub account_deltas: HashMap<Bytes, AccountDelta>,
84 pub new_tokens: HashMap<Address, Token>,
85 pub new_protocol_components: HashMap<String, ProtocolComponent>,
86 pub deleted_protocol_components: HashMap<String, ProtocolComponent>,
87 pub component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
88 pub account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
89 pub component_tvl: HashMap<String, f64>,
90 pub dci_update: DCIUpdate,
91}
92
93impl BlockAggregatedChanges {
94 #[allow(clippy::too_many_arguments)]
95 pub fn new(
96 extractor: &str,
97 chain: Chain,
98 block: Block,
99 db_committed_upto_block_height: u64,
100 finalized_block_height: u64,
101 revert: bool,
102 state_deltas: HashMap<String, ProtocolComponentStateDelta>,
103 account_deltas: HashMap<Bytes, AccountDelta>,
104 new_tokens: HashMap<Address, Token>,
105 new_components: HashMap<String, ProtocolComponent>,
106 deleted_components: HashMap<String, ProtocolComponent>,
107 component_balances: HashMap<ComponentId, HashMap<Bytes, ComponentBalance>>,
108 account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
109 component_tvl: HashMap<String, f64>,
110 dci_update: DCIUpdate,
111 ) -> Self {
112 Self {
113 extractor: extractor.to_string(),
114 chain,
115 block,
116 db_committed_upto_block_height,
117 finalized_block_height,
118 revert,
119 state_deltas,
120 account_deltas,
121 new_tokens,
122 new_protocol_components: new_components,
123 deleted_protocol_components: deleted_components,
124 component_balances,
125 account_balances,
126 component_tvl,
127 dci_update,
128 }
129 }
130}
131
132impl std::fmt::Display for BlockAggregatedChanges {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 write!(f, "block_number: {}, extractor: {}", self.block.number, self.extractor)
135 }
136}
137
138impl BlockAggregatedChanges {
139 pub fn drop_state(&self) -> Self {
140 Self {
141 extractor: self.extractor.clone(),
142 chain: self.chain,
143 block: self.block.clone(),
144 db_committed_upto_block_height: self.db_committed_upto_block_height,
145 finalized_block_height: self.finalized_block_height,
146 revert: self.revert,
147 account_deltas: HashMap::new(),
148 state_deltas: HashMap::new(),
149 new_tokens: self.new_tokens.clone(),
150 new_protocol_components: self.new_protocol_components.clone(),
151 deleted_protocol_components: self.deleted_protocol_components.clone(),
152 component_balances: self.component_balances.clone(),
153 account_balances: self.account_balances.clone(),
154 component_tvl: self.component_tvl.clone(),
155 dci_update: self.dci_update.clone(),
156 }
157 }
158}
159
160pub trait BlockScoped {
161 fn block(&self) -> Block;
162}
163
164impl BlockScoped for BlockAggregatedChanges {
165 fn block(&self) -> Block {
166 self.block.clone()
167 }
168}
169
170impl From<dto::Block> for Block {
171 fn from(value: dto::Block) -> Self {
172 Self {
173 number: value.number,
174 chain: value.chain.into(),
175 hash: value.hash,
176 parent_hash: value.parent_hash,
177 ts: value.ts,
178 }
179 }
180}
181
182#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
183pub struct DCIUpdate {
184 pub new_entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
185 pub new_entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
186 pub trace_results: HashMap<EntryPointId, TracingResult>,
187}
188
189#[derive(Debug, Clone, PartialEq, Default)]
191pub struct TxWithChanges {
192 pub tx: Transaction,
193 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
194 pub account_deltas: HashMap<Address, AccountDelta>,
195 pub state_updates: HashMap<ComponentId, ProtocolComponentStateDelta>,
196 pub balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
197 pub account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
198 pub entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
199 pub entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
200}
201
202impl TxWithChanges {
203 #[allow(clippy::too_many_arguments)]
204 pub fn new(
205 tx: Transaction,
206 protocol_components: HashMap<ComponentId, ProtocolComponent>,
207 account_deltas: HashMap<Address, AccountDelta>,
208 protocol_states: HashMap<ComponentId, ProtocolComponentStateDelta>,
209 balance_changes: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
210 account_balance_changes: HashMap<Address, HashMap<Address, AccountBalance>>,
211 entrypoints: HashMap<ComponentId, HashSet<EntryPoint>>,
212 entrypoint_params: HashMap<EntryPointId, HashSet<(TracingParams, Option<ComponentId>)>>,
213 ) -> Self {
214 Self {
215 tx,
216 account_deltas,
217 protocol_components,
218 state_updates: protocol_states,
219 balance_changes,
220 account_balance_changes,
221 entrypoints,
222 entrypoint_params,
223 }
224 }
225
226 pub fn merge(&mut self, other: TxWithChanges) -> Result<(), MergeError> {
236 if self.tx.block_hash != other.tx.block_hash {
237 return Err(MergeError::BlockMismatch(
238 "TxWithChanges".to_string(),
239 self.tx.block_hash.clone(),
240 other.tx.block_hash,
241 ));
242 }
243 if self.tx.index > other.tx.index {
244 return Err(MergeError::TransactionOrderError(
245 "TxWithChanges".to_string(),
246 self.tx.index,
247 other.tx.index,
248 ));
249 }
250
251 self.tx = other.tx;
252
253 for (key, value) in other.protocol_components {
257 match self.protocol_components.entry(key) {
258 Entry::Occupied(mut entry) => {
259 warn!(
260 "Overwriting new protocol component for id {} with a new one. This should never happen! Please check logic",
261 entry.get().id
262 );
263 entry.insert(value);
264 }
265 Entry::Vacant(entry) => {
266 entry.insert(value);
267 }
268 }
269 }
270
271 for (address, update) in other.account_deltas.clone().into_iter() {
273 match self.account_deltas.entry(address) {
274 Entry::Occupied(mut e) => {
275 e.get_mut().merge(update)?;
276 }
277 Entry::Vacant(e) => {
278 e.insert(update);
279 }
280 }
281 }
282
283 for (key, value) in other.state_updates {
285 match self.state_updates.entry(key) {
286 Entry::Occupied(mut entry) => {
287 entry.get_mut().merge(value)?;
288 }
289 Entry::Vacant(entry) => {
290 entry.insert(value);
291 }
292 }
293 }
294
295 for (component_id, balance_changes) in other.balance_changes {
297 let token_balances = self
298 .balance_changes
299 .entry(component_id)
300 .or_default();
301 for (token, balance) in balance_changes {
302 token_balances.insert(token, balance);
303 }
304 }
305
306 for (account_addr, balance_changes) in other.account_balance_changes {
308 let token_balances = self
309 .account_balance_changes
310 .entry(account_addr)
311 .or_default();
312 for (token, balance) in balance_changes {
313 token_balances.insert(token, balance);
314 }
315 }
316
317 for (component_id, entrypoints) in other.entrypoints {
319 self.entrypoints
320 .entry(component_id)
321 .or_default()
322 .extend(entrypoints);
323 }
324
325 for (entrypoint_id, params) in other.entrypoint_params {
327 self.entrypoint_params
328 .entry(entrypoint_id)
329 .or_default()
330 .extend(params);
331 }
332
333 Ok(())
334 }
335}
336
337impl From<AccountChangesWithTx> for TxWithChanges {
338 fn from(value: AccountChangesWithTx) -> Self {
339 Self {
340 tx: value.tx,
341 protocol_components: value.protocol_components,
342 account_deltas: value.account_deltas,
343 balance_changes: value.component_balances,
344 account_balance_changes: value.account_balances,
345 ..Default::default()
346 }
347 }
348}
349
350impl From<ProtocolChangesWithTx> for TxWithChanges {
351 fn from(value: ProtocolChangesWithTx) -> Self {
352 Self {
353 tx: value.tx,
354 protocol_components: value.new_protocol_components,
355 state_updates: value.protocol_states,
356 balance_changes: value.balance_changes,
357 ..Default::default()
358 }
359 }
360}
361
362#[derive(Copy, Clone, Debug, PartialEq)]
363pub enum BlockTag {
364 Finalized,
366 Safe,
368 Latest,
370 Earliest,
372 Pending,
374 Number(u64),
376}
377#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
378pub struct EntryPoint {
379 pub external_id: String,
381 pub target: Address,
383 pub signature: String,
385}
386
387impl EntryPoint {
388 pub fn new(external_id: String, target: Address, signature: String) -> Self {
389 Self { external_id, target, signature }
390 }
391}
392
393impl From<dto::EntryPoint> for EntryPoint {
394 fn from(value: dto::EntryPoint) -> Self {
395 Self { external_id: value.external_id, target: value.target, signature: value.signature }
396 }
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, Hash)]
401pub struct EntryPointWithTracingParams {
402 pub entry_point: EntryPoint,
404 pub params: TracingParams,
406}
407
408impl From<dto::EntryPointWithTracingParams> for EntryPointWithTracingParams {
409 fn from(value: dto::EntryPointWithTracingParams) -> Self {
410 match value.params {
411 dto::TracingParams::RPCTracer(ref tracer_params) => Self {
412 entry_point: EntryPoint {
413 external_id: value.entry_point.external_id,
414 target: value.entry_point.target,
415 signature: value.entry_point.signature,
416 },
417 params: TracingParams::RPCTracer(RPCTracerParams {
418 caller: tracer_params.caller.clone(),
419 calldata: tracer_params.calldata.clone(),
420 state_overrides: tracer_params
421 .state_overrides
422 .clone()
423 .map(|s| {
424 s.into_iter()
425 .map(|(k, v)| (k, v.into()))
426 .collect()
427 }),
428 prune_addresses: tracer_params.prune_addresses.clone(),
429 }),
430 },
431 }
432 }
433}
434
435impl EntryPointWithTracingParams {
436 pub fn new(entry_point: EntryPoint, params: TracingParams) -> Self {
437 Self { entry_point, params }
438 }
439}
440
441impl std::fmt::Display for EntryPointWithTracingParams {
442 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 let tracer_type = match &self.params {
444 TracingParams::RPCTracer(_) => "RPC",
445 };
446 write!(f, "{} [{}]", self.entry_point.external_id, tracer_type)
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
451pub enum TracingParams {
454 RPCTracer(RPCTracerParams),
456}
457
458impl std::fmt::Display for TracingParams {
459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460 match self {
461 TracingParams::RPCTracer(params) => write!(f, "RPC: {params}"),
462 }
463 }
464}
465
466impl From<dto::TracingParams> for TracingParams {
467 fn from(value: dto::TracingParams) -> Self {
468 match value {
469 dto::TracingParams::RPCTracer(tracer_params) => {
470 TracingParams::RPCTracer(tracer_params.into())
471 }
472 }
473 }
474}
475
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
477pub enum StorageOverride {
478 Diff(BTreeMap<StoreKey, StoreVal>),
479 Replace(BTreeMap<StoreKey, StoreVal>),
480}
481
482impl From<dto::StorageOverride> for StorageOverride {
483 fn from(value: dto::StorageOverride) -> Self {
484 match value {
485 dto::StorageOverride::Diff(diff) => StorageOverride::Diff(diff),
486 dto::StorageOverride::Replace(replace) => StorageOverride::Replace(replace),
487 }
488 }
489}
490
491#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)]
492pub struct AccountOverrides {
493 pub slots: Option<StorageOverride>,
494 pub native_balance: Option<Balance>,
495 pub code: Option<Code>,
496}
497
498impl From<dto::AccountOverrides> for AccountOverrides {
499 fn from(value: dto::AccountOverrides) -> Self {
500 Self {
501 slots: value.slots.map(|s| s.into()),
502 native_balance: value.native_balance,
503 code: value.code,
504 }
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
509pub struct RPCTracerParams {
510 pub caller: Option<Address>,
513 pub calldata: Bytes,
515 pub state_overrides: Option<BTreeMap<Address, AccountOverrides>>,
517 pub prune_addresses: Option<Vec<Address>>,
520}
521
522impl From<dto::RPCTracerParams> for RPCTracerParams {
523 fn from(value: dto::RPCTracerParams) -> Self {
524 Self {
525 caller: value.caller,
526 calldata: value.calldata,
527 state_overrides: value.state_overrides.map(|overrides| {
528 overrides
529 .into_iter()
530 .map(|(address, account_overrides)| (address, account_overrides.into()))
531 .collect()
532 }),
533 prune_addresses: value.prune_addresses,
534 }
535 }
536}
537
538impl RPCTracerParams {
539 pub fn new(caller: Option<Address>, calldata: Bytes) -> Self {
540 Self { caller, calldata, state_overrides: None, prune_addresses: None }
541 }
542
543 pub fn with_state_overrides(mut self, state: BTreeMap<Address, AccountOverrides>) -> Self {
544 self.state_overrides = Some(state);
545 self
546 }
547
548 pub fn with_prune_addresses(mut self, addresses: Vec<Address>) -> Self {
549 self.prune_addresses = Some(addresses);
550 self
551 }
552}
553
554impl std::fmt::Display for RPCTracerParams {
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556 let caller_str = match &self.caller {
557 Some(addr) => format!("caller={addr}"),
558 None => String::new(),
559 };
560
561 let calldata_str = if self.calldata.len() >= 8 {
562 format!(
563 "calldata=0x{}..({} bytes)",
564 hex::encode(&self.calldata[..8]),
565 self.calldata.len()
566 )
567 } else {
568 format!("calldata={}", self.calldata)
569 };
570
571 let overrides_str = match &self.state_overrides {
572 Some(overrides) if !overrides.is_empty() => {
573 format!(", {} state override(s)", overrides.len())
574 }
575 _ => String::new(),
576 };
577
578 write!(f, "{caller_str}, {calldata_str}{overrides_str}")
579 }
580}
581
582impl Serialize for RPCTracerParams {
584 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
585 where
586 S: Serializer,
587 {
588 let mut field_count = 2;
590 if self.state_overrides.is_some() {
591 field_count += 1;
592 }
593 if self.prune_addresses.is_some() {
594 field_count += 1;
595 }
596
597 let mut state = serializer.serialize_struct("RPCTracerEntryPoint", field_count)?;
598 state.serialize_field("caller", &self.caller)?;
599 state.serialize_field("calldata", &self.calldata)?;
600
601 if let Some(ref overrides) = self.state_overrides {
603 state.serialize_field("state_overrides", overrides)?;
604 }
605 if let Some(ref prune_addrs) = self.prune_addresses {
606 state.serialize_field("prune_addresses", prune_addrs)?;
607 }
608
609 state.end()
610 }
611}
612
613#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
614pub struct AddressStorageLocation {
615 pub key: StoreKey,
616 pub offset: u8,
617}
618
619impl AddressStorageLocation {
620 pub fn new(key: StoreKey, offset: u8) -> Self {
621 Self { key, offset }
622 }
623}
624
625#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
626pub struct TracingResult {
627 pub retriggers: HashSet<(Address, AddressStorageLocation)>,
630 pub accessed_slots: HashMap<Address, HashSet<StoreKey>>,
633}
634
635impl TracingResult {
636 pub fn new(
637 retriggers: HashSet<(Address, AddressStorageLocation)>,
638 accessed_slots: HashMap<Address, HashSet<StoreKey>>,
639 ) -> Self {
640 Self { retriggers, accessed_slots }
641 }
642
643 pub fn merge(&mut self, other: TracingResult) {
647 self.retriggers.extend(other.retriggers);
648 for (address, slots) in other.accessed_slots {
649 self.accessed_slots
650 .entry(address)
651 .or_default()
652 .extend(slots);
653 }
654 }
655}
656
657#[derive(Debug, Clone, PartialEq)]
658pub struct TracedEntryPoint {
660 pub entry_point_with_params: EntryPointWithTracingParams,
662 pub detection_block_hash: BlockHash,
664 pub tracing_result: TracingResult,
666}
667
668impl TracedEntryPoint {
669 pub fn new(
670 entry_point_with_params: EntryPointWithTracingParams,
671 detection_block_hash: BlockHash,
672 result: TracingResult,
673 ) -> Self {
674 Self { entry_point_with_params, detection_block_hash, tracing_result: result }
675 }
676
677 pub fn entry_point_id(&self) -> String {
678 self.entry_point_with_params
679 .entry_point
680 .external_id
681 .clone()
682 }
683}
684
685impl std::fmt::Display for TracedEntryPoint {
686 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
687 write!(
688 f,
689 "[{}: {} retriggers, {} accessed addresses]",
690 self.entry_point_id(),
691 self.tracing_result.retriggers.len(),
692 self.tracing_result.accessed_slots.len()
693 )
694 }
695}
696
697#[cfg(test)]
698pub mod fixtures {
699 use std::str::FromStr;
700
701 use rstest::rstest;
702
703 use super::*;
704 use crate::models::ChangeType;
705
706 pub fn transaction01() -> Transaction {
707 Transaction::new(
708 Bytes::zero(32),
709 Bytes::zero(32),
710 Bytes::zero(20),
711 Some(Bytes::zero(20)),
712 10,
713 )
714 }
715
716 pub fn create_transaction(hash: &str, block: &str, index: u64) -> Transaction {
717 Transaction::new(
718 hash.parse().unwrap(),
719 block.parse().unwrap(),
720 Bytes::zero(20),
721 Some(Bytes::zero(20)),
722 index,
723 )
724 }
725
726 #[test]
727 fn test_merge_tx_with_changes() {
728 let base_token = Bytes::from_str("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
729 let quote_token = Bytes::from_str("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap();
730 let contract_addr = Bytes::from_str("aaaaaaaaa24eeeb8d57d431224f73832bc34f688").unwrap();
731 let tx_hash0 = "0x2f6350a292c0fc918afe67cb893744a080dacb507b0cea4cc07437b8aff23cdb";
732 let tx_hash1 = "0x0d9e0da36cf9f305a189965b248fc79c923619801e8ab5ef158d4fd528a291ad";
733 let block = "0x0000000000000000000000000000000000000000000000000000000000000000";
734 let component = ProtocolComponent::new(
735 "ambient_USDC_ETH",
736 "test",
737 "vm:pool",
738 Chain::Ethereum,
739 vec![base_token.clone(), quote_token.clone()],
740 vec![contract_addr.clone()],
741 Default::default(),
742 Default::default(),
743 Bytes::from_str(tx_hash0).unwrap(),
744 Default::default(),
745 );
746 let account_delta = AccountDelta::new(
747 Chain::Ethereum,
748 contract_addr.clone(),
749 HashMap::new(),
750 None,
751 Some(vec![0, 0, 0, 0].into()),
752 ChangeType::Creation,
753 );
754
755 let mut changes1 = TxWithChanges::new(
756 create_transaction(tx_hash0, block, 1),
757 HashMap::from([(component.id.clone(), component.clone())]),
758 HashMap::from([(contract_addr.clone(), account_delta.clone())]),
759 HashMap::new(),
760 HashMap::from([(
761 component.id.clone(),
762 HashMap::from([(
763 base_token.clone(),
764 ComponentBalance {
765 token: base_token.clone(),
766 balance: Bytes::from(800_u64).lpad(32, 0),
767 balance_float: 800.0,
768 component_id: component.id.clone(),
769 modify_tx: Bytes::from_str(tx_hash0).unwrap(),
770 },
771 )]),
772 )]),
773 HashMap::from([(
774 contract_addr.clone(),
775 HashMap::from([(
776 base_token.clone(),
777 AccountBalance {
778 token: base_token.clone(),
779 balance: Bytes::from(800_u64).lpad(32, 0),
780 modify_tx: Bytes::from_str(tx_hash0).unwrap(),
781 account: contract_addr.clone(),
782 },
783 )]),
784 )]),
785 HashMap::from([(
786 component.id.clone(),
787 HashSet::from([EntryPoint::new(
788 "test".to_string(),
789 contract_addr.clone(),
790 "function()".to_string(),
791 )]),
792 )]),
793 HashMap::from([(
794 "test".to_string(),
795 HashSet::from([(
796 TracingParams::RPCTracer(RPCTracerParams::new(
797 None,
798 Bytes::from_str("0x000001ef").unwrap(),
799 )),
800 Some(component.id.clone()),
801 )]),
802 )]),
803 );
804 let changes2 = TxWithChanges::new(
805 create_transaction(tx_hash1, block, 2),
806 HashMap::from([(
807 component.id.clone(),
808 ProtocolComponent {
809 creation_tx: Bytes::from_str(tx_hash1).unwrap(),
810 ..component.clone()
811 },
812 )]),
813 HashMap::from([(
814 contract_addr.clone(),
815 AccountDelta::new(
816 Chain::Ethereum,
817 contract_addr.clone(),
818 HashMap::from([(vec![0, 0, 0, 0].into(), Some(vec![0, 0, 0, 0].into()))]),
819 None,
820 None,
821 ChangeType::Update,
822 ),
823 )]),
824 HashMap::new(),
825 HashMap::from([(
826 component.id.clone(),
827 HashMap::from([(
828 base_token.clone(),
829 ComponentBalance {
830 token: base_token.clone(),
831 balance: Bytes::from(1000_u64).lpad(32, 0),
832 balance_float: 1000.0,
833 component_id: component.id.clone(),
834 modify_tx: Bytes::from_str(tx_hash1).unwrap(),
835 },
836 )]),
837 )]),
838 HashMap::from([(
839 contract_addr.clone(),
840 HashMap::from([(
841 base_token.clone(),
842 AccountBalance {
843 token: base_token.clone(),
844 balance: Bytes::from(1000_u64).lpad(32, 0),
845 modify_tx: Bytes::from_str(tx_hash1).unwrap(),
846 account: contract_addr.clone(),
847 },
848 )]),
849 )]),
850 HashMap::from([(
851 component.id.clone(),
852 HashSet::from([
853 EntryPoint::new(
854 "test".to_string(),
855 contract_addr.clone(),
856 "function()".to_string(),
857 ),
858 EntryPoint::new(
859 "test2".to_string(),
860 contract_addr.clone(),
861 "function_2()".to_string(),
862 ),
863 ]),
864 )]),
865 HashMap::from([(
866 "test2".to_string(),
867 HashSet::from([(
868 TracingParams::RPCTracer(RPCTracerParams::new(
869 None,
870 Bytes::from_str("0x000001").unwrap(),
871 )),
872 None,
873 )]),
874 )]),
875 );
876
877 assert!(changes1.merge(changes2).is_ok());
878 assert_eq!(
879 changes1
880 .account_balance_changes
881 .get(&contract_addr)
882 .unwrap()
883 .get(&base_token)
884 .unwrap()
885 .balance,
886 Bytes::from(1000_u64).lpad(32, 0),
887 );
888 assert_eq!(
889 changes1
890 .balance_changes
891 .get(&component.id)
892 .unwrap()
893 .get(&base_token)
894 .unwrap()
895 .balance,
896 Bytes::from(1000_u64).lpad(32, 0),
897 );
898 assert_eq!(changes1.tx.hash, Bytes::from_str(tx_hash1).unwrap(),);
899 assert_eq!(changes1.entrypoints.len(), 1);
900 assert_eq!(
901 changes1
902 .entrypoints
903 .get(&component.id)
904 .unwrap()
905 .len(),
906 2
907 );
908 let mut expected_entry_points = changes1
909 .entrypoints
910 .values()
911 .flat_map(|ep| ep.iter())
912 .map(|ep| ep.signature.clone())
913 .collect::<Vec<_>>();
914 expected_entry_points.sort();
915 assert_eq!(
916 expected_entry_points,
917 vec!["function()".to_string(), "function_2()".to_string()],
918 );
919 }
920
921 #[rstest]
922 #[case::mismatched_blocks(
923 fixtures::create_transaction("0x01", "0x0abc", 1),
924 fixtures::create_transaction("0x02", "0x0def", 2)
925 )]
926 #[case::older_transaction(
927 fixtures::create_transaction("0x02", "0x0abc", 2),
928 fixtures::create_transaction("0x01", "0x0abc", 1)
929 )]
930 fn test_merge_errors(#[case] tx1: Transaction, #[case] tx2: Transaction) {
931 let mut changes1 = TxWithChanges { tx: tx1, ..Default::default() };
932
933 let changes2 = TxWithChanges { tx: tx2, ..Default::default() };
934
935 assert!(changes1.merge(changes2).is_err());
936 }
937
938 #[test]
939 fn test_rpc_tracer_entry_point_serialization_order() {
940 use std::str::FromStr;
941
942 use serde_json;
943
944 let entry_point = RPCTracerParams::new(
945 Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()),
946 Bytes::from_str("0xabcdef").unwrap(),
947 );
948
949 let serialized = serde_json::to_string(&entry_point).unwrap();
950
951 assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"calldata\"").unwrap());
953
954 let deserialized: RPCTracerParams = serde_json::from_str(&serialized).unwrap();
956 assert_eq!(entry_point, deserialized);
957 }
958
959 #[test]
960 fn test_tracing_result_merge() {
961 let address1 = Address::from_str("0x1234567890123456789012345678901234567890").unwrap();
962 let address2 = Address::from_str("0x2345678901234567890123456789012345678901").unwrap();
963 let address3 = Address::from_str("0x3456789012345678901234567890123456789012").unwrap();
964
965 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
966 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
967
968 let mut result1 = TracingResult::new(
969 HashSet::from([(
970 address1.clone(),
971 AddressStorageLocation::new(store_key1.clone(), 12),
972 )]),
973 HashMap::from([
974 (address2.clone(), HashSet::from([store_key1.clone()])),
975 (address3.clone(), HashSet::from([store_key2.clone()])),
976 ]),
977 );
978
979 let result2 = TracingResult::new(
980 HashSet::from([(
981 address3.clone(),
982 AddressStorageLocation::new(store_key2.clone(), 12),
983 )]),
984 HashMap::from([
985 (address1.clone(), HashSet::from([store_key1.clone()])),
986 (address2.clone(), HashSet::from([store_key2.clone()])),
987 ]),
988 );
989
990 result1.merge(result2);
991
992 assert_eq!(result1.retriggers.len(), 2);
994 assert!(result1
995 .retriggers
996 .contains(&(address1.clone(), AddressStorageLocation::new(store_key1.clone(), 12))));
997 assert!(result1
998 .retriggers
999 .contains(&(address3.clone(), AddressStorageLocation::new(store_key2.clone(), 12))));
1000
1001 assert_eq!(result1.accessed_slots.len(), 3);
1003 assert!(result1
1004 .accessed_slots
1005 .contains_key(&address1));
1006 assert!(result1
1007 .accessed_slots
1008 .contains_key(&address2));
1009 assert!(result1
1010 .accessed_slots
1011 .contains_key(&address3));
1012
1013 assert_eq!(
1014 result1
1015 .accessed_slots
1016 .get(&address2)
1017 .unwrap(),
1018 &HashSet::from([store_key1.clone(), store_key2.clone()])
1019 );
1020 }
1021
1022 #[test]
1023 fn test_entry_point_with_tracing_params_display() {
1024 use std::str::FromStr;
1025
1026 let entry_point = EntryPoint::new(
1027 "uniswap_v3_pool_swap".to_string(),
1028 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1029 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1030 );
1031
1032 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1033 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1034 Bytes::from_str("0xabcdef").unwrap(),
1035 ));
1036
1037 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1038
1039 let display_output = entry_point_with_params.to_string();
1040 assert_eq!(display_output, "uniswap_v3_pool_swap [RPC]");
1041 }
1042
1043 #[test]
1044 fn test_traced_entry_point_display() {
1045 use std::str::FromStr;
1046
1047 let entry_point = EntryPoint::new(
1048 "uniswap_v3_pool_swap".to_string(),
1049 Address::from_str("0x1234567890123456789012345678901234567890").unwrap(),
1050 "swapExactETHForTokens(uint256,address[],address,uint256)".to_string(),
1051 );
1052
1053 let tracing_params = TracingParams::RPCTracer(RPCTracerParams::new(
1054 Some(Address::from_str("0x9876543210987654321098765432109876543210").unwrap()),
1055 Bytes::from_str("0xabcdef").unwrap(),
1056 ));
1057
1058 let entry_point_with_params = EntryPointWithTracingParams::new(entry_point, tracing_params);
1059
1060 let address1 = Address::from_str("0x1111111111111111111111111111111111111111").unwrap();
1062 let address2 = Address::from_str("0x2222222222222222222222222222222222222222").unwrap();
1063 let address3 = Address::from_str("0x3333333333333333333333333333333333333333").unwrap();
1064
1065 let store_key1 = StoreKey::from(vec![1, 2, 3, 4]);
1066 let store_key2 = StoreKey::from(vec![5, 6, 7, 8]);
1067
1068 let tracing_result = TracingResult::new(
1069 HashSet::from([
1070 (address1.clone(), AddressStorageLocation::new(store_key1.clone(), 0)),
1071 (address2.clone(), AddressStorageLocation::new(store_key2.clone(), 12)),
1072 ]),
1073 HashMap::from([
1074 (address1.clone(), HashSet::from([store_key1.clone()])),
1075 (address2.clone(), HashSet::from([store_key2.clone()])),
1076 (address3.clone(), HashSet::from([store_key1.clone()])),
1077 ]),
1078 );
1079
1080 let traced_entry_point = TracedEntryPoint::new(
1081 entry_point_with_params,
1082 Bytes::from_str("0xabcdef1234567890").unwrap(),
1083 tracing_result,
1084 );
1085
1086 let display_output = traced_entry_point.to_string();
1087 assert_eq!(display_output, "[uniswap_v3_pool_swap: 2 retriggers, 3 accessed addresses]");
1088 }
1089}