snarkvm_synthesizer_program/logic/instruction/operation/
cast.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::{
17    Opcode,
18    Operand,
19    traits::{
20        RegistersLoad,
21        RegistersLoadCircuit,
22        RegistersSigner,
23        RegistersSignerCircuit,
24        RegistersStore,
25        RegistersStoreCircuit,
26        StackMatches,
27        StackProgram,
28    },
29};
30use console::{
31    network::prelude::*,
32    program::{
33        ArrayType,
34        Entry,
35        EntryType,
36        Identifier,
37        Literal,
38        LiteralType,
39        Locator,
40        Owner,
41        Plaintext,
42        PlaintextType,
43        Record,
44        Register,
45        RegisterType,
46        Value,
47        ValueType,
48    },
49    types::Field,
50};
51
52use indexmap::IndexMap;
53
54#[derive(Clone, PartialEq, Eq, Hash)]
55/// The type of the cast operation.
56pub enum CastType<N: Network> {
57    GroupXCoordinate,
58    GroupYCoordinate,
59    Plaintext(PlaintextType<N>),
60    Record(Identifier<N>),
61    ExternalRecord(Locator<N>),
62}
63
64impl<N: Network> Parser for CastType<N> {
65    fn parse(string: &str) -> ParserResult<Self> {
66        // Parse the cast type from the string.
67        alt((
68            map(tag("group.x"), |_| Self::GroupXCoordinate),
69            map(tag("group.y"), |_| Self::GroupYCoordinate),
70            map(pair(Locator::parse, tag(".record")), |(locator, _)| Self::ExternalRecord(locator)),
71            map(pair(Identifier::parse, tag(".record")), |(identifier, _)| Self::Record(identifier)),
72            map(PlaintextType::parse, Self::Plaintext),
73        ))(string)
74    }
75}
76
77impl<N: Network> Display for CastType<N> {
78    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
79        match self {
80            Self::GroupXCoordinate => write!(f, "group.x"),
81            Self::GroupYCoordinate => write!(f, "group.y"),
82            Self::Plaintext(plaintext_type) => write!(f, "{}", plaintext_type),
83            Self::Record(identifier) => write!(f, "{}.record", identifier),
84            Self::ExternalRecord(locator) => write!(f, "{}.record", locator),
85        }
86    }
87}
88
89impl<N: Network> Debug for CastType<N> {
90    /// Prints the cast type as a string.
91    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
92        Display::fmt(self, f)
93    }
94}
95
96impl<N: Network> FromStr for CastType<N> {
97    type Err = Error;
98
99    /// Returns a cast type from a string literal.
100    fn from_str(string: &str) -> Result<Self> {
101        match Self::parse(string) {
102            Ok((remainder, object)) => {
103                // Ensure the remainder is empty.
104                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
105                // Return the object.
106                Ok(object)
107            }
108            Err(error) => bail!("Failed to parse string. {error}"),
109        }
110    }
111}
112
113impl<N: Network> ToBytes for CastType<N> {
114    /// Writes the cast type to a buffer.
115    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
116        match self {
117            Self::GroupXCoordinate => 0u8.write_le(&mut writer),
118            Self::GroupYCoordinate => 1u8.write_le(&mut writer),
119            CastType::Plaintext(plaintext_type) => {
120                2u8.write_le(&mut writer)?;
121                plaintext_type.write_le(&mut writer)
122            }
123            CastType::Record(identifier) => {
124                3u8.write_le(&mut writer)?;
125                identifier.write_le(&mut writer)
126            }
127            CastType::ExternalRecord(locator) => {
128                4u8.write_le(&mut writer)?;
129                locator.write_le(&mut writer)
130            }
131        }
132    }
133}
134
135impl<N: Network> FromBytes for CastType<N> {
136    /// Reads the cast type from a buffer.
137    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
138        let variant = u8::read_le(&mut reader)?;
139        match variant {
140            0 => Ok(Self::GroupXCoordinate),
141            1 => Ok(Self::GroupYCoordinate),
142            2 => Ok(Self::Plaintext(PlaintextType::read_le(&mut reader)?)),
143            3 => Ok(Self::Record(Identifier::read_le(&mut reader)?)),
144            4 => Ok(Self::ExternalRecord(Locator::read_le(&mut reader)?)),
145            5.. => Err(error(format!("Failed to deserialize cast type variant {variant}"))),
146        }
147    }
148}
149
150/// The `cast` instruction.
151pub type Cast<N> = CastOperation<N, { CastVariant::Cast as u8 }>;
152/// The `cast.lossy` instruction.
153pub type CastLossy<N> = CastOperation<N, { CastVariant::CastLossy as u8 }>;
154
155/// The variant of the cast operation.
156enum CastVariant {
157    Cast,
158    CastLossy,
159}
160
161/// Casts the operands into the declared type.
162#[derive(Clone, PartialEq, Eq, Hash)]
163pub struct CastOperation<N: Network, const VARIANT: u8> {
164    /// The operands.
165    operands: Vec<Operand<N>>,
166    /// The destination register.
167    destination: Register<N>,
168    /// The cast type.
169    cast_type: CastType<N>,
170}
171
172impl<N: Network, const VARIANT: u8> CastOperation<N, VARIANT> {
173    /// Returns the opcode.
174    #[inline]
175    pub const fn opcode() -> Opcode {
176        Opcode::Cast(match VARIANT {
177            0 => "cast",
178            1 => "cast.lossy",
179            2.. => panic!("Invalid cast variant"),
180        })
181    }
182
183    /// Returns the operands in the operation.
184    #[inline]
185    pub fn operands(&self) -> &[Operand<N>] {
186        &self.operands
187    }
188
189    /// Returns the destination register.
190    #[inline]
191    pub fn destinations(&self) -> Vec<Register<N>> {
192        vec![self.destination.clone()]
193    }
194
195    /// Returns the cast type.
196    #[inline]
197    pub const fn cast_type(&self) -> &CastType<N> {
198        &self.cast_type
199    }
200}
201
202impl<N: Network, const VARIANT: u8> CastOperation<N, VARIANT> {
203    /// Evaluates the instruction.
204    #[inline]
205    pub fn evaluate(
206        &self,
207        stack: &(impl StackMatches<N> + StackProgram<N>),
208        registers: &mut (impl RegistersSigner<N> + RegistersLoad<N> + RegistersStore<N>),
209    ) -> Result<()> {
210        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
211        if VARIANT == CastVariant::CastLossy as u8 {
212            ensure!(
213                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
214                "`cast.lossy` is only supported for casting to a literal type"
215            )
216        }
217
218        // Load the operands values.
219        let inputs: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
220
221        match &self.cast_type {
222            CastType::GroupXCoordinate => {
223                ensure!(inputs.len() == 1, "Casting to a group x-coordinate requires exactly 1 operand");
224                let field = match &inputs[0] {
225                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_x_coordinate(),
226                    _ => bail!("Casting to a group x-coordinate requires a group element"),
227                };
228                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
229            }
230            CastType::GroupYCoordinate => {
231                ensure!(inputs.len() == 1, "Casting to a group y-coordinate requires exactly 1 operand");
232                let field = match &inputs[0] {
233                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_y_coordinate(),
234                    _ => bail!("Casting to a group y-coordinate requires a group element"),
235                };
236                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
237            }
238            CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
239                ensure!(inputs.len() == 1, "Casting to a literal requires exactly 1 operand");
240                let value = match &inputs[0] {
241                    Value::Plaintext(Plaintext::Literal(literal, ..)) => match VARIANT {
242                        0 => literal.cast(*literal_type)?,
243                        1 => literal.cast_lossy(*literal_type)?,
244                        2.. => unreachable!("Invalid cast variant"),
245                    },
246                    _ => bail!("Casting to a literal requires a literal"),
247                };
248                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(value)))
249            }
250            CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
251                self.cast_to_struct(stack, registers, *struct_name, inputs)
252            }
253            CastType::Plaintext(PlaintextType::Array(array_type)) => {
254                self.cast_to_array(stack, registers, array_type, inputs)
255            }
256            CastType::Record(record_name) => {
257                // Ensure the operands length is at least the minimum.
258                if inputs.len() < N::MIN_RECORD_ENTRIES {
259                    bail!("Casting to a record requires at least {} operand", N::MIN_RECORD_ENTRIES)
260                }
261
262                // Retrieve the struct and ensure it is defined in the program.
263                let record_type = stack.program().get_record(record_name)?;
264
265                // Ensure that the number of operands is equal to the number of record entries, including the `owner`.
266                if inputs.len() != record_type.entries().len() + 1 {
267                    bail!(
268                        "Casting to the record {} requires {} operands, but {} were provided",
269                        record_type.name(),
270                        record_type.entries().len() + 1,
271                        inputs.len()
272                    )
273                }
274
275                // Initialize the record owner.
276                let owner: Owner<N, Plaintext<N>> = match &inputs[0] {
277                    // Ensure the entry is an address.
278                    Value::Plaintext(Plaintext::Literal(Literal::Address(owner), ..)) => {
279                        match record_type.owner().is_public() {
280                            true => Owner::Public(*owner),
281                            false => Owner::Private(Plaintext::Literal(Literal::Address(*owner), Default::default())),
282                        }
283                    }
284                    _ => bail!("Invalid record 'owner'"),
285                };
286
287                // Initialize the record entries.
288                let mut entries = IndexMap::new();
289                for (entry, (entry_name, entry_type)) in
290                    inputs.iter().skip(N::MIN_RECORD_ENTRIES).zip_eq(record_type.entries())
291                {
292                    // Compute the plaintext type.
293                    let plaintext_type = entry_type.plaintext_type();
294                    // Retrieve the plaintext value from the entry.
295                    let plaintext = match entry {
296                        Value::Plaintext(plaintext) => {
297                            // Ensure the entry matches the register type.
298                            stack.matches_plaintext(plaintext, plaintext_type)?;
299                            // Output the plaintext.
300                            plaintext.clone()
301                        }
302                        // Ensure the record entry is not a record.
303                        Value::Record(..) => bail!("Casting a record into a record entry is illegal"),
304                        // Ensure the record entry is not a future.
305                        Value::Future(..) => bail!("Casting a future into a record entry is illegal"),
306                    };
307                    // Append the entry to the record entries.
308                    match entry_type {
309                        EntryType::Constant(..) => entries.insert(*entry_name, Entry::Constant(plaintext)),
310                        EntryType::Public(..) => entries.insert(*entry_name, Entry::Public(plaintext)),
311                        EntryType::Private(..) => entries.insert(*entry_name, Entry::Private(plaintext)),
312                    };
313                }
314
315                // Prepare the index as a field element.
316                let index = Field::from_u64(self.destination.locator());
317                // Compute the randomizer as `HashToScalar(tvk || index)`.
318                let randomizer = N::hash_to_scalar_psd2(&[registers.tvk()?, index])?;
319                // Compute the nonce from the randomizer.
320                let nonce = N::g_scalar_multiply(&randomizer);
321
322                // Construct the record.
323                let record = Record::<N, Plaintext<N>>::from_plaintext(owner, entries, nonce)?;
324                // Store the record.
325                registers.store(stack, &self.destination, Value::Record(record))
326            }
327            CastType::ExternalRecord(_locator) => {
328                bail!("Illegal operation: Cannot cast to an external record.")
329            }
330        }
331    }
332
333    /// Executes the instruction.
334    #[inline]
335    pub fn execute<A: circuit::Aleo<Network = N>>(
336        &self,
337        stack: &(impl StackMatches<N> + StackProgram<N>),
338        registers: &mut (impl RegistersSignerCircuit<N, A> + RegistersLoadCircuit<N, A> + RegistersStoreCircuit<N, A>),
339    ) -> Result<()> {
340        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
341        if VARIANT == CastVariant::CastLossy as u8 {
342            ensure!(
343                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
344                "`cast.lossy` is only supported for casting to a literal type"
345            )
346        }
347
348        use circuit::{Eject, Inject};
349
350        // Load the operands values.
351        let inputs: Vec<_> =
352            self.operands.iter().map(|operand| registers.load_circuit(stack, operand)).try_collect()?;
353
354        match &self.cast_type {
355            CastType::GroupXCoordinate => {
356                ensure!(inputs.len() == 1, "Casting to a group x-coordinate requires exactly 1 operand");
357                let field = match &inputs[0] {
358                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Group(group), ..)) => {
359                        group.to_x_coordinate()
360                    }
361                    _ => bail!("Casting to a group x-coordinate requires a group element"),
362                };
363                registers.store_circuit(
364                    stack,
365                    &self.destination,
366                    circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Field(field))),
367                )
368            }
369            CastType::GroupYCoordinate => {
370                ensure!(inputs.len() == 1, "Casting to a group y-coordinate requires exactly 1 operand");
371                let field = match &inputs[0] {
372                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Group(group), ..)) => {
373                        group.to_y_coordinate()
374                    }
375                    _ => bail!("Casting to a group y-coordinate requires a group element"),
376                };
377                registers.store_circuit(
378                    stack,
379                    &self.destination,
380                    circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Field(field))),
381                )
382            }
383            CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
384                ensure!(inputs.len() == 1, "Casting to a literal requires exactly 1 operand");
385                let value = match &inputs[0] {
386                    circuit::Value::Plaintext(circuit::Plaintext::Literal(literal, ..)) => match VARIANT {
387                        0 => literal.cast(*literal_type)?,
388                        1 => literal.cast_lossy(*literal_type)?,
389                        2.. => unreachable!("Invalid cast variant"),
390                    },
391                    _ => bail!("Casting to a literal requires a literal"),
392                };
393                registers.store_circuit(
394                    stack,
395                    &self.destination,
396                    circuit::Value::Plaintext(circuit::Plaintext::from(value)),
397                )
398            }
399            CastType::Plaintext(PlaintextType::Struct(struct_)) => {
400                // Ensure the operands length is at least the minimum.
401                if inputs.len() < N::MIN_STRUCT_ENTRIES {
402                    bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES)
403                }
404                // Ensure the number of members does not exceed the maximum.
405                if inputs.len() > N::MAX_STRUCT_ENTRIES {
406                    bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES)
407                }
408
409                // Retrieve the struct and ensure it is defined in the program.
410                let struct_ = stack.program().get_struct(struct_)?;
411
412                // Ensure that the number of operands is equal to the number of struct members.
413                if inputs.len() != struct_.members().len() {
414                    bail!(
415                        "Casting to the struct {} requires {} operands, but {} were provided",
416                        struct_.name(),
417                        struct_.members().len(),
418                        inputs.len()
419                    )
420                }
421
422                // Initialize the struct members.
423                let mut members = IndexMap::new();
424                for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) {
425                    // Retrieve the plaintext value from the entry.
426                    let plaintext = match member {
427                        circuit::Value::Plaintext(plaintext) => {
428                            // Ensure the member matches the register type.
429                            stack.matches_plaintext(&plaintext.eject_value(), member_type)?;
430                            // Output the plaintext.
431                            plaintext.clone()
432                        }
433                        // Ensure the struct member is not a record.
434                        circuit::Value::Record(..) => {
435                            bail!("Casting a record into a struct member is illegal")
436                        }
437                        // Ensure the struct member is not a future.
438                        circuit::Value::Future(..) => {
439                            bail!("Casting a future into a struct member is illegal")
440                        }
441                    };
442                    // Append the member to the struct members.
443                    members.insert(circuit::Identifier::constant(*member_name), plaintext);
444                }
445
446                // Construct the struct.
447                let struct_ = circuit::Plaintext::Struct(members, Default::default());
448                // Store the struct.
449                registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(struct_))
450            }
451            CastType::Plaintext(PlaintextType::Array(array_type)) => {
452                // Ensure the operands length is at least the minimum.
453                if inputs.len() < N::MIN_ARRAY_ELEMENTS {
454                    bail!("Casting to an array requires at least {} operand(s)", N::MIN_ARRAY_ELEMENTS)
455                }
456                // Ensure the number of elements does not exceed the maximum.
457                if inputs.len() > N::MAX_ARRAY_ELEMENTS {
458                    bail!("Casting to array '{array_type}' cannot exceed {} elements", N::MAX_ARRAY_ELEMENTS)
459                }
460
461                // Ensure that the number of operands is equal to the number of array entries.
462                if inputs.len() != **array_type.length() as usize {
463                    bail!(
464                        "Casting to the array {} requires {} operands, but {} were provided",
465                        array_type,
466                        array_type.length(),
467                        inputs.len()
468                    )
469                }
470
471                // Initialize the elements.
472                let mut elements = Vec::with_capacity(inputs.len());
473                for element in inputs.iter() {
474                    // Retrieve the plaintext value from the element.
475                    let plaintext = match element {
476                        circuit::Value::Plaintext(plaintext) => {
477                            // Ensure the plaintext matches the element type.
478                            stack.matches_plaintext(&plaintext.eject_value(), array_type.next_element_type())?;
479                            // Output the plaintext.
480                            plaintext.clone()
481                        }
482                        // Ensure the element is not a record.
483                        circuit::Value::Record(..) => bail!("Casting a record into an array element is illegal"),
484                        // Ensure the element is not a future.
485                        circuit::Value::Future(..) => bail!("Casting a future into an array element is illegal"),
486                    };
487                    // Store the element.
488                    elements.push(plaintext);
489                }
490
491                // Construct the array.
492                let array = circuit::Plaintext::Array(elements, Default::default());
493                // Store the array.
494                registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(array))
495            }
496            CastType::Record(record_name) => {
497                // Ensure the operands length is at least the minimum.
498                if inputs.len() < N::MIN_RECORD_ENTRIES {
499                    bail!("Casting to a record requires at least {} operand(s)", N::MIN_RECORD_ENTRIES)
500                }
501                // Ensure the number of entries does not exceed the maximum.
502                if inputs.len() > N::MAX_RECORD_ENTRIES {
503                    bail!("Casting to record '{record_name}' cannot exceed {} members", N::MAX_RECORD_ENTRIES)
504                }
505
506                // Retrieve the struct and ensure it is defined in the program.
507                let record_type = stack.program().get_record(record_name)?;
508
509                // Ensure that the number of operands is equal to the number of record entries, including the `owner`.
510                if inputs.len() != record_type.entries().len() + 1 {
511                    bail!(
512                        "Casting to the record {} requires {} operands, but {} were provided",
513                        record_type.name(),
514                        record_type.entries().len() + 1,
515                        inputs.len()
516                    )
517                }
518
519                // Initialize the record owner.
520                let owner: circuit::Owner<A, circuit::Plaintext<A>> = match &inputs[0] {
521                    // Ensure the entry is an address.
522                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Address(owner), ..)) => {
523                        match record_type.owner().is_public() {
524                            true => circuit::Owner::Public(owner.clone()),
525                            false => circuit::Owner::Private(circuit::Plaintext::Literal(
526                                circuit::Literal::Address(owner.clone()),
527                                Default::default(),
528                            )),
529                        }
530                    }
531                    _ => bail!("Invalid record 'owner'"),
532                };
533
534                // Initialize the record entries.
535                let mut entries = IndexMap::new();
536                for (entry, (entry_name, entry_type)) in
537                    inputs.iter().skip(N::MIN_RECORD_ENTRIES).zip_eq(record_type.entries())
538                {
539                    // Compute the register type.
540                    let register_type = RegisterType::from(ValueType::from(entry_type.clone()));
541                    // Retrieve the plaintext value from the entry.
542                    let plaintext = match entry {
543                        circuit::Value::Plaintext(plaintext) => {
544                            // Ensure the entry matches the register type.
545                            stack.matches_register_type(
546                                &circuit::Value::Plaintext(plaintext.clone()).eject_value(),
547                                &register_type,
548                            )?;
549                            // Output the plaintext.
550                            plaintext.clone()
551                        }
552                        // Ensure the record entry is not a record.
553                        circuit::Value::Record(..) => bail!("Casting a record into a record entry is illegal"),
554                        // Ensure the record entry is not a future.
555                        circuit::Value::Future(..) => bail!("Casting a future into a record entry is illegal"),
556                    };
557                    // Construct the entry name constant circuit.
558                    let entry_name = circuit::Identifier::constant(*entry_name);
559                    // Append the entry to the record entries.
560                    match entry_type {
561                        EntryType::Constant(..) => entries.insert(entry_name, circuit::Entry::Constant(plaintext)),
562                        EntryType::Public(..) => entries.insert(entry_name, circuit::Entry::Public(plaintext)),
563                        EntryType::Private(..) => entries.insert(entry_name, circuit::Entry::Private(plaintext)),
564                    };
565                }
566
567                // Prepare the index as a constant field element.
568                let index = circuit::Field::constant(Field::from_u64(self.destination.locator()));
569                // Compute the randomizer as `HashToScalar(tvk || index)`.
570                let randomizer = A::hash_to_scalar_psd2(&[registers.tvk_circuit()?, index]);
571                // Compute the nonce from the randomizer.
572                let nonce = A::g_scalar_multiply(&randomizer);
573
574                // Construct the record.
575                let record = circuit::Record::<A, circuit::Plaintext<A>>::from_plaintext(owner, entries, nonce)?;
576                // Store the record.
577                registers.store_circuit(stack, &self.destination, circuit::Value::Record(record))
578            }
579            CastType::ExternalRecord(_locator) => {
580                bail!("Illegal operation: Cannot cast to an external record.")
581            }
582        }
583    }
584
585    /// Finalizes the instruction.
586    #[inline]
587    pub fn finalize(
588        &self,
589        stack: &(impl StackMatches<N> + StackProgram<N>),
590        registers: &mut (impl RegistersLoad<N> + RegistersStore<N>),
591    ) -> Result<()> {
592        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
593        if VARIANT == CastVariant::CastLossy as u8 {
594            ensure!(
595                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
596                "`cast.lossy` is only supported for casting to a literal type"
597            )
598        }
599
600        // Load the operands values.
601        let inputs: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
602
603        match &self.cast_type {
604            CastType::GroupXCoordinate => {
605                ensure!(inputs.len() == 1, "Casting to a group x-coordinate requires exactly 1 operand");
606                let field = match &inputs[0] {
607                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_x_coordinate(),
608                    _ => bail!("Casting to a group x-coordinate requires a group element"),
609                };
610                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
611            }
612            CastType::GroupYCoordinate => {
613                ensure!(inputs.len() == 1, "Casting to a group y-coordinate requires exactly 1 operand");
614                let field = match &inputs[0] {
615                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_y_coordinate(),
616                    _ => bail!("Casting to a group y-coordinate requires a group element"),
617                };
618                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
619            }
620            CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
621                ensure!(inputs.len() == 1, "Casting to a literal requires exactly 1 operand");
622                let value = match &inputs[0] {
623                    Value::Plaintext(Plaintext::Literal(literal, ..)) => match VARIANT {
624                        0 => literal.cast(*literal_type)?,
625                        1 => literal.cast_lossy(*literal_type)?,
626                        2.. => unreachable!("Invalid cast variant"),
627                    },
628                    _ => bail!("Casting to a literal requires a literal"),
629                };
630                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(value)))
631            }
632            CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
633                self.cast_to_struct(stack, registers, *struct_name, inputs)
634            }
635            CastType::Plaintext(PlaintextType::Array(array_type)) => {
636                self.cast_to_array(stack, registers, array_type, inputs)
637            }
638            CastType::Record(_record_name) => {
639                bail!("Illegal operation: Cannot cast to a record in a finalize block.")
640            }
641            CastType::ExternalRecord(_locator) => {
642                bail!("Illegal operation: Cannot cast to an external record.")
643            }
644        }
645    }
646
647    /// Returns the output type from the given program and input types.
648    #[inline]
649    pub fn output_types(
650        &self,
651        stack: &impl StackProgram<N>,
652        input_types: &[RegisterType<N>],
653    ) -> Result<Vec<RegisterType<N>>> {
654        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
655        if VARIANT == CastVariant::CastLossy as u8 {
656            ensure!(
657                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
658                "`cast.lossy` is only supported for casting to a literal type"
659            )
660        }
661
662        // Ensure the number of operands is correct.
663        ensure!(
664            input_types.len() == self.operands.len(),
665            "Instruction '{}' expects {} operands, found {} operands",
666            Self::opcode(),
667            input_types.len(),
668            self.operands.len(),
669        );
670
671        // Ensure the output type is defined in the program.
672        match &self.cast_type {
673            CastType::GroupXCoordinate | CastType::GroupYCoordinate => {
674                ensure!(input_types.len() == 1, "Casting to a group coordinate requires exactly 1 operand");
675                ensure!(
676                    matches!(input_types[0], RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Group))),
677                    "Type mismatch: expected 'group', found '{}'",
678                    input_types[0]
679                );
680            }
681            CastType::Plaintext(PlaintextType::Literal(..)) => {
682                ensure!(input_types.len() == 1, "Casting to a literal requires exactly 1 operand");
683            }
684            CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
685                // Retrieve the struct and ensure it is defined in the program.
686                let struct_ = stack.program().get_struct(struct_name)?;
687
688                // Ensure the input types length is at least the minimum.
689                if input_types.len() < N::MIN_STRUCT_ENTRIES {
690                    bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES)
691                }
692                // Ensure the number of members does not exceed the maximum.
693                if input_types.len() > N::MAX_STRUCT_ENTRIES {
694                    bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES)
695                }
696
697                // Ensure that the number of input types is equal to the number of struct members.
698                ensure!(
699                    input_types.len() == struct_.members().len(),
700                    "Casting to the struct {} requires {} operands, but {} were provided",
701                    struct_.name(),
702                    struct_.members().len(),
703                    input_types.len()
704                );
705                // Ensure the input types match the struct.
706                for ((_, member_type), input_type) in struct_.members().iter().zip_eq(input_types) {
707                    match input_type {
708                        // Ensure the plaintext type matches the member type.
709                        RegisterType::Plaintext(plaintext_type) => {
710                            ensure!(
711                                member_type == plaintext_type,
712                                "Struct '{struct_name}' member type mismatch: expected '{member_type}', found '{plaintext_type}'"
713                            )
714                        }
715                        // Ensure the input type cannot be a record (this is unsupported behavior).
716                        RegisterType::Record(record_name) => bail!(
717                            "Struct '{struct_name}' member type mismatch: expected '{member_type}', found record '{record_name}'"
718                        ),
719                        // Ensure the input type cannot be an external record (this is unsupported behavior).
720                        RegisterType::ExternalRecord(locator) => bail!(
721                            "Struct '{struct_name}' member type mismatch: expected '{member_type}', found external record '{locator}'"
722                        ),
723                        // Ensure the input type cannot be a future (this is unsupported behavior).
724                        RegisterType::Future(..) => {
725                            bail!("Struct '{struct_name}' member type mismatch: expected '{member_type}', found future")
726                        }
727                    }
728                }
729            }
730            CastType::Plaintext(PlaintextType::Array(array_type)) => {
731                // Ensure the input types length is at least the minimum.
732                if input_types.len() < N::MIN_ARRAY_ELEMENTS {
733                    bail!("Casting to an array requires at least {} operand(s)", N::MIN_ARRAY_ELEMENTS)
734                }
735                // Ensure the number of elements does not exceed the maximum.
736                if input_types.len() > N::MAX_ARRAY_ELEMENTS {
737                    bail!("Casting to array '{array_type}' cannot exceed {} elements", N::MAX_ARRAY_ELEMENTS)
738                }
739
740                // Ensure that the number of input types is equal to the number of array entries.
741                if input_types.len() != **array_type.length() as usize {
742                    bail!(
743                        "Casting to the array {} requires {} operands, but {} were provided",
744                        array_type,
745                        array_type.length(),
746                        input_types.len()
747                    )
748                }
749
750                // Ensure the input types match the element type.
751                for input_type in input_types {
752                    match input_type {
753                        // Ensure the plaintext type matches the member type.
754                        RegisterType::Plaintext(plaintext_type) => {
755                            ensure!(
756                                plaintext_type == array_type.next_element_type(),
757                                "Array element type mismatch: expected '{}', found '{plaintext_type}'",
758                                array_type.next_element_type()
759                            )
760                        }
761                        // Ensure the input type cannot be a record (this is unsupported behavior).
762                        RegisterType::Record(record_name) => bail!(
763                            "Array element type mismatch: expected '{}', found record '{record_name}'",
764                            array_type.next_element_type()
765                        ),
766                        // Ensure the input type cannot be an external record (this is unsupported behavior).
767                        RegisterType::ExternalRecord(locator) => bail!(
768                            "Array element type mismatch: expected '{}', found external record '{locator}'",
769                            array_type.next_element_type()
770                        ),
771                        // Ensure the input type cannot be a future (this is unsupported behavior).
772                        RegisterType::Future(..) => bail!(
773                            "Array element type mismatch: expected '{}', found future",
774                            array_type.next_element_type()
775                        ),
776                    }
777                }
778            }
779            CastType::Record(record_name) => {
780                // Retrieve the record type and ensure is defined in the program.
781                let record = stack.program().get_record(record_name)?;
782
783                // Ensure the input types length is at least the minimum.
784                if input_types.len() < N::MIN_RECORD_ENTRIES {
785                    bail!("Casting to a record requires at least {} operand(s)", N::MIN_RECORD_ENTRIES)
786                }
787                // Ensure the number of entries does not exceed the maximum.
788                if input_types.len() > N::MAX_RECORD_ENTRIES {
789                    bail!("Casting to record '{record_name}' cannot exceed {} members", N::MAX_RECORD_ENTRIES)
790                }
791
792                // Ensure that the number of input types is equal to the number of record entries, including the `owner`.
793                ensure!(
794                    input_types.len() == record.entries().len() + 1,
795                    "Casting to the record {} requires {} operands, but {} were provided",
796                    record.name(),
797                    record.entries().len() + 1,
798                    input_types.len()
799                );
800                // Ensure the first input type is an address.
801                ensure!(
802                    input_types[0] == RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Address)),
803                    "Casting to a record requires the first operand to be an address"
804                );
805
806                // Ensure the input types match the record.
807                for (input_type, (_, entry_type)) in
808                    input_types.iter().skip(N::MIN_RECORD_ENTRIES).zip_eq(record.entries())
809                {
810                    match input_type {
811                        // Ensure the plaintext type matches the entry type.
812                        RegisterType::Plaintext(plaintext_type) => match entry_type {
813                            EntryType::Constant(entry_type)
814                            | EntryType::Public(entry_type)
815                            | EntryType::Private(entry_type) => {
816                                ensure!(
817                                    entry_type == plaintext_type,
818                                    "Record '{record_name}' entry type mismatch: expected '{entry_type}', found '{plaintext_type}'"
819                                )
820                            }
821                        },
822                        // Ensure the input type cannot be a record (this is unsupported behavior).
823                        RegisterType::Record(record_name) => bail!(
824                            "Record '{record_name}' entry type mismatch: expected '{entry_type}', found record '{record_name}'"
825                        ),
826                        // Ensure the input type cannot be an external record (this is unsupported behavior).
827                        RegisterType::ExternalRecord(locator) => bail!(
828                            "Record '{record_name}' entry type mismatch: expected '{entry_type}', found external record '{locator}'"
829                        ),
830                        // Ensure the input type cannot be a future (this is unsupported behavior).
831                        RegisterType::Future(..) => {
832                            bail!("Record '{record_name}' entry type mismatch: expected '{entry_type}', found future",)
833                        }
834                    }
835                }
836            }
837            CastType::ExternalRecord(_locator) => {
838                bail!("Illegal operation: Cannot cast to an external record.")
839            }
840        }
841
842        Ok(vec![match &self.cast_type {
843            CastType::GroupXCoordinate => RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field)),
844            CastType::GroupYCoordinate => RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field)),
845            CastType::Plaintext(plaintext_type) => RegisterType::Plaintext(plaintext_type.clone()),
846            CastType::Record(identifier) => RegisterType::Record(*identifier),
847            CastType::ExternalRecord(locator) => RegisterType::ExternalRecord(*locator),
848        }])
849    }
850}
851
852impl<N: Network, const VARIANT: u8> CastOperation<N, VARIANT> {
853    /// A helper method to handle casting to a struct.
854    fn cast_to_struct(
855        &self,
856        stack: &(impl StackMatches<N> + StackProgram<N>),
857        registers: &mut impl RegistersStore<N>,
858        struct_name: Identifier<N>,
859        inputs: Vec<Value<N>>,
860    ) -> Result<()> {
861        // Ensure the operands length is at least the minimum.
862        if inputs.len() < N::MIN_STRUCT_ENTRIES {
863            bail!("Casting to a struct requires at least {} operand", N::MIN_STRUCT_ENTRIES)
864        }
865
866        // Retrieve the struct and ensure it is defined in the program.
867        let struct_ = stack.program().get_struct(&struct_name)?;
868
869        // Ensure that the number of operands is equal to the number of struct members.
870        if inputs.len() != struct_.members().len() {
871            bail!(
872                "Casting to the struct {} requires {} operands, but {} were provided",
873                struct_.name(),
874                struct_.members().len(),
875                inputs.len()
876            )
877        }
878
879        // Initialize the struct members.
880        let mut members = IndexMap::new();
881        for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) {
882            // Retrieve the plaintext value from the entry.
883            let plaintext = match member {
884                Value::Plaintext(plaintext) => {
885                    // Ensure the plaintext matches the member type.
886                    stack.matches_plaintext(plaintext, member_type)?;
887                    // Output the plaintext.
888                    plaintext.clone()
889                }
890                // Ensure the struct member is not a record.
891                Value::Record(..) => bail!("Casting a record into a struct member is illegal"),
892                // Ensure the struct member is not a future.
893                Value::Future(..) => bail!("Casting a future into a struct member is illegal"),
894            };
895            // Append the member to the struct members.
896            members.insert(*member_name, plaintext);
897        }
898
899        // Construct the struct.
900        let struct_ = Plaintext::Struct(members, Default::default());
901        // Store the struct.
902        registers.store(stack, &self.destination, Value::Plaintext(struct_))
903    }
904
905    /// A helper method to handle casting to an array.
906    fn cast_to_array(
907        &self,
908        stack: &(impl StackMatches<N> + StackProgram<N>),
909        registers: &mut impl RegistersStore<N>,
910        array_type: &ArrayType<N>,
911        inputs: Vec<Value<N>>,
912    ) -> Result<()> {
913        // Ensure that there is at least one operand.
914        if inputs.len() < N::MIN_ARRAY_ELEMENTS {
915            bail!("Casting to an array requires at least {} operand", N::MIN_ARRAY_ELEMENTS)
916        }
917
918        // Ensure that the number of operands is equal to the number of array entries.
919        if inputs.len() != **array_type.length() as usize {
920            bail!(
921                "Casting to the array {} requires {} operands, but {} were provided",
922                array_type,
923                array_type.length(),
924                inputs.len()
925            )
926        }
927
928        // Initialize the elements.
929        let mut elements = Vec::with_capacity(inputs.len());
930        for element in inputs.iter() {
931            // Retrieve the plaintext value from the element.
932            let plaintext = match element {
933                Value::Plaintext(plaintext) => {
934                    // Ensure the plaintext matches the element type.
935                    stack.matches_plaintext(plaintext, array_type.next_element_type())?;
936                    // Output the plaintext.
937                    plaintext.clone()
938                }
939                // Ensure the element is not a record.
940                Value::Record(..) => bail!("Casting a record into an array element is illegal"),
941                // Ensure the element is not a future.
942                Value::Future(..) => bail!("Casting a future into an array element is illegal"),
943            };
944            // Store the element.
945            elements.push(plaintext);
946        }
947
948        // Construct the array.
949        let array = Plaintext::Array(elements, Default::default());
950        // Store the array.
951        registers.store(stack, &self.destination, Value::Plaintext(array))
952    }
953}
954
955impl<N: Network, const VARIANT: u8> Parser for CastOperation<N, VARIANT> {
956    /// Parses a string into an operation.
957    #[inline]
958    fn parse(string: &str) -> ParserResult<Self> {
959        /// Parses an operand from the string.
960        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
961            // Parse the whitespace from the string.
962            let (string, _) = Sanitizer::parse_whitespaces(string)?;
963            // Parse the operand from the string.
964            Operand::parse(string)
965        }
966
967        // Parse the opcode from the string.
968        let (string, _) = tag(*Self::opcode())(string)?;
969        // Parse the operands from the string.
970        let (string, operands) = many1(parse_operand)(string)?;
971        // Parse the whitespace from the string.
972        let (string, _) = Sanitizer::parse_whitespaces(string)?;
973        // Parse the "into" from the string.
974        let (string, _) = tag("into")(string)?;
975        // Parse the whitespace from the string.
976        let (string, _) = Sanitizer::parse_whitespaces(string)?;
977        // Parse the destination register from the string.
978        let (string, destination) = Register::parse(string)?;
979        // Parse the whitespace from the string.
980        let (string, _) = Sanitizer::parse_whitespaces(string)?;
981        // Parse the "as" from the string.
982        let (string, _) = tag("as")(string)?;
983        // Parse the whitespace from the string.
984        let (string, _) = Sanitizer::parse_whitespaces(string)?;
985        // Parse the cast type from the string.
986        let (string, cast_type) = CastType::parse(string)?;
987        // Check that the number of operands does not exceed the maximum number of data entries.
988        let max_operands = match cast_type {
989            CastType::GroupXCoordinate
990            | CastType::GroupYCoordinate
991            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
992            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
993            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
994            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
995        };
996        match !operands.is_empty() && (operands.len() <= max_operands) {
997            true => Ok((string, Self { operands, destination, cast_type })),
998            false => {
999                map_res(fail, |_: ParserResult<Self>| Err(error("Failed to parse 'cast' opcode: too many operands")))(
1000                    string,
1001                )
1002            }
1003        }
1004    }
1005}
1006
1007impl<N: Network, const VARIANT: u8> FromStr for CastOperation<N, VARIANT> {
1008    type Err = Error;
1009
1010    /// Parses a string into an operation.
1011    #[inline]
1012    fn from_str(string: &str) -> Result<Self> {
1013        match Self::parse(string) {
1014            Ok((remainder, object)) => {
1015                // Ensure the remainder is empty.
1016                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
1017                // Return the object.
1018                Ok(object)
1019            }
1020            Err(error) => bail!("Failed to parse string. {error}"),
1021        }
1022    }
1023}
1024
1025impl<N: Network, const VARIANT: u8> Debug for CastOperation<N, VARIANT> {
1026    /// Prints the operation as a string.
1027    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1028        Display::fmt(self, f)
1029    }
1030}
1031
1032impl<N: Network, const VARIANT: u8> Display for CastOperation<N, VARIANT> {
1033    /// Prints the operation to a string.
1034    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1035        // Ensure the number of operands is within the bounds.
1036        let max_operands = match self.cast_type {
1037            CastType::GroupYCoordinate
1038            | CastType::GroupXCoordinate
1039            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1040            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1041            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1042            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1043        };
1044        if self.operands.is_empty() || self.operands.len() > max_operands {
1045            return Err(fmt::Error);
1046        }
1047        // Print the operation.
1048        write!(f, "{} ", Self::opcode())?;
1049        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
1050        write!(f, "into {} as {}", self.destination, self.cast_type)
1051    }
1052}
1053
1054impl<N: Network, const VARIANT: u8> FromBytes for CastOperation<N, VARIANT> {
1055    /// Reads the operation from a buffer.
1056    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
1057        // Read the number of operands.
1058        let num_operands = u8::read_le(&mut reader)? as usize;
1059
1060        // Ensure that the number of operands does not exceed the upper bound.
1061        // Note: Although a similar check is performed later, this check is performed to ensure that an exceedingly large number of operands is not allocated.
1062        // Note: This check is purely a sanity check, as it is not type-aware.
1063        if num_operands.is_zero() || num_operands > N::MAX_RECORD_ENTRIES {
1064            return Err(error(format!("The number of operands must be nonzero and <= {}", N::MAX_RECORD_ENTRIES)));
1065        }
1066
1067        // Initialize the vector for the operands.
1068        let mut operands = Vec::with_capacity(num_operands);
1069        // Read the operands.
1070        for _ in 0..num_operands {
1071            operands.push(Operand::read_le(&mut reader)?);
1072        }
1073
1074        // Read the destination register.
1075        let destination = Register::read_le(&mut reader)?;
1076
1077        // Read the cast type.
1078        let cast_type = CastType::read_le(&mut reader)?;
1079
1080        // Ensure the number of operands is within the bounds for the cast type.
1081        let max_operands = match cast_type {
1082            CastType::GroupYCoordinate
1083            | CastType::GroupXCoordinate
1084            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1085            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1086            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1087            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1088        };
1089        if num_operands.is_zero() || num_operands > max_operands {
1090            return Err(error(format!("The number of operands must be nonzero and <= {max_operands}")));
1091        }
1092
1093        // Return the operation.
1094        Ok(Self { operands, destination, cast_type })
1095    }
1096}
1097
1098impl<N: Network, const VARIANT: u8> ToBytes for CastOperation<N, VARIANT> {
1099    /// Writes the operation to a buffer.
1100    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
1101        // Ensure the number of operands is within the bounds.
1102        let max_operands = match self.cast_type {
1103            CastType::GroupYCoordinate
1104            | CastType::GroupXCoordinate
1105            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1106            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1107            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1108            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1109        };
1110        if self.operands.is_empty() || self.operands.len() > max_operands {
1111            return Err(error(format!("The number of operands must be nonzero and <= {max_operands}")));
1112        }
1113
1114        // Write the number of operands.
1115        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
1116        // Write the operands.
1117        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
1118        // Write the destination register.
1119        self.destination.write_le(&mut writer)?;
1120        // Write the cast type.
1121        self.cast_type.write_le(&mut writer)
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use console::{
1129        network::MainnetV0,
1130        program::{Access, Identifier},
1131    };
1132
1133    type CurrentNetwork = MainnetV0;
1134
1135    #[test]
1136    fn test_parse() {
1137        let (string, cast) =
1138            Cast::<CurrentNetwork>::parse("cast r0.owner r0.token_amount into r1 as token.record").unwrap();
1139        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1140        assert_eq!(cast.operands.len(), 2, "The number of operands is incorrect");
1141        assert_eq!(
1142            cast.operands[0],
1143            Operand::Register(Register::Access(0, vec![Access::from(Identifier::from_str("owner").unwrap())])),
1144            "The first operand is incorrect"
1145        );
1146        assert_eq!(
1147            cast.operands[1],
1148            Operand::Register(Register::Access(0, vec![Access::from(Identifier::from_str("token_amount").unwrap())])),
1149            "The second operand is incorrect"
1150        );
1151        assert_eq!(cast.destination, Register::Locator(1), "The destination register is incorrect");
1152        assert_eq!(
1153            cast.cast_type,
1154            CastType::Record(Identifier::from_str("token").unwrap()),
1155            "The value type is incorrect"
1156        );
1157    }
1158
1159    #[test]
1160    fn test_parse_cast_into_plaintext_max_operands() {
1161        let mut string = "cast ".to_string();
1162        let mut operands = Vec::with_capacity(CurrentNetwork::MAX_STRUCT_ENTRIES);
1163        for i in 0..CurrentNetwork::MAX_STRUCT_ENTRIES {
1164            string.push_str(&format!("r{i} "));
1165            operands.push(Operand::Register(Register::Locator(i as u64)));
1166        }
1167        string.push_str(&format!("into r{} as foo", CurrentNetwork::MAX_STRUCT_ENTRIES));
1168        let (string, cast) = Cast::<CurrentNetwork>::parse(&string).unwrap();
1169        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1170        assert_eq!(cast.operands.len(), CurrentNetwork::MAX_STRUCT_ENTRIES, "The number of operands is incorrect");
1171        assert_eq!(cast.operands, operands, "The operands are incorrect");
1172        assert_eq!(
1173            cast.destination,
1174            Register::Locator(CurrentNetwork::MAX_STRUCT_ENTRIES as u64),
1175            "The destination register is incorrect"
1176        );
1177        assert_eq!(
1178            cast.cast_type,
1179            CastType::Plaintext(PlaintextType::Struct(Identifier::from_str("foo").unwrap())),
1180            "The value type is incorrect"
1181        );
1182    }
1183
1184    #[test]
1185    fn test_parse_cast_into_record_max_operands() {
1186        let mut string = "cast ".to_string();
1187        let mut operands = Vec::with_capacity(CurrentNetwork::MAX_RECORD_ENTRIES);
1188        for i in 0..CurrentNetwork::MAX_RECORD_ENTRIES {
1189            string.push_str(&format!("r{i} "));
1190            operands.push(Operand::Register(Register::Locator(i as u64)));
1191        }
1192        string.push_str(&format!("into r{} as token.record", CurrentNetwork::MAX_RECORD_ENTRIES));
1193        let (string, cast) = Cast::<CurrentNetwork>::parse(&string).unwrap();
1194        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1195        assert_eq!(cast.operands.len(), CurrentNetwork::MAX_RECORD_ENTRIES, "The number of operands is incorrect");
1196        assert_eq!(cast.operands, operands, "The operands are incorrect");
1197        assert_eq!(
1198            cast.destination,
1199            Register::Locator((CurrentNetwork::MAX_RECORD_ENTRIES) as u64),
1200            "The destination register is incorrect"
1201        );
1202        assert_eq!(
1203            cast.cast_type,
1204            CastType::Record(Identifier::from_str("token").unwrap()),
1205            "The value type is incorrect"
1206        );
1207    }
1208
1209    #[test]
1210    fn test_parse_cast_into_record_too_many_operands() {
1211        let mut string = "cast ".to_string();
1212        for i in 0..=CurrentNetwork::MAX_RECORD_ENTRIES {
1213            string.push_str(&format!("r{i} "));
1214        }
1215        string.push_str(&format!("into r{} as token.record", CurrentNetwork::MAX_RECORD_ENTRIES + 1));
1216        assert!(Cast::<CurrentNetwork>::parse(&string).is_err(), "Parser did not error");
1217    }
1218
1219    #[test]
1220    fn test_parse_cast_into_plaintext_too_many_operands() {
1221        let mut string = "cast ".to_string();
1222        for i in 0..=CurrentNetwork::MAX_STRUCT_ENTRIES {
1223            string.push_str(&format!("r{i} "));
1224        }
1225        string.push_str(&format!("into r{} as foo", CurrentNetwork::MAX_STRUCT_ENTRIES + 1));
1226        assert!(Cast::<CurrentNetwork>::parse(&string).is_err(), "Parser did not error");
1227    }
1228}