snarkvm_console_program/data/record/
parse_plaintext.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Parser for Record<N, Plaintext<N>> {
19    /// Parses a string as a record: `{ owner: address, identifier_0: entry_0, ..., identifier_n: entry_n, _nonce: field, _version: u8 }`.
20    #[inline]
21    fn parse(string: &str) -> ParserResult<Self> {
22        /// Parses a sanitized pair: `identifier: entry`.
23        #[allow(clippy::type_complexity)]
24        fn parse_pair<N: Network>(string: &str) -> ParserResult<(Identifier<N>, Entry<N, Plaintext<N>>)> {
25            // Parse the whitespace and comments from the string.
26            let (string, _) = Sanitizer::parse(string)?;
27            // Parse the identifier from the string.
28            let (string, identifier) = Identifier::parse(string)?;
29            // Parse the whitespace from the string.
30            let (string, _) = Sanitizer::parse_whitespaces(string)?;
31            // Parse the ":" from the string.
32            let (string, _) = tag(":")(string)?;
33            // Parse the whitespace and comments from the string.
34            let (string, _) = Sanitizer::parse(string)?;
35            // Parse the entry from the string.
36            let (string, entry) = Entry::parse(string)?;
37            // Parse the whitespace from the string.
38            let (string, _) = Sanitizer::parse_whitespaces(string)?;
39            // Return the identifier and entry.
40            Ok((string, (identifier, entry)))
41        }
42
43        // Parse the whitespace and comments from the string.
44        let (string, _) = Sanitizer::parse(string)?;
45        // Parse the "{" from the string.
46        let (string, _) = tag("{")(string)?;
47
48        // Parse the whitespace and comments from the string.
49        let (string, _) = Sanitizer::parse(string)?;
50        // Parse the "owner" tag from the string.
51        let (string, _) = tag("owner")(string)?;
52        // Parse the whitespace from the string.
53        let (string, _) = Sanitizer::parse_whitespaces(string)?;
54        // Parse the ":" from the string.
55        let (string, _) = tag(":")(string)?;
56        // Parse the whitespace and comments from the string.
57        let (string, _) = Sanitizer::parse(string)?;
58        // Parse the owner from the string.
59        let (string, owner) = alt((
60            map(pair(Address::parse, tag(".public")), |(owner, _)| Owner::Public(owner)),
61            map(pair(Address::parse, tag(".private")), |(owner, _)| {
62                Owner::Private(Plaintext::from(Literal::Address(owner)))
63            }),
64        ))(string)?;
65        // Parse the "," from the string.
66        let (string, _) = tag(",")(string)?;
67
68        // Parse the entries.
69        let (string, entries) = map_res(separated_list0(tag(","), parse_pair), |entries: Vec<_>| {
70            // Prepare the reserved entry names.
71            let reserved = [Identifier::from_str("owner").map_err(|e| error(e.to_string()))?];
72            // Ensure the entries has no duplicate names.
73            if has_duplicates(entries.iter().map(|(identifier, _)| identifier).chain(reserved.iter())) {
74                return Err(error("Duplicate entry type found in record"));
75            }
76            // Ensure the number of entries is within the maximum limit.
77            match entries.len() <= N::MAX_DATA_ENTRIES {
78                true => Ok(entries),
79                false => Err(error(format!("Found a record that exceeds size ({})", entries.len()))),
80            }
81        })(string)?;
82
83        // If there are entries, then parse the "," from the string.
84        let string = match !entries.is_empty() {
85            // Parse the "," from the string.
86            true => tag(",")(string)?.0,
87            false => string,
88        };
89
90        // Parse the whitespace and comments from the string.
91        let (string, _) = Sanitizer::parse(string)?;
92        // Parse the "_nonce" tag from the string.
93        let (string, _) = tag("_nonce")(string)?;
94        // Parse the ":" from the string.
95        let (string, _) = tag(":")(string)?;
96        // Parse the whitespace and comments from the string.
97        let (string, _) = Sanitizer::parse(string)?;
98        // Parse the nonce from the string.
99        let (string, (nonce, _)) = pair(Group::parse, tag(".public"))(string)?;
100
101        // There may be an optional "_version" tag. Parse the "," from the string if it exists.
102        let string = match opt(tag(","))(string)? {
103            // If there is a version, then parse the "," from the string.
104            (string, Some(_)) => string,
105            // If there is no version, then keep the string as is.
106            (string, None) => string,
107        };
108
109        // Parse the whitespace and comments from the string.
110        let (string, _) = Sanitizer::parse(string)?;
111        // Parse the optional "_version" tag from the string.
112        let (string, version) = match opt(tag("_version"))(string)? {
113            // If there is no version, then set the version to zero.
114            (string, None) => (string, U8::zero()),
115            // If there is a version, then parse the version from the string.
116            (string, Some(_)) => {
117                // Parse the whitespace and comments from the string.
118                let (string, _) = Sanitizer::parse(string)?;
119                // Parse the ":" from the string.
120                let (string, _) = tag(":")(string)?;
121                // Parse the whitespace and comments from the string.
122                let (string, _) = Sanitizer::parse(string)?;
123                // Parse the version from the string.
124                terminated(U8::parse, tag(".public"))(string)?
125            }
126        };
127
128        // Parse the whitespace and comments from the string.
129        let (string, _) = Sanitizer::parse(string)?;
130        // Parse the '}' from the string.
131        let (string, _) = tag("}")(string)?;
132        // Output the record.
133        Ok((string, Record { owner, data: IndexMap::from_iter(entries), nonce, version }))
134    }
135}
136
137impl<N: Network> FromStr for Record<N, Plaintext<N>> {
138    type Err = Error;
139
140    /// Returns a record from a string literal.
141    fn from_str(string: &str) -> Result<Self> {
142        match Self::parse(string) {
143            Ok((remainder, object)) => {
144                // Ensure the remainder is empty.
145                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
146                // Return the object.
147                Ok(object)
148            }
149            Err(error) => bail!("Failed to parse string. {error}"),
150        }
151    }
152}
153
154impl<N: Network> Debug for Record<N, Plaintext<N>> {
155    /// Prints the record as a string.
156    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
157        Display::fmt(self, f)
158    }
159}
160
161impl<N: Network> Display for Record<N, Plaintext<N>> {
162    /// Prints the record as a string.
163    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
164        self.fmt_internal(f, 0)
165    }
166}
167
168impl<N: Network> Record<N, Plaintext<N>> {
169    /// Prints the record with the given indentation depth.
170    fn fmt_internal(&self, f: &mut Formatter, depth: usize) -> fmt::Result {
171        /// The number of spaces to indent.
172        const INDENT: usize = 2;
173
174        // Print the opening brace.
175        write!(f, "{{")?;
176        // Print the owner with a comma.
177        write!(f, "\n{:indent$}owner: {},", "", self.owner, indent = (depth + 1) * INDENT)?;
178        // Print the data with a comma.
179        for (identifier, entry) in self.data.iter() {
180            // Print the identifier.
181            write!(f, "\n{:indent$}{identifier}: ", "", indent = (depth + 1) * INDENT)?;
182            // Print the entry.
183            match entry {
184                // If the entry is a literal, print the entry without indentation.
185                Entry::Constant(Plaintext::Literal(..))
186                | Entry::Public(Plaintext::Literal(..))
187                | Entry::Private(Plaintext::Literal(..)) => write!(f, "{entry}")?,
188                // If the entry is a struct or an array, print the entry with indentation.
189                Entry::Constant(Plaintext::Struct(..))
190                | Entry::Public(Plaintext::Struct(..))
191                | Entry::Private(Plaintext::Struct(..))
192                | Entry::Constant(Plaintext::Array(..))
193                | Entry::Public(Plaintext::Array(..))
194                | Entry::Private(Plaintext::Array(..)) => entry.fmt_internal(f, depth + 1)?,
195            }
196            // Print the comma.
197            write!(f, ",")?;
198        }
199        // Print the nonce with a comma.
200        write!(f, "\n{:indent$}_nonce: {}.public,", "", self.nonce, indent = (depth + 1) * INDENT)?;
201        // Print the version without a comma.
202        write!(f, "\n{:indent$}_version: {}.public", "", self.version, indent = (depth + 1) * INDENT)?;
203        // Print the closing brace.
204        write!(f, "\n{:indent$}}}", "", indent = depth * INDENT)
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use snarkvm_console_network::MainnetV0;
212
213    type CurrentNetwork = MainnetV0;
214
215    #[test]
216    fn test_parse_without_version() -> Result<()> {
217        // Sanity check.
218        let expected = r"{
219  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
220  _nonce: 0group.public,
221  _version: 0u8.public
222}";
223        let given =
224            "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, _nonce: 0group.public }";
225        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(given)?;
226        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
227        assert_eq!(expected, candidate.to_string());
228        assert_eq!("", remainder);
229        Ok(())
230    }
231
232    #[test]
233    fn test_parse_with_version() -> Result<()> {
234        // Sanity check.
235        let expected = r"{
236  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
237  _nonce: 0group.public,
238  _version: 0u8.public
239}";
240        let given = "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, _nonce: 0group.public, _version: 0u8.public }";
241        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(given)?;
242        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
243        assert_eq!(expected, candidate.to_string());
244        assert_eq!("", remainder);
245
246        // Sanity check.
247        let expected = r"{
248  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
249  _nonce: 0group.public,
250  _version: 1u8.public
251}";
252        let given = "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, _nonce: 0group.public, _version: 1u8.public }";
253        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(given)?;
254        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
255        assert_eq!(expected, candidate.to_string());
256        assert_eq!("", remainder);
257        Ok(())
258    }
259
260    #[test]
261    fn test_parse_without_data_entries() -> Result<()> {
262        // Sanity check.
263        let expected = r"{
264  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private,
265  _nonce: 0group.public,
266  _version: 0u8.public
267}";
268        let given =
269            "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, _nonce: 0group.public }";
270        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(given)?;
271        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
272        assert_eq!(expected, candidate.to_string());
273        assert_eq!("", remainder);
274        Ok(())
275    }
276
277    #[test]
278    fn test_parse_with_literal_entry() -> Result<()> {
279        let expected = r"{
280  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
281  foo: 5u8.constant,
282  _nonce: 0group.public,
283  _version: 0u8.public
284}";
285        let given = "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public, foo: 5u8.constant, _nonce: 0group.public }";
286        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(given)?;
287        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
288        assert_eq!(expected, candidate.to_string());
289        assert_eq!("", remainder);
290        Ok(())
291    }
292
293    #[test]
294    fn test_parse_with_struct_entry() -> Result<()> {
295        let expected = r"{
296  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
297  foo: 5u8.public,
298  bar: {
299    baz: 6u8.constant,
300    qux: 7u8.constant
301  },
302  quux: 8u8.private,
303  corge: {
304    grault: 9u8.constant,
305    garply: {
306      waldo: 10u8.constant,
307      fred: 11u8.constant
308    }
309  },
310  xyzzy: {
311    thud: 12u8.public
312  },
313  _nonce: 2293253577170800572742339369209137467208538700597121244293392265726446806023group.public,
314  _version: 0u8.public
315}";
316        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(expected)?;
317        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
318        assert_eq!(expected, candidate.to_string());
319        assert_eq!("", remainder);
320
321        let expected = r"{
322  owner: aleo1lhcpfumagern97esytsgdva2ytme043zydlzyprhejsd0gw5vypqqz0zkw.private,
323  foo: {
324    bar: 0u128.private
325  },
326  baz: {
327    quine: {
328      flop: 0u64.private
329    },
330    slice: 0u16.private,
331    flag: true.private,
332    square: {
333      first: 0u128.private,
334      second: 1u128.private,
335      third: 2u128.private,
336      fourth: 3u128.private
337    }
338  },
339  _nonce: 0group.public,
340  _version: 0u8.public
341}";
342        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(expected)?;
343        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
344        assert_eq!(expected, candidate.to_string());
345        assert_eq!("", remainder);
346
347        let expected = r"{
348  owner: aleo18ttcegpydcs95yw4je0u400j3u7r26yqr9h8evqps3qa9slrvyrsqjwt9l.private,
349  c: {
350    c: {
351      a: 0u8.private,
352      b: 1u8.private
353    },
354    d: {
355      a: 0u8.private,
356      b: 1u8.private
357    }
358  },
359  d: {
360    c: {
361      a: 0u8.private,
362      b: 1u8.private
363    },
364    d: {
365      a: 0u8.private,
366      b: 1u8.private
367    }
368  },
369  _nonce: 8102307625287186026775464343238779600702564007094834161216556016558567413871group.public,
370  _version: 0u8.public
371}";
372        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(expected)?;
373        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
374        assert_eq!(expected, candidate.to_string());
375        assert_eq!("", remainder);
376
377        Ok(())
378    }
379
380    #[test]
381    fn test_parse_with_array_entry() -> Result<()> {
382        let expected = r"{
383  owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public,
384  foo: 5u8.public,
385  bar: [
386    6u8.private,
387    7u8.private,
388    8u8.private
389  ],
390  _nonce: 0group.public,
391  _version: 0u8.public
392}";
393        let (remainder, candidate) = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::parse(expected)?;
394        println!("\nExpected: {expected}\n\nFound: {candidate}\n");
395        assert_eq!(expected, candidate.to_string());
396        assert_eq!("", remainder);
397        Ok(())
398    }
399
400    #[test]
401    fn test_parse_fails() -> Result<()> {
402        // Missing owner.
403        let expected = "{ foo: 5u8.private, _nonce: 0group.public }";
404        assert!(Plaintext::<CurrentNetwork>::parse(expected).is_err());
405
406        // Missing nonce.
407        let expected =
408            "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.public, foo: 5u8.private }";
409        assert!(Plaintext::<CurrentNetwork>::parse(expected).is_err());
410
411        // Entry 'd' contains members with different visibility.
412        let expected = r"{
413    owner: aleo14tlamssdmg3d0p5zmljma573jghe2q9n6wz29qf36re2glcedcpqfg4add.private,
414    a: true.private,
415    b: 123456789field.private,
416    c: 0group.private,
417    d: {
418        e: true.private,
419        f: 123456789field.public,
420        g: 0group.private
421    },
422    _nonce: 0group.public,
423    _version: 0u8.public
424}";
425        assert!(Plaintext::<CurrentNetwork>::parse(expected).is_err());
426        Ok(())
427    }
428}