Skip to main content

snarkvm_synthesizer_process/
finalize.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::*;
17use console::program::{FinalizeType, Future, Identifier, Register};
18use snarkvm_synthesizer_error::{FinalizeError, IndexedFinalizeError, IntoIndexedFinalize, indexed_finalize_bail};
19use snarkvm_synthesizer_program::{Await, FinalizeRegistersState, FinalizeStoreTrait, Operand, RegistersTrait};
20use snarkvm_utilities::try_vm_runtime;
21
22use std::collections::HashSet;
23
24type TotalAwaits = usize;
25
26impl<'a, N: Network> ProcessExclusiveGuard<'a, N> {
27    /// Finalizes the deployment and fee.
28    /// This method assumes the given deployment **is valid**.
29    /// This method should **only** be called by `VM::finalize()`.
30    #[inline]
31    pub fn finalize_deployment<P: FinalizeStorage<N>>(
32        &self,
33        state: FinalizeGlobalState,
34        store: &FinalizeStore<N, P>,
35        deployment: &Deployment<N>,
36        fee: &Fee<N>,
37    ) -> Result<(Stack<N>, Vec<FinalizeOperation<N>>), IndexedFinalizeError<N, Command<N>>> {
38        let timer = timer!("Process::finalize_deployment");
39
40        // Fetch the program ID.
41        let deploy_program_id = deployment.program().id();
42
43        // Get the deployment version.
44        let version = deployment.version()?;
45
46        // Finalize the deployment based on its version.
47        match version {
48            DeploymentVersion::V1 | DeploymentVersion::V2 => {
49                // Compute the program stack.
50                let mut stack = Stack::new(self.process, deployment.program()).into_indexed(
51                    Some((*deploy_program_id, deployment.edition())),
52                    None,
53                    None::<(usize, Command<N>)>,
54                )?;
55                lap!(timer, "Compute the stack");
56
57                // Set the program owner.
58                stack.set_program_owner(deployment.program_owner());
59
60                // Insert all verifying keys (unified: functions + records).
61                for (function_name, (verifying_key, _)) in deployment.verifying_keys() {
62                    stack.insert_verifying_key(function_name, verifying_key.clone()).into_indexed(
63                        Some((*deploy_program_id, deployment.edition())),
64                        None,
65                        None::<(usize, Command<N>)>,
66                    )?;
67                }
68                lap!(timer, "Insert the verifying keys");
69
70                // Determine which mappings must be initialized.
71                let mappings = match deployment.edition().is_zero() {
72                    true => deployment.program().mappings().values().collect::<Vec<_>>(),
73                    false => {
74                        // Get the existing stack.
75                        let existing_stack = self.process.get_stack(deployment.program_id()).into_indexed(
76                            Some((*deploy_program_id, deployment.edition())),
77                            None,
78                            None::<(usize, Command<N>)>,
79                        )?;
80                        // Get the existing mappings.
81                        let existing_mappings = existing_stack.program().mappings();
82                        // Determine and return the new mappings.
83                        let mut new_mappings = Vec::new();
84                        for mapping in deployment.program().mappings().values() {
85                            if !existing_mappings.contains_key(mapping.name()) {
86                                new_mappings.push(mapping);
87                            }
88                        }
89                        new_mappings
90                    }
91                };
92                lap!(timer, "Retrieve the mappings to initialize");
93
94                // Initialize the mappings, and store their finalize operations.
95                atomic_batch_scope!(store, IndexedFinalizeError::<N, Command<N>>, {
96                    // Initialize a list for the finalize operations.
97                    let mut finalize_operations = Vec::with_capacity(deployment.program().mappings().len());
98
99                    /* Finalize the fee. */
100
101                    // Retrieve the fee stack.
102                    let fee_stack = self.process.get_stack(fee.program_id()).into_indexed(
103                        Some((*fee.program_id(), self.get_latest_edition_for_program(fee.program_id()))),
104                        Some(*fee.function_name()),
105                        None::<(usize, Command<N>)>,
106                    )?;
107                    // Finalize the fee transition.
108                    finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
109                    lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
110
111                    /* Finalize the deployment. */
112
113                    // Retrieve the program ID.
114                    let program_id = deployment.program_id();
115                    // Iterate over the mappings that must be initialized.
116                    for mapping in mappings {
117                        // Initialize the mapping.
118                        finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name()).into_indexed(
119                            Some((*program_id, deployment.edition())),
120                            None,
121                            None::<(usize, Command<N>)>,
122                        )?);
123                    }
124                    lap!(timer, "Initialize the program mappings");
125
126                    // If the program has a constructor, execute it and extend the finalize operations.
127                    // This must happen after the mappings are initialized as the constructor may depend on them.
128                    if deployment.program().contains_constructor() {
129                        let operations = finalize_constructor(state, store, &stack, N::TransitionID::default())?;
130                        finalize_operations.extend(operations);
131                        lap!(timer, "Execute the constructor");
132                    }
133
134                    finish!(timer, "Finished finalizing the deployment");
135                    // Return the stack and finalize operations.
136                    Ok((stack, finalize_operations))
137                })
138            }
139            DeploymentVersion::V3 => {
140                // Ensure that the program is not `credits.aleo`.
141                if deployment.program_id() == &ProgramID::credits() {
142                    return Err(
143                        anyhow!("The 'credits.aleo' program cannot be deployed with DeploymentVersion::V3").into()
144                    );
145                }
146
147                // Get the existing stack.
148                let existing_stack = self.process.get_stack(deployment.program_id())?;
149                // Increment the amendment count while preserving the existing edition.
150                let amendment_count = existing_stack
151                    .program_amendment_count()
152                    .checked_add(1)
153                    .ok_or_else(|| anyhow!("Overflow while incrementing the program amendment count"))?;
154
155                // Compute a new stack with the same program and edition.
156                // Note: `Stack::new` cannot be used here because it would increment the edition.
157                // Amendments must preserve the existing edition. Validity is verified by `initialize_and_check`.
158                let mut stack = Stack::new_raw(self.process, deployment.program(), *existing_stack.program_edition())?;
159                stack.initialize_and_check(self.process)?;
160                lap!(timer, "Compute the stack");
161
162                // Set the amendment count for this edition.
163                stack.set_program_amendment_count(amendment_count);
164                // Set the program owner to the existing owner.
165                stack.set_program_owner(*existing_stack.program_owner());
166
167                // Insert all verifying keys (unified: functions + records).
168                for (name, (verifying_key, _)) in deployment.verifying_keys() {
169                    stack.insert_verifying_key(name, verifying_key.clone())?;
170                }
171                lap!(timer, "Insert the verifying keys");
172
173                // Finalize the fee (amendments don't initialize mappings or run constructors).
174                atomic_batch_scope!(store, IndexedFinalizeError::<N, Command<N>>, {
175                    let mut finalize_operations = Vec::new();
176
177                    // Retrieve the fee stack.
178                    let fee_stack = self.process.get_stack(fee.program_id())?;
179                    // Finalize the fee transition.
180                    finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
181                    lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
182
183                    finish!(timer, "Finished finalizing the V3 deployment");
184                    Ok((stack, finalize_operations))
185                })
186            }
187        }
188    }
189
190    /// Finalizes the execution and fee.
191    /// This method assumes the given execution **is valid**.
192    /// This method should **only** be called by `VM::finalize()`.
193    #[inline]
194    pub fn finalize_execution<P: FinalizeStorage<N>>(
195        &self,
196        state: FinalizeGlobalState,
197        store: &FinalizeStore<N, P>,
198        execution: &Execution<N>,
199        fee: Option<&Fee<N>>,
200    ) -> Result<Vec<FinalizeOperation<N>>, IndexedFinalizeError<N, Command<N>>> {
201        let timer = timer!("Program::finalize_execution");
202
203        // Ensure the execution contains transitions.
204        if execution.is_empty() {
205            indexed_finalize_bail!(None, None, "There are no transitions in the execution");
206        }
207
208        // Ensure the number of transitions matches the program function.
209        // Retrieve the root transition (without popping it).
210        let transition = execution.peek().into_indexed(None, None, None::<(usize, Command<N>)>)?;
211        // Extract the program ID and function name for error reporting.
212        let transition_program_id = *transition.program_id();
213        let transition_function_name = *transition.function_name();
214        // Retrieve the stack.
215        let stack = self.process.get_stack(transition.program_id()).into_indexed(
216            Some((transition_program_id, self.get_latest_edition_for_program(&transition_program_id))),
217            Some(transition_function_name),
218            None::<(usize, Command<N>)>,
219        )?;
220        // Calculate the minimum number of calls for the root transition.
221        let minimum_number_of_calls = stack.get_minimum_number_of_calls(transition.function_name()).into_indexed(
222            Some((transition_program_id, *stack.program_edition())),
223            Some(transition_function_name),
224            None::<(usize, Command<N>)>,
225        )?;
226        // If the root transition contains a dynamic call,
227        // - ensure that the number of calls is less than or equal to the number of transitions.
228        // - otherwise, ensure that the number of calls matches the number of transitions.
229        if stack.contains_dynamic_call(transition.function_name())? {
230            if minimum_number_of_calls > execution.len() {
231                indexed_finalize_bail!(
232                    Some((transition_program_id, *stack.program_edition())),
233                    Some(transition_function_name),
234                    "The number of transitions in the execution is incorrect. \
235                    Expected at least {minimum_number_of_calls}, but found {}",
236                    execution.len()
237                );
238            }
239        } else if minimum_number_of_calls != execution.len() {
240            indexed_finalize_bail!(
241                Some((transition_program_id, *stack.program_edition())),
242                Some(transition_function_name),
243                "The number of transitions in the execution is incorrect. \
244                Expected {minimum_number_of_calls}, but found {}",
245                execution.len()
246            );
247        }
248        lap!(timer, "Verify the number of transitions");
249
250        // Collect all of the futures in the execution's transitions and compute their corresponding dynamic future keys.
251        // The key is (program_name, program_network, function_name, checksum). Futures with identical program,
252        // function, and arguments produce the same key and the same checksum by design — they represent the same
253        // logical future, so the map correctly de-duplicates them.
254        let dynamic_future_to_future: HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>> = execution
255            .transitions()
256            .filter_map(|transition| {
257                transition.outputs().last().and_then(|output| output.future()).and_then(|future| {
258                    let dynamic_future = DynamicFuture::from_future(future).ok()?;
259                    let key = (
260                        *dynamic_future.program_name(),
261                        *dynamic_future.program_network(),
262                        *dynamic_future.function_name(),
263                        *dynamic_future.checksum(),
264                    );
265                    Some((key, future))
266                })
267            })
268            .collect();
269
270        // Construct the call graph.
271        let consensus_version = N::CONSENSUS_VERSION(state.block_height()).into_indexed(
272            Some((transition_program_id, *stack.program_edition())),
273            Some(transition_function_name),
274            None::<(usize, Command<N>)>,
275        )?;
276        let call_graph = match (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
277            true => {
278                let mut execution_stacks = indexmap::IndexMap::new();
279                for transition in execution.transitions() {
280                    execution_stacks.insert(*transition.program_id(), self.process.get_stack(transition.program_id())?);
281                }
282                Process::construct_call_graph(execution.transitions(), &execution_stacks).into_indexed(
283                    Some((transition_program_id, *stack.program_edition())),
284                    Some(transition_function_name),
285                    None::<(usize, Command<N>)>,
286                )?
287            }
288            // If the height is greater than or equal to `ConsensusVersion::V3`, then provide an empty call graph, as it is no longer used during finalization.
289            false => HashMap::new(),
290        };
291
292        atomic_batch_scope!(store, IndexedFinalizeError::<N, Command<N>>, {
293            // Finalize the root transition.
294            // Note that this will result in all the remaining transitions being finalized, since the number
295            // of calls matches the number of transitions.
296            let (mut finalize_operations, total_awaits) =
297                finalize_transition(state, store, &stack, transition, call_graph, dynamic_future_to_future)?;
298
299            if consensus_version >= ConsensusVersion::V15 {
300                // Check that the total number of `Await` commands evaluated during
301                // finalization matches the number of `Future`s defined in the
302                // execution's transitions' outputs.
303                let total_futures = execution
304                    .transitions()
305                    .filter(|t| t.outputs().last().and_then(|output| output.future()).is_some())
306                    .count();
307                let expected_total_awaits = total_futures.saturating_sub(1);
308                if total_awaits != expected_total_awaits {
309                    indexed_finalize_bail!(
310                        Some((transition_program_id, *stack.program_edition())),
311                        Some(transition_function_name),
312                        "The number of 'await' calls during finalization is incorrect. \
313                        Expected {expected_total_awaits}, but found {total_awaits}"
314                    );
315                }
316            }
317
318            /* Finalize the fee. */
319            if let Some(fee) = fee {
320                // Retrieve the fee stack.
321                let fee_stack = self.process.get_stack(fee.program_id()).into_indexed(
322                    Some((*fee.program_id(), self.get_latest_edition_for_program(fee.program_id()))),
323                    Some(*fee.function_name()),
324                    None::<(usize, Command<N>)>,
325                )?;
326                // Finalize the fee transition.
327                finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
328                lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
329            }
330
331            finish!(timer);
332            // Return the finalize operations.
333            Ok(finalize_operations)
334        })
335    }
336
337    /// Finalizes the fee.
338    /// This method assumes the given fee **is valid**.
339    /// This method should **only** be called by `VM::finalize()`.
340    #[inline]
341    pub fn finalize_fee<P: FinalizeStorage<N>>(
342        &self,
343        state: FinalizeGlobalState,
344        store: &FinalizeStore<N, P>,
345        fee: &Fee<N>,
346    ) -> Result<Vec<FinalizeOperation<N>>, IndexedFinalizeError<N, Command<N>>> {
347        let timer = timer!("Program::finalize_fee");
348
349        atomic_batch_scope!(store, IndexedFinalizeError::<N, Command<N>>, {
350            // Retrieve the stack.
351            let stack = self.process.get_stack(fee.program_id()).into_indexed(
352                Some((*fee.program_id(), self.get_latest_edition_for_program(fee.program_id()))),
353                Some(*fee.function_name()),
354                None::<(usize, Command<N>)>,
355            )?;
356            // Finalize the fee transition.
357            let result = finalize_fee_transition(state, store, &stack, fee);
358            finish!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
359            // Return the result.
360            result
361        })
362    }
363}
364
365/// Finalizes the given fee transition.
366fn finalize_fee_transition<N: Network, P: FinalizeStorage<N>>(
367    state: FinalizeGlobalState,
368    store: &FinalizeStore<N, P>,
369    stack: &Arc<Stack<N>>,
370    fee: &Fee<N>,
371) -> Result<Vec<FinalizeOperation<N>>, IndexedFinalizeError<N, Command<N>>> {
372    // Construct the call graph.
373    let consensus_version = N::CONSENSUS_VERSION(state.block_height()).into_indexed(
374        Some((*fee.program_id(), *stack.program_edition())),
375        Some(*fee.function_name()),
376        None::<(usize, Command<N>)>,
377    )?;
378    let call_graph = match (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
379        true => HashMap::from([(*fee.transition_id(), Vec::new())]),
380        // If the height is greater than or equal to `ConsensusVersion::V3`, then provide an empty call graph, as it is no longer used during finalization.
381        false => HashMap::new(),
382    };
383
384    // Finalize the transition.
385    let (finalize_operations, total_awaits) =
386        finalize_transition(state, store, stack, fee, call_graph, Default::default())?;
387    // Create IndexedFinalizeError if the fee path has awaits.
388    if consensus_version >= ConsensusVersion::V15 && total_awaits != 0 {
389        indexed_finalize_bail!(
390            Some((*fee.program_id(), *stack.program_edition())),
391            Some(*fee.function_name()),
392            "Fees must not have any awaits"
393        );
394    }
395    Ok(finalize_operations)
396}
397
398/// Finalizes the constructor.
399fn finalize_constructor<N: Network, P: FinalizeStorage<N>>(
400    state: FinalizeGlobalState,
401    store: &FinalizeStore<N, P>,
402    stack: &Stack<N>,
403    transition_id: N::TransitionID,
404) -> Result<Vec<FinalizeOperation<N>>, IndexedFinalizeError<N, Command<N>>> {
405    // Retrieve the program ID.
406    let program_id = stack.program_id();
407    let edition = stack.program_edition();
408    let resource = Identifier::from_str("constructor")?;
409    dev_println!("Finalizing constructor for {}...", stack.program_id());
410
411    // Initialize a list for finalize operations.
412    let mut finalize_operations = Vec::new();
413
414    // Initialize a nonce for the constructor registers.
415    // Currently, this nonce is set to zero for every constructor.
416    let nonce = 0;
417
418    // Get the constructor logic. If the program does not have a constructor, return early.
419    let Some(constructor) = stack.program().constructor() else {
420        return Ok(finalize_operations);
421    };
422
423    // Get the constructor types.
424    let constructor_types = match stack.get_constructor_types() {
425        Ok(types) => types.clone(),
426        Err(error) => {
427            indexed_finalize_bail!(
428                Some((*program_id, *edition)),
429                Some(resource),
430                "Failed to get constructor types - {error}"
431            )
432        }
433    };
434
435    // Initialize the finalize registers.
436    let mut registers =
437        FinalizeRegisters::new(state, Some(transition_id), *program_id.name(), constructor_types, Some(nonce));
438
439    // Determine the scope name.
440    let scope_name = Identifier::<N>::from_str("constructor")?;
441
442    // Initialize a counter for the commands.
443    let mut counter = 0;
444
445    // Evaluate the commands.
446    while counter < constructor.commands().len() {
447        // Retrieve the command.
448        let command = &constructor.commands()[counter];
449        // Finalize the command.
450        match &command {
451            Command::Await(_) => {
452                indexed_finalize_bail!(
453                    Some((*program_id, *edition)),
454                    Some(resource),
455                    counter,
456                    command.clone(),
457                    "Cannot `await` a Future in a constructor"
458                )
459            }
460            _ => finalize_command_except_await(
461                Some((*program_id, *edition)),
462                Some(resource),
463                store,
464                stack,
465                &mut registers,
466                constructor.positions(),
467                command,
468                &mut counter,
469                &mut finalize_operations,
470                &scope_name,
471            )?,
472        };
473    }
474
475    // Return the finalize operations.
476    Ok(finalize_operations)
477}
478
479/// Finalizes the given transition.
480fn finalize_transition<N: Network, P: FinalizeStorage<N>>(
481    state: FinalizeGlobalState,
482    store: &FinalizeStore<N, P>,
483    stack: &Arc<Stack<N>>,
484    transition: &Transition<N>,
485    call_graph: HashMap<N::TransitionID, Vec<N::TransitionID>>,
486    dynamic_future_to_future: HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>>,
487) -> Result<(Vec<FinalizeOperation<N>>, TotalAwaits), IndexedFinalizeError<N, Command<N>>> {
488    // Retrieve the program ID.
489    let program_id = transition.program_id();
490    // Retrieve the function name.
491    let function_name = transition.function_name();
492
493    dev_println!("Finalizing transition for {program_id}/{function_name}...");
494    debug_assert_eq!(stack.program_id(), transition.program_id());
495
496    // If the last output of the transition is a future, retrieve and finalize it. Otherwise, there are no operations to finalize.
497    let future = match transition.outputs().last().and_then(|output| output.future()) {
498        Some(future) => future,
499        _ => return Ok((Vec::new(), 0)),
500    };
501
502    // Check that the program ID and function name of the transition match those in the future.
503    if future.program_id() != program_id || future.function_name() != function_name {
504        indexed_finalize_bail!(
505            Some((*program_id, *stack.program_edition())),
506            Some(*function_name),
507            "The program ID and function name of the future do not match the transition"
508        );
509    }
510
511    // Initialize a list for finalize operations.
512    let mut finalize_operations = Vec::new();
513
514    // Initialize a stack of active finalize states.
515    let mut states = Vec::new();
516
517    // Initialize a nonce for the finalize registers.
518    // Note that this nonce must be unique for each sub-transition being finalized.
519    let mut nonce = 0;
520    // Top-level outputs are always static futures.
521    let is_dynamic = false;
522
523    // Initialize the top-level finalize state.
524    states.push(initialize_finalize_state(state, future, stack, *transition.id(), nonce, is_dynamic).into_indexed(
525        Some((*program_id, *stack.program_edition())),
526        Some(*function_name),
527        None::<(usize, Command<N>)>,
528    )?);
529
530    // Track the total number of `Await` commands evaluated across all `FinalizeState`s.
531    let mut total_awaits: TotalAwaits = 0;
532
533    // While there are active finalize states, finalize them.
534    'outer: while let Some(FinalizeState { mut counter, mut registers, stack, mut call_counter, mut awaited }) =
535        states.pop()
536    {
537        // Retrieve the current program ID, edition, and function name for error reporting.
538        let finalize_program_id = *stack.program_id();
539        let finalize_edition = *stack.program_edition();
540        let finalize_resource = *registers.function_name();
541        // Get the finalize logic.
542        let Some(finalize) = stack
543            .get_function_ref(registers.function_name())
544            .into_indexed(
545                Some((finalize_program_id, finalize_edition)),
546                Some(finalize_resource),
547                None::<(usize, Command<N>)>,
548            )?
549            .finalize_logic()
550        else {
551            indexed_finalize_bail!(
552                Some((finalize_program_id, finalize_edition)),
553                Some(finalize_resource),
554                "The function '{finalize_program_id}/{finalize_resource}' does not have an associated finalize scope",
555            )
556        };
557        // Determine the scope name.
558        let scope_name = *registers.function_name();
559        // Evaluate the commands.
560        while counter < finalize.commands().len() {
561            // Retrieve the command.
562            let command = &finalize.commands()[counter];
563            // Finalize the command.
564            match &command {
565                Command::Await(await_) => {
566                    // Check that the `await` register is a locator.
567                    if let Register::Access(_, _) = await_.register() {
568                        indexed_finalize_bail!(
569                            Some((finalize_program_id, finalize_edition)),
570                            Some(finalize_resource),
571                            "The 'await' register must be a locator"
572                        )
573                    };
574                    // Check that the future has not previously been awaited.
575                    if awaited.contains(await_.register()) {
576                        indexed_finalize_bail!(
577                            Some((finalize_program_id, finalize_edition)),
578                            Some(finalize_resource),
579                            counter,
580                            command.clone(),
581                            "The future register '{}' has already been awaited",
582                            await_.register()
583                        );
584                    }
585                    // Get the transition ID used to initialize the finalize registers.
586                    // If the block height is greater than or equal to `ConsensusVersion::V3`, then use the top-level transition ID.
587                    // Otherwise, view the call graph for the child transition ID corresponding to the future that is being awaited.
588                    let consensus_version = N::CONSENSUS_VERSION(state.block_height()).into_indexed(
589                        Some((finalize_program_id, finalize_edition)),
590                        Some(finalize_resource),
591                        Some((counter, command.clone())),
592                    )?;
593                    let transition_id = if (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
594                        // Get the current transition ID. The finalize path always initializes
595                        // registers with `Some(transition_id)`; only the view path uses `None`,
596                        // and `await` is forbidden on the view path, so this is unreachable
597                        // there. Treat `None` as a logic error.
598                        let transition_id = registers
599                            .transition_id()
600                            .ok_or_else(|| anyhow!("Cannot resolve a child transition ID without a transition ID"))?;
601                        // Get the child transition ID.
602                        match call_graph.get(transition_id) {
603                            Some(transitions) => match transitions.get(call_counter) {
604                                Some(transition_id) => *transition_id,
605                                None => indexed_finalize_bail!(
606                                    Some((finalize_program_id, finalize_edition)),
607                                    Some(finalize_resource),
608                                    counter,
609                                    command.clone(),
610                                    "Child transition ID not found."
611                                ),
612                            },
613                            None => indexed_finalize_bail!(
614                                Some((finalize_program_id, finalize_edition)),
615                                Some(finalize_resource),
616                                counter,
617                                command.clone(),
618                                "Transition ID '{transition_id}' not found in call graph"
619                            ),
620                        }
621                    } else {
622                        *transition.id()
623                    };
624
625                    // Increment the nonce.
626                    nonce += 1;
627
628                    // Set up the finalize state for the await.
629                    let callee_state = match try_vm_runtime!(|| setup_await(
630                        state,
631                        await_,
632                        &stack,
633                        &registers,
634                        transition_id,
635                        nonce,
636                        &dynamic_future_to_future,
637                    )) {
638                        Ok(Ok(callee_state)) => callee_state,
639                        // If the evaluation fails, bail and return the error.
640                        Ok(Err(error)) => indexed_finalize_bail!(
641                            Some((finalize_program_id, finalize_edition)),
642                            Some(finalize_resource),
643                            counter,
644                            command.clone(),
645                            "'finalize' failed to evaluate command: {error}"
646                        ),
647                        // If the evaluation fails, bail and return the error.
648                        Err(_) => indexed_finalize_bail!(
649                            Some((finalize_program_id, finalize_edition)),
650                            Some(finalize_resource),
651                            counter,
652                            command.clone(),
653                            "'finalize' failed to evaluate command"
654                        ),
655                    };
656
657                    // Increment the call counter.
658                    call_counter += 1;
659                    // Increment the total number of `Await` commands evaluated.
660                    total_awaits += 1;
661                    // Increment the counter.
662                    counter += 1;
663                    // Add the awaited register to the tracked set.
664                    awaited.insert(await_.register().clone());
665
666                    // Aggregate the caller state.
667                    let caller_state = FinalizeState { counter, registers, stack, call_counter, awaited };
668
669                    // Push the caller state onto the stack.
670                    states.push(caller_state);
671                    // Push the callee state onto the stack.
672                    states.push(callee_state);
673
674                    continue 'outer;
675                }
676                _ => finalize_command_except_await(
677                    Some((finalize_program_id, finalize_edition)),
678                    Some(finalize_resource),
679                    store,
680                    stack.deref(),
681                    &mut registers,
682                    finalize.positions(),
683                    command,
684                    &mut counter,
685                    &mut finalize_operations,
686                    &scope_name,
687                )?,
688            };
689        }
690        // Check that all future registers have been awaited.
691        let mut unawaited = Vec::new();
692        for input in finalize.inputs() {
693            if matches!(input.finalize_type(), FinalizeType::Future(_) | FinalizeType::DynamicFuture)
694                && !awaited.contains(input.register())
695            {
696                unawaited.push(input.register().clone());
697            }
698        }
699        if !unawaited.is_empty() {
700            indexed_finalize_bail!(
701                Some((finalize_program_id, finalize_edition)),
702                Some(finalize_resource),
703                "The following future registers have not been awaited: {}",
704                unawaited.iter().map(|r| r.to_string()).collect::<Vec<_>>().join(", ")
705            );
706        }
707    }
708
709    // Return the finalize operations and the total number of `Await` commands evaluated.
710    Ok((finalize_operations, total_awaits))
711}
712
713// A helper struct to track the execution of a finalize scope.
714struct FinalizeState<N: Network> {
715    // A counter for the index of the commands.
716    counter: usize,
717    // The registers.
718    registers: FinalizeRegisters<N>,
719    // The stack.
720    stack: Arc<Stack<N>>,
721    // Call counter.
722    call_counter: usize,
723    // Awaited futures.
724    awaited: HashSet<Register<N>>,
725}
726
727// A helper function to initialize the finalize state for transitions (not constructors).
728fn initialize_finalize_state<N: Network>(
729    state: FinalizeGlobalState,
730    future: &Future<N>,
731    stack: &Arc<Stack<N>>,
732    transition_id: N::TransitionID,
733    nonce: u64,
734    is_dynamic: bool,
735) -> Result<FinalizeState<N>> {
736    // Get the stack.
737    let stack = match (stack.program_id() == future.program_id(), is_dynamic) {
738        (true, _) => stack.clone(),
739        (false, true) => stack.get_stack_global(future.program_id())?,
740        (false, false) => stack.get_external_stack(future.program_id())?,
741    };
742    // Get the finalize logic and check that it exists.
743    let Some(finalize) = stack.get_function_ref(future.function_name())?.finalize_logic() else {
744        bail!(
745            "The function '{}/{}' does not have an associated finalize scope",
746            future.program_id(),
747            future.function_name()
748        )
749    };
750    // Initialize the registers.
751    let mut registers = FinalizeRegisters::new(
752        state,
753        Some(transition_id),
754        *future.function_name(),
755        stack.get_finalize_types(future.function_name())?.clone(),
756        Some(nonce),
757    );
758
759    // Store the inputs. The argument count is guaranteed to match the finalize's declared inputs
760    // because the Future was validated against the finalize type signature at execution time.
761    finalize.inputs().iter().map(|i| i.register()).zip_eq(future.arguments().iter()).try_for_each(
762        |(register, input)| {
763            // Assign the input value to the register.
764            registers.store(stack.deref(), register, Value::from(input))
765        },
766    )?;
767
768    Ok(FinalizeState { counter: 0, registers, stack, call_counter: 0, awaited: Default::default() })
769}
770
771// A helper function to finalize all commands except `await`, updating the finalize operations and the counter.
772//
773// Generic over the store so the view evaluator (which passes either the canonical
774// `FinalizeStore` or a read-only historic adapter) can reuse this dispatch. The stack must be
775// the concrete `Stack<N>` so we can resolve `Call`-to-view targets and read their cached
776// `FinalizeTypes` (the in-block call path needs concrete access).
777#[inline]
778pub(crate) fn finalize_command_except_await<N: Network>(
779    program_id: Option<(ProgramID<N>, u16)>,
780    resource: Option<Identifier<N>>,
781    store: &dyn FinalizeStoreTrait<N>,
782    stack: &Stack<N>,
783    registers: &mut FinalizeRegisters<N>,
784    positions: &HashMap<Identifier<N>, usize>,
785    command: &Command<N>,
786    counter: &mut usize,
787    finalize_operations: &mut Vec<FinalizeOperation<N>>,
788    scope_name: &Identifier<N>,
789) -> Result<(), IndexedFinalizeError<N, Command<N>>> {
790    // Finalize the command.
791    match &command {
792        Command::BranchEq(branch_eq) => {
793            let result = try_vm_runtime!(|| branch_to(*counter, branch_eq, positions, stack, registers));
794            match result {
795                Ok(Ok(new_counter)) => {
796                    *counter = new_counter;
797                }
798                // If the evaluation fails, bail and return the error.
799                Ok(Err(error)) => indexed_finalize_bail!(
800                    program_id,
801                    resource,
802                    *counter,
803                    command.clone(),
804                    "'{scope_name}' failed to evaluate command ({command}): {error}"
805                ),
806                // If the evaluation fails, bail and return the error.
807                Err(_) => indexed_finalize_bail!(
808                    program_id,
809                    resource,
810                    *counter,
811                    command.clone(),
812                    "'{scope_name}' failed to evaluate command"
813                ),
814            }
815        }
816        Command::BranchNeq(branch_neq) => {
817            let result = try_vm_runtime!(|| branch_to(*counter, branch_neq, positions, stack, registers));
818            match result {
819                Ok(Ok(new_counter)) => {
820                    *counter = new_counter;
821                }
822                // If the evaluation fails, bail and return the error.
823                Ok(Err(error)) => indexed_finalize_bail!(
824                    program_id,
825                    resource,
826                    *counter,
827                    command.clone(),
828                    "'{scope_name}' failed to evaluate command: {error}"
829                ),
830                // If the evaluation fails, bail and return the error.
831                Err(_) => indexed_finalize_bail!(
832                    program_id,
833                    resource,
834                    *counter,
835                    command.clone(),
836                    "'{scope_name}' failed to evaluate command"
837                ),
838            }
839        }
840        Command::Await(_) => {
841            indexed_finalize_bail!(
842                program_id,
843                resource,
844                *counter,
845                command.clone(),
846                "Cannot use `finalize_command_except_await` with an 'await' command"
847            )
848        }
849        _ => {
850            let result = try_vm_runtime!(|| command.finalize(stack, store, registers));
851            match result {
852                // If the evaluation succeeds with an operation, add it to the list.
853                Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation),
854                // If the evaluation succeeds with no operation, continue.
855                Ok(Ok(None)) => {}
856                // If the evaluation fails, bail and return the error.
857                Ok(Err(error)) => {
858                    return Err(IndexedFinalizeError::new(
859                        program_id,
860                        resource,
861                        Some((*counter, command.clone())),
862                        error,
863                    ));
864                }
865                // If the evaluation fails, bail and return the error.
866                Err(_) => indexed_finalize_bail!(
867                    program_id,
868                    resource,
869                    *counter,
870                    command.clone(),
871                    "'{scope_name}' failed to evaluate command"
872                ),
873            }
874            *counter += 1;
875        }
876    };
877    Ok(())
878}
879
880// A helper function that sets up the await operation.
881#[inline]
882fn setup_await<N: Network>(
883    state: FinalizeGlobalState,
884    await_: &Await<N>,
885    stack: &Arc<Stack<N>>,
886    registers: &FinalizeRegisters<N>,
887    transition_id: N::TransitionID,
888    nonce: u64,
889    dynamic_future_to_future: &HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>>,
890) -> Result<FinalizeState<N>> {
891    // Retrieve the input as a future.
892    let (future, is_dynamic) = match registers.load(stack.deref(), &Operand::Register(await_.register().clone()))? {
893        Value::Future(future) => (future, false),
894        Value::DynamicFuture(dynamic_future) => {
895            // Construct the key from the dynamic future's program name, network, function name, and checksum.
896            let key = (
897                *dynamic_future.program_name(),
898                *dynamic_future.program_network(),
899                *dynamic_future.function_name(),
900                *dynamic_future.checksum(),
901            );
902            // Look up the corresponding future from the dynamic future key.
903            match dynamic_future_to_future.get(&key) {
904                Some(future) => ((*future).clone(), true),
905                None => bail!("Dynamic future '{key:?}' not found in dynamic-future-to-future map"),
906            }
907        }
908        _ => bail!("The input to 'await' is not a future or dynamic future"),
909    };
910    // Initialize the state.
911    initialize_finalize_state(state, &future, stack, transition_id, nonce, is_dynamic)
912}
913
914// A helper function that returns the index to branch to.
915fn branch_to<N: Network, const VARIANT: u8>(
916    counter: usize,
917    branch: &Branch<N, VARIANT>,
918    positions: &HashMap<Identifier<N>, usize>,
919    stack: &impl StackTrait<N>,
920    registers: &impl RegistersTrait<N>,
921) -> Result<usize> {
922    // Retrieve the inputs.
923    let first = registers.load(stack, branch.first())?;
924    let second = registers.load(stack, branch.second())?;
925
926    // A helper to get the index corresponding to a position.
927    let get_position_index = |position: &Identifier<N>| match positions.get(position) {
928        Some(index) if *index > counter => Ok(*index),
929        Some(_) => bail!("Cannot branch to an earlier position '{position}' in the program"),
930        None => bail!("The position '{position}' does not exist."),
931    };
932
933    // Compare the operands and determine the index to branch to.
934    match VARIANT {
935        // The `branch.eq` variant.
936        0 if first == second => get_position_index(branch.position()),
937        0 if first != second => Ok(counter + 1),
938        // The `branch.neq` variant.
939        1 if first == second => Ok(counter + 1),
940        1 if first != second => get_position_index(branch.position()),
941        _ => bail!("Invalid 'branch' variant: {VARIANT}"),
942    }
943}
944
945#[cfg(test)]
946mod tests {
947    use super::*;
948    use crate::tests::test_execute::{sample_fee, sample_finalize_state};
949    use console::prelude::TestRng;
950    use snarkvm_ledger_store::{
951        BlockStore,
952        helpers::memory::{BlockMemory, FinalizeMemory},
953    };
954
955    use aleo_std::StorageMode;
956
957    type CurrentNetwork = console::network::MainnetV0;
958    type CurrentAleo = circuit::network::AleoV0;
959
960    #[test]
961    fn test_finalize_deployment() {
962        let rng = &mut TestRng::default();
963
964        // Initialize a new program.
965        let program = Program::<CurrentNetwork>::from_str(
966            r"
967program testing.aleo;
968
969struct message:
970    amount as u128;
971
972mapping account:
973    key as address.public;
974    value as u64.public;
975
976record token:
977    owner as address.private;
978    amount as u64.private;
979
980function initialize:
981    input r0 as address.private;
982    input r1 as u64.private;
983    cast r0 r1 into r2 as token.record;
984    output r2 as token.record;
985
986function compute:
987    input r0 as message.private;
988    input r1 as message.public;
989    input r2 as message.private;
990    input r3 as token.record;
991    add r0.amount r1.amount into r4;
992    cast r3.owner r3.amount into r5 as token.record;
993    output r4 as u128.public;
994    output r5 as token.record;",
995        )
996        .unwrap();
997
998        // Initialize a new process.
999        let process = Process::load().unwrap();
1000        // Deploy the program.
1001        let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
1002
1003        // Initialize a new block store.
1004        let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
1005        // Initialize a new finalize store.
1006        let finalize_store = FinalizeStore::<_, FinalizeMemory<_>>::open(StorageMode::new_test(None)).unwrap();
1007
1008        // Ensure the program does not exist.
1009        assert!(!process.contains_program(program.id()));
1010
1011        // Compute the fee.
1012        let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng);
1013        // Finalize the deployment.
1014        let (stack, _) =
1015            process.lock().finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap();
1016        // Add the stack *manually* to the process.
1017        process.lock().add_stack(stack);
1018
1019        // Ensure the program exists.
1020        assert!(process.contains_program(program.id()));
1021    }
1022}