Skip to content

Commit 500df82

Browse files
committed
extend normalizer to handle all alloy parsing incompatibilities
1 parent 928b0eb commit 500df82

File tree

1 file changed

+116
-8
lines changed

1 file changed

+116
-8
lines changed

graph/src/data_source/common.rs

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,33 @@ use slog::Logger;
2424
use std::collections::HashMap;
2525
use 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.
2748
fn 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)]
65102
pub 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

Comments
 (0)