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