Skip to main content

snarkvm_synthesizer_process/stack/helpers/
stack_trait.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> StackTrait<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            (Value::DynamicRecord(_), ValueType::DynamicRecord) => Ok(()),
34            (Value::DynamicFuture(_), ValueType::DynamicFuture) => Ok(()),
35            (value, _) => bail!("A value '{value}' does not match its declared value type '{value_type}'"),
36        }
37    }
38
39    /// Checks that the given stack value matches the layout of the register type.
40    fn matches_register_type(&self, stack_value: &Value<N>, register_type: &RegisterType<N>) -> Result<()> {
41        match (stack_value, register_type) {
42            (Value::Plaintext(plaintext), RegisterType::Plaintext(plaintext_type)) => {
43                self.matches_plaintext(plaintext, plaintext_type)
44            }
45            (Value::Record(record), RegisterType::Record(record_name)) => self.matches_record(record, record_name),
46            (Value::Record(record), RegisterType::ExternalRecord(locator)) => {
47                self.matches_external_record(record, locator)
48            }
49            (Value::Future(future), RegisterType::Future(locator)) => self.matches_future(future, locator),
50            (Value::DynamicRecord(_), RegisterType::DynamicRecord) => Ok(()),
51            (Value::DynamicFuture(_), RegisterType::DynamicFuture) => Ok(()),
52            (value, _) => bail!("A value '{value}' does not match its declared register type '{register_type}'"),
53        }
54    }
55
56    /// Checks that the given record matches the layout of the external record type.
57    fn matches_external_record(&self, record: &Record<N, Plaintext<N>>, locator: &Locator<N>) -> Result<()> {
58        // Retrieve the record name.
59        let record_name = locator.resource();
60
61        // Ensure the record name is valid.
62        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
63
64        // Retrieve the external stack.
65        let external_stack = self.get_external_stack(locator.program_id())?;
66        // Retrieve the record type from the program.
67        let Ok(record_type) = external_stack.program().get_record(locator.resource()) else {
68            bail!("External '{locator}' is not defined in the program")
69        };
70
71        // Ensure the record name matches.
72        if record_type.name() != record_name {
73            bail!("Expected external record '{record_name}', found external record '{}'", record_type.name())
74        }
75
76        external_stack.matches_record_internal(record, record_type, 0)
77    }
78
79    /// Checks that the given record matches the layout of the record type.
80    fn matches_record(&self, record: &Record<N, Plaintext<N>>, record_name: &Identifier<N>) -> Result<()> {
81        // Ensure the record name is valid.
82        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
83
84        // Retrieve the record type from the program.
85        let Ok(record_type) = self.program().get_record(record_name) else {
86            bail!("Record '{record_name}' is not defined in the program")
87        };
88
89        // Ensure the record name matches.
90        if record_type.name() != record_name {
91            bail!("Expected record '{record_name}', found record '{}'", record_type.name())
92        }
93
94        self.matches_record_internal(record, record_type, 0)
95    }
96
97    /// Checks that the given plaintext matches the layout of the plaintext type.
98    fn matches_plaintext(&self, plaintext: &Plaintext<N>, plaintext_type: &PlaintextType<N>) -> Result<()> {
99        self.matches_plaintext_internal(plaintext, plaintext_type, 0)
100    }
101
102    /// Checks that the given future matches the layout of the future type.
103    fn matches_future(&self, future: &Future<N>, locator: &Locator<N>) -> Result<()> {
104        self.matches_future_internal(future, locator, 0)
105    }
106
107    /// Returns `true` if the proving key for the given name exists.
108    /// The name can be a function name or a record name (for translation keys).
109    fn contains_proving_key(&self, function_or_record_name: &Identifier<N>) -> bool {
110        self.proving_keys.read().contains_key(function_or_record_name)
111    }
112
113    /// Returns the proving key for the given name.
114    /// The name can be a function name or a record name (for translation keys).
115    fn get_proving_key(&self, function_or_record_name: &Identifier<N>) -> Result<ProvingKey<N>> {
116        // If the program is 'credits.aleo', try to load the proving key, if it does not exist.
117        self.try_insert_credits_function_proving_key(function_or_record_name)?;
118        // Return the proving key, if it exists.
119        match self.proving_keys.read().get(function_or_record_name) {
120            Some(pk) => Ok(pk.clone()),
121            None => bail!("Proving key not found for: {}/{}", self.program.id(), function_or_record_name),
122        }
123    }
124
125    /// Inserts the given proving key for the given name.
126    /// The name can be a function name or a record name (for translation keys).
127    fn insert_proving_key(&self, function_or_record_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()> {
128        // Ensure the name exists in the program as a function or record.
129        ensure!(
130            self.program.contains_function(function_or_record_name)
131                || self.program.contains_record(function_or_record_name),
132            "'{function_or_record_name}' does not exist as a function or record in program '{}'.",
133            self.program.id()
134        );
135        // Insert the proving key.
136        self.proving_keys.write().insert(*function_or_record_name, proving_key);
137        Ok(())
138    }
139
140    /// Removes the proving key for the given name.
141    /// The name can be a function name or a record name (for translation keys).
142    fn remove_proving_key(&self, function_or_record_name: &Identifier<N>) {
143        self.proving_keys.write().shift_remove(function_or_record_name);
144    }
145
146    /// Returns `true` if the verifying key for the given name exists.
147    /// The name can be a function name or a record name (for translation keys).
148    fn contains_verifying_key(&self, function_or_record_name: &Identifier<N>) -> bool {
149        self.verifying_keys.read().contains_key(function_or_record_name)
150    }
151
152    /// Returns the verifying key for the given name.
153    /// The name can be a function name or a record name (for translation keys).
154    fn get_verifying_key(&self, function_or_record_name: &Identifier<N>) -> Result<VerifyingKey<N>> {
155        // Return the verifying key, if it exists.
156        match self.verifying_keys.read().get(function_or_record_name) {
157            Some(vk) => Ok(vk.clone()),
158            None => bail!("Verifying key not found for: {}/{}", self.program.id(), function_or_record_name),
159        }
160    }
161
162    /// Inserts the given verifying key for the given name.
163    /// The name can be a function name or a record name (for translation keys).
164    fn insert_verifying_key(
165        &self,
166        function_or_record_name: &Identifier<N>,
167        verifying_key: VerifyingKey<N>,
168    ) -> Result<()> {
169        // Ensure the name exists in the program as a function or record.
170        ensure!(
171            self.program.contains_function(function_or_record_name)
172                || self.program.contains_record(function_or_record_name),
173            "'{function_or_record_name}' does not exist as a function or record in program '{}'.",
174            self.program.id()
175        );
176        // Insert the verifying key.
177        self.verifying_keys.write().insert(*function_or_record_name, verifying_key);
178        Ok(())
179    }
180
181    /// Removes the verifying key for the given name.
182    /// The name can be a function name or a record name (for translation keys).
183    fn remove_verifying_key(&self, function_or_record_name: &Identifier<N>) {
184        self.verifying_keys.write().shift_remove(function_or_record_name);
185    }
186
187    /// Returns the program.
188    fn program(&self) -> &Program<N> {
189        &self.program
190    }
191
192    /// Returns the program ID.
193    fn program_id(&self) -> &ProgramID<N> {
194        self.program.id()
195    }
196
197    /// Returns the program address.
198    fn program_address(&self) -> &Address<N> {
199        &self.program_address
200    }
201
202    /// Returns the program checksum.
203    fn program_checksum(&self) -> &[U8<N>; 32] {
204        &self.program_checksum
205    }
206
207    /// Returns the program checksum as a field element.
208    #[inline]
209    fn program_checksum_as_field(&self) -> Result<Field<N>> {
210        // Get the bits of the program checksum, truncated to the field size.
211        let bits = self
212            .program_checksum
213            .iter()
214            .flat_map(|byte| byte.to_bits_le())
215            .take(Field::<N>::SIZE_IN_DATA_BITS)
216            .collect::<Vec<_>>();
217        // Return the field element from the bits.
218        Field::from_bits_le(&bits)
219    }
220
221    /// Returns the program edition.
222    #[inline]
223    fn program_edition(&self) -> U16<N> {
224        self.program_edition
225    }
226
227    /// Returns the number of amendments for the current program edition.
228    #[inline]
229    fn program_amendment_count(&self) -> u64 {
230        self.program_amendment_count
231    }
232
233    /// Sets the number of amendments for the current program edition.
234    fn set_program_amendment_count(&mut self, program_amendment_count: u64) {
235        self.program_amendment_count = program_amendment_count;
236    }
237
238    /// Returns the program owner.
239    #[inline]
240    fn program_owner(&self) -> &Option<Address<N>> {
241        &self.program_owner
242    }
243
244    /// Sets the program owner.
245    /// The program owner should only be set for programs that are deployed after `ConsensusVersion::V9` is active.
246    fn set_program_owner(&mut self, program_owner: Option<Address<N>>) {
247        self.program_owner = program_owner;
248    }
249
250    /// Returns the external stack for the given program ID.
251    ///
252    /// Attention - this function is used to check the existence of the external program.
253    /// Developers should explicitly handle the error case so as to not default to the main program.
254    fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<Arc<Stack<N>>> {
255        // Check that the program ID is not itself.
256        ensure!(
257            program_id != self.program.id(),
258            "Attempted to get the main program '{program_id}' as an external program."
259        );
260        // Check that the program ID is imported by the program.
261        ensure!(self.program.contains_import(program_id), "External program '{program_id}' is not imported.");
262        // Upgrade the weak reference to the process-level stack map and retrieve the external stack.
263        self.stacks
264            .upgrade()
265            .ok_or_else(|| anyhow!("Process-level stack map does not exist"))?
266            .read()
267            .get(program_id)
268            .cloned()
269            .ok_or_else(|| anyhow!("External stack for '{program_id}' does not exist"))
270    }
271
272    /// Returns the stack for the given program ID.
273    ///
274    /// Attention - this function does **NOT** check that the program is imported by the current program.
275    /// This function is only to be used for resolution during dynamic dispatch.
276    fn get_stack_global(&self, program_id: &ProgramID<N>) -> Result<Arc<Stack<N>>> {
277        // Upgrade the weak reference to the process-level stack map and retrieve the external stack.
278        self.stacks
279            .upgrade()
280            .ok_or_else(|| anyhow!("Process-level stack map does not exist"))?
281            .read()
282            .get(program_id)
283            .cloned()
284            .ok_or_else(|| anyhow!("External stack for '{program_id}' does not exist"))
285    }
286
287    /// Returns the function with the given function name.
288    fn get_function(&self, function_name: &Identifier<N>) -> Result<Function<N>> {
289        self.program.get_function(function_name)
290    }
291
292    /// Returns a reference to the function with the given function name.
293    fn get_function_ref(&self, function_name: &Identifier<N>) -> Result<&Function<N>> {
294        self.program.get_function_ref(function_name)
295    }
296
297    /// Returns the minimum number of calls for the given function name.
298    /// In the case where there are no dynamic calls, this is equivalent to the total number of calls.
299    fn get_minimum_number_of_calls(&self, function_name: &Identifier<N>) -> Result<usize> {
300        // Initialize the base number of calls.
301        let mut num_calls = 1;
302        // Initialize a queue of functions to check.
303        let mut queue = vec![(StackRef::Internal(self), *function_name)];
304        // Iterate over the queue.
305        while let Some((stack_ref, function_name)) = queue.pop() {
306            // Ensure that the number of calls does not exceed the maximum.
307            // Note that one transition is reserved for the fee.
308            ensure!(
309                num_calls < Transaction::<N>::MAX_TRANSITIONS,
310                "Number of calls must be less than '{}'",
311                Transaction::<N>::MAX_TRANSITIONS
312            );
313            // Determine the number of calls for the function.
314            for instruction in stack_ref.get_function_ref(&function_name)?.instructions() {
315                match instruction {
316                    Instruction::Call(call) => {
317                        // Determine if this is a function call.
318                        if call.is_function_call(&*stack_ref)? {
319                            // Increment the number of calls.
320                            num_calls += 1;
321                            // Add the function to the queue.
322                            match call.operator() {
323                                CallOperator::Locator(locator) => {
324                                    // If the locator matches the program ID of the provided stack, use it directly.
325                                    // Otherwise, retrieve the external stack.
326                                    let stack = if locator.program_id() == self.program().id() {
327                                        StackRef::Internal(self)
328                                    } else {
329                                        StackRef::External(stack_ref.get_external_stack(locator.program_id())?)
330                                    };
331                                    queue.push((stack, *locator.resource()));
332                                }
333                                CallOperator::Resource(resource) => {
334                                    queue.push((stack_ref.clone(), *resource));
335                                }
336                            }
337                        }
338                    }
339                    Instruction::CallDynamic(_) => {
340                        // Increment the number of calls.
341                        num_calls += 1
342                    }
343                    _ => (),
344                }
345            }
346        }
347        // Return the number of calls.
348        Ok(num_calls)
349    }
350
351    /// Returns whether or not a function has a dynamic call in its execution.
352    fn contains_dynamic_call(&self, function_name: &Identifier<N>) -> Result<bool> {
353        // Initialize the base number of calls.
354        let mut num_calls = 1;
355        // Initialize a queue of functions to check.
356        let mut queue = vec![(StackRef::Internal(self), *function_name)];
357        // Iterate over the queue.
358        while let Some((stack_ref, function_name)) = queue.pop() {
359            // Ensure that the number of calls does not exceed the maximum.
360            // Note that one transition is reserved for the fee.
361            ensure!(
362                num_calls < Transaction::<N>::MAX_TRANSITIONS,
363                "Number of calls must be less than '{}'",
364                Transaction::<N>::MAX_TRANSITIONS
365            );
366            // Determine the number of calls for the function.
367            for instruction in stack_ref.get_function_ref(&function_name)?.instructions() {
368                match instruction {
369                    Instruction::Call(call) => {
370                        // Determine if this is a function call.
371                        if call.is_function_call(&*stack_ref)? {
372                            // Increment by the number of calls.
373                            num_calls += 1;
374                            // Add the function to the queue.
375                            match call.operator() {
376                                CallOperator::Locator(locator) => {
377                                    // If the locator matches the program ID of the provided stack, use it directly.
378                                    // Otherwise, retrieve the external stack.
379                                    let stack = if locator.program_id() == self.program().id() {
380                                        StackRef::Internal(self)
381                                    } else {
382                                        StackRef::External(stack_ref.get_external_stack(locator.program_id())?)
383                                    };
384                                    queue.push((stack, *locator.resource()));
385                                }
386                                CallOperator::Resource(resource) => {
387                                    queue.push((stack_ref.clone(), *resource));
388                                }
389                            }
390                        }
391                    }
392                    Instruction::CallDynamic(_) => return Ok(true),
393                    _ => (),
394                }
395            }
396        }
397        // No dynamic calls have been found.
398        Ok(false)
399    }
400
401    /// Returns a value for the given register type.
402    fn sample_value<R: Rng + CryptoRng>(
403        &self,
404        burner_address: &Address<N>,
405        register_type: &RegisterType<N>,
406        rng: &mut R,
407    ) -> Result<Value<N>> {
408        match register_type {
409            RegisterType::Plaintext(plaintext_type) => {
410                Ok(Value::Plaintext(self.sample_plaintext(plaintext_type, rng)?))
411            }
412            RegisterType::Record(record_name) => {
413                Ok(Value::Record(self.sample_record(burner_address, record_name, Group::rand(rng), rng)?))
414            }
415            RegisterType::ExternalRecord(locator) => {
416                // Retrieve the external stack.
417                let stack = self.get_external_stack(locator.program_id())?;
418                // Sample the output.
419                Ok(Value::Record(stack.sample_record(burner_address, locator.resource(), Group::rand(rng), rng)?))
420            }
421            RegisterType::Future(locator) => Ok(Value::Future(self.sample_future(locator, rng)?)),
422            RegisterType::DynamicRecord => Ok(Value::DynamicRecord(self.sample_dynamic_record(rng)?)),
423            RegisterType::DynamicFuture => Ok(Value::DynamicFuture(self.sample_dynamic_future(rng)?)),
424        }
425    }
426
427    /// Returns a record for the given record name, with the given burner address and nonce.
428    fn sample_record<R: Rng + CryptoRng>(
429        &self,
430        burner_address: &Address<N>,
431        record_name: &Identifier<N>,
432        nonce: Group<N>,
433        rng: &mut R,
434    ) -> Result<Record<N, Plaintext<N>>> {
435        // Sample a record.
436        let record = self.sample_record_internal(burner_address, record_name, nonce, 0, rng)?;
437        // Ensure the record matches the value type.
438        self.matches_record(&record, record_name)?;
439        // Return the record.
440        Ok(record)
441    }
442
443    /// Returns a record for the given record name, deriving the nonce from tvk and index.
444    fn sample_record_using_tvk<R: Rng + CryptoRng>(
445        &self,
446        burner_address: &Address<N>,
447        record_name: &Identifier<N>,
448        tvk: Field<N>,
449        index: Field<N>,
450        rng: &mut R,
451    ) -> Result<Record<N, Plaintext<N>>> {
452        // Compute the randomizer.
453        let randomizer = N::hash_to_scalar_psd2(&[tvk, index])?;
454        // Construct the record nonce from that randomizer.
455        let record_nonce = N::g_scalar_multiply(&randomizer);
456        // Sample the record with that nonce.
457        self.sample_record(burner_address, record_name, record_nonce, rng)
458    }
459
460    fn evaluate_view(
461        &self,
462        state: FinalizeGlobalState,
463        store: &dyn FinalizeStoreTrait<N>,
464        view_name: &Identifier<N>,
465        inputs: Vec<Value<N>>,
466    ) -> Result<Vec<Value<N>>> {
467        crate::view::evaluate_view_inner(state, store, self, view_name, inputs)
468    }
469}
470
471impl<N: Network> Stack<N> {
472    /// Checks that the given record matches the layout of the record type.
473    fn matches_record_internal(
474        &self,
475        record: &Record<N, Plaintext<N>>,
476        record_type: &RecordType<N>,
477        depth: usize,
478    ) -> Result<()> {
479        // If the depth exceeds the maximum depth, then the plaintext type is invalid.
480        ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
481
482        // Retrieve the record name.
483        let record_name = record_type.name();
484        // Ensure the record name is valid.
485        ensure!(!Program::is_reserved_keyword(record_name), "Record name '{record_name}' is reserved");
486
487        // Ensure the visibility of the record owner matches the visibility in the record type.
488        ensure!(
489            record.owner().is_public() == record_type.owner().is_public(),
490            "Visibility of record entry 'owner' does not match"
491        );
492        ensure!(
493            record.owner().is_private() == record_type.owner().is_private(),
494            "Visibility of record entry 'owner' does not match"
495        );
496
497        // Ensure the number of record entries does not exceed the maximum.
498        let num_entries = record.data().len();
499        ensure!(num_entries <= N::MAX_DATA_ENTRIES, "'{record_name}' cannot exceed {} entries", N::MAX_DATA_ENTRIES);
500
501        // Ensure the number of record entries match.
502        let expected_num_entries = record_type.entries().len();
503        if expected_num_entries != num_entries {
504            bail!("'{record_name}' expected {expected_num_entries} entries, found {num_entries} entries")
505        }
506
507        // Ensure the record data match, in the same order.
508        for (i, ((expected_name, expected_type), (entry_name, entry))) in
509            record_type.entries().iter().zip_eq(record.data().iter()).enumerate()
510        {
511            // Ensure the entry name matches.
512            if expected_name != entry_name {
513                bail!("Entry '{i}' in '{record_name}' is incorrect: expected '{expected_name}', found '{entry_name}'")
514            }
515            // Ensure the entry name is valid.
516            ensure!(!Program::is_reserved_keyword(entry_name), "Entry name '{entry_name}' is reserved");
517            // Ensure the entry matches (recursive call).
518            self.matches_entry_internal(record_name, entry_name, entry, expected_type, depth + 1)?;
519        }
520
521        Ok(())
522    }
523
524    /// Checks that the given entry matches the layout of the entry type.
525    fn matches_entry_internal(
526        &self,
527        record_name: &Identifier<N>,
528        entry_name: &Identifier<N>,
529        entry: &Entry<N, Plaintext<N>>,
530        entry_type: &EntryType<N>,
531        depth: usize,
532    ) -> Result<()> {
533        match (entry, entry_type) {
534            (Entry::Constant(plaintext), EntryType::Constant(plaintext_type))
535            | (Entry::Public(plaintext), EntryType::Public(plaintext_type))
536            | (Entry::Private(plaintext), EntryType::Private(plaintext_type)) => {
537                match self.matches_plaintext_internal(plaintext, plaintext_type, depth) {
538                    Ok(()) => Ok(()),
539                    Err(error) => bail!("Invalid record entry '{record_name}.{entry_name}': {error}"),
540                }
541            }
542            _ => bail!(
543                "Type mismatch in record entry '{record_name}.{entry_name}':\n'{entry}'\n does not match\n'{entry_type}'"
544            ),
545        }
546    }
547
548    /// Checks that the given plaintext matches the layout of the plaintext type.
549    fn matches_plaintext_internal(
550        &self,
551        plaintext: &Plaintext<N>,
552        plaintext_type: &PlaintextType<N>,
553        depth: usize,
554    ) -> Result<()> {
555        // If the depth exceeds the maximum depth, then the plaintext type is invalid.
556        ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
557
558        // Ensure the plaintext matches the plaintext definition in the program.
559        match plaintext_type {
560            PlaintextType::Literal(literal_type) => match plaintext {
561                // If `plaintext` is a literal, it must match the literal type.
562                Plaintext::Literal(literal, ..) => {
563                    // Ensure the literal type matches.
564                    match literal.to_type() == *literal_type {
565                        true => Ok(()),
566                        false => bail!("'{literal}' is invalid: expected {literal_type}"),
567                    }
568                }
569                // If `plaintext` is a struct, this is a mismatch.
570                Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected literal, found struct"),
571                // If `plaintext` is an array, this is a mismatch.
572                Plaintext::Array(..) => bail!("'{plaintext_type}' is invalid: expected literal, found array"),
573            },
574            PlaintextType::ExternalStruct(locator) => {
575                let external_stack = self.get_external_stack(locator.program_id())?;
576                let new_type = PlaintextType::Struct(*locator.resource());
577                external_stack.matches_plaintext_internal(plaintext, &new_type, depth)
578            }
579            PlaintextType::Struct(struct_name) => {
580                // Ensure the struct name is valid.
581                ensure!(!Program::is_reserved_keyword(struct_name), "Struct '{struct_name}' is reserved");
582
583                // Retrieve the struct from the program.
584                let Ok(struct_) = self.program().get_struct(struct_name) else {
585                    bail!("Struct '{struct_name}' is not defined in the program")
586                };
587
588                // Ensure the struct name matches.
589                if struct_.name() != struct_name {
590                    bail!("Expected struct '{struct_name}', found struct '{}'", struct_.name())
591                }
592
593                // Retrieve the struct members.
594                let members = match plaintext {
595                    Plaintext::Literal(..) => bail!("'{struct_name}' is invalid: expected struct, found literal"),
596                    Plaintext::Struct(members, ..) => members,
597                    Plaintext::Array(..) => bail!("'{struct_name}' is invalid: expected struct, found array"),
598                };
599
600                let num_members = members.len();
601                // Ensure the number of struct members does not go below the minimum.
602                ensure!(
603                    num_members >= N::MIN_STRUCT_ENTRIES,
604                    "'{struct_name}' cannot be less than {} entries",
605                    N::MIN_STRUCT_ENTRIES
606                );
607                // Ensure the number of struct members does not exceed the maximum.
608                ensure!(
609                    num_members <= N::MAX_STRUCT_ENTRIES,
610                    "'{struct_name}' cannot exceed {} entries",
611                    N::MAX_STRUCT_ENTRIES
612                );
613
614                // Ensure the number of struct members match.
615                let expected_num_members = struct_.members().len();
616                if expected_num_members != num_members {
617                    bail!("'{struct_name}' expected {expected_num_members} members, found {num_members} members")
618                }
619
620                // Ensure the struct members match, in the same order.
621                for (i, ((expected_name, expected_type), (member_name, member))) in
622                    struct_.members().iter().zip_eq(members.iter()).enumerate()
623                {
624                    // Ensure the member name matches.
625                    if expected_name != member_name {
626                        bail!(
627                            "Member '{i}' in '{struct_name}' is incorrect: expected '{expected_name}', found '{member_name}'"
628                        )
629                    }
630                    // Ensure the member name is valid.
631                    ensure!(!Program::is_reserved_keyword(member_name), "Member name '{member_name}' is reserved");
632                    // Ensure the member plaintext matches (recursive call).
633                    self.matches_plaintext_internal(member, expected_type, depth + 1)?;
634                }
635
636                Ok(())
637            }
638            PlaintextType::Array(array_type) => match plaintext {
639                // If `plaintext` is a literal, this is a mismatch.
640                Plaintext::Literal(..) => bail!("'{plaintext_type}' is invalid: expected array, found literal"),
641                // If `plaintext` is a struct, this is a mismatch.
642                Plaintext::Struct(..) => bail!("'{plaintext_type}' is invalid: expected array, found struct"),
643                // If `plaintext` is an array, it must match the array type.
644                Plaintext::Array(array, ..) => {
645                    // Ensure the array length matches.
646                    let (actual_length, expected_length) = (array.len(), array_type.length());
647                    if **expected_length as usize != actual_length {
648                        bail!(
649                            "'{plaintext_type}' is invalid: expected {expected_length} elements, found {actual_length} elements"
650                        )
651                    }
652                    // Ensure the array elements match.
653                    for element in array.iter() {
654                        self.matches_plaintext_internal(element, array_type.next_element_type(), depth + 1)?;
655                    }
656                    Ok(())
657                }
658            },
659        }
660    }
661
662    /// Checks that the given future matches the layout of the future type.
663    fn matches_future_internal(&self, future: &Future<N>, locator: &Locator<N>, depth: usize) -> Result<()> {
664        // If the depth exceeds the maximum depth, then the future type is invalid.
665        ensure!(depth <= N::MAX_DATA_DEPTH, "Future exceeded maximum depth of {}", N::MAX_DATA_DEPTH);
666
667        // Ensure that the program IDs match.
668        ensure!(future.program_id() == locator.program_id(), "Future program ID does not match");
669
670        // Ensure that the function names match.
671        ensure!(future.function_name() == locator.resource(), "Future name does not match");
672
673        // Retrieve the external stack, if needed.
674        let external_stack = match locator.program_id() == self.program_id() {
675            true => None,
676            // Attention - This method must fail here and early return if the external program is missing.
677            // Otherwise, this method will proceed to look for the requested function in its own program.
678            false => Some(self.get_external_stack(locator.program_id())?),
679        };
680        // Retrieve the associated function.
681        let function = match &external_stack {
682            Some(external_stack) => external_stack.get_function_ref(locator.resource())?,
683            None => self.get_function_ref(locator.resource())?,
684        };
685        // Retrieve the finalize inputs.
686        let inputs = match function.finalize_logic() {
687            Some(finalize_logic) => finalize_logic.inputs(),
688            None => bail!("Function '{locator}' does not have a finalize block"),
689        };
690
691        // Ensure the number of arguments matches the number of inputs.
692        ensure!(future.arguments().len() == inputs.len(), "Future arguments do not match");
693
694        // Check that the arguments match the inputs.
695        // Use the external stack if the future is from an external program.
696        for (argument, input) in future.arguments().iter().zip_eq(inputs.iter()) {
697            match (argument, input.finalize_type()) {
698                (Argument::Plaintext(plaintext), FinalizeType::Plaintext(plaintext_type)) => match &external_stack {
699                    Some(external_stack) => {
700                        external_stack.matches_plaintext_internal(plaintext, plaintext_type, depth + 1)?
701                    }
702                    None => self.matches_plaintext_internal(plaintext, plaintext_type, depth + 1)?,
703                },
704                (Argument::Future(future), FinalizeType::Future(locator)) => match &external_stack {
705                    Some(external_stack) => external_stack.matches_future_internal(future, locator, depth + 1)?,
706                    None => self.matches_future_internal(future, locator, depth + 1)?,
707                },
708                (Argument::DynamicFuture(_), FinalizeType::DynamicFuture) => {}
709                (_, input_type) => {
710                    bail!("Argument type does not match input type: expected '{input_type}'")
711                }
712            }
713        }
714
715        Ok(())
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use console::network::MainnetV0;
723    use snarkvm_synthesizer_program::Program;
724
725    type CurrentNetwork = MainnetV0;
726
727    // This test verifies that `contains_dynamic_call` returns `false` for a function
728    // that contains only static `call` instructions (no `call.dynamic`).
729    #[test]
730    fn test_contains_dynamic_call_with_static_calls() {
731        // `Process::load` always includes `credits.aleo`, which has no dynamic calls.
732        let process = Process::<CurrentNetwork>::load().unwrap();
733        let stack = process.get_stack("credits.aleo").unwrap();
734        // `transfer_public` uses only static operations — no `call.dynamic`.
735        let function_name = Identifier::from_str("transfer_public").unwrap();
736        assert!(!stack.contains_dynamic_call(&function_name).unwrap());
737    }
738
739    // This test verifies that `contains_dynamic_call` returns `true` for a function
740    // that directly contains a `call.dynamic` instruction.
741    #[test]
742    fn test_contains_dynamic_call_with_dynamic_calls() {
743        // Define a program with a function that issues a bare `call.dynamic` (no inputs or outputs).
744        let program = Program::<CurrentNetwork>::from_str(
745            r"
746program dynamic_test.aleo;
747
748function dynamic_func:
749    input r0 as field.public;
750    input r1 as field.public;
751    input r2 as field.public;
752    call.dynamic r0 r1 r2;",
753        )
754        .unwrap();
755        // Add the program to a fresh process (no deployment needed for stack inspection).
756        let process = Process::<CurrentNetwork>::load().unwrap();
757        process.lock().add_program(&program).unwrap();
758        let stack = process.get_stack("dynamic_test.aleo").unwrap();
759        // `dynamic_func` contains a `call.dynamic` instruction and must be detected.
760        let function_name = Identifier::from_str("dynamic_func").unwrap();
761        assert!(stack.contains_dynamic_call(&function_name).unwrap());
762    }
763
764    // This test verifies that `contains_dynamic_call` returns `true` for a function that
765    // transitively reaches a `call.dynamic` via a static external call.
766    #[test]
767    fn test_contains_dynamic_call_transitive() {
768        // Define a helper program whose function issues `call.dynamic`.
769        let helper_program = Program::<CurrentNetwork>::from_str(
770            r"
771program helper.aleo;
772
773function dynamic_helper:
774    input r0 as field.public;
775    input r1 as field.public;
776    input r2 as field.public;
777    call.dynamic r0 r1 r2;",
778        )
779        .unwrap();
780        // Define a caller program that statically calls `helper.aleo/dynamic_helper`.
781        let caller_program = Program::<CurrentNetwork>::from_str(
782            r"
783import helper.aleo;
784
785program caller.aleo;
786
787function caller_func:
788    input r0 as field.public;
789    input r1 as field.public;
790    input r2 as field.public;
791    call helper.aleo/dynamic_helper r0 r1 r2;",
792        )
793        .unwrap();
794        // Add programs in dependency order: helper first, then caller.
795        let process = Process::<CurrentNetwork>::load().unwrap();
796        process.lock().add_program(&helper_program).unwrap();
797        process.lock().add_program(&caller_program).unwrap();
798        let stack = process.get_stack("caller.aleo").unwrap();
799        // `caller_func` transitively reaches `call.dynamic` via `helper.aleo/dynamic_helper`.
800        let function_name = Identifier::from_str("caller_func").unwrap();
801        assert!(stack.contains_dynamic_call(&function_name).unwrap());
802    }
803}