alloy_dyn_abi/
arbitrary.rs

1//! Arbitrary implementations for `DynSolType` and `DynSolValue`.
2//!
3//! These implementations are guaranteed to be valid, including `CustomStruct`
4//! identifiers.
5
6// TODO: Maybe make array sizes configurable? Also change parameters type from
7// tuple to a struct
8
9// `prop_oneof!` / `TupleUnion` uses `Arc`s for cheap cloning
10#![allow(clippy::arc_with_non_send_sync)]
11
12use crate::{DynSolType, DynSolValue};
13use alloy_primitives::{Address, Function, B256, I256, U256};
14use arbitrary::{size_hint, Unstructured};
15use core::ops::RangeInclusive;
16use proptest::{
17    collection::{vec as vec_strategy, VecStrategy},
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        // note: do not use u.arbitrary() with String or Vec<u8> because it's always
95        // too short
96        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, derive_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            // re-use name and prop_names
188            #[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
217// rustscript
218type 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        // TODO: Avoid boxing. This is currently needed because we capture $elem
237        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
254// we must explicitly the final types of the strategies
255type TypeRecurseStrategy = TupleUnion<
256    tuple_type_cfg![
257        (
258            WA<BoxedStrategy<DynSolType>>,                   // Basic
259            MappedWA<BoxedStrategy<DynSolType>, DynSolType>, // Array
260            MappedWA<(BoxedStrategy<DynSolType>, RangeInclusive<usize>), DynSolType>, // FixedArray
261            MappedWA<VecStrategy<BoxedStrategy<DynSolType>>, DynSolType>, // Tuple
262        ),
263        WA<CustomStructStrategy<DynSolType>>, // CustomStruct
264    ],
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>>,            // Basic
275            MappedWA<ValueArrayStrategy, DynSolValue>, // Array
276            MappedWA<ValueArrayStrategy, DynSolValue>, // FixedArray
277            MappedWA<VecStrategy<BoxedStrategy<DynSolValue>>, DynSolValue>, // Tuple
278        ),
279        WA<CustomStructStrategy<DynSolValue>>, // CustomStruct
280    ],
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    /// Generate an arbitrary [`DynSolValue`] from this type.
301    #[inline]
302    pub fn arbitrary_value(&self, u: &mut Unstructured<'_>) -> arbitrary::Result<DynSolValue> {
303        DynSolValue::arbitrary_from_type(self, u)
304    }
305
306    /// Create a [proptest strategy][Strategy] to generate [`DynSolValue`]s from
307    /// this type.
308    #[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    /// Generate an arbitrary [`DynSolValue`] from the given [`DynSolType`].
356    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    /// Create a [proptest strategy][Strategy] to generate [`DynSolValue`]s from
408    /// the given type.
409    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    /// Create a [proptest strategy][Strategy] to generate [`DynSolValue`]s from
458    /// the given value's type.
459    #[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    /// Recursive array strategy that generates same-type arrays of up to 16
490    /// elements.
491    ///
492    /// NOTE: this has to be a separate function so Rust can turn the closure
493    /// type (`impl Fn`) into an `fn` type.
494    ///
495    /// If you manually inline this into the function above, the compiler will
496    /// fail with "expected fn pointer, found closure":
497    ///
498    /// ```ignore (error)
499    ///    error[E0308]: mismatched types
500    ///    --> crates/dyn-abi/src/arbitrary.rs:264:18
501    ///     |
502    /// 261 | /         prop_oneof![
503    /// 262 | |             1 => element.clone(),
504    /// 263 | |             2 => Self::array_strategy(element.clone()).prop_map(Self::Array),
505    /// 264 | |             2 => element.prop_flat_map(|x| vec_strategy(x.value_strategy(), 1..=16)).prop_map(Self::FixedArray),
506    ///     | |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found closure
507    /// 265 | |             2 => vec_strategy(element, 1..=16).prop_map(Self::Tuple),
508    /// 266 | |         ]
509    ///     | |_________- arguments to this function are incorrect
510    ///     |
511    ///     = note: expected struct `Map<Flatten<Map<BoxedStrategy<DynSolValue>, fn(DynSolValue) -> VecStrategy<BoxedStrategy<DynSolValue>>>>, ...>`
512    ///                found struct `Map<Flatten<Map<BoxedStrategy<DynSolValue>, [closure@arbitrary.rs:264:40]>>, ...>`
513    /// ```
514    #[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// Trim words and integers to the given size.
527#[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)))] // doesn't run in isolation and would take too long
556mod tests {
557    use super::*;
558    #[cfg(feature = "eip712")]
559    use parser::{is_id_continue, is_id_start, is_valid_identifier};
560
561    proptest! {
562        #![proptest_config(ProptestConfig {
563            cases: 1024,
564            ..Default::default()
565        })]
566
567        #[test]
568        fn int_size(x: usize) {
569            let sz = super::int_size(x);
570            prop_assert!(sz > 0 && sz <= 256, "{sz}");
571            prop_assert!(sz % 8 == 0, "{sz}");
572        }
573
574        #[test]
575        #[cfg(feature = "eip712")]
576        fn ident_char(x: u8) {
577            let start = super::ident_char(x, true);
578            prop_assert!(is_id_start(start as char));
579            prop_assert!(is_id_continue(start as char));
580
581            let cont = super::ident_char(x, false);
582            prop_assert!(is_id_continue(cont as char));
583        }
584    }
585
586    proptest! {
587        #![proptest_config(ProptestConfig {
588            cases: 256,
589            ..Default::default()
590        })]
591
592        #[test]
593        #[cfg(feature = "eip712")]
594        fn arbitrary_string(bytes: Vec<u8>) {
595            prop_assume!(!bytes.is_empty());
596            let mut u = Unstructured::new(&bytes);
597
598            let s = u.arbitrary::<AString>();
599            prop_assume!(s.is_ok());
600
601            let s = s.unwrap().0;
602            prop_assume!(!s.is_empty());
603
604            prop_assert!(
605                is_valid_identifier(&s),
606                "not a valid identifier: {:?}\ndata: {}",
607                s,
608                hex::encode_prefixed(&bytes),
609            );
610        }
611
612        #[test]
613        fn arbitrary_type(bytes: Vec<u8>) {
614            prop_assume!(!bytes.is_empty());
615            let mut u = Unstructured::new(&bytes);
616            let ty = u.arbitrary::<DynSolType>();
617            prop_assume!(ty.is_ok());
618            type_test(ty.unwrap())?;
619        }
620
621        #[test]
622        fn arbitrary_value(bytes: Vec<u8>) {
623            prop_assume!(!bytes.is_empty());
624            let mut u = Unstructured::new(&bytes);
625            let value = u.arbitrary::<DynSolValue>();
626            prop_assume!(value.is_ok());
627            value_test(value.unwrap())?;
628        }
629
630        #[test]
631        fn proptest_type(ty: DynSolType) {
632            type_test(ty)?;
633        }
634
635        #[test]
636        fn proptest_value(value: DynSolValue) {
637            value_test(value)?;
638        }
639    }
640
641    fn type_test(ty: DynSolType) -> Result<(), TestCaseError> {
642        let s = ty.sol_type_name();
643        prop_assume!(!ty.has_custom_struct());
644        prop_assert_eq!(DynSolType::parse(&s), Ok(ty), "type strings don't match");
645        Ok(())
646    }
647
648    fn value_test(value: DynSolValue) -> Result<(), TestCaseError> {
649        let ty = match value.as_type() {
650            Some(ty) => ty,
651            None => {
652                prop_assert!(false, "generated invalid type: {value:?}");
653                unreachable!()
654            }
655        };
656        // this shouldn't fail after the previous assertion
657        let s = value.sol_type_name().unwrap();
658
659        prop_assert_eq!(&s, &ty.sol_type_name(), "type strings don't match");
660
661        assert_valid_value(&value)?;
662
663        // allow this to fail if the type contains a CustomStruct
664        if !ty.has_custom_struct() {
665            let parsed = s.parse::<DynSolType>();
666            prop_assert_eq!(parsed.as_ref(), Ok(&ty), "types don't match {:?}", s);
667        }
668
669        let data = value.abi_encode_params();
670        match ty.abi_decode_params(&data) {
671            // skip the check if the type contains a CustomStruct, since
672            // decoding will not populate names
673            Ok(decoded) if !decoded.has_custom_struct() => prop_assert_eq!(
674                &decoded,
675                &value,
676                "\n\ndecoded value doesn't match {:?} ({:?})\ndata: {:?}",
677                s,
678                ty,
679                hex::encode_prefixed(&data),
680            ),
681            Ok(_) => {}
682            Err(e @ crate::Error::SolTypes(alloy_sol_types::Error::RecursionLimitExceeded(_))) => {
683                return Err(TestCaseError::Reject(e.to_string().into()));
684            }
685            Err(e) => prop_assert!(
686                false,
687                "failed to decode {s:?}: {e}\nvalue: {value:?}\ndata: {:?}",
688                hex::encode_prefixed(&data),
689            ),
690        }
691
692        Ok(())
693    }
694
695    pub(crate) fn assert_valid_value(value: &DynSolValue) -> Result<(), TestCaseError> {
696        match &value {
697            DynSolValue::Array(values) | DynSolValue::FixedArray(values) => {
698                prop_assert!(!values.is_empty());
699                let mut values = values.iter();
700                let ty = values.next().unwrap().as_type().unwrap();
701                prop_assert!(
702                    values.all(|v| ty.matches(v)),
703                    "array elements have different types: {value:#?}",
704                );
705            }
706            #[cfg(feature = "eip712")]
707            DynSolValue::CustomStruct { name, prop_names, tuple } => {
708                prop_assert!(is_valid_identifier(name));
709                prop_assert!(prop_names.iter().all(|s| is_valid_identifier(s)));
710                prop_assert_eq!(prop_names.len(), tuple.len());
711            }
712            _ => {}
713        }
714
715        match value {
716            DynSolValue::Int(int, size) => {
717                let bits = int.into_sign_and_abs().1.bit_len();
718                prop_assert!(bits <= *size, "int: {int}, {size}, {bits}")
719            }
720            DynSolValue::Uint(uint, size) => {
721                let bits = uint.bit_len();
722                prop_assert!(bits <= *size, "uint: {uint}, {size}, {bits}")
723            }
724            DynSolValue::FixedBytes(fb, size) => {
725                prop_assert!(fb[*size..].iter().all(|x| *x == 0), "fb {fb}, {size}")
726            }
727            _ => {}
728        }
729
730        // recurse
731        match value {
732            DynSolValue::Array(t)
733            | DynSolValue::FixedArray(t)
734            | crate::dynamic::ty::as_tuple!(DynSolValue t) => {
735                t.iter().try_for_each(assert_valid_value)?
736            }
737            _ => {}
738        }
739
740        Ok(())
741    }
742}