snarkvm_synthesizer_process/stack/helpers/
matches.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> StackMatches<N> for Stack<N> {
19    /// Checks that the given value matches the layout of the value type.
20    fn matches_value_type(&self, value: &Value<N>, value_type: &ValueType<N>) -> Result<()> {
21        // Ensure the value matches the declared value type in the register.
22        match (value, value_type) {
23            (Value::Plaintext(plaintext), ValueType::Constant(plaintext_type))
24            | (Value::Plaintext(plaintext), ValueType::Public(plaintext_type))
25            | (Value::Plaintext(plaintext), ValueType::Private(plaintext_type)) => {
26                self.matches_plaintext(plaintext, plaintext_type)
27            }
28            (Value::Record(record), ValueType::Record(record_name)) => self.matches_record(record, record_name),
29            (Value::Record(record), ValueType::ExternalRecord(locator)) => {
30                self.matches_external_record(record, locator)
31            }
32            (Value::Future(future), ValueType::Future(locator)) => self.matches_future(future, locator),
33            _ => bail!("A value does not match its declared value type '{value_type}'"),
34        }
35    }
36
37    /// Checks that the given stack value matches the layout of the register type.
38    fn matches_register_type(&self, stack_value: &Value<N>, register_type: &RegisterType<N>) -> Result<()> {
39        match (stack_value, register_type) {
40            (Value::Plaintext(plaintext), RegisterType::Plaintext(plaintext_type)) => {
41                self.matches_plaintext(plaintext, plaintext_type)
42            }
43            (Value::Record(record), RegisterType::Record(record_name)) => self.matches_record(record, record_name),
44            (Value::Record(record), RegisterType::ExternalRecord(locator)) => {
45                self.matches_external_record(record, locator)
46            }
47            (Value::Future(future), RegisterType::Future(locator)) => self.matches_future(future, locator),
48            _ => bail!("A value does not match its declared register type '{register_type}'"),
49        }
50    }
51
52    /// Checks that the given record matches the layout of the external record type.
53    fn matches_external_record(&self, record: &Record<N, Plaintext<N>>, locator: &Locator<N>) -> Result<()> {
54        // Retrieve the record name.
55        let record_name = locator.resource();
56
57        // Ensure the record name is valid.
58        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
59
60        // Retrieve the external stack.
61        let external_stack = self.get_external_stack(locator.program_id())?;
62        // Retrieve the record type from the program.
63        let Ok(record_type) = external_stack.program().get_record(locator.resource()) else {
64            bail!("External '{locator}' is not defined in the program")
65        };
66
67        // Ensure the record name matches.
68        if record_type.name() != record_name {
69            bail!("Expected external record '{record_name}', found external record '{}'", record_type.name())
70        }
71
72        self.matches_record_internal(record, record_type, 0)
73    }
74
75    /// Checks that the given record matches the layout of the record type.
76    fn matches_record(&self, record: &Record<N, Plaintext<N>>, record_name: &Identifier<N>) -> Result<()> {
77        // Ensure the record name is valid.
78        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
79
80        // Retrieve the record type from the program.
81        let Ok(record_type) = self.program().get_record(record_name) else {
82            bail!("Record '{record_name}' is not defined in the program")
83        };
84
85        // Ensure the record name matches.
86        if record_type.name() != record_name {
87            bail!("Expected record '{record_name}', found record '{}'", record_type.name())
88        }
89
90        self.matches_record_internal(record, record_type, 0)
91    }
92
93    /// Checks that the given plaintext matches the layout of the plaintext type.
94    fn matches_plaintext(&self, plaintext: &Plaintext<N>, plaintext_type: &PlaintextType<N>) -> Result<()> {
95        self.matches_plaintext_internal(plaintext, plaintext_type, 0)
96    }
97
98    /// Checks that the given future matches the layout of the future type.
99    fn matches_future(&self, future: &Future<N>, locator: &Locator<N>) -> Result<()> {
100        self.matches_future_internal(future, locator, 0)
101    }
102}
103
104impl<N: Network> Stack<N> {
105    /// Checks that the given record matches the layout of the record type.
106    fn matches_record_internal(
107        &self,
108        record: &Record<N, Plaintext<N>>,
109        record_type: &RecordType<N>,
110        depth: usize,
111    ) -> Result<()> {
112        // If the depth exceeds the maximum depth, then the plaintext type is invalid.
113        ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
114
115        // Retrieve the record name.
116        let record_name = record_type.name();
117        // Ensure the record name is valid.
118        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
119
120        // Ensure the visibility of the record owner matches the visibility in the record type.
121        ensure!(
122            record.owner().is_public() == record_type.owner().is_public(),
123            "Visibility of record entry 'owner' does not match"
124        );
125        ensure!(
126            record.owner().is_private() == record_type.owner().is_private(),
127            "Visibility of record entry 'owner' does not match"
128        );
129
130        // Ensure the number of record entries does not exceed the maximum.
131        let num_entries = record.data().len();
132        ensure!(num_entries <= N::MAX_DATA_ENTRIES, "'{record_name}' cannot exceed {} entries", N::MAX_DATA_ENTRIES);
133
134        // Ensure the number of record entries match.
135        let expected_num_entries = record_type.entries().len();
136        if expected_num_entries != num_entries {
137            bail!("'{record_name}' expected {expected_num_entries} entries, found {num_entries} entries")
138        }
139
140        // Ensure the record data match, in the same order.
141        for (i, ((expected_name, expected_type), (entry_name, entry))) in
142            record_type.entries().iter().zip_eq(record.data().iter()).enumerate()
143        {
144            // Ensure the entry name matches.
145            if expected_name != entry_name {
146                bail!("Entry '{i}' in '{record_name}' is incorrect: expected '{expected_name}', found '{entry_name}'")
147            }
148            // Ensure the entry name is valid.
149            ensure!(!Program::is_reserved_keyword(entry_name), "Entry name '{entry_name}' is reserved");
150            // Ensure the entry matches (recursive call).
151            self.matches_entry_internal(record_name, entry_name, entry, expected_type, depth + 1)?;
152        }
153
154        Ok(())
155    }
156
157    /// Checks that the given entry matches the layout of the entry type.
158    fn matches_entry_internal(
159        &self,
160        record_name: &Identifier<N>,
161        entry_name: &Identifier<N>,
162        entry: &Entry<N, Plaintext<N>>,
163        entry_type: &EntryType<N>,
164        depth: usize,
165    ) -> Result<()> {
166        match (entry, entry_type) {
167            (Entry::Constant(plaintext), EntryType::Constant(plaintext_type))
168            | (Entry::Public(plaintext), EntryType::Public(plaintext_type))
169            | (Entry::Private(plaintext), EntryType::Private(plaintext_type)) => {
170                match self.matches_plaintext_internal(plaintext, plaintext_type, depth) {
171                    Ok(()) => Ok(()),
172                    Err(error) => bail!("Invalid record entry '{record_name}.{entry_name}': {error}"),
173                }
174            }
175            _ => bail!(
176                "Type mismatch in record entry '{record_name}.{entry_name}':\n'{entry}'\n does not match\n'{entry_type}'"
177            ),
178        }
179    }
180
181    /// Checks that the given plaintext matches the layout of the plaintext type.
182    fn matches_plaintext_internal(
183        &self,
184        plaintext: &Plaintext<N>,
185        plaintext_type: &PlaintextType<N>,
186        depth: usize,
187    ) -> Result<()> {
188        // If the depth exceeds the maximum depth, then the plaintext type is invalid.
189        ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
190
191        // Ensure the plaintext matches the plaintext definition in the program.
192        match plaintext_type {
193            PlaintextType::Literal(literal_type) => match plaintext {
194                // If `plaintext` is a literal, it must match the literal type.
195                Plaintext::Literal(literal, ..) => {
196                    // Ensure the literal type matches.
197                    match literal.to_type() == *literal_type {
198                        true => Ok(()),
199                        false => bail!("'{plaintext_type}' is invalid: expected {literal_type}, found {literal}"),
200                    }
201                }
202                // If `plaintext` is a struct, this is a mismatch.
203                Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected literal, found struct"),
204                // If `plaintext` is an array, this is a mismatch.
205                Plaintext::Array(..) => bail!("'{plaintext_type}' is invalid: expected literal, found array"),
206            },
207            PlaintextType::Struct(struct_name) => {
208                // Ensure the struct name is valid.
209                ensure!(!Program::is_reserved_keyword(struct_name), "Struct '{struct_name}' is reserved");
210
211                // Retrieve the struct from the program.
212                let Ok(struct_) = self.program().get_struct(struct_name) else {
213                    bail!("Struct '{struct_name}' is not defined in the program")
214                };
215
216                // Ensure the struct name matches.
217                if struct_.name() != struct_name {
218                    bail!("Expected struct '{struct_name}', found struct '{}'", struct_.name())
219                }
220
221                // Retrieve the struct members.
222                let members = match plaintext {
223                    Plaintext::Literal(..) => bail!("'{struct_name}' is invalid: expected struct, found literal"),
224                    Plaintext::Struct(members, ..) => members,
225                    Plaintext::Array(..) => bail!("'{struct_name}' is invalid: expected struct, found array"),
226                };
227
228                let num_members = members.len();
229                // Ensure the number of struct members does not go below the minimum.
230                ensure!(
231                    num_members >= N::MIN_STRUCT_ENTRIES,
232                    "'{struct_name}' cannot be less than {} entries",
233                    N::MIN_STRUCT_ENTRIES
234                );
235                // Ensure the number of struct members does not exceed the maximum.
236                ensure!(
237                    num_members <= N::MAX_STRUCT_ENTRIES,
238                    "'{struct_name}' cannot exceed {} entries",
239                    N::MAX_STRUCT_ENTRIES
240                );
241
242                // Ensure the number of struct members match.
243                let expected_num_members = struct_.members().len();
244                if expected_num_members != num_members {
245                    bail!("'{struct_name}' expected {expected_num_members} members, found {num_members} members")
246                }
247
248                // Ensure the struct members match, in the same order.
249                for (i, ((expected_name, expected_type), (member_name, member))) in
250                    struct_.members().iter().zip_eq(members.iter()).enumerate()
251                {
252                    // Ensure the member name matches.
253                    if expected_name != member_name {
254                        bail!(
255                            "Member '{i}' in '{struct_name}' is incorrect: expected '{expected_name}', found '{member_name}'"
256                        )
257                    }
258                    // Ensure the member name is valid.
259                    ensure!(!Program::is_reserved_keyword(member_name), "Member name '{member_name}' is reserved");
260                    // Ensure the member plaintext matches (recursive call).
261                    self.matches_plaintext_internal(member, expected_type, depth + 1)?;
262                }
263
264                Ok(())
265            }
266            PlaintextType::Array(array_type) => match plaintext {
267                // If `plaintext` is a literal, this is a mismatch.
268                Plaintext::Literal(..) => bail!("'{plaintext_type}' is invalid: expected array, found literal"),
269                // If `plaintext` is a struct, this is a mismatch.
270                Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected array, found struct"),
271                // If `plaintext` is an array, it must match the array type.
272                Plaintext::Array(array, ..) => {
273                    // Ensure the array length matches.
274                    let (actual_length, expected_length) = (array.len(), array_type.length());
275                    if **expected_length as usize != actual_length {
276                        bail!(
277                            "'{plaintext_type}' is invalid: expected {expected_length} elements, found {actual_length} elements"
278                        )
279                    }
280                    // Ensure the array elements match.
281                    for element in array.iter() {
282                        self.matches_plaintext_internal(element, array_type.next_element_type(), depth + 1)?;
283                    }
284                    Ok(())
285                }
286            },
287        }
288    }
289
290    /// Checks that the given future matches the layout of the future type.
291    fn matches_future_internal(&self, future: &Future<N>, locator: &Locator<N>, depth: usize) -> Result<()> {
292        // If the depth exceeds the maximum depth, then the future type is invalid.
293        ensure!(depth <= N::MAX_DATA_DEPTH, "Future exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
294
295        // Ensure that the program IDs match.
296        ensure!(future.program_id() == locator.program_id(), "Future program ID does not match");
297
298        // Ensure that the function names match.
299        ensure!(future.function_name() == locator.resource(), "Future name does not match");
300
301        // Retrieve the external stack, if needed.
302        let external_stack = match locator.program_id() == self.program_id() {
303            true => None,
304            // Attention - This method must fail here and early return if the external program is missing.
305            // Otherwise, this method will proceed to look for the requested function in its own program.
306            false => Some(self.get_external_stack(locator.program_id())?),
307        };
308        // Retrieve the associated function.
309        let function = match &external_stack {
310            Some(external_stack) => external_stack.get_function_ref(locator.resource())?,
311            None => self.get_function_ref(locator.resource())?,
312        };
313        // Retrieve the finalize inputs.
314        let inputs = match function.finalize_logic() {
315            Some(finalize_logic) => finalize_logic.inputs(),
316            None => bail!("Function '{locator}' does not have a finalize block"),
317        };
318
319        // Ensure the number of arguments matches the number of inputs.
320        ensure!(future.arguments().len() == inputs.len(), "Future arguments do not match");
321
322        // Check that the arguments match the inputs.
323        for (argument, input) in future.arguments().iter().zip_eq(inputs.iter()) {
324            match (argument, input.finalize_type()) {
325                (Argument::Plaintext(plaintext), FinalizeType::Plaintext(plaintext_type)) => {
326                    self.matches_plaintext_internal(plaintext, plaintext_type, depth + 1)?
327                }
328                (Argument::Future(future), FinalizeType::Future(locator)) => {
329                    self.matches_future_internal(future, locator, depth + 1)?
330                }
331                (_, input_type) => {
332                    bail!("Argument type does not match input type: expected '{input_type}'")
333                }
334            }
335        }
336
337        Ok(())
338    }
339}