alloy_sol_type_parser/
state_mutability.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "serde")]
5const COMPAT_ERROR: &str = "state mutability cannot be both `payable` and `constant`";
6
7/// A JSON ABI function's state mutability.
8///
9/// This will serialize/deserialize as the `stateMutability` JSON ABI field's value, see
10/// [`as_json_str`](Self::as_json_str).
11/// For backwards compatible deserialization, see [`serde_state_mutability_compat`].
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
15pub enum StateMutability {
16    /// Pure functions promise not to read from or modify the state.
17    Pure,
18    /// View functions promise not to modify the state.
19    View,
20    /// Nonpayable functions promise not to receive Ether.
21    ///
22    /// This is the solidity default: <https://docs.soliditylang.org/en/latest/abi-spec.html#json>
23    ///
24    /// The state mutability nonpayable is reflected in Solidity by not specifying a state
25    /// mutability modifier at all.
26    #[default]
27    NonPayable,
28    /// Payable functions make no promises.
29    Payable,
30}
31
32impl core::str::FromStr for StateMutability {
33    type Err = ();
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        Self::parse(s).ok_or(())
37    }
38}
39
40impl StateMutability {
41    /// Parses a state mutability from a string.
42    pub fn parse(s: &str) -> Option<Self> {
43        match s {
44            "pure" => Some(Self::Pure),
45            "view" => Some(Self::View),
46            "payable" => Some(Self::Payable),
47            _ => None,
48        }
49    }
50
51    /// Returns the string representation of the state mutability.
52    #[inline]
53    pub const fn as_str(self) -> Option<&'static str> {
54        if let Self::NonPayable = self { None } else { Some(self.as_json_str()) }
55    }
56
57    /// Returns the string representation of the state mutability when serialized to JSON.
58    #[inline]
59    pub const fn as_json_str(self) -> &'static str {
60        match self {
61            Self::Pure => "pure",
62            Self::View => "view",
63            Self::NonPayable => "nonpayable",
64            Self::Payable => "payable",
65        }
66    }
67}
68
69/// [`serde`] implementation for [`StateMutability`] for backwards compatibility with older
70/// versions of the JSON ABI.
71///
72/// In particular, this will deserialize the `stateMutability` field if it is present,
73/// and otherwise fall back to the deprecated `constant` and `payable` fields.
74///
75/// Since it must be used in combination with `#[serde(flatten)]`, a `serialize` implementation
76/// is also provided, which will always serialize the `stateMutability` field.
77///
78/// # Examples
79///
80/// Usage: `#[serde(default, flatten, with = "serde_state_mutability_compat")]` on a
81/// [`StateMutability`] struct field.
82///
83/// ```rust
84/// use alloy_sol_type_parser::{StateMutability, serde_state_mutability_compat};
85/// use serde::{Deserialize, Serialize};
86///
87/// #[derive(Serialize, Deserialize)]
88/// #[serde(rename_all = "camelCase")]
89/// struct MyStruct {
90///     #[serde(default, flatten, with = "serde_state_mutability_compat")]
91///     state_mutability: StateMutability,
92/// }
93///
94/// let json = r#"{"constant":true,"payable":false}"#;
95/// let ms = serde_json::from_str::<MyStruct>(json).expect("failed deserializing");
96/// assert_eq!(ms.state_mutability, StateMutability::View);
97///
98/// let reserialized = serde_json::to_string(&ms).expect("failed reserializing");
99/// assert_eq!(reserialized, r#"{"stateMutability":"view"}"#);
100/// ```
101#[cfg(feature = "serde")]
102pub mod serde_state_mutability_compat {
103    use super::*;
104    use serde::ser::SerializeStruct;
105
106    /// Deserializes a [`StateMutability`], compatible with older JSON ABI versions.
107    ///
108    /// See [the module-level documentation](self) for more information.
109    pub fn deserialize<'de, D: serde::Deserializer<'de>>(
110        deserializer: D,
111    ) -> Result<StateMutability, D::Error> {
112        #[derive(Deserialize)]
113        #[serde(rename_all = "camelCase")]
114        struct StateMutabilityCompat {
115            #[serde(default)]
116            state_mutability: Option<StateMutability>,
117            #[serde(default)]
118            payable: Option<bool>,
119            #[serde(default)]
120            constant: Option<bool>,
121        }
122
123        impl StateMutabilityCompat {
124            fn flatten(self) -> Option<StateMutability> {
125                let Self { state_mutability, payable, constant } = self;
126                if state_mutability.is_some() {
127                    return state_mutability;
128                }
129                match (payable.unwrap_or(false), constant.unwrap_or(false)) {
130                    (false, false) => Some(StateMutability::default()),
131                    (true, false) => Some(StateMutability::Payable),
132                    (false, true) => Some(StateMutability::View),
133                    (true, true) => None,
134                }
135            }
136        }
137
138        StateMutabilityCompat::deserialize(deserializer).and_then(|compat| {
139            compat.flatten().ok_or_else(|| serde::de::Error::custom(COMPAT_ERROR))
140        })
141    }
142
143    /// Serializes a [`StateMutability`] as a single-field struct (`stateMutability`).
144    ///
145    /// See [the module-level documentation](self) for more information.
146    pub fn serialize<S: serde::Serializer>(
147        state_mutability: &StateMutability,
148        serializer: S,
149    ) -> Result<S::Ok, S::Error> {
150        let mut s = serializer.serialize_struct("StateMutability", 1)?;
151        s.serialize_field("stateMutability", state_mutability)?;
152        s.end()
153    }
154}
155
156#[cfg(all(test, feature = "serde"))]
157mod tests {
158    use super::*;
159    use alloc::string::ToString;
160
161    #[derive(Debug, Serialize, Deserialize)]
162    struct CompatTest {
163        #[serde(default, flatten, with = "serde_state_mutability_compat")]
164        sm: StateMutability,
165    }
166
167    #[test]
168    fn test_compat() {
169        let test = |expect: StateMutability, json: &str| {
170            let compat = serde_json::from_str::<CompatTest>(json).expect(json);
171            assert_eq!(compat.sm, expect, "{json:?}");
172
173            let re_ser = serde_json::to_string(&compat).expect(json);
174            let expect = format!(r#"{{"stateMutability":"{}"}}"#, expect.as_json_str());
175            assert_eq!(re_ser, expect, "{json:?}");
176        };
177
178        test(StateMutability::Pure, r#"{"stateMutability":"pure"}"#);
179        test(
180            StateMutability::Pure,
181            r#"{"stateMutability":"pure","constant":false,"payable":false}"#,
182        );
183
184        test(StateMutability::View, r#"{"constant":true}"#);
185        test(StateMutability::View, r#"{"constant":true,"payable":false}"#);
186
187        test(StateMutability::Payable, r#"{"payable":true}"#);
188        test(StateMutability::Payable, r#"{"constant":false,"payable":true}"#);
189
190        test(StateMutability::NonPayable, r#"{}"#);
191        test(StateMutability::NonPayable, r#"{"constant":false}"#);
192        test(StateMutability::NonPayable, r#"{"payable":false}"#);
193        test(StateMutability::NonPayable, r#"{"constant":false,"payable":false}"#);
194
195        let json = r#"{"constant":true,"payable":true}"#;
196        let e = serde_json::from_str::<CompatTest>(json).unwrap_err().to_string();
197        assert!(e.contains(COMPAT_ERROR), "{e:?}");
198    }
199}