Repository: ergoplatform/sigma-rust (language: rust)

Issue:
Arbitrary impl for Expr with nesting level and type parameters
Switch serialization round trip and eval tests to PBT. 


Discussion / hints:

Fix patch:
diff --git a/ergo-lib/src/ast/bin_op.rs b/ergo-lib/src/ast/bin_op.rs
index 5bd30228d..15c14b68e 100644
--- a/ergo-lib/src/ast/bin_op.rs
+++ b/ergo-lib/src/ast/bin_op.rs
@@ -15,6 +15,9 @@ use super::value::Value;
 extern crate derive_more;
 use derive_more::From;
 
+#[cfg(test)]
+use proptest_derive::Arbitrary;
+
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
 /// Operations for numerical types
 pub enum NumOp {
@@ -29,13 +32,14 @@ impl From<NumOp> for OpCode {
 }
 
 #[derive(PartialEq, Eq, Debug, Clone, Copy)]
+#[cfg_attr(test, derive(Arbitrary))]
 pub enum LogicOp {
     Eq,
     NEq,
-    GE,
-    GT,
-    LE,
-    LT,
+    // GE,
+    // GT,
+    // LE,
+    // LT,
 }
 
 impl From<LogicOp> for OpCode {
@@ -43,7 +47,6 @@ impl From<LogicOp> for OpCode {
         match op {
             LogicOp::Eq => OpCode::EQ,
             LogicOp::NEq => OpCode::NEQ,
-            _ => todo!(),
         }
     }
 }
@@ -52,14 +55,14 @@ impl From<LogicOp> for OpCode {
 /// Binary operations
 pub enum BinOpKind {
     /// Binary operations for numerical types
-    Num(NumOp),
+    // Num(NumOp),
     Logic(LogicOp),
 }
 
 impl From<BinOpKind> for OpCode {
     fn from(op: BinOpKind) -> Self {
         match op {
-            BinOpKind::Num(o) => o.into(),
+            // BinOpKind::Num(o) => o.into(),
             BinOpKind::Logic(o) => o.into(),
         }
     }
@@ -84,27 +87,57 @@ impl Evaluable for BinOp {
         let rv = self.right.eval(env, ctx)?;
         ctx.cost_accum.add(Costs::DEFAULT.eq_const_size)?;
         match self.kind {
-            BinOpKind::Num(_) => todo!(),
             BinOpKind::Logic(op) => match op {
                 LogicOp::Eq => Ok(Value::Boolean(lv == rv)),
                 LogicOp::NEq => Ok(Value::Boolean(lv != rv)),
-                _ => todo!(),
             },
         }
     }
 }
 
 #[cfg(test)]
-mod tests {
+pub mod tests {
     use std::rc::Rc;
 
     use crate::ast::constant::Constant;
+    use crate::ast::expr::tests::ArbExprParams;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
     use crate::test_util::force_any_val;
+    use crate::types::stype::SType;
 
     use super::*;
 
+    impl Arbitrary for BinOp {
+        type Parameters = ArbExprParams;
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            match args.tpe {
+                SType::SBoolean => (
+                    any::<LogicOp>().prop_map_into(),
+                    any_with::<Expr>(ArbExprParams {
+                        tpe: SType::SAny,
+                        depth: args.depth,
+                    }),
+                    any_with::<Expr>(ArbExprParams {
+                        tpe: SType::SAny,
+                        depth: args.depth,
+                    }),
+                )
+                    .prop_map(|(kind, left, right)| BinOp { kind, left, right }),
+
+                _ => todo!(),
+                // SType::SByte => {}
+                // SType::SShort => {}
+                // SType::SInt => {}
+                // SType::SLong => {}
+                // SType::SBigInt => {}
+            }
+            .boxed()
+        }
+    }
+
     fn check_eq_neq(left: Constant, right: Constant) -> bool {
         let eq_op: Expr = Box::new(BinOp {
             kind: BinOpKind::Logic(LogicOp::Eq),
diff --git a/ergo-lib/src/ast/constant.rs b/ergo-lib/src/ast/constant.rs
index 4aaec5b45..4573701f5 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergo-lib/src/ast/constant.rs
@@ -239,8 +239,8 @@ mod tests {
             any::<i64>().prop_map_into(),
             any::<EcPoint>().prop_map_into(),
             any::<SigmaProp>().prop_map_into(),
+            // although it's not strictly a primitive type, byte array is widely used as one
             vec(any::<i8>(), 0..100).prop_map_into(),
-            vec(any::<i64>(), 0..100).prop_map_into(),
         ]
         .boxed()
     }
@@ -268,54 +268,85 @@ mod tests {
         }
     }
 
+    fn const_with_type(tpe: SType) -> BoxedStrategy<Constant> {
+        match tpe {
+            SType::SAny => any::<Constant>(),
+            SType::SBoolean => any::<bool>().prop_map_into().boxed(),
+            SType::SByte => any::<i8>().prop_map_into().boxed(),
+            SType::SShort => any::<i16>().prop_map_into().boxed(),
+            SType::SInt => any::<i32>().prop_map_into().boxed(),
+            SType::SLong => any::<i64>().prop_map_into().boxed(),
+            // SType::SBigInt => {}
+            SType::SGroupElement => any::<EcPoint>().prop_map_into().boxed(),
+            SType::SSigmaProp => any::<SigmaProp>().prop_map_into().boxed(),
+            // SType::SBox => {}
+            // SType::SAvlTree => {}
+            // SType::SOption(tpe) =>
+            // SType::SColl() => {}
+            // SType::STuple(_) => {}
+            _ => todo!(),
+        }
+    }
+
+    impl Default for SType {
+        fn default() -> Self {
+            SType::SAny
+        }
+    }
+
     impl Arbitrary for Constant {
-        type Parameters = ();
+        type Parameters = SType;
         type Strategy = BoxedStrategy<Self>;
 
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            prop_oneof![primitive_type_value().prop_recursive(4, 64, 15, |elem| {
-                prop_oneof![
-                    // Coll[_]
-                    elem.clone().prop_map(|c| coll_from_constant(c, 0)),
-                    elem.clone().prop_map(|c| coll_from_constant(c, 1)),
-                    elem.clone().prop_map(|c| coll_from_constant(c, 2)),
-                    elem.clone().prop_map(|c| coll_from_constant(c, 10)),
-                    // no Option[_] since it cannot be serialized (for now)
-                    // // Some(v)
-                    // elem.clone().prop_map(|c| Constant {
-                    //     tpe: SType::SOption(Box::new(c.tpe)),
-                    //     v: Value::Opt(Box::new(Some(c.v)))
-                    // }),
-                    // // None
-                    // elem.prop_map(|c| Constant {
-                    //     tpe: SType::SOption(Box::new(c.tpe)),
-                    //     v: Value::Opt(Box::new(None))
-                    // })
-
-                    // Tuple
-                    vec(elem, 2..=4).prop_map(|constants| Constant {
-                        tpe: SType::STuple(
-                            TupleItems::try_from(
-                                constants
-                                    .clone()
-                                    .into_iter()
-                                    .map(|c| c.tpe)
-                                    .collect::<Vec<SType>>()
-                            )
-                            .unwrap()
-                        ),
-                        v: Value::Tup(
-                            constants
-                                .into_iter()
-                                .map(|c| c.v)
-                                .collect::<Vec<Value>>()
-                                .try_into()
-                                .unwrap()
-                        )
-                    }),
-                ]
-            })]
-            .boxed()
+        fn arbitrary_with(tpe: Self::Parameters) -> Self::Strategy {
+            match tpe {
+                SType::SAny => {
+                    prop_oneof![primitive_type_value().prop_recursive(3, 16, 8, |elem| {
+                        prop_oneof![
+                            // Coll[_]
+                            elem.clone().prop_map(|c| coll_from_constant(c, 0)),
+                            elem.clone().prop_map(|c| coll_from_constant(c, 1)),
+                            elem.clone().prop_map(|c| coll_from_constant(c, 2)),
+                            elem.clone().prop_map(|c| coll_from_constant(c, 10)),
+                            // no Option[_] since it cannot be serialized (for now)
+                            // // Some(v)
+                            // elem.clone().prop_map(|c| Constant {
+                            //     tpe: SType::SOption(Box::new(c.tpe)),
+                            //     v: Value::Opt(Box::new(Some(c.v)))
+                            // }),
+                            // // None
+                            // elem.prop_map(|c| Constant {
+                            //     tpe: SType::SOption(Box::new(c.tpe)),
+                            //     v: Value::Opt(Box::new(None))
+                            // })
+
+                            // Tuple
+                            vec(elem, 2..=4).prop_map(|constants| Constant {
+                                tpe: SType::STuple(
+                                    TupleItems::try_from(
+                                        constants
+                                            .clone()
+                                            .into_iter()
+                                            .map(|c| c.tpe)
+                                            .collect::<Vec<SType>>()
+                                    )
+                                    .unwrap()
+                                ),
+                                v: Value::Tup(
+                                    constants
+                                        .into_iter()
+                                        .map(|c| c.v)
+                                        .collect::<Vec<Value>>()
+                                        .try_into()
+                                        .unwrap()
+                                )
+                            }),
+                        ]
+                    })]
+                    .boxed()
+                }
+                _ => const_with_type(tpe),
+            }
         }
     }
 
diff --git a/ergo-lib/src/ast/expr.rs b/ergo-lib/src/ast/expr.rs
index 91f72c399..3db61961e 100644
--- a/ergo-lib/src/ast/expr.rs
+++ b/ergo-lib/src/ast/expr.rs
@@ -77,18 +77,83 @@ impl fmt::Display for Expr {
 }
 
 #[cfg(test)]
-mod tests {
+pub mod tests {
     #![allow(unused_imports)]
     use super::*;
     use crate::sigma_protocol::sigma_boolean::SigmaProp;
     use proptest::prelude::*;
 
+    #[derive(PartialEq, Eq, Debug, Clone)]
+    pub struct ArbExprParams {
+        pub tpe: SType,
+        pub depth: usize,
+    }
+
+    impl Default for ArbExprParams {
+        fn default() -> Self {
+            ArbExprParams {
+                tpe: SType::SBoolean,
+                depth: 2,
+            }
+        }
+    }
+
+    fn bool_nested_expr(depth: usize) -> BoxedStrategy<Expr> {
+        prop_oneof![any_with::<BinOp>(ArbExprParams {
+            tpe: SType::SBoolean,
+            depth
+        })
+        .prop_map(Box::new)
+        .prop_map_into()]
+        .boxed()
+    }
+
+    fn any_nested_expr(depth: usize) -> BoxedStrategy<Expr> {
+        prop_oneof![bool_nested_expr(depth)]
+    }
+
+    fn nested_expr(tpe: SType, depth: usize) -> BoxedStrategy<Expr> {
+        match tpe {
+            SType::SAny => any_nested_expr(depth),
+            SType::SBoolean => bool_nested_expr(depth),
+            _ => todo!(),
+        }
+        .boxed()
+    }
+
+    fn int_non_nested_expr() -> BoxedStrategy<Expr> {
+        prop_oneof![Just(Box::new(GlobalVars::Height).into()),].boxed()
+    }
+
+    fn any_non_nested_expr() -> BoxedStrategy<Expr> {
+        prop_oneof![int_non_nested_expr()]
+    }
+
+    fn non_nested_expr(tpe: &SType) -> BoxedStrategy<Expr> {
+        match tpe {
+            SType::SAny => any_non_nested_expr(),
+            SType::SInt => int_non_nested_expr(),
+            _ => todo!(),
+        }
+    }
+
     impl Arbitrary for Expr {
-        type Parameters = ();
+        type Parameters = ArbExprParams;
         type Strategy = BoxedStrategy<Self>;
 
-        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            prop_oneof![any::<Constant>().prop_map(Box::new).prop_map(Expr::Const)].boxed()
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            if args.depth == 0 {
+                prop_oneof![
+                    any_with::<Constant>(args.tpe.clone())
+                        .prop_map(Box::new)
+                        .prop_map(Expr::Const)
+                        .boxed(),
+                    non_nested_expr(&args.tpe)
+                ]
+                .boxed()
+            } else {
+                nested_expr(args.tpe, args.depth - 1)
+            }
         }
     }
 }
diff --git a/ergo-lib/src/eval/expr.rs b/ergo-lib/src/eval/expr.rs
index 55da93452..d664d405d 100644
--- a/ergo-lib/src/eval/expr.rs
+++ b/ergo-lib/src/eval/expr.rs
@@ -27,3 +27,30 @@ impl Evaluable for Expr {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::ast::expr::tests::*;
+    use crate::eval::context::Context;
+    use crate::eval::cost_accum::CostAccumulator;
+    use crate::test_util::force_any_val;
+    use crate::types::stype::SType;
+
+    use super::*;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn eval(e in any_with::<Expr>(ArbExprParams{tpe: SType::SBoolean, depth: 4})) {
+            dbg!(&e);
+            let ctx = Rc::new(force_any_val::<Context>());
+            let cost_accum = CostAccumulator::new(0, None);
+            let mut ectx = EvalContext::new(ctx, cost_accum);
+            let r = e.eval(&Env::empty(), &mut ectx);
+            prop_assert!(r.is_ok());
+        }
+    }
+}
diff --git a/ergo-lib/src/serialization/bin_op.rs b/ergo-lib/src/serialization/bin_op.rs
index d91dcbd18..ef39a35b5 100644
--- a/ergo-lib/src/serialization/bin_op.rs
+++ b/ergo-lib/src/serialization/bin_op.rs
@@ -33,8 +33,10 @@ pub fn bin_op_sigma_parse<R: SigmaByteRead>(
 mod tests {
     use super::*;
     use crate::ast::bin_op::LogicOp;
+    use crate::ast::expr::tests::ArbExprParams;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
+    use crate::test_util::force_any_val_with;
+    use crate::types::stype::SType;
 
     fn test_ser_roundtrip(kind: BinOpKind, left: Expr, right: Expr) {
         let eq_op: Expr = Box::new(BinOp { kind, left, right }).into();
@@ -45,8 +47,14 @@ mod tests {
     fn ser_roundtrip_eq() {
         test_ser_roundtrip(
             LogicOp::Eq.into(),
-            force_any_val::<Expr>(),
-            force_any_val::<Expr>(),
+            force_any_val_with::<Expr>(ArbExprParams {
+                tpe: SType::SAny,
+                depth: 1,
+            }),
+            force_any_val_with::<Expr>(ArbExprParams {
+                tpe: SType::SAny,
+                depth: 1,
+            }),
         )
     }
 
@@ -54,8 +62,14 @@ mod tests {
     fn ser_roundtrip_neq() {
         test_ser_roundtrip(
             LogicOp::NEq.into(),
-            force_any_val::<Expr>(),
-            force_any_val::<Expr>(),
+            force_any_val_with::<Expr>(ArbExprParams {
+                tpe: SType::SAny,
+                depth: 1,
+            }),
+            force_any_val_with::<Expr>(ArbExprParams {
+                tpe: SType::SAny,
+                depth: 1,
+            }),
         )
     }
 }
diff --git a/ergo-lib/src/serialization/constant.rs b/ergo-lib/src/serialization/constant.rs
index d7f02439f..d3ddf055f 100644
--- a/ergo-lib/src/serialization/constant.rs
+++ b/ergo-lib/src/serialization/constant.rs
@@ -31,6 +31,7 @@ mod tests {
 
         #[test]
         fn ser_roundtrip(v in any::<Constant>()) {
+            dbg!(&v);
             prop_assert_eq![sigma_serialize_roundtrip(&v), v];
         }
     }
diff --git a/ergo-lib/src/serialization/expr.rs b/ergo-lib/src/serialization/expr.rs
index 83675b9c2..8144fec9b 100644
--- a/ergo-lib/src/serialization/expr.rs
+++ b/ergo-lib/src/serialization/expr.rs
@@ -94,3 +94,19 @@ impl SigmaSerializable for Expr {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<Expr>()) {
+            dbg!(&v);
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}


Test patch: