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 {
55            None
56        } else {
57            Some(self.as_json_str())
58        }
59    }
60
61    /// Returns the string representation of the state mutability when serialized to JSON.
62    #[inline]
63    pub const fn as_json_str(self) -> &'static str {
64        match self {
65            Self::Pure => "pure",
66            Self::View => "view",
67            Self::NonPayable => "nonpayable",
68            Self::Payable => "payable",
69        }
70    }
71}
72
73/// [`serde`] implementation for [`StateMutability`] for backwards compatibility with older
74/// versions of the JSON ABI.
75///
76/// In particular, this will deserialize the `stateMutability` field if it is present,
77/// and otherwise fall back to the deprecated `constant` and `payable` fields.
78///
79/// Since it must be used in combination with `#[serde(flatten)]`, a `serialize` implementation
80/// is also provided, which will always serialize the `stateMutability` field.
81///
82/// # Examples
83///
84/// Usage: `#[serde(default, flatten, with = "serde_state_mutability_compat")]` on a
85/// [`StateMutability`] struct field.
86///
87/// ```rust
88/// use alloy_sol_type_parser::{serde_state_mutability_compat, StateMutability};
89/// use serde::{Deserialize, Serialize};
90///
91/// #[derive(Serialize, Deserialize)]
92/// #[serde(rename_all = "camelCase")]
93/// struct MyStruct {
94///     #[serde(default, flatten, with = "serde_state_mutability_compat")]
95///     state_mutability: StateMutability,
96/// }
97///
98/// let json = r#"{"constant":true,"payable":false}"#;
99/// let ms = serde_json::from_str::<MyStruct>(json).expect("failed deserializing");
100/// assert_eq!(ms.state_mutability, StateMutability::View);
101///
102/// let reserialized = serde_json::to_string(&ms).expect("failed reserializing");
103/// assert_eq!(reserialized, r#"{"stateMutability":"view"}"#);
104/// ```
105#[cfg(feature = "serde")]
106pub mod serde_state_mutability_compat {
107    use super::*;
108    use serde::ser::SerializeStruct;
109
110    /// Deserializes a [`StateMutability`], compatible with older JSON ABI versions.
111    ///
112    /// See [the module-level documentation](self) for more information.
113    pub fn deserialize<'de, D: serde::Deserializer<'de>>(
114        deserializer: D,
115    ) -> Result<StateMutability, D::Error> {
116        #[derive(Deserialize)]
117        #[serde(rename_all = "camelCase")]
118        struct StateMutabilityCompat {
119            #[serde(default)]
120            state_mutability: Option<StateMutability>,
121            #[serde(default)]
122            payable: Option<bool>,
123            #[serde(default)]
124            constant: Option<bool>,
125        }
126
127        impl StateMutabilityCompat {
128            fn flatten(self) -> Option<StateMutability> {
129                let Self { state_mutability, payable, constant } = self;
130                if state_mutability.is_some() {
131                    return state_mutability;
132                }
133                match (payable.unwrap_or(false), constant.unwrap_or(false)) {
134                    (false, false) => Some(StateMutability::default()),
135                    (true, false) => Some(StateMutability::Payable),
136                    (false, true) => Some(StateMutability::View),
137                    (true, true) => None,
138                }
139            }
140        }
141
142        StateMutabilityCompat::deserialize(deserializer).and_then(|compat| {
143            compat.flatten().ok_or_else(|| serde::de::Error::custom(COMPAT_ERROR))
144        })
145    }
146
147    /// Serializes a [`StateMutability`] as a single-field struct (`stateMutability`).
148    ///
149    /// See [the module-level documentation](self) for more information.
150    pub fn serialize<S: serde::Serializer>(
151        state_mutability: &StateMutability,
152        serializer: S,
153    ) -> Result<S::Ok, S::Error> {
154        let mut s = serializer.serialize_struct("StateMutability", 1)?;
155        s.serialize_field("stateMutability", state_mutability)?;
156        s.end()
157    }
158}
159
160#[cfg(all(test, feature = "serde"))]
161mod tests {
162    use super::*;
163    use alloc::string::ToString;
164
165    #[derive(Debug, Serialize, Deserialize)]
166    struct CompatTest {
167        #[serde(default, flatten, with = "serde_state_mutability_compat")]
168        sm: StateMutability,
169    }
170
171    #[test]
172    fn test_compat() {
173        let test = |expect: StateMutability, json: &str| {
174            let compat = serde_json::from_str::<CompatTest>(json).expect(json);
175            assert_eq!(compat.sm, expect, "{json:?}");
176
177            let re_ser = serde_json::to_string(&compat).expect(json);
178            let expect = format!(r#"{{"stateMutability":"{}"}}"#, expect.as_json_str());
179            assert_eq!(re_ser, expect, "{json:?}");
180        };
181
182        test(StateMutability::Pure, r#"{"stateMutability":"pure"}"#);
183        test(
184            StateMutability::Pure,
185            r#"{"stateMutability":"pure","constant":false,"payable":false}"#,
186        );
187
188        test(StateMutability::View, r#"{"constant":true}"#);
189        test(StateMutability::View, r#"{"constant":true,"payable":false}"#);
190
191        test(StateMutability::Payable, r#"{"payable":true}"#);
192        test(StateMutability::Payable, r#"{"constant":false,"payable":true}"#);
193
194        test(StateMutability::NonPayable, r#"{}"#);
195        test(StateMutability::NonPayable, r#"{"constant":false}"#);
196        test(StateMutability::NonPayable, r#"{"payable":false}"#);
197        test(StateMutability::NonPayable, r#"{"constant":false,"payable":false}"#);
198
199        let json = r#"{"constant":true,"payable":true}"#;
200        let e = serde_json::from_str::<CompatTest>(json).unwrap_err().to_string();
201        assert!(e.contains(COMPAT_ERROR), "{e:?}");
202    }
203}