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