/ synthesizer / program / tests / instruction / deserialize.rs
deserialize.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use crate::helpers::sample::{sample_finalize_registers, sample_registers};
 17  
 18  use alphavm_synthesizer_process::{Process, Stack};
 19  use alphavm_synthesizer_program::{
 20      DeserializeBits,
 21      DeserializeBitsRaw,
 22      DeserializeInstruction,
 23      DeserializeVariant,
 24      Opcode,
 25      Operand,
 26      Program,
 27      RegistersCircuit as _,
 28      RegistersTrait as _,
 29  };
 30  use circuit::{AleoV0, Eject};
 31  use console::{
 32      network::MainnetV0,
 33      prelude::*,
 34      program::{ArrayType, Identifier, LiteralType, Plaintext, PlaintextType, Register, U32, Value},
 35  };
 36  
 37  type CurrentNetwork = MainnetV0;
 38  type CurrentAleo = AleoV0;
 39  
 40  const ITERATIONS: usize = 25;
 41  
 42  /// Samples the stack. Note: Do not replicate this for real program use, it is insecure.
 43  #[allow(clippy::type_complexity)]
 44  fn sample_stack(
 45      opcode: Opcode,
 46      type_: &PlaintextType<CurrentNetwork>,
 47      bits: &ArrayType<CurrentNetwork>,
 48      mode: circuit::Mode,
 49  ) -> Result<(Stack<CurrentNetwork>, Vec<Operand<CurrentNetwork>>, Register<CurrentNetwork>)> {
 50      // Initialize the opcode.
 51      let opcode = opcode.to_string();
 52  
 53      // Initialize the function name.
 54      let function_name = Identifier::<CurrentNetwork>::from_str("run")?;
 55  
 56      // Initialize the registers.
 57      let r0 = Register::Locator(0);
 58      let r1 = Register::Locator(1);
 59  
 60      // Initialize the program.
 61      let program = Program::from_str(&format!(
 62          "program testing.alpha;
 63              function {function_name}:
 64                  input {r0} as {bits}.{mode};
 65                  {opcode} {r0} ({bits}) into {r1} ({type_});
 66                  async {function_name} {r0} into r2;
 67                  output r2 as testing.alpha/{function_name}.future;
 68              finalize {function_name}:
 69                  input {r0} as {bits}.public;
 70                  {opcode} {r0} ({bits}) into {r1} ({type_});
 71          "
 72      ))?;
 73  
 74      // Initialize the operands.
 75      let operands = vec![Operand::Register(r0)];
 76  
 77      // Initialize the stack.
 78      let stack = Stack::new(&Process::load()?, &program)?;
 79  
 80      Ok((stack, operands, r1))
 81  }
 82  
 83  fn check_deserialize<const VARIANT: u8>(
 84      operation: impl FnOnce(
 85          Vec<Operand<CurrentNetwork>>,
 86          ArrayType<CurrentNetwork>,
 87          Register<CurrentNetwork>,
 88          PlaintextType<CurrentNetwork>,
 89      ) -> DeserializeInstruction<CurrentNetwork, VARIANT>,
 90      opcode: Opcode,
 91      type_: &PlaintextType<CurrentNetwork>,
 92      mode: &circuit::Mode,
 93      iterations: usize,
 94  ) {
 95      // Initalize an RNG.
 96      let rng = &mut TestRng::default();
 97  
 98      // Struct definitions are not supported.
 99      let fail_get_struct = |_: &Identifier<CurrentNetwork>| bail!("structs are not supported");
100  
101      // Get the size in bits.
102      let size_in_bits = match VARIANT {
103          0 => type_.size_in_bits(&fail_get_struct).unwrap(),
104          1 => type_.size_in_bits_raw(&fail_get_struct).unwrap(),
105          _ => panic!("Invalid 'deserialize' variant"),
106      };
107      let size_in_bits = u32::try_from(size_in_bits).unwrap();
108  
109      println!("Checking '{opcode}' for '{type_}.{mode}' to [boolean; {size_in_bits}u32]");
110  
111      // Construct the array type.
112      let bits_type = ArrayType::new(PlaintextType::Literal(LiteralType::Boolean), vec![U32::new(size_in_bits)]).unwrap();
113  
114      // Initialize the stack.
115      let (stack, operands, destination) = sample_stack(opcode, type_, &bits_type, *mode).unwrap();
116  
117      // Initialize the operation.
118      let operation = operation(operands, bits_type, destination.clone(), type_.clone());
119      // Initialize the function name.
120      let function_name = Identifier::from_str("run").unwrap();
121      // Initialize a destination operand.
122      let destination_operand = Operand::Register(destination);
123  
124      // Run the test for a desired number of iterations.
125      for _ in 0..iterations {
126          // Sample the plaintext.
127          let plaintext = stack.sample_plaintext(type_, rng).unwrap();
128  
129          // Get the bits of the plaintext.
130          let bits = match VARIANT {
131              0 => plaintext.to_bits_le(),
132              1 => plaintext.to_bits_raw_le(),
133              _ => panic!("Invalid 'deserialize' variant"),
134          };
135  
136          // Check that the number of bits matches.
137          assert_eq!(bits.len(), size_in_bits as usize, "The number of bits does not match the expected size");
138  
139          // Construct the bit array input.
140          let bit_array = Plaintext::from_bit_array(bits, size_in_bits).unwrap();
141  
142          // Attempt to evaluate the valid operand case.
143          let mut evaluate_registers =
144              sample_registers(&stack, &function_name, &[(Value::Plaintext(bit_array.clone()), None)]).unwrap();
145          let result_a = operation.evaluate(&stack, &mut evaluate_registers);
146  
147          // Attempt to execute the valid operand case.
148          let mut execute_registers =
149              sample_registers(&stack, &function_name, &[(Value::Plaintext(bit_array.clone()), Some(*mode))]).unwrap();
150          let result_b = operation.execute::<CurrentAleo>(&stack, &mut execute_registers);
151  
152          // Attempt to finalize the valid operand case.
153          let mut finalize_registers = sample_finalize_registers(&stack, &function_name, &[bit_array]).unwrap();
154          let result_c = operation.finalize(&stack, &mut finalize_registers);
155  
156          // Check that either all operations failed, or all operations succeeded.
157          let all_failed = result_a.is_err() && result_b.is_err() && result_c.is_err();
158          let all_succeeded = result_a.is_ok() && result_b.is_ok() && result_c.is_ok();
159          assert!(
160              all_failed || all_succeeded,
161              "The results of the evaluation, execution, and finalization should either all succeed or all fail"
162          );
163  
164          // If all operations succeeded, check that the outputs are consistent.
165          if all_succeeded {
166              // Retrieve the output of evaluation.
167              let output_a = evaluate_registers.load(&stack, &destination_operand).unwrap();
168  
169              // Retrieve the output of execution.
170              let output_b = execute_registers.load_circuit(&stack, &destination_operand).unwrap();
171  
172              // Retrieve the output of finalization.
173              let output_c = finalize_registers.load(&stack, &destination_operand).unwrap();
174  
175              // Check that the outputs are consistent.
176              assert_eq!(
177                  output_a,
178                  output_b.eject_value(),
179                  "The results of the evaluation and execution are inconsistent"
180              );
181              assert_eq!(output_a, output_c, "The results of the evaluation and finalization are inconsistent");
182  
183              // Check that the output type is consistent with the declared type.
184              match output_a {
185                  Value::Plaintext(output_plaintext) => {
186                      // Check that the output plaintext matches the input.
187                      assert_eq!(output_plaintext, plaintext, "The output value does not match the input type");
188                  }
189                  _ => unreachable!("The output type is inconsistent with the declared type"),
190              }
191          }
192          // Reset the circuit.
193          <CurrentAleo as circuit::Environment>::reset();
194      }
195  }
196  
197  // Get the types to be tested.
198  fn test_types(variant: DeserializeVariant) -> Vec<PlaintextType<CurrentNetwork>> {
199      let mut types = vec![
200          PlaintextType::Literal(LiteralType::Address),
201          PlaintextType::Literal(LiteralType::Boolean),
202          PlaintextType::Literal(LiteralType::Field),
203          PlaintextType::Literal(LiteralType::Group),
204          PlaintextType::Literal(LiteralType::I8),
205          PlaintextType::Literal(LiteralType::I16),
206          PlaintextType::Literal(LiteralType::I32),
207          PlaintextType::Literal(LiteralType::I64),
208          PlaintextType::Literal(LiteralType::I128),
209          PlaintextType::Literal(LiteralType::U8),
210          PlaintextType::Literal(LiteralType::U16),
211          PlaintextType::Literal(LiteralType::U32),
212          PlaintextType::Literal(LiteralType::U64),
213          PlaintextType::Literal(LiteralType::U128),
214          PlaintextType::Literal(LiteralType::Scalar),
215          PlaintextType::Array(ArrayType::new(PlaintextType::Literal(LiteralType::U8), vec![U32::new(8)]).unwrap()),
216      ];
217  
218      // Add additional types for the raw variant.
219      if variant == DeserializeVariant::FromBitsRaw {
220          types.push(PlaintextType::Array(
221              ArrayType::new(PlaintextType::Literal(LiteralType::U8), vec![U32::new(32)]).unwrap(),
222          ))
223      }
224  
225      types
226  }
227  
228  macro_rules! test_deserialize {
229          ($name: tt, $deserialize:ident, $variant:ident, $iterations:expr) => {
230              paste::paste! {
231                  #[test]
232                  fn [<test _ $name _ is _ consistent>]() {
233                      // Initialize the operation.
234                      let operation = |operands, operand_type, destination, destination_type| $deserialize::<CurrentNetwork>::new(operands, operand_type, destination, destination_type).unwrap();
235                      // Initialize the opcode.
236                      let opcode = $deserialize::<CurrentNetwork>::opcode();
237  
238                      // Prepare the test.
239                      let modes = [circuit::Mode::Public, circuit::Mode::Private];
240  
241                      for mode in modes.iter() {
242                          for type_ in test_types(DeserializeVariant::$variant).iter() {
243                              check_deserialize(
244                                  operation,
245                                  opcode,
246                                  type_,
247                                  mode,
248                                  $iterations
249                              );
250                          }
251                      }
252                  }
253              }
254          };
255      }
256  
257  test_deserialize!(deserialize_bits, DeserializeBits, FromBits, ITERATIONS);
258  test_deserialize!(deserialize_bits_raw, DeserializeBitsRaw, FromBitsRaw, ITERATIONS);