From cd79d8d25a6fc67df5e07c9fd16d3d758f9873f0 Mon Sep 17 00:00:00 2001 From: Thomas Coratger Date: Mon, 21 Jul 2025 23:14:46 +0200 Subject: [PATCH] memory: design skeleton with simplest operations --- Cargo.toml | 3 + crates/leanVm/Cargo.toml | 5 + crates/leanVm/src/memory/address.rs | 28 +++ crates/leanVm/src/memory/cell.rs | 243 +++++++++++++++++----- crates/leanVm/src/memory/error.rs | 34 +++ crates/leanVm/src/memory/mem.rs | 312 ++++++++++++++++++++++++++++ crates/leanVm/src/memory/mod.rs | 4 + crates/leanVm/src/memory/val.rs | 29 +++ 8 files changed, 609 insertions(+), 49 deletions(-) create mode 100644 crates/leanVm/src/memory/address.rs create mode 100644 crates/leanVm/src/memory/error.rs create mode 100644 crates/leanVm/src/memory/mem.rs create mode 100644 crates/leanVm/src/memory/val.rs diff --git a/Cargo.toml b/Cargo.toml index 30e94f24..3ad2b1d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,6 @@ lean-vm = { path = "crates/leanVm" } p3-field = { git = "https://github.com/Plonky3/Plonky3.git", rev = "d0c4a36" } p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3.git", rev = "d0c4a36" } p3-koala-bear = { git = "https://github.com/Plonky3/Plonky3.git", rev = "d0c4a36" } + +thiserror = "2.0" +proptest = "1.7" diff --git a/crates/leanVm/Cargo.toml b/crates/leanVm/Cargo.toml index ff15e6b3..649e0683 100644 --- a/crates/leanVm/Cargo.toml +++ b/crates/leanVm/Cargo.toml @@ -12,3 +12,8 @@ workspace = true p3-baby-bear.workspace = true p3-koala-bear.workspace = true p3-field.workspace = true + +thiserror.workspace = true + +[dev-dependencies] +proptest.workspace = true diff --git a/crates/leanVm/src/memory/address.rs b/crates/leanVm/src/memory/address.rs new file mode 100644 index 00000000..be04846b --- /dev/null +++ b/crates/leanVm/src/memory/address.rs @@ -0,0 +1,28 @@ +#[cfg(test)] +use proptest::prelude::*; + +#[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Copy, Debug, Default)] +pub struct MemoryAddress { + pub segment_index: usize, + pub offset: usize, +} + +#[cfg(test)] +impl Arbitrary for MemoryAddress { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + // segment_index fits in 29 bits + 0..((1u64 << 29) - 1) as usize, + // offset fits in 32 bits + 0..((1u64 << 32) - 1) as usize, + ) + .prop_map(|(segment_index, offset)| Self { + segment_index, + offset, + }) + .boxed() + } +} diff --git a/crates/leanVm/src/memory/cell.rs b/crates/leanVm/src/memory/cell.rs index db528267..f7b7b5ed 100644 --- a/crates/leanVm/src/memory/cell.rs +++ b/crates/leanVm/src/memory/cell.rs @@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut}; use p3_field::PrimeField64; +use super::{address::MemoryAddress, val::MemoryValue}; + /// A memory cell used by the VM for storing 64-bit field elements with metadata. /// /// Internally, the cell holds a single `u64` value. @@ -34,26 +36,13 @@ impl MemoryCell { pub(crate) const NONE_MASK: u64 = 1 << 63; /// Flag bit indicating the cell was accessed (bit 62 set). pub(crate) const ACCESS_MASK: u64 = 1 << 62; + /// Flag bit indicating the cell contains a MemoryAddress (bit 61 set). + pub(crate) const ADDRESS_MASK: u64 = 1 << 61; + /// A mask to extract only the value bits, ignoring all flags. + pub(crate) const VALUE_MASK: u64 = 0x1FFF_FFFF_FFFF_FFFF; /// Constant representing an empty cell. pub(crate) const NONE: Self = Self(Self::NONE_MASK); - /// Creates a `MemoryCell` from a field element, using its canonical `u64` representation. - /// - /// This clears any flag bits and assumes the value is valid. - pub(crate) fn from_f(value: F) -> Self - where - F: PrimeField64, - { - Self(value.as_canonical_u64()) - } - - /// Creates a raw `MemoryCell` from a `u64` value. - /// - /// Caller is responsible for ensuring no flag bits are set unless intentional. - pub(crate) const fn from_u64(value: u64) -> Self { - Self(value) - } - /// Returns true if the cell is marked as empty (`NONE`). pub(crate) const fn is_none(self) -> bool { self.0 & Self::NONE_MASK == Self::NONE_MASK @@ -73,67 +62,223 @@ impl MemoryCell { pub(crate) const fn is_accessed(self) -> bool { self.0 & Self::ACCESS_MASK == Self::ACCESS_MASK } + + pub(crate) fn value(self) -> Option> + where + MemoryValue: From, + { + self.is_some().then(|| self.into()) + } +} + +impl From> for MemoryCell +where + F: PrimeField64, +{ + fn from(value: MemoryValue) -> Self { + match value { + // If it's an integer, store its u64 representation. + // The ADDRESS_MASK bit will be 0 by default. + MemoryValue::Int(f) => Self(f.as_canonical_u64()), + + // If it's an address, pack it into the u64. + MemoryValue::Address(addr) => { + // Ensure the address components fit within their allocated bit-space. + // 29 bits for segment allows for 536+ million segments. + // 32 bits for offset allows for 4+ billion items per segment. + debug_assert!( + addr.segment_index < (1 << 29), + "Segment index out of bounds" + ); + debug_assert!(addr.offset < (1 << 32), "Offset out of bounds"); + + // Pack segment and offset into a single u64, and set the address flag. + let segment = (addr.segment_index as u64) << 32; + let offset = addr.offset as u64; + Self(segment | offset | Self::ADDRESS_MASK) + } + } + } +} + +impl From for MemoryValue +where + F: PrimeField64, +{ + fn from(cell: MemoryCell) -> Self { + // Check the address flag to determine the type of value. + if (cell.0 & MemoryCell::ADDRESS_MASK) == MemoryCell::ADDRESS_MASK { + // It's an address, so we unpack it. + let segment_index = ((cell.0 & MemoryCell::VALUE_MASK) >> 32) as usize; + // Mask for lower 32 bits + let offset = (cell.0 & 0xFFFF_FFFF) as usize; + + Self::Address(MemoryAddress { + segment_index, + offset, + }) + } else { + // It's an integer. We extract the value bits and convert to a field element. + let value_bits = cell.0 & MemoryCell::VALUE_MASK; + Self::Int(F::from_u64(value_bits)) + } + } } #[cfg(test)] mod tests { use p3_baby_bear::BabyBear; use p3_field::PrimeCharacteristicRing; + use proptest::prelude::*; use super::*; type F = BabyBear; - #[test] - fn test_from_f_and_accessors() { - let f = F::from_u64(123); - let cell = MemoryCell::from_f(f); - assert_eq!(*cell, 123); - assert!(cell.is_some()); - assert!(!cell.is_none()); - assert!(!cell.is_accessed()); - } - - #[test] - fn test_from_u64_and_flags() { - let raw = 0xFFFF_FFFF; - let cell = MemoryCell::from_u64(raw); - assert_eq!(*cell, raw); - assert!(cell.is_some()); - assert!(!cell.is_none()); - } - #[test] fn test_is_none_and_is_some() { + // A cell explicitly created as NONE should be none. let none_cell = MemoryCell::NONE; assert!(none_cell.is_none()); assert!(!none_cell.is_some()); - let some_cell = MemoryCell::from_u64(42); + // A cell with a value (even zero) should be some. + let some_cell = MemoryCell::from(MemoryValue::::Int(F::from_u64(42))); assert!(!some_cell.is_none()); assert!(some_cell.is_some()); + + let zero_cell = MemoryCell::from(MemoryValue::::Int(F::ZERO)); + assert!(!zero_cell.is_none()); + assert!(zero_cell.is_some()); } #[test] - fn test_mark_accessed_and_is_accessed() { - let mut cell = MemoryCell::from_u64(7); + fn test_mark_and_check_accessed() { + let mut cell = MemoryCell::from(MemoryValue::::Int(F::from_u64(99))); + + // Initially not accessed. assert!(!cell.is_accessed()); + + // Mark it as accessed. cell.mark_accessed(); + + // Now it should be accessed. assert!(cell.is_accessed()); + // Should not affect the NONE flag. + assert!(cell.is_some()); - // Ensure value bits are still preserved - assert_eq!(*cell & 0x3FFF_FFFF_FFFF_FFFF, 7); + // The original value should be preserved alongside the flag. + let value_without_flags = cell.0 & MemoryCell::VALUE_MASK; + assert_eq!(value_without_flags, 99); } #[test] - fn test_none_and_access_bits_do_not_conflict() { - let mut cell = MemoryCell::NONE; - assert!(cell.is_none()); - assert!(!cell.is_accessed()); + fn test_flag_interactions() { + // Mark a NONE cell as accessed + let mut none_cell = MemoryCell::NONE; + none_cell.mark_accessed(); + assert!(none_cell.is_none(), "is_none should be true after access"); + assert!(none_cell.is_accessed(), "is_accessed should be true"); + assert_eq!(none_cell.0, MemoryCell::NONE_MASK | MemoryCell::ACCESS_MASK); - // Mark accessed should not clear the NONE flag - cell.mark_accessed(); - assert!(cell.is_none()); - assert!(cell.is_accessed()); + // Mark an ADDRESS cell as accessed + let mut addr_cell = MemoryCell::from(MemoryValue::::Address(MemoryAddress { + segment_index: 1, + offset: 2, + })); + addr_cell.mark_accessed(); + assert!(addr_cell.is_some(), "Address cell should be 'some'"); + assert!( + (addr_cell.0 & MemoryCell::ADDRESS_MASK) != 0, + "Address flag should be set" + ); + assert!( + addr_cell.is_accessed(), + "Address cell should be marked accessed" + ); + } + + #[test] + fn test_value_method() { + // Test on a NONE cell. + let none_cell = MemoryCell::NONE; + assert_eq!(none_cell.value::(), None); + + // Test on a valid integer cell. + let int_val = MemoryValue::Int(F::from_u64(123)); + let int_cell = MemoryCell::from(int_val.clone()); + assert_eq!(int_cell.value(), Some(int_val)); + + // Test on a valid address cell. + let addr_val = MemoryValue::::Address(MemoryAddress { + segment_index: 5, + offset: 10, + }); + let addr_cell = MemoryCell::from(addr_val.clone()); + assert_eq!(addr_cell.value(), Some(addr_val)); + } + + #[test] + fn test_conversion_from_int_value() { + let val = MemoryValue::Int(F::from_u64(500)); + let cell = MemoryCell::from(val); + // Should just be the raw value, no flags set. + assert_eq!(cell.0, 500); + } + + #[test] + fn test_conversion_from_address_value() { + let val = MemoryValue::::Address(MemoryAddress { + segment_index: 10, + offset: 20, + }); + let cell = MemoryCell::from(val); + + // Expected packed value: 0x2000000A00000014 + // Bit 61 (ADDRESS_MASK) + segment 10 shifted by 32 + offset 20 + let expected = (10u64 << 32) | 20u64 | MemoryCell::ADDRESS_MASK; + assert_eq!(cell.0, expected); + } + + #[test] + fn test_conversion_to_int_value() { + // Raw u64 for an integer. + let int_cell = MemoryCell(42); + let val = MemoryValue::::from(int_cell); + assert_eq!(val, MemoryValue::Int(F::from_u64(42))); + + // An integer cell can also be marked accessed; the flag should be ignored. + let accessed_int_cell = MemoryCell(42 | MemoryCell::ACCESS_MASK); + let accessed_val = MemoryValue::::from(accessed_int_cell); + assert_eq!(accessed_val, MemoryValue::Int(F::from_u64(42))); + } + + #[test] + fn test_conversion_to_address_value() { + let raw_addr = (50u64 << 32) | 100u64 | MemoryCell::ADDRESS_MASK; + let addr_cell = MemoryCell(raw_addr); + let val = MemoryValue::::from(addr_cell); + + let expected = MemoryValue::Address(MemoryAddress { + segment_index: 50, + offset: 100, + }); + assert_eq!(val, expected); + } + + proptest! { + #[test] + fn proptest_roundtrip_conversion( + val in any::>() + ) { + // Convert the generated MemoryValue to a MemoryCell. + let cell = MemoryCell::from(val.clone()); + + // Convert the MemoryCell back to a MemoryValue. + let roundtrip_val = MemoryValue::::from(cell); + + // Assert that the original and round-tripped values are identical. + prop_assert_eq!(val, roundtrip_val); + } } } diff --git a/crates/leanVm/src/memory/error.rs b/crates/leanVm/src/memory/error.rs new file mode 100644 index 00000000..c171ec7b --- /dev/null +++ b/crates/leanVm/src/memory/error.rs @@ -0,0 +1,34 @@ +use std::fmt::Debug; + +use thiserror::Error; + +use super::{address::MemoryAddress, val::MemoryValue}; + +#[derive(Debug, Eq, PartialEq, Error)] +pub enum MemoryError +where + F: Debug, +{ + /// Error for when an operation targets a memory segment that has not been allocated. + #[error( + "Memory access out of bounds: cannot access segment {}, as only {} segments are allocated.", + 0.0, + 0.1 + )] + UnallocatedSegment(Box<(usize, usize)>), + + /// Error for attempting to overwrite an existing, different value in a memory cell, violating write-once consistency. + #[error( + "Write-once violation at address {:?}: cannot overwrite existing value '{:?}' with new value '{:?}'.", + 0.0, + 0.1, + 0.2 + )] + InconsistentMemory(Box<(MemoryAddress, MemoryValue, MemoryValue)>), + + /// Error for when a memory operation would exceed the maximum capacity of a segment vector. + #[error( + "Memory overflow: the requested memory address is too large and exceeds the machine's capacity." + )] + VecCapacityExceeded, +} diff --git a/crates/leanVm/src/memory/mem.rs b/crates/leanVm/src/memory/mem.rs new file mode 100644 index 00000000..49cf9f6e --- /dev/null +++ b/crates/leanVm/src/memory/mem.rs @@ -0,0 +1,312 @@ +use p3_field::PrimeField64; + +use super::{address::MemoryAddress, cell::MemoryCell, error::MemoryError, val::MemoryValue}; + +#[derive(Debug, Default)] +pub struct Memory { + pub(crate) data: Vec>, +} + +impl Memory { + /// Inserts a value into a specific memory address, handling segment allocation and write-once logic. + /// + /// This function acts as the primary way to write data to the VM's memory. It ensures that memory + /// segments are correctly sized to accommodate new values and enforces the rule that a memory cell, + /// once written, cannot be altered. + /// + /// # Type Parameters + /// * `V`: A generic type that can be converted into a `MemoryValue`. This allows for flexible + /// insertion of different kinds of values (e.g., raw field elements, addresses). + /// * `F`: The `PrimeField64` type used for integer values in memory. + /// + /// # Arguments + /// * `address`: The `MemoryAddress` where the value should be stored. + /// * `value`: The value to insert, which will be converted into a `MemoryValue`. + /// + /// # Returns + /// * `Ok(())` on successful insertion. + /// * `Err(MemoryError)` if: + /// - The `address.segment_index` points to a segment that has not been allocated. + /// - The cell at the `address` already contains a different value. + /// - The operation would cause the memory segment to exceed its maximum capacity. + pub fn insert(&mut self, address: MemoryAddress, value: V) -> Result<(), MemoryError> + where + F: PrimeField64, + MemoryValue: From, + { + // Convert the input `value` into the canonical `MemoryValue` enum. + let value = MemoryValue::from(value); + // Destructure the address into its constituent parts for easier access. + let MemoryAddress { + segment_index, + offset, + } = address; + + // Attempt to get a mutable reference to the target segment. + // + // If the segment index is out of bounds, return an `UnallocatedSegment` error. + let segment = self + .data + .get_mut(segment_index) + .ok_or_else(|| MemoryError::UnallocatedSegment(Box::new((segment_index, offset))))?; + + // Check if the target offset is outside the current bounds of the segment. + if segment.len() <= offset { + // If so, the segment needs to be extended. Calculate the required new length. + // + // `checked_add` prevents an integer overflow if the offset is `usize::MAX`. + let new_len = offset + .checked_add(1) + .ok_or(MemoryError::VecCapacityExceeded)?; + + // Efficiently reserve additional capacity if needed before resizing. + segment + .try_reserve(new_len.saturating_sub(segment.capacity())) + .map_err(|_| MemoryError::VecCapacityExceeded)?; + + // Resize the segment, filling any new, intermediate cells with `MemoryCell::NONE`. + segment.resize(new_len, MemoryCell::NONE); + } + + // Now that the segment is guaranteed to be long enough, access the target cell. + // The `value()` method checks the cell's `NONE` flag. + match segment[offset].value() { + // If the cell is empty (`None`), we can write the new value. + None => segment[offset] = MemoryCell::from(value), + // If the cell already has a value (`Some`), we must check if it's the same. + Some(current_cell) => { + // If the existing value is different from the new one, it's an error. + if current_cell != value { + // Return an error to enforce the write-once, immutable memory rule. + return Err(MemoryError::InconsistentMemory(Box::new(( + address, + current_cell, + value, + )))); + } + // If the values are the same, do nothing. The operation is idempotent. + } + } + + // If all checks pass and the value is written (or was already present), return Ok. + Ok(()) + } + + /// Retrieves the value stored at a given memory address. + /// + /// This is a read-only operation that safely checks for the existence of a value + /// without causing any side effects or panics. It's the primary way to read + /// from memory in a fallible manner. + /// + /// # Arguments + /// * `address`: The `MemoryAddress` specifying the location to read from. + /// + /// # Returns + /// An `Option>` containing the value if found. This will be: + /// - `Some(MemoryValue)` if the address is valid and the cell is initialized. + /// - `None` if the segment index is out of bounds, the offset is out of bounds + /// for the given segment, or the memory cell at the address is uninitialized (`NONE`). + pub(crate) fn get(&self, address: MemoryAddress) -> Option> + where + F: PrimeField64, + MemoryValue: From, + { + let MemoryAddress { + segment_index, + offset, + } = address; + + // The following chain of operations safely retrieves the value. It will short-circuit + // and return `None` at the first step that fails: + // 1. `None` if the segment does not exist. + // 2. `None` if the offset is out of bounds for the segment. + // 3. `None` if the memory cell is present but uninitialized (is `NONE`). + self.data + .get(segment_index) + .and_then(|segment| segment.get(offset)) + .and_then(|mem_cell| mem_cell.value()) + } +} + +#[cfg(test)] +mod tests { + use p3_baby_bear::BabyBear; + use p3_field::PrimeCharacteristicRing; + + use super::*; + + type F = BabyBear; + + /// Helper function to create a Memory instance with a specified number of empty segments. + fn create_memory_with_segments(num_segments: usize) -> Memory { + let mut memory = Memory::default(); + for _ in 0..num_segments { + memory.data.push(Vec::new()); + } + memory + } + + #[test] + fn test_insert_successful_at_start_of_segment() { + let mut memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 0, + }; + let val = MemoryValue::::Int(F::from_u64(100)); + + assert!(memory.insert(addr, val.clone()).is_ok()); + assert_eq!(memory.get(addr), Some(val)); + } + + #[test] + fn test_insert_successful_at_offset_creates_gap() { + let mut memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 5, + }; + let val = MemoryValue::::Int(F::from_u64(200)); + + assert!(memory.insert(addr, val.clone()).is_ok()); + + // Verify the segment was resized and padded with NONE. + assert_eq!(memory.data[0].len(), 6); + assert!(memory.data[0][4].is_none()); + assert!(memory.data[0][5].is_some()); + + // Verify the inserted value and a value in the gap. + assert_eq!(memory.get(addr), Some(val)); + assert_eq!( + memory.get::(MemoryAddress { + segment_index: 0, + offset: 3 + }), + None + ); + } + + #[test] + fn test_insert_same_value_is_ok() { + let mut memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 2, + }; + let val = MemoryValue::::Int(F::from_u64(300)); + + // First insert should succeed. + assert!(memory.insert(addr, val.clone()).is_ok()); + // Inserting the exact same value again should also succeed. + assert!(memory.insert(addr, val.clone()).is_ok()); + + assert_eq!(memory.get(addr), Some(val)); + } + + #[test] + fn test_insert_fails_on_unallocated_segment() { + let mut memory = create_memory_with_segments(1); // Only segment 0 exists. + let addr = MemoryAddress { + segment_index: 1, + offset: 0, + }; + let val = MemoryValue::::Int(F::from_u64(400)); + + let err = memory.insert(addr, val).unwrap_err(); + assert_eq!(err, MemoryError::UnallocatedSegment(Box::new((1, 0)))); + } + + #[test] + fn test_insert_fails_on_inconsistent_write() { + let mut memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 0, + }; + let val1 = MemoryValue::::Int(F::from_u64(500)); + let val2 = MemoryValue::::Int(F::from_u64(600)); + + // First write is OK. + memory.insert(addr, val1.clone()).unwrap(); + + // Second write with a different value should fail. + let err = memory.insert(addr, val2.clone()).unwrap_err(); + assert_eq!( + err, + MemoryError::InconsistentMemory(Box::new((addr, val1, val2))) + ); + } + + #[test] + fn test_insert_address_value() { + let mut memory = create_memory_with_segments(2); + let addr_to_insert_at = MemoryAddress { + segment_index: 0, + offset: 1, + }; + let address_value = MemoryAddress { + segment_index: 1, + offset: 10, + }; + let val = MemoryValue::::Address(address_value); + + assert!(memory.insert(addr_to_insert_at, val.clone()).is_ok()); + assert_eq!(memory.get(addr_to_insert_at), Some(val)); + } + + #[test] + fn test_get_successful() { + let mut memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 0, + }; + let val = MemoryValue::::Int(F::from_u64(123)); + memory.insert(addr, val.clone()).unwrap(); + + assert_eq!(memory.get(addr), Some(val)); + } + + #[test] + fn test_get_returns_none_for_unallocated_segment() { + let memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 1, + offset: 0, + }; // Segment 1 does not exist. + + assert_eq!(memory.get::(addr), None); + } + + #[test] + fn test_get_returns_none_for_out_of_bounds_offset() { + let memory = create_memory_with_segments(1); + let addr = MemoryAddress { + segment_index: 0, + offset: 100, + }; // Offset 100 is out of bounds. + + assert_eq!(memory.get::(addr), None); + } + + #[test] + fn test_get_returns_none_for_gap_in_memory() { + let mut memory = create_memory_with_segments(1); + // Insert at offset 5, creating a gap from 0 to 4. + memory + .insert( + MemoryAddress { + segment_index: 0, + offset: 5, + }, + MemoryValue::::Int(F::ONE), + ) + .unwrap(); + + let gap_addr = MemoryAddress { + segment_index: 0, + offset: 2, + }; + assert_eq!(memory.get::(gap_addr), None); + } +} diff --git a/crates/leanVm/src/memory/mod.rs b/crates/leanVm/src/memory/mod.rs index 48433da8..7223b140 100644 --- a/crates/leanVm/src/memory/mod.rs +++ b/crates/leanVm/src/memory/mod.rs @@ -1 +1,5 @@ +pub mod address; pub mod cell; +pub mod error; +pub mod mem; +pub mod val; diff --git a/crates/leanVm/src/memory/val.rs b/crates/leanVm/src/memory/val.rs new file mode 100644 index 00000000..094f5d81 --- /dev/null +++ b/crates/leanVm/src/memory/val.rs @@ -0,0 +1,29 @@ +#[cfg(test)] +use proptest::prelude::*; + +use super::address::MemoryAddress; + +#[derive(Eq, Ord, Hash, PartialEq, PartialOrd, Clone, Debug)] +pub enum MemoryValue { + Address(MemoryAddress), + Int(F), +} + +#[cfg(test)] +impl Arbitrary for MemoryValue +where + F: p3_field::PrimeField64, +{ + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + // Strategy for Int: any u64 + (0..u64::MAX).prop_map(|n| Self::Int(F::from_u64(n))), + // Strategy for Address: use the Arbitrary impl + any::().prop_map(Self::Address), + ] + .boxed() + } +}