@@ -24,11 +24,33 @@ use slog::Logger;
2424use std:: collections:: HashMap ;
2525use std:: { str:: FromStr , sync:: Arc } ;
2626
27+ /// Normalizes ABI JSON to handle compatibility issues between the legacy `ethabi`/`rust-web3`
28+ /// parser and the stricter `alloy` parser.
29+ ///
30+ /// Some deployed subgraph ABIs contain non-standard constructs that `ethabi` accepted but
31+ /// `alloy` rejects. This function patches these issues to maintain backward compatibility:
32+ ///
33+ /// 1. **`stateMutability: "undefined"`** - Some ABIs use "undefined" which is not a valid
34+ /// Solidity state mutability. We replace it with "nonpayable".
35+ ///
36+ /// 2. **Duplicate constructors** - Some ABIs contain multiple constructor definitions.
37+ /// We keep only the first one.
38+ ///
39+ /// 3. **Duplicate fallback functions** - Similar to constructors, some ABIs have multiple
40+ /// fallback definitions. We keep only the first one.
41+ ///
42+ /// 4. **`indexed` field in non-event params** - The `indexed` field is only valid for event
43+ /// parameters, but some ABIs include it on function inputs/outputs. We strip it from
44+ /// non-event items.
45+ ///
46+ /// These issues were identified by validating ABIs across deployed subgraphs in production
47+ /// before the migration to alloy.
2748fn normalize_abi_json ( json_bytes : & [ u8 ] ) -> Result < Vec < u8 > , anyhow:: Error > {
2849 let mut value: serde_json:: Value = serde_json:: from_slice ( json_bytes) ?;
2950
3051 if let Some ( array) = value. as_array_mut ( ) {
3152 let mut found_constructor = false ;
53+ let mut found_fallback = false ;
3254 let mut indices_to_remove = Vec :: new ( ) ;
3355
3456 for ( index, item) in array. iter_mut ( ) . enumerate ( ) {
@@ -41,14 +63,19 @@ fn normalize_abi_json(json_bytes: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
4163 }
4264 }
4365
44- if let Some ( item_type) = obj. get ( "type" ) {
45- if item_type == "constructor" {
46- if found_constructor {
47- indices_to_remove. push ( index) ;
48- } else {
49- found_constructor = true ;
50- }
51- }
66+ let item_type = obj. get ( "type" ) . and_then ( |t| t. as_str ( ) ) ;
67+
68+ match item_type {
69+ Some ( "constructor" ) if found_constructor => indices_to_remove. push ( index) ,
70+ Some ( "constructor" ) => found_constructor = true ,
71+ Some ( "fallback" ) if found_fallback => indices_to_remove. push ( index) ,
72+ Some ( "fallback" ) => found_fallback = true ,
73+ _ => { }
74+ }
75+
76+ if item_type != Some ( "event" ) {
77+ strip_indexed_from_params ( obj. get_mut ( "inputs" ) ) ;
78+ strip_indexed_from_params ( obj. get_mut ( "outputs" ) ) ;
5279 }
5380 }
5481 }
@@ -61,6 +88,16 @@ fn normalize_abi_json(json_bytes: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
6188 Ok ( serde_json:: to_vec ( & value) ?)
6289}
6390
91+ fn strip_indexed_from_params ( params : Option < & mut serde_json:: Value > ) {
92+ if let Some ( serde_json:: Value :: Array ( arr) ) = params {
93+ for param in arr. iter_mut ( ) {
94+ if let Some ( obj) = param. as_object_mut ( ) {
95+ obj. remove ( "indexed" ) ;
96+ }
97+ }
98+ }
99+ }
100+
64101#[ derive( Clone , Debug , PartialEq ) ]
65102pub struct MappingABI {
66103 pub name : String ,
@@ -401,6 +438,8 @@ impl UnresolvedMappingABI {
401438 self . name, self . file. link
402439 )
403440 } ) ?;
441+ // Normalize the ABI to handle compatibility issues between ethabi and alloy parsers.
442+ // See `normalize_abi_json` for details on the specific issues being addressed.
404443 let normalized_bytes = normalize_abi_json ( & contract_bytes)
405444 . with_context ( || format ! ( "failed to normalize ABI JSON for {}" , self . name) ) ?;
406445
@@ -2214,6 +2253,75 @@ mod tests {
22142253 assert_eq ! ( json_abi. len( ) , 2 ) ;
22152254 }
22162255
2256+ #[ test]
2257+ fn test_normalize_abi_json_with_duplicate_fallbacks ( ) {
2258+ let abi_with_duplicate_fallbacks = r#"[
2259+ {
2260+ "type": "fallback",
2261+ "stateMutability": "payable"
2262+ },
2263+ {
2264+ "type": "function",
2265+ "name": "someFunction",
2266+ "inputs": [],
2267+ "outputs": [],
2268+ "stateMutability": "view"
2269+ },
2270+ {
2271+ "type": "fallback",
2272+ "stateMutability": "nonpayable"
2273+ }
2274+ ]"# ;
2275+
2276+ let normalized = normalize_abi_json ( abi_with_duplicate_fallbacks. as_bytes ( ) ) . unwrap ( ) ;
2277+ let result: serde_json:: Value = serde_json:: from_slice ( & normalized) . unwrap ( ) ;
2278+
2279+ if let Some ( array) = result. as_array ( ) {
2280+ assert_eq ! ( array. len( ) , 2 ) ;
2281+ assert_eq ! ( array[ 0 ] [ "type" ] , "fallback" ) ;
2282+ assert_eq ! ( array[ 0 ] [ "stateMutability" ] , "payable" ) ;
2283+ assert_eq ! ( array[ 1 ] [ "type" ] , "function" ) ;
2284+ } else {
2285+ panic ! ( "Expected JSON array" ) ;
2286+ }
2287+
2288+ let json_abi: abi:: JsonAbi = serde_json:: from_slice ( & normalized) . unwrap ( ) ;
2289+ assert_eq ! ( json_abi. len( ) , 2 ) ;
2290+ }
2291+
2292+ #[ test]
2293+ fn test_normalize_abi_json_strips_indexed_from_non_events ( ) {
2294+ let abi_with_indexed_in_function = r#"[
2295+ {
2296+ "type": "function",
2297+ "name": "testFunction",
2298+ "inputs": [{"name": "x", "type": "uint256", "indexed": true}],
2299+ "outputs": [{"name": "y", "type": "address", "indexed": false}],
2300+ "stateMutability": "view"
2301+ },
2302+ {
2303+ "type": "event",
2304+ "name": "TestEvent",
2305+ "anonymous": false,
2306+ "inputs": [{"name": "from", "type": "address", "indexed": true}]
2307+ }
2308+ ]"# ;
2309+
2310+ let normalized = normalize_abi_json ( abi_with_indexed_in_function. as_bytes ( ) ) . unwrap ( ) ;
2311+ let result: serde_json:: Value = serde_json:: from_slice ( & normalized) . unwrap ( ) ;
2312+
2313+ if let Some ( array) = result. as_array ( ) {
2314+ assert ! ( array[ 0 ] [ "inputs" ] [ 0 ] . get( "indexed" ) . is_none( ) ) ;
2315+ assert ! ( array[ 0 ] [ "outputs" ] [ 0 ] . get( "indexed" ) . is_none( ) ) ;
2316+ assert_eq ! ( array[ 1 ] [ "inputs" ] [ 0 ] [ "indexed" ] , true ) ;
2317+ } else {
2318+ panic ! ( "Expected JSON array" ) ;
2319+ }
2320+
2321+ let json_abi: abi:: JsonAbi = serde_json:: from_slice ( & normalized) . unwrap ( ) ;
2322+ assert_eq ! ( json_abi. len( ) , 2 ) ;
2323+ }
2324+
22172325 // Helper function to create consistent test ABI
22182326 fn create_test_mapping_abi ( ) -> AbiJson {
22192327 const ABI_JSON : & str = r#"[
0 commit comments