-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(json-abi): support legacy JSON ABIs (#596)
feat(json-abi): support legacy JSON ABI
- Loading branch information
Showing
8 changed files
with
245 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
const COMPAT_ERROR: &str = "state mutability cannot be both `payable` and `constant`"; | ||
|
||
/// A JSON ABI function's state mutability. | ||
/// | ||
/// This will serialize/deserialize as the `stateMutability` JSON ABI field's value, see | ||
/// [`as_json_str`](Self::as_json_str). | ||
/// For backwards compatible deserialization, see [`serde_state_mutability_compat`]. | ||
#[derive( | ||
Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, | ||
)] | ||
#[serde(rename_all = "lowercase")] | ||
pub enum StateMutability { | ||
/// Pure functions promise not to read from or modify the state. | ||
Pure, | ||
/// View functions promise not to modify the state. | ||
View, | ||
/// Nonpayable functions promise not to receive Ether. | ||
/// | ||
/// This is the solidity default: <https://docs.soliditylang.org/en/latest/abi-spec.html#json> | ||
/// | ||
/// The state mutability nonpayable is reflected in Solidity by not specifying a state | ||
/// mutability modifier at all. | ||
#[default] | ||
NonPayable, | ||
/// Payable functions make no promises. | ||
Payable, | ||
} | ||
|
||
impl StateMutability { | ||
/// Returns the string representation of the state mutability. | ||
#[inline] | ||
pub const fn as_str(self) -> Option<&'static str> { | ||
if let Self::NonPayable = self { | ||
None | ||
} else { | ||
Some(self.as_json_str()) | ||
} | ||
} | ||
|
||
/// Returns the string representation of the state mutability when serialized to JSON. | ||
#[inline] | ||
pub const fn as_json_str(self) -> &'static str { | ||
match self { | ||
Self::Pure => "pure", | ||
Self::View => "view", | ||
Self::NonPayable => "nonpayable", | ||
Self::Payable => "payable", | ||
} | ||
} | ||
} | ||
|
||
/// [`serde`] implementation for [`StateMutability`] for backwards compatibility with older | ||
/// versions of the JSON ABI. | ||
/// | ||
/// In particular, this will deserialize the `stateMutability` field if it is present, | ||
/// and otherwise fall back to the deprecated `constant` and `payable` fields. | ||
/// | ||
/// Since it must be used in combination with `#[serde(flatten)]`, a `serialize` implementation | ||
/// is also provided, which will always serialize the `stateMutability` field. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Usage: `#[serde(default, flatten, with = "serde_state_mutability_compat")]` on a | ||
/// [`StateMutability`] struct field. | ||
/// | ||
/// ```rust | ||
/// use alloy_json_abi::{serde_state_mutability_compat, StateMutability}; | ||
/// use serde::{Deserialize, Serialize}; | ||
/// | ||
/// #[derive(Serialize, Deserialize)] | ||
/// #[serde(rename_all = "camelCase")] | ||
/// struct MyStruct { | ||
/// #[serde(default, flatten, with = "serde_state_mutability_compat")] | ||
/// state_mutability: StateMutability, | ||
/// } | ||
/// | ||
/// let json = r#"{"constant":true,"payable":false}"#; | ||
/// let ms = serde_json::from_str::<MyStruct>(json).expect("failed deserializing"); | ||
/// assert_eq!(ms.state_mutability, StateMutability::View); | ||
/// | ||
/// let reserialized = serde_json::to_string(&ms).expect("failed reserializing"); | ||
/// assert_eq!(reserialized, r#"{"stateMutability":"view"}"#); | ||
/// ``` | ||
pub mod serde_state_mutability_compat { | ||
use super::*; | ||
use serde::ser::SerializeStruct; | ||
|
||
/// Deserializes a [`StateMutability`], compatibile with older JSON ABI versions. | ||
/// | ||
/// See [the module-level documentation](self) for more information. | ||
pub fn deserialize<'de, D: serde::Deserializer<'de>>( | ||
deserializer: D, | ||
) -> Result<StateMutability, D::Error> { | ||
#[derive(Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
struct StateMutabilityCompat { | ||
#[serde(default)] | ||
state_mutability: Option<StateMutability>, | ||
#[serde(default)] | ||
payable: Option<bool>, | ||
#[serde(default)] | ||
constant: Option<bool>, | ||
} | ||
|
||
impl StateMutabilityCompat { | ||
fn flatten(self) -> Option<StateMutability> { | ||
let Self { state_mutability, payable, constant } = self; | ||
if state_mutability.is_some() { | ||
return state_mutability; | ||
} | ||
match (payable.unwrap_or(false), constant.unwrap_or(false)) { | ||
(false, false) => Some(StateMutability::default()), | ||
(true, false) => Some(StateMutability::Payable), | ||
(false, true) => Some(StateMutability::View), | ||
(true, true) => None, | ||
} | ||
} | ||
} | ||
|
||
StateMutabilityCompat::deserialize(deserializer).and_then(|compat| { | ||
compat.flatten().ok_or_else(|| serde::de::Error::custom(COMPAT_ERROR)) | ||
}) | ||
} | ||
|
||
/// Serializes a [`StateMutability`] as a single-field struct (`stateMutability`). | ||
/// | ||
/// See [the module-level documentation](self) for more information. | ||
pub fn serialize<S: serde::Serializer>( | ||
state_mutability: &StateMutability, | ||
serializer: S, | ||
) -> Result<S::Ok, S::Error> { | ||
let mut s = serializer.serialize_struct("StateMutability", 1)?; | ||
s.serialize_field("stateMutability", state_mutability)?; | ||
s.end() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use alloc::string::ToString; | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
struct CompatTest { | ||
#[serde(default, flatten, with = "serde_state_mutability_compat")] | ||
sm: StateMutability, | ||
} | ||
|
||
#[test] | ||
fn test_compat() { | ||
let test = |expect: StateMutability, json: &str| { | ||
let compat = serde_json::from_str::<CompatTest>(json).expect(json); | ||
assert_eq!(compat.sm, expect, "{json:?}"); | ||
|
||
let re_ser = serde_json::to_string(&compat).expect(json); | ||
let expect = format!(r#"{{"stateMutability":"{}"}}"#, expect.as_json_str()); | ||
assert_eq!(re_ser, expect, "{json:?}"); | ||
}; | ||
|
||
test(StateMutability::Pure, r#"{"stateMutability":"pure"}"#); | ||
test( | ||
StateMutability::Pure, | ||
r#"{"stateMutability":"pure","constant":false,"payable":false}"#, | ||
); | ||
|
||
test(StateMutability::View, r#"{"constant":true}"#); | ||
test(StateMutability::View, r#"{"constant":true,"payable":false}"#); | ||
|
||
test(StateMutability::Payable, r#"{"payable":true}"#); | ||
test(StateMutability::Payable, r#"{"constant":false,"payable":true}"#); | ||
|
||
test(StateMutability::NonPayable, r#"{}"#); | ||
test(StateMutability::NonPayable, r#"{"constant":false}"#); | ||
test(StateMutability::NonPayable, r#"{"payable":false}"#); | ||
test(StateMutability::NonPayable, r#"{"constant":false,"payable":false}"#); | ||
|
||
let json = r#"{"constant":true,"payable":true}"#; | ||
let e = serde_json::from_str::<CompatTest>(json).unwrap_err().to_string(); | ||
assert!(e.contains(COMPAT_ERROR), "{e:?}"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.