Skip to main content

snarkvm_synthesizer_program/logic/instruction/operation/
call.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 crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait};
17use console::{
18    network::prelude::*,
19    program::{Identifier, Locator, Register, RegisterType},
20};
21
22/// The operator references a function name or closure name.
23#[derive(Clone, PartialEq, Eq, Hash)]
24pub enum CallOperator<N: Network> {
25    /// The reference to a non-local function or closure.
26    Locator(Locator<N>),
27    /// The reference to a local function or closure.
28    Resource(Identifier<N>),
29}
30
31impl<N: Network> Parser for CallOperator<N> {
32    /// Parses a string into an operator.
33    #[inline]
34    fn parse(string: &str) -> ParserResult<Self> {
35        alt((map(Locator::parse, CallOperator::Locator), map(Identifier::parse, CallOperator::Resource)))(string)
36    }
37}
38
39impl<N: Network> FromStr for CallOperator<N> {
40    type Err = Error;
41
42    /// Parses a string into an operator.
43    #[inline]
44    fn from_str(string: &str) -> Result<Self> {
45        match Self::parse(string) {
46            Ok((remainder, object)) => {
47                // Ensure the remainder is empty.
48                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
49                // Return the object.
50                Ok(object)
51            }
52            Err(error) => bail!("Failed to parse string. {error}"),
53        }
54    }
55}
56
57impl<N: Network> Debug for CallOperator<N> {
58    /// Prints the operator as a string.
59    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
60        Display::fmt(self, f)
61    }
62}
63
64impl<N: Network> Display for CallOperator<N> {
65    /// Prints the operator to a string.
66    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
67        match self {
68            CallOperator::Locator(locator) => Display::fmt(locator, f),
69            CallOperator::Resource(resource) => Display::fmt(resource, f),
70        }
71    }
72}
73
74impl<N: Network> FromBytes for CallOperator<N> {
75    /// Reads the operation from a buffer.
76    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
77        // Read the variant.
78        let variant = u8::read_le(&mut reader)?;
79        // Match the variant.
80        match variant {
81            0 => Ok(CallOperator::Locator(Locator::read_le(&mut reader)?)),
82            1 => Ok(CallOperator::Resource(Identifier::read_le(&mut reader)?)),
83            _ => Err(error("Failed to read CallOperator. Invalid variant.")),
84        }
85    }
86}
87
88impl<N: Network> ToBytes for CallOperator<N> {
89    /// Writes the operation to a buffer.
90    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
91        match self {
92            CallOperator::Locator(locator) => {
93                // Write the variant.
94                0u8.write_le(&mut writer)?;
95                // Write the locator.
96                locator.write_le(&mut writer)
97            }
98            CallOperator::Resource(resource) => {
99                // Write the variant.
100                1u8.write_le(&mut writer)?;
101                // Write the resource.
102                resource.write_le(&mut writer)
103            }
104        }
105    }
106}
107
108/// Calls the operands into the declared type.
109/// i.e. `call transfer r0.owner 0u64 r1.amount into r1 r2;`
110#[derive(Clone, PartialEq, Eq, Hash)]
111pub struct Call<N: Network> {
112    /// The reference.
113    operator: CallOperator<N>,
114    /// The operands.
115    operands: Vec<Operand<N>>,
116    /// The destination registers.
117    destinations: Vec<Register<N>>,
118}
119
120impl<N: Network> Call<N> {
121    /// Returns the opcode.
122    #[inline]
123    pub const fn opcode() -> Opcode {
124        Opcode::Call
125    }
126
127    /// Return the operator.
128    #[inline]
129    pub const fn operator(&self) -> &CallOperator<N> {
130        &self.operator
131    }
132
133    /// Returns the operands in the operation.
134    #[inline]
135    pub fn operands(&self) -> &[Operand<N>] {
136        &self.operands
137    }
138
139    /// Returns the destination registers.
140    #[inline]
141    pub fn destinations(&self) -> Vec<Register<N>> {
142        self.destinations.clone()
143    }
144
145    /// Returns whether this instruction refers to an external struct.
146    #[inline]
147    pub fn contains_external_struct(&self) -> bool {
148        false
149    }
150
151    /// Returns `true` if the instruction is a function call.
152    #[inline]
153    pub fn is_function_call(&self, stack: &impl StackTrait<N>) -> Result<bool> {
154        match self.operator() {
155            // Check if the locator is for a function.
156            CallOperator::Locator(locator) => {
157                // Get the external stack.
158                let external_stack = stack.get_external_stack(locator.program_id())?;
159                // Retrieve the program.
160                let program = external_stack.program();
161                // Check if the resource is a function.
162                Ok(program.contains_function(locator.resource()))
163            }
164            // Check if the resource is a function.
165            CallOperator::Resource(resource) => Ok(stack.program().contains_function(resource)),
166        }
167    }
168
169    /// Evaluates the instruction.
170    pub fn evaluate(&self, _stack: &impl StackTrait<N>, _registers: &mut impl RegistersTrait<N>) -> Result<()> {
171        bail!("Forbidden operation: Evaluate cannot invoke a 'call' directly. Use 'call' in 'Stack' instead.")
172    }
173
174    /// Executes the instruction.
175    pub fn execute<A: circuit::Aleo<Network = N>>(
176        &self,
177        _stack: &impl StackTrait<N>,
178        _registers: &mut impl RegistersCircuit<N, A>,
179    ) -> Result<()> {
180        bail!("Forbidden operation: Execute cannot invoke a 'call' directly. Use 'call' in 'Stack' instead.")
181    }
182
183    /// Finalizes the instruction.
184    #[inline]
185    pub fn finalize(&self, _stack: &impl StackTrait<N>, _registers: &mut impl RegistersTrait<N>) -> Result<()> {
186        bail!("Forbidden operation: Finalize cannot invoke a 'call' directly. Use 'call' in 'Stack' instead.")
187    }
188
189    /// Returns the output type from the given program and input types.
190    pub fn output_types(
191        &self,
192        stack: &impl StackTrait<N>,
193        input_types: &[RegisterType<N>],
194    ) -> Result<Vec<RegisterType<N>>> {
195        // Retrieve the program (external if necessary) and the name of the function or closure.
196        let stack_value;
197        let (is_external, program, name) = match &self.operator {
198            CallOperator::Locator(locator) => {
199                let program_name = locator.program_id();
200                stack_value = Some(stack.get_external_stack(program_name)?);
201                (true, stack_value.as_ref().unwrap().program(), locator.resource())
202            }
203            CallOperator::Resource(resource) => {
204                // TODO (howardwu): Revisit this decision to forbid calling internal functions. A record cannot be spent again.
205                //  But there are legitimate uses for passing a record through to an internal function.
206                //  We could invoke the internal function without a state transition, but need to match visibility.
207                if stack.program().contains_function(resource) {
208                    bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.")
209                }
210                (false, stack.program(), resource)
211            }
212        };
213
214        // If the operator is a closure, retrieve the closure and compute the output types.
215        if let Ok(closure) = program.get_closure(name) {
216            // Ensure the number of operands matches the number of input statements.
217            if closure.inputs().len() != self.operands.len() {
218                bail!("Expected {} inputs, found {}", closure.inputs().len(), self.operands.len())
219            }
220            // Ensure the number of inputs matches the number of input statements.
221            if closure.inputs().len() != input_types.len() {
222                bail!("Expected {} input types, found {}", closure.inputs().len(), input_types.len())
223            }
224            // Ensure the number of destinations matches the number of output statements.
225            if closure.outputs().len() != self.destinations.len() {
226                bail!("Expected {} outputs, found {}", closure.outputs().len(), self.destinations.len())
227            }
228            // Return the output register types.
229            Ok(closure
230                .output_types()
231                .into_iter()
232                // If the function is an external program, we need to qualify its structs with
233                // the appropriate `ProgramID`.
234                .map(|output_type| if is_external { output_type.qualify(*program.id()) } else { output_type })
235                .collect::<Vec<_>>())
236        }
237        // If the operator is a function, retrieve the function and compute the output types.
238        else if let Ok(function) = program.get_function(name) {
239            // Ensure the number of operands matches the number of input statements.
240            if function.inputs().len() != self.operands.len() {
241                bail!("Expected {} inputs, found {}", function.inputs().len(), self.operands.len())
242            }
243            // Ensure the number of inputs matches the number of input statements.
244            if function.inputs().len() != input_types.len() {
245                bail!("Expected {} input types, found {}", function.inputs().len(), input_types.len())
246            }
247            // Ensure the number of destinations matches the number of output statements.
248            if function.outputs().len() != self.destinations.len() {
249                bail!("Expected {} outputs, found {}", function.outputs().len(), self.destinations.len())
250            }
251            // Return the output register types.
252            Ok(function
253                .output_types()
254                .into_iter()
255                .map(RegisterType::from)
256                // If the function is an external program, we need to qualify its structs or records with
257                // the appropriate `ProgramID`.
258                .map(|register_type| if is_external { register_type.qualify(*program.id()) } else { register_type })
259                .collect::<Vec<_>>())
260        }
261        // Else, throw an error.
262        else {
263            bail!("Call operator '{}' is invalid or unsupported.", self.operator)
264        }
265    }
266}
267
268impl<N: Network> Parser for Call<N> {
269    /// Parses a string into an operation.
270    #[inline]
271    fn parse(string: &str) -> ParserResult<Self> {
272        /// Parses an operand from the string.
273        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
274            // Parse the whitespace from the string.
275            let (string, _) = Sanitizer::parse_whitespaces(string)?;
276            // Parse the operand from the string.
277            Operand::parse(string)
278        }
279
280        /// Parses a destination register from the string.
281        fn parse_destination<N: Network>(string: &str) -> ParserResult<Register<N>> {
282            // Parse the whitespace from the string.
283            let (string, _) = Sanitizer::parse_whitespaces(string)?;
284            // Parse the destination from the string.
285            Register::parse(string)
286        }
287
288        // Parse the opcode from the string.
289        let (string, _) = tag(*Self::opcode())(string)?;
290        // Parse the whitespace from the string.
291        let (string, _) = Sanitizer::parse_whitespaces(string)?;
292        // Parse the name of the call from the string.
293        let (string, operator) = CallOperator::parse(string)?;
294        // Parse the whitespace from the string.
295        let (string, _) = Sanitizer::parse_whitespaces(string)?;
296        // Parse the operands from the string.
297        let (string, operands) = map_res(many0(complete(parse_operand)), |operands: Vec<Operand<N>>| {
298            // Ensure the number of operands is within the bounds.
299            match operands.len() <= N::MAX_OPERANDS {
300                true => Ok(operands),
301                false => Err(error("Failed to parse 'call' opcode: too many operands")),
302            }
303        })(string)?;
304        // Parse the whitespace from the string.
305        let (string, _) = Sanitizer::parse_whitespaces(string)?;
306
307        // Optionally parse the "into" from the string.
308        let (string, destinations) = match opt(tag("into"))(string)? {
309            // If the "into" was not parsed, return the string and an empty vector of destinations.
310            (string, None) => (string, vec![]),
311            // If the "into" was parsed, parse the destinations from the string.
312            (string, Some(_)) => {
313                // Parse the whitespace from the string.
314                let (string, _) = Sanitizer::parse_whitespaces(string)?;
315                // Parse the destinations from the string.
316                let (string, destinations) =
317                    map_res(many1(complete(parse_destination)), |destinations: Vec<Register<N>>| {
318                        // Ensure the number of destinations is within the bounds.
319                        match destinations.len() <= N::MAX_OPERANDS {
320                            true => Ok(destinations),
321                            false => Err(error("Failed to parse 'call' opcode: too many destinations")),
322                        }
323                    })(string)?;
324                // Return the string and the destinations.
325                (string, destinations)
326            }
327        };
328
329        Ok((string, Self { operator, operands, destinations }))
330    }
331}
332
333impl<N: Network> FromStr for Call<N> {
334    type Err = Error;
335
336    /// Parses a string into an operation.
337    #[inline]
338    fn from_str(string: &str) -> Result<Self> {
339        match Self::parse(string) {
340            Ok((remainder, object)) => {
341                // Ensure the remainder is empty.
342                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
343                // Return the object.
344                Ok(object)
345            }
346            Err(error) => bail!("Failed to parse string. {error}"),
347        }
348    }
349}
350
351impl<N: Network> Debug for Call<N> {
352    /// Prints the operation as a string.
353    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
354        Display::fmt(self, f)
355    }
356}
357
358impl<N: Network> Display for Call<N> {
359    /// Prints the operation to a string.
360    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
361        // Ensure the number of operands is within the bounds.
362        if self.operands.len() > N::MAX_OPERANDS {
363            return Err(fmt::Error);
364        }
365        // Ensure the number of destinations is within the bounds.
366        if self.destinations.len() > N::MAX_OPERANDS {
367            return Err(fmt::Error);
368        }
369        // Print the operation.
370        write!(f, "{} {}", Self::opcode(), self.operator)?;
371        self.operands.iter().try_for_each(|operand| write!(f, " {operand}"))?;
372        if !self.destinations.is_empty() {
373            write!(f, " into")?;
374            self.destinations.iter().try_for_each(|destination| write!(f, " {destination}"))?;
375        }
376        Ok(())
377    }
378}
379
380impl<N: Network> FromBytes for Call<N> {
381    /// Reads the operation from a buffer.
382    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
383        // Read the operator of the call.
384        let operator = CallOperator::read_le(&mut reader)?;
385
386        // Read the number of operands.
387        let num_operands = u8::read_le(&mut reader)? as usize;
388        // Ensure the number of operands is within the bounds.
389        if num_operands > N::MAX_OPERANDS {
390            return Err(error(format!("The number of operands must be <= {}", N::MAX_OPERANDS)));
391        }
392
393        // Initialize the vector for the operands.
394        let mut operands = Vec::with_capacity(num_operands);
395        // Read the operands.
396        for _ in 0..num_operands {
397            operands.push(Operand::read_le(&mut reader)?);
398        }
399
400        // Read the number of destination registers.
401        let num_destinations = u8::read_le(&mut reader)? as usize;
402        // Ensure the number of destinations is within the bounds.
403        if num_destinations > N::MAX_OPERANDS {
404            return Err(error(format!("The number of destinations must be <= {}", N::MAX_OPERANDS)));
405        }
406
407        // Initialize the vector for the destinations.
408        let mut destinations = Vec::with_capacity(num_destinations);
409        // Read the destination registers.
410        for _ in 0..num_destinations {
411            destinations.push(Register::read_le(&mut reader)?);
412        }
413
414        // Return the operation.
415        Ok(Self { operator, operands, destinations })
416    }
417}
418
419impl<N: Network> ToBytes for Call<N> {
420    /// Writes the operation to a buffer.
421    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
422        // Ensure the number of operands is within the bounds.
423        if self.operands.len() > N::MAX_OPERANDS {
424            return Err(error(format!("The number of operands must be <= {}", N::MAX_OPERANDS)));
425        }
426        // Ensure the number of destinations is within the bounds.
427        if self.destinations.len() > N::MAX_OPERANDS {
428            return Err(error(format!("The number of destinations must be <= {}", N::MAX_OPERANDS)));
429        }
430
431        // Write the name of the call.
432        self.operator.write_le(&mut writer)?;
433        // Write the number of operands.
434        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
435        // Write the operands.
436        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
437        // Write the number of destination register.
438        u8::try_from(self.destinations.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
439        // Write the destination registers.
440        self.destinations.iter().try_for_each(|destination| destination.write_le(&mut writer))
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use console::{
448        network::MainnetV0,
449        program::{Access, Address, Identifier, Literal, U64},
450    };
451
452    type CurrentNetwork = MainnetV0;
453
454    const TEST_CASES: &[&str] = &[
455        "call foo",
456        "call foo r0",
457        "call foo r0.owner",
458        "call foo r0 r1",
459        "call foo into r0",
460        "call foo into r0 r1",
461        "call foo into r0 r1 r2",
462        "call foo r0 into r1",
463        "call foo r0 r1 into r2",
464        "call foo r0 r1 into r2 r3",
465        "call foo r0 r1 r2 into r3 r4",
466        "call foo r0 r1 r2 into r3 r4 r5",
467    ];
468
469    fn check_parser(
470        string: &str,
471        expected_operator: CallOperator<CurrentNetwork>,
472        expected_operands: Vec<Operand<CurrentNetwork>>,
473        expected_destinations: Vec<Register<CurrentNetwork>>,
474    ) {
475        // Check that the parser works.
476        let (string, call) = Call::<CurrentNetwork>::parse(string).unwrap();
477
478        // Check that the entire string was consumed.
479        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
480
481        // Check that the operator is correct.
482        assert_eq!(call.operator, expected_operator, "The call operator is incorrect");
483
484        // Check that the operands are correct.
485        assert_eq!(call.operands.len(), expected_operands.len(), "The number of operands is incorrect");
486        for (i, (given, expected)) in call.operands.iter().zip(expected_operands.iter()).enumerate() {
487            assert_eq!(given, expected, "The {i}-th operand is incorrect");
488        }
489
490        // Check that the destinations are correct.
491        assert_eq!(call.destinations.len(), expected_destinations.len(), "The number of destinations is incorrect");
492        for (i, (given, expected)) in call.destinations.iter().zip(expected_destinations.iter()).enumerate() {
493            assert_eq!(given, expected, "The {i}-th destination is incorrect");
494        }
495    }
496
497    #[test]
498    fn test_parse() {
499        check_parser(
500            "call transfer r0.owner r0.token_amount into r1 r2 r3",
501            CallOperator::from_str("transfer").unwrap(),
502            vec![
503                Operand::Register(Register::Access(0, vec![Access::from(Identifier::from_str("owner").unwrap())])),
504                Operand::Register(Register::Access(0, vec![Access::from(
505                    Identifier::from_str("token_amount").unwrap(),
506                )])),
507            ],
508            vec![Register::Locator(1), Register::Locator(2), Register::Locator(3)],
509        );
510
511        check_parser(
512            "call mint_public aleo1wfyyj2uvwuqw0c0dqa5x70wrawnlkkvuepn4y08xyaqfqqwweqys39jayw 100u64",
513            CallOperator::from_str("mint_public").unwrap(),
514            vec![
515                Operand::Literal(Literal::Address(
516                    Address::from_str("aleo1wfyyj2uvwuqw0c0dqa5x70wrawnlkkvuepn4y08xyaqfqqwweqys39jayw").unwrap(),
517                )),
518                Operand::Literal(Literal::U64(U64::from_str("100u64").unwrap())),
519            ],
520            vec![],
521        );
522
523        check_parser(
524            "call get_magic_number into r0",
525            CallOperator::from_str("get_magic_number").unwrap(),
526            vec![],
527            vec![Register::Locator(0)],
528        );
529
530        check_parser("call noop", CallOperator::from_str("noop").unwrap(), vec![], vec![])
531    }
532
533    #[test]
534    fn test_display() {
535        for expected in TEST_CASES {
536            assert_eq!(Call::<CurrentNetwork>::from_str(expected).unwrap().to_string(), *expected);
537        }
538    }
539
540    #[test]
541    fn test_bytes() {
542        for case in TEST_CASES {
543            let expected = Call::<CurrentNetwork>::from_str(case).unwrap();
544
545            // Check the byte representation.
546            let expected_bytes = expected.to_bytes_le().unwrap();
547            assert_eq!(expected, Call::read_le(&expected_bytes[..]).unwrap());
548        }
549    }
550}