diff --git a/Cargo.toml b/Cargo.toml index d7da7d1..e6f5793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ members = ["proc"] [dependencies] ahash = "0.8.11" anyhow = { version = "1.0", optional = true } -arbitrary = { version = "1", optional = true } +arbitrary = { version = "1", features = ["derive"], optional = true } base64 = { version = "0.22", optional = true } bitflags = "2.3" blake3 = { version = "1.5", optional = true } diff --git a/src/arbitrary.rs b/src/arbitrary.rs index bc7edd1..caf36cd 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -1,354 +1,130 @@ -//! +//! More specific logic for generating arbitrary data. -use arbitrary::{Arbitrary, MaxRecursionReached, Result, Unstructured}; +use arbitrary::{Arbitrary, Result, Unstructured}; -use crate::cell::{ - Cell, CellBuilder, CellFamily, CellType, DynCell, HashBytes, LevelMask, Store, MAX_BIT_LEN, -}; -use crate::merkle::{MerkleProof, MerkleUpdate}; -use crate::num::{SplitDepth, Tokens, Uint12, Uint15, Uint9, VarUint24, VarUint248, VarUint56}; +use crate::cell::{Cell, CellBuilder, DynCell, MAX_BIT_LEN}; use crate::util::ArrayVec; -impl<'a> Arbitrary<'a> for HashBytes { - #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - u.arbitrary().map(Self) - } +/// [`Arbitrary`] helper for generating trees of only ordinary cells. +#[repr(transparent)] +pub struct OrdinaryCell(pub Cell); - #[inline] - fn size_hint(_: usize) -> (usize, Option) { - (32, Some(32)) - } -} +impl std::ops::Deref for OrdinaryCell { + type Target = Cell; -impl<'a> Arbitrary<'a> for CellType { #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - static ALL_TYPES: [CellType; 5] = [ - CellType::Ordinary, - CellType::PrunedBranch, - CellType::LibraryReference, - CellType::MerkleProof, - CellType::MerkleUpdate, - ]; - - u.choose(&ALL_TYPES).copied() - } - - #[inline] - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1)) + fn deref(&self) -> &Self::Target { + &self.0 } } -impl<'a> Arbitrary<'a> for LevelMask { +impl AsRef for OrdinaryCell { #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - u.int_in_range(0b000..=0b111).map(LevelMask::new) + fn as_ref(&self) -> &DynCell { + self.0.as_ref() } +} +impl From for Cell { #[inline] - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1)) + fn from(value: OrdinaryCell) -> Self { + value.0 } } -impl<'a> Arbitrary<'a> for Cell { - #[inline] +impl<'a> Arbitrary<'a> for OrdinaryCell { fn arbitrary(u: &mut Unstructured<'a>) -> Result { - Ok(CellBuilder::arbitrary(u)?.build().unwrap()) + let OrdinaryCellBuilder(b) = u.arbitrary()?; + Ok(Self(b.build().unwrap())) } #[inline] fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() - } - - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - ::try_size_hint(depth) - } -} - -impl<'a> Arbitrary<'a> for CellBuilder { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - match CellType::arbitrary(u)? { - CellType::Ordinary => { - let bit_len = u.int_in_range(0..=MAX_BIT_LEN)?; - let refs = u.int_in_range(0..=4)?; - - let mut b = CellBuilder::new(); - b.store_raw(u.bytes(bit_len.div_ceil(8) as _)?, bit_len) - .unwrap(); - - let mut children = ArrayVec::::new(); - for i in 0..refs as u8 { - let cell = 'cell: { - if i > 0 { - // Allow to reuse cells. - if let Some(i) = u.int_in_range(0..=i)?.checked_sub(1) { - break 'cell children.get(i).cloned().unwrap(); - } - } - - Cell::arbitrary(u).and_then(check_max_depth)? - }; - - b.store_reference(cell.clone()).unwrap(); - - // SAFETY: `refs` is at most 4. - unsafe { children.push(cell) }; - } - - Ok(b) - } - CellType::PrunedBranch => { - let level_mask = LevelMask::new(u.int_in_range(0b001..=0b111)?); - - let mut b = CellBuilder::new(); - b.set_exotic(true); - b.store_u16(u16::from_be_bytes([ - CellType::PrunedBranch.to_byte(), - level_mask.to_byte(), - ])) - .unwrap(); - - let level_count = level_mask.level() as usize; - - let hashes = 32 * level_count; - b.store_raw(u.bytes(hashes)?, hashes as u16 * 8).unwrap(); - - for _ in 0..level_count { - b.store_u16(u.int_in_range(0..=(u16::MAX - 1))?).unwrap(); - } - - Ok(b) - } - CellType::LibraryReference => { - let hash = u.bytes(32)?; - - let mut b = CellBuilder::new(); - b.set_exotic(true); - b.store_u8(CellType::LibraryReference.to_byte()).unwrap(); - b.store_raw(hash, 256).unwrap(); - Ok(b) - } - CellType::MerkleProof => { - let mut b = CellBuilder::new(); - MerkleProof::arbitrary(u)? - .store_into(&mut b, Cell::empty_context()) - .unwrap(); - Ok(b) - } - CellType::MerkleUpdate => { - let mut b = CellBuilder::new(); - MerkleUpdate::arbitrary(u)? - .store_into(&mut b, Cell::empty_context()) - .unwrap(); - Ok(b) - } - } - } - - fn size_hint(_: usize) -> (usize, Option) { - (2, None) - } -} - -impl<'a> Arbitrary<'a> for MerkleProof { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let cell = Cell::arbitrary(u).and_then(check_max_depth)?; - Ok(Self { - hash: *cell.hash(0), - depth: cell.depth(0), - cell, - }) + ::size_hint(depth) } } -impl<'a> Arbitrary<'a> for MerkleUpdate { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let old = Cell::arbitrary(u).and_then(check_max_depth)?; - let new = Cell::arbitrary(u).and_then(check_max_depth)?; - Ok(Self { - old_hash: *old.hash(0), - new_hash: *new.hash(0), - old_depth: old.depth(0), - new_depth: new.depth(0), - old, - new, - }) - } - - fn size_hint(_: usize) -> (usize, Option) { - (4, None) - } -} +/// [`Arbitrary`] helper for generating trees of only ordinary cells. +#[repr(transparent)] +pub struct OrdinaryCellBuilder(pub CellBuilder); -fn check_max_depth>(cell: T) -> Result { - if has_max_depth(cell.as_ref()) { - return Err(arbitrary::Error::IncorrectFormat); - } - - Ok(cell) -} - -fn has_max_depth(cell: &DynCell) -> bool { - for level in cell.descriptor().level_mask() { - if cell.depth(level) == u16::MAX { - return false; - } - } - true -} - -macro_rules! impl_custom_int { - ($($ty:ty => $n:literal),*$(,)?) => { - $(impl<'a> Arbitrary<'a> for $ty { - #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - u.int_in_range(0..=<$ty>::MAX.into_inner()).map(<$ty>::new) - } - - #[inline] - fn size_hint(_: usize) -> (usize, Option) { - ($n, Some($n)) - } - })* - }; -} - -impl_custom_int! { - Uint9 => 2, - Uint12 => 2, - Uint15 => 2, - VarUint24 => 4, - VarUint56 => 8, - Tokens => 16, -} - -impl<'a> Arbitrary<'a> for VarUint248 { +impl From for CellBuilder { #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - Ok(Self::from_words( - u.int_in_range(0..=(u128::MAX >> 8))?, - u.arbitrary()?, - )) - } - - #[inline] - fn size_hint(_: usize) -> (usize, Option) { - (32, Some(32)) + fn from(value: OrdinaryCellBuilder) -> Self { + value.0 } } -impl<'a> Arbitrary<'a> for SplitDepth { - #[inline] +impl<'a> Arbitrary<'a> for OrdinaryCellBuilder { fn arbitrary(u: &mut Unstructured<'a>) -> Result { - const MIN: u8 = SplitDepth::MIN.into_bit_len() as u8; - const MAX: u8 = SplitDepth::MAX.into_bit_len() as u8; - Ok(Self::new(u.int_in_range(MIN..=MAX)?).unwrap()) - } + let bit_len = u.int_in_range(0..=MAX_BIT_LEN)?; + let refs = u.int_in_range(0..=4)?; + + let mut b = CellBuilder::new(); + b.store_raw(u.bytes(bit_len.div_ceil(8) as _)?, bit_len) + .unwrap(); + + let mut children = ArrayVec::::new(); + for i in 0..refs as u8 { + let cell = 'cell: { + if i > 0 { + // Allow to reuse cells. + if let Some(i) = u.int_in_range(0..=i)?.checked_sub(1) { + break 'cell children.get(i).cloned().unwrap(); + } + } - fn size_hint(_: usize) -> (usize, Option) { - (1, Some(1)) - } -} + u.arbitrary::().and_then(check_max_depth)?.0 + }; -#[cfg(feature = "models")] -impl<'a> Arbitrary<'a> for crate::models::Anycast { - #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let split_depth = SplitDepth::arbitrary(u)?; - let bit_len = split_depth.into_bit_len(); + b.store_reference(cell.clone()).unwrap(); - let bytes = u.bytes(bit_len.div_ceil(8) as _)?; + // SAFETY: `refs` is at most 4. + unsafe { children.push(cell) }; + } - let b = CellBuilder::from_raw_data(&bytes, bit_len).unwrap(); - Ok(Self::from_slice(&b.as_data_slice()).unwrap()) + Ok(Self(b)) } #[inline] fn size_hint(_: usize) -> (usize, Option) { - (2, Some(5)) + (3, None) } } +/// [`Arbitrary`] helper for generating a "real-life" balance. #[cfg(feature = "models")] -impl<'a> Arbitrary<'a> for crate::models::StdAddr { - #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - Ok(Self { - anycast: u.ratio(1u8, 20u8)?.then(|| u.arbitrary()).transpose()?, - workchain: u.arbitrary()?, - address: u.arbitrary()?, - }) - } - - #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - arbitrary::size_hint::and( - Option::::size_hint(depth), - (33, Some(33)), - ) - } -} +#[repr(transparent)] +pub struct OrdinaryCurrencyCollection(pub crate::models::CurrencyCollection); #[cfg(feature = "models")] -impl<'a> Arbitrary<'a> for crate::models::VarAddr { +impl From for crate::models::CurrencyCollection { #[inline] - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let anycast = u.ratio(1u8, 20u8)?.then(|| u.arbitrary()).transpose()?; - let address_len = u.arbitrary::()?; - let workchain = u.arbitrary()?; - - let bit_len = address_len.into_inner() as usize; - let mut address = u.bytes(bit_len.div_ceil(8))?.to_vec(); - if let Some(last_byte) = address.last_mut() { - let rem = bit_len % 8; - if rem != 0 { - *last_byte &= u8::MAX << (8 - rem); - } - } - - Ok(Self { - anycast, - address_len, - workchain, - address, - }) - } - - #[inline] - fn size_hint(_: usize) -> (usize, Option) { - (1 + 2 + 4, None) + fn from(value: OrdinaryCurrencyCollection) -> Self { + value.0 } } #[cfg(feature = "models")] -impl<'a> Arbitrary<'a> for crate::models::IntAddr { +impl<'a> Arbitrary<'a> for OrdinaryCurrencyCollection { #[inline] fn arbitrary(u: &mut Unstructured<'a>) -> Result { - if u.ratio(1u8, 20u8)? { - u.arbitrary().map(Self::Var) - } else { - u.arbitrary().map(Self::Std) - } + Ok(Self(crate::models::CurrencyCollection { + tokens: u.arbitrary()?, + other: crate::models::ExtraCurrencyCollection::new(), + })) } #[inline] fn size_hint(depth: usize) -> (usize, Option) { - Self::try_size_hint(depth).unwrap_or_default() + ::size_hint(depth) } +} - #[inline] - fn try_size_hint(depth: usize) -> Result<(usize, Option), MaxRecursionReached> { - Ok(arbitrary::size_hint::and( - ::try_size_hint(depth)?, - arbitrary::size_hint::or( - crate::models::StdAddr::size_hint(depth), - crate::models::VarAddr::size_hint(depth), - ), - )) +/// Returns the argument if it doesn't have levels with max depth. +pub fn check_max_depth>(cell: T) -> Result { + if cell.as_ref().has_max_depth() { + return Err(arbitrary::Error::IncorrectFormat); } + Ok(cell) } diff --git a/src/cell/builder.rs b/src/cell/builder.rs index f7f4e54..c5c3622 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -1083,6 +1083,94 @@ impl CellBuilder { } } +/// Generates a fully random builder with any type of child cells. +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for CellBuilder { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + match u.arbitrary::()? { + CellType::Ordinary => { + let bit_len = u.int_in_range(0..=MAX_BIT_LEN)?; + let refs = u.int_in_range(0..=4)?; + + let mut b = CellBuilder::new(); + b.store_raw(u.bytes(bit_len.div_ceil(8) as _)?, bit_len) + .unwrap(); + + let mut children = ArrayVec::::new(); + for i in 0..refs as u8 { + let cell = 'cell: { + if i > 0 { + // Allow to reuse cells. + if let Some(i) = u.int_in_range(0..=i)?.checked_sub(1) { + break 'cell children.get(i).cloned().unwrap(); + } + } + + u.arbitrary::() + .and_then(crate::arbitrary::check_max_depth)? + }; + + b.store_reference(cell.clone()).unwrap(); + + // SAFETY: `refs` is at most 4. + unsafe { children.push(cell) }; + } + + Ok(b) + } + CellType::PrunedBranch => { + let level_mask = LevelMask::new(u.int_in_range(0b001..=0b111)?); + + let mut b = CellBuilder::new(); + b.set_exotic(true); + b.store_u16(u16::from_be_bytes([ + CellType::PrunedBranch.to_byte(), + level_mask.to_byte(), + ])) + .unwrap(); + + let level_count = level_mask.level() as usize; + + let hashes = 32 * level_count; + b.store_raw(u.bytes(hashes)?, hashes as u16 * 8).unwrap(); + + for _ in 0..level_count { + b.store_u16(u.int_in_range(0..=(u16::MAX - 1))?).unwrap(); + } + + Ok(b) + } + CellType::LibraryReference => { + let hash = u.bytes(32)?; + + let mut b = CellBuilder::new(); + b.set_exotic(true); + b.store_u8(CellType::LibraryReference.to_byte()).unwrap(); + b.store_raw(hash, 256).unwrap(); + Ok(b) + } + CellType::MerkleProof => { + let mut b = CellBuilder::new(); + u.arbitrary::()? + .store_into(&mut b, Cell::empty_context()) + .unwrap(); + Ok(b) + } + CellType::MerkleUpdate => { + let mut b = CellBuilder::new(); + u.arbitrary::()? + .store_into(&mut b, Cell::empty_context()) + .unwrap(); + Ok(b) + } + } + } + + fn size_hint(_: usize) -> (usize, Option) { + (3, None) + } +} + /// Builder for constructing cell references array. /// /// Can be used later for [`CellBuilder::set_references`]. diff --git a/src/cell/mod.rs b/src/cell/mod.rs index 96acbf9..e695721 100644 --- a/src/cell/mod.rs +++ b/src/cell/mod.rs @@ -219,6 +219,16 @@ impl DynCell { self.depth(LevelMask::MAX_LEVEL) } + /// Returns `true` if any of cell levels has the maximum depth. + pub fn has_max_depth(&self) -> bool { + for level in self.descriptor().level_mask() { + if self.depth(level) == u16::MAX { + return false; + } + } + true + } + /// Returns true if the cell is empty (no bits, no refs). pub fn is_empty(&self) -> bool { self.hash(LevelMask::MAX_LEVEL) == EMPTY_CELL_HASH @@ -382,6 +392,26 @@ impl serde::Serialize for DynCell { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Cell { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(u.arbitrary::()?.build().unwrap()) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + #[inline] + fn try_size_hint( + depth: usize, + ) -> Result<(usize, Option), arbitrary::MaxRecursionReached> { + ::try_size_hint(depth) + } +} + /// An iterator through child nodes. #[must_use = "iterators are lazy and do nothing unless consumed"] pub struct RefsIter<'a> { @@ -892,6 +922,19 @@ impl<'de> serde::Deserialize<'de> for HashBytes { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for HashBytes { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + u.arbitrary().map(Self) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (32, Some(32)) + } +} + /// Hash of an empty (0 bits of data, no refs) ordinary cell. pub static EMPTY_CELL_HASH: &HashBytes = HashBytes::wrap(&[ 0x96, 0xa2, 0x96, 0xd2, 0x24, 0xf2, 0x85, 0xc6, 0x7b, 0xee, 0x93, 0xc3, 0x0f, 0x8a, 0x30, 0x91, @@ -986,6 +1029,27 @@ impl From for u8 { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for CellType { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + static ALL_TYPES: [CellType; 5] = [ + CellType::Ordinary, + CellType::PrunedBranch, + CellType::LibraryReference, + CellType::MerkleProof, + CellType::MerkleUpdate, + ]; + + u.choose(&ALL_TYPES).copied() + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (1, Some(1)) + } +} + /// Tightly packed info about a cell. #[derive(Hash, Debug, Clone, Copy)] #[repr(C)] @@ -1254,6 +1318,18 @@ impl std::fmt::Debug for LevelMask { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for LevelMask { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + u.int_in_range(0b000..=0b111).map(Self::new) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (1, Some(1)) + } +} + /// _de Brujn_ level presence bitset iterator. #[derive(Clone)] pub struct LevelMaskIter(u8); diff --git a/src/lib.rs b/src/lib.rs index 3ff729c..51088f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ pub mod models; pub mod abi; #[cfg(feature = "arbitrary")] -mod arbitrary; +pub mod arbitrary; pub mod error; diff --git a/src/merkle/proof.rs b/src/merkle/proof.rs index cea8de5..6069c04 100644 --- a/src/merkle/proof.rs +++ b/src/merkle/proof.rs @@ -152,6 +152,22 @@ impl Store for MerkleProof { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for MerkleProof { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let cell = Cell::arbitrary(u).and_then(crate::arbitrary::check_max_depth)?; + Ok(Self { + hash: *cell.hash(0), + depth: cell.depth(0), + cell, + }) + } + + fn size_hint(_: usize) -> (usize, Option) { + (2, None) + } +} + impl MerkleProof { /// The number of data bits that the Merkle proof occupies. pub const BITS: u16 = 8 + 256 + 16; diff --git a/src/merkle/update.rs b/src/merkle/update.rs index 588d1b4..008d01e 100644 --- a/src/merkle/update.rs +++ b/src/merkle/update.rs @@ -103,6 +103,26 @@ impl Store for MerkleUpdate { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for MerkleUpdate { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let old = Cell::arbitrary(u).and_then(crate::arbitrary::check_max_depth)?; + let new = Cell::arbitrary(u).and_then(crate::arbitrary::check_max_depth)?; + Ok(Self { + old_hash: *old.hash(0), + new_hash: *new.hash(0), + old_depth: old.depth(0), + new_depth: new.depth(0), + old, + new, + }) + } + + fn size_hint(_: usize) -> (usize, Option) { + (4, None) + } +} + impl MerkleUpdate { /// The number of data bits that the Merkle update occupies. pub const BITS: u16 = 8 + (256 + 16) * 2; diff --git a/src/models/account/mod.rs b/src/models/account/mod.rs index a913bcc..0c819d6 100644 --- a/src/models/account/mod.rs +++ b/src/models/account/mod.rs @@ -12,6 +12,7 @@ use crate::models::Lazy; /// Amount of unique cells and bits for shard states. #[derive(Debug, Default, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct StorageUsed { /// Amount of unique cells. pub cells: VarUint56, @@ -64,6 +65,7 @@ impl StorageUsed { /// Amount of unique cells and bits. #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct StorageUsedShort { /// Amount of unique cells. pub cells: VarUint56, @@ -82,6 +84,7 @@ impl StorageUsedShort { /// Storage profile of an account. #[derive(Debug, Default, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct StorageInfo { /// Amount of unique cells and bits which account state occupies. pub used: StorageUsed, @@ -94,6 +97,7 @@ pub struct StorageInfo { /// Brief account status. #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum AccountStatus { /// Account exists but has not yet been deployed. Uninit = 0b00, @@ -157,6 +161,33 @@ impl ShardAccount { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ShardAccount { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let account = u.arbitrary::()?; + Ok(Self { + account: Lazy::new(&account).unwrap(), + last_trans_hash: u.arbitrary()?, + last_trans_lt: u.arbitrary()?, + }) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + fn try_size_hint( + depth: usize, + ) -> arbitrary::Result<(usize, Option), arbitrary::MaxRecursionReached> { + Ok(arbitrary::size_hint::and_all(&[ + ::try_size_hint(depth)?, + ::try_size_hint(depth)?, + ::try_size_hint(depth)?, + ])) + } +} + /// A wrapper for `Option` with customized representation. #[derive(Default, Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -257,9 +288,32 @@ impl From for OptionalAccount { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for OptionalAccount { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + u.ratio(9u8, 10u8)? + .then(|| u.arbitrary()) + .transpose() + .map(Self) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + #[inline] + fn try_size_hint( + depth: usize, + ) -> arbitrary::Result<(usize, Option), arbitrary::MaxRecursionReached> { + >::try_size_hint(depth) + } +} + /// Existing account data. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Account { /// Account address. pub address: IntAddr, @@ -277,6 +331,7 @@ pub struct Account { #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "status"))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum AccountState { /// Account exists but has not yet been deployed. Uninit, @@ -394,9 +449,57 @@ impl ExactSize for StateInit { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for StateInit { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let split_depth = u.ratio(1u8, 50u8)?.then(|| u.arbitrary()).transpose()?; + let special = u.ratio(1u8, 50u8)?.then(|| u.arbitrary()).transpose()?; + let code = u.ratio(9u8, 10u8)?.then(|| u.arbitrary()).transpose()?; + let data = u.ratio(9u8, 10u8)?.then(|| u.arbitrary()).transpose()?; + + let mut libraries = Dict::new(); + match u.arbitrary::()? { + 0..=128 => {} + n => { + for _ in 128..n { + libraries + .set(u.arbitrary::()?, u.arbitrary::()?) + .unwrap(); + } + } + } + + Ok(Self { + split_depth, + special, + code, + data, + libraries, + }) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + fn try_size_hint( + depth: usize, + ) -> arbitrary::Result<(usize, Option), arbitrary::MaxRecursionReached> { + Ok(arbitrary::size_hint::and_all(&[ + >::try_size_hint(depth)?, + >::try_size_hint(depth)?, + >::try_size_hint(depth)?, + >::try_size_hint(depth)?, + (1, None), + ])) + } +} + /// Special transactions execution flags. #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct SpecialFlags { /// Account will be called at the beginning of each block. pub tick: bool, @@ -430,6 +533,7 @@ impl<'a> Load<'a> for SpecialFlags { /// Simple TVM library. #[derive(Debug, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct SimpleLib { /// Whether this library is accessible from other accounts. pub public: bool, diff --git a/src/models/currency.rs b/src/models/currency.rs index a03526c..ea82146 100644 --- a/src/models/currency.rs +++ b/src/models/currency.rs @@ -149,6 +149,31 @@ impl AugDictExtra for CurrencyCollection { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for CurrencyCollection { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self { + tokens: u.arbitrary()?, + other: u.arbitrary()?, + }) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + #[inline] + fn try_size_hint( + depth: usize, + ) -> Result<(usize, Option), arbitrary::MaxRecursionReached> { + Ok(arbitrary::size_hint::and( + ::try_size_hint(depth)?, + ::try_size_hint(depth)?, + )) + } +} + /// Dictionary with amounts for multiple currencies. #[derive(Debug, Clone, Eq, PartialEq, Store, Load)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -300,6 +325,27 @@ impl ExactSize for ExtraCurrencyCollection { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ExtraCurrencyCollection { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let size = u.arbitrary::()?; + if size <= 128 { + Ok(Self(Dict::new())) + } else { + let mut dict = Dict::::new(); + for _ in 128..size { + dict.set(u.arbitrary::()?, u.arbitrary::()?) + .unwrap(); + } + Ok(Self(dict)) + } + } + + fn size_hint(_: usize) -> (usize, Option) { + (1, None) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/models/message/address.rs b/src/models/message/address.rs index 76931e5..bd33585 100644 --- a/src/models/message/address.rs +++ b/src/models/message/address.rs @@ -205,6 +205,33 @@ impl<'de> serde::Deserialize<'de> for IntAddr { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for IntAddr { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + if u.ratio(1u8, 20u8)? { + u.arbitrary().map(Self::Var) + } else { + u.arbitrary().map(Self::Std) + } + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + Self::try_size_hint(depth).unwrap_or_default() + } + + #[inline] + fn try_size_hint( + depth: usize, + ) -> Result<(usize, Option), arbitrary::MaxRecursionReached> { + Ok(arbitrary::size_hint::and( + ::try_size_hint(depth)?, + arbitrary::size_hint::or(StdAddr::size_hint(depth), VarAddr::size_hint(depth)), + )) + } +} + /// Standard internal address. #[derive(Debug, Default, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct StdAddr { @@ -538,6 +565,23 @@ impl<'de> serde::Deserialize<'de> for StdAddr { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for StdAddr { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self { + anycast: u.ratio(1u8, 20u8)?.then(|| u.arbitrary()).transpose()?, + workchain: u.arbitrary()?, + address: u.arbitrary()?, + }) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + arbitrary::size_hint::and(Option::::size_hint(depth), (33, Some(33))) + } +} + /// A helper struct to work with base64-encoded addresses. #[cfg(feature = "base64")] pub struct StdAddrBase64Repr; @@ -750,6 +794,37 @@ impl Addr for VarAddr { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for VarAddr { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let anycast = u.ratio(1u8, 20u8)?.then(|| u.arbitrary()).transpose()?; + let address_len = u.arbitrary::()?; + let workchain = u.arbitrary()?; + + let bit_len = address_len.into_inner() as usize; + let mut address = u.bytes(bit_len.div_ceil(8))?.to_vec(); + if let Some(last_byte) = address.last_mut() { + let rem = bit_len % 8; + if rem != 0 { + *last_byte &= u8::MAX << (8 - rem); + } + } + + Ok(Self { + anycast, + address_len, + workchain, + address, + }) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (1 + 2 + 4, None) + } +} + /// External address. /// /// ```text @@ -858,6 +933,29 @@ impl<'de> serde::Deserialize<'de> for ExtAddr { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ExtAddr { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let data_bit_len = u.arbitrary::()?; + + let bit_len = data_bit_len.into_inner() as usize; + let mut data = u.bytes(bit_len.div_ceil(8))?.to_vec(); + if let Some(last_byte) = data.last_mut() { + let rem = bit_len % 8; + if rem != 0 { + *last_byte &= u8::MAX << (8 - rem); + } + } + + Ok(Self { data_bit_len, data }) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (2, None) + } +} + /// Anycast prefix info. /// /// ```text @@ -940,6 +1038,25 @@ impl<'a> Load<'a> for Anycast { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Anycast { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let split_depth = SplitDepth::arbitrary(u)?; + let bit_len = split_depth.into_bit_len(); + + let bytes = u.bytes(bit_len.div_ceil(8) as _)?; + + let b = CellBuilder::from_raw_data(bytes, bit_len).unwrap(); + Ok(Self::from_slice(&b.as_data_slice()).unwrap()) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (2, Some(5)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/models/message/mod.rs b/src/models/message/mod.rs index 84fb5e8..9e18ebf 100644 --- a/src/models/message/mod.rs +++ b/src/models/message/mod.rs @@ -361,6 +361,7 @@ impl<'a, T: Load<'a>> Load<'a> for SliceOrCell { /// Message payload layout. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct MessageLayout { /// Whether to store state init in a child cell. pub init_to_cell: bool, @@ -509,6 +510,7 @@ impl DetailedMessageLayout { #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "ty"))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum RelaxedMsgInfo { /// Internal message info, Int(RelaxedIntMsgInfo), @@ -631,6 +633,7 @@ impl<'a> Load<'a> for RelaxedMsgInfo { /// Message type. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum MsgType { /// Internal message. Int, @@ -683,6 +686,7 @@ impl<'a> Load<'a> for MsgType { #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "ty"))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum MsgInfo { /// Internal message info, Int(IntMsgInfo), @@ -817,6 +821,7 @@ impl From for MsgInfo { /// Internal message info. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct IntMsgInfo { /// Whether IHR is disabled for the message. pub ihr_disabled: bool, @@ -912,6 +917,7 @@ impl<'a> Load<'a> for IntMsgInfo { /// Unfinished internal message info. #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RelaxedIntMsgInfo { /// Whether IHR is disabled for the message. pub ihr_disabled: bool, @@ -1007,6 +1013,7 @@ impl<'a> Load<'a> for RelaxedIntMsgInfo { /// External incoming message info. #[derive(Debug, Default, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ExtInMsgInfo { /// Optional external source address. #[cfg_attr( @@ -1062,6 +1069,7 @@ impl<'a> Load<'a> for ExtInMsgInfo { /// External outgoing message info. #[derive(Debug, Default, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct ExtOutMsgInfo { /// Internal source address. pub src: IntAddr, @@ -1114,6 +1122,7 @@ impl<'a> Load<'a> for ExtOutMsgInfo { /// Unfinalized external outgoing message info. #[derive(Debug, Default, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct RelaxedExtOutMsgInfo { /// Optional internal source address. pub src: Option, diff --git a/src/num/mod.rs b/src/num/mod.rs index b453d90..7d6bb8f 100644 --- a/src/num/mod.rs +++ b/src/num/mod.rs @@ -855,6 +855,33 @@ impl_serde!(Uint9, u16); impl_serde!(Uint12, u16); impl_serde!(Uint15, u16); +#[cfg(feature = "arbitrary")] +macro_rules! impl_arbitrary { + ($($ty:ty => $n:literal),*$(,)?) => { + $(impl<'a> arbitrary::Arbitrary<'a> for $ty { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + u.int_in_range(0..=<$ty>::MAX.into_inner()).map(<$ty>::new) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + ($n, Some($n)) + } + })* + }; +} + +#[cfg(feature = "arbitrary")] +impl_arbitrary! { + Uint9 => 2, + Uint12 => 2, + Uint15 => 2, + VarUint24 => 4, + VarUint56 => 8, + Tokens => 16, +} + /// Account split depth. Fixed-length 5-bit integer of range `1..=30` #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)] #[repr(transparent)] @@ -951,6 +978,20 @@ impl<'de> serde::Deserialize<'de> for SplitDepth { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for SplitDepth { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + const MIN: u8 = SplitDepth::MIN.into_bit_len() as u8; + const MAX: u8 = SplitDepth::MAX.into_bit_len() as u8; + Ok(Self::new(u.int_in_range(MIN..=MAX)?).unwrap()) + } + + fn size_hint(_: usize) -> (usize, Option) { + (1, Some(1)) + } +} + fn store_u128(builder: &mut CellBuilder, value: u128, mut bits: u16) -> Result<(), Error> { if let Some(high_bits) = bits.checked_sub(64) { ok!(builder.store_uint((value >> 64) as u64, high_bits)); diff --git a/src/num/varuint248.rs b/src/num/varuint248.rs index b1a4c65..26669ce 100644 --- a/src/num/varuint248.rs +++ b/src/num/varuint248.rs @@ -388,6 +388,22 @@ impl<'de> serde::Deserialize<'de> for VarUint248 { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for VarUint248 { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self::from_words( + u.int_in_range(0..=(u128::MAX >> 8))?, + u.arbitrary()?, + )) + } + + #[inline] + fn size_hint(_: usize) -> (usize, Option) { + (32, Some(32)) + } +} + impl std::ops::Add for VarUint248 { type Output = Self;