@@ -46,6 +46,32 @@ pub enum CallbackResult {
4646 Trace ( Vec < TraceResult > ) ,
4747}
4848
49+ impl CallbackResult {
50+ /// return the (from_block, to_block, network) entry regardless of variant
51+ pub fn first_metadata ( & self ) -> Option < ( U64 , U64 , String ) > {
52+ match self {
53+ Self :: Event ( events) => events. first ( ) . map ( |e| {
54+ (
55+ e. found_in_request . from_block ,
56+ e. found_in_request . to_block ,
57+ e. tx_information . network . clone ( ) ,
58+ )
59+ } ) ,
60+ Self :: Trace ( traces) => traces. first ( ) . map ( |t| {
61+ let ( fir, tx) = match t {
62+ TraceResult :: NativeTransfer { found_in_request, tx_information, .. } => {
63+ ( found_in_request, tx_information)
64+ }
65+ TraceResult :: Block { found_in_request, tx_information, .. } => {
66+ ( found_in_request, tx_information)
67+ }
68+ } ;
69+ ( fir. from_block , fir. to_block , tx. network . clone ( ) )
70+ } ) ,
71+ }
72+ }
73+ }
74+
4975#[ derive( Debug , Serialize , Deserialize , Clone ) ]
5076pub struct TxInformation {
5177 pub chain_id : u64 ,
@@ -507,3 +533,128 @@ where
507533 }
508534 }
509535}
536+
537+ #[ cfg( test) ]
538+ mod tests {
539+ use super :: * ;
540+ use alloy:: network:: AnyRpcBlock ;
541+
542+ fn test_tx_information ( network : & str ) -> TxInformation {
543+ TxInformation {
544+ chain_id : 31337 ,
545+ network : network. to_string ( ) ,
546+ address : Address :: ZERO ,
547+ block_hash : BlockHash :: ZERO ,
548+ block_number : 0 ,
549+ block_timestamp : None ,
550+ transaction_hash : TxHash :: ZERO ,
551+ transaction_index : 0 ,
552+ log_index : U256 :: ZERO ,
553+ }
554+ }
555+
556+ fn test_found_in_request ( from_block : u64 , to_block : u64 ) -> LogFoundInRequest {
557+ LogFoundInRequest { from_block : U64 :: from ( from_block) , to_block : U64 :: from ( to_block) }
558+ }
559+
560+ //Minimal valid json for alloy::network::AnyRpcBlock deserialization
561+ const TEST_BLOCK_JSON : & str = r#"{
562+ "number": "0x0",
563+ "hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
564+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
565+ "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
566+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
567+ "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
568+ "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
569+ "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
570+ "miner": "0x0000000000000000000000000000000000000000",
571+ "difficulty": "0x0",
572+ "extraData": "0x",
573+ "size": "0x0",
574+ "gasLimit": "0x0",
575+ "gasUsed": "0x0",
576+ "timestamp": "0x0",
577+ "transactions": [],
578+ "uncles": []
579+ }"# ;
580+
581+ const TEST_LOG_JSON : & str = r#"{
582+ "address": "0x0000000000000000000000000000000000000000",
583+ "topics": [],
584+ "data": "0x",
585+ "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
586+ "blockNumber": "0x0",
587+ "transactionHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
588+ "transactionIndex": "0x0",
589+ "logIndex": "0x0",
590+ "removed": false
591+ }"# ;
592+
593+ fn test_block ( ) -> AnyRpcBlock {
594+ serde_json:: from_str ( TEST_BLOCK_JSON ) . expect ( "test block JSON should deserialize" )
595+ }
596+
597+ fn test_log ( ) -> Log {
598+ serde_json:: from_str ( TEST_LOG_JSON ) . expect ( "test log JSON should deserialize" )
599+ }
600+
601+ #[ test]
602+ fn first_metadata_event_returns_fields_from_first_entry ( ) {
603+ let event = EventResult {
604+ log : test_log ( ) ,
605+ decoded_data : Arc :: new ( ( ) ) ,
606+ tx_information : test_tx_information ( "mainnet" ) ,
607+ found_in_request : test_found_in_request ( 10 , 20 ) ,
608+ } ;
609+ let result = CallbackResult :: Event ( vec ! [ event] ) ;
610+ let ( from_block, to_block, network) = result. first_metadata ( ) . expect ( "non-empty batch" ) ;
611+ assert_eq ! ( from_block, U64 :: from( 10 ) ) ;
612+ assert_eq ! ( to_block, U64 :: from( 20 ) ) ;
613+ assert_eq ! ( network, "mainnet" ) ;
614+ }
615+
616+ #[ test]
617+ fn first_metadata_trace_native_transfer_returns_fields ( ) {
618+ let nt = TraceResult :: NativeTransfer {
619+ from : Address :: ZERO ,
620+ to : Address :: ZERO ,
621+ value : U256 :: ZERO ,
622+ tx_information : test_tx_information ( "anvil" ) ,
623+ found_in_request : test_found_in_request ( 5 , 15 ) ,
624+ } ;
625+ let result = CallbackResult :: Trace ( vec ! [ nt] ) ;
626+ let ( from_block, to_block, network) = result. first_metadata ( ) . expect ( "non-empty batch" ) ;
627+ assert_eq ! ( from_block, U64 :: from( 5 ) ) ;
628+ assert_eq ! ( to_block, U64 :: from( 15 ) ) ;
629+ assert_eq ! ( network, "anvil" ) ;
630+ }
631+
632+ //native_transfer_block_consumer always fires trigger_event with a Block-only batch before firing the
633+ // NativeTransfer batch, so first_metadata must
634+ // extract cleanly from a TraceResult::Block without panicking
635+ #[ test]
636+ fn first_metadata_trace_block_returns_fields ( ) {
637+ let b = TraceResult :: Block {
638+ block : Box :: new ( test_block ( ) ) ,
639+ tx_information : test_tx_information ( "polygon" ) ,
640+ found_in_request : test_found_in_request ( 100 , 200 ) ,
641+ } ;
642+ let result = CallbackResult :: Trace ( vec ! [ b] ) ;
643+ let ( from_block, to_block, network) = result. first_metadata ( ) . expect ( "non-empty batch" ) ;
644+ assert_eq ! ( from_block, U64 :: from( 100 ) ) ;
645+ assert_eq ! ( to_block, U64 :: from( 200 ) ) ;
646+ assert_eq ! ( network, "polygon" ) ;
647+ }
648+
649+ #[ test]
650+ fn first_metadata_empty_event_returns_none ( ) {
651+ let result = CallbackResult :: Event ( vec ! [ ] ) ;
652+ assert ! ( result. first_metadata( ) . is_none( ) ) ;
653+ }
654+
655+ #[ test]
656+ fn first_metadata_empty_trace_returns_none ( ) {
657+ let result = CallbackResult :: Trace ( vec ! [ ] ) ;
658+ assert ! ( result. first_metadata( ) . is_none( ) ) ;
659+ }
660+ }
0 commit comments