1pub mod input;
17pub use input::Input;
18
19pub mod output;
20pub use output::Output;
21
22mod bytes;
23mod merkle;
24mod serialize;
25mod string;
26
27use console::{
28 network::prelude::*,
29 program::{
30 Ciphertext,
31 Identifier,
32 InputID,
33 OutputID,
34 ProgramID,
35 Record,
36 Register,
37 Request,
38 Response,
39 TRANSITION_DEPTH,
40 ToFields,
41 TransitionLeaf,
42 TransitionPath,
43 TransitionTree,
44 Value,
45 ValueType,
46 compute_function_id,
47 },
48 types::{Address, Field, Group},
49};
50
51fn compute_output_hash<N: Network>(
53 function_id: Field<N>,
54 value: &impl ToFields<Field = Field<N>>,
55 tvk: &Field<N>,
56 num_inputs: usize,
57 index: usize,
58) -> Result<Field<N>> {
59 let index = Field::from_u16(u16::try_from(num_inputs + index)?);
60 let mut preimage = vec![function_id];
61 preimage.extend(value.to_fields()?);
62 preimage.push(*tvk);
63 preimage.push(index);
64 N::hash_psd8(&preimage)
65}
66
67#[derive(Clone, PartialEq, Eq)]
68pub struct Transition<N: Network> {
69 id: N::TransitionID,
71 program_id: ProgramID<N>,
73 function_name: Identifier<N>,
75 inputs: Vec<Input<N>>,
77 outputs: Vec<Output<N>>,
79 tpk: Group<N>,
81 tcm: Field<N>,
83 scm: Field<N>,
85}
86
87impl<N: Network> Transition<N> {
88 #[allow(clippy::too_many_arguments)]
90 pub fn new(
91 program_id: ProgramID<N>,
92 function_name: Identifier<N>,
93 inputs: Vec<Input<N>>,
94 outputs: Vec<Output<N>>,
95 tpk: Group<N>,
96 tcm: Field<N>,
97 scm: Field<N>,
98 ) -> Result<Self> {
99 let function_tree = Self::function_tree(&inputs, &outputs)?;
101 let id = N::hash_bhp512(&(*function_tree.root(), tcm).to_bits_le())?.into();
103 Ok(Self { id, program_id, function_name, inputs, outputs, tpk, tcm, scm })
105 }
106
107 pub fn from(
109 request: &Request<N>,
110 response: &Response<N>,
111 output_types: &[ValueType<N>],
112 output_registers: &[Option<Register<N>>],
113 ) -> Result<Self> {
114 let network_id = *request.network_id();
115 let program_id = *request.program_id();
116 let function_name = *request.function_name();
117 let num_inputs = request.inputs().len();
118
119 let function_id = compute_function_id(&network_id, &program_id, &function_name)?;
121
122 let construct_inputs = |input_ids: &[InputID<N>],
126 inputs: &[Value<N>],
127 caller_input_ids: Option<&[InputID<N>]>|
128 -> Result<Vec<Input<N>>> {
129 ensure!(
130 input_ids.len() == inputs.len(),
131 "Mismatched number of input IDs and inputs: {} vs. {}",
132 input_ids.len(),
133 inputs.len(),
134 );
135 if let Some(caller_ids) = caller_input_ids {
136 ensure!(
137 caller_ids.len() == inputs.len(),
138 "Mismatched number of caller input IDs and inputs: {} vs. {}",
139 caller_ids.len(),
140 inputs.len(),
141 );
142 }
143
144 input_ids
145 .iter()
146 .zip_eq(inputs)
147 .enumerate()
148 .map(|(index, (input_id, input))| {
149 let caller_input_id = caller_input_ids.map(|ids| &ids[index]);
151
152 match (input_id, input) {
154 (InputID::Constant(input_hash), Value::Plaintext(plaintext)) => {
155 let input = Input::Constant(*input_hash, Some(plaintext.clone()));
157 match input.verify(function_id, request.tcm(), index) {
159 true => Ok(input),
160 false => bail!("Malformed constant transition input: '{input}'"),
161 }
162 }
163 (InputID::Public(input_hash), Value::Plaintext(plaintext)) => {
164 let input = Input::Public(*input_hash, Some(plaintext.clone()));
166 match input.verify(function_id, request.tcm(), index) {
168 true => Ok(input),
169 false => bail!("Malformed public transition input: '{input}'"),
170 }
171 }
172 (InputID::Private(input_hash), Value::Plaintext(plaintext)) => {
173 let index = Field::from_u16(index as u16);
175 let ciphertext =
177 plaintext.encrypt_symmetric(N::hash_psd4(&[function_id, *request.tvk(), index])?)?;
178 let ciphertext_hash = N::hash_psd8(&ciphertext.to_fields()?)?;
180 ensure!(*input_hash == ciphertext_hash, "The input ciphertext hash is incorrect");
182 Ok(Input::Private(*input_hash, Some(ciphertext)))
184 }
185 (InputID::Record(_, _, _, serial_number, tag), Value::Record(..)) => {
186 if let Some(InputID::DynamicRecord(dynamic_id)) = caller_input_id {
188 Ok(Input::RecordWithDynamicID(*serial_number, *tag, *dynamic_id))
190 } else {
191 Ok(Input::Record(*serial_number, *tag))
193 }
194 }
195 (InputID::ExternalRecord(input_hash), Value::Record(..)) => {
196 if let Some(InputID::DynamicRecord(dynamic_id)) = caller_input_id {
198 Ok(Input::ExternalRecordWithDynamicID(*input_hash, *dynamic_id))
200 } else {
201 Ok(Input::ExternalRecord(*input_hash))
203 }
204 }
205 (InputID::DynamicRecord(input_hash), Value::DynamicRecord(..)) => {
206 Ok(Input::DynamicRecord(*input_hash))
208 }
209 _ => bail!("Malformed request input: {input_id:?}, {input}"),
210 }
211 })
212 .collect::<Result<Vec<_>>>()
213 };
214
215 let (caller_input_ids, caller_output_values) = if request.is_dynamic() {
217 (Some(request.to_dynamic_input_ids()?), Some(response.to_dynamic_outputs()?))
218 } else {
219 (None, None)
220 };
221
222 let inputs = construct_inputs(request.input_ids(), request.inputs(), caller_input_ids.as_deref())?;
224
225 {
227 let num_outputs = response.outputs().len();
228
229 ensure!(
230 response.output_ids().len() == num_outputs
231 && num_outputs == output_types.len()
232 && num_outputs == output_registers.len(),
233 "Mismatched number of output IDs, outputs, output types, and output registers: {} vs. {} vs. {} vs. {}",
234 response.output_ids().len(),
235 num_outputs,
236 output_types.len(),
237 output_registers.len(),
238 );
239
240 if let Some(ref caller_values) = caller_output_values {
242 ensure!(
243 caller_values.len() == num_outputs,
244 "Mismatched caller outputs and callee outputs: {} vs. {}",
245 caller_values.len(),
246 num_outputs
247 );
248 }
249 }
250
251 let outputs = itertools::izip!(response.output_ids(), response.outputs(), output_types, output_registers)
253 .enumerate()
254 .map(|(output_index, (output_id, output, output_type, output_register))| {
255 let caller_value = caller_output_values.as_ref().map(|values| &values[output_index]);
257 Self::construct_output(
258 function_id,
259 &program_id,
260 num_inputs,
261 request.tvk(),
262 request.tcm(),
263 request.signer(),
264 output_index,
265 &Some(output_id.clone()),
266 output,
267 output_type,
268 output_register,
269 caller_value,
270 )
271 })
272 .collect::<Result<Vec<_>>>()?;
273
274 let tpk = request.to_tpk();
276 let tcm = *request.tcm();
278 let scm = *request.scm();
280 Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm)
282 }
283
284 pub fn from_unchecked(
289 request: &Request<N>,
290 response: &Response<N>,
291 output_types: &[ValueType<N>],
292 output_registers: &[Option<Register<N>>],
293 ) -> Result<Self> {
294 let network_id = *request.network_id();
295 let program_id = *request.program_id();
296 let function_name = *request.function_name();
297 let num_inputs = request.inputs().len();
298
299 let function_id = compute_function_id(&network_id, &program_id, &function_name)?;
301
302 let construct_inputs = |input_ids: &[InputID<N>],
306 inputs: &[Value<N>],
307 caller_input_ids: Option<&[InputID<N>]>|
308 -> Result<Vec<Input<N>>> {
309 ensure!(
310 input_ids.len() == inputs.len(),
311 "Mismatched number of input IDs and inputs: {} vs. {}",
312 input_ids.len(),
313 inputs.len(),
314 );
315 if let Some(caller_ids) = caller_input_ids {
316 ensure!(
317 caller_ids.len() == inputs.len(),
318 "Mismatched number of caller input IDs and inputs: {} vs. {}",
319 caller_ids.len(),
320 inputs.len(),
321 );
322 }
323
324 input_ids
325 .iter()
326 .zip_eq(inputs)
327 .enumerate()
328 .map(|(index, (input_id, input))| {
329 let caller_input_id = caller_input_ids.map(|ids| &ids[index]);
331
332 match (input_id, input) {
334 (InputID::Constant(input_hash), Value::Plaintext(plaintext)) => {
335 Ok(Input::Constant(*input_hash, Some(plaintext.clone())))
337 }
338 (InputID::Public(input_hash), Value::Plaintext(plaintext)) => {
339 Ok(Input::Public(*input_hash, Some(plaintext.clone())))
341 }
342 (InputID::Private(input_hash), Value::Plaintext(plaintext)) => {
343 let index = Field::from_u16(index as u16);
345 let ciphertext =
347 plaintext.encrypt_symmetric(N::hash_psd4(&[function_id, *request.tvk(), index])?)?;
348 Ok(Input::Private(*input_hash, Some(ciphertext)))
350 }
351 (InputID::Record(_, _, _, serial_number, tag), Value::Record(..)) => {
352 if let Some(InputID::DynamicRecord(dynamic_id)) = caller_input_id {
354 Ok(Input::RecordWithDynamicID(*serial_number, *tag, *dynamic_id))
356 } else {
357 Ok(Input::Record(*serial_number, *tag))
359 }
360 }
361 (InputID::ExternalRecord(input_hash), Value::Record(..)) => {
362 if let Some(InputID::DynamicRecord(dynamic_id)) = caller_input_id {
364 Ok(Input::ExternalRecordWithDynamicID(*input_hash, *dynamic_id))
366 } else {
367 Ok(Input::ExternalRecord(*input_hash))
369 }
370 }
371 (InputID::DynamicRecord(input_hash), Value::DynamicRecord(..)) => {
372 Ok(Input::DynamicRecord(*input_hash))
374 }
375 _ => bail!("Malformed request input: {input_id:?}, {input}"),
376 }
377 })
378 .collect::<Result<Vec<_>>>()
379 };
380
381 let (caller_input_ids, caller_output_values) = if request.is_dynamic() {
383 (Some(request.to_dynamic_input_ids()?), Some(response.to_dynamic_outputs()?))
384 } else {
385 (None, None)
386 };
387
388 let inputs = construct_inputs(request.input_ids(), request.inputs(), caller_input_ids.as_deref())?;
390
391 {
393 let num_outputs = response.outputs().len();
394
395 ensure!(
396 response.output_ids().len() == num_outputs
397 && num_outputs == output_types.len()
398 && num_outputs == output_registers.len(),
399 "Mismatched number of output IDs, outputs, output types, and output registers: {} vs. {} vs. {} vs. {}",
400 response.output_ids().len(),
401 num_outputs,
402 output_types.len(),
403 output_registers.len(),
404 );
405
406 if let Some(ref caller_values) = caller_output_values {
408 ensure!(
409 caller_values.len() == num_outputs,
410 "Mismatched caller outputs and callee outputs: {} vs. {}",
411 caller_values.len(),
412 num_outputs
413 );
414 }
415 }
416
417 let outputs = itertools::izip!(response.output_ids(), response.outputs(), output_types, output_registers)
419 .enumerate()
420 .map(|(output_index, (output_id, output, output_type, output_register))| {
421 let caller_value = caller_output_values.as_ref().map(|values| &values[output_index]);
423 Self::construct_output(
424 function_id,
425 &program_id,
426 num_inputs,
427 request.tvk(),
428 request.tcm(),
429 request.signer(),
430 output_index,
431 &Some(output_id.clone()),
432 output,
433 output_type,
434 output_register,
435 caller_value,
436 )
437 })
438 .collect::<Result<Vec<_>>>()?;
439
440 let tpk = request.to_tpk();
442 let tcm = *request.tcm();
444 let scm = *request.scm();
446 Self::new(program_id, function_name, inputs, outputs, tpk, tcm, scm)
448 }
449}
450
451impl<N: Network> Transition<N> {
452 pub const fn id(&self) -> &N::TransitionID {
454 &self.id
455 }
456
457 pub const fn program_id(&self) -> &ProgramID<N> {
459 &self.program_id
460 }
461
462 pub const fn function_name(&self) -> &Identifier<N> {
464 &self.function_name
465 }
466
467 pub fn inputs(&self) -> &[Input<N>] {
469 &self.inputs
470 }
471
472 pub fn outputs(&self) -> &[Output<N>] {
474 &self.outputs
475 }
476
477 pub const fn tpk(&self) -> &Group<N> {
479 &self.tpk
480 }
481
482 pub const fn tcm(&self) -> &Field<N> {
484 &self.tcm
485 }
486
487 pub const fn scm(&self) -> &Field<N> {
489 &self.scm
490 }
491}
492
493impl<N: Network> Transition<N> {
494 #[inline]
496 pub fn is_credits(&self) -> bool {
497 self.program_id.to_string() == "credits.aleo"
498 }
499
500 #[inline]
502 pub fn is_bond_public(&self) -> bool {
503 self.inputs.len() == 3
504 && self.outputs.len() == 1
505 && self.program_id.to_string() == "credits.aleo"
506 && self.function_name.to_string() == "bond_public"
507 }
508
509 #[inline]
511 pub fn is_bond_validator(&self) -> bool {
512 self.inputs.len() == 3
513 && self.outputs.len() == 1
514 && self.program_id.to_string() == "credits.aleo"
515 && self.function_name.to_string() == "bond_validator"
516 }
517
518 #[inline]
520 pub fn is_unbond_public(&self) -> bool {
521 self.inputs.len() == 2
522 && self.outputs.len() == 1
523 && self.program_id.to_string() == "credits.aleo"
524 && self.function_name.to_string() == "unbond_public"
525 }
526
527 #[inline]
529 pub fn is_fee_private(&self) -> bool {
530 self.inputs.len() == 4
531 && self.outputs.len() == 1
532 && self.program_id.to_string() == "credits.aleo"
533 && self.function_name.to_string() == "fee_private"
534 }
535
536 #[inline]
538 pub fn is_fee_public(&self) -> bool {
539 self.inputs.len() == 3
540 && self.outputs.len() == 1
541 && self.program_id.to_string() == "credits.aleo"
542 && self.function_name.to_string() == "fee_public"
543 }
544
545 #[inline]
547 pub fn is_split(&self) -> bool {
548 self.inputs.len() == 2
549 && self.outputs.len() == 2
550 && self.program_id.to_string() == "credits.aleo"
551 && self.function_name.to_string() == "split"
552 }
553
554 #[inline]
556 pub fn is_upgrade(&self) -> bool {
557 self.inputs.len() == 1
558 && self.outputs.len() == 2
559 && self.program_id.to_string() == "credits.aleo"
560 && self.function_name.to_string() == "upgrade"
561 }
562}
563
564impl<N: Network> Transition<N> {
565 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
567 self.inputs.iter().any(|input| match input {
568 Input::Constant(_, _) => false,
569 Input::Public(_, _) => false,
570 Input::Private(_, _) => false,
571 Input::Record(input_sn, _) | Input::RecordWithDynamicID(input_sn, _, _) => input_sn == serial_number,
572 Input::ExternalRecord(_) => false,
573 Input::DynamicRecord(_) => false,
574 Input::ExternalRecordWithDynamicID(_, _) => false,
575 })
576 }
577
578 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
580 self.outputs.iter().any(|output| match output {
581 Output::Constant(_, _) => false,
582 Output::Public(_, _) => false,
583 Output::Private(_, _) => false,
584 Output::Record(output_cm, _, _, _) | Output::RecordWithDynamicID(output_cm, _, _, _, _) => {
585 output_cm == commitment
586 }
587 Output::ExternalRecord(_) => false,
588 Output::Future(_, _) => false,
589 Output::DynamicRecord(_) => false,
590 Output::ExternalRecordWithDynamicID(_, _) => false,
591 })
592 }
593}
594
595impl<N: Network> Transition<N> {
596 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
598 self.outputs.iter().find_map(|output| match output {
599 Output::Constant(_, _) => None,
600 Output::Public(_, _) => None,
601 Output::Private(_, _) => None,
602 Output::Record(output_cm, _, Some(record), _) if output_cm == commitment => Some(record),
603 Output::Record(_, _, _, _) => None,
604 Output::RecordWithDynamicID(output_cm, _, Some(record), _, _) if output_cm == commitment => Some(record),
605 Output::RecordWithDynamicID(_, _, _, _, _) => None,
606 Output::ExternalRecord(_) => None,
607 Output::Future(_, _) => None,
608 Output::DynamicRecord(_) => None,
609 Output::ExternalRecordWithDynamicID(_, _) => None,
610 })
611 }
612}
613
614impl<N: Network> Transition<N> {
615 pub fn input_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
619 self.inputs.iter().map(Input::id)
620 }
621
622 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
624 self.inputs.iter().flat_map(Input::serial_number)
625 }
626
627 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
629 self.inputs.iter().flat_map(Input::tag)
630 }
631
632 pub fn output_ids(&self) -> impl '_ + ExactSizeIterator<Item = &Field<N>> {
636 self.outputs.iter().map(Output::id)
637 }
638
639 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
641 self.outputs.iter().flat_map(Output::commitment)
642 }
643
644 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
646 self.outputs.iter().flat_map(Output::nonce)
647 }
648
649 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
651 self.outputs.iter().flat_map(Output::record)
652 }
653}
654
655impl<N: Network> Transition<N> {
656 pub fn into_id(self) -> N::TransitionID {
658 self.id
659 }
660
661 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
665 self.inputs.into_iter().flat_map(Input::into_serial_number)
666 }
667
668 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
670 self.inputs.into_iter().flat_map(Input::into_tag)
671 }
672
673 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
677 self.outputs.into_iter().flat_map(Output::into_commitment)
678 }
679
680 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
682 self.outputs.into_iter().flat_map(Output::into_nonce)
683 }
684
685 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
687 self.outputs.into_iter().flat_map(Output::into_record)
688 }
689
690 pub fn into_tpk(self) -> Group<N> {
692 self.tpk
693 }
694}
695
696impl<N: Network> Transition<N> {
700 fn construct_output(
701 function_id: Field<N>,
702 program_id: &ProgramID<N>,
703 num_inputs: usize,
704 tvk: &Field<N>,
705 tcm: &Field<N>,
706 signer: &Address<N>,
707 index: usize,
708 output_id: &Option<OutputID<N>>,
709 output: &Value<N>,
710 output_type: &ValueType<N>,
711 output_register: &Option<Register<N>>,
712 caller_value: Option<&Value<N>>,
713 ) -> Result<Output<N>> {
714 match (output_id, output) {
716 (Some(OutputID::Constant(output_hash)), Value::Plaintext(plaintext)) => {
717 let output = Output::Constant(*output_hash, Some(plaintext.clone()));
719 match output.verify(function_id, tcm, num_inputs + index) {
721 true => Ok(output),
722 false => bail!("Malformed constant transition output: '{output}'"),
723 }
724 }
725 (Some(OutputID::Public(output_hash)), Value::Plaintext(plaintext)) => {
726 let output = Output::Public(*output_hash, Some(plaintext.clone()));
728 match output.verify(function_id, tcm, num_inputs + index) {
730 true => Ok(output),
731 false => bail!("Malformed public transition output: '{output}'"),
732 }
733 }
734 (Some(OutputID::Private(output_hash)), Value::Plaintext(plaintext)) => {
735 let index = Field::from_u16(u16::try_from(num_inputs + index)?);
737 let ciphertext = plaintext.encrypt_symmetric(N::hash_psd4(&[function_id, *tvk, index])?)?;
739 let ciphertext_hash = N::hash_psd8(&ciphertext.to_fields()?)?;
741 ensure!(*output_hash == ciphertext_hash, "The output ciphertext hash is incorrect");
743 Ok(Output::Private(*output_hash, Some(ciphertext)))
745 }
746 (Some(OutputID::Record(commitment, checksum, sender_ciphertext)), Value::Record(record)) => {
747 let record_name = match output_type {
749 ValueType::Record(record_name) => record_name,
750 _ => bail!("Expected a record type at output {index}"),
752 };
753
754 let output_register = match output_register {
756 Some(output_register) => output_register,
757 None => bail!("Expected a register to be paired with a record output"),
758 };
759
760 let output_index = Field::from_u64(output_register.locator());
762 let randomizer = N::hash_to_scalar_psd2(&[*tvk, output_index])?;
764
765 let (record_ciphertext, record_view_key) = record.encrypt_symmetric(randomizer)?;
767
768 let candidate_cm = record.to_commitment(program_id, record_name, &record_view_key)?;
770 ensure!(*commitment == candidate_cm, "The output record commitment is incorrect");
772
773 let ciphertext_checksum = N::hash_bhp1024(&record_ciphertext.to_bits_le())?;
775 ensure!(*checksum == ciphertext_checksum, "The output record ciphertext checksum is incorrect");
777
778 let randomizer = N::hash_psd4(&[N::encryption_domain(), record_view_key, Field::one()])?;
780 let candidate_sender_ciphertext = (**signer).to_x_coordinate() + randomizer;
782 ensure!(
786 (*sender_ciphertext == candidate_sender_ciphertext) || sender_ciphertext.is_zero(),
787 "The output record sender ciphertext is incorrect"
788 );
789
790 if let Some(Value::DynamicRecord(dynamic_record)) = caller_value {
792 let dynamic_id = compute_output_hash(function_id, dynamic_record, tvk, num_inputs, index)?;
794 Ok(Output::RecordWithDynamicID(
796 *commitment,
797 *checksum,
798 Some(record_ciphertext),
799 Some(*sender_ciphertext),
800 dynamic_id,
801 ))
802 } else {
803 Ok(Output::Record(*commitment, *checksum, Some(record_ciphertext), Some(*sender_ciphertext)))
805 }
806 }
807 (Some(OutputID::ExternalRecord(hash)), Value::Record(record)) => {
808 let candidate_hash = compute_output_hash(function_id, record, tvk, num_inputs, index)?;
810 ensure!(*hash == candidate_hash, "The output external hash is incorrect");
812
813 if let Some(Value::DynamicRecord(dynamic_record)) = caller_value {
815 let dynamic_id = compute_output_hash(function_id, dynamic_record, tvk, num_inputs, index)?;
817 Ok(Output::ExternalRecordWithDynamicID(*hash, dynamic_id))
819 } else {
820 Ok(Output::ExternalRecord(*hash))
822 }
823 }
824 (Some(OutputID::Future(output_hash)), Value::Future(future)) => {
825 let output = Output::Future(*output_hash, Some(future.clone()));
827 match output.verify(function_id, tcm, num_inputs + index) {
829 true => Ok(output),
830 false => bail!("Malformed future transition output: '{output}'"),
831 }
832 }
833 (Some(OutputID::DynamicRecord(hash)), Value::DynamicRecord(dynamic_record)) => {
834 let candidate_hash = compute_output_hash(function_id, dynamic_record, tvk, num_inputs, index)?;
836 ensure!(*hash == candidate_hash, "The output dynamic record hash is incorrect");
838 Ok(Output::DynamicRecord(*hash))
840 }
841 (None, Value::DynamicRecord(dynamic_record)) => {
842 let hash = compute_output_hash(function_id, dynamic_record, tvk, num_inputs, index)?;
844 Ok(Output::DynamicRecord(hash))
846 }
847 _ => bail!("Malformed response output: {output_id:?}, {output}"),
848 }
849 }
850}
851
852#[cfg(test)]
853pub mod test_helpers {
854 use super::*;
855 use crate::Transaction;
856
857 type CurrentNetwork = console::network::MainnetV0;
858
859 pub(crate) fn sample_transition(rng: &mut TestRng) -> Transition<CurrentNetwork> {
861 if let Transaction::Execute(_, _, execution, _) =
862 crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0)
863 {
864 execution.into_transitions().next().unwrap()
865 } else {
866 unreachable!()
867 }
868 }
869}