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

Issue:
DRY idea for IR node's `fn op_code(&self)` 
Extracted from #241

> Given that we can replace op_code() in the existing approach with some HasOpCode trait that would add a line impl HasOpCode for CalcBlake2b256 {} for each node.

```rust
trait HasOpCode{
    const OP_CODE: OpCode;

    fn op_code(&self) -> OpCode {
        Self::OP_CODE
    }
}
```


Discussion / hints:

Fix patch:
diff --git a/ergotree-ir/src/has_opcode.rs b/ergotree-ir/src/has_opcode.rs
new file mode 100644
index 000000000..27ee2cacf
--- /dev/null
+++ b/ergotree-ir/src/has_opcode.rs
@@ -0,0 +1,15 @@
+use crate::serialization::op_code::OpCode;
+
+pub(crate) trait HasStaticOpCode {
+    const OP_CODE: OpCode;
+}
+
+pub(crate) trait HasOpCode {
+    fn op_code(&self) -> OpCode;
+}
+
+impl<T: HasStaticOpCode> HasOpCode for T {
+    fn op_code(&self) -> OpCode {
+        T::OP_CODE
+    }
+}
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
index 789f23750..e7ef81e6d 100644
--- a/ergotree-ir/src/lib.rs
+++ b/ergotree-ir/src/lib.rs
@@ -15,6 +15,8 @@
 #![deny(clippy::unwrap_used)]
 #![deny(clippy::expect_used)]
 
+mod has_opcode;
+
 pub mod address;
 pub mod ergo_tree;
 pub mod ir_ergo_box;
diff --git a/ergotree-ir/src/mir/and.rs b/ergotree-ir/src/mir/and.rs
index e14d2c05e..2da39bb3a 100644
--- a/ergotree-ir/src/mir/and.rs
+++ b/ergotree-ir/src/mir/and.rs
@@ -8,6 +8,7 @@ use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 
 /// AND logical conjunction
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -17,16 +18,14 @@ pub struct And {
 }
 
 impl And {
-    pub(crate) const OP_CODE: OpCode = OpCode::AND;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for And {
+    const OP_CODE: OpCode = OpCode::AND;
 }
 
 impl SigmaSerializable for And {
diff --git a/ergotree-ir/src/mir/apply.rs b/ergotree-ir/src/mir/apply.rs
index d22bb0c5c..154d8cdde 100644
--- a/ergotree-ir/src/mir/apply.rs
+++ b/ergotree-ir/src/mir/apply.rs
@@ -10,6 +10,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Application of function `func` to given arguments `args`
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -55,10 +56,10 @@ impl Apply {
             _ => panic!("unexpected Apply::func: {0:?}", self.func.tpe()),
         }
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::APPLY
-    }
+impl HasStaticOpCode for Apply {
+    const OP_CODE: OpCode = OpCode::APPLY;
 }
 
 impl SigmaSerializable for Apply {
diff --git a/ergotree-ir/src/mir/atleast.rs b/ergotree-ir/src/mir/atleast.rs
index 9abb47ead..c190a8dfa 100644
--- a/ergotree-ir/src/mir/atleast.rs
+++ b/ergotree-ir/src/mir/atleast.rs
@@ -7,6 +7,7 @@ use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 
 /// THRESHOLD composition for sigma expressions
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -18,16 +19,14 @@ pub struct Atleast {
 }
 
 impl Atleast {
-    pub(crate) const OP_CODE: OpCode = OpCode::ATLEAST;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Atleast {
+    const OP_CODE: OpCode = OpCode::ATLEAST;
 }
 
 impl SigmaSerializable for Atleast {
diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs
index 5b2ae3fb0..c1ccb6dda 100644
--- a/ergotree-ir/src/mir/bin_op.rs
+++ b/ergotree-ir/src/mir/bin_op.rs
@@ -1,6 +1,7 @@
 //! Operators in ErgoTree
 
 use super::expr::Expr;
+use crate::has_opcode::HasOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -109,11 +110,6 @@ pub struct BinOp {
 }
 
 impl BinOp {
-    /// Op code (serialization)
-    pub(crate) fn op_code(&self) -> OpCode {
-        self.kind.into()
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         match self.kind {
@@ -123,6 +119,12 @@ impl BinOp {
     }
 }
 
+impl HasOpCode for BinOp {
+    fn op_code(&self) -> OpCode {
+        self.kind.into()
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 /// Arbitrary impl
 mod arbitrary {
diff --git a/ergotree-ir/src/mir/block.rs b/ergotree-ir/src/mir/block.rs
index e3faf754e..271266ca0 100644
--- a/ergotree-ir/src/mir/block.rs
+++ b/ergotree-ir/src/mir/block.rs
@@ -2,6 +2,7 @@
 use std::io;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -30,10 +31,10 @@ impl BlockValue {
     pub fn tpe(&self) -> SType {
         self.result.tpe()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::BLOCK_VALUE
-    }
+impl HasStaticOpCode for BlockValue {
+    const OP_CODE: OpCode = OpCode::BLOCK_VALUE;
 }
 
 impl SigmaSerializable for BlockValue {
diff --git a/ergotree-ir/src/mir/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs
index 480bc6b31..d7a6f00bb 100644
--- a/ergotree-ir/src/mir/bool_to_sigma.rs
+++ b/ergotree-ir/src/mir/bool_to_sigma.rs
@@ -1,4 +1,5 @@
 //! Embedding of Boolean values to SigmaProp
+use crate::has_opcode::HasStaticOpCode;
 use crate::mir::unary_op::UnaryOp;
 use crate::mir::unary_op::UnaryOpTryBuild;
 use crate::serialization::op_code::OpCode;
@@ -18,16 +19,14 @@ pub struct BoolToSigmaProp {
 }
 
 impl BoolToSigmaProp {
-    pub(crate) const OP_CODE: OpCode = OpCode::BOOL_TO_SIGMA_PROP;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for BoolToSigmaProp {
+    const OP_CODE: OpCode = OpCode::BOOL_TO_SIGMA_PROP;
 }
 
 impl UnaryOp for BoolToSigmaProp {
diff --git a/ergotree-ir/src/mir/byte_array_to_bigint.rs b/ergotree-ir/src/mir/byte_array_to_bigint.rs
index 0b4394b33..b21979bf2 100644
--- a/ergotree-ir/src/mir/byte_array_to_bigint.rs
+++ b/ergotree-ir/src/mir/byte_array_to_bigint.rs
@@ -6,6 +6,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Convert byte array to SBigInt
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -15,16 +16,14 @@ pub struct ByteArrayToBigInt {
 }
 
 impl ByteArrayToBigInt {
-    pub(crate) const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_BIGINT;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBigInt
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ByteArrayToBigInt {
+    const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_BIGINT;
 }
 
 impl UnaryOp for ByteArrayToBigInt {
diff --git a/ergotree-ir/src/mir/byte_array_to_long.rs b/ergotree-ir/src/mir/byte_array_to_long.rs
index c997dad4a..19c620536 100644
--- a/ergotree-ir/src/mir/byte_array_to_long.rs
+++ b/ergotree-ir/src/mir/byte_array_to_long.rs
@@ -6,6 +6,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Convert byte array to SLong
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -15,16 +16,14 @@ pub struct ByteArrayToLong {
 }
 
 impl ByteArrayToLong {
-    pub(crate) const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_LONG;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SLong
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ByteArrayToLong {
+    const OP_CODE: OpCode = OpCode::BYTE_ARRAY_TO_LONG;
 }
 
 impl UnaryOp for ByteArrayToLong {
diff --git a/ergotree-ir/src/mir/calc_blake2b256.rs b/ergotree-ir/src/mir/calc_blake2b256.rs
index 3c735fcef..1cfe10011 100644
--- a/ergotree-ir/src/mir/calc_blake2b256.rs
+++ b/ergotree-ir/src/mir/calc_blake2b256.rs
@@ -1,3 +1,4 @@
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -18,10 +19,10 @@ impl CalcBlake2b256 {
     pub fn tpe(&self) -> SType {
         SType::SColl(Box::new(SType::SByte))
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::CALC_BLAKE2B256
-    }
+impl HasStaticOpCode for CalcBlake2b256 {
+    const OP_CODE: OpCode = OpCode::CALC_BLAKE2B256;
 }
 
 impl UnaryOp for CalcBlake2b256 {
diff --git a/ergotree-ir/src/mir/coll_by_index.rs b/ergotree-ir/src/mir/coll_by_index.rs
index 5df1d41ac..ac8b0d917 100644
--- a/ergotree-ir/src/mir/coll_by_index.rs
+++ b/ergotree-ir/src/mir/coll_by_index.rs
@@ -7,6 +7,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Get collection element by index
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -20,8 +21,6 @@ pub struct ByIndex {
 }
 
 impl ByIndex {
-    pub(crate) const OP_CODE: OpCode = OpCode::BY_INDEX;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(
         input: Expr,
@@ -65,10 +64,10 @@ impl ByIndex {
             _ => panic!("collection is expected"),
         }
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ByIndex {
+    const OP_CODE: OpCode = OpCode::BY_INDEX;
 }
 
 impl SigmaSerializable for ByIndex {
diff --git a/ergotree-ir/src/mir/coll_exists.rs b/ergotree-ir/src/mir/coll_exists.rs
index f36710cbb..df694fe33 100644
--- a/ergotree-ir/src/mir/coll_exists.rs
+++ b/ergotree-ir/src/mir/coll_exists.rs
@@ -1,5 +1,6 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -19,8 +20,6 @@ pub struct Exists {
 }
 
 impl Exists {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXISTS;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, condition: Expr) -> Result<Self, InvalidArgumentError> {
         let input_elem_type: SType = *match input.post_eval_tpe() {
@@ -52,10 +51,10 @@ impl Exists {
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Exists {
+    const OP_CODE: OpCode = OpCode::EXISTS;
 }
 
 impl SigmaSerializable for Exists {
diff --git a/ergotree-ir/src/mir/coll_filter.rs b/ergotree-ir/src/mir/coll_filter.rs
index 4bb19dad5..0213abeeb 100644
--- a/ergotree-ir/src/mir/coll_filter.rs
+++ b/ergotree-ir/src/mir/coll_filter.rs
@@ -1,5 +1,6 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -19,8 +20,6 @@ pub struct Filter {
 }
 
 impl Filter {
-    pub(crate) const OP_CODE: OpCode = OpCode::FILTER;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, condition: Expr) -> Result<Self, InvalidArgumentError> {
         let input_elem_type: SType = *match input.post_eval_tpe() {
@@ -52,10 +51,10 @@ impl Filter {
     pub fn tpe(&self) -> SType {
         SType::SColl(self.elem_tpe.clone().into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Filter {
+    const OP_CODE: OpCode = OpCode::FILTER;
 }
 
 impl SigmaSerializable for Filter {
diff --git a/ergotree-ir/src/mir/coll_fold.rs b/ergotree-ir/src/mir/coll_fold.rs
index 786e83962..e755fdbcc 100644
--- a/ergotree-ir/src/mir/coll_fold.rs
+++ b/ergotree-ir/src/mir/coll_fold.rs
@@ -8,6 +8,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Applies a binary function to a start value and all elements of this collection,
 /// going left to right.
@@ -52,10 +53,10 @@ impl Fold {
     pub fn tpe(&self) -> SType {
         self.zero.tpe()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::FOLD
-    }
+impl HasStaticOpCode for Fold {
+    const OP_CODE: OpCode = OpCode::FOLD;
 }
 
 impl SigmaSerializable for Fold {
diff --git a/ergotree-ir/src/mir/coll_forall.rs b/ergotree-ir/src/mir/coll_forall.rs
index 35b36d1c1..ed62ec48e 100644
--- a/ergotree-ir/src/mir/coll_forall.rs
+++ b/ergotree-ir/src/mir/coll_forall.rs
@@ -1,5 +1,6 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -19,8 +20,6 @@ pub struct ForAll {
 }
 
 impl ForAll {
-    pub(crate) const OP_CODE: OpCode = OpCode::FOR_ALL;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, condition: Expr) -> Result<Self, InvalidArgumentError> {
         let input_elem_type: SType = *match input.post_eval_tpe() {
@@ -52,10 +51,10 @@ impl ForAll {
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ForAll {
+    const OP_CODE: OpCode = OpCode::FOR_ALL;
 }
 
 impl SigmaSerializable for ForAll {
diff --git a/ergotree-ir/src/mir/coll_map.rs b/ergotree-ir/src/mir/coll_map.rs
index 069cd73c7..994cefde5 100644
--- a/ergotree-ir/src/mir/coll_map.rs
+++ b/ergotree-ir/src/mir/coll_map.rs
@@ -8,6 +8,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Builds a new collection by applying a function to all elements of this collection.
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -21,8 +22,6 @@ pub struct Map {
 }
 
 impl Map {
-    pub(crate) const OP_CODE: OpCode = OpCode::MAP;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, mapper: Expr) -> Result<Self, InvalidArgumentError> {
         let input_elem_type: SType = *match input.post_eval_tpe() {
@@ -54,10 +53,10 @@ impl Map {
     pub fn out_elem_tpe(&self) -> SType {
         *self.mapper_sfunc.t_range.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Map {
+    const OP_CODE: OpCode = OpCode::MAP;
 }
 
 impl SigmaSerializable for Map {
diff --git a/ergotree-ir/src/mir/coll_size.rs b/ergotree-ir/src/mir/coll_size.rs
index 82562a07b..e2e11c4f5 100644
--- a/ergotree-ir/src/mir/coll_size.rs
+++ b/ergotree-ir/src/mir/coll_size.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Collection size
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct SizeOf {
 }
 
 impl SizeOf {
-    pub(crate) const OP_CODE: OpCode = OpCode::SIZE_OF;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SInt
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for SizeOf {
+    const OP_CODE: OpCode = OpCode::SIZE_OF;
 }
 
 impl UnaryOp for SizeOf {
diff --git a/ergotree-ir/src/mir/collection.rs b/ergotree-ir/src/mir/collection.rs
index e9db7ecb2..75e494b68 100644
--- a/ergotree-ir/src/mir/collection.rs
+++ b/ergotree-ir/src/mir/collection.rs
@@ -1,3 +1,4 @@
+use crate::has_opcode::HasOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -59,8 +60,10 @@ impl Collection {
             .into(),
         )
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
+impl HasOpCode for Collection {
+    fn op_code(&self) -> OpCode {
         match self {
             Collection::BoolConstants(_) => OpCode::COLL_OF_BOOL_CONST,
             Collection::Exprs { .. } => OpCode::COLL,
diff --git a/ergotree-ir/src/mir/constant/constant_placeholder.rs b/ergotree-ir/src/mir/constant/constant_placeholder.rs
index 08037c1c9..01c16544e 100644
--- a/ergotree-ir/src/mir/constant/constant_placeholder.rs
+++ b/ergotree-ir/src/mir/constant/constant_placeholder.rs
@@ -1,3 +1,4 @@
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -10,12 +11,8 @@ pub struct ConstantPlaceholder {
     pub tpe: SType,
 }
 
-impl ConstantPlaceholder {
-    /// OpCode value
-    pub(crate) const OP_CODE: OpCode = OpCode::CONSTANT_PLACEHOLDER;
+impl ConstantPlaceholder {}
 
-    /// OpCode for the serialization
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::CONSTANT_PLACEHOLDER
-    }
+impl HasStaticOpCode for ConstantPlaceholder {
+    const OP_CODE: OpCode = OpCode::CONSTANT_PLACEHOLDER;
 }
diff --git a/ergotree-ir/src/mir/create_prove_dh_tuple.rs b/ergotree-ir/src/mir/create_prove_dh_tuple.rs
index 71b2d6f8f..2c380dfba 100644
--- a/ergotree-ir/src/mir/create_prove_dh_tuple.rs
+++ b/ergotree-ir/src/mir/create_prove_dh_tuple.rs
@@ -6,8 +6,8 @@ use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 use crate::mir::expr::InvalidArgumentError;
-//use super::expr::InvalidArgumentError;
 
 /// Diffie-Hellman tuple.
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -23,8 +23,6 @@ pub struct CreateProveDhTuple {
 }
 
 impl CreateProveDhTuple {
-    pub(crate) const OP_CODE: OpCode = OpCode::PROVE_DIFFIE_HELLMAN_TUPLE;
-
     /// Create ProveDHTuple from four points on elliptic curve
     pub fn new(gv: Expr, hv: Expr, uv: Expr, vv: Expr) -> Result<Self, InvalidArgumentError> {
         gv.check_post_eval_tpe(SType::SGroupElement)?;
@@ -43,10 +41,10 @@ impl CreateProveDhTuple {
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for CreateProveDhTuple {
+    const OP_CODE: OpCode = OpCode::PROVE_DIFFIE_HELLMAN_TUPLE;
 }
 
 impl SigmaSerializable for CreateProveDhTuple {
diff --git a/ergotree-ir/src/mir/create_provedlog.rs b/ergotree-ir/src/mir/create_provedlog.rs
index 2234fff95..a174da266 100644
--- a/ergotree-ir/src/mir/create_provedlog.rs
+++ b/ergotree-ir/src/mir/create_provedlog.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Create ProveDlog from PK
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct CreateProveDlog {
 }
 
 impl CreateProveDlog {
-    pub(crate) const OP_CODE: OpCode = OpCode::PROVE_DLOG;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for CreateProveDlog {
+    const OP_CODE: OpCode = OpCode::PROVE_DLOG;
 }
 
 impl UnaryOp for CreateProveDlog {
diff --git a/ergotree-ir/src/mir/decode_point.rs b/ergotree-ir/src/mir/decode_point.rs
index 663a87d58..35d64fb84 100644
--- a/ergotree-ir/src/mir/decode_point.rs
+++ b/ergotree-ir/src/mir/decode_point.rs
@@ -7,6 +7,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Decode byte array to EC point
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -16,16 +17,14 @@ pub struct DecodePoint {
 }
 
 impl DecodePoint {
-    pub(crate) const OP_CODE: OpCode = OpCode::DECODE_POINT;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SGroupElement
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for DecodePoint {
+    const OP_CODE: OpCode = OpCode::DECODE_POINT;
 }
 
 impl UnaryOp for DecodePoint {
diff --git a/ergotree-ir/src/mir/deserialize_context.rs b/ergotree-ir/src/mir/deserialize_context.rs
index b0a99335a..508428fba 100644
--- a/ergotree-ir/src/mir/deserialize_context.rs
+++ b/ergotree-ir/src/mir/deserialize_context.rs
@@ -1,5 +1,6 @@
 //! Extracts context variable as byte array
 
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -19,16 +20,14 @@ pub struct DeserializeContext {
     pub id: u8,
 }
 impl DeserializeContext {
-    pub(crate) const OP_CODE: OpCode = OpCode::DESERIALIZE_CONTEXT;
-
     /// Type of value
     pub fn tpe(&self) -> SType {
         self.tpe.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for DeserializeContext {
+    const OP_CODE: OpCode = OpCode::DESERIALIZE_CONTEXT;
 }
 
 impl SigmaSerializable for DeserializeContext {
diff --git a/ergotree-ir/src/mir/deserialize_register.rs b/ergotree-ir/src/mir/deserialize_register.rs
index 8c72705ee..081264121 100644
--- a/ergotree-ir/src/mir/deserialize_register.rs
+++ b/ergotree-ir/src/mir/deserialize_register.rs
@@ -1,6 +1,7 @@
 //! Extract register of SELF box as `Coll[Byte]`, deserialize it into Value and inline into executing script.
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -20,16 +21,14 @@ pub struct DeserializeRegister {
     pub default: Option<Box<Expr>>,
 }
 impl DeserializeRegister {
-    pub(crate) const OP_CODE: OpCode = OpCode::DESERIALIZE_REGISTER;
-
     /// Type
     pub fn tpe(&self) -> SType {
         self.tpe.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for DeserializeRegister {
+    const OP_CODE: OpCode = OpCode::DESERIALIZE_REGISTER;
 }
 
 impl SigmaSerializable for DeserializeRegister {
diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs
index da91ab30e..49245f882 100644
--- a/ergotree-ir/src/mir/expr.rs
+++ b/ergotree-ir/src/mir/expr.rs
@@ -3,6 +3,7 @@
 use std::convert::TryFrom;
 use std::convert::TryInto;
 
+use crate::has_opcode::HasOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
diff --git a/ergotree-ir/src/mir/extract_amount.rs b/ergotree-ir/src/mir/extract_amount.rs
index 955163fab..50a39cce5 100644
--- a/ergotree-ir/src/mir/extract_amount.rs
+++ b/ergotree-ir/src/mir/extract_amount.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Box value
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct ExtractAmount {
 }
 
 impl ExtractAmount {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_AMOUNT;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SLong
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ExtractAmount {
+    const OP_CODE: OpCode = OpCode::EXTRACT_AMOUNT;
 }
 
 impl UnaryOp for ExtractAmount {
diff --git a/ergotree-ir/src/mir/extract_creation_info.rs b/ergotree-ir/src/mir/extract_creation_info.rs
index d29bbaa7b..e87241369 100644
--- a/ergotree-ir/src/mir/extract_creation_info.rs
+++ b/ergotree-ir/src/mir/extract_creation_info.rs
@@ -6,6 +6,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// 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.
@@ -16,16 +17,14 @@ pub struct ExtractCreationInfo {
 }
 
 impl ExtractCreationInfo {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_CREATION_INFO;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::STuple(STuple::pair(SType::SInt, SType::SColl(SType::SByte.into())))
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ExtractCreationInfo {
+    const OP_CODE: OpCode = OpCode::EXTRACT_CREATION_INFO;
 }
 
 impl UnaryOp for ExtractCreationInfo {
diff --git a/ergotree-ir/src/mir/extract_id.rs b/ergotree-ir/src/mir/extract_id.rs
index 668956811..1ea94ee07 100644
--- a/ergotree-ir/src/mir/extract_id.rs
+++ b/ergotree-ir/src/mir/extract_id.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Box id, Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)`
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct ExtractId {
 }
 
 impl ExtractId {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_ID;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ExtractId {
+    const OP_CODE: OpCode = OpCode::EXTRACT_ID;
 }
 
 impl UnaryOp for ExtractId {
diff --git a/ergotree-ir/src/mir/extract_reg_as.rs b/ergotree-ir/src/mir/extract_reg_as.rs
index daa2417ef..d287924e3 100644
--- a/ergotree-ir/src/mir/extract_reg_as.rs
+++ b/ergotree-ir/src/mir/extract_reg_as.rs
@@ -7,6 +7,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Get box register value (Box.R0 - R9)
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -20,8 +21,6 @@ pub struct ExtractRegisterAs {
 }
 
 impl ExtractRegisterAs {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_REGISTER_AS;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, register_id: i8, tpe: SType) -> Result<Self, InvalidArgumentError> {
         if input.post_eval_tpe() != SType::SBox {
@@ -45,16 +44,16 @@ impl ExtractRegisterAs {
         })
     }
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SOption(self.elem_tpe.clone().into())
     }
 }
 
+impl HasStaticOpCode for ExtractRegisterAs {
+    const OP_CODE: OpCode = OpCode::EXTRACT_REGISTER_AS;
+}
+
 impl SigmaSerializable for ExtractRegisterAs {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)?;
diff --git a/ergotree-ir/src/mir/extract_script_bytes.rs b/ergotree-ir/src/mir/extract_script_bytes.rs
index 87118604f..e5db1ecc0 100644
--- a/ergotree-ir/src/mir/extract_script_bytes.rs
+++ b/ergotree-ir/src/mir/extract_script_bytes.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Serialized box guarding script
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct ExtractScriptBytes {
 }
 
 impl ExtractScriptBytes {
-    pub(crate) const OP_CODE: OpCode = OpCode::EXTRACT_SCRIPT_BYTES;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for ExtractScriptBytes {
+    const OP_CODE: OpCode = OpCode::EXTRACT_SCRIPT_BYTES;
 }
 
 impl UnaryOp for ExtractScriptBytes {
diff --git a/ergotree-ir/src/mir/func_value.rs b/ergotree-ir/src/mir/func_value.rs
index 6f0f401c1..9c7b7f983 100644
--- a/ergotree-ir/src/mir/func_value.rs
+++ b/ergotree-ir/src/mir/func_value.rs
@@ -1,5 +1,6 @@
 use std::io;
 
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -76,10 +77,10 @@ impl FuncValue {
     pub fn tpe(&self) -> SType {
         self.tpe.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::FUNC_VALUE
-    }
+impl HasStaticOpCode for FuncValue {
+    const OP_CODE: OpCode = OpCode::FUNC_VALUE;
 }
 
 impl SigmaSerializable for FuncValue {
diff --git a/ergotree-ir/src/mir/get_var.rs b/ergotree-ir/src/mir/get_var.rs
index e37ed8815..a05870caa 100644
--- a/ergotree-ir/src/mir/get_var.rs
+++ b/ergotree-ir/src/mir/get_var.rs
@@ -1,4 +1,5 @@
 //! Extracts Context variable by id and type
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -16,16 +17,14 @@ pub struct GetVar {
 }
 
 impl GetVar {
-    pub(crate) const OP_CODE: OpCode = OpCode::GET_VAR;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SOption(self.var_tpe.clone().into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for GetVar {
+    const OP_CODE: OpCode = OpCode::GET_VAR;
 }
 
 impl SigmaSerializable for GetVar {
diff --git a/ergotree-ir/src/mir/global_vars.rs b/ergotree-ir/src/mir/global_vars.rs
index a61b1dae8..c8768eed4 100644
--- a/ergotree-ir/src/mir/global_vars.rs
+++ b/ergotree-ir/src/mir/global_vars.rs
@@ -1,5 +1,6 @@
 //! Global variables
 
+use crate::has_opcode::HasOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -19,17 +20,6 @@ pub enum GlobalVars {
 }
 
 impl GlobalVars {
-    /// Op code (serialization)
-    pub(crate) fn op_code(&self) -> OpCode {
-        match self {
-            GlobalVars::SelfBox => OpCode::SELF_BOX,
-            GlobalVars::Inputs => OpCode::INPUTS,
-            GlobalVars::Outputs => OpCode::OUTPUTS,
-            GlobalVars::Height => OpCode::HEIGHT,
-            GlobalVars::MinerPubKey => OpCode::MINER_PUBKEY,
-        }
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         match self {
@@ -42,6 +32,19 @@ impl GlobalVars {
     }
 }
 
+impl HasOpCode for GlobalVars {
+    /// Op code (serialization)
+    fn op_code(&self) -> OpCode {
+        match self {
+            GlobalVars::SelfBox => OpCode::SELF_BOX,
+            GlobalVars::Inputs => OpCode::INPUTS,
+            GlobalVars::Outputs => OpCode::OUTPUTS,
+            GlobalVars::Height => OpCode::HEIGHT,
+            GlobalVars::MinerPubKey => OpCode::MINER_PUBKEY,
+        }
+    }
+}
+
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
diff --git a/ergotree-ir/src/mir/if_op.rs b/ergotree-ir/src/mir/if_op.rs
index 0eed4af36..7e0d0ec4c 100644
--- a/ergotree-ir/src/mir/if_op.rs
+++ b/ergotree-ir/src/mir/if_op.rs
@@ -1,4 +1,5 @@
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -18,16 +19,14 @@ pub struct If {
 }
 
 impl If {
-    pub(crate) const OP_CODE: OpCode = OpCode::IF;
-
     /// Type
     pub fn tpe(&self) -> SType {
         self.true_branch.tpe()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for If {
+    const OP_CODE: OpCode = OpCode::IF;
 }
 
 impl SigmaSerializable for If {
diff --git a/ergotree-ir/src/mir/logical_not.rs b/ergotree-ir/src/mir/logical_not.rs
index b78a4b87d..12f970804 100644
--- a/ergotree-ir/src/mir/logical_not.rs
+++ b/ergotree-ir/src/mir/logical_not.rs
@@ -2,6 +2,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -13,16 +14,14 @@ pub struct LogicalNot {
 }
 
 impl LogicalNot {
-    pub(crate) const OP_CODE: OpCode = OpCode::LOGICAL_NOT;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for LogicalNot {
+    const OP_CODE: OpCode = OpCode::LOGICAL_NOT;
 }
 
 impl UnaryOp for LogicalNot {
diff --git a/ergotree-ir/src/mir/long_to_byte_array.rs b/ergotree-ir/src/mir/long_to_byte_array.rs
index 3b1e46b1e..0ddd6c6d9 100644
--- a/ergotree-ir/src/mir/long_to_byte_array.rs
+++ b/ergotree-ir/src/mir/long_to_byte_array.rs
@@ -6,6 +6,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Convert SLong to byte array
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -15,16 +16,14 @@ pub struct LongToByteArray {
 }
 
 impl LongToByteArray {
-    pub(crate) const OP_CODE: OpCode = OpCode::LONG_TO_BYTE_ARRAY;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for LongToByteArray {
+    const OP_CODE: OpCode = OpCode::LONG_TO_BYTE_ARRAY;
 }
 
 impl UnaryOp for LongToByteArray {
diff --git a/ergotree-ir/src/mir/method_call.rs b/ergotree-ir/src/mir/method_call.rs
index e5e52dcb9..2e7a1d178 100644
--- a/ergotree-ir/src/mir/method_call.rs
+++ b/ergotree-ir/src/mir/method_call.rs
@@ -4,6 +4,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /** Represents in ErgoTree an invocation of method of the object `obj` with arguments `args`.
  * The SMethod instances in STypeCompanions may have type STypeIdent in methods types,
@@ -60,8 +61,8 @@ impl MethodCall {
     pub fn tpe(&self) -> SType {
         *self.method.tpe().t_range.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::METHOD_CALL
-    }
+impl HasStaticOpCode for MethodCall {
+    const OP_CODE: OpCode = OpCode::METHOD_CALL;
 }
diff --git a/ergotree-ir/src/mir/negation.rs b/ergotree-ir/src/mir/negation.rs
index 89db293a0..2e57773d8 100644
--- a/ergotree-ir/src/mir/negation.rs
+++ b/ergotree-ir/src/mir/negation.rs
@@ -2,6 +2,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -13,16 +14,14 @@ pub struct Negation {
 }
 
 impl Negation {
-    pub(crate) const OP_CODE: OpCode = OpCode::NEGATION;
-
     /// Type
     pub fn tpe(&self) -> SType {
         self.input.post_eval_tpe()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Negation {
+    const OP_CODE: OpCode = OpCode::NEGATION;
 }
 
 impl UnaryOp for Negation {
diff --git a/ergotree-ir/src/mir/option_get.rs b/ergotree-ir/src/mir/option_get.rs
index 617624eb6..dffa3ad5b 100644
--- a/ergotree-ir/src/mir/option_get.rs
+++ b/ergotree-ir/src/mir/option_get.rs
@@ -2,6 +2,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -13,12 +14,6 @@ pub struct OptionGet {
 }
 
 impl OptionGet {
-    pub(crate) const OP_CODE: OpCode = OpCode::OPTION_GET;
-
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         match self.input.tpe() {
@@ -31,6 +26,10 @@ impl OptionGet {
     }
 }
 
+impl HasStaticOpCode for OptionGet {
+    const OP_CODE: OpCode = OpCode::OPTION_GET;
+}
+
 impl UnaryOp for OptionGet {
     fn input(&self) -> &Expr {
         &self.input
diff --git a/ergotree-ir/src/mir/option_get_or_else.rs b/ergotree-ir/src/mir/option_get_or_else.rs
index 2b4a47017..a1fb0216e 100644
--- a/ergotree-ir/src/mir/option_get_or_else.rs
+++ b/ergotree-ir/src/mir/option_get_or_else.rs
@@ -1,5 +1,6 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -17,8 +18,6 @@ pub struct OptionGetOrElse {
 }
 
 impl OptionGetOrElse {
-    pub(crate) const OP_CODE: OpCode = OpCode::OPTION_GET_OR_ELSE;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, default: Expr) -> Result<Self, InvalidArgumentError> {
         match input.post_eval_tpe() {
@@ -36,10 +35,6 @@ impl OptionGetOrElse {
         }
     }
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         match self.input.tpe() {
@@ -52,6 +47,10 @@ impl OptionGetOrElse {
     }
 }
 
+impl HasStaticOpCode for OptionGetOrElse {
+    const OP_CODE: OpCode = OpCode::OPTION_GET_OR_ELSE;
+}
+
 impl SigmaSerializable for OptionGetOrElse {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)?;
diff --git a/ergotree-ir/src/mir/option_is_defined.rs b/ergotree-ir/src/mir/option_is_defined.rs
index 5aeac6520..db9228347 100644
--- a/ergotree-ir/src/mir/option_is_defined.rs
+++ b/ergotree-ir/src/mir/option_is_defined.rs
@@ -2,6 +2,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
@@ -13,18 +14,16 @@ pub struct OptionIsDefined {
 }
 
 impl OptionIsDefined {
-    pub(crate) const OP_CODE: OpCode = OpCode::OPTION_IS_DEFINED;
-
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
 }
 
+impl HasStaticOpCode for OptionIsDefined {
+    const OP_CODE: OpCode = OpCode::OPTION_IS_DEFINED;
+}
+
 impl UnaryOp for OptionIsDefined {
     fn input(&self) -> &Expr {
         &self.input
diff --git a/ergotree-ir/src/mir/or.rs b/ergotree-ir/src/mir/or.rs
index 2c6289614..bf46788d0 100644
--- a/ergotree-ir/src/mir/or.rs
+++ b/ergotree-ir/src/mir/or.rs
@@ -1,4 +1,5 @@
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -14,16 +15,14 @@ pub struct Or {
 }
 
 impl Or {
-    pub(crate) const OP_CODE: OpCode = OpCode::OR;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SBoolean
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Or {
+    const OP_CODE: OpCode = OpCode::OR;
 }
 
 impl SigmaSerializable for Or {
diff --git a/ergotree-ir/src/mir/property_call.rs b/ergotree-ir/src/mir/property_call.rs
index fa2383696..f0afc0c50 100644
--- a/ergotree-ir/src/mir/property_call.rs
+++ b/ergotree-ir/src/mir/property_call.rs
@@ -4,6 +4,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Invocation of object's property
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -47,8 +48,8 @@ impl PropertyCall {
     pub fn tpe(&self) -> SType {
         *self.method.tpe().t_range.clone()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::PROPERTY_CALL
-    }
+impl HasStaticOpCode for PropertyCall {
+    const OP_CODE: OpCode = OpCode::PROPERTY_CALL;
 }
diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs
index e1610a996..a8a010a50 100644
--- a/ergotree-ir/src/mir/select_field.rs
+++ b/ergotree-ir/src/mir/select_field.rs
@@ -10,6 +10,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Tuple field access index (1..=255)
 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
@@ -87,10 +88,10 @@ impl SelectField {
             ))),
         }
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::SELECT_FIELD
-    }
+impl HasStaticOpCode for SelectField {
+    const OP_CODE: OpCode = OpCode::SELECT_FIELD;
 }
 
 impl SelectField {
diff --git a/ergotree-ir/src/mir/sigma_and.rs b/ergotree-ir/src/mir/sigma_and.rs
index 20e52baec..89db0b0ee 100644
--- a/ergotree-ir/src/mir/sigma_and.rs
+++ b/ergotree-ir/src/mir/sigma_and.rs
@@ -8,6 +8,7 @@ use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 
 /// AND conjunction for sigma propositions
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -17,16 +18,14 @@ pub struct SigmaAnd {
 }
 
 impl SigmaAnd {
-    pub(crate) const OP_CODE: OpCode = OpCode::SIGMA_AND;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for SigmaAnd {
+    const OP_CODE: OpCode = OpCode::SIGMA_AND;
 }
 
 impl SigmaSerializable for SigmaAnd {
diff --git a/ergotree-ir/src/mir/sigma_or.rs b/ergotree-ir/src/mir/sigma_or.rs
index dbd666aff..1a4e56d72 100644
--- a/ergotree-ir/src/mir/sigma_or.rs
+++ b/ergotree-ir/src/mir/sigma_or.rs
@@ -8,6 +8,7 @@ use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use crate::has_opcode::HasStaticOpCode;
 
 /// OR conjunction for sigma propositions
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -17,16 +18,14 @@ pub struct SigmaOr {
 }
 
 impl SigmaOr {
-    pub(crate) const OP_CODE: OpCode = OpCode::SIGMA_OR;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SSigmaProp
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for SigmaOr {
+    const OP_CODE: OpCode = OpCode::SIGMA_OR;
 }
 
 impl SigmaSerializable for SigmaOr {
diff --git a/ergotree-ir/src/mir/sigma_prop_bytes.rs b/ergotree-ir/src/mir/sigma_prop_bytes.rs
index 32c92f875..082670848 100644
--- a/ergotree-ir/src/mir/sigma_prop_bytes.rs
+++ b/ergotree-ir/src/mir/sigma_prop_bytes.rs
@@ -5,6 +5,7 @@ use super::expr::Expr;
 use super::expr::InvalidArgumentError;
 use super::unary_op::UnaryOp;
 use super::unary_op::UnaryOpTryBuild;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Extract serialized bytes of a SigmaProp value
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,16 +15,14 @@ pub struct SigmaPropBytes {
 }
 
 impl SigmaPropBytes {
-    pub(crate) const OP_CODE: OpCode = OpCode::SIGMA_PROP_BYTES;
-
     /// Type
     pub fn tpe(&self) -> SType {
         SType::SColl(SType::SByte.into())
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for SigmaPropBytes {
+    const OP_CODE: OpCode = OpCode::SIGMA_PROP_BYTES;
 }
 
 impl UnaryOp for SigmaPropBytes {
diff --git a/ergotree-ir/src/mir/tuple.rs b/ergotree-ir/src/mir/tuple.rs
index 819c75d25..7976dae34 100644
--- a/ergotree-ir/src/mir/tuple.rs
+++ b/ergotree-ir/src/mir/tuple.rs
@@ -11,6 +11,7 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
+use crate::has_opcode::HasStaticOpCode;
 
 /// Tuple of elements
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -20,8 +21,6 @@ pub struct Tuple {
 }
 
 impl Tuple {
-    pub(crate) const OP_CODE: OpCode = OpCode::TUPLE;
-
     /// Create new object
     pub fn new(items: Vec<Expr>) -> Result<Self, InvalidArgumentError> {
         Ok(Tuple {
@@ -35,10 +34,10 @@ impl Tuple {
             items: self.items.mapped_ref(|it| it.tpe()),
         })
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
+impl HasStaticOpCode for Tuple {
+    const OP_CODE: OpCode = OpCode::TUPLE;
 }
 
 impl SigmaSerializable for Tuple {
diff --git a/ergotree-ir/src/mir/upcast.rs b/ergotree-ir/src/mir/upcast.rs
index 1aacedc3a..ae4af15c7 100644
--- a/ergotree-ir/src/mir/upcast.rs
+++ b/ergotree-ir/src/mir/upcast.rs
@@ -9,6 +9,7 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
+use crate::has_opcode::HasStaticOpCode;
 use std::io::Error;
 
 /// Numerical upcast
@@ -21,8 +22,6 @@ pub struct Upcast {
 }
 
 impl Upcast {
-    pub(crate) const OP_CODE: OpCode = OpCode::UPCAST;
-
     /// Create new object, returns an error if any of the requirements failed
     pub fn new(input: Expr, target_tpe: SType) -> Result<Self, InvalidArgumentError> {
         if !target_tpe.is_numeric() {
@@ -45,16 +44,16 @@ impl Upcast {
         }
     }
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        Self::OP_CODE
-    }
-
     /// Type
     pub fn tpe(&self) -> SType {
         self.tpe.clone()
     }
 }
 
+impl HasStaticOpCode for Upcast {
+    const OP_CODE: OpCode = OpCode::UPCAST;
+}
+
 impl SigmaSerializable for Upcast {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), Error> {
         self.input.sigma_serialize(w)?;
diff --git a/ergotree-ir/src/mir/val_def.rs b/ergotree-ir/src/mir/val_def.rs
index 4d13c7077..0a6a46fb2 100644
--- a/ergotree-ir/src/mir/val_def.rs
+++ b/ergotree-ir/src/mir/val_def.rs
@@ -12,6 +12,7 @@ use super::expr::Expr;
 extern crate derive_more;
 use derive_more::From;
 
+use crate::has_opcode::HasStaticOpCode;
 #[cfg(feature = "arbitrary")]
 use proptest_derive::Arbitrary;
 
@@ -49,10 +50,10 @@ impl ValDef {
     pub fn tpe(&self) -> SType {
         self.rhs.tpe()
     }
+}
 
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::VAL_DEF
-    }
+impl HasStaticOpCode for ValDef {
+    const OP_CODE: OpCode = OpCode::VAL_DEF;
 }
 
 impl SigmaSerializable for ValDef {
diff --git a/ergotree-ir/src/mir/val_use.rs b/ergotree-ir/src/mir/val_use.rs
index ced312411..e4b896e58 100644
--- a/ergotree-ir/src/mir/val_use.rs
+++ b/ergotree-ir/src/mir/val_use.rs
@@ -1,6 +1,7 @@
 use std::io;
 
 use super::val_def::ValId;
+use crate::has_opcode::HasStaticOpCode;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -17,10 +18,8 @@ pub struct ValUse {
     pub tpe: SType,
 }
 
-impl ValUse {
-    pub(crate) fn op_code(&self) -> OpCode {
-        OpCode::VAL_USE
-    }
+impl HasStaticOpCode for ValUse {
+    const OP_CODE: OpCode = OpCode::VAL_USE;
 }
 
 impl SigmaSerializable for ValUse {
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 51c306772..8a4a18f94 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -1,6 +1,8 @@
 use super::bin_op::bin_op_sigma_parse;
 use super::bin_op::bin_op_sigma_serialize;
 use super::{op_code::OpCode, sigma_byte_writer::SigmaByteWrite};
+use crate::has_opcode::HasOpCode;
+use crate::has_opcode::HasStaticOpCode;
 use crate::mir::and::And;
 use crate::mir::apply::Apply;
 use crate::mir::atleast::Atleast;
diff --git a/ergotree-ir/src/serialization/sigmaboolean.rs b/ergotree-ir/src/serialization/sigmaboolean.rs
index ecbc50d03..673cd904f 100644
--- a/ergotree-ir/src/serialization/sigmaboolean.rs
+++ b/ergotree-ir/src/serialization/sigmaboolean.rs
@@ -1,4 +1,5 @@
 use super::{op_code::OpCode, sigma_byte_writer::SigmaByteWrite};
+use crate::has_opcode::HasOpCode;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index 3aa2ab8a0..ae4f8b342 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -2,6 +2,7 @@
 
 use super::dlog_group::EcPoint;
 use crate::ergo_tree::ErgoTree;
+use crate::has_opcode::HasOpCode;
 use crate::mir::constant::Constant;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::SigmaSerializable;
@@ -70,9 +71,9 @@ pub enum SigmaBoolean {
     Cand(Vec<SigmaBoolean>),
 }
 
-impl SigmaBoolean {
+impl HasOpCode for SigmaBoolean {
     /// get OpCode for serialization
-    pub(crate) fn op_code(&self) -> OpCode {
+    fn op_code(&self) -> OpCode {
         match self {
             SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(_)) => {
                 OpCode::PROVE_DLOG


Test patch: