Skip to main content

noirc_abi/input_parser/
json.rs

1use super::{
2    InputValue, field_to_signed_hex, parse_integer_to_signed, parse_str_to_field,
3    parse_str_to_signed,
4};
5use crate::{Abi, AbiType, MAIN_RETURN_NAME, errors::InputParserError};
6use acvm::{AcirField, FieldElement};
7use iter_extended::{try_btree_map, try_vecmap};
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10
11pub fn parse_json(
12    input_string: &str,
13    abi: &Abi,
14) -> Result<BTreeMap<String, InputValue>, InputParserError> {
15    // Parse input.json into a BTreeMap.
16    let data: BTreeMap<String, JsonTypes> = serde_json::from_str(input_string)?;
17
18    // Convert arguments to field elements.
19    let mut parsed_inputs = try_btree_map(abi.to_btree_map(), |(arg_name, abi_type)| {
20        // Check that json contains a value for each argument in the ABI.
21        let value = data
22            .get(&arg_name)
23            .ok_or_else(|| InputParserError::MissingArgument(arg_name.clone()))?;
24
25        InputValue::try_from_json(value.clone(), &abi_type, &arg_name)
26            .map(|input_value| (arg_name, input_value))
27    })?;
28
29    // If the json file also includes a return value then we parse it as well.
30    // This isn't required as the prover calculates the return value itself.
31    if let (Some(return_type), Some(json_return_value)) =
32        (&abi.return_type, data.get(MAIN_RETURN_NAME))
33    {
34        let return_value = InputValue::try_from_json(
35            json_return_value.clone(),
36            &return_type.abi_type,
37            MAIN_RETURN_NAME,
38        )?;
39        parsed_inputs.insert(MAIN_RETURN_NAME.to_owned(), return_value);
40    }
41
42    Ok(parsed_inputs)
43}
44
45pub fn serialize_to_json(
46    input_map: &BTreeMap<String, InputValue>,
47    abi: &Abi,
48) -> Result<String, InputParserError> {
49    let mut json_map = try_btree_map(abi.to_btree_map(), |(key, param_type)| {
50        JsonTypes::try_from_input_value(&input_map[&key], &param_type)
51            .map(|value| (key.to_owned(), value))
52    })?;
53
54    if let (Some(return_type), Some(return_value)) =
55        (&abi.return_type, input_map.get(MAIN_RETURN_NAME))
56    {
57        let return_value = JsonTypes::try_from_input_value(return_value, &return_type.abi_type)?;
58        json_map.insert(MAIN_RETURN_NAME.to_owned(), return_value);
59    }
60
61    let json_string = serde_json::to_string(&json_map)?;
62
63    Ok(json_string)
64}
65
66#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
67#[serde(untagged)]
68pub enum JsonTypes {
69    // This is most likely going to be a hex string
70    // But it is possible to support UTF-8
71    String(String),
72    // Just a regular integer, that can fit in 64 bits.
73    //
74    // The JSON spec does not specify any limit on the size of integer number types,
75    // however we restrict the allowable size. Values which do not fit in a i64 should be passed
76    // as a string.
77    Integer(i64),
78    // Simple boolean flag
79    Bool(bool),
80    // Array of JsonTypes
81    Array(Vec<JsonTypes>),
82    // Struct of JsonTypes
83    Table(BTreeMap<String, JsonTypes>),
84}
85
86impl JsonTypes {
87    pub fn try_from_input_value(
88        value: &InputValue,
89        abi_type: &AbiType,
90    ) -> Result<JsonTypes, InputParserError> {
91        let json_value = match (value, abi_type) {
92            (InputValue::Field(f), AbiType::Integer { sign: crate::Sign::Signed, width }) => {
93                JsonTypes::String(field_to_signed_hex(*f, *width))
94            }
95            (InputValue::Field(f), AbiType::Field | AbiType::Integer { .. }) => {
96                JsonTypes::String(Self::format_field_string(*f))
97            }
98            (InputValue::Field(f), AbiType::Boolean) => JsonTypes::Bool(f.is_one()),
99
100            (InputValue::Vec(vector), AbiType::Array { typ, .. }) => {
101                let array =
102                    try_vecmap(vector, |value| JsonTypes::try_from_input_value(value, typ))?;
103                JsonTypes::Array(array)
104            }
105
106            (InputValue::String(s), AbiType::String { .. }) => JsonTypes::String(s.to_string()),
107
108            (InputValue::Struct(map), AbiType::Struct { fields, .. }) => {
109                let map_with_json_types = try_btree_map(fields, |(key, field_type)| {
110                    JsonTypes::try_from_input_value(&map[key], field_type)
111                        .map(|json_value| (key.to_owned(), json_value))
112                })?;
113                JsonTypes::Table(map_with_json_types)
114            }
115
116            (InputValue::Vec(vector), AbiType::Tuple { fields }) => {
117                let fields = try_vecmap(vector.iter().zip(fields), |(value, typ)| {
118                    JsonTypes::try_from_input_value(value, typ)
119                })?;
120                JsonTypes::Array(fields)
121            }
122
123            _ => {
124                return Err(InputParserError::AbiTypeMismatch(
125                    format!("{value:?}"),
126                    abi_type.clone(),
127                ));
128            }
129        };
130        Ok(json_value)
131    }
132
133    /// This trims any leading zeroes.
134    /// A singular '0' will be prepended as well if the trimmed string has an odd length.
135    /// A hex string's length needs to be even to decode into bytes, as two digits correspond to
136    /// one byte.
137    fn format_field_string(field: FieldElement) -> String {
138        if field.is_zero() {
139            return "0x00".to_owned();
140        }
141        let mut trimmed_field = field.to_hex().trim_start_matches('0').to_owned();
142        if trimmed_field.len() % 2 != 0 {
143            trimmed_field = "0".to_owned() + &trimmed_field;
144        }
145        "0x".to_owned() + &trimmed_field
146    }
147}
148
149impl InputValue {
150    pub fn try_from_json(
151        value: JsonTypes,
152        param_type: &AbiType,
153        arg_name: &str,
154    ) -> Result<InputValue, InputParserError> {
155        let input_value = match (value, param_type) {
156            (JsonTypes::String(string), AbiType::String { .. }) => InputValue::String(string),
157            (JsonTypes::String(string), AbiType::Integer { sign: crate::Sign::Signed, width }) => {
158                InputValue::Field(parse_str_to_signed(&string, *width, arg_name)?)
159            }
160            (
161                JsonTypes::String(string),
162                AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean,
163            ) => InputValue::Field(parse_str_to_field(&string, arg_name)?),
164
165            (
166                JsonTypes::Integer(integer),
167                AbiType::Integer { sign: crate::Sign::Signed, width },
168            ) => {
169                let new_value = parse_integer_to_signed(integer as i128, *width, arg_name)?;
170                InputValue::Field(new_value)
171            }
172
173            (
174                JsonTypes::Integer(integer),
175                AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean,
176            ) => {
177                let new_value = FieldElement::from(i128::from(integer));
178
179                InputValue::Field(new_value)
180            }
181
182            (JsonTypes::Bool(boolean), AbiType::Boolean) => InputValue::Field(boolean.into()),
183
184            (JsonTypes::Array(array), AbiType::Array { typ, .. }) => {
185                let mut index = 0;
186                let array_elements = try_vecmap(array, |value| {
187                    let sub_name = format!("{arg_name}[{index}]");
188                    let value = InputValue::try_from_json(value, typ, &sub_name);
189                    index += 1;
190                    value
191                })?;
192                InputValue::Vec(array_elements)
193            }
194
195            (JsonTypes::Table(table), AbiType::Struct { fields, .. }) => {
196                let native_table = try_btree_map(fields, |(field_name, abi_type)| {
197                    // Check that json contains a value for each field of the struct.
198                    let field_id = format!("{arg_name}.{field_name}");
199                    let value = table
200                        .get(field_name)
201                        .ok_or_else(|| InputParserError::MissingArgument(field_id.clone()))?;
202                    InputValue::try_from_json(value.clone(), abi_type, &field_id)
203                        .map(|input_value| (field_name.to_string(), input_value))
204                })?;
205
206                InputValue::Struct(native_table)
207            }
208
209            (JsonTypes::Array(array), AbiType::Tuple { fields }) => {
210                let mut index = 0;
211                let tuple_fields = try_vecmap(array.into_iter().zip(fields), |(value, typ)| {
212                    let sub_name = format!("{arg_name}[{index}]");
213                    let value = InputValue::try_from_json(value, typ, &sub_name);
214                    index += 1;
215                    value
216                })?;
217                InputValue::Vec(tuple_fields)
218            }
219
220            (value, _) => {
221                return Err(InputParserError::AbiTypeMismatch(
222                    format!("{value:?}"),
223                    param_type.clone(),
224                ));
225            }
226        };
227
228        Ok(input_value)
229    }
230}
231
232#[cfg(test)]
233mod test {
234    use acvm::FieldElement;
235    use proptest::prelude::*;
236
237    use crate::{
238        AbiType,
239        arbitrary::arb_abi_and_input_map,
240        input_parser::{InputValue, arbitrary::arb_signed_integer_type_and_value, json::JsonTypes},
241    };
242
243    use super::{parse_json, serialize_to_json};
244
245    proptest! {
246        #[test]
247        fn serializing_and_parsing_returns_original_input((abi, input_map) in arb_abi_and_input_map()) {
248            let json = serialize_to_json(&input_map, &abi).expect("should be serializable");
249            let parsed_input_map = parse_json(&json, &abi).expect("should be parsable");
250
251            prop_assert_eq!(parsed_input_map, input_map);
252        }
253
254        #[test]
255        fn signed_integer_serialization_roundtrip((typ, value) in arb_signed_integer_type_and_value()) {
256            let string_input = JsonTypes::String(value.to_string());
257            let input_value = InputValue::try_from_json(string_input, &typ, "foo").expect("should be parsable");
258            let JsonTypes::String(output_string) = JsonTypes::try_from_input_value(&input_value, &typ).expect("should be serializable") else {
259                panic!("wrong type output");
260            };
261            let output_number = if let Some(output_string) = output_string.strip_prefix("-0x") {
262                -i64::from_str_radix(output_string, 16).unwrap()
263            } else {
264                i64::from_str_radix(output_string.strip_prefix("0x").unwrap(), 16).unwrap()
265            };
266            prop_assert_eq!(output_number, value);
267        }
268    }
269
270    #[test]
271    fn errors_on_integer_to_signed_integer_overflow() {
272        let typ = AbiType::Integer { sign: crate::Sign::Signed, width: 8 };
273        let input = JsonTypes::Integer(128);
274        assert!(InputValue::try_from_json(input, &typ, "foo").is_err());
275
276        let typ = AbiType::Integer { sign: crate::Sign::Signed, width: 16 };
277        let input = JsonTypes::Integer(32768);
278        assert!(InputValue::try_from_json(input, &typ, "foo").is_err());
279    }
280
281    #[test]
282    fn try_from_json_negative_integer() {
283        let typ = AbiType::Integer { sign: crate::Sign::Signed, width: 8 };
284        let input = JsonTypes::Integer(-1);
285        let InputValue::Field(field) = InputValue::try_from_json(input, &typ, "foo").unwrap()
286        else {
287            panic!("Expected field");
288        };
289        assert_eq!(field, FieldElement::from(255_u128));
290    }
291}