1#![allow(clippy::arc_with_non_send_sync)]
11
12use crate::{DynSolType, DynSolValue};
13use alloy_primitives::{Address, B256, Function, I256, U256};
14use arbitrary::{Unstructured, size_hint};
15use core::ops::RangeInclusive;
16use proptest::{
17 collection::{VecStrategy, vec as vec_strategy},
18 prelude::*,
19 strategy::{Flatten, Map, Recursive, TupleUnion, WA},
20};
21
22const DEPTH: u32 = 16;
23const DESIRED_SIZE: u32 = 64;
24const EXPECTED_BRANCH_SIZE: u32 = 32;
25
26macro_rules! prop_oneof_cfg {
27 ($($(@[$attr:meta])* $w:expr => $x:expr,)+) => {
28 TupleUnion::new(($(
29 $(#[$attr])*
30 {
31 ($w as u32, ::alloc::sync::Arc::new($x))
32 }
33 ),+))
34 };
35}
36
37#[cfg(not(feature = "eip712"))]
38macro_rules! tuple_type_cfg {
39 (($($t:ty),+ $(,)?), $c:ty $(,)?) => {
40 ($($t,)+)
41 };
42}
43#[cfg(feature = "eip712")]
44macro_rules! tuple_type_cfg {
45 (($($t:ty),+ $(,)?), $c:ty $(,)?) => {
46 ($($t,)+ $c)
47 };
48}
49
50#[inline]
51const fn int_size(n: usize) -> usize {
52 let n = (n % 255) + 1;
53 n + (8 - (n % 8))
54}
55
56#[inline]
57#[cfg(feature = "eip712")]
58const fn ident_char(x: u8, first: bool) -> u8 {
59 let x = x % 64;
60 match x {
61 0..=25 => x + b'a',
62 26..=51 => (x - 26) + b'A',
63 52 => b'_',
64 53 => b'$',
65 _ => {
66 if first {
67 b'a'
68 } else {
69 (x - 54) + b'0'
70 }
71 }
72 }
73}
74
75fn non_empty_vec<'a, T: arbitrary::Arbitrary<'a>>(
76 u: &mut Unstructured<'a>,
77) -> arbitrary::Result<Vec<T>> {
78 let sz = u.int_in_range(1..=16u8)?;
79 let mut v = Vec::with_capacity(sz as usize);
80 for _ in 0..sz {
81 v.push(u.arbitrary()?);
82 }
83 Ok(v)
84}
85
86#[cfg(feature = "eip712")]
87struct AString(String);
88
89#[cfg(feature = "eip712")]
90impl<'a> arbitrary::Arbitrary<'a> for AString {
91 #[inline]
92 #[cfg_attr(debug_assertions, track_caller)]
93 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
94 let len = u.int_in_range(1..=128)?;
97 let mut bytes = Vec::with_capacity(len);
98 for i in 0..len {
99 bytes.push(ident_char(u.arbitrary()?, i == 0));
100 }
101 Ok(Self::new(bytes))
102 }
103
104 #[inline]
105 #[cfg_attr(debug_assertions, track_caller)]
106 fn arbitrary_take_rest(u: Unstructured<'a>) -> arbitrary::Result<Self> {
107 let mut bytes = u.take_rest().to_owned();
108 for (i, byte) in bytes.iter_mut().enumerate() {
109 *byte = ident_char(*byte, i == 0);
110 }
111 Ok(Self::new(bytes))
112 }
113
114 #[inline]
115 fn size_hint(depth: usize) -> (usize, Option<usize>) {
116 String::size_hint(depth)
117 }
118}
119
120#[cfg(feature = "eip712")]
121impl AString {
122 #[inline]
123 #[cfg_attr(debug_assertions, track_caller)]
124 fn new(bytes: Vec<u8>) -> Self {
125 debug_assert!(core::str::from_utf8(&bytes).is_ok());
126 Self(unsafe { String::from_utf8_unchecked(bytes) })
127 }
128}
129
130#[derive(Debug, arbitrary::Arbitrary)]
131enum Choice {
132 Bool,
133 Int,
134 Uint,
135 Address,
136 Function,
137 FixedBytes,
138 Bytes,
139 String,
140
141 Array,
142 FixedArray,
143 Tuple,
144 #[cfg(feature = "eip712")]
145 CustomStruct,
146}
147
148impl<'a> arbitrary::Arbitrary<'a> for DynSolType {
149 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
150 match u.arbitrary::<Choice>()? {
151 Choice::Bool => Ok(Self::Bool),
152 Choice::Int => u.arbitrary().map(int_size).map(Self::Int),
153 Choice::Uint => u.arbitrary().map(int_size).map(Self::Uint),
154 Choice::Address => Ok(Self::Address),
155 Choice::Function => Ok(Self::Function),
156 Choice::FixedBytes => Ok(Self::FixedBytes(u.int_in_range(1..=32)?)),
157 Choice::Bytes => Ok(Self::Bytes),
158 Choice::String => Ok(Self::String),
159 Choice::Array => u.arbitrary().map(Self::Array),
160 Choice::FixedArray => Ok(Self::FixedArray(u.arbitrary()?, u.int_in_range(1..=16)?)),
161 Choice::Tuple => non_empty_vec(u).map(Self::Tuple),
162 #[cfg(feature = "eip712")]
163 Choice::CustomStruct => {
164 let name = u.arbitrary::<AString>()?.0;
165 let (prop_names, tuple) =
166 u.arbitrary_iter::<(AString, Self)>()?.flatten().map(|(a, b)| (a.0, b)).unzip();
167 Ok(Self::CustomStruct { name, prop_names, tuple })
168 }
169 }
170 }
171
172 fn size_hint(depth: usize) -> (usize, Option<usize>) {
173 if depth == DEPTH as usize {
174 (0, Some(0))
175 } else {
176 size_hint::and(
177 u32::size_hint(depth),
178 size_hint::or_all(&[usize::size_hint(depth), Self::size_hint(depth + 1)]),
179 )
180 }
181 }
182}
183
184impl<'a> arbitrary::Arbitrary<'a> for DynSolValue {
185 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
186 match u.arbitrary::<DynSolType>()? {
187 #[cfg(feature = "eip712")]
189 DynSolType::CustomStruct { name, prop_names, tuple } => Ok(Self::CustomStruct {
190 name,
191 prop_names,
192 tuple: tuple
193 .iter()
194 .map(|ty| Self::arbitrary_from_type(ty, u))
195 .collect::<Result<_, _>>()?,
196 }),
197 t => Self::arbitrary_from_type(&t, u),
198 }
199 }
200
201 fn size_hint(depth: usize) -> (usize, Option<usize>) {
202 if depth == DEPTH as usize {
203 (0, Some(0))
204 } else {
205 size_hint::and(
206 u32::size_hint(depth),
207 size_hint::or_all(&[
208 B256::size_hint(depth),
209 usize::size_hint(depth),
210 Self::size_hint(depth + 1),
211 ]),
212 )
213 }
214 }
215}
216
217type ValueOfStrategy<S> = <S as Strategy>::Value;
219
220type StratMap<S, T> = Map<S, fn(ValueOfStrategy<S>) -> T>;
221
222type MappedWA<S, T> = WA<StratMap<S, T>>;
223
224type Flat<S, T> = Flatten<StratMap<S, T>>;
225
226type Rec<T, S> = Recursive<T, fn(BoxedStrategy<T>) -> S>;
227
228#[cfg(feature = "eip712")]
229const IDENT_STRATEGY: &str = parser::IDENT_REGEX;
230#[cfg(feature = "eip712")]
231type CustomStructStrategy<T> = BoxedStrategy<T>;
232
233#[cfg(feature = "eip712")]
234macro_rules! custom_struct_strategy {
235 ($range:expr, $elem:expr) => {{
236 let range: RangeInclusive<usize> = $range;
238 let elem: BoxedStrategy<Self> = $elem;
239 let strat: CustomStructStrategy<Self> = range
240 .prop_flat_map(move |sz| {
241 (
242 IDENT_STRATEGY,
243 proptest::collection::hash_set(IDENT_STRATEGY, sz..=sz)
244 .prop_map(|prop_names| prop_names.into_iter().collect()),
245 vec_strategy(elem.clone(), sz..=sz),
246 )
247 })
248 .prop_map(|(name, prop_names, tuple)| Self::CustomStruct { name, prop_names, tuple })
249 .boxed();
250 strat
251 }};
252}
253
254type TypeRecurseStrategy = TupleUnion<
256 tuple_type_cfg![
257 (
258 WA<BoxedStrategy<DynSolType>>, MappedWA<BoxedStrategy<DynSolType>, DynSolType>, MappedWA<(BoxedStrategy<DynSolType>, RangeInclusive<usize>), DynSolType>, MappedWA<VecStrategy<BoxedStrategy<DynSolType>>, DynSolType>, ),
263 WA<CustomStructStrategy<DynSolType>>, ],
265>;
266type TypeStrategy = Rec<DynSolType, TypeRecurseStrategy>;
267
268type ValueArrayStrategy =
269 Flat<BoxedStrategy<DynSolValue>, VecStrategy<SBoxedStrategy<DynSolValue>>>;
270
271type ValueRecurseStrategy = TupleUnion<
272 tuple_type_cfg![
273 (
274 WA<BoxedStrategy<DynSolValue>>, MappedWA<ValueArrayStrategy, DynSolValue>, MappedWA<ValueArrayStrategy, DynSolValue>, MappedWA<VecStrategy<BoxedStrategy<DynSolValue>>, DynSolValue>, ),
279 WA<CustomStructStrategy<DynSolValue>>, ],
281>;
282type ValueStrategy = Rec<DynSolValue, ValueRecurseStrategy>;
283
284impl proptest::arbitrary::Arbitrary for DynSolType {
285 type Parameters = (u32, u32, u32);
286 type Strategy = TypeStrategy;
287
288 #[inline]
289 fn arbitrary() -> Self::Strategy {
290 Self::arbitrary_with((DEPTH, DESIRED_SIZE, EXPECTED_BRANCH_SIZE))
291 }
292
293 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
294 let (depth, desired_size, expected_branch_size) = args;
295 Self::leaf().prop_recursive(depth, desired_size, expected_branch_size, Self::recurse)
296 }
297}
298
299impl DynSolType {
300 #[inline]
302 pub fn arbitrary_value(&self, u: &mut Unstructured<'_>) -> arbitrary::Result<DynSolValue> {
303 DynSolValue::arbitrary_from_type(self, u)
304 }
305
306 #[inline]
309 pub fn value_strategy(&self) -> SBoxedStrategy<DynSolValue> {
310 DynSolValue::type_strategy(self)
311 }
312
313 #[inline]
314 fn leaf() -> impl Strategy<Value = Self> {
315 prop_oneof![
316 Just(Self::Bool),
317 Just(Self::Address),
318 any::<usize>().prop_map(|x| Self::Int(int_size(x))),
319 any::<usize>().prop_map(|x| Self::Uint(int_size(x))),
320 (1..=32usize).prop_map(Self::FixedBytes),
321 Just(Self::Bytes),
322 Just(Self::String),
323 ]
324 }
325
326 #[inline]
327 fn recurse(element: BoxedStrategy<Self>) -> TypeRecurseStrategy {
328 prop_oneof_cfg![
329 1 => element.clone(),
330 2 => element.clone().prop_map(|ty| Self::Array(Box::new(ty))),
331 2 => (element.clone(), 1..=16).prop_map(|(ty, sz)| Self::FixedArray(Box::new(ty), sz)),
332 2 => vec_strategy(element.clone(), 1..=16).prop_map(Self::Tuple),
333 @[cfg(feature = "eip712")]
334 1 => custom_struct_strategy!(1..=16, element),
335 ]
336 }
337}
338
339impl proptest::arbitrary::Arbitrary for DynSolValue {
340 type Parameters = (u32, u32, u32);
341 type Strategy = ValueStrategy;
342
343 #[inline]
344 fn arbitrary() -> Self::Strategy {
345 Self::arbitrary_with((DEPTH, DESIRED_SIZE, EXPECTED_BRANCH_SIZE))
346 }
347
348 fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
349 let (depth, desired_size, expected_branch_size) = args;
350 Self::leaf().prop_recursive(depth, desired_size, expected_branch_size, Self::recurse)
351 }
352}
353
354impl DynSolValue {
355 pub fn arbitrary_from_type(
357 ty: &DynSolType,
358 u: &mut Unstructured<'_>,
359 ) -> arbitrary::Result<Self> {
360 match ty {
361 DynSolType::Bool => u.arbitrary().map(Self::Bool),
362 DynSolType::Address => u.arbitrary().map(Self::Address),
363 DynSolType::Function => u.arbitrary().map(Self::Function),
364 &DynSolType::Int(sz) => u.arbitrary().map(|x| Self::Int(adjust_int(x, sz), sz)),
365 &DynSolType::Uint(sz) => u.arbitrary().map(|x| Self::Uint(adjust_uint(x, sz), sz)),
366 &DynSolType::FixedBytes(sz) => {
367 u.arbitrary().map(|x| Self::FixedBytes(adjust_fb(x, sz), sz))
368 }
369 DynSolType::Bytes => u.arbitrary().map(Self::Bytes),
370 DynSolType::String => u.arbitrary().map(Self::String),
371 DynSolType::Array(ty) => {
372 let sz = u.int_in_range(1..=16u8)?;
373 let mut v = Vec::with_capacity(sz as usize);
374 for _ in 0..sz {
375 v.push(Self::arbitrary_from_type(ty, u)?);
376 }
377 Ok(Self::Array(v))
378 }
379 &DynSolType::FixedArray(ref ty, sz) => {
380 let mut v = Vec::with_capacity(sz);
381 for _ in 0..sz {
382 v.push(Self::arbitrary_from_type(ty, u)?);
383 }
384 Ok(Self::FixedArray(v))
385 }
386 DynSolType::Tuple(tuple) => tuple
387 .iter()
388 .map(|ty| Self::arbitrary_from_type(ty, u))
389 .collect::<Result<Vec<_>, _>>()
390 .map(Self::Tuple),
391 #[cfg(feature = "eip712")]
392 DynSolType::CustomStruct { tuple, .. } => {
393 let name = u.arbitrary::<AString>()?.0;
394 let tuple = tuple
395 .iter()
396 .map(|ty| Self::arbitrary_from_type(ty, u))
397 .collect::<Result<Vec<_>, _>>()?;
398 let sz = tuple.len();
399 let prop_names = (0..sz)
400 .map(|_| u.arbitrary::<AString>().map(|s| s.0))
401 .collect::<Result<Vec<_>, _>>()?;
402 Ok(Self::CustomStruct { name, prop_names, tuple })
403 }
404 }
405 }
406
407 pub fn type_strategy(ty: &DynSolType) -> SBoxedStrategy<Self> {
410 match ty {
411 DynSolType::Bool => any::<bool>().prop_map(Self::Bool).sboxed(),
412 DynSolType::Address => any::<Address>().prop_map(Self::Address).sboxed(),
413 DynSolType::Function => any::<Function>().prop_map(Self::Function).sboxed(),
414 &DynSolType::Int(sz) => {
415 any::<I256>().prop_map(move |x| Self::Int(adjust_int(x, sz), sz)).sboxed()
416 }
417 &DynSolType::Uint(sz) => {
418 any::<U256>().prop_map(move |x| Self::Uint(adjust_uint(x, sz), sz)).sboxed()
419 }
420 &DynSolType::FixedBytes(sz) => {
421 any::<B256>().prop_map(move |x| Self::FixedBytes(adjust_fb(x, sz), sz)).sboxed()
422 }
423 DynSolType::Bytes => any::<Vec<u8>>().prop_map(Self::Bytes).sboxed(),
424 DynSolType::String => any::<String>().prop_map(Self::String).sboxed(),
425 DynSolType::Array(ty) => {
426 let element = Self::type_strategy(ty);
427 vec_strategy(element, 1..=16).prop_map(Self::Array).sboxed()
428 }
429 DynSolType::FixedArray(ty, sz) => {
430 let element = Self::type_strategy(ty);
431 vec_strategy(element, *sz).prop_map(Self::FixedArray).sboxed()
432 }
433 DynSolType::Tuple(tys) => tys
434 .iter()
435 .map(Self::type_strategy)
436 .collect::<Vec<_>>()
437 .prop_map(Self::Tuple)
438 .sboxed(),
439 #[cfg(feature = "eip712")]
440 DynSolType::CustomStruct { tuple, prop_names, name } => {
441 let name = name.clone();
442 let prop_names = prop_names.clone();
443 tuple
444 .iter()
445 .map(Self::type_strategy)
446 .collect::<Vec<_>>()
447 .prop_map(move |tuple| Self::CustomStruct {
448 name: name.clone(),
449 prop_names: prop_names.clone(),
450 tuple,
451 })
452 .sboxed()
453 }
454 }
455 }
456
457 #[inline]
460 pub fn value_strategy(&self) -> SBoxedStrategy<Self> {
461 Self::type_strategy(&self.as_type().unwrap())
462 }
463
464 #[inline]
465 fn leaf() -> impl Strategy<Value = Self> {
466 prop_oneof![
467 any::<bool>().prop_map(Self::Bool),
468 any::<Address>().prop_map(Self::Address),
469 int_strategy::<I256>().prop_map(|(x, sz)| Self::Int(adjust_int(x, sz), sz)),
470 int_strategy::<U256>().prop_map(|(x, sz)| Self::Uint(adjust_uint(x, sz), sz)),
471 (any::<B256>(), 1..=32usize).prop_map(|(x, sz)| Self::FixedBytes(adjust_fb(x, sz), sz)),
472 any::<Vec<u8>>().prop_map(Self::Bytes),
473 any::<String>().prop_map(Self::String),
474 ]
475 }
476
477 #[inline]
478 fn recurse(element: BoxedStrategy<Self>) -> ValueRecurseStrategy {
479 prop_oneof_cfg![
480 1 => element.clone(),
481 2 => Self::array_strategy(element.clone()).prop_map(Self::Array),
482 2 => Self::array_strategy(element.clone()).prop_map(Self::FixedArray),
483 2 => vec_strategy(element.clone(), 1..=16).prop_map(Self::Tuple),
484 @[cfg(feature = "eip712")]
485 1 => custom_struct_strategy!(1..=16, element),
486 ]
487 }
488
489 #[inline]
515 #[allow(rustdoc::invalid_rust_codeblocks)]
516 fn array_strategy(element: BoxedStrategy<Self>) -> ValueArrayStrategy {
517 element.prop_flat_map(|x| vec_strategy(x.value_strategy(), 1..=16))
518 }
519}
520
521#[inline]
522fn int_strategy<T: Arbitrary>() -> impl Strategy<Value = (ValueOfStrategy<T::Strategy>, usize)> {
523 (any::<T>(), any::<usize>().prop_map(int_size))
524}
525
526#[inline]
528fn adjust_int(mut int: I256, size: usize) -> I256 {
529 if size < 256 {
530 if int.bit(size - 1) {
531 int |= I256::MINUS_ONE - (I256::ONE << size).wrapping_sub(I256::ONE);
532 } else {
533 int &= (I256::ONE << size).wrapping_sub(I256::ONE);
534 }
535 }
536 int
537}
538
539#[inline]
540fn adjust_uint(mut uint: U256, size: usize) -> U256 {
541 if size < 256 {
542 uint &= (U256::from(1u64) << size).wrapping_sub(U256::from(1u64));
543 }
544 uint
545}
546
547#[inline]
548fn adjust_fb(mut word: B256, size: usize) -> B256 {
549 if size < 32 {
550 word[size..].fill(0);
551 }
552 word
553}
554
555#[cfg(all(test, not(miri)))] mod tests {
557 use super::*;
558 use alloy_primitives::hex;
559 #[cfg(feature = "eip712")]
560 use parser::{is_id_continue, is_id_start, is_valid_identifier};
561
562 proptest! {
563 #![proptest_config(ProptestConfig {
564 cases: 1024,
565 ..Default::default()
566 })]
567
568 #[test]
569 fn int_size(x: usize) {
570 let sz = super::int_size(x);
571 prop_assert!(sz > 0 && sz <= 256, "{sz}");
572 prop_assert!(sz % 8 == 0, "{sz}");
573 }
574
575 #[test]
576 #[cfg(feature = "eip712")]
577 fn ident_char(x: u8) {
578 let start = super::ident_char(x, true);
579 prop_assert!(is_id_start(start as char));
580 prop_assert!(is_id_continue(start as char));
581
582 let cont = super::ident_char(x, false);
583 prop_assert!(is_id_continue(cont as char));
584 }
585 }
586
587 proptest! {
588 #![proptest_config(ProptestConfig {
589 cases: 256,
590 ..Default::default()
591 })]
592
593 #[test]
594 #[cfg(feature = "eip712")]
595 fn arbitrary_string(bytes: Vec<u8>) {
596 prop_assume!(!bytes.is_empty());
597 let mut u = Unstructured::new(&bytes);
598
599 let s = u.arbitrary::<AString>();
600 prop_assume!(s.is_ok());
601
602 let s = s.unwrap().0;
603 prop_assume!(!s.is_empty());
604
605 prop_assert!(
606 is_valid_identifier(&s),
607 "not a valid identifier: {:?}\ndata: {}",
608 s,
609 hex::encode_prefixed(&bytes),
610 );
611 }
612
613 #[test]
614 fn arbitrary_type(bytes: Vec<u8>) {
615 prop_assume!(!bytes.is_empty());
616 let mut u = Unstructured::new(&bytes);
617 let ty = u.arbitrary::<DynSolType>();
618 prop_assume!(ty.is_ok());
619 type_test(ty.unwrap())?;
620 }
621
622 #[test]
623 fn arbitrary_value(bytes: Vec<u8>) {
624 prop_assume!(!bytes.is_empty());
625 let mut u = Unstructured::new(&bytes);
626 let value = u.arbitrary::<DynSolValue>();
627 prop_assume!(value.is_ok());
628 value_test(value.unwrap())?;
629 }
630
631 #[test]
632 fn proptest_type(ty: DynSolType) {
633 type_test(ty)?;
634 }
635
636 #[test]
637 fn proptest_value(value: DynSolValue) {
638 value_test(value)?;
639 }
640 }
641
642 fn type_test(ty: DynSolType) -> Result<(), TestCaseError> {
643 let s = ty.sol_type_name();
644 prop_assume!(!ty.has_custom_struct());
645 prop_assert_eq!(DynSolType::parse(&s), Ok(ty), "type strings don't match");
646 Ok(())
647 }
648
649 fn value_test(value: DynSolValue) -> Result<(), TestCaseError> {
650 let ty = match value.as_type() {
651 Some(ty) => ty,
652 None => {
653 prop_assert!(false, "generated invalid type: {value:?}");
654 unreachable!()
655 }
656 };
657 let s = value.sol_type_name().unwrap();
659
660 prop_assert_eq!(&s, &ty.sol_type_name(), "type strings don't match");
661
662 assert_valid_value(&value)?;
663
664 if !ty.has_custom_struct() {
666 let parsed = s.parse::<DynSolType>();
667 prop_assert_eq!(parsed.as_ref(), Ok(&ty), "types don't match {:?}", s);
668 }
669
670 let data = value.abi_encode_params();
671 match ty.abi_decode_params(&data) {
672 Ok(decoded) if !decoded.has_custom_struct() => prop_assert_eq!(
675 &decoded,
676 &value,
677 "\n\ndecoded value doesn't match {:?} ({:?})\ndata: {:?}",
678 s,
679 ty,
680 hex::encode_prefixed(&data),
681 ),
682 Ok(_) => {}
683 Err(e @ crate::Error::SolTypes(alloy_sol_types::Error::RecursionLimitExceeded(_))) => {
684 return Err(TestCaseError::Reject(e.to_string().into()));
685 }
686 Err(e) => prop_assert!(
687 false,
688 "failed to decode {s:?}: {e}\nvalue: {value:?}\ndata: {:?}",
689 hex::encode_prefixed(&data),
690 ),
691 }
692
693 Ok(())
694 }
695
696 pub(crate) fn assert_valid_value(value: &DynSolValue) -> Result<(), TestCaseError> {
697 match &value {
698 DynSolValue::Array(values) | DynSolValue::FixedArray(values) => {
699 prop_assert!(!values.is_empty());
700 let mut values = values.iter();
701 let ty = values.next().unwrap().as_type().unwrap();
702 prop_assert!(
703 values.all(|v| ty.matches(v)),
704 "array elements have different types: {value:#?}",
705 );
706 }
707 #[cfg(feature = "eip712")]
708 DynSolValue::CustomStruct { name, prop_names, tuple } => {
709 prop_assert!(is_valid_identifier(name));
710 prop_assert!(prop_names.iter().all(|s| is_valid_identifier(s)));
711 prop_assert_eq!(prop_names.len(), tuple.len());
712 }
713 _ => {}
714 }
715
716 match value {
717 DynSolValue::Int(int, size) => {
718 let bits = int.into_sign_and_abs().1.bit_len();
719 prop_assert!(bits <= *size, "int: {int}, {size}, {bits}")
720 }
721 DynSolValue::Uint(uint, size) => {
722 let bits = uint.bit_len();
723 prop_assert!(bits <= *size, "uint: {uint}, {size}, {bits}")
724 }
725 DynSolValue::FixedBytes(fb, size) => {
726 prop_assert!(fb[*size..].iter().all(|x| *x == 0), "fb {fb}, {size}")
727 }
728 _ => {}
729 }
730
731 match value {
733 DynSolValue::Array(t)
734 | DynSolValue::FixedArray(t)
735 | crate::dynamic::ty::as_tuple!(DynSolValue t) => {
736 t.iter().try_for_each(assert_valid_value)?
737 }
738 _ => {}
739 }
740
741 Ok(())
742 }
743}