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