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 version.
323                // Attention: The record version is currently on Version 1. If the record version is updated, change this value.
324                let version = console::program::U8::one();
325
326                // Construct the record.
327                let record = Record::<N, Plaintext<N>>::from_plaintext(owner, entries, nonce, version)?;
328                // Store the record.
329                registers.store(stack, &self.destination, Value::Record(record))
330            }
331            CastType::ExternalRecord(_locator) => {
332                bail!("Illegal operation: Cannot cast to an external record.")
333            }
334        }
335    }
336
337    /// Executes the instruction.
338    #[inline]
339    pub fn execute<A: circuit::Aleo<Network = N>>(
340        &self,
341        stack: &(impl StackMatches<N> + StackProgram<N>),
342        registers: &mut (impl RegistersSignerCircuit<N, A> + RegistersLoadCircuit<N, A> + RegistersStoreCircuit<N, A>),
343    ) -> Result<()> {
344        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
345        if VARIANT == CastVariant::CastLossy as u8 {
346            ensure!(
347                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
348                "`cast.lossy` is only supported for casting to a literal type"
349            )
350        }
351
352        use circuit::{Eject, Inject};
353
354        // Load the operands values.
355        let inputs: Vec<_> =
356            self.operands.iter().map(|operand| registers.load_circuit(stack, operand)).try_collect()?;
357
358        match &self.cast_type {
359            CastType::GroupXCoordinate => {
360                ensure!(inputs.len() == 1, "Casting to a group x-coordinate requires exactly 1 operand");
361                let field = match &inputs[0] {
362                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Group(group), ..)) => {
363                        group.to_x_coordinate()
364                    }
365                    _ => bail!("Casting to a group x-coordinate requires a group element"),
366                };
367                registers.store_circuit(
368                    stack,
369                    &self.destination,
370                    circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Field(field))),
371                )
372            }
373            CastType::GroupYCoordinate => {
374                ensure!(inputs.len() == 1, "Casting to a group y-coordinate requires exactly 1 operand");
375                let field = match &inputs[0] {
376                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Group(group), ..)) => {
377                        group.to_y_coordinate()
378                    }
379                    _ => bail!("Casting to a group y-coordinate requires a group element"),
380                };
381                registers.store_circuit(
382                    stack,
383                    &self.destination,
384                    circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Field(field))),
385                )
386            }
387            CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
388                ensure!(inputs.len() == 1, "Casting to a literal requires exactly 1 operand");
389                let value = match &inputs[0] {
390                    circuit::Value::Plaintext(circuit::Plaintext::Literal(literal, ..)) => match VARIANT {
391                        0 => literal.cast(*literal_type)?,
392                        1 => literal.cast_lossy(*literal_type)?,
393                        2.. => unreachable!("Invalid cast variant"),
394                    },
395                    _ => bail!("Casting to a literal requires a literal"),
396                };
397                registers.store_circuit(
398                    stack,
399                    &self.destination,
400                    circuit::Value::Plaintext(circuit::Plaintext::from(value)),
401                )
402            }
403            CastType::Plaintext(PlaintextType::Struct(struct_)) => {
404                // Ensure the operands length is at least the minimum.
405                if inputs.len() < N::MIN_STRUCT_ENTRIES {
406                    bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES)
407                }
408                // Ensure the number of members does not exceed the maximum.
409                if inputs.len() > N::MAX_STRUCT_ENTRIES {
410                    bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES)
411                }
412
413                // Retrieve the struct and ensure it is defined in the program.
414                let struct_ = stack.program().get_struct(struct_)?;
415
416                // Ensure that the number of operands is equal to the number of struct members.
417                if inputs.len() != struct_.members().len() {
418                    bail!(
419                        "Casting to the struct {} requires {} operands, but {} were provided",
420                        struct_.name(),
421                        struct_.members().len(),
422                        inputs.len()
423                    )
424                }
425
426                // Initialize the struct members.
427                let mut members = IndexMap::new();
428                for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) {
429                    // Retrieve the plaintext value from the entry.
430                    let plaintext = match member {
431                        circuit::Value::Plaintext(plaintext) => {
432                            // Ensure the member matches the register type.
433                            stack.matches_plaintext(&plaintext.eject_value(), member_type)?;
434                            // Output the plaintext.
435                            plaintext.clone()
436                        }
437                        // Ensure the struct member is not a record.
438                        circuit::Value::Record(..) => {
439                            bail!("Casting a record into a struct member is illegal")
440                        }
441                        // Ensure the struct member is not a future.
442                        circuit::Value::Future(..) => {
443                            bail!("Casting a future into a struct member is illegal")
444                        }
445                    };
446                    // Append the member to the struct members.
447                    members.insert(circuit::Identifier::constant(*member_name), plaintext);
448                }
449
450                // Construct the struct.
451                let struct_ = circuit::Plaintext::Struct(members, Default::default());
452                // Store the struct.
453                registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(struct_))
454            }
455            CastType::Plaintext(PlaintextType::Array(array_type)) => {
456                // Ensure the operands length is at least the minimum.
457                if inputs.len() < N::MIN_ARRAY_ELEMENTS {
458                    bail!("Casting to an array requires at least {} operand(s)", N::MIN_ARRAY_ELEMENTS)
459                }
460                // Ensure the number of elements does not exceed the maximum.
461                if inputs.len() > N::MAX_ARRAY_ELEMENTS {
462                    bail!("Casting to array '{array_type}' cannot exceed {} elements", N::MAX_ARRAY_ELEMENTS)
463                }
464
465                // Ensure that the number of operands is equal to the number of array entries.
466                if inputs.len() != **array_type.length() as usize {
467                    bail!(
468                        "Casting to the array {} requires {} operands, but {} were provided",
469                        array_type,
470                        array_type.length(),
471                        inputs.len()
472                    )
473                }
474
475                // Initialize the elements.
476                let mut elements = Vec::with_capacity(inputs.len());
477                for element in inputs.iter() {
478                    // Retrieve the plaintext value from the element.
479                    let plaintext = match element {
480                        circuit::Value::Plaintext(plaintext) => {
481                            // Ensure the plaintext matches the element type.
482                            stack.matches_plaintext(&plaintext.eject_value(), array_type.next_element_type())?;
483                            // Output the plaintext.
484                            plaintext.clone()
485                        }
486                        // Ensure the element is not a record.
487                        circuit::Value::Record(..) => bail!("Casting a record into an array element is illegal"),
488                        // Ensure the element is not a future.
489                        circuit::Value::Future(..) => bail!("Casting a future into an array element is illegal"),
490                    };
491                    // Store the element.
492                    elements.push(plaintext);
493                }
494
495                // Construct the array.
496                let array = circuit::Plaintext::Array(elements, Default::default());
497                // Store the array.
498                registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(array))
499            }
500            CastType::Record(record_name) => {
501                // Ensure the operands length is at least the minimum.
502                if inputs.len() < N::MIN_RECORD_ENTRIES {
503                    bail!("Casting to a record requires at least {} operand(s)", N::MIN_RECORD_ENTRIES)
504                }
505                // Ensure the number of entries does not exceed the maximum.
506                if inputs.len() > N::MAX_RECORD_ENTRIES {
507                    bail!("Casting to record '{record_name}' cannot exceed {} members", N::MAX_RECORD_ENTRIES)
508                }
509
510                // Retrieve the struct and ensure it is defined in the program.
511                let record_type = stack.program().get_record(record_name)?;
512
513                // Ensure that the number of operands is equal to the number of record entries, including the `owner`.
514                if inputs.len() != record_type.entries().len() + 1 {
515                    bail!(
516                        "Casting to the record {} requires {} operands, but {} were provided",
517                        record_type.name(),
518                        record_type.entries().len() + 1,
519                        inputs.len()
520                    )
521                }
522
523                // Initialize the record owner.
524                let owner: circuit::Owner<A, circuit::Plaintext<A>> = match &inputs[0] {
525                    // Ensure the entry is an address.
526                    circuit::Value::Plaintext(circuit::Plaintext::Literal(circuit::Literal::Address(owner), ..)) => {
527                        match record_type.owner().is_public() {
528                            true => circuit::Owner::Public(owner.clone()),
529                            false => circuit::Owner::Private(circuit::Plaintext::Literal(
530                                circuit::Literal::Address(owner.clone()),
531                                Default::default(),
532                            )),
533                        }
534                    }
535                    _ => bail!("Invalid record 'owner'"),
536                };
537
538                // Initialize the record entries.
539                let mut entries = IndexMap::new();
540                for (entry, (entry_name, entry_type)) in
541                    inputs.iter().skip(N::MIN_RECORD_ENTRIES).zip_eq(record_type.entries())
542                {
543                    // Compute the register type.
544                    let register_type = RegisterType::from(ValueType::from(entry_type.clone()));
545                    // Retrieve the plaintext value from the entry.
546                    let plaintext = match entry {
547                        circuit::Value::Plaintext(plaintext) => {
548                            // Ensure the entry matches the register type.
549                            stack.matches_register_type(
550                                &circuit::Value::Plaintext(plaintext.clone()).eject_value(),
551                                &register_type,
552                            )?;
553                            // Output the plaintext.
554                            plaintext.clone()
555                        }
556                        // Ensure the record entry is not a record.
557                        circuit::Value::Record(..) => bail!("Casting a record into a record entry is illegal"),
558                        // Ensure the record entry is not a future.
559                        circuit::Value::Future(..) => bail!("Casting a future into a record entry is illegal"),
560                    };
561                    // Construct the entry name constant circuit.
562                    let entry_name = circuit::Identifier::constant(*entry_name);
563                    // Append the entry to the record entries.
564                    match entry_type {
565                        EntryType::Constant(..) => entries.insert(entry_name, circuit::Entry::Constant(plaintext)),
566                        EntryType::Public(..) => entries.insert(entry_name, circuit::Entry::Public(plaintext)),
567                        EntryType::Private(..) => entries.insert(entry_name, circuit::Entry::Private(plaintext)),
568                    };
569                }
570
571                // Prepare the index as a constant field element.
572                let index = circuit::Field::constant(Field::from_u64(self.destination.locator()));
573                // Compute the randomizer as `HashToScalar(tvk || index)`.
574                let randomizer = A::hash_to_scalar_psd2(&[registers.tvk_circuit()?, index]);
575                // Compute the nonce from the randomizer.
576                let nonce = A::g_scalar_multiply(&randomizer);
577
578                // Inject the version (as `Mode::Private`).
579                // Attention: The record version is currently on Version 1. If the record version is updated, change this value.
580                // Note: The record version is injected as `Mode::Private` as the version is enforced by consensus
581                // when verifying a transaction to use the correct record version. See `Output::verify` in `Transition`
582                // for the verification logic enforced by consensus.
583                let version = circuit::U8::new(circuit::Mode::Private, console::program::U8::one());
584
585                // Construct the record.
586                let record =
587                    circuit::Record::<A, circuit::Plaintext<A>>::from_plaintext(owner, entries, nonce, version)?;
588                // Store the record.
589                registers.store_circuit(stack, &self.destination, circuit::Value::Record(record))
590            }
591            CastType::ExternalRecord(_locator) => {
592                bail!("Illegal operation: Cannot cast to an external record.")
593            }
594        }
595    }
596
597    /// Finalizes the instruction.
598    #[inline]
599    pub fn finalize(
600        &self,
601        stack: &(impl StackMatches<N> + StackProgram<N>),
602        registers: &mut (impl RegistersLoad<N> + RegistersStore<N>),
603    ) -> Result<()> {
604        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
605        if VARIANT == CastVariant::CastLossy as u8 {
606            ensure!(
607                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
608                "`cast.lossy` is only supported for casting to a literal type"
609            )
610        }
611
612        // Load the operands values.
613        let inputs: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;
614
615        match &self.cast_type {
616            CastType::GroupXCoordinate => {
617                ensure!(inputs.len() == 1, "Casting to a group x-coordinate requires exactly 1 operand");
618                let field = match &inputs[0] {
619                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_x_coordinate(),
620                    _ => bail!("Casting to a group x-coordinate requires a group element"),
621                };
622                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
623            }
624            CastType::GroupYCoordinate => {
625                ensure!(inputs.len() == 1, "Casting to a group y-coordinate requires exactly 1 operand");
626                let field = match &inputs[0] {
627                    Value::Plaintext(Plaintext::Literal(Literal::Group(group), ..)) => group.to_y_coordinate(),
628                    _ => bail!("Casting to a group y-coordinate requires a group element"),
629                };
630                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(Literal::Field(field))))
631            }
632            CastType::Plaintext(PlaintextType::Literal(literal_type)) => {
633                ensure!(inputs.len() == 1, "Casting to a literal requires exactly 1 operand");
634                let value = match &inputs[0] {
635                    Value::Plaintext(Plaintext::Literal(literal, ..)) => match VARIANT {
636                        0 => literal.cast(*literal_type)?,
637                        1 => literal.cast_lossy(*literal_type)?,
638                        2.. => unreachable!("Invalid cast variant"),
639                    },
640                    _ => bail!("Casting to a literal requires a literal"),
641                };
642                registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(value)))
643            }
644            CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
645                self.cast_to_struct(stack, registers, *struct_name, inputs)
646            }
647            CastType::Plaintext(PlaintextType::Array(array_type)) => {
648                self.cast_to_array(stack, registers, array_type, inputs)
649            }
650            CastType::Record(_record_name) => {
651                bail!("Illegal operation: Cannot cast to a record in a finalize block.")
652            }
653            CastType::ExternalRecord(_locator) => {
654                bail!("Illegal operation: Cannot cast to an external record.")
655            }
656        }
657    }
658
659    /// Returns the output type from the given program and input types.
660    #[inline]
661    pub fn output_types(
662        &self,
663        stack: &impl StackProgram<N>,
664        input_types: &[RegisterType<N>],
665    ) -> Result<Vec<RegisterType<N>>> {
666        // If the variant is `cast.lossy`, then check that the `cast_type` is a `PlaintextType::Literal`.
667        if VARIANT == CastVariant::CastLossy as u8 {
668            ensure!(
669                matches!(self.cast_type, CastType::Plaintext(PlaintextType::Literal(..))),
670                "`cast.lossy` is only supported for casting to a literal type"
671            )
672        }
673
674        // Ensure the number of operands is correct.
675        ensure!(
676            input_types.len() == self.operands.len(),
677            "Instruction '{}' expects {} operands, found {} operands",
678            Self::opcode(),
679            input_types.len(),
680            self.operands.len(),
681        );
682
683        // Ensure the output type is defined in the program.
684        match &self.cast_type {
685            CastType::GroupXCoordinate | CastType::GroupYCoordinate => {
686                ensure!(input_types.len() == 1, "Casting to a group coordinate requires exactly 1 operand");
687                ensure!(
688                    matches!(input_types[0], RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Group))),
689                    "Type mismatch: expected 'group', found '{}'",
690                    input_types[0]
691                );
692            }
693            CastType::Plaintext(PlaintextType::Literal(..)) => {
694                ensure!(input_types.len() == 1, "Casting to a literal requires exactly 1 operand");
695            }
696            CastType::Plaintext(PlaintextType::Struct(struct_name)) => {
697                // Retrieve the struct and ensure it is defined in the program.
698                let struct_ = stack.program().get_struct(struct_name)?;
699
700                // Ensure the input types length is at least the minimum.
701                if input_types.len() < N::MIN_STRUCT_ENTRIES {
702                    bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES)
703                }
704                // Ensure the number of members does not exceed the maximum.
705                if input_types.len() > N::MAX_STRUCT_ENTRIES {
706                    bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES)
707                }
708
709                // Ensure that the number of input types is equal to the number of struct members.
710                ensure!(
711                    input_types.len() == struct_.members().len(),
712                    "Casting to the struct {} requires {} operands, but {} were provided",
713                    struct_.name(),
714                    struct_.members().len(),
715                    input_types.len()
716                );
717                // Ensure the input types match the struct.
718                for ((_, member_type), input_type) in struct_.members().iter().zip_eq(input_types) {
719                    match input_type {
720                        // Ensure the plaintext type matches the member type.
721                        RegisterType::Plaintext(plaintext_type) => {
722                            ensure!(
723                                member_type == plaintext_type,
724                                "Struct '{struct_name}' member type mismatch: expected '{member_type}', found '{plaintext_type}'"
725                            )
726                        }
727                        // Ensure the input type cannot be a record (this is unsupported behavior).
728                        RegisterType::Record(record_name) => bail!(
729                            "Struct '{struct_name}' member type mismatch: expected '{member_type}', found record '{record_name}'"
730                        ),
731                        // Ensure the input type cannot be an external record (this is unsupported behavior).
732                        RegisterType::ExternalRecord(locator) => bail!(
733                            "Struct '{struct_name}' member type mismatch: expected '{member_type}', found external record '{locator}'"
734                        ),
735                        // Ensure the input type cannot be a future (this is unsupported behavior).
736                        RegisterType::Future(..) => {
737                            bail!("Struct '{struct_name}' member type mismatch: expected '{member_type}', found future")
738                        }
739                    }
740                }
741            }
742            CastType::Plaintext(PlaintextType::Array(array_type)) => {
743                // Ensure the input types length is at least the minimum.
744                if input_types.len() < N::MIN_ARRAY_ELEMENTS {
745                    bail!("Casting to an array requires at least {} operand(s)", N::MIN_ARRAY_ELEMENTS)
746                }
747                // Ensure the number of elements does not exceed the maximum.
748                if input_types.len() > N::MAX_ARRAY_ELEMENTS {
749                    bail!("Casting to array '{array_type}' cannot exceed {} elements", N::MAX_ARRAY_ELEMENTS)
750                }
751
752                // Ensure that the number of input types is equal to the number of array entries.
753                if input_types.len() != **array_type.length() as usize {
754                    bail!(
755                        "Casting to the array {} requires {} operands, but {} were provided",
756                        array_type,
757                        array_type.length(),
758                        input_types.len()
759                    )
760                }
761
762                // Ensure the input types match the element type.
763                for input_type in input_types {
764                    match input_type {
765                        // Ensure the plaintext type matches the member type.
766                        RegisterType::Plaintext(plaintext_type) => {
767                            ensure!(
768                                plaintext_type == array_type.next_element_type(),
769                                "Array element type mismatch: expected '{}', found '{plaintext_type}'",
770                                array_type.next_element_type()
771                            )
772                        }
773                        // Ensure the input type cannot be a record (this is unsupported behavior).
774                        RegisterType::Record(record_name) => bail!(
775                            "Array element type mismatch: expected '{}', found record '{record_name}'",
776                            array_type.next_element_type()
777                        ),
778                        // Ensure the input type cannot be an external record (this is unsupported behavior).
779                        RegisterType::ExternalRecord(locator) => bail!(
780                            "Array element type mismatch: expected '{}', found external record '{locator}'",
781                            array_type.next_element_type()
782                        ),
783                        // Ensure the input type cannot be a future (this is unsupported behavior).
784                        RegisterType::Future(..) => bail!(
785                            "Array element type mismatch: expected '{}', found future",
786                            array_type.next_element_type()
787                        ),
788                    }
789                }
790            }
791            CastType::Record(record_name) => {
792                // Retrieve the record type and ensure is defined in the program.
793                let record = stack.program().get_record(record_name)?;
794
795                // Ensure the input types length is at least the minimum.
796                if input_types.len() < N::MIN_RECORD_ENTRIES {
797                    bail!("Casting to a record requires at least {} operand(s)", N::MIN_RECORD_ENTRIES)
798                }
799                // Ensure the number of entries does not exceed the maximum.
800                if input_types.len() > N::MAX_RECORD_ENTRIES {
801                    bail!("Casting to record '{record_name}' cannot exceed {} members", N::MAX_RECORD_ENTRIES)
802                }
803
804                // Ensure that the number of input types is equal to the number of record entries, including the `owner`.
805                ensure!(
806                    input_types.len() == record.entries().len() + 1,
807                    "Casting to the record {} requires {} operands, but {} were provided",
808                    record.name(),
809                    record.entries().len() + 1,
810                    input_types.len()
811                );
812                // Ensure the first input type is an address.
813                ensure!(
814                    input_types[0] == RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Address)),
815                    "Casting to a record requires the first operand to be an address"
816                );
817
818                // Ensure the input types match the record.
819                for (input_type, (_, entry_type)) in
820                    input_types.iter().skip(N::MIN_RECORD_ENTRIES).zip_eq(record.entries())
821                {
822                    match input_type {
823                        // Ensure the plaintext type matches the entry type.
824                        RegisterType::Plaintext(plaintext_type) => match entry_type {
825                            EntryType::Constant(entry_type)
826                            | EntryType::Public(entry_type)
827                            | EntryType::Private(entry_type) => {
828                                ensure!(
829                                    entry_type == plaintext_type,
830                                    "Record '{record_name}' entry type mismatch: expected '{entry_type}', found '{plaintext_type}'"
831                                )
832                            }
833                        },
834                        // Ensure the input type cannot be a record (this is unsupported behavior).
835                        RegisterType::Record(record_name) => bail!(
836                            "Record '{record_name}' entry type mismatch: expected '{entry_type}', found record '{record_name}'"
837                        ),
838                        // Ensure the input type cannot be an external record (this is unsupported behavior).
839                        RegisterType::ExternalRecord(locator) => bail!(
840                            "Record '{record_name}' entry type mismatch: expected '{entry_type}', found external record '{locator}'"
841                        ),
842                        // Ensure the input type cannot be a future (this is unsupported behavior).
843                        RegisterType::Future(..) => {
844                            bail!("Record '{record_name}' entry type mismatch: expected '{entry_type}', found future",)
845                        }
846                    }
847                }
848            }
849            CastType::ExternalRecord(_locator) => {
850                bail!("Illegal operation: Cannot cast to an external record.")
851            }
852        }
853
854        Ok(vec![match &self.cast_type {
855            CastType::GroupXCoordinate => RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field)),
856            CastType::GroupYCoordinate => RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Field)),
857            CastType::Plaintext(plaintext_type) => RegisterType::Plaintext(plaintext_type.clone()),
858            CastType::Record(identifier) => RegisterType::Record(*identifier),
859            CastType::ExternalRecord(locator) => RegisterType::ExternalRecord(*locator),
860        }])
861    }
862}
863
864impl<N: Network, const VARIANT: u8> CastOperation<N, VARIANT> {
865    /// A helper method to handle casting to a struct.
866    fn cast_to_struct(
867        &self,
868        stack: &(impl StackMatches<N> + StackProgram<N>),
869        registers: &mut impl RegistersStore<N>,
870        struct_name: Identifier<N>,
871        inputs: Vec<Value<N>>,
872    ) -> Result<()> {
873        // Ensure the operands length is at least the minimum.
874        if inputs.len() < N::MIN_STRUCT_ENTRIES {
875            bail!("Casting to a struct requires at least {} operand", N::MIN_STRUCT_ENTRIES)
876        }
877
878        // Retrieve the struct and ensure it is defined in the program.
879        let struct_ = stack.program().get_struct(&struct_name)?;
880
881        // Ensure that the number of operands is equal to the number of struct members.
882        if inputs.len() != struct_.members().len() {
883            bail!(
884                "Casting to the struct {} requires {} operands, but {} were provided",
885                struct_.name(),
886                struct_.members().len(),
887                inputs.len()
888            )
889        }
890
891        // Initialize the struct members.
892        let mut members = IndexMap::new();
893        for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) {
894            // Retrieve the plaintext value from the entry.
895            let plaintext = match member {
896                Value::Plaintext(plaintext) => {
897                    // Ensure the plaintext matches the member type.
898                    stack.matches_plaintext(plaintext, member_type)?;
899                    // Output the plaintext.
900                    plaintext.clone()
901                }
902                // Ensure the struct member is not a record.
903                Value::Record(..) => bail!("Casting a record into a struct member is illegal"),
904                // Ensure the struct member is not a future.
905                Value::Future(..) => bail!("Casting a future into a struct member is illegal"),
906            };
907            // Append the member to the struct members.
908            members.insert(*member_name, plaintext);
909        }
910
911        // Construct the struct.
912        let struct_ = Plaintext::Struct(members, Default::default());
913        // Store the struct.
914        registers.store(stack, &self.destination, Value::Plaintext(struct_))
915    }
916
917    /// A helper method to handle casting to an array.
918    fn cast_to_array(
919        &self,
920        stack: &(impl StackMatches<N> + StackProgram<N>),
921        registers: &mut impl RegistersStore<N>,
922        array_type: &ArrayType<N>,
923        inputs: Vec<Value<N>>,
924    ) -> Result<()> {
925        // Ensure that there is at least one operand.
926        if inputs.len() < N::MIN_ARRAY_ELEMENTS {
927            bail!("Casting to an array requires at least {} operand", N::MIN_ARRAY_ELEMENTS)
928        }
929
930        // Ensure that the number of operands is equal to the number of array entries.
931        if inputs.len() != **array_type.length() as usize {
932            bail!(
933                "Casting to the array {} requires {} operands, but {} were provided",
934                array_type,
935                array_type.length(),
936                inputs.len()
937            )
938        }
939
940        // Initialize the elements.
941        let mut elements = Vec::with_capacity(inputs.len());
942        for element in inputs.iter() {
943            // Retrieve the plaintext value from the element.
944            let plaintext = match element {
945                Value::Plaintext(plaintext) => {
946                    // Ensure the plaintext matches the element type.
947                    stack.matches_plaintext(plaintext, array_type.next_element_type())?;
948                    // Output the plaintext.
949                    plaintext.clone()
950                }
951                // Ensure the element is not a record.
952                Value::Record(..) => bail!("Casting a record into an array element is illegal"),
953                // Ensure the element is not a future.
954                Value::Future(..) => bail!("Casting a future into an array element is illegal"),
955            };
956            // Store the element.
957            elements.push(plaintext);
958        }
959
960        // Construct the array.
961        let array = Plaintext::Array(elements, Default::default());
962        // Store the array.
963        registers.store(stack, &self.destination, Value::Plaintext(array))
964    }
965}
966
967impl<N: Network, const VARIANT: u8> Parser for CastOperation<N, VARIANT> {
968    /// Parses a string into an operation.
969    #[inline]
970    fn parse(string: &str) -> ParserResult<Self> {
971        /// Parses an operand from the string.
972        fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
973            // Parse the whitespace from the string.
974            let (string, _) = Sanitizer::parse_whitespaces(string)?;
975            // Parse the operand from the string.
976            Operand::parse(string)
977        }
978
979        // Parse the opcode from the string.
980        let (string, _) = tag(*Self::opcode())(string)?;
981        // Parse the operands from the string.
982        let (string, operands) = many1(parse_operand)(string)?;
983        // Parse the whitespace from the string.
984        let (string, _) = Sanitizer::parse_whitespaces(string)?;
985        // Parse the "into" from the string.
986        let (string, _) = tag("into")(string)?;
987        // Parse the whitespace from the string.
988        let (string, _) = Sanitizer::parse_whitespaces(string)?;
989        // Parse the destination register from the string.
990        let (string, destination) = Register::parse(string)?;
991        // Parse the whitespace from the string.
992        let (string, _) = Sanitizer::parse_whitespaces(string)?;
993        // Parse the "as" from the string.
994        let (string, _) = tag("as")(string)?;
995        // Parse the whitespace from the string.
996        let (string, _) = Sanitizer::parse_whitespaces(string)?;
997        // Parse the cast type from the string.
998        let (string, cast_type) = CastType::parse(string)?;
999        // Check that the number of operands does not exceed the maximum number of data entries.
1000        let max_operands = match cast_type {
1001            CastType::GroupXCoordinate
1002            | CastType::GroupYCoordinate
1003            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1004            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1005            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1006            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1007        };
1008        match !operands.is_empty() && (operands.len() <= max_operands) {
1009            true => Ok((string, Self { operands, destination, cast_type })),
1010            false => {
1011                map_res(fail, |_: ParserResult<Self>| Err(error("Failed to parse 'cast' opcode: too many operands")))(
1012                    string,
1013                )
1014            }
1015        }
1016    }
1017}
1018
1019impl<N: Network, const VARIANT: u8> FromStr for CastOperation<N, VARIANT> {
1020    type Err = Error;
1021
1022    /// Parses a string into an operation.
1023    #[inline]
1024    fn from_str(string: &str) -> Result<Self> {
1025        match Self::parse(string) {
1026            Ok((remainder, object)) => {
1027                // Ensure the remainder is empty.
1028                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
1029                // Return the object.
1030                Ok(object)
1031            }
1032            Err(error) => bail!("Failed to parse string. {error}"),
1033        }
1034    }
1035}
1036
1037impl<N: Network, const VARIANT: u8> Debug for CastOperation<N, VARIANT> {
1038    /// Prints the operation as a string.
1039    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1040        Display::fmt(self, f)
1041    }
1042}
1043
1044impl<N: Network, const VARIANT: u8> Display for CastOperation<N, VARIANT> {
1045    /// Prints the operation to a string.
1046    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1047        // Ensure the number of operands is within the bounds.
1048        let max_operands = match self.cast_type {
1049            CastType::GroupYCoordinate
1050            | CastType::GroupXCoordinate
1051            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1052            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1053            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1054            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1055        };
1056        if self.operands.is_empty() || self.operands.len() > max_operands {
1057            return Err(fmt::Error);
1058        }
1059        // Print the operation.
1060        write!(f, "{} ", Self::opcode())?;
1061        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
1062        write!(f, "into {} as {}", self.destination, self.cast_type)
1063    }
1064}
1065
1066impl<N: Network, const VARIANT: u8> FromBytes for CastOperation<N, VARIANT> {
1067    /// Reads the operation from a buffer.
1068    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
1069        // Read the number of operands.
1070        let num_operands = u8::read_le(&mut reader)? as usize;
1071
1072        // Ensure that the number of operands does not exceed the upper bound.
1073        // Note: Although a similar check is performed later, this check is performed to ensure that an exceedingly large number of operands is not allocated.
1074        // Note: This check is purely a sanity check, as it is not type-aware.
1075        if num_operands.is_zero() || num_operands > N::MAX_RECORD_ENTRIES {
1076            return Err(error(format!("The number of operands must be nonzero and <= {}", N::MAX_RECORD_ENTRIES)));
1077        }
1078
1079        // Initialize the vector for the operands.
1080        let mut operands = Vec::with_capacity(num_operands);
1081        // Read the operands.
1082        for _ in 0..num_operands {
1083            operands.push(Operand::read_le(&mut reader)?);
1084        }
1085
1086        // Read the destination register.
1087        let destination = Register::read_le(&mut reader)?;
1088
1089        // Read the cast type.
1090        let cast_type = CastType::read_le(&mut reader)?;
1091
1092        // Ensure the number of operands is within the bounds for the cast type.
1093        let max_operands = match cast_type {
1094            CastType::GroupYCoordinate
1095            | CastType::GroupXCoordinate
1096            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1097            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1098            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1099            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1100        };
1101        if num_operands.is_zero() || num_operands > max_operands {
1102            return Err(error(format!("The number of operands must be nonzero and <= {max_operands}")));
1103        }
1104
1105        // Return the operation.
1106        Ok(Self { operands, destination, cast_type })
1107    }
1108}
1109
1110impl<N: Network, const VARIANT: u8> ToBytes for CastOperation<N, VARIANT> {
1111    /// Writes the operation to a buffer.
1112    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
1113        // Ensure the number of operands is within the bounds.
1114        let max_operands = match self.cast_type {
1115            CastType::GroupYCoordinate
1116            | CastType::GroupXCoordinate
1117            | CastType::Plaintext(PlaintextType::Literal(_)) => 1,
1118            CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES,
1119            CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS,
1120            CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES,
1121        };
1122        if self.operands.is_empty() || self.operands.len() > max_operands {
1123            return Err(error(format!("The number of operands must be nonzero and <= {max_operands}")));
1124        }
1125
1126        // Write the number of operands.
1127        u8::try_from(self.operands.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?;
1128        // Write the operands.
1129        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
1130        // Write the destination register.
1131        self.destination.write_le(&mut writer)?;
1132        // Write the cast type.
1133        self.cast_type.write_le(&mut writer)
1134    }
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139    use super::*;
1140    use console::{
1141        network::MainnetV0,
1142        program::{Access, Identifier},
1143    };
1144
1145    type CurrentNetwork = MainnetV0;
1146
1147    #[test]
1148    fn test_parse() {
1149        let (string, cast) =
1150            Cast::<CurrentNetwork>::parse("cast r0.owner r0.token_amount into r1 as token.record").unwrap();
1151        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1152        assert_eq!(cast.operands.len(), 2, "The number of operands is incorrect");
1153        assert_eq!(
1154            cast.operands[0],
1155            Operand::Register(Register::Access(0, vec![Access::from(Identifier::from_str("owner").unwrap())])),
1156            "The first operand is incorrect"
1157        );
1158        assert_eq!(
1159            cast.operands[1],
1160            Operand::Register(Register::Access(0, vec![Access::from(Identifier::from_str("token_amount").unwrap())])),
1161            "The second operand is incorrect"
1162        );
1163        assert_eq!(cast.destination, Register::Locator(1), "The destination register is incorrect");
1164        assert_eq!(
1165            cast.cast_type,
1166            CastType::Record(Identifier::from_str("token").unwrap()),
1167            "The value type is incorrect"
1168        );
1169    }
1170
1171    #[test]
1172    fn test_parse_cast_into_plaintext_max_operands() {
1173        let mut string = "cast ".to_string();
1174        let mut operands = Vec::with_capacity(CurrentNetwork::MAX_STRUCT_ENTRIES);
1175        for i in 0..CurrentNetwork::MAX_STRUCT_ENTRIES {
1176            string.push_str(&format!("r{i} "));
1177            operands.push(Operand::Register(Register::Locator(i as u64)));
1178        }
1179        string.push_str(&format!("into r{} as foo", CurrentNetwork::MAX_STRUCT_ENTRIES));
1180        let (string, cast) = Cast::<CurrentNetwork>::parse(&string).unwrap();
1181        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1182        assert_eq!(cast.operands.len(), CurrentNetwork::MAX_STRUCT_ENTRIES, "The number of operands is incorrect");
1183        assert_eq!(cast.operands, operands, "The operands are incorrect");
1184        assert_eq!(
1185            cast.destination,
1186            Register::Locator(CurrentNetwork::MAX_STRUCT_ENTRIES as u64),
1187            "The destination register is incorrect"
1188        );
1189        assert_eq!(
1190            cast.cast_type,
1191            CastType::Plaintext(PlaintextType::Struct(Identifier::from_str("foo").unwrap())),
1192            "The value type is incorrect"
1193        );
1194    }
1195
1196    #[test]
1197    fn test_parse_cast_into_record_max_operands() {
1198        let mut string = "cast ".to_string();
1199        let mut operands = Vec::with_capacity(CurrentNetwork::MAX_RECORD_ENTRIES);
1200        for i in 0..CurrentNetwork::MAX_RECORD_ENTRIES {
1201            string.push_str(&format!("r{i} "));
1202            operands.push(Operand::Register(Register::Locator(i as u64)));
1203        }
1204        string.push_str(&format!("into r{} as token.record", CurrentNetwork::MAX_RECORD_ENTRIES));
1205        let (string, cast) = Cast::<CurrentNetwork>::parse(&string).unwrap();
1206        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
1207        assert_eq!(cast.operands.len(), CurrentNetwork::MAX_RECORD_ENTRIES, "The number of operands is incorrect");
1208        assert_eq!(cast.operands, operands, "The operands are incorrect");
1209        assert_eq!(
1210            cast.destination,
1211            Register::Locator((CurrentNetwork::MAX_RECORD_ENTRIES) as u64),
1212            "The destination register is incorrect"
1213        );
1214        assert_eq!(
1215            cast.cast_type,
1216            CastType::Record(Identifier::from_str("token").unwrap()),
1217            "The value type is incorrect"
1218        );
1219    }
1220
1221    #[test]
1222    fn test_parse_cast_into_record_too_many_operands() {
1223        let mut string = "cast ".to_string();
1224        for i in 0..=CurrentNetwork::MAX_RECORD_ENTRIES {
1225            string.push_str(&format!("r{i} "));
1226        }
1227        string.push_str(&format!("into r{} as token.record", CurrentNetwork::MAX_RECORD_ENTRIES + 1));
1228        assert!(Cast::<CurrentNetwork>::parse(&string).is_err(), "Parser did not error");
1229    }
1230
1231    #[test]
1232    fn test_parse_cast_into_plaintext_too_many_operands() {
1233        let mut string = "cast ".to_string();
1234        for i in 0..=CurrentNetwork::MAX_STRUCT_ENTRIES {
1235            string.push_str(&format!("r{i} "));
1236        }
1237        string.push_str(&format!("into r{} as foo", CurrentNetwork::MAX_STRUCT_ENTRIES + 1));
1238        assert!(Cast::<CurrentNetwork>::parse(&string).is_err(), "Parser did not error");
1239    }
1240}