snarkvm_synthesizer_process/stack/
evaluate.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> Stack<N> {
19    /// Evaluates a program closure on the given inputs.
20    ///
21    /// # Errors
22    /// This method will halt if the given inputs are not the same length as the input statements.
23    pub fn evaluate_closure<A: circuit::Aleo<Network = N>>(
24        &self,
25        closure: &Closure<N>,
26        inputs: &[Value<N>],
27        call_stack: CallStack<N>,
28        signer: Address<N>,
29        caller: Address<N>,
30        tvk: Field<N>,
31    ) -> Result<Vec<Value<N>>> {
32        let timer = timer!("Stack::evaluate_closure");
33
34        // Ensure the number of inputs matches the number of input statements.
35        if closure.inputs().len() != inputs.len() {
36            bail!("Expected {} inputs, found {}", closure.inputs().len(), inputs.len())
37        }
38
39        // Initialize the registers.
40        let mut registers =
41            Registers::<N, A>::new(call_stack.clone(), self.get_register_types(closure.name())?.clone());
42        // Set the transition signer.
43        registers.set_signer(signer);
44        // Set the transition caller.
45        registers.set_caller(caller);
46        // Set the transition view key.
47        registers.set_tvk(tvk);
48        lap!(timer, "Initialize the registers");
49
50        // Store the inputs.
51        closure.inputs().iter().map(|i| i.register()).zip_eq(inputs).try_for_each(|(register, input)| {
52            // Assign the input value to the register.
53            registers.store(self, register, input.clone())
54        })?;
55        lap!(timer, "Store the inputs");
56
57        // Evaluate the instructions.
58        for instruction in closure.instructions() {
59            // If the evaluation fails, bail and return the error.
60            if let Err(error) = instruction.evaluate(self, &mut registers) {
61                bail!("Failed to evaluate instruction ({instruction}): {error}");
62            }
63        }
64        lap!(timer, "Evaluate the instructions");
65
66        // Load the outputs.
67        let outputs = closure
68            .outputs()
69            .iter()
70            .map(|output| {
71                match output.operand() {
72                    // If the operand is a literal, use the literal directly.
73                    Operand::Literal(literal) => Ok(Value::Plaintext(Plaintext::from(literal))),
74                    // If the operand is a register, retrieve the stack value from the register.
75                    Operand::Register(register) => registers.load(self, &Operand::Register(register.clone())),
76                    // If the operand is the program ID, convert the program ID into an address.
77                    Operand::ProgramID(program_id) => {
78                        Ok(Value::Plaintext(Plaintext::from(Literal::Address(program_id.to_address()?))))
79                    }
80                    // If the operand is the signer, retrieve the signer from the registers.
81                    Operand::Signer => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.signer()?)))),
82                    // If the operand is the caller, retrieve the caller from the registers.
83                    Operand::Caller => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.caller()?)))),
84                    // If the operand is the block height, throw an error.
85                    Operand::BlockHeight => bail!("Cannot retrieve the block height from a closure scope."),
86                    // If the operand is the block timestamp, throw an error.
87                    Operand::BlockTimestamp => bail!("Cannot retrieve the block timestamp from a closure scope."),
88                    // If the operand is the network id, throw an error.
89                    Operand::NetworkID => bail!("Cannot retrieve the network ID from a closure scope."),
90                    // If the operand is the program checksum, throw an error.
91                    Operand::Checksum(_) => bail!("Cannot retrieve the program checksum from a closure scope."),
92                    // If the operand is the program edition, throw an error.
93                    Operand::Edition(_) => bail!("Cannot retrieve the edition from a closure scope."),
94                    // If the operand is the program owner, throw an error.
95                    Operand::ProgramOwner(_) => bail!("Cannot retrieve the program owner from a closure scope."),
96                }
97            })
98            .collect();
99        lap!(timer, "Load the outputs");
100
101        finish!(timer);
102        outputs
103    }
104
105    /// Evaluates a program function on the given inputs.
106    ///
107    /// # Errors
108    /// This method will halt if the given inputs are not the same length as the input statements.
109    pub fn evaluate_function<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
110        &self,
111        mut call_stack: CallStack<N>,
112        caller: Option<ProgramID<N>>,
113        root_tvk: Option<Field<N>>,
114        rng: &mut R,
115    ) -> Result<Response<N>> {
116        let timer = timer!("Stack::evaluate_function");
117
118        // Retrieve the next request, based on the call stack mode.
119        let (request, call_stack) = match &mut call_stack {
120            CallStack::Authorize(..) => (call_stack.pop()?, call_stack),
121            CallStack::Evaluate(authorization) => (authorization.next()?, call_stack),
122            // If the evaluation is performed in the `Execute` mode, create a new `Evaluate` mode.
123            // This is done to ensure that evaluation during execution is performed consistently.
124            CallStack::Execute(authorization, _) => {
125                // Note: We need to replicate the authorization, so that 'execute' can call 'authorization.next()?'.
126                // This way, the authorization remains unmodified in this 'evaluate' scope.
127                let authorization = authorization.replicate();
128                let request = authorization.next()?;
129                let call_stack = CallStack::Evaluate(authorization);
130                (request, call_stack)
131            }
132            _ => bail!(
133                "Illegal operation: call stack must be `Authorize`, `Evaluate` or `Execute` in `evaluate_function`."
134            ),
135        };
136        lap!(timer, "Retrieve the next request");
137
138        // Ensure the network ID matches.
139        ensure!(
140            **request.network_id() == N::ID,
141            "Network ID mismatch. Expected {}, but found {}",
142            N::ID,
143            request.network_id()
144        );
145
146        // Retrieve the function, inputs, and transition view key.
147        let function = self.get_function(request.function_name())?;
148        let inputs = request.inputs();
149        let signer = *request.signer();
150        let (is_root, caller) = match caller {
151            // If a caller is provided, then this is an evaluation of a child function.
152            Some(caller) => (false, caller.to_address()?),
153            // If no caller is provided, then this is an evaluation of a top-level function.
154            None => (true, signer),
155        };
156        let tvk = *request.tvk();
157        // Retrieve the program checksum, if the program has a constructor.
158        let program_checksum = match self.program().contains_constructor() {
159            true => Some(self.program_checksum_as_field()?),
160            false => None,
161        };
162
163        // Ensure the number of inputs matches.
164        if function.inputs().len() != inputs.len() {
165            bail!(
166                "Function '{}' in the program '{}' expects {} inputs, but {} were provided.",
167                function.name(),
168                self.program.id(),
169                function.inputs().len(),
170                inputs.len()
171            )
172        }
173        lap!(timer, "Perform input checks");
174
175        // Initialize the registers.
176        let mut registers = Registers::<N, A>::new(call_stack, self.get_register_types(function.name())?.clone());
177        // Set the transition signer.
178        registers.set_signer(signer);
179        // Set the transition caller.
180        registers.set_caller(caller);
181        // Set the transition view key.
182        registers.set_tvk(tvk);
183        // Set the root tvk.
184        if let Some(root_tvk) = root_tvk {
185            registers.set_root_tvk(root_tvk);
186        } else {
187            registers.set_root_tvk(tvk);
188        }
189        lap!(timer, "Initialize the registers");
190
191        // Ensure the request is well-formed.
192        ensure!(request.verify(&function.input_types(), is_root, program_checksum), "[Evaluate] Request is invalid");
193        lap!(timer, "Verify the request");
194
195        // Store the inputs.
196        function.inputs().iter().map(|i| i.register()).zip_eq(inputs).try_for_each(|(register, input)| {
197            // Assign the input value to the register.
198            registers.store(self, register, input.clone())
199        })?;
200        lap!(timer, "Store the inputs");
201
202        // Evaluate the instructions.
203        // Note: We handle the `call` instruction separately, as it requires special handling.
204        for instruction in function.instructions() {
205            // Evaluate the instruction.
206            let result = match instruction {
207                // If the instruction is a `call` instruction, we need to handle it separately.
208                Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers, rng),
209                // Otherwise, evaluate the instruction normally.
210                _ => instruction.evaluate(self, &mut registers),
211            };
212            // If the evaluation fails, bail and return the error.
213            if let Err(error) = result {
214                bail!("Failed to evaluate instruction ({instruction}): {error}");
215            }
216        }
217        lap!(timer, "Evaluate the instructions");
218
219        // Retrieve the output operands.
220        let output_operands = &function.outputs().iter().map(|output| output.operand()).collect::<Vec<_>>();
221        lap!(timer, "Retrieve the output operands");
222
223        // Load the outputs.
224        let outputs = output_operands
225            .iter()
226            .map(|operand| {
227                match operand {
228                    // If the operand is a literal, use the literal directly.
229                    Operand::Literal(literal) => Ok(Value::Plaintext(Plaintext::from(literal))),
230                    // If the operand is a register, retrieve the stack value from the register.
231                    Operand::Register(register) => registers.load(self, &Operand::Register(register.clone())),
232                    // If the operand is the program ID, convert the program ID into an address.
233                    Operand::ProgramID(program_id) => {
234                        Ok(Value::Plaintext(Plaintext::from(Literal::Address(program_id.to_address()?))))
235                    }
236                    // If the operand is the signer, retrieve the signer from the registers.
237                    Operand::Signer => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.signer()?)))),
238                    // If the operand is the caller, retrieve the caller from the registers.
239                    Operand::Caller => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.caller()?)))),
240                    // If the operand is the block height, throw an error.
241                    Operand::BlockHeight => bail!("Cannot retrieve the block height from a function scope."),
242                    // If the operand is the block timestamp, throw an error.
243                    Operand::BlockTimestamp => bail!("Cannot retrieve the block timestamp from a function scope."),
244                    // If the operand is the network id, throw an error.
245                    Operand::NetworkID => bail!("Cannot retrieve the network ID from a function scope."),
246                    // If the operand is the program checksum, throw an error.
247                    Operand::Checksum(_) => bail!("Cannot retrieve the program checksum from a function scope."),
248                    // If the operand is the program edition, throw an error.
249                    Operand::Edition(_) => bail!("Cannot retrieve the edition from a function scope."),
250                    // If the operand is the program owner, throw an error.
251                    Operand::ProgramOwner(_) => bail!("Cannot retrieve the program owner from a function scope."),
252                }
253            })
254            .collect::<Result<Vec<_>>>()?;
255        lap!(timer, "Load the outputs");
256
257        // Map the output operands to registers.
258        let output_registers = output_operands
259            .iter()
260            .map(|operand| match operand {
261                Operand::Register(register) => Some(register.clone()),
262                _ => None,
263            })
264            .collect::<Vec<_>>();
265        lap!(timer, "Loaded the output registers");
266
267        // Compute the response.
268        let response = Response::new(
269            request.signer(),
270            request.network_id(),
271            self.program.id(),
272            function.name(),
273            request.inputs().len(),
274            request.tvk(),
275            request.tcm(),
276            outputs,
277            &function.output_types(),
278            &output_registers,
279        )?;
280        finish!(timer);
281
282        // If the circuit is in `Authorize` mode, then save the transition.
283        if let CallStack::Authorize(_, _, authorization) = registers.call_stack_ref() {
284            // Construct the transition.
285            let transition = Transition::from(&request, &response, &function.output_types(), &output_registers)?;
286            // Add the transition to the authorization.
287            authorization.insert_transition(transition)?;
288            lap!(timer, "Save the transition");
289        }
290
291        Ok(response)
292    }
293}