|
1 | 1 | use std::{ |
2 | 2 | any::Any, |
3 | | - collections::{hash_map::Entry, HashMap}, |
| 3 | + collections::{hash_map::Entry, HashMap, HashSet}, |
4 | 4 | sync::Arc, |
5 | 5 | }; |
6 | 6 |
|
7 | 7 | use chrono::NaiveDateTime; |
8 | | -use serde::{Deserialize, Serialize}; |
| 8 | +use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; |
9 | 9 | use tracing::warn; |
10 | 10 |
|
| 11 | +use super::{BlockHash, StoreKey}; |
11 | 12 | use crate::{ |
12 | 13 | models::{ |
13 | 14 | contract::{AccountBalance, AccountChangesWithTx, AccountDelta}, |
@@ -341,6 +342,111 @@ pub enum BlockTag { |
341 | 342 | /// Block by number |
342 | 343 | Number(u64), |
343 | 344 | } |
| 345 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 346 | +pub struct EntryPoint { |
| 347 | + /// The id of the protocol component that the entry point belongs to. |
| 348 | + pub external_id: String, |
| 349 | + /// The address of the contract to trace. |
| 350 | + pub target: Address, |
| 351 | + /// The signature of the function to trace. |
| 352 | + pub signature: String, |
| 353 | +} |
| 354 | + |
| 355 | +impl EntryPoint { |
| 356 | + pub fn new(external_id: String, target: Address, signature: String) -> Self { |
| 357 | + Self { external_id, target, signature } |
| 358 | + } |
| 359 | +} |
| 360 | + |
| 361 | +/// A struct that combines an entry point with its associated tracing data. |
| 362 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 363 | +pub struct EntryPointWithData { |
| 364 | + /// The entry point to trace, containing the target contract address and function signature |
| 365 | + pub entry_point: EntryPoint, |
| 366 | + /// The tracing configuration and data for this entry point |
| 367 | + pub data: EntryPointTracingData, |
| 368 | +} |
| 369 | + |
| 370 | +impl EntryPointWithData { |
| 371 | + pub fn new(entry_point: EntryPoint, data: EntryPointTracingData) -> Self { |
| 372 | + Self { entry_point, data } |
| 373 | + } |
| 374 | +} |
| 375 | + |
| 376 | +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)] |
| 377 | +/// An entry point to trace. Different types of entry points tracing will be supported in the |
| 378 | +/// future. Like RPC debug tracing, symbolic execution, etc. |
| 379 | +pub enum EntryPointTracingData { |
| 380 | + /// Uses RPC calls to retrieve the called addresses and retriggers |
| 381 | + RPCTracer(RPCTracerEntryPoint), |
| 382 | +} |
| 383 | + |
| 384 | +#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] |
| 385 | +pub struct RPCTracerEntryPoint { |
| 386 | + /// The caller address of the transaction, if not provided tracing will use the default value |
| 387 | + /// for an address defined by the VM. |
| 388 | + pub caller: Option<Address>, |
| 389 | + /// The data used for the tracing call, this needs to include the function selector |
| 390 | + pub data: Bytes, |
| 391 | +} |
| 392 | + |
| 393 | +impl RPCTracerEntryPoint { |
| 394 | + pub fn new(caller: Option<Address>, data: Bytes) -> Self { |
| 395 | + Self { caller, data } |
| 396 | + } |
| 397 | +} |
| 398 | + |
| 399 | +// Ensure serialization order, required by the storage layer |
| 400 | +impl Serialize for RPCTracerEntryPoint { |
| 401 | + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
| 402 | + where |
| 403 | + S: Serializer, |
| 404 | + { |
| 405 | + let mut state = serializer.serialize_struct("RPCTracerEntryPoint", 2)?; |
| 406 | + state.serialize_field("caller", &self.caller)?; |
| 407 | + state.serialize_field("data", &self.data)?; |
| 408 | + state.end() |
| 409 | + } |
| 410 | +} |
| 411 | + |
| 412 | +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] |
| 413 | +pub struct TracingResult { |
| 414 | + /// A set of (address, storage slot) pairs representing state that contain a called address. |
| 415 | + /// If any of these storage slots change, the execution path might change. |
| 416 | + pub retriggers: HashSet<(Address, StoreKey)>, |
| 417 | + /// A set of all addresses that were called during the trace. |
| 418 | + pub called_addresses: HashSet<Address>, |
| 419 | +} |
| 420 | + |
| 421 | +impl TracingResult { |
| 422 | + pub fn new( |
| 423 | + retriggers: HashSet<(Address, StoreKey)>, |
| 424 | + called_addresses: HashSet<Address>, |
| 425 | + ) -> Self { |
| 426 | + Self { retriggers, called_addresses } |
| 427 | + } |
| 428 | +} |
| 429 | + |
| 430 | +#[derive(Debug, Clone, PartialEq)] |
| 431 | +/// Represents a traced entry point and the results of the tracing operation. |
| 432 | +pub struct TracedEntryPoint { |
| 433 | + /// The combined entry point and tracing data that was traced |
| 434 | + pub entry_point_with_data: EntryPointWithData, |
| 435 | + /// The block hash of the block that the entry point was traced on. |
| 436 | + pub detection_block_hash: BlockHash, |
| 437 | + /// The results of the tracing operation |
| 438 | + pub tracing_result: TracingResult, |
| 439 | +} |
| 440 | + |
| 441 | +impl TracedEntryPoint { |
| 442 | + pub fn new( |
| 443 | + entry_point: EntryPointWithData, |
| 444 | + detection_block_hash: BlockHash, |
| 445 | + result: TracingResult, |
| 446 | + ) -> Self { |
| 447 | + Self { entry_point_with_data: entry_point, detection_block_hash, tracing_result: result } |
| 448 | + } |
| 449 | +} |
344 | 450 |
|
345 | 451 | #[cfg(test)] |
346 | 452 | pub mod fixtures { |
@@ -585,4 +691,25 @@ pub mod fixtures { |
585 | 691 |
|
586 | 692 | assert!(tx1.merge(tx2).is_err()); |
587 | 693 | } |
| 694 | + |
| 695 | + #[test] |
| 696 | + fn test_rpc_tracer_entry_point_serialization_order() { |
| 697 | + use std::str::FromStr; |
| 698 | + |
| 699 | + use serde_json; |
| 700 | + |
| 701 | + let entry_point = RPCTracerEntryPoint::new( |
| 702 | + Some(Address::from_str("0x1234567890123456789012345678901234567890").unwrap()), |
| 703 | + Bytes::from_str("0xabcdef").unwrap(), |
| 704 | + ); |
| 705 | + |
| 706 | + let serialized = serde_json::to_string(&entry_point).unwrap(); |
| 707 | + |
| 708 | + // Verify that "caller" comes before "data" in the serialized output |
| 709 | + assert!(serialized.find("\"caller\"").unwrap() < serialized.find("\"data\"").unwrap()); |
| 710 | + |
| 711 | + // Verify we can deserialize it back |
| 712 | + let deserialized: RPCTracerEntryPoint = serde_json::from_str(&serialized).unwrap(); |
| 713 | + assert_eq!(entry_point, deserialized); |
| 714 | + } |
588 | 715 | } |
0 commit comments