diff --git a/crates/json-abi/tests/abi_item_test.rs b/crates/json-abi/tests/abi_item_test.rs new file mode 100644 index 0000000000..685d6cda76 --- /dev/null +++ b/crates/json-abi/tests/abi_item_test.rs @@ -0,0 +1,140 @@ +mod test_helpers; + +use alloy_json_abi::{ + AbiItem, Event, EventParam, Function, + InternalType::{Other, Struct}, + Param, StateMutability, +}; +use std::borrow::Cow; + +#[test] +fn operation() { + let s = r#"{ + "type":"function", + "inputs": [{ + "name":"a", + "type":"address" + }], + "name":"foo", + "outputs": [], + "stateMutability": "nonpayable" + }"#; + + let deserialized: AbiItem<'static> = serde_json::from_str(s).unwrap(); + + #[allow(deprecated)] + let function = Function { + name: "foo".to_owned(), + inputs: vec![Param { + name: "a".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![], + }], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + }; + + assert_eq!(deserialized, AbiItem::Function(Cow::Owned(function))); + let ser = serde_json::to_string(&deserialized).unwrap(); + let de: AbiItem<'_> = serde_json::from_str(&ser).unwrap(); + assert_eq!(&deserialized, &de); + assert_ser_de!(AbiItem<'_>, deserialized); +} + +#[test] +fn event_operation_with_tuple_array_input() { + let s = r#"{ + "type":"event", + "inputs": [ + { + "name":"a", + "type":"address", + "indexed":true + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct Action[]", + "name": "b", + "type": "tuple[]" + } + ], + "name":"E", + "outputs": [], + "anonymous": false + }"#; + + let deserialized: AbiItem<'static> = serde_json::from_str(s).unwrap(); + + let event = Event { + name: "E".to_owned(), + inputs: vec![ + EventParam { + name: "a".to_owned(), + indexed: true, + ty: "address".into(), + components: vec![], + internal_type: None, + }, + EventParam { + name: "b".to_owned(), + indexed: false, + ty: "tuple[]".into(), + components: vec![ + Param { + name: "to".into(), + ty: "address".into(), + components: vec![], + internal_type: Some(Other { + contract: None, + ty: "address".into(), + }), + }, + Param { + name: "value".into(), + ty: "uint256".into(), + components: vec![], + internal_type: Some(Other { + contract: None, + ty: "uint256".into(), + }), + }, + Param { + name: "data".into(), + ty: "bytes".into(), + components: vec![], + internal_type: Some(Other { + contract: None, + ty: "bytes".into(), + }), + }, + ], + internal_type: Some(Struct { + contract: None, + ty: "Action[]".into(), + }), + }, + ], + anonymous: false, + }; + + assert_eq!(deserialized, AbiItem::Event(Cow::Owned(event))); + assert_ser_de!(AbiItem<'_>, deserialized); +} diff --git a/crates/json-abi/tests/event_param_test.rs b/crates/json-abi/tests/event_param_test.rs new file mode 100644 index 0000000000..0f1def5e15 --- /dev/null +++ b/crates/json-abi/tests/event_param_test.rs @@ -0,0 +1,274 @@ +mod test_helpers; + +use alloy_json_abi::{EventParam, Param}; + +#[test] +fn event_param_deserialization() { + let s = r#"{ + "name": "foo", + "type": "address", + "indexed": true + }"#; + + let deserialized: EventParam = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + EventParam { + name: "foo".to_owned(), + indexed: true, + ty: "address".into(), + components: vec![], + internal_type: None, + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn event_param_tuple_deserialization() { + let s = r#"{ + "name": "foo", + "type": "tuple", + "indexed": true, + "components": [ + { + "name": "a", + "type": "uint48" + }, + { + "name": "b", + "type": "tuple", + "components": [ + { + "name": "c", + "type": "address" + } + ] + } + ] + }"#; + + let deserialized: EventParam = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + EventParam { + name: "foo".to_owned(), + indexed: true, + ty: "tuple".into(), + components: vec![ + Param { + name: "a".into(), + ty: "uint48".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "b".into(), + ty: "tuple".into(), + components: vec![Param { + name: "c".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }], + internal_type: None, + } + ], + internal_type: None, + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn event_param_tuple_array_deserialization() { + let s = r#"{ + "components": [ + { + "type": "uint256", + "name": "a" + }, + { + "type": "address", + "name": "b" + }, + { + "components": [ + { + "type": "address", + "name": "c" + }, + { + "type": "address", + "name": "d" + } + ], + "type": "tuple", + "name": "e" + }, + { + "type": "uint256", + "name": "f" + }, + { + "components": [ + { + "components": [ + { + "type": "address", + "name": "g" + }, + { + "type": "bytes", + "name": "h" + } + ], + "type": "tuple[]", + "name": "i" + }, + { + "components": [ + { + "type": "address", + "name": "j" + }, + { + "type": "uint256", + "name": "k" + } + ], + "type": "tuple[]", + "name": "l" + }, + { + "type": "uint256", + "name": "m" + } + ], + "type": "tuple[]", + "name": "n" + }, + { + "type": "uint256", + "name": "o" + } + ], + "indexed": false, + "name": "LogTaskSubmitted", + "type": "tuple" + }"#; + + let deserialized: EventParam = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + EventParam { + name: "LogTaskSubmitted".to_owned(), + indexed: false, + ty: "tuple".into(), + components: vec![ + Param { + name: "a".into(), + ty: "uint256".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "b".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "e".into(), + ty: "tuple".into(), + components: vec![ + Param { + name: "c".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "d".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }, + ], + internal_type: None, + }, + Param { + name: "f".into(), + ty: "uint256".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "n".into(), + ty: "tuple[]".into(), + components: vec![ + Param { + name: "i".into(), + ty: "tuple[]".into(), + components: vec![ + Param { + name: "g".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "h".into(), + ty: "bytes".into(), + components: vec![], + internal_type: None, + }, + ], + internal_type: None, + }, + Param { + name: "l".into(), + ty: "tuple[]".into(), + components: vec![ + Param { + name: "j".into(), + ty: "address".into(), + components: vec![], + internal_type: None, + }, + Param { + name: "k".into(), + ty: "uint256".into(), + components: vec![], + internal_type: None, + }, + ], + internal_type: None, + }, + Param { + name: "m".into(), + ty: "uint256".into(), + components: vec![], + internal_type: None, + }, + ], + internal_type: None, + }, + Param { + name: "o".into(), + ty: "uint256".into(), + components: vec![], + internal_type: None, + }, + ], + internal_type: None + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} diff --git a/crates/json-abi/tests/json_abi_test.rs b/crates/json-abi/tests/json_abi_test.rs new file mode 100644 index 0000000000..1eeaa799fd --- /dev/null +++ b/crates/json-abi/tests/json_abi_test.rs @@ -0,0 +1,603 @@ +mod test_helpers; + +use alloy_json_abi::{ + Constructor, Error, Event, EventParam, Fallback, Function, JsonAbi, Param, Receive, + StateMutability, +}; +use std::collections::BTreeMap; + +#[test] +fn empty() { + let json = "[]"; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn constructor() { + let json = r#" + [ + { + "type": "constructor", + "inputs": [ + { + "name":"a", + "type":"address" + } + ], + "stateMutability": "nonpayable" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: Some(Constructor { + inputs: vec![Param { + name: "a".to_string(), + internal_type: None, + ty: "address".into(), + components: vec![], + }], + state_mutability: StateMutability::NonPayable + }), + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn functions() { + let json = r#" + [ + { + "type": "function", + "name": "foo", + "inputs": [ + { + "name":"a", + "type":"address" + } + ], + "outputs": [ + { + "name": "res", + "type":"address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "bar", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::from_iter(vec![ + ( + "foo".into(), + vec![Function { + name: "foo".into(), + inputs: vec![Param { + name: "a".into(), + internal_type: None, + ty: "address".into(), + components: vec![] + }], + outputs: vec![Param { + name: "res".into(), + internal_type: None, + ty: "address".into(), + components: vec![] + }], + state_mutability: StateMutability::NonPayable, + }] + ), + ( + "bar".into(), + vec![Function { + name: "bar".into(), + inputs: vec![], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + }] + ), + ]), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn functions_overloads() { + let json = r#" + [ + { + "type": "function", + "name": "foo", + "inputs": [ + { + "name":"a", + "type":"address" + } + ], + "outputs": [ + { + "name": "res", + "type":"address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "foo", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::from_iter(vec![( + "foo".to_string(), + vec![ + Function { + name: "foo".into(), + inputs: vec![Param { + name: "a".into(), + internal_type: None, + ty: "address".into(), + components: vec![], + }], + outputs: vec![Param { + name: "res".into(), + internal_type: None, + ty: "address".into(), + components: vec![] + }], + state_mutability: StateMutability::NonPayable, + }, + Function { + name: "foo".into(), + inputs: vec![], + outputs: vec![], + state_mutability: StateMutability::NonPayable, + }, + ] + )]), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn events() { + let json = r#" + [ + { + "type": "event", + "name": "foo", + "inputs": [ + { + "name":"a", + "type":"address", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "bar", + "inputs": [ + { + "name":"a", + "type":"address", + "indexed": true + } + ], + "anonymous": false + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::from_iter(vec![ + ( + "foo".into(), + vec![Event { + name: "foo".into(), + inputs: vec![EventParam { + name: "a".into(), + indexed: false, + ty: "address".into(), + components: vec![], + internal_type: None + }], + anonymous: false, + }] + ), + ( + "bar".to_string(), + vec![Event { + name: "bar".into(), + inputs: vec![EventParam { + name: "a".into(), + indexed: true, + ty: "address".into(), + components: vec![], + internal_type: None + }], + anonymous: false, + }] + ), + ]), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn events_overload() { + let json = r#" + [ + { + "type": "event", + "name": "foo", + "inputs": [ + { + "name":"a", + "type":"address", + "indexed": false + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "foo", + "inputs": [ + { + "name":"a", + "type":"address", + "indexed": true + } + ], + "anonymous": false + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::from_iter(vec![( + "foo".to_string(), + vec![ + Event { + name: "foo".into(), + inputs: vec![EventParam { + name: "a".into(), + indexed: false, + ty: "address".into(), + components: vec![], + internal_type: None + }], + anonymous: false, + }, + Event { + name: "foo".into(), + inputs: vec![EventParam { + name: "a".into(), + indexed: true, + ty: "address".into(), + components: vec![], + internal_type: None + }], + anonymous: false, + }, + ] + )]), + errors: BTreeMap::new(), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn errors() { + let json = r#" + [ + { + "type": "error", + "inputs": [ + { + "name": "available", + "type": "uint256" + }, + { + "name": "required", + "type": "address" + } + ], + "name": "foo" + }, + { + "type": "error", + "inputs": [ + { + "name": "a", + "type": "uint256" + }, + { + "name": "b", + "type": "address" + } + ], + "name": "bar" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::from_iter(vec![ + ( + "foo".to_string(), + vec![Error { + name: "foo".into(), + inputs: vec![ + Param { + name: "available".into(), + internal_type: None, + ty: "uint256".into(), + components: vec![] + }, + Param { + name: "required".into(), + internal_type: None, + ty: "address".into(), + components: vec![], + } + ], + }] + ), + ( + "bar".to_string(), + vec![Error { + name: "bar".into(), + inputs: vec![ + Param { + name: "a".into(), + internal_type: None, + ty: "uint256".into(), + components: vec![] + }, + Param { + name: "b".into(), + internal_type: None, + ty: "address".into(), + components: vec![] + } + ], + }] + ), + ]), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn errors_overload() { + let json = r#" + [ + { + "type": "error", + "inputs": [ + { + "name": "a", + "type": "uint256" + } + ], + "name": "foo" + }, + { + "type": "error", + "inputs": [ + { + "name": "a", + "type": "uint256" + }, + { + "name": "b", + "type": "address" + } + ], + "name": "foo" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::from_iter(vec![( + "foo".to_string(), + vec![ + Error { + name: "foo".into(), + inputs: vec![Param { + name: "a".into(), + internal_type: None, + ty: "uint256".into(), + components: vec![], + }], + }, + Error { + name: "foo".into(), + inputs: vec![ + Param { + name: "a".into(), + internal_type: None, + ty: "uint256".into(), + components: vec![], + }, + Param { + name: "b".into(), + internal_type: None, + ty: "address".into(), + components: vec![], + } + ], + }, + ] + ),]), + receive: None, + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn receive() { + let json = r#" + [ + { + "type": "receive", + "stateMutability": "nonpayable" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: Some(Receive { + state_mutability: StateMutability::NonPayable, + }), + fallback: None, + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} + +#[test] +fn fallback() { + let json = r#" + [ + { + "type": "fallback", + "stateMutability": "nonpayable" + } + ] + "#; + + let deserialized: JsonAbi = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + JsonAbi { + constructor: None, + functions: BTreeMap::new(), + events: BTreeMap::new(), + errors: BTreeMap::new(), + receive: None, + fallback: Some(Fallback { + state_mutability: StateMutability::NonPayable, + }), + } + ); + + assert_ser_de!(JsonAbi, deserialized); +} diff --git a/crates/json-abi/tests/param_tests.rs b/crates/json-abi/tests/param_tests.rs new file mode 100644 index 0000000000..6ff171a956 --- /dev/null +++ b/crates/json-abi/tests/param_tests.rs @@ -0,0 +1,444 @@ +mod test_helpers; + +use alloy_json_abi::{InternalType::Struct, Param}; + +#[test] +fn param_simple() { + let s = r#"{ + "name": "foo", + "type": "address" + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_simple_internal_type() { + let s = r#"{ + "name": "foo", + "type": "address", + "internalType": "struct Verifier.Proof" + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: Some(Struct { + contract: Some("Verifier".into()), + ty: "Proof".into(), + }), + ty: "address".into(), + components: vec![] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_tuple() { + let s = r#"{ + "name": "foo", + "type": "tuple", + "components": [ + { + "name": "a", + "type": "uint48" + }, + { + "name": "b", + "type": "tuple", + "components": [ + { + "type": "address", + "name": "c" + } + ] + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "uint48".into(), + components: vec![], + }, + Param { + name: "b".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![Param { + name: "c".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![], + },], + }, + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_tuple_internal_type() { + let s = r#"{ + "name": "foo", + "type": "tuple", + "internalType": "struct Pairing.G1Point[]", + "components": [ + { + "name": "a", + "type": "uint48" + }, + { + "name": "b", + "type": "tuple", + "components": [ + { + "name": "c", + "type": "address" + } + ] + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: Some(Struct { + contract: Some("Pairing".into()), + ty: "G1Point[]".into(), + }), + ty: "tuple".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "uint48".into(), + components: vec![] + }, + Param { + name: "b".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![Param { + name: "c".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + }] + } + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_tuple_named() { + let s = r#"{ + "name": "foo", + "type": "tuple", + "components": [ + { + "name": "amount", + "type": "uint48" + }, + { + "name": "things", + "type": "tuple", + "components": [ + { + "name": "baseTupleParam", + "type": "address" + } + ] + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![ + Param { + name: "amount".to_owned(), + internal_type: None, + ty: "uint48".into(), + components: vec![] + }, + Param { + name: "things".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![Param { + name: "baseTupleParam".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + },] + }, + ] + } + ); + + assert_ser_de!(Param, deserialized); +} + +#[test] +fn param_tuple_array() { + let s = r#"{ + "name": "foo", + "type": "tuple[]", + "components": [ + { + "name": "a", + "type": "uint48" + }, + { + "name": "b", + "type": "address" + }, + { + "name": "c", + "type": "address" + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple[]".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "uint48".into(), + components: vec![] + }, + Param { + name: "b".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + }, + Param { + name: "c".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + } + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_array_of_array_of_tuple() { + let s = r#"{ + "name": "foo", + "type": "tuple[][]", + "components": [ + { + "name": "a", + "type": "uint8" + }, + { + "name": "b", + "type": "uint16" + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple[][]".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "uint8".into(), + components: vec![] + }, + Param { + name: "b".to_owned(), + internal_type: None, + ty: "uint16".into(), + components: vec![] + } + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_tuple_fixed_array() { + let s = r#"{ + "name": "foo", + "type": "tuple[2]", + "components": [ + { + "name": "a", + "type": "uint48" + }, + { + "name": "b", + "type": "address" + }, + { + "name": "c", + "type": "address" + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple[2]".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "uint48".into(), + components: vec![] + }, + Param { + name: "b".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + }, + Param { + name: "c".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + } + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} + +#[test] +fn param_tuple_with_nested_tuple_arrays() { + let s = r#"{ + "name": "foo", + "type": "tuple", + "components": [ + { + "name": "a", + "type": "tuple[]", + "components": [ + { + "name": "b", + "type": "address" + } + ] + }, + { + "name": "c", + "type": "tuple[42]", + "components": [ + { + "name": "d", + "type": "address" + } + ] + } + ] + }"#; + + let deserialized: Param = serde_json::from_str(s).unwrap(); + + assert_eq!( + deserialized, + Param { + name: "foo".to_owned(), + internal_type: None, + ty: "tuple".into(), + components: vec![ + Param { + name: "a".to_owned(), + internal_type: None, + ty: "tuple[]".into(), + components: vec![Param { + name: "b".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + },] + }, + Param { + name: "c".to_owned(), + internal_type: None, + ty: "tuple[42]".into(), + components: vec![Param { + name: "d".to_owned(), + internal_type: None, + ty: "address".into(), + components: vec![] + }] + } + ] + } + ); + + assert_json_eq!(s, serde_json::to_string(&deserialized).unwrap().as_str()); +} diff --git a/crates/json-abi/tests/state_mutability_test.rs b/crates/json-abi/tests/state_mutability_test.rs new file mode 100644 index 0000000000..3ca4065716 --- /dev/null +++ b/crates/json-abi/tests/state_mutability_test.rs @@ -0,0 +1,29 @@ +mod test_helpers; + +use alloy_json_abi::StateMutability; + +#[test] +fn state_mutability() { + let json = r#" + [ + "pure", + "view", + "nonpayable", + "payable" + ] + "#; + + let deserialized: Vec = serde_json::from_str(json).unwrap(); + + assert_eq!( + deserialized, + vec![ + StateMutability::Pure, + StateMutability::View, + StateMutability::NonPayable, + StateMutability::Payable, + ] + ); + + assert_json_eq!(json, &serde_json::to_string(&deserialized).unwrap()); +} diff --git a/crates/json-abi/tests/test_helpers.rs b/crates/json-abi/tests/test_helpers.rs new file mode 100644 index 0000000000..01d3a03477 --- /dev/null +++ b/crates/json-abi/tests/test_helpers.rs @@ -0,0 +1,24 @@ +#[macro_export] +macro_rules! assert_ser_de { + ($type:ty, $value:expr) => {{ + let ser = serde_json::to_string(&$value).unwrap(); + let de: $type = serde_json::from_str(&ser).unwrap(); + assert_eq!( + &$value, &de, + "Original value and deserialized value do not match." + ); + }}; +} + +#[macro_export] +macro_rules! assert_json_eq { + ($left:expr, $right:expr) => {{ + let left_val: serde_json::Value = serde_json::from_str($left).unwrap(); + let right_val: serde_json::Value = serde_json::from_str($right).unwrap(); + assert_eq!( + left_val, right_val, + "JSON values are not equal: {} != {}", + $left, $right + ); + }}; +}