Skip to content

Commit 1492eda

Browse files
incrypto32claude
andcommitted
graph: Normalize ABI JSON to handle undefined stateMutability
Some ABIs contain "undefined" as a stateMutability value, which is not part of the official Solidity ABI specification. The spec only defines four valid values: pure, view, nonpayable, and payable. Alloy's StateMutability enum strictly follows the spec and rejects "undefined" during deserialization, causing subgraph deployment failures with: unknown variant `undefined`, expected one of `pure`, `view`, `nonpayable`, `payable` This adds a normalize_abi_json() function that preprocesses ABI JSON before deserialization, replacing "undefined" with "nonpayable" (the default state mutability). This handles non-compliant ABIs gracefully while maintaining spec compliance. Also adds a unit test to verify the normalization works correctly. 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
1 parent ade80f1 commit 1492eda

File tree

1 file changed

+58
-2
lines changed

1 file changed

+58
-2
lines changed

graph/src/data_source/common.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ use slog::Logger;
2424
use std::collections::HashMap;
2525
use std::{str::FromStr, sync::Arc};
2626

27+
fn normalize_abi_json(json_bytes: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
28+
let mut value: serde_json::Value = serde_json::from_slice(json_bytes)?;
29+
30+
if let Some(array) = value.as_array_mut() {
31+
for item in array {
32+
if let Some(obj) = item.as_object_mut() {
33+
if let Some(state_mutability) = obj.get_mut("stateMutability") {
34+
if let Some(s) = state_mutability.as_str() {
35+
if s == "undefined" {
36+
*state_mutability = serde_json::Value::String("nonpayable".to_string());
37+
}
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
Ok(serde_json::to_vec(&value)?)
45+
}
46+
2747
#[derive(Clone, Debug, PartialEq)]
2848
pub struct MappingABI {
2949
pub name: String,
@@ -364,11 +384,14 @@ impl UnresolvedMappingABI {
364384
self.name, self.file.link
365385
)
366386
})?;
367-
let contract = serde_json::from_slice(&*contract_bytes)
387+
let normalized_bytes = normalize_abi_json(&contract_bytes)
388+
.with_context(|| format!("failed to normalize ABI JSON for {}", self.name))?;
389+
390+
let contract = serde_json::from_slice(&normalized_bytes)
368391
.with_context(|| format!("failed to load ABI {}", self.name))?;
369392

370393
// Parse ABI JSON for on-demand struct field extraction
371-
let abi_json = AbiJson::new(&contract_bytes)
394+
let abi_json = AbiJson::new(&normalized_bytes)
372395
.with_context(|| format!("Failed to parse ABI JSON for {}", self.name))?;
373396

374397
Ok((
@@ -2103,6 +2126,39 @@ mod tests {
21032126
assert!(error_msg.contains("is not a struct"));
21042127
}
21052128

2129+
#[test]
2130+
fn test_normalize_abi_json_with_undefined_state_mutability() {
2131+
let abi_with_undefined = r#"[
2132+
{
2133+
"type": "function",
2134+
"name": "testFunction",
2135+
"inputs": [],
2136+
"outputs": [],
2137+
"stateMutability": "undefined"
2138+
},
2139+
{
2140+
"type": "function",
2141+
"name": "normalFunction",
2142+
"inputs": [],
2143+
"outputs": [],
2144+
"stateMutability": "view"
2145+
}
2146+
]"#;
2147+
2148+
let normalized = normalize_abi_json(abi_with_undefined.as_bytes()).unwrap();
2149+
let result: serde_json::Value = serde_json::from_slice(&normalized).unwrap();
2150+
2151+
if let Some(array) = result.as_array() {
2152+
assert_eq!(array[0]["stateMutability"], "nonpayable");
2153+
assert_eq!(array[1]["stateMutability"], "view");
2154+
} else {
2155+
panic!("Expected JSON array");
2156+
}
2157+
2158+
let json_abi: abi::JsonAbi = serde_json::from_slice(&normalized).unwrap();
2159+
assert_eq!(json_abi.len(), 2);
2160+
}
2161+
21062162
// Helper function to create consistent test ABI
21072163
fn create_test_mapping_abi() -> AbiJson {
21082164
const ABI_JSON: &str = r#"[

0 commit comments

Comments
 (0)