alloy_dyn_abi/eip712/
coerce.rs

1use crate::{DynSolType, DynSolValue, Error, Result, Word};
2use alloc::{
3    string::{String, ToString},
4    vec::Vec,
5};
6use alloy_primitives::{Address, Function, I256, U256};
7
8impl DynSolType {
9    /// Coerce a [`serde_json::Value`] to a [`DynSolValue`] via this type.
10    pub fn coerce_json(&self, value: &serde_json::Value) -> Result<DynSolValue> {
11        let err = || Error::eip712_coerce(self, value);
12        match self {
13            Self::Bool
14            | Self::Int(_)
15            | Self::Uint(_)
16            | Self::FixedBytes(_)
17            | Self::Address
18            | Self::Function
19            | Self::String
20            | Self::Bytes => self.coerce_json_simple(value).ok_or_else(err),
21
22            Self::Array(inner) => array(inner, value)
23                .ok_or_else(err)
24                .and_then(core::convert::identity)
25                .map(DynSolValue::Array),
26            Self::FixedArray(inner, n) => fixed_array(inner, *n, value)
27                .ok_or_else(err)
28                .and_then(core::convert::identity)
29                .map(DynSolValue::FixedArray),
30            Self::Tuple(inner) => tuple(inner, value)
31                .ok_or_else(err)
32                .and_then(core::convert::identity)
33                .map(DynSolValue::Tuple),
34            Self::CustomStruct { name, prop_names, tuple } => {
35                custom_struct(name, prop_names, tuple, value)
36            }
37        }
38    }
39
40    fn coerce_json_simple(&self, value: &serde_json::Value) -> Option<DynSolValue> {
41        match self {
42            Self::Bool => bool(value).map(DynSolValue::Bool),
43            &Self::Int(n) => int(n, value).map(|x| DynSolValue::Int(x, n)),
44            &Self::Uint(n) => uint(n, value).map(|x| DynSolValue::Uint(x, n)),
45            &Self::FixedBytes(n) => fixed_bytes(n, value).map(|x| DynSolValue::FixedBytes(x, n)),
46            Self::Address => address(value).map(DynSolValue::Address),
47            Self::Function => function(value).map(DynSolValue::Function),
48            Self::String => string(value).map(DynSolValue::String),
49            Self::Bytes => bytes(value).map(DynSolValue::Bytes),
50            _ => unreachable!(),
51        }
52    }
53}
54
55fn bool(value: &serde_json::Value) -> Option<bool> {
56    value.as_bool().or_else(|| value.as_str().and_then(|s| s.parse().ok()))
57}
58
59fn int(n: usize, value: &serde_json::Value) -> Option<I256> {
60    (|| {
61        if let Some(num) = value.as_i64() {
62            return Some(I256::try_from(num).unwrap());
63        }
64        value.as_str().and_then(|s| s.parse().ok())
65    })()
66    .and_then(|x| (x.bits() <= n as u32).then_some(x))
67}
68
69fn uint(n: usize, value: &serde_json::Value) -> Option<U256> {
70    (|| {
71        if let Some(num) = value.as_u64() {
72            return Some(U256::from(num));
73        }
74        value.as_str().and_then(|s| s.parse().ok())
75    })()
76    .and_then(|x| (x.bit_len() <= n).then_some(x))
77}
78
79fn fixed_bytes(n: usize, value: &serde_json::Value) -> Option<Word> {
80    if let Some(Ok(buf)) = value.as_str().map(hex::decode) {
81        let mut word = Word::ZERO;
82        let min = n.min(buf.len());
83        if min <= 32 {
84            word[..min].copy_from_slice(&buf[..min]);
85            return Some(word);
86        }
87    }
88    None
89}
90
91fn address(value: &serde_json::Value) -> Option<Address> {
92    value.as_str().and_then(|s| s.parse().ok())
93}
94
95fn function(value: &serde_json::Value) -> Option<Function> {
96    value.as_str().and_then(|s| s.parse().ok())
97}
98
99fn string(value: &serde_json::Value) -> Option<String> {
100    value.as_str().map(|s| s.to_string())
101}
102
103fn bytes(value: &serde_json::Value) -> Option<Vec<u8>> {
104    if let Some(s) = value.as_str() {
105        return hex::decode(s).ok();
106    }
107
108    let arr = value.as_array()?;
109    let mut vec = Vec::with_capacity(arr.len());
110    for elem in arr.iter() {
111        vec.push(elem.as_u64()?.try_into().ok()?);
112    }
113    Some(vec)
114}
115
116fn tuple(inner: &[DynSolType], value: &serde_json::Value) -> Option<Result<Vec<DynSolValue>>> {
117    if let Some(arr) = value.as_array() {
118        if inner.len() == arr.len() {
119            return Some(core::iter::zip(arr, inner).map(|(v, t)| t.coerce_json(v)).collect());
120        }
121    }
122    None
123}
124
125fn array(inner: &DynSolType, value: &serde_json::Value) -> Option<Result<Vec<DynSolValue>>> {
126    if let Some(arr) = value.as_array() {
127        return Some(arr.iter().map(|v| inner.coerce_json(v)).collect());
128    }
129    None
130}
131
132fn fixed_array(
133    inner: &DynSolType,
134    n: usize,
135    value: &serde_json::Value,
136) -> Option<Result<Vec<DynSolValue>>> {
137    if let Some(arr) = value.as_array() {
138        if arr.len() == n {
139            return Some(arr.iter().map(|v| inner.coerce_json(v)).collect());
140        }
141    }
142    None
143}
144
145pub(crate) fn custom_struct(
146    name: &str,
147    prop_names: &[String],
148    inner: &[DynSolType],
149    value: &serde_json::Value,
150) -> Result<DynSolValue> {
151    if let Some(map) = value.as_object() {
152        let mut tuple = vec![];
153        for (name, ty) in core::iter::zip(prop_names, inner) {
154            if let Some(v) = map.get(name) {
155                tuple.push(ty.coerce_json(v)?);
156            } else {
157                return Err(Error::eip712_coerce(
158                    &DynSolType::CustomStruct {
159                        name: name.to_string(),
160                        prop_names: prop_names.to_vec(),
161                        tuple: inner.to_vec(),
162                    },
163                    value,
164                ));
165            }
166        }
167        return Ok(DynSolValue::CustomStruct {
168            name: name.to_string(),
169            prop_names: prop_names.to_vec(),
170            tuple,
171        });
172    }
173
174    Err(Error::eip712_coerce(
175        &DynSolType::CustomStruct {
176            name: name.to_string(),
177            prop_names: prop_names.to_vec(),
178            tuple: inner.to_vec(),
179        },
180        value,
181    ))
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use alloc::{borrow::ToOwned, string::ToString};
188    use serde_json::json;
189
190    #[test]
191    fn test_bytes_num_array() {
192        let ty = DynSolType::Bytes;
193        let j = json!([1, 2, 3, 4]);
194        assert_eq!(ty.coerce_json(&j), Ok(DynSolValue::Bytes(vec![1, 2, 3, 4])));
195    }
196
197    #[test]
198    fn it_coerces() {
199        let j = json!({
200            "message": {
201                "contents": "Hello, Bob!",
202                "attachedMoneyInEth": 4.2,
203                "from": {
204                    "name": "Cow",
205                    "wallets": [
206                        "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
207                        "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF",
208                    ]
209                },
210                "to": [{
211                    "name": "Bob",
212                    "wallets": [
213                        "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
214                        "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
215                        "0xB0B0b0b0b0b0B000000000000000000000000000",
216                    ]
217                }]
218            }
219        });
220
221        let ty = DynSolType::CustomStruct {
222            name: "Message".to_owned(),
223            prop_names: vec!["contents".to_string(), "from".to_string(), "to".to_string()],
224            tuple: vec![
225                DynSolType::String,
226                DynSolType::CustomStruct {
227                    name: "Person".to_owned(),
228                    prop_names: vec!["name".to_string(), "wallets".to_string()],
229                    tuple: vec![
230                        DynSolType::String,
231                        DynSolType::Array(Box::new(DynSolType::Address)),
232                    ],
233                },
234                DynSolType::Array(Box::new(DynSolType::CustomStruct {
235                    name: "Person".to_owned(),
236                    prop_names: vec!["name".to_string(), "wallets".to_string()],
237                    tuple: vec![
238                        DynSolType::String,
239                        DynSolType::Array(Box::new(DynSolType::Address)),
240                    ],
241                })),
242            ],
243        };
244        let top = j.as_object().unwrap().get("message").unwrap();
245
246        assert_eq!(
247            ty.coerce_json(top),
248            Ok(DynSolValue::CustomStruct {
249                name: "Message".to_owned(),
250                prop_names: vec!["contents".to_string(), "from".to_string(), "to".to_string()],
251                tuple: vec![
252                    DynSolValue::String("Hello, Bob!".to_string()),
253                    DynSolValue::CustomStruct {
254                        name: "Person".to_owned(),
255                        prop_names: vec!["name".to_string(), "wallets".to_string()],
256                        tuple: vec![
257                            DynSolValue::String("Cow".to_string()),
258                            vec![
259                                DynSolValue::Address(
260                                    "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826".parse().unwrap()
261                                ),
262                                DynSolValue::Address(
263                                    "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF".parse().unwrap()
264                                ),
265                            ]
266                            .into()
267                        ]
268                    },
269                    vec![DynSolValue::CustomStruct {
270                        name: "Person".to_owned(),
271                        prop_names: vec!["name".to_string(), "wallets".to_string()],
272                        tuple: vec![
273                            DynSolValue::String("Bob".to_string()),
274                            vec![
275                                DynSolValue::Address(
276                                    "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB".parse().unwrap()
277                                ),
278                                DynSolValue::Address(
279                                    "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57".parse().unwrap()
280                                ),
281                                DynSolValue::Address(
282                                    "0xB0B0b0b0b0b0B000000000000000000000000000".parse().unwrap()
283                                ),
284                            ]
285                            .into()
286                        ]
287                    }]
288                    .into()
289                ]
290            })
291        );
292    }
293}