11//! Transaction-related network functions: fetching, decoding, and resolving call ABIs.
22
3+ use std:: collections:: HashMap ;
34use std:: sync:: Arc ;
45
6+ use starknet:: core:: types:: Felt ;
57use tokio:: sync:: mpsc;
68use tracing:: { debug, error, info} ;
79
810use crate :: app:: actions:: Action ;
911use crate :: data:: DataSource ;
12+ use crate :: data:: pathfinder:: PathfinderClient ;
1013use crate :: data:: types:: SnTransaction ;
1114use crate :: decode:: AbiRegistry ;
15+ use crate :: decode:: abi:: ParsedAbi ;
1216use crate :: decode:: events:: decode_event;
1317use crate :: decode:: functions:: parse_multicall;
1418use crate :: decode:: outside_execution:: {
1519 OutsideExecutionInfo , OutsideExecutionVersion , is_avnu_forwarder, is_outside_execution,
1620 looks_like_outside_execution, parse_forwarder_call, parse_outside_execution,
1721} ;
1822
23+ /// Resolve the ABI for `addr` as of `block`. Prefers the prewarmed
24+ /// `addr_to_class` map; on miss, falls back to a synchronous resolve via
25+ /// `class_history` (cached or fetched). If that also fails, falls through
26+ /// to the latest-ABI path (same behaviour as pre-issue-#24 code) so any
27+ /// degradation is graceful.
28+ async fn abi_at_block (
29+ addr : & Felt ,
30+ block : u64 ,
31+ addr_to_class : & HashMap < Felt , Felt > ,
32+ ds : & Arc < dyn DataSource > ,
33+ pf : Option < & Arc < PathfinderClient > > ,
34+ abi_reg : & AbiRegistry ,
35+ ) -> Option < Arc < ParsedAbi > > {
36+ if let Some ( class_hash) = addr_to_class. get ( addr) {
37+ return abi_reg. get_abi_for_class ( class_hash) . await ;
38+ }
39+ if block > 0
40+ && let Some ( class_hash) = super :: helpers:: resolve_class_hash_at ( * addr, block, ds, pf) . await
41+ {
42+ return abi_reg. get_abi_for_class ( & class_hash) . await ;
43+ }
44+ abi_reg. get_abi_for_address ( addr) . await
45+ }
46+
1947/// Resolve selector names, function definitions, and contract ABIs for a list of calls.
2048/// Shared by all code paths that produce a `TransactionLoaded` action.
2149pub ( super ) async fn resolve_call_abis (
2250 calls : & mut [ crate :: decode:: functions:: RawCall ] ,
51+ block : u64 ,
52+ addr_to_class : & HashMap < Felt , Felt > ,
53+ ds : & Arc < dyn DataSource > ,
54+ pf : Option < & Arc < PathfinderClient > > ,
2355 abi_reg : & Arc < AbiRegistry > ,
2456) {
2557 for call in calls. iter_mut ( ) {
2658 if let Some ( name) = abi_reg. get_selector_name ( & call. selector ) {
2759 call. function_name = Some ( name) ;
2860 }
29- if let Some ( abi) = abi_reg. get_abi_for_address ( & call. contract_address ) . await {
61+ if let Some ( abi) = abi_at_block (
62+ & call. contract_address ,
63+ block,
64+ addr_to_class,
65+ ds,
66+ pf,
67+ abi_reg,
68+ )
69+ . await
70+ {
3071 if let Some ( func) = abi. get_function ( & call. selector ) {
3172 if call. function_name . is_none ( ) {
3273 call. function_name = Some ( func. name . clone ( ) ) ;
@@ -43,42 +84,57 @@ pub(super) async fn decode_and_send_transaction(
4384 transaction : SnTransaction ,
4485 receipt : crate :: data:: types:: SnReceipt ,
4586 ds : & Arc < dyn DataSource > ,
87+ pf : Option < & Arc < PathfinderClient > > ,
4688 abi_reg : & Arc < AbiRegistry > ,
4789 action_tx : & mpsc:: UnboundedSender < Action > ,
4890) {
91+ let block = receipt. block_number ;
92+
4993 // Parse multicall up front so we can prewarm the ABI cache for every
5094 // unique address referenced by this tx (event sources + call targets)
51- // in a single parallel round-trip. Every subsequent per-event /
52- // per-call `get_abi_for_address` call then hits warm cache.
95+ // in a single parallel round-trip — resolving each address's class
96+ // hash *as of `block`* so post-upgrade contracts decode pre-upgrade
97+ // events correctly (issue #24). Subsequent per-event / per-call ABI
98+ // lookups consult the resolved (address → class_hash) map first.
5399 let mut decoded_calls = match & transaction {
54100 SnTransaction :: Invoke ( invoke) => parse_multicall ( & invoke. calldata ) ,
55101 _ => Vec :: new ( ) ,
56102 } ;
57- let mut prewarm_targets: std:: collections:: HashSet < starknet :: core :: types :: Felt > =
103+ let mut prewarm_targets: std:: collections:: HashSet < Felt > =
58104 std:: collections:: HashSet :: with_capacity ( receipt. events . len ( ) + decoded_calls. len ( ) ) ;
59105 for event in & receipt. events {
60106 prewarm_targets. insert ( event. from_address ) ;
61107 }
62108 for call in & decoded_calls {
63109 prewarm_targets. insert ( call. contract_address ) ;
64110 }
65- super :: helpers:: prewarm_abis ( prewarm_targets, abi_reg) . await ;
111+ let addr_to_class = if block > 0 {
112+ super :: helpers:: prewarm_abis_at ( prewarm_targets, block, ds, pf, abi_reg) . await
113+ } else {
114+ // Pending tx (no block yet). Use latest-ABI prewarm.
115+ super :: helpers:: prewarm_abis ( prewarm_targets, abi_reg) . await ;
116+ HashMap :: new ( )
117+ } ;
66118
67119 let mut decoded_events = Vec :: with_capacity ( receipt. events . len ( ) ) ;
68120 for event in & receipt. events {
69- let abi = abi_reg . get_abi_for_address ( & event. from_address ) . await ;
121+ let abi = abi_at_block ( & event. from_address , block , & addr_to_class , ds , pf , abi_reg ) . await ;
70122 decoded_events. push ( decode_event ( event, abi. as_deref ( ) ) ) ;
71123 }
72- resolve_call_abis ( & mut decoded_calls, abi_reg) . await ;
73- let outside_executions = detect_and_resolve_outside_executions ( & decoded_calls, abi_reg) . await ;
124+ resolve_call_abis ( & mut decoded_calls, block, & addr_to_class, ds, pf, abi_reg) . await ;
125+ let outside_executions = detect_and_resolve_outside_executions (
126+ & decoded_calls,
127+ block,
128+ & addr_to_class,
129+ ds,
130+ pf,
131+ abi_reg,
132+ )
133+ . await ;
74134
75135 // Fetch block timestamp (used for age display and price lookups on tracked tokens).
76136 // Block fetches are cached, so repeat calls for the same block are cheap.
77- let block_timestamp = ds
78- . get_block ( receipt. block_number )
79- . await
80- . ok ( )
81- . map ( |b| b. timestamp ) ;
137+ let block_timestamp = ds. get_block ( block) . await . ok ( ) . map ( |b| b. timestamp ) ;
82138
83139 let _ = action_tx. send ( Action :: TransactionLoaded {
84140 transaction,
@@ -98,6 +154,10 @@ pub(super) async fn decode_and_send_transaction(
98154/// 3. By known forwarder address (AVNU paymaster wraps outside execution in execute/execute_sponsored)
99155async fn detect_and_resolve_outside_executions (
100156 calls : & [ crate :: decode:: functions:: RawCall ] ,
157+ block : u64 ,
158+ addr_to_class : & HashMap < Felt , Felt > ,
159+ ds : & Arc < dyn DataSource > ,
160+ pf : Option < & Arc < PathfinderClient > > ,
101161 abi_reg : & Arc < AbiRegistry > ,
102162) -> Vec < ( usize , OutsideExecutionInfo ) > {
103163 let mut results = Vec :: new ( ) ;
@@ -120,7 +180,7 @@ async fn detect_and_resolve_outside_executions(
120180 }
121181
122182 if let Some ( mut oe) = oe {
123- resolve_call_abis ( & mut oe. inner_calls , abi_reg) . await ;
183+ resolve_call_abis ( & mut oe. inner_calls , block , addr_to_class , ds , pf , abi_reg) . await ;
124184 results. push ( ( i, oe) ) ;
125185 }
126186 }
@@ -132,6 +192,7 @@ async fn detect_and_resolve_outside_executions(
132192pub ( super ) async fn fetch_and_send_transaction (
133193 hash : starknet:: core:: types:: Felt ,
134194 ds : & Arc < dyn DataSource > ,
195+ pf : Option < & Arc < PathfinderClient > > ,
135196 abi_reg : & Arc < AbiRegistry > ,
136197 tx : & mpsc:: UnboundedSender < Action > ,
137198) {
@@ -145,7 +206,7 @@ pub(super) async fn fetch_and_send_transaction(
145206 match ( tx_result, receipt_result) {
146207 ( Ok ( transaction) , Ok ( receipt) ) => {
147208 info ! ( tx_hash = %hash_short, elapsed_ms = start. elapsed( ) . as_millis( ) , "Transaction fetched, decoding" ) ;
148- decode_and_send_transaction ( transaction, receipt, ds, abi_reg, tx) . await ;
209+ decode_and_send_transaction ( transaction, receipt, ds, pf , abi_reg, tx) . await ;
149210 }
150211 ( Err ( e) , _) | ( _, Err ( e) ) => {
151212 error ! ( tx_hash = %hash_short, elapsed_ms = start. elapsed( ) . as_millis( ) , error = %e, "Failed to fetch transaction" ) ;
0 commit comments