1use 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#[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 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 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 alt((
74 map(tag("group.x"), |_| Self::GroupXCoordinate),
75 map(tag("group.y"), |_| Self::GroupYCoordinate),
76 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 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 fn from_str(string: &str) -> Result<Self> {
111 match Self::parse(string) {
112 Ok((remainder, object)) => {
113 ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
115 Ok(object)
117 }
118 Err(error) => bail!("Failed to parse string. {error}"),
119 }
120 }
121}
122
123impl<N: Network> ToBytes for CastType<N> {
124 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 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
162pub type Cast<N> = CastOperation<N, { CastVariant::Cast as u8 }>;
164pub type CastLossy<N> = CastOperation<N, { CastVariant::CastLossy as u8 }>;
166
167enum CastVariant {
169 Cast,
170 CastLossy,
171}
172
173#[derive(Clone, PartialEq, Eq, Hash)]
175pub struct CastOperation<N: Network, const VARIANT: u8> {
176 operands: Vec<Operand<N>>,
178 destination: Register<N>,
180 cast_type: CastType<N>,
182}
183
184impl<N: Network, const VARIANT: u8> CastOperation<N, VARIANT> {
185 #[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 #[inline]
197 pub fn operands(&self) -> &[Operand<N>] {
198 &self.operands
199 }
200
201 #[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 #[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 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 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 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 pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersSigner<N>) -> Result<()> {
242 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 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 if inputs.len() < N::MIN_RECORD_ENTRIES {
297 bail!("Casting to a record requires at least {} operand", N::MIN_RECORD_ENTRIES)
298 }
299
300 let record_type = stack.program().get_record(record_name)?;
302
303 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 let owner: Owner<N, Plaintext<N>> = match &inputs[0] {
315 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 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 let plaintext_type = entry_type.plaintext_type();
332 let plaintext = match entry {
334 Value::Plaintext(plaintext) => {
335 stack.matches_plaintext(plaintext, plaintext_type)?;
337 plaintext.clone()
339 }
340 Value::Record(..) => bail!("Casting a record into a record entry is illegal"),
342 Value::Future(..) => bail!("Casting a future into a record entry is illegal"),
344 Value::DynamicRecord(..) => {
346 bail!("Casting a dynamic record into a record entry is illegal")
347 }
348 Value::DynamicFuture(..) => bail!("Casting a dynamic future into a record entry is illegal"),
350 };
351 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 let index = Field::from_u64(self.destination.locator());
361 let randomizer = N::hash_to_scalar_psd2(&[registers.tvk()?, index])?;
363 let nonce = N::g_scalar_multiply(&randomizer);
365
366 let version = console::program::U8::one();
369
370 let record = Record::<N, Plaintext<N>>::from_plaintext(owner, entries, nonce, version)?;
372 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 ensure!(inputs.len() == 1, "Casting to a dynamic record requires exactly 1 operand");
381 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 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 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 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 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 registers.store_circuit(stack, &self.destination, plaintext.into())
466 }
467 CastType::Plaintext(PlaintextType::Array(array_type)) => {
468 if inputs.len() < N::MIN_ARRAY_ELEMENTS {
470 bail!("Casting to an array requires at least {} operand(s)", N::MIN_ARRAY_ELEMENTS)
471 }
472 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 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 let mut elements = Vec::with_capacity(inputs.len());
489 for element in inputs.iter() {
490 let plaintext = match element {
492 circuit::Value::Plaintext(plaintext) => {
493 stack.matches_plaintext(&plaintext.eject_value(), array_type.next_element_type())?;
495 plaintext.clone()
497 }
498 circuit::Value::Record(..) => bail!("Casting a record into an array element is illegal"),
500 circuit::Value::Future(..) => bail!("Casting a future into an array element is illegal"),
502 circuit::Value::DynamicRecord(..) => {
504 bail!("Casting a dynamic record into an array element is illegal")
505 }
506 circuit::Value::DynamicFuture(..) => {
508 bail!("Casting a dynamic future into an array element is illegal")
509 }
510 };
511 elements.push(plaintext);
513 }
514
515 let array = circuit::Plaintext::Array(elements, Default::default());
517 registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(array))
519 }
520 CastType::Record(record_name) => {
521 if inputs.len() < N::MIN_RECORD_ENTRIES {
523 bail!("Casting to a record requires at least {} operand(s)", N::MIN_RECORD_ENTRIES)
524 }
525 if inputs.len() > N::MAX_RECORD_ENTRIES {
527 bail!("Casting to record '{record_name}' cannot exceed {} members", N::MAX_RECORD_ENTRIES)
528 }
529
530 let record_type = stack.program().get_record(record_name)?;
532
533 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 let owner: circuit::Owner<A, circuit::Plaintext<A>> = match &inputs[0] {
545 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 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 let register_type = RegisterType::from(ValueType::from(entry_type.clone()));
565 let plaintext = match entry {
567 circuit::Value::Plaintext(plaintext) => {
568 stack.matches_register_type(
570 &circuit::Value::Plaintext(plaintext.clone()).eject_value(),
571 ®ister_type,
572 )?;
573 plaintext.clone()
575 }
576 circuit::Value::Record(..) => bail!("Casting a record into a record entry is illegal"),
578 circuit::Value::Future(..) => bail!("Casting a future into a record entry is illegal"),
580 circuit::Value::DynamicRecord(..) => {
582 bail!("Casting a dynamic record into a record entry is illegal")
583 }
584 circuit::Value::DynamicFuture(..) => {
586 bail!("Casting a dynamic future into a record entry is illegal")
587 }
588 };
589 let entry_name = circuit::Identifier::constant(*entry_name);
591 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 let index = circuit::Field::constant(Field::from_u64(self.destination.locator()));
601 let randomizer = A::hash_to_scalar_psd2(&[registers.tvk_circuit()?, index]);
603 let nonce = A::g_scalar_multiply(&randomizer);
605
606 let version = circuit::U8::new(circuit::Mode::Private, console::program::U8::one());
612
613 let record =
615 circuit::Record::<A, circuit::Plaintext<A>>::from_plaintext(owner, entries, nonce, version)?;
616 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 ensure!(inputs.len() == 1, "Casting to a dynamic record requires exactly 1 operand");
625 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 registers.store_circuit(stack, &self.destination, circuit::Value::DynamicRecord(dynamic_record))
632 }
633 }
634 }
635
636 pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
638 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 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 pub fn output_types(
704 &self,
705 stack: &impl StackTrait<N>,
706 input_types: &[RegisterType<N>],
707 ) -> Result<Vec<RegisterType<N>>> {
708 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!(
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 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 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!(
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 for ((_, member_type), input_type) in struct_type.members().iter().zip_eq(input_types) {
752 match input_type {
753 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 RegisterType::Record(record_name) => bail!(
762 "Struct '{struct_name}' member type mismatch: expected '{member_type}', found record '{record_name}'"
763 ),
764 RegisterType::ExternalRecord(locator) => bail!(
766 "Struct '{struct_name}' member type mismatch: expected '{member_type}', found external record '{locator}'"
767 ),
768 RegisterType::Future(..) => {
770 bail!("Struct '{struct_name}' member type mismatch: expected '{member_type}', found future")
771 }
772 RegisterType::DynamicRecord => {
774 bail!(
775 "Struct '{struct_name}' member type mismatch: expected '{member_type}', found dynamic record"
776 )
777 }
778 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 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 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 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 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 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 for input_type in input_types {
835 match input_type {
836 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 RegisterType::Record(record_name) => bail!(
846 "Array element type mismatch: expected '{}', found record '{record_name}'",
847 array_type.next_element_type()
848 ),
849 RegisterType::ExternalRecord(locator) => bail!(
851 "Array element type mismatch: expected '{}', found external record '{locator}'",
852 array_type.next_element_type()
853 ),
854 RegisterType::Future(..) => bail!(
856 "Array element type mismatch: expected '{}', found future",
857 array_type.next_element_type()
858 ),
859 RegisterType::DynamicRecord => bail!(
861 "Array element type mismatch: expected '{}', found dynamic record",
862 array_type.next_element_type()
863 ),
864 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 let record = stack.program().get_record(record_name)?;
875
876 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 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!(
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!(
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 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 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 RegisterType::Record(record_name) => bail!(
917 "Record '{record_name}' entry type mismatch: expected '{entry_type}', found record '{record_name}'"
918 ),
919 RegisterType::ExternalRecord(locator) => bail!(
921 "Record '{record_name}' entry type mismatch: expected '{entry_type}', found external record '{locator}'"
922 ),
923 RegisterType::Future(..) => {
925 bail!("Record '{record_name}' entry type mismatch: expected '{entry_type}', found future")
926 }
927 RegisterType::DynamicRecord => {
929 bail!(
930 "Record '{record_name}' entry type mismatch: expected '{entry_type}', found dynamic record"
931 )
932 }
933 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 if $inputs.len() < $N::MIN_STRUCT_ENTRIES {
971 bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES)
972 }
973 if $inputs.len() > $N::MAX_STRUCT_ENTRIES {
975 bail!("Casting to a struct cannot exceed {} members", N::MAX_STRUCT_ENTRIES)
976 }
977
978 let struct_ = $stack.program().get_struct($struct_name)?;
980
981 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 let mut members = IndexMap::new();
993 for (member, (member_name, member_type)) in $inputs.iter().zip_eq(struct_.members()) {
994 let plaintext = match member {
996 $plaintext(plaintext) => {
997 $stack.matches_plaintext(&$eject_value(plaintext), member_type)?;
999 plaintext.clone()
1001 }
1002 $record(..) => {
1004 bail!("Casting a record into a struct member is illegal")
1005 }
1006 $future(..) => {
1008 bail!("Casting a future into a struct member is illegal")
1009 }
1010 _ => {
1012 bail!("Casting this value type into a struct member is illegal")
1013 }
1014 };
1015 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 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 Ok(circuit::Plaintext::Struct(members, Default::default()))
1065 }
1066
1067 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 if inputs.len() < N::MIN_ARRAY_ELEMENTS {
1077 bail!("Casting to an array requires at least {} operand", N::MIN_ARRAY_ELEMENTS)
1078 }
1079
1080 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 let mut elements = Vec::with_capacity(inputs.len());
1092 for element in inputs.iter() {
1093 let plaintext = match element {
1095 Value::Plaintext(plaintext) => {
1096 stack.matches_plaintext(plaintext, array_type.next_element_type())?;
1098 plaintext.clone()
1100 }
1101 Value::Record(..) => bail!("Casting a record into an array element is illegal"),
1103 Value::Future(..) => bail!("Casting a future into an array element is illegal"),
1105 Value::DynamicRecord(..) => bail!("Casting a dynamic record into an array element is illegal"),
1107 Value::DynamicFuture(..) => bail!("Casting a dynamic future into an array element is illegal"),
1109 };
1110 elements.push(plaintext);
1112 }
1113
1114 let array = Plaintext::Array(elements, Default::default());
1116 registers.store(stack, &self.destination, Value::Plaintext(array))
1118 }
1119}
1120
1121impl<N: Network, const VARIANT: u8> Parser for CastOperation<N, VARIANT> {
1122 fn parse(string: &str) -> ParserResult<Self> {
1124 fn parse_operand<N: Network>(string: &str) -> ParserResult<Operand<N>> {
1126 let (string, _) = Sanitizer::parse_whitespaces(string)?;
1128 Operand::parse(string)
1130 }
1131
1132 let (string, _) = tag(*Self::opcode())(string)?;
1134 let (string, operands) = many1(parse_operand)(string)?;
1136 let (string, _) = Sanitizer::parse_whitespaces(string)?;
1138 let (string, _) = tag("into")(string)?;
1140 let (string, _) = Sanitizer::parse_whitespaces(string)?;
1142 let (string, destination) = Register::parse(string)?;
1144 let (string, _) = Sanitizer::parse_whitespaces(string)?;
1146 let (string, _) = tag("as")(string)?;
1148 let (string, _) = Sanitizer::parse_whitespaces(string)?;
1150 let (string, cast_type) = CastType::parse(string)?;
1152 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 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 #[inline]
1184 fn from_str(string: &str) -> Result<Self> {
1185 match Self::parse(string) {
1186 Ok((remainder, object)) => {
1187 ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
1189 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 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 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1207 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 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 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
1230 let mut num_operands = u8::read_le(&mut reader)? as usize;
1232 if num_operands == u8::MAX as usize {
1234 num_operands = u16::read_le(&mut reader)? as usize
1235 }
1236
1237 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 let mut operands = Vec::with_capacity(num_operands);
1249 for _ in 0..num_operands {
1251 operands.push(Operand::read_le(&mut reader)?);
1252 }
1253
1254 let destination = Register::read_le(&mut reader)?;
1256
1257 let cast_type = CastType::read_le(&mut reader)?;
1259
1260 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 Self::validate_dynamic_record_cast(&cast_type, &operands, &destination).map_err(|e| error(e.to_string()))?;
1276
1277 Ok(Self { operands, destination, cast_type })
1279 }
1280}
1281
1282impl<N: Network, const VARIANT: u8> ToBytes for CastOperation<N, VARIANT> {
1283 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
1285 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 if self.operands.len() >= u8::MAX as usize {
1301 u8::MAX.write_le(&mut writer)?;
1303 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 self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
1310 self.destination.write_le(&mut writer)?;
1312 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 "cast into r1 as dynamic.record",
1428 "cast r0 r1 into r2 as dynamic.record",
1430 "cast r0 r1 r2 into r3 as dynamic.record",
1432 "cast r0 into as dynamic.record",
1434 "cast r0 as dynamic.record",
1436 "cast r0.owner into r1 as dynamic.record",
1438 "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 let cast_type = CastType::<CurrentNetwork>::Plaintext(PlaintextType::Literal(LiteralType::Identifier));
1455 assert!(cast_type.contains_identifier_type().unwrap());
1456
1457 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 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 let (_, cast) = Cast::<CurrentNetwork>::parse("cast r0 into r1 as identifier").unwrap();
1476 assert!(cast.cast_type().contains_identifier_type().unwrap());
1477
1478 let (_, cast) = Cast::<CurrentNetwork>::parse("cast r0 into r1 as field").unwrap();
1480 assert!(!cast.cast_type().contains_identifier_type().unwrap());
1481
1482 let (_, cast) = CastLossy::<CurrentNetwork>::parse("cast.lossy r0 into r1 as identifier").unwrap();
1484 assert!(cast.cast_type().contains_identifier_type().unwrap());
1485 }
1486}