Skip to main content

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