Skip to main content

snarkvm_synthesizer_program/logic/instruction/operation/
cast.rs

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