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

Issue:
DRY idea for SigmaSerializable impl for one-paramater IR nodes
Extracted from #241  

> Alternative idea - traits for existing nodes that allow blanket serialization implementation for one-value-input nodes.

```rust
pub trait UnaryOp {
    fn input(&self) -> &Expr;
}

pub trait CheckedCtor {
    fn new(expr: Expr) -> Result<Self, InvalidArgumentError>;
}

impl<T: UnaryOp, CheckedCtor> SigmaSerializable for T {
    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
        self.input().sigma_serialize(w)
    }

    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
        let input = Expr::sigma_parse(r)?;
        let r = T::new(input)?;
        Ok(r)
    }
}
```
> So for every node that implements UnaryOp and CheckedCtor we would have serialization implemented automatically.



Discussion / hints:

Fix patch:
diff --git a/ergotree-interpreter/src/eval/coll_filter.rs b/ergotree-interpreter/src/eval/coll_filter.rs
index 525db0d48..093a2940f 100644
--- a/ergotree-interpreter/src/eval/coll_filter.rs
+++ b/ergotree-interpreter/src/eval/coll_filter.rs
@@ -86,6 +86,7 @@ mod tests {
     use ergotree_ir::mir::func_value::FuncArg;
     use ergotree_ir::mir::func_value::FuncValue;
     use ergotree_ir::mir::property_call::PropertyCall;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use ergotree_ir::mir::val_use::ValUse;
     use ergotree_ir::types::scontext;
     use ergotree_ir::types::stype::SType;
@@ -108,7 +109,7 @@ mod tests {
                 kind: RelationOp::Le.into(),
                 left: Box::new(Expr::Const(1i64.into())),
                 right: Box::new(Expr::ExtractAmount(
-                        ExtractAmount::new(val_use)
+                        ExtractAmount::try_build(val_use)
                     .unwrap(),
                 )),
             }
diff --git a/ergotree-interpreter/src/eval/coll_fold.rs b/ergotree-interpreter/src/eval/coll_fold.rs
index 9cdbf1ac3..ae65410eb 100644
--- a/ergotree-interpreter/src/eval/coll_fold.rs
+++ b/ergotree-interpreter/src/eval/coll_fold.rs
@@ -68,6 +68,7 @@ mod tests {
     use ergotree_ir::mir::func_value::FuncValue;
     use ergotree_ir::mir::property_call::PropertyCall;
     use ergotree_ir::mir::select_field::SelectField;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use ergotree_ir::mir::val_use::ValUse;
     use ergotree_ir::types::scontext;
     use ergotree_ir::types::stuple::STuple;
@@ -98,7 +99,7 @@ mod tests {
                     SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap(),
                 )),
                 right: Box::new(Expr::ExtractAmount(
-                    ExtractAmount::new(Expr::SelectField(
+                    ExtractAmount::try_build(Expr::SelectField(
                         SelectField::new(tuple, 2.try_into().unwrap()).unwrap(),
                     ))
                     .unwrap(),
diff --git a/ergotree-interpreter/src/eval/coll_map.rs b/ergotree-interpreter/src/eval/coll_map.rs
index f6fa10ec7..2944a5e92 100644
--- a/ergotree-interpreter/src/eval/coll_map.rs
+++ b/ergotree-interpreter/src/eval/coll_map.rs
@@ -79,6 +79,7 @@ mod tests {
     use ergotree_ir::mir::func_value::FuncArg;
     use ergotree_ir::mir::func_value::FuncValue;
     use ergotree_ir::mir::property_call::PropertyCall;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use ergotree_ir::mir::val_use::ValUse;
     use ergotree_ir::types::scontext;
     use ergotree_ir::types::stype::SType;
@@ -104,7 +105,7 @@ mod tests {
                 kind: ArithOp::Plus.into(),
                 left: Box::new(Expr::Const(1i64.into())),
                 right: Box::new(Expr::ExtractAmount(
-                        ExtractAmount::new(val_use)
+                        ExtractAmount::try_build(val_use)
                     .unwrap(),
                 )),
             }
diff --git a/ergotree-interpreter/src/eval/coll_size.rs b/ergotree-interpreter/src/eval/coll_size.rs
index cdde397c7..9b21ac7d0 100644
--- a/ergotree-interpreter/src/eval/coll_size.rs
+++ b/ergotree-interpreter/src/eval/coll_size.rs
@@ -28,12 +28,15 @@ mod tests {
     use crate::eval::tests::eval_out;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use sigma_test_util::force_any_val;
     use std::rc::Rc;
 
     #[test]
     fn eval() {
-        let expr: Expr = SizeOf::new(GlobalVars::Outputs.into()).unwrap().into();
+        let expr: Expr = SizeOf::try_build(GlobalVars::Outputs.into())
+            .unwrap()
+            .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i32>(&expr, ctx.clone()),
diff --git a/ergotree-interpreter/src/eval/create_provedlog.rs b/ergotree-interpreter/src/eval/create_provedlog.rs
index 2fa1f4bbd..b672cc79e 100644
--- a/ergotree-interpreter/src/eval/create_provedlog.rs
+++ b/ergotree-interpreter/src/eval/create_provedlog.rs
@@ -9,7 +9,7 @@ use crate::eval::Evaluable;
 
 impl Evaluable for CreateProveDlog {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let value_v = self.value.eval(env, ctx)?;
+        let value_v = self.input.eval(env, ctx)?;
         match value_v {
             Value::GroupElement(ecpoint) => {
                 let prove_dlog = ProveDlog::new(*ecpoint);
diff --git a/ergotree-interpreter/src/eval/extract_creation_info.rs b/ergotree-interpreter/src/eval/extract_creation_info.rs
index 8948760cd..bb22feb3b 100644
--- a/ergotree-interpreter/src/eval/extract_creation_info.rs
+++ b/ergotree-interpreter/src/eval/extract_creation_info.rs
@@ -28,13 +28,14 @@ mod tests {
 
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use sigma_test_util::force_any_val;
 
     use super::*;
 
     #[test]
     fn eval() {
-        let expr: Expr = ExtractCreationInfo::new(GlobalVars::SelfBox.into())
+        let expr: Expr = ExtractCreationInfo::try_build(GlobalVars::SelfBox.into())
             .unwrap()
             .into();
         let ctx = Rc::new(force_any_val::<Context>());
diff --git a/ergotree-interpreter/src/eval/extract_reg_as.rs b/ergotree-interpreter/src/eval/extract_reg_as.rs
index e80852d45..2d59e20d6 100644
--- a/ergotree-interpreter/src/eval/extract_reg_as.rs
+++ b/ergotree-interpreter/src/eval/extract_reg_as.rs
@@ -30,6 +30,7 @@ mod tests {
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
     use ergotree_ir::mir::option_get::OptionGet;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use ergotree_ir::types::stype::SType;
     use sigma_test_util::force_any_val;
     use std::rc::Rc;
@@ -43,7 +44,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        let option_get_expr: Expr = OptionGet::new(get_reg_expr).unwrap().into();
+        let option_get_expr: Expr = OptionGet::try_build(get_reg_expr).unwrap().into();
         let ctx = Rc::new(force_any_val::<Context>());
         let v = eval_out::<i64>(&option_get_expr, ctx.clone());
         assert_eq!(v, ctx.self_box.get_box(&ctx.box_arena).unwrap().value());
diff --git a/ergotree-interpreter/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs
index 722235620..350db9efd 100644
--- a/ergotree-interpreter/src/eval/method_call.rs
+++ b/ergotree-interpreter/src/eval/method_call.rs
@@ -26,6 +26,7 @@ mod tests {
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::global_vars::GlobalVars;
     use ergotree_ir::mir::option_get::OptionGet;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use ergotree_ir::types::sbox;
     use sigma_test_util::force_any_val;
 
@@ -43,7 +44,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        let option_get_expr: Expr = OptionGet::new(mc).unwrap().into();
+        let option_get_expr: Expr = OptionGet::try_build(mc).unwrap().into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&option_get_expr, ctx.clone()),
diff --git a/ergotree-interpreter/src/eval/negation.rs b/ergotree-interpreter/src/eval/negation.rs
index d51fcdc8b..8f1f41c48 100644
--- a/ergotree-interpreter/src/eval/negation.rs
+++ b/ergotree-interpreter/src/eval/negation.rs
@@ -32,11 +32,14 @@ mod tests {
     use ergotree_ir::mir::constant::Constant;
     use ergotree_ir::mir::constant::TryExtractFrom;
     use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::unary_op::UnaryOpTryBuild;
     use num_bigint::ToBigInt;
     use num_traits::Num;
 
     fn run_eval<T: Num + Into<Constant> + TryExtractFrom<Value>>(input: T) -> T {
-        let expr: Expr = Negation::new(Expr::Const(input.into())).unwrap().into();
+        let expr: Expr = Negation::try_build(Expr::Const(input.into()))
+            .unwrap()
+            .into();
         eval_out_wo_ctx::<T>(&expr)
     }
 
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index 67fe3ff36..1bcf4df14 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -78,6 +78,7 @@ pub mod sigma_or;
 pub mod sigma_prop_bytes;
 /// Tuple of elements
 pub mod tuple;
+pub mod unary_op;
 pub mod upcast;
 /// Variable definition
 pub mod val_def;
diff --git a/ergotree-ir/src/mir/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs
index baafd5435..480bc6b31 100644
--- a/ergotree-ir/src/mir/bool_to_sigma.rs
+++ b/ergotree-ir/src/mir/bool_to_sigma.rs
@@ -1,9 +1,7 @@
 //! Embedding of Boolean values to SigmaProp
+use crate::mir::unary_op::UnaryOp;
+use crate::mir::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
@@ -22,14 +20,6 @@ pub struct BoolToSigmaProp {
 impl BoolToSigmaProp {
     pub(crate) const OP_CODE: OpCode = OpCode::BOOL_TO_SIGMA_PROP;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SBoolean)?;
-        Ok(Self {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
@@ -40,14 +30,17 @@ impl BoolToSigmaProp {
     }
 }
 
-impl SigmaSerializable for BoolToSigmaProp {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for BoolToSigmaProp {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for BoolToSigmaProp {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBoolean)?;
         Ok(Self {
-            input: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
diff --git a/ergotree-ir/src/mir/byte_array_to_bigint.rs b/ergotree-ir/src/mir/byte_array_to_bigint.rs
index f6861a1ce..0b4394b33 100644
--- a/ergotree-ir/src/mir/byte_array_to_bigint.rs
+++ b/ergotree-ir/src/mir/byte_array_to_bigint.rs
@@ -1,13 +1,11 @@
 //! Convert byte array to SBigInt
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Convert byte array to SBigInt
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -19,14 +17,6 @@ pub struct ByteArrayToBigInt {
 impl ByteArrayToBigInt {
     pub(crate) const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_BIGINT;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
-        Ok(ByteArrayToBigInt {
-            input: Box::new(input),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBigInt
@@ -37,14 +27,18 @@ impl ByteArrayToBigInt {
     }
 }
 
-impl SigmaSerializable for ByteArrayToBigInt {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ByteArrayToBigInt {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let input = Expr::sigma_parse(r)?;
-        Ok(ByteArrayToBigInt::new(input)?)
+impl UnaryOpTryBuild for ByteArrayToBigInt {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
+        Ok(ByteArrayToBigInt {
+            input: Box::new(input),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/byte_array_to_long.rs b/ergotree-ir/src/mir/byte_array_to_long.rs
index 0708f9a1c..c997dad4a 100644
--- a/ergotree-ir/src/mir/byte_array_to_long.rs
+++ b/ergotree-ir/src/mir/byte_array_to_long.rs
@@ -1,13 +1,11 @@
 //! Convert byte array to SLong
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Convert byte array to SLong
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -19,14 +17,6 @@ pub struct ByteArrayToLong {
 impl ByteArrayToLong {
     pub(crate) const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_LONG;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
-        Ok(ByteArrayToLong {
-            input: Box::new(input),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SLong
@@ -37,14 +27,18 @@ impl ByteArrayToLong {
     }
 }
 
-impl SigmaSerializable for ByteArrayToLong {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ByteArrayToLong {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let input = Expr::sigma_parse(r)?;
-        Ok(ByteArrayToLong::new(input)?)
+impl UnaryOpTryBuild for ByteArrayToLong {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
+        Ok(ByteArrayToLong {
+            input: Box::new(input),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/calc_blake2b256.rs b/ergotree-ir/src/mir/calc_blake2b256.rs
index eff0ce1bf..3c735fcef 100644
--- a/ergotree-ir/src/mir/calc_blake2b256.rs
+++ b/ergotree-ir/src/mir/calc_blake2b256.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Calc Blake2b 256-bit hash
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -16,14 +14,6 @@ pub struct CalcBlake2b256 {
 }
 
 impl CalcBlake2b256 {
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
-        Ok(CalcBlake2b256 {
-            input: Box::new(input),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(Box::new(SType::SByte))
@@ -34,14 +24,18 @@ impl CalcBlake2b256 {
     }
 }
 
-impl SigmaSerializable for CalcBlake2b256 {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for CalcBlake2b256 {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let input = Expr::sigma_parse(r)?;
-        Ok(CalcBlake2b256::new(input)?)
+impl UnaryOpTryBuild for CalcBlake2b256 {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
+        Ok(CalcBlake2b256 {
+            input: Box::new(input),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/coll_size.rs b/ergotree-ir/src/mir/coll_size.rs
index 5c13e6810..82562a07b 100644
--- a/ergotree-ir/src/mir/coll_size.rs
+++ b/ergotree-ir/src/mir/coll_size.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Collection size
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,19 +16,6 @@ pub struct SizeOf {
 impl SizeOf {
     pub(crate) const OP_CODE: OpCode = OpCode::SIZE_OF;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        match input.post_eval_tpe() {
-            SType::SColl(_) => Ok(Self {
-                input: input.into(),
-            }),
-            _ => Err(InvalidArgumentError(format!(
-                "Expected SizeOf input to be SColl, got {0:?}",
-                input.tpe()
-            ))),
-        }
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SInt
@@ -41,14 +26,23 @@ impl SizeOf {
     }
 }
 
-impl SigmaSerializable for SizeOf {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for SizeOf {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let input = Expr::sigma_parse(r)?;
-        Ok(Self::new(input)?)
+impl UnaryOpTryBuild for SizeOf {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        match input.post_eval_tpe() {
+            SType::SColl(_) => Ok(Self {
+                input: input.into(),
+            }),
+            _ => Err(InvalidArgumentError(format!(
+                "Expected SizeOf input to be SColl, got {0:?}",
+                input.tpe()
+            ))),
+        }
     }
 }
 
diff --git a/ergotree-ir/src/mir/create_provedlog.rs b/ergotree-ir/src/mir/create_provedlog.rs
index 30c351fe3..2234fff95 100644
--- a/ergotree-ir/src/mir/create_provedlog.rs
+++ b/ergotree-ir/src/mir/create_provedlog.rs
@@ -1,31 +1,21 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Create ProveDlog from PK
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct CreateProveDlog {
     /// GroupElement (PK)
-    pub value: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl CreateProveDlog {
     pub(crate) const OP_CODE: OpCode = OpCode::PROVE_DLOG;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(value: Expr) -> Result<Self, InvalidArgumentError> {
-        value.check_post_eval_tpe(SType::SGroupElement)?;
-        Ok(CreateProveDlog {
-            value: value.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
@@ -36,14 +26,17 @@ impl CreateProveDlog {
     }
 }
 
-impl SigmaSerializable for CreateProveDlog {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.value.sigma_serialize(w)
+impl UnaryOp for CreateProveDlog {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for CreateProveDlog {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SGroupElement)?;
         Ok(CreateProveDlog {
-            value: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
@@ -62,7 +55,7 @@ mod tests {
 
     #[test]
     fn ser_roundtrip() {
-        let e: Expr = CreateProveDlog::new(
+        let e: Expr = CreateProveDlog::try_build(
             force_any_val_with::<Constant>(ArbConstantParams::Exact(SType::SGroupElement)).into(),
         )
         .unwrap()
diff --git a/ergotree-ir/src/mir/decode_point.rs b/ergotree-ir/src/mir/decode_point.rs
index 920b331bf..663a87d58 100644
--- a/ergotree-ir/src/mir/decode_point.rs
+++ b/ergotree-ir/src/mir/decode_point.rs
@@ -1,13 +1,12 @@
 //! Decode byte array to EC point
 
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Decode byte array to EC point
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -29,14 +28,17 @@ impl DecodePoint {
     }
 }
 
-impl SigmaSerializable for DecodePoint {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for DecodePoint {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for DecodePoint {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SColl(Box::new(SType::SByte)))?;
         Ok(Self {
-            input: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
diff --git a/ergotree-ir/src/mir/extract_amount.rs b/ergotree-ir/src/mir/extract_amount.rs
index d860e81d2..955163fab 100644
--- a/ergotree-ir/src/mir/extract_amount.rs
+++ b/ergotree-ir/src/mir/extract_amount.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Box value
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,14 +16,6 @@ pub struct ExtractAmount {
 impl ExtractAmount {
     pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_AMOUNT;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SBox)?;
-        Ok(ExtractAmount {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SLong
@@ -36,14 +26,17 @@ impl ExtractAmount {
     }
 }
 
-impl SigmaSerializable for ExtractAmount {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ExtractAmount {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for ExtractAmount {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBox)?;
         Ok(ExtractAmount {
-            input: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
diff --git a/ergotree-ir/src/mir/extract_creation_info.rs b/ergotree-ir/src/mir/extract_creation_info.rs
index 62d130988..d29bbaa7b 100644
--- a/ergotree-ir/src/mir/extract_creation_info.rs
+++ b/ergotree-ir/src/mir/extract_creation_info.rs
@@ -1,13 +1,11 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stuple::STuple;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Tuple of height when block got included into the blockchain and transaction identifier with
 /// box index in the transaction outputs serialized to the byte array.
@@ -20,14 +18,6 @@ pub struct ExtractCreationInfo {
 impl ExtractCreationInfo {
     pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_CREATION_INFO;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SBox)?;
-        Ok(ExtractCreationInfo {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::STuple(STuple::pair(SType::SInt, SType::SColl(SType::SByte.into())))
@@ -38,14 +28,17 @@ impl ExtractCreationInfo {
     }
 }
 
-impl SigmaSerializable for ExtractCreationInfo {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ExtractCreationInfo {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for ExtractCreationInfo {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBox)?;
         Ok(ExtractCreationInfo {
-            input: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
diff --git a/ergotree-ir/src/mir/extract_id.rs b/ergotree-ir/src/mir/extract_id.rs
index c4cb5fd9a..668956811 100644
--- a/ergotree-ir/src/mir/extract_id.rs
+++ b/ergotree-ir/src/mir/extract_id.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Box id, Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)`
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,14 +16,6 @@ pub struct ExtractId {
 impl ExtractId {
     pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_ID;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SBox)?;
-        Ok(ExtractId {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
@@ -36,13 +26,18 @@ impl ExtractId {
     }
 }
 
-impl SigmaSerializable for ExtractId {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ExtractId {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(ExtractId::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for ExtractId {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBox)?;
+        Ok(ExtractId {
+            input: input.into(),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/extract_script_bytes.rs b/ergotree-ir/src/mir/extract_script_bytes.rs
index 700ab3876..87118604f 100644
--- a/ergotree-ir/src/mir/extract_script_bytes.rs
+++ b/ergotree-ir/src/mir/extract_script_bytes.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Serialized box guarding script
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,14 +16,6 @@ pub struct ExtractScriptBytes {
 impl ExtractScriptBytes {
     pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_SCRIPT_BYTES;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SBox)?;
-        Ok(ExtractScriptBytes {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
@@ -36,13 +26,18 @@ impl ExtractScriptBytes {
     }
 }
 
-impl SigmaSerializable for ExtractScriptBytes {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for ExtractScriptBytes {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(ExtractScriptBytes::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for ExtractScriptBytes {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBox)?;
+        Ok(ExtractScriptBytes {
+            input: input.into(),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/logical_not.rs b/ergotree-ir/src/mir/logical_not.rs
index 0b0fc11bf..b78a4b87d 100644
--- a/ergotree-ir/src/mir/logical_not.rs
+++ b/ergotree-ir/src/mir/logical_not.rs
@@ -1,9 +1,8 @@
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 /// Logical NOT (inverts the input)
@@ -26,14 +25,17 @@ impl LogicalNot {
     }
 }
 
-impl SigmaSerializable for LogicalNot {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for LogicalNot {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+impl UnaryOpTryBuild for LogicalNot {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SBoolean)?;
         Ok(Self {
-            input: Expr::sigma_parse(r)?.into(),
+            input: input.into(),
         })
     }
 }
diff --git a/ergotree-ir/src/mir/long_to_byte_array.rs b/ergotree-ir/src/mir/long_to_byte_array.rs
index bdc40c70e..3b1e46b1e 100644
--- a/ergotree-ir/src/mir/long_to_byte_array.rs
+++ b/ergotree-ir/src/mir/long_to_byte_array.rs
@@ -1,13 +1,11 @@
 //! Convert SLong to byte array
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Convert SLong to byte array
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -19,14 +17,6 @@ pub struct LongToByteArray {
 impl LongToByteArray {
     pub(crate) const OP_CODE: OpCode = OpCode::LONG_TO_BYTE_ARRAY;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SLong)?;
-        Ok(LongToByteArray {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
@@ -37,14 +27,21 @@ impl LongToByteArray {
     }
 }
 
-impl SigmaSerializable for LongToByteArray {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for LongToByteArray {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let input = Expr::sigma_parse(r)?;
-        Ok(LongToByteArray::new(input)?)
+impl UnaryOpTryBuild for LongToByteArray {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError>
+    where
+        Self: Sized,
+    {
+        input.check_post_eval_tpe(SType::SLong)?;
+        Ok(LongToByteArray {
+            input: input.into(),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/negation.rs b/ergotree-ir/src/mir/negation.rs
index 06ef7084f..89db293a0 100644
--- a/ergotree-ir/src/mir/negation.rs
+++ b/ergotree-ir/src/mir/negation.rs
@@ -1,10 +1,8 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 /// Negation operation on numeric type.
@@ -17,20 +15,6 @@ pub struct Negation {
 impl Negation {
     pub(crate) const OP_CODE: OpCode = OpCode::NEGATION;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        let post_eval_tpe = input.post_eval_tpe();
-        if !post_eval_tpe.is_numeric() {
-            return Err(InvalidArgumentError(format!(
-                "Negation: expected input type to be numeric, got {:?}",
-                post_eval_tpe
-            )));
-        }
-        Ok(Self {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         self.input.post_eval_tpe()
@@ -41,13 +25,24 @@ impl Negation {
     }
 }
 
-impl SigmaSerializable for Negation {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for Negation {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(Self::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for Negation {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        let post_eval_tpe = input.post_eval_tpe();
+        if !post_eval_tpe.is_numeric() {
+            return Err(InvalidArgumentError(format!(
+                "Negation: expected input type to be numeric, got {:?}",
+                post_eval_tpe
+            )));
+        }
+        Ok(Self {
+            input: input.into(),
+        })
     }
 }
 
@@ -82,7 +77,7 @@ mod arbitrary {
                     depth: 0,
                 }),
             ]
-            .prop_map(|input| Self::new(input).unwrap())
+            .prop_map(|input| Self::try_build(input).unwrap())
             .boxed()
         }
     }
diff --git a/ergotree-ir/src/mir/option_get.rs b/ergotree-ir/src/mir/option_get.rs
index 17ed29e64..617624eb6 100644
--- a/ergotree-ir/src/mir/option_get.rs
+++ b/ergotree-ir/src/mir/option_get.rs
@@ -1,10 +1,8 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 /// Returns the Option's value or error if no value
@@ -17,19 +15,6 @@ pub struct OptionGet {
 impl OptionGet {
     pub(crate) const OP_CODE: OpCode = OpCode::OPTION_GET;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        match input.post_eval_tpe() {
-            SType::SOption(_) => Ok(OptionGet {
-                input: Box::new(input),
-            }),
-            _ => Err(InvalidArgumentError(format!(
-                "expected OptionGet::input type to be SOption, got: {0:?}",
-                input.tpe(),
-            ))),
-        }
-    }
-
     pub(crate) fn op_code(&self) -> OpCode {
         Self::OP_CODE
     }
@@ -46,13 +31,23 @@ impl OptionGet {
     }
 }
 
-impl SigmaSerializable for OptionGet {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for OptionGet {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(OptionGet::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for OptionGet {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        match input.post_eval_tpe() {
+            SType::SOption(_) => Ok(OptionGet {
+                input: Box::new(input),
+            }),
+            _ => Err(InvalidArgumentError(format!(
+                "expected OptionGet::input type to be SOption, got: {0:?}",
+                input.tpe(),
+            ))),
+        }
     }
 }
 
diff --git a/ergotree-ir/src/mir/option_is_defined.rs b/ergotree-ir/src/mir/option_is_defined.rs
index b235b1612..5aeac6520 100644
--- a/ergotree-ir/src/mir/option_is_defined.rs
+++ b/ergotree-ir/src/mir/option_is_defined.rs
@@ -1,10 +1,8 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 /// Returns false if the option is None, true otherwise.
@@ -17,19 +15,6 @@ pub struct OptionIsDefined {
 impl OptionIsDefined {
     pub(crate) const OP_CODE: OpCode = OpCode::OPTION_IS_DEFINED;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        match input.post_eval_tpe() {
-            SType::SOption(_) => Ok(OptionIsDefined {
-                input: Box::new(input),
-            }),
-            _ => Err(InvalidArgumentError(format!(
-                "expected OptionIsDefined::input type to be SOption, got: {0:?}",
-                input.tpe(),
-            ))),
-        }
-    }
-
     pub(crate) fn op_code(&self) -> OpCode {
         Self::OP_CODE
     }
@@ -40,13 +25,26 @@ impl OptionIsDefined {
     }
 }
 
-impl SigmaSerializable for OptionIsDefined {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for OptionIsDefined {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(OptionIsDefined::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for OptionIsDefined {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError>
+    where
+        Self: Sized,
+    {
+        match input.post_eval_tpe() {
+            SType::SOption(_) => Ok(OptionIsDefined {
+                input: Box::new(input),
+            }),
+            _ => Err(InvalidArgumentError(format!(
+                "expected OptionIsDefined::input type to be SOption, got: {0:?}",
+                input.tpe(),
+            ))),
+        }
     }
 }
 
@@ -70,7 +68,7 @@ mod tests {
         )
         .unwrap()
         .into();
-        let e: Expr = OptionIsDefined::new(get_reg_expr).unwrap().into();
+        let e: Expr = OptionIsDefined::try_build(get_reg_expr).unwrap().into();
         assert_eq![sigma_serialize_roundtrip(&e), e];
     }
 }
diff --git a/ergotree-ir/src/mir/sigma_prop_bytes.rs b/ergotree-ir/src/mir/sigma_prop_bytes.rs
index 5447bf6c0..32c92f875 100644
--- a/ergotree-ir/src/mir/sigma_prop_bytes.rs
+++ b/ergotree-ir/src/mir/sigma_prop_bytes.rs
@@ -1,12 +1,10 @@
 use crate::serialization::op_code::OpCode;
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use super::unary_op::UnaryOp;
+use super::unary_op::UnaryOpTryBuild;
 
 /// Extract serialized bytes of a SigmaProp value
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,14 +16,6 @@ pub struct SigmaPropBytes {
 impl SigmaPropBytes {
     pub(crate) const OP_CODE: OpCode = OpCode::SIGMA_PROP_BYTES;
 
-    /// Create new object, returns an error if any of the requirements failed
-    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
-        input.check_post_eval_tpe(SType::SSigmaProp)?;
-        Ok(SigmaPropBytes {
-            input: input.into(),
-        })
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
@@ -36,13 +26,18 @@ impl SigmaPropBytes {
     }
 }
 
-impl SigmaSerializable for SigmaPropBytes {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        self.input.sigma_serialize(w)
+impl UnaryOp for SigmaPropBytes {
+    fn input(&self) -> &Expr {
+        &self.input
     }
+}
 
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(SigmaPropBytes::new(Expr::sigma_parse(r)?)?)
+impl UnaryOpTryBuild for SigmaPropBytes {
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError> {
+        input.check_post_eval_tpe(SType::SSigmaProp)?;
+        Ok(SigmaPropBytes {
+            input: input.into(),
+        })
     }
 }
 
diff --git a/ergotree-ir/src/mir/unary_op.rs b/ergotree-ir/src/mir/unary_op.rs
new file mode 100644
index 000000000..7c8b785cf
--- /dev/null
+++ b/ergotree-ir/src/mir/unary_op.rs
@@ -0,0 +1,33 @@
+//! Traits for IR nodes with one input value(expr)
+
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+
+/// IR nodes with one input value(expr)
+pub trait UnaryOp {
+    /// Input value(expr) of the IR node
+    fn input(&self) -> &Expr;
+}
+
+/// Constructor for unary IR nodes that check the validity of the argument
+pub trait UnaryOpTryBuild: Sized {
+    /// Create new IR node, returns an error if any of the requirements failed
+    fn try_build(input: Expr) -> Result<Self, InvalidArgumentError>;
+}
+
+impl<T: UnaryOp + UnaryOpTryBuild> SigmaSerializable for T {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input().sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let input = Expr::sigma_parse(r)?;
+        let r = T::try_build(input)?;
+        Ok(r)
+    }
+}


Test patch: