Skip to main content

snarkvm_console_program/data/dynamic/record/
parse.rs

1// Copyright (c) 2019-2026 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 DynamicRecord<N> {
19    /// Parses a string as a dynamic record: `{ owner: address, _root: field, _nonce: group, _version: u8 }`.
20    #[inline]
21    fn parse(string: &str) -> ParserResult<Self> {
22        // Parse the whitespace and comments from the string.
23        let (string, _) = Sanitizer::parse(string)?;
24        // Parse the "{" from the string.
25        let (string, _) = tag("{")(string)?;
26
27        // Parse the whitespace and comments from the string.
28        let (string, _) = Sanitizer::parse(string)?;
29        // Parse the "owner" tag from the string.
30        let (string, _) = tag("owner")(string)?;
31        // Parse the whitespace from the string.
32        let (string, _) = Sanitizer::parse_whitespaces(string)?;
33        // Parse the ":" from the string.
34        let (string, _) = tag(":")(string)?;
35        // Parse the whitespace and comments from the string.
36        let (string, _) = Sanitizer::parse(string)?;
37        // Parse the owner from the string.
38        let (string, owner) = Address::parse(string)?;
39        // Parse the "," from the string.
40        let (string, _) = tag(",")(string)?;
41
42        // Parse the whitespace and comments from the string.
43        let (string, _) = Sanitizer::parse(string)?;
44        // Parse the "_root" tag from the string.
45        let (string, _) = tag("_root")(string)?;
46        // Parse the whitespace from the string.
47        let (string, _) = Sanitizer::parse_whitespaces(string)?;
48        // Parse the ":" from the string.
49        let (string, _) = tag(":")(string)?;
50        // Parse the whitespace and comments from the string.
51        let (string, _) = Sanitizer::parse(string)?;
52        // Parse the root from the string.
53        let (string, root) = Field::parse(string)?;
54        // Parse the "," from the string.
55        let (string, _) = tag(",")(string)?;
56
57        // Parse the whitespace and comments from the string.
58        let (string, _) = Sanitizer::parse(string)?;
59        // Parse the "_nonce" tag from the string.
60        let (string, _) = tag("_nonce")(string)?;
61        // Parse the whitespace from the string.
62        let (string, _) = Sanitizer::parse_whitespaces(string)?;
63        // Parse the ":" from the string.
64        let (string, _) = tag(":")(string)?;
65        // Parse the whitespace and comments from the string.
66        let (string, _) = Sanitizer::parse(string)?;
67        // Parse the nonce from the string.
68        let (string, nonce) = Group::parse(string)?;
69
70        // There may be an optional "_version" tag. Consume the "," separator if it exists.
71        let string = match opt(tag(","))(string)? {
72            // The "," was consumed; continue with the advanced string.
73            (string, Some(_)) => string,
74            // No "," found; keep the string as is.
75            (string, None) => string,
76        };
77
78        // Parse the whitespace and comments from the string.
79        let (string, _) = Sanitizer::parse(string)?;
80        // Parse the optional "_version" tag from the string.
81        let (string, version) = match opt(tag("_version"))(string)? {
82            // If there is no version, then set the version to zero.
83            (string, None) => (string, U8::zero()),
84            // If there is a version, then parse the version from the string.
85            (string, Some(_)) => {
86                // Parse the whitespace from the string.
87                let (string, _) = Sanitizer::parse_whitespaces(string)?;
88                // Parse the ":" from the string.
89                let (string, _) = tag(":")(string)?;
90                // Parse the whitespace and comments from the string.
91                let (string, _) = Sanitizer::parse(string)?;
92                // Parse the version from the string.
93                U8::parse(string)?
94            }
95        };
96
97        // Parse the whitespace and comments from the string.
98        let (string, _) = Sanitizer::parse(string)?;
99        // Parse the '}' from the string.
100        let (string, _) = tag("}")(string)?;
101        // Output the dynamic record.
102        Ok((string, DynamicRecord::new_unchecked(owner, root, nonce, version, None)))
103    }
104}
105
106impl<N: Network> FromStr for DynamicRecord<N> {
107    type Err = Error;
108
109    /// Returns a dynamic record from a string literal.
110    fn from_str(string: &str) -> Result<Self> {
111        match Self::parse(string) {
112            Ok((remainder, object)) => {
113                // Ensure the remainder is empty.
114                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
115                // Return the object.
116                Ok(object)
117            }
118            Err(error) => bail!("Failed to parse string. {error}"),
119        }
120    }
121}
122
123impl<N: Network> Debug for DynamicRecord<N> {
124    /// Prints the dynamic record as a string.
125    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
126        Display::fmt(self, f)
127    }
128}
129
130impl<N: Network> Display for DynamicRecord<N> {
131    /// Prints the dynamic record as a string.
132    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
133        self.fmt_internal(f, 0)
134    }
135}
136
137impl<N: Network> DynamicRecord<N> {
138    /// Prints the dynamic record with the given indentation depth.
139    fn fmt_internal(&self, f: &mut Formatter, depth: usize) -> fmt::Result {
140        /// The number of spaces to indent.
141        const INDENT: usize = 2;
142
143        // Print the opening brace.
144        write!(f, "{{")?;
145        // Print the owner with a comma.
146        write!(f, "\n{:indent$}owner: {},", "", self.owner, indent = (depth + 1) * INDENT)?;
147        // Print the root with a comma.
148        write!(f, "\n{:indent$}_root: {},", "", self.root, indent = (depth + 1) * INDENT)?;
149        // Print the nonce with a comma.
150        write!(f, "\n{:indent$}_nonce: {},", "", self.nonce, indent = (depth + 1) * INDENT)?;
151        // Print the version without a comma.
152        write!(f, "\n{:indent$}_version: {}", "", self.version, indent = (depth + 1) * INDENT)?;
153        // Print the closing brace.
154        write!(f, "\n{:indent$}}}", "", indent = depth * INDENT)
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::{Entry, Literal, Owner, Record};
162    use snarkvm_console_network::MainnetV0;
163    use snarkvm_console_types::U64;
164    use snarkvm_utilities::{TestRng, Uniform};
165
166    use core::str::FromStr;
167
168    type CurrentNetwork = MainnetV0;
169
170    #[test]
171    fn test_parse_display_roundtrip() {
172        let rng = &mut TestRng::default();
173
174        // Create a simple record.
175        let data = indexmap::indexmap! {
176            Identifier::from_str("amount").unwrap() => Entry::Private(Plaintext::from(Literal::U64(U64::rand(rng)))),
177        };
178        let owner = Owner::Public(Address::rand(rng));
179        let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_plaintext(
180            owner,
181            data,
182            Group::rand(rng),
183            U8::new(0),
184        )
185        .unwrap();
186
187        // Convert to dynamic record.
188        let expected = DynamicRecord::from_record(&record).unwrap();
189
190        // Convert to string.
191        let expected_string = expected.to_string();
192
193        // Parse the string.
194        let candidate = DynamicRecord::<CurrentNetwork>::from_str(&expected_string).unwrap();
195
196        // Verify the fields match.
197        assert_eq!(expected.owner(), candidate.owner());
198        assert_eq!(expected.root(), candidate.root());
199        assert_eq!(expected.nonce(), candidate.nonce());
200        assert_eq!(expected.version(), candidate.version());
201    }
202
203    #[test]
204    fn test_parse() {
205        // Parse a dynamic record from a string.
206        let string = "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah, _root: 0field, _nonce: 0group, _version: 0u8 }";
207        let (remainder, candidate) = DynamicRecord::<CurrentNetwork>::parse(string).unwrap();
208        assert!(remainder.is_empty());
209        assert_eq!(
210            *candidate.owner(),
211            Address::from_str("aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah").unwrap()
212        );
213        assert_eq!(*candidate.root(), Field::from_u64(0));
214        assert_eq!(*candidate.nonce(), Group::zero());
215        assert_eq!(*candidate.version(), U8::new(0));
216    }
217
218    #[test]
219    fn test_parse_without_version() {
220        // Parse a dynamic record without a version (should default to 0).
221        let string = "{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah, _root: 123field, _nonce: 0group }";
222        let (remainder, candidate) = DynamicRecord::<CurrentNetwork>::parse(string).unwrap();
223        assert!(remainder.is_empty());
224        assert_eq!(*candidate.version(), U8::new(0));
225    }
226
227    #[test]
228    fn test_display() {
229        let rng = &mut TestRng::default();
230
231        // Create a simple record.
232        let owner = Owner::Public(Address::rand(rng));
233        let record = Record::<CurrentNetwork, Plaintext<CurrentNetwork>>::from_plaintext(
234            owner,
235            indexmap::IndexMap::new(),
236            Group::rand(rng),
237            U8::new(1),
238        )
239        .unwrap();
240
241        // Convert to dynamic record.
242        let dynamic = DynamicRecord::from_record(&record).unwrap();
243
244        // Check that the display contains expected fields.
245        let display = dynamic.to_string();
246        assert!(display.contains("owner:"));
247        assert!(display.contains("_root:"));
248        assert!(display.contains("_nonce:"));
249        assert!(display.contains("_version:"));
250    }
251}