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

Issue:
Extract ErgoTree interpreter into separate crate
This will allow to use ErgoScript compiler in the interpreter tests.q


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index c9c71342e..ec4753b2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,11 @@
 [workspace]
 members = [
+    "test-util",
     "sigma-ser",
     "sigma-util",
     "ergoscript-compiler",
     "ergotree-ir",
+    "ergotree-interpreter",
     "ergo-lib",
     "bindings/ergo-lib-wasm",
     "bindings/ergo-lib-c-core",
diff --git a/bindings/ergo-lib-wasm/src/context_extension.rs b/bindings/ergo-lib-wasm/src/context_extension.rs
index 123fe7f0b..b96004c16 100644
--- a/bindings/ergo-lib-wasm/src/context_extension.rs
+++ b/bindings/ergo-lib-wasm/src/context_extension.rs
@@ -9,26 +9,28 @@ use derive_more::{From, Into};
 /// Proof of correctness of tx spending
 #[wasm_bindgen]
 #[derive(PartialEq, Debug, Clone, From, Into)]
-pub struct ContextExtension(ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension);
+pub struct ContextExtension(
+    ergo_lib::ergotree_interpreter::sigma_protocol::prover::ContextExtension,
+);
 
 #[wasm_bindgen]
 impl ContextExtension {
     /// Returns the number of elements in the collection
     pub fn len(&self) -> usize {
-        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+        let wrapped: ergo_lib::ergotree_interpreter::sigma_protocol::prover::ContextExtension =
             self.0.clone();
         wrapped.values.len()
     }
     /// get from map or fail if key is missing
     pub fn get(&self, key: u8) -> Constant {
-        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+        let wrapped: ergo_lib::ergotree_interpreter::sigma_protocol::prover::ContextExtension =
             self.0.clone();
         wrapped.values.get(&key).unwrap().clone().into()
     }
 
     /// Returns all keys in the map
     pub fn keys(&self) -> Vec<u8> {
-        let wrapped: ergo_lib::ergotree_ir::sigma_protocol::prover::ContextExtension =
+        let wrapped: ergo_lib::ergotree_interpreter::sigma_protocol::prover::ContextExtension =
             self.0.clone();
         wrapped.values.keys().cloned().collect()
     }
diff --git a/bindings/ergo-lib-wasm/src/secret_key.rs b/bindings/ergo-lib-wasm/src/secret_key.rs
index fdd06286c..7f3d8a685 100644
--- a/bindings/ergo-lib-wasm/src/secret_key.rs
+++ b/bindings/ergo-lib-wasm/src/secret_key.rs
@@ -1,7 +1,7 @@
 //! Secret key
 use std::convert::TryInto;
 
-use ergo_lib::ergotree_ir::sigma_protocol::private_input::DlogProverInput;
+use ergo_lib::ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
 use ergo_lib::wallet;
 use wasm_bindgen::prelude::*;
 
diff --git a/ergo-lib/Cargo.toml b/ergo-lib/Cargo.toml
index a9e783ccf..bcd0eb175 100644
--- a/ergo-lib/Cargo.toml
+++ b/ergo-lib/Cargo.toml
@@ -17,7 +17,8 @@ crate-type = ["cdylib", "rlib"]
 sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
 sigma-util = { version = "^0.1.0", path = "../sigma-util" }
 ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir" }
-ergoscript-compiler = { version = "^0.1.0", path = "../ergoscript-compiler" }
+ergotree-interpreter = { version = "^0.5.1", path = "../ergotree-interpreter" }
+ergoscript-compiler = { version = "^0.1.0", path = "../ergoscript-compiler" , optional = true}
 indexmap = "1.3.2"
 base16 = "0.2.1"
 k256 = {version = "0.4.2", features = ["rand", "zeroize"]}
@@ -25,7 +26,6 @@ elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 serde = { version = "1.0", features = ["derive"], optional = true }
 serde_json = "1.0"
 thiserror = "1"
-# bs58 = "0.4.0"
 num-bigint = "0.3.0"
 rand = "0.7"
 lazy_static = "1.4"
@@ -34,14 +34,17 @@ impl-trait-for-tuples = "0.2.0"
 num = "0.3.1"
 
 [features]
-default = ["json"]
+default = ["json", "compiler"]
 json = ["serde"]
+compiler = ["ergoscript-compiler"]
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.10"
 proptest-derive = "0.3.0"
 rand = "0.7.3"
 ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir", features = ["arbitrary"] }
+ergotree-interpreter = { version = "^0.5.1", path = "../ergotree-interpreter", features = ["arbitrary"] }
+test-util = { version = "0.1.0", path = "../test-util" }
 
 [dev-dependencies.proptest]
 # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
diff --git a/ergo-lib/src/chain/contract.rs b/ergo-lib/src/chain/contract.rs
index 55dd4cfcf..1b34d531d 100644
--- a/ergo-lib/src/chain/contract.rs
+++ b/ergo-lib/src/chain/contract.rs
@@ -1,8 +1,5 @@
 //! Ergo contract
 
-use ergoscript_compiler::compiler::compile;
-use ergoscript_compiler::compiler::CompileError;
-use ergoscript_compiler::script_env::ScriptEnv;
 use ergotree_ir::address::Address;
 use ergotree_ir::ergo_tree::ErgoTree;
 use ergotree_ir::serialization::SerializationError;
@@ -30,8 +27,12 @@ impl Contract {
     }
 
     /// Compiles a contract from ErgoScript source code
-    pub fn compile(source: &str, env: ScriptEnv) -> Result<Contract, CompileError> {
-        let ergo_tree = compile(source, env)?;
+    #[cfg(feature = "compiler")]
+    pub fn compile(
+        source: &str,
+        env: ergoscript_compiler::script_env::ScriptEnv,
+    ) -> Result<Contract, ergoscript_compiler::compiler::CompileError> {
+        let ergo_tree = ergoscript_compiler::compiler::compile(source, env)?;
         Ok(Contract { ergo_tree })
     }
 }
@@ -40,9 +41,11 @@ impl Contract {
 mod tests {
     use super::*;
 
+    #[cfg(feature = "compiler")]
     #[test]
     fn compile() {
-        let contract = Contract::compile("HEIGHT", ScriptEnv::new()).unwrap();
+        let contract =
+            Contract::compile("HEIGHT", ergoscript_compiler::script_env::ScriptEnv::new()).unwrap();
         dbg!(&contract);
     }
 }
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index e13719cd9..4b50ad026 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -520,10 +520,10 @@ mod tests {
 
     use super::box_value::tests::ArbBoxValueRange;
     use super::*;
-    use crate::test_util::force_any_val;
     use ergotree_ir::mir::constant::TryExtractInto;
     use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
+    use test_util::force_any_val;
 
     impl Arbitrary for ErgoBoxCandidate {
         type Parameters = ArbBoxValueRange;
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index 5a81d845c..eebb5080d 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -240,11 +240,11 @@ impl ErgoBoxCandidateBuilder {
 #[cfg(test)]
 mod tests {
 
+    use test_util::force_any_val;
     use NonMandatoryRegisterId::*;
 
     use crate::chain::token::TokenId;
     use crate::chain::Base16Str;
-    use crate::test_util::force_any_val;
 
     use super::*;
 
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index 6f22f1c33..2daf0d8f2 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -1,8 +1,8 @@
 //! JSON serialization
 
+use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
+use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use ergotree_ir::mir::constant::Constant;
-use ergotree_ir::sigma_protocol::prover::ContextExtension;
-use ergotree_ir::sigma_protocol::prover::ProofBytes;
 use indexmap::IndexMap;
 use serde::Serializer;
 use serde::{Deserialize, Serialize};
diff --git a/ergo-lib/src/chain/transaction/input.rs b/ergo-lib/src/chain/transaction/input.rs
index c8a944c7b..00dd868e5 100644
--- a/ergo-lib/src/chain/transaction/input.rs
+++ b/ergo-lib/src/chain/transaction/input.rs
@@ -5,12 +5,12 @@ pub mod prover_result;
 use std::io;
 
 use crate::chain::ergo_box::{BoxId, ErgoBoxId};
+use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
+use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SerializationError;
 use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::sigma_protocol::prover::ContextExtension;
-use ergotree_ir::sigma_protocol::prover::ProofBytes;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
diff --git a/ergo-lib/src/chain/transaction/input/prover_result.rs b/ergo-lib/src/chain/transaction/input/prover_result.rs
index fbeac82fc..650c63db3 100644
--- a/ergo-lib/src/chain/transaction/input/prover_result.rs
+++ b/ergo-lib/src/chain/transaction/input/prover_result.rs
@@ -2,12 +2,12 @@
 
 use std::io;
 
+use ergotree_interpreter::sigma_protocol::prover::ContextExtension;
+use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
 use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
 use ergotree_ir::serialization::SerializationError;
 use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::sigma_protocol::prover::ContextExtension;
-use ergotree_ir::sigma_protocol::prover::ProofBytes;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
@@ -31,8 +31,8 @@ pub struct ProverResult {
     pub extension: ContextExtension,
 }
 
-impl From<ergotree_ir::sigma_protocol::prover::ProverResult> for ProverResult {
-    fn from(ir: ergotree_ir::sigma_protocol::prover::ProverResult) -> Self {
+impl From<ergotree_interpreter::sigma_protocol::prover::ProverResult> for ProverResult {
+    fn from(ir: ergotree_interpreter::sigma_protocol::prover::ProverResult) -> Self {
         ProverResult {
             proof: ir.proof,
             extension: ir.extension,
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index 408533536..7643fe0e4 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -15,8 +15,8 @@ use crate::chain::transaction::ErgoBox;
 use crate::chain::transaction::TransactionFromJsonError;
 #[cfg(feature = "json")]
 use core::convert::TryFrom;
+use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::sigma_protocol::prover::ProofBytes;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 
@@ -72,7 +72,7 @@ impl UnsignedTransaction {
         self.tx_id.clone()
     }
 
-    /// message to be signed by the [`ergotree_ir::sigma_protocol::prover::Prover`] (serialized tx)
+    /// message to be signed by the [`ergotree_interpreter::sigma_protocol::prover::Prover`] (serialized tx)
     pub fn bytes_to_sign(&self) -> Vec<u8> {
         let empty_proofs_input = self
             .inputs
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index 8bb435140..cb4311536 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -19,9 +19,8 @@ pub mod chain;
 pub mod constants;
 pub mod wallet;
 
-#[cfg(test)]
-pub mod test_util;
-
 /// Re-exported types from dependencies
+#[cfg(feature = "compiler")]
 pub extern crate ergoscript_compiler;
+pub extern crate ergotree_interpreter;
 pub extern crate ergotree_ir;
diff --git a/ergo-lib/src/wallet.rs b/ergo-lib/src/wallet.rs
index 28373f3ac..393d5c86e 100644
--- a/ergo-lib/src/wallet.rs
+++ b/ergo-lib/src/wallet.rs
@@ -5,9 +5,9 @@ pub mod secret_key;
 pub mod signing;
 pub mod tx_builder;
 
-use ergotree_ir::sigma_protocol::private_input::PrivateInput;
-use ergotree_ir::sigma_protocol::prover::Prover;
-use ergotree_ir::sigma_protocol::prover::TestProver;
+use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
+use ergotree_interpreter::sigma_protocol::prover::Prover;
+use ergotree_interpreter::sigma_protocol::prover::TestProver;
 use secret_key::SecretKey;
 use signing::{sign_transaction, TxSigningError};
 use thiserror::Error;
diff --git a/ergo-lib/src/wallet/secret_key.rs b/ergo-lib/src/wallet/secret_key.rs
index 94133964f..58ba89fdd 100644
--- a/ergo-lib/src/wallet/secret_key.rs
+++ b/ergo-lib/src/wallet/secret_key.rs
@@ -1,7 +1,7 @@
 //! Secret types
+use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
+use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
 use ergotree_ir::address::Address;
-use ergotree_ir::sigma_protocol::private_input::DlogProverInput;
-use ergotree_ir::sigma_protocol::private_input::PrivateInput;
 
 /// Types of secrets
 #[derive(PartialEq, Debug, Clone)]
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index a73a98c20..d87203970 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -11,14 +11,14 @@ use crate::chain::{
     transaction::{unsigned::UnsignedTransaction, Transaction},
 };
 
-use ergotree_ir::eval::context::Context;
-use ergotree_ir::eval::env::Env;
+use ergotree_interpreter::eval::context::Context;
+use ergotree_interpreter::eval::env::Env;
+use ergotree_interpreter::sigma_protocol::prover::Prover;
+use ergotree_interpreter::sigma_protocol::prover::ProverError;
 use ergotree_ir::ir_ergo_box::IrBoxId;
 use ergotree_ir::ir_ergo_box::IrErgoBox;
 use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
 use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
-use ergotree_ir::sigma_protocol::prover::Prover;
-use ergotree_ir::sigma_protocol::prover::ProverError;
 use thiserror::Error;
 
 /// Errors on transaction signing
@@ -46,7 +46,7 @@ pub struct TransactionContext {
     pub data_boxes: Vec<ErgoBox>,
 }
 
-/// Holding all ErgoBox needed for interpreter [`ergotree_ir::eval::context::Context`]
+/// Holding all ErgoBox needed for interpreter [`ergotree_interpreter::eval::context::Context`]
 #[derive(Debug)]
 pub struct ErgoBoxArena(HashMap<BoxId, ErgoBox>);
 
@@ -156,15 +156,17 @@ pub fn sign_transaction(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use ergotree_ir::sigma_protocol::private_input::DlogProverInput;
-    use ergotree_ir::sigma_protocol::private_input::PrivateInput;
-    use ergotree_ir::sigma_protocol::prover::TestProver;
-    use ergotree_ir::sigma_protocol::verifier::TestVerifier;
-    use ergotree_ir::sigma_protocol::verifier::Verifier;
-    use ergotree_ir::sigma_protocol::verifier::VerifierError;
-    use ergotree_ir::test_util::force_any_val;
+    use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
+    use ergotree_interpreter::sigma_protocol::private_input::PrivateInput;
+    use ergotree_interpreter::sigma_protocol::prover::TestProver;
+    use ergotree_interpreter::sigma_protocol::verifier::TestVerifier;
+    use ergotree_interpreter::sigma_protocol::verifier::Verifier;
+    use ergotree_interpreter::sigma_protocol::verifier::VerifierError;
+    use ergotree_ir::address::AddressEncoder;
+    use ergotree_ir::address::NetworkPrefix;
     use proptest::collection::vec;
     use proptest::prelude::*;
+    use test_util::force_any_val;
 
     use crate::chain::{
         ergo_box::{box_builder::ErgoBoxCandidateBuilder, BoxValue, NonMandatoryRegisters},
@@ -228,4 +230,103 @@ mod tests {
             prop_assert!(verify_tx_proofs(&signed_tx, &boxes_to_spend).unwrap());
         }
     }
+
+    #[test]
+    fn test_proof_from_mainnet() {
+        use crate::chain::transaction::Transaction;
+
+        let tx_json = r#"
+         {
+      "id": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
+      "inputs": [
+        {
+          "boxId": "f353ae1b2027e40ea318e7a2673ea4bbaa281b7acee518a0994c5cbdefb05f55",
+          "spendingProof": {
+            "proofBytes":"",
+            "extension": {}
+          }
+        },
+        {
+          "boxId": "56111b039b86f71004b768d2e8b4579f1d79e28e7a617fd5add57a5239498c26",
+          "spendingProof": {
+            "proofBytes": "6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48",
+            "extension": {}
+          }
+        }
+      ],
+      "dataInputs": [
+        {
+          "boxId": "e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece"
+        }
+      ],
+      "outputs": [
+        {
+          "boxId": "55be517150fcb7f0f1661ad3ab30f1ac62084b83ad6aa772579bc06cbb52832e",
+          "value": 1000000,
+          "ergoTree": "100604000400050004000e20b662db51cf2dc39f110a021c2a31c74f0a1a18ffffbf73e8a051a7b8c0f09ebc0e2079974b2314c531e62776e6bc4babff35b37b178cebf0976fc0f416ff34ddbc4fd803d601b2a5730000d602e4c6a70407d603b2db6501fe730100ea02d1ededededed93e4c672010407720293e4c67201050ec5720391e4c672010605730293c27201c2a793db63087201db6308a7ed938cb2db6308720373030001730493cbc272037305cd7202",
+          "assets": [
+            {
+              "tokenId": "12caaacb51c89646fac9a3786eb98d0113bd57d68223ccc11754a4f67281daed",
+              "amount": 1
+            }
+          ],
+          "creationHeight": 299218,
+          "additionalRegisters": {
+            "R4": "070327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
+            "R5": "0e20e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece",
+            "R6": "05feaff5de0f"
+          },
+          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
+          "index": 0
+        },
+        {
+          "boxId": "fa4a484c855d32a60987a4ddcf1c506aa6bab1c4cb0293c2d5ff35fcd11f2c7b",
+          "value": 1000000,
+          "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
+          "assets": [],
+          "creationHeight": 299218,
+          "additionalRegisters": {},
+          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
+          "index": 1
+        },
+        {
+          "boxId": "3dee27d0dfb193fd6a263cf2b5b58cab99cb640d1443cd1ce63d909ad3a54197",
+          "value": 44516500000,
+          "ergoTree": "0008cd0327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
+          "assets": [],
+          "creationHeight": 299218,
+          "additionalRegisters": {},
+          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
+          "index": 2
+        }
+      ],
+      "size": 673
+    }
+        "#;
+
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let decoded_addr = encoder
+            .parse_address_from_str("9gmNsqrqdSppLUBqg2UzREmmivgqh1r3jmNcLAc53hk3YCvAGWE")
+            .unwrap();
+
+        let ergo_tree = decoded_addr.script().unwrap();
+
+        // let spending_proof_input1 = Base16DecodedBytes::try_from("6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48".to_string()).unwrap();
+        let tx: Transaction = serde_json::from_str(tx_json).unwrap();
+        let tx_id_str: String = tx.id().into();
+        assert_eq!(
+            "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
+            tx_id_str
+        );
+        let message = tx.bytes_to_sign();
+        let verifier = TestVerifier;
+        let ver_res = verifier.verify(
+            &ergo_tree,
+            &Env::empty(),
+            Rc::new(force_any_val::<Context>()),
+            &tx.inputs.get(1).unwrap().spending_proof.proof,
+            message.as_slice(),
+        );
+        assert_eq!(ver_res.unwrap().result, true);
+    }
 }
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index 88abc81cb..ea60e47b3 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -2,11 +2,11 @@
 
 use std::collections::HashSet;
 
+use ergotree_interpreter::sigma_protocol;
+use ergotree_interpreter::sigma_protocol::prover::ProofBytes;
 use ergotree_ir::address::{Address, AddressEncoder, NetworkPrefix};
 use ergotree_ir::serialization::SerializationError;
 use ergotree_ir::serialization::SigmaSerializable;
-use ergotree_ir::sigma_protocol;
-use ergotree_ir::sigma_protocol::prover::ProofBytes;
 use thiserror::Error;
 
 use crate::chain::contract::Contract;
@@ -285,13 +285,14 @@ mod tests {
 
     use ergotree_ir::ergo_tree::ErgoTree;
     use proptest::{collection::vec, prelude::*};
+    use test_util::force_any_val;
+    use test_util::force_any_val_with;
 
     use crate::chain::{
         ergo_box::{checked_sum, ErgoBox, NonMandatoryRegisters},
         token::{tests::ArbTokenIdParam, Token, TokenAmount, TokenId},
         transaction::TxId,
     };
-    use crate::test_util::{force_any_val, force_any_val_with};
     use crate::wallet::box_selector::{BoxSelector, SimpleBoxSelector};
 
     use super::*;
diff --git a/ergoscript-compiler/src/compiler.rs b/ergoscript-compiler/src/compiler.rs
index a29952556..ee0661561 100644
--- a/ergoscript-compiler/src/compiler.rs
+++ b/ergoscript-compiler/src/compiler.rs
@@ -14,8 +14,8 @@ use crate::type_infer::TypeInferenceError;
 extern crate derive_more;
 use derive_more::From;
 use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::type_check::TypeCheckError;
 use mir::lower::MirLoweringError;
-use mir::type_check::TypeCheckError;
 
 /// Compilation errors
 #[derive(Debug, PartialEq, From)]
@@ -50,8 +50,11 @@ impl CompileError {
     }
 }
 
-/// Compiles given source code to [`ErgoTree`], or returns an error
-pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
+/// Compiles given source code to [`ergotree_ir::mir::expr::Expr`], or returns an error
+pub fn compile_expr(
+    source: &str,
+    env: ScriptEnv,
+) -> Result<ergotree_ir::mir::expr::Expr, CompileError> {
     let parse = super::parser::parse(&source);
     dbg!(parse.debug_tree());
     if !parse.errors.is_empty() {
@@ -67,8 +70,14 @@ pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
     let typed = assign_type(bind)?;
     dbg!(typed.debug_tree());
     let mir = mir::lower::lower(typed)?;
-    let res = mir::type_check::type_check(mir)?;
-    Ok(res.into())
+    let res = ergotree_ir::type_check::type_check(mir)?;
+    Ok(res)
+}
+
+/// Compiles given source code to [`ErgoTree`], or returns an error
+pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
+    let expr = compile_expr(source, env)?;
+    Ok(expr.into())
 }
 
 #[cfg(test)]
diff --git a/ergoscript-compiler/src/mir.rs b/ergoscript-compiler/src/mir.rs
index 2ff8bd46c..745db11d3 100644
--- a/ergoscript-compiler/src/mir.rs
+++ b/ergoscript-compiler/src/mir.rs
@@ -1,2 +1,1 @@
 pub mod lower;
-pub mod type_check;
diff --git a/ergotree-interpreter/Cargo.toml b/ergotree-interpreter/Cargo.toml
new file mode 100644
index 000000000..68e78e454
--- /dev/null
+++ b/ergotree-interpreter/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+name = "ergotree-interpreter"
+version = "0.5.1"
+license = "CC0-1.0"
+authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
+edition = "2018"
+description = "ErgoTree interpreter"
+repository = "https://github.com/ergoplatform/sigma-rust"
+exclude = [
+    "proptest-regressions/*"
+]
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
+sigma-util = { version = "^0.1.0", path = "../sigma-util" }
+ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir" }
+indexmap = "1.3.2"
+k256 = { version = "0.4.2", features = ["rand", "zeroize"] }
+blake2 = "0.9"
+elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
+thiserror = "1"
+num-bigint = "0.3.0"
+rand = "0.7"
+lazy_static = "1.4"
+derive_more = "0.99"
+impl-trait-for-tuples = "0.2.0"
+num = "0.3.1"
+proptest = {version = "1.0.0", optional = true }
+proptest-derive = {version = "0.3.0", optional = true }
+bs58 = "0.4.0"
+base16 = "0.2.1"
+
+[features]
+default = []
+arbitrary = ["proptest", "proptest-derive"]
+
+[dev-dependencies]
+ergotree-ir = { version = "^0.5.1", path = "../ergotree-ir", features = ["arbitrary"] }
+ergoscript-compiler = { version = "^0.1.0", path = "../ergoscript-compiler" }
+proptest = "1.0.0"
+test-util = { version = "0.1.0", path = "../test-util" }
+
diff --git a/ergotree-interpreter/src/contracts.rs b/ergotree-interpreter/src/contracts.rs
new file mode 100644
index 000000000..e50c234fa
--- /dev/null
+++ b/ergotree-interpreter/src/contracts.rs
@@ -0,0 +1,169 @@
+#[cfg(test)]
+mod tests {
+    use std::convert::TryInto;
+    use std::rc::Rc;
+
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::address::AddressEncoder;
+    use ergotree_ir::address::NetworkPrefix;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
+
+    #[test]
+    fn simplified_age_usd_bank_contract() {
+        // simplified version of
+        // https://github.com/Emurgo/age-usd/tree/main/ageusd-smart-contracts/v0.4
+        /*
+
+          val rcDefaultPrice = 1000000L
+
+          val minStorageRent = 10000000L
+
+          val feePercent = 1
+
+          val HEIGHT = 377771
+
+          val coolingOffHeight: Int = 377770
+          val INF = 1000000000L
+
+          val longMax = 9223372036854775807L
+
+          val minReserveRatioPercent = 400L // percent
+          val defaultMaxReserveRatioPercent = 800L // percent
+
+            // val dataInput = CONTEXT.dataInputs(0)
+            // val validDataInput = dataInput.tokens(0)._1 == oraclePoolNFT
+            val validDataInput = true
+
+            // val bankBoxIn = SELF
+            // val bankBoxOut = OUTPUTS(0)
+
+            // val rateBox = dataInput
+            // val receiptBox = OUTPUTS(1)
+
+            // val rate = rateBox.R4[Long].get / 100
+            val rate = 100000000 / 100
+
+            // val scCircIn = bankBoxIn.R4[Long].get
+            val scCircIn = 100L
+            // val rcCircIn = bankBoxIn.R5[Long].get
+            val rcCircIn = 100L
+            // val bcReserveIn = bankBoxIn.value
+            val bcReserveIn = 100000000L
+
+            // val scTokensIn = bankBoxIn.tokens(0)._2
+            val scTokensIn = 100
+            // val rcTokensIn = bankBoxIn.tokens(1)._2
+            val rcTokensIn = 100
+
+            // val scCircOut = bankBoxOut.R4[Long].get
+            val scCircOut = 100
+            // val rcCircOut = bankBoxOut.R5[Long].get
+            val rcCircOut = 101
+
+            // val scTokensOut = bankBoxOut.tokens(0)._2
+            val scTokensOut = 100
+            // val rcTokensOut = bankBoxOut.tokens(1)._2
+            val rcTokensOut = 99
+
+            val totalScIn = scTokensIn + scCircIn
+            val totalScOut = scTokensOut + scCircOut
+
+            val totalRcIn = rcTokensIn + rcCircIn
+            val totalRcOut = rcTokensOut + rcCircOut
+
+            val rcExchange = rcTokensIn != rcTokensOut
+            val scExchange = scTokensIn != scTokensOut
+
+            val rcExchangeXorScExchange = (rcExchange || scExchange) && !(rcExchange && scExchange)
+
+            // val circDelta = receiptBox.R4[Long].get
+            val circDelta = 1L
+            // val bcReserveDelta = receiptBox.R5[Long].get
+            val bcReserveDelta = 1010000L
+
+            // val bcReserveOut = bankBoxOut.value
+            val bcReserveOut = 100000000L + 1010000L
+
+            val rcCircDelta = if (rcExchange) circDelta else 0L
+            val scCircDelta = if (rcExchange) 0L else circDelta
+
+            val validDeltas = (scCircIn + scCircDelta == scCircOut) &&
+                              (rcCircIn + rcCircDelta == rcCircOut) &&
+                              (bcReserveIn + bcReserveDelta == bcReserveOut)
+
+            val coinsConserved = totalRcIn == totalRcOut && totalScIn == totalScOut
+
+            // val tokenIdsConserved = bankBoxOut.tokens(0)._1 == bankBoxIn.tokens(0)._1 && // also ensures that at least one token exists
+            //                         bankBoxOut.tokens(1)._1 == bankBoxIn.tokens(1)._1 && // also ensures that at least one token exists
+            //                         bankBoxOut.tokens(2)._1 == bankBoxIn.tokens(2)._1    // also ensures that at least one token exists
+
+            val tokenIdsConserved = true
+
+            // val mandatoryRateConditions = rateBox.tokens(0)._1 == oraclePoolNFT
+            val mandatoryRateConditions = true
+            val mandatoryBankConditions = //bankBoxOut.value >= $minStorageRent &&
+                                          rcExchangeXorScExchange &&
+                                          coinsConserved &&
+                                          validDeltas &&
+                                          tokenIdsConserved
+
+            // exchange equations
+            val bcReserveNeededOut = scCircOut * rate
+            val bcReserveNeededIn = scCircIn * rate
+            val liabilitiesIn = max(min(bcReserveIn, bcReserveNeededIn), 0)
+
+
+            val maxReserveRatioPercent = if (HEIGHT > coolingOffHeight) defaultMaxReserveRatioPercent else INF
+
+            val reserveRatioPercentOut =
+                if (bcReserveNeededOut == 0) maxReserveRatioPercent else bcReserveOut * 100 / bcReserveNeededOut
+
+            val validReserveRatio = if (scExchange) {
+              if (scCircDelta > 0) {
+                reserveRatioPercentOut >= minReserveRatioPercent
+              } else true
+            } else {
+              if (rcCircDelta > 0) {
+                reserveRatioPercentOut <= maxReserveRatioPercent
+              } else {
+                reserveRatioPercentOut >= minReserveRatioPercent
+              }
+            }
+
+            val brDeltaExpected = if (scExchange) { // sc
+              val liableRate = if (scCircIn == 0) longMax else liabilitiesIn / scCircIn
+              val scNominalPrice = min(rate, liableRate)
+              scNominalPrice * scCircDelta
+            } else { // rc
+              val equityIn = bcReserveIn - liabilitiesIn
+              val equityRate = if (rcCircIn == 0) rcDefaultPrice else equityIn / rcCircIn
+              val rcNominalPrice = if (equityIn == 0) rcDefaultPrice else equityRate
+              rcNominalPrice * rcCircDelta
+            }
+
+            val fee = brDeltaExpected * feePercent / 100
+
+            val actualFee = if (fee < 0) {fee * -1} else fee
+            // actualFee is always positive, irrespective of brDeltaExpected
+
+            val brDeltaExpectedWithFee = brDeltaExpected + actualFee
+
+            mandatoryRateConditions &&
+            mandatoryBankConditions &&
+            bcReserveDelta == brDeltaExpectedWithFee &&
+            validReserveRatio &&
+            validDataInput
+        }
+                 */
+        let p2s_addr_str = "7Nq5tKsVYCgneNgEfA2BJKwGsWozezNLhCNsRBihcHVFkDTuTThd4Qt1bi7NfCK1HuuVfjksMrEftV6MEFajjuyp1TMD2PX7SYWvkg9zH4CtgpdoBjekCNXs5XawxXnW6FT7GCqXTpJUP2TkkuqBh1df99PTigehys36uZz9wQnkrJXrv3mw3Yy4CM622qe5wdqLtpEonjazEmsw8weqEYegDyfJnswDvDkLPXtcCB86i19jik4fnSTtCcYj3jpWCQ7WL5dZn1ivs5JGRsR2ioNCRiZd3Gu1zJBgbHkMg41Z6VeCRWXjGY99BUtgtQiepSHGHajFCVcFAHhVxccdVUPCxGeEL6c2dNx6qzEkVfTfHs5qBgJewR8KCZTCVTurNBHeqCSVdxnfFvhW3f72cNrae5E1UhTAXU2iX4LZMHQsKyefY24Aq1b1srTyRWLpixjbcezFqA2TKjGSn1p1ruxbR7AQpW24ByPKT9sFE9ii4qNeXDnLcGtAAGS9FC5SD1s516a4NCu6v9zZfTvRKGkCwt78J8DEVnhTbttjcsvqFsUXQrvAv7TGVsaT4mL6B7F5BhRoZwFkgRXqFUVCWvgqJrwwjFRtbc5aZz";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+            .try_into()
+            .unwrap();
+        assert!(res);
+    }
+}
diff --git a/ergotree-ir/src/eval.rs b/ergotree-interpreter/src/eval.rs
similarity index 71%
rename from ergotree-ir/src/eval.rs
rename to ergotree-interpreter/src/eval.rs
index dbf137e2a..59debc403 100644
--- a/ergotree-ir/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -1,13 +1,14 @@
 //! Interpreter
 use std::rc::Rc;
 
-use crate::ir_ergo_box::IrErgoBoxArenaError;
-use crate::mir::constant::TryExtractFromError;
-use crate::mir::expr::Expr;
-use crate::mir::value::Value;
-use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
+use ergotree_ir::ir_ergo_box::IrErgoBoxArenaError;
+use ergotree_ir::mir::constant::TryExtractFromError;
+use ergotree_ir::mir::expr::Expr;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
 
 use cost_accum::CostAccumulator;
+use ergotree_ir::types::smethod::SMethod;
 use thiserror::Error;
 
 use self::context::Context;
@@ -17,12 +18,37 @@ use self::env::Env;
 pub mod context;
 pub mod env;
 
+pub(crate) mod and;
+pub(crate) mod apply;
+pub(crate) mod bin_op;
+pub(crate) mod block;
+pub(crate) mod bool_to_sigma;
+pub(crate) mod calc_blake2b256;
+pub(crate) mod coll_by_index;
+pub(crate) mod coll_filter;
+pub(crate) mod coll_fold;
+pub(crate) mod coll_map;
+pub(crate) mod coll_size;
+pub(crate) mod collection;
 pub(crate) mod cost_accum;
 pub(crate) mod costs;
 pub(crate) mod expr;
+pub(crate) mod extract_amount;
+pub(crate) mod extract_reg_as;
+pub(crate) mod extract_script_bytes;
+pub(crate) mod func_value;
 pub(crate) mod global_vars;
+pub(crate) mod if_op;
+pub(crate) mod logical_not;
 pub(crate) mod method_call;
+pub(crate) mod option_get;
+pub(crate) mod or;
 pub(crate) mod property_call;
+pub(crate) mod sbox;
+pub(crate) mod scontext;
+pub(crate) mod select_field;
+pub(crate) mod upcast;
+pub(crate) mod val_use;
 
 /// Interpreter errors
 #[derive(Error, PartialEq, Eq, Debug, Clone)]
@@ -114,17 +140,31 @@ pub trait Evaluable {
     fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError>;
 }
 
+type EvalFn = fn(ctx: Rc<Context>, Value, Vec<Value>) -> Result<Value, EvalError>;
+
+fn smethod_eval_fn(method: &SMethod) -> EvalFn {
+    use ergotree_ir::types::*;
+    match method.obj_type.type_id() {
+        scontext::TYPE_ID if method.method_id() == scontext::DATA_INPUTS_PROPERTY_METHOD_ID => {
+            self::scontext::DATA_INPUTS_EVAL_FN
+        }
+        sbox::TYPE_ID if method.method_id() == sbox::VALUE_METHOD_ID => self::sbox::VALUE_EVAL_FN,
+        sbox::TYPE_ID if method.method_id() == sbox::GET_REG_METHOD_ID => {
+            self::sbox::GET_REG_EVAL_FN
+        }
+        sbox::TYPE_ID if method.method_id() == sbox::TOKENS_METHOD_ID => self::sbox::TOKENS_EVAL_FN,
+        _ => todo!("no EvalFn for SMethod: {:?}", method),
+    }
+}
+
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 pub mod tests {
-
-    #![allow(dead_code)]
-
-    use crate::mir::constant::TryExtractInto;
-    use crate::{mir::constant::TryExtractFrom, test_util::force_any_val};
-
     use super::env::Env;
     use super::*;
+    use ergotree_ir::mir::constant::TryExtractFrom;
+    use ergotree_ir::mir::constant::TryExtractInto;
+    use test_util::force_any_val;
 
     pub fn eval_out_wo_ctx<T: TryExtractFrom<Value>>(expr: &Expr) -> T {
         let ctx = Rc::new(force_any_val::<Context>());
diff --git a/ergotree-interpreter/src/eval/and.rs b/ergotree-interpreter/src/eval/and.rs
new file mode 100644
index 000000000..808c99633
--- /dev/null
+++ b/ergotree-interpreter/src/eval/and.rs
@@ -0,0 +1,42 @@
+use ergotree_ir::mir::and::And;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for And {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let input_v_bools = input_v.try_extract_into::<Vec<bool>>()?;
+        Ok(input_v_bools.iter().all(|b| *b).into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+
+    use super::*;
+
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::collection;
+    use proptest::prelude::*;
+    use test_util::force_any_val;
+
+    proptest! {
+
+        #[test]
+        fn eval(bools in collection::vec(any::<bool>(), 0..10)) {
+            let expr: Expr = And {input: Expr::Const(bools.clone().into()).into()}.into();
+            let ctx = Rc::new(force_any_val::<Context>());
+            let res = eval_out::<bool>(&expr, ctx);
+            prop_assert_eq!(res, bools.iter().all(|b| *b));
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/apply.rs b/ergotree-interpreter/src/eval/apply.rs
new file mode 100644
index 000000000..761c156ae
--- /dev/null
+++ b/ergotree-interpreter/src/eval/apply.rs
@@ -0,0 +1,98 @@
+use ergotree_ir::mir::apply::Apply;
+use ergotree_ir::mir::val_def::ValId;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Apply {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let func_v = self.func.eval(env, ctx)?;
+        let args_v_res: Result<Vec<Value>, EvalError> =
+            self.args.iter().map(|arg| arg.eval(env, ctx)).collect();
+        let args_v = args_v_res?;
+        match func_v {
+            Value::FuncValue(fv) => {
+                let arg_ids: Vec<ValId> = fv.args().iter().map(|a| a.idx).collect();
+                let mut cur_env = env.clone();
+                arg_ids.iter().zip(args_v).for_each(|(idx, arg_v)| {
+                    cur_env.insert(*idx, arg_v);
+                });
+                fv.body().eval(&cur_env, ctx)
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected func_v to be Value::FuncValue got: {0:?}",
+                func_v
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::bin_op::RelationOp;
+    use ergotree_ir::mir::block::BlockValue;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::func_value::FuncArg;
+    use ergotree_ir::mir::func_value::FuncValue;
+    use ergotree_ir::mir::val_def::ValDef;
+    use ergotree_ir::mir::val_use::ValUse;
+    use ergotree_ir::types::stype::SType;
+    use test_util::force_any_val;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+
+    use super::*;
+
+    #[test]
+    fn eval_user_defined_func_call() {
+        let arg = Expr::Const(1i32.into());
+        let bin_op = Expr::BinOp(BinOp {
+            kind: RelationOp::Eq.into(),
+            left: Box::new(
+                ValUse {
+                    val_id: 1.into(),
+                    tpe: SType::SInt,
+                }
+                .into(),
+            ),
+            right: Box::new(
+                ValUse {
+                    val_id: 2.into(),
+                    tpe: SType::SInt,
+                }
+                .into(),
+            ),
+        });
+        let body = Expr::BlockValue(BlockValue {
+            items: vec![ValDef {
+                id: 2.into(),
+                rhs: Box::new(Expr::Const(1i32.into())),
+            }
+            .into()],
+            result: Box::new(bin_op),
+        });
+        let apply: Expr = Apply {
+            func: Box::new(
+                FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::SInt,
+                    }],
+                    body,
+                )
+                .into(),
+            ),
+            args: vec![arg],
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert!(eval_out::<bool>(&apply, ctx));
+    }
+}
diff --git a/ergotree-interpreter/src/eval/bin_op.rs b/ergotree-interpreter/src/eval/bin_op.rs
new file mode 100644
index 000000000..98fc1a92e
--- /dev/null
+++ b/ergotree-interpreter/src/eval/bin_op.rs
@@ -0,0 +1,469 @@
+//! Operators in ErgoTree
+
+use ergotree_ir::mir::bin_op::ArithOp;
+use ergotree_ir::mir::bin_op::BinOp;
+use ergotree_ir::mir::bin_op::BinOpKind;
+use ergotree_ir::mir::bin_op::RelationOp;
+use ergotree_ir::mir::constant::TryExtractFrom;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::Value;
+use eval::costs::Costs;
+use num::CheckedAdd;
+use num::CheckedDiv;
+use num::CheckedMul;
+use num::CheckedSub;
+use num::Num;
+
+use crate::eval;
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+fn eval_plus<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + CheckedAdd + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.checked_add(&rv_raw).ok_or_else(|| {
+        EvalError::ArithmeticException(format!(
+            "({0}) + ({1}) resulted in overflow",
+            lv_raw, rv_raw
+        ))
+    })?)
+    .into())
+}
+
+fn eval_minus<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + CheckedSub + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.checked_sub(&rv_raw).ok_or_else(|| {
+        EvalError::ArithmeticException(format!(
+            "({0}) - ({1}) resulted in overflow",
+            lv_raw, rv_raw
+        ))
+    })?)
+    .into())
+}
+
+fn eval_mul<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + CheckedMul + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.checked_mul(&rv_raw).ok_or_else(|| {
+        EvalError::ArithmeticException(format!(
+            "({0}) * ({1}) resulted in overflow",
+            lv_raw, rv_raw
+        ))
+    })?)
+    .into())
+}
+
+fn eval_div<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + CheckedDiv + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.checked_div(&rv_raw).ok_or_else(|| {
+        EvalError::ArithmeticException(format!(
+            "({0}) / ({1}) resulted in exception",
+            lv_raw, rv_raw
+        ))
+    })?)
+    .into())
+}
+
+fn eval_ge(lv: Value, rv: Value) -> Result<Value, EvalError> {
+    match lv {
+        Value::Byte(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i8>()?).into()),
+        Value::Short(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i16>()?).into()),
+        Value::Int(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i32>()?).into()),
+        Value::Long(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i64>()?).into()),
+        Value::BigInt => todo!(),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected BinOp::left to be numeric value, got {0:?}",
+            lv
+        ))),
+    }
+}
+
+fn eval_gt(lv: Value, rv: Value) -> Result<Value, EvalError> {
+    match lv {
+        Value::Byte(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i8>()?).into()),
+        Value::Short(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i16>()?).into()),
+        Value::Int(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i32>()?).into()),
+        Value::Long(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i64>()?).into()),
+        Value::BigInt => todo!(),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected BinOp::left to be numeric value, got {0:?}",
+            lv
+        ))),
+    }
+}
+
+fn eval_lt(lv: Value, rv: Value) -> Result<Value, EvalError> {
+    match lv {
+        Value::Byte(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i8>()?).into()),
+        Value::Short(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i16>()?).into()),
+        Value::Int(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i32>()?).into()),
+        Value::Long(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i64>()?).into()),
+        Value::BigInt => todo!(),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected BinOp::left to be numeric value, got {0:?}",
+            lv
+        ))),
+    }
+}
+
+fn eval_le(lv: Value, rv: Value) -> Result<Value, EvalError> {
+    match lv {
+        Value::Byte(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i8>()?).into()),
+        Value::Short(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i16>()?).into()),
+        Value::Int(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i32>()?).into()),
+        Value::Long(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i64>()?).into()),
+        Value::BigInt => todo!(),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected BinOp::left to be numeric value, got {0:?}",
+            lv
+        ))),
+    }
+}
+
+fn eval_max<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + Ord + TryExtractFrom<Value> + Into<Value>,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.max(rv_raw)).into())
+}
+
+fn eval_min<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
+where
+    T: Num + Ord + TryExtractFrom<Value> + Into<Value>,
+{
+    let rv_raw = rv.try_extract_into::<T>()?;
+    Ok((lv_raw.min(rv_raw)).into())
+}
+
+impl Evaluable for BinOp {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        ctx.cost_accum.add(Costs::DEFAULT.eq_const_size)?;
+        let lv = self.left.eval(env, ctx)?;
+        // using closure to keep right value from evaluation (for lazy AND, OR)
+        let mut rv = || self.right.eval(env, ctx);
+        match self.kind {
+            BinOpKind::Relation(op) => match op {
+                RelationOp::Eq => Ok(Value::Boolean(lv == rv()?)),
+                RelationOp::NEq => Ok(Value::Boolean(lv != rv()?)),
+                RelationOp::GT => eval_gt(lv, rv()?),
+                RelationOp::LT => eval_lt(lv, rv()?),
+                RelationOp::GE => eval_ge(lv, rv()?),
+                RelationOp::LE => eval_le(lv, rv()?),
+                RelationOp::And => Ok(Value::Boolean(if lv.try_extract_into::<bool>()? {
+                    rv()?.try_extract_into::<bool>()?
+                } else {
+                    false
+                })),
+                RelationOp::Or => Ok(Value::Boolean(if !lv.try_extract_into::<bool>()? {
+                    rv()?.try_extract_into::<bool>()?
+                } else {
+                    true
+                })),
+            },
+            BinOpKind::Arith(op) => match op {
+                ArithOp::Plus => match lv {
+                    Value::Byte(lv_raw) => eval_plus(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_plus(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_plus(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_plus(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+                ArithOp::Minus => match lv {
+                    Value::Byte(lv_raw) => eval_minus(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_minus(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_minus(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_minus(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+                ArithOp::Multiply => match lv {
+                    Value::Byte(lv_raw) => eval_mul(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_mul(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_mul(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_mul(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+                ArithOp::Divide => match lv {
+                    Value::Byte(lv_raw) => eval_div(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_div(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_div(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_div(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+                ArithOp::Max => match lv {
+                    Value::Byte(lv_raw) => eval_max(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_max(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_max(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_max(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+                ArithOp::Min => match lv {
+                    Value::Byte(lv_raw) => eval_min(lv_raw, rv()?),
+                    Value::Short(lv_raw) => eval_min(lv_raw, rv()?),
+                    Value::Int(lv_raw) => eval_min(lv_raw, rv()?),
+                    Value::Long(lv_raw) => eval_min(lv_raw, rv()?),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+            },
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::eval::tests::try_eval_out;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::prelude::*;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    fn check_eq_neq(left: Constant, right: Constant) -> bool {
+        let eq_op: Expr = BinOp {
+            kind: BinOpKind::Relation(RelationOp::Eq),
+            left: Box::new(left.clone().into()),
+            right: Box::new(right.clone().into()),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        let neq_op: Expr = BinOp {
+            kind: BinOpKind::Relation(RelationOp::NEq),
+            left: Box::new(left.into()),
+            right: Box::new(right.into()),
+        }
+        .into();
+        let ctx1 = Rc::new(force_any_val::<Context>());
+        eval_out::<bool>(&eq_op, ctx) && !eval_out::<bool>(&neq_op, ctx1)
+    }
+
+    #[test]
+    fn num_eq() {
+        assert!(check_eq_neq(1i64.into(), 1i64.into()));
+    }
+
+    #[test]
+    fn num_neq() {
+        assert!(!check_eq_neq(2i64.into(), 1i64.into()));
+    }
+
+    #[test]
+    fn option_eq() {
+        assert!(check_eq_neq(Some(1i64).into(), Some(1i64).into()));
+        let none: Option<i64> = None;
+        assert!(check_eq_neq(none.into(), none.into()));
+        // Option<Vec<i8>>
+        assert!(check_eq_neq(
+            Some(vec![1i8, 2i8]).into(),
+            Some(vec![1i8, 2i8]).into()
+        ));
+        // Vec<Option<i64>>
+        assert!(check_eq_neq(
+            vec![Some(1i64), Some(1i64)].into(),
+            vec![Some(1i64), Some(1i64)].into()
+        ));
+    }
+
+    #[test]
+    fn option_neq() {
+        assert!(!check_eq_neq(Some(2i64).into(), Some(1i64).into()));
+        let none: Option<i64> = None;
+        assert!(!check_eq_neq(none.into(), Some(1i64).into()));
+        // Option<Vec<i8>>
+        assert!(!check_eq_neq(
+            Some(vec![1i8, 2i8]).into(),
+            Some(vec![2i8, 2i8]).into()
+        ));
+        // Vec<Option<i64>>
+        assert!(!check_eq_neq(
+            vec![Some(1i64), Some(1i64)].into(),
+            vec![Some(2i64), Some(1i64)].into()
+        ));
+    }
+
+    #[test]
+    fn tuple_eq() {
+        assert!(check_eq_neq((1i64, true).into(), (1i64, true).into()));
+    }
+
+    #[test]
+    fn bin_or_eval_laziness() {
+        let e: Expr = BinOp {
+            kind: BinOpKind::Relation(RelationOp::Or),
+            left: Box::new(Expr::Const(true.into())),
+            // something that should blow-up the evaluation
+            right: Box::new(
+                BinOp {
+                    kind: ArithOp::Divide.into(),
+                    left: Box::new(Expr::Const(1i32.into())),
+                    right: Box::new(Expr::Const(0i32.into())),
+                }
+                .into(),
+            ),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(eval_out::<bool>(&e, ctx), true);
+    }
+
+    #[test]
+    fn bin_and_eval_laziness() {
+        let e: Expr = BinOp {
+            kind: BinOpKind::Relation(RelationOp::And),
+            left: Box::new(Expr::Const(false.into())),
+            // something that should blow-up the evaluation
+            right: Box::new(
+                BinOp {
+                    kind: ArithOp::Divide.into(),
+                    left: Box::new(Expr::Const(1i32.into())),
+                    right: Box::new(Expr::Const(0i32.into())),
+                }
+                .into(),
+            ),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(eval_out::<bool>(&e, ctx), false);
+    }
+
+    fn eval_num_op<T: TryExtractFrom<Value>>(
+        op: ArithOp,
+        left: Constant,
+        right: Constant,
+    ) -> Result<T, EvalError> {
+        let expr: Expr = BinOp {
+            kind: BinOpKind::Arith(op),
+            left: Box::new(left.into()),
+            right: Box::new(right.into()),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        try_eval_out::<T>(&expr, ctx)
+    }
+
+    fn eval_relation_op(op: RelationOp, left: Constant, right: Constant) -> bool {
+        let expr: Expr = BinOp {
+            kind: BinOpKind::Relation(op),
+            left: Box::new(left.into()),
+            right: Box::new(right.into()),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        eval_out::<bool>(&expr, ctx)
+    }
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(32))]
+
+        #[test]
+        fn test_eq(v in any::<Constant>()) {
+            prop_assert![check_eq_neq(v.clone(), v)];
+        }
+
+        #[test]
+        fn test_num_slong(l in any::<i64>(), r in any::<i64>()) {
+            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
+            prop_assert_eq!(eval_num_op::<i64>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
+            prop_assert_eq!(eval_num_op::<i64>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
+
+            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
+        }
+
+        #[test]
+        fn test_num_sint(l in any::<i32>(), r in any::<i32>()) {
+            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
+            prop_assert_eq!(eval_num_op::<i32>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
+            prop_assert_eq!(eval_num_op::<i32>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
+
+            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
+        }
+
+        #[test]
+        fn test_num_sshort(l in any::<i16>(), r in any::<i16>()) {
+            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
+            prop_assert_eq!(eval_num_op::<i16>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
+            prop_assert_eq!(eval_num_op::<i16>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
+
+            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
+        }
+
+        #[test]
+        fn test_num_sbyte(l in any::<i8>(), r in any::<i8>()) {
+            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
+            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
+            prop_assert_eq!(eval_num_op::<i8>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
+            prop_assert_eq!(eval_num_op::<i8>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
+
+            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
+        }
+
+        #[test]
+        fn test_and_or(l in any::<bool>(), r in any::<bool>()) {
+            prop_assert_eq!(eval_relation_op(RelationOp::And, l.into(), r.into()), l && r);
+            prop_assert_eq!(eval_relation_op(RelationOp::Or, l.into(), r.into()), l || r);
+        }
+
+    }
+}
diff --git a/ergotree-interpreter/src/eval/block.rs b/ergotree-interpreter/src/eval/block.rs
new file mode 100644
index 000000000..8fa73e31e
--- /dev/null
+++ b/ergotree-interpreter/src/eval/block.rs
@@ -0,0 +1,38 @@
+use ergotree_ir::mir::block::BlockValue;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::val_def::ValDef;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for BlockValue {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let mut cur_env = env.clone();
+        for i in self.items.iter() {
+            let val_def = i.clone().try_extract_into::<ValDef>()?;
+            let v: Value = val_def.rhs.eval(&cur_env, ctx)?;
+            cur_env.insert(val_def.id, v);
+        }
+        self.result.eval(&cur_env, ctx)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use ergotree_ir::mir::block::BlockValue;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(block in any::<BlockValue>()) {
+            let e = Expr::BlockValue(block);
+            prop_assert_eq![sigma_serialize_roundtrip(&e), e];
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/bool_to_sigma.rs b/ergotree-interpreter/src/eval/bool_to_sigma.rs
new file mode 100644
index 000000000..16d45a456
--- /dev/null
+++ b/ergotree-interpreter/src/eval/bool_to_sigma.rs
@@ -0,0 +1,36 @@
+use ergotree_ir::mir::bool_to_sigma::BoolToSigmaProp;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for BoolToSigmaProp {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let input_v_bool = input_v.try_extract_into::<bool>()?;
+        Ok((SigmaProp::new(SigmaBoolean::TrivialProp(input_v_bool))).into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn eval(b in any::<bool>()) {
+            let expr: Expr = BoolToSigmaProp {input: Expr::Const(b.into()).into()}.into();
+            let res = eval_out_wo_ctx::<SigmaProp>(&expr);
+            prop_assert_eq!(res, SigmaProp::new(SigmaBoolean::TrivialProp(b)));
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/calc_blake2b256.rs b/ergotree-interpreter/src/eval/calc_blake2b256.rs
new file mode 100644
index 000000000..57e44f00e
--- /dev/null
+++ b/ergotree-interpreter/src/eval/calc_blake2b256.rs
@@ -0,0 +1,54 @@
+use ergotree_ir::mir::calc_blake2b256::CalcBlake2b256;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::NativeColl;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::util::AsVecU8;
+use sigma_util::hash::blake2b256_hash;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for CalcBlake2b256 {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v.clone() {
+            Value::Coll(CollKind::NativeColl(NativeColl::CollByte(coll_byte))) => {
+                let expected_hash: Vec<u8> =
+                    blake2b256_hash(coll_byte.as_vec_u8().as_slice()).to_vec();
+                Ok(expected_hash.into())
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected CalcBlake2b256 input to be byte array, got: {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::prelude::*;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    proptest! {
+
+        #[test]
+        fn eval(byte_array in any::<Vec<u8>>()) {
+            let expected_hash = blake2b256_hash(byte_array.as_slice()).to_vec();
+            let expr: Expr = CalcBlake2b256 {
+                input: Box::new(Expr::Const(byte_array.into())),
+            }
+            .into();
+            let ctx = Rc::new(force_any_val::<Context>());
+            assert_eq!(eval_out::<Vec<i8>>(&expr, ctx).as_vec_u8(), expected_hash);
+        }
+
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_by_index.rs b/ergotree-interpreter/src/eval/coll_by_index.rs
new file mode 100644
index 000000000..6a2ba3330
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_by_index.rs
@@ -0,0 +1,79 @@
+use ergotree_ir::mir::coll_by_index::ByIndex;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ByIndex {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let index_v = self.index.eval(env, ctx)?;
+        let normalized_input_vals: Vec<Value> = match input_v {
+            Value::Coll(coll) => Ok(coll.as_vec()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "ByIndex: expected input to be Value::Coll, got: {0:?}",
+                input_v
+            ))),
+        }?;
+        match self.default.clone() {
+            Some(default) => {
+                let default_v = default.eval(env, ctx)?;
+                Ok(normalized_input_vals
+                    .get(index_v.try_extract_into::<i32>()? as usize)
+                    .cloned()
+                    .unwrap_or(default_v))
+            }
+            None => normalized_input_vals
+                .get(index_v.clone().try_extract_into::<i32>()? as usize)
+                .cloned()
+                .ok_or_else(|| {
+                    EvalError::Misc(format!(
+                        "ByIndex: index {0:?} out of bounds for collection size {1:?}",
+                        index_v,
+                        normalized_input_vals.len()
+                    ))
+                }),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use test_util::force_any_val;
+
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use std::rc::Rc;
+
+    #[test]
+    fn eval() {
+        let expr: Expr = ByIndex::new(GlobalVars::Outputs.into(), Expr::Const(0i32.into()), None)
+            .unwrap()
+            .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<IrBoxId>(&expr, ctx.clone()),
+            ctx.outputs.get(0).unwrap().clone()
+        );
+    }
+
+    #[test]
+    fn eval_with_default() {
+        let expr: Expr = ByIndex::new(
+            Expr::Const(vec![1i64, 2i64].into()),
+            Expr::Const(3i32.into()),
+            Some(Box::new(Expr::Const(5i64.into()))),
+        )
+        .unwrap()
+        .into();
+        assert_eq!(eval_out_wo_ctx::<i64>(&expr), 5);
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_filter.rs b/ergotree-interpreter/src/eval/coll_filter.rs
new file mode 100644
index 000000000..2b07764b6
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_filter.rs
@@ -0,0 +1,140 @@
+use ergotree_ir::mir::coll_filter::Filter;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Filter {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let condition_v = self.condition.eval(env, ctx)?;
+        let input_v_clone = input_v.clone();
+        let mut condition_call = |arg: Value| match &condition_v {
+            Value::FuncValue(func_value) => {
+                let func_arg = func_value.args().first().ok_or_else(|| {
+                    EvalError::NotFound(
+                        "Filter: evaluated condition has empty arguments list".to_string(),
+                    )
+                })?;
+                let env1 = env.clone().extend(func_arg.idx, arg);
+                func_value.body().eval(&env1, ctx)
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected Filter::condition to be Value::FuncValue got: {0:?}",
+                input_v_clone
+            ))),
+        };
+        let normalized_input_vals: Vec<Value> = match input_v {
+            Value::Coll(coll) => {
+                if *coll.elem_tpe() != self.elem_tpe {
+                    return Err(EvalError::UnexpectedValue(format!(
+                        "expected Filter input element type to be {0:?}, got: {1:?}",
+                        self.elem_tpe,
+                        coll.elem_tpe()
+                    )));
+                };
+                Ok(coll.as_vec())
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected Map input to be Value::Coll, got: {0:?}",
+                input_v
+            ))),
+        }?;
+
+        let items_conditions: Vec<bool> = normalized_input_vals
+            .clone()
+            .into_iter()
+            .map(|item| {
+                condition_call(item).and_then(|res| {
+                    res.try_extract_into::<bool>()
+                        .map_err(EvalError::TryExtractFrom)
+                })
+            })
+            .collect::<Result<Vec<bool>, EvalError>>()?;
+        let filtered_items = normalized_input_vals
+            .into_iter()
+            .zip(items_conditions)
+            .filter(|(_, condition)| *condition)
+            .map(|(item, _)| item)
+            .collect();
+        Ok(Value::Coll(CollKind::from_vec(
+            self.elem_tpe.clone(),
+            filtered_items,
+        )?))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+
+    use super::*;
+
+    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::bin_op::RelationOp;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::extract_amount::ExtractAmount;
+    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::val_use::ValUse;
+    use ergotree_ir::types::scontext;
+    use ergotree_ir::types::stype::SType;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
+        #[test]
+        fn eval_box_value(ctx in any::<Context>()) {
+            let data_inputs: Expr = PropertyCall {
+                obj: Box::new(Expr::Context),
+                method: scontext::DATA_INPUTS_PROPERTY.clone(),
+            }
+            .into();
+            let val_use: Expr = ValUse {
+                val_id: 1.into(),
+                tpe: SType::SBox,
+            }
+            .into();
+            let body: Expr = BinOp {
+                kind: RelationOp::LE.into(),
+                left: Box::new(Expr::Const(1i64.into())),
+                right: Box::new(Expr::ExtractAmount(
+                        ExtractAmount::new(val_use)
+                    .unwrap(),
+                )),
+            }
+            .into();
+            let expr: Expr = Filter::new(
+                data_inputs,
+                FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::SBox,
+                    }],
+                    body,
+                )
+                .into(),
+            )
+            .unwrap()
+            .into();
+            let ctx = Rc::new(ctx);
+            assert_eq!(
+                eval_out::<Vec<IrBoxId>>(&expr, ctx.clone()),
+                ctx.data_inputs.clone()
+                    .into_iter()
+                    .filter(| b| 1 <= b.get_box(&ctx.box_arena).unwrap().value()).collect::<Vec<IrBoxId>>()
+            );
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_fold.rs b/ergotree-interpreter/src/eval/coll_fold.rs
new file mode 100644
index 000000000..77a4d6829
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_fold.rs
@@ -0,0 +1,137 @@
+use ergotree_ir::mir::coll_fold::Fold;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::NativeColl;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::stuple::TupleItems;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Fold {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let zero_v = self.zero.eval(env, ctx)?;
+        let fold_op_v = self.fold_op.eval(env, ctx)?;
+        let input_v_clone = input_v.clone();
+        let mut fold_op_call = |arg: Value| match &fold_op_v {
+            Value::FuncValue(func_value) => {
+                let func_arg = func_value
+                    .args()
+                    .first()
+                    .ok_or_else(|| EvalError::NotFound("empty argument for fold op".to_string()))?;
+                let env1 = env.clone().extend(func_arg.idx, arg);
+                func_value.body().eval(&env1, ctx)
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected fold_op to be Value::FuncValue got: {0:?}",
+                input_v_clone
+            ))),
+        };
+        match input_v {
+            Value::Coll(coll) => match coll {
+                CollKind::NativeColl(NativeColl::CollByte(coll_byte)) => {
+                    coll_byte.iter().try_fold(zero_v, |acc, byte| {
+                        let tup_arg = Value::Tup(TupleItems::pair(acc, Value::Byte(*byte)));
+                        fold_op_call(tup_arg)
+                    })
+                }
+                CollKind::WrappedColl {
+                    elem_tpe: _,
+                    items: v,
+                } => v.iter().try_fold(zero_v, |acc, item| {
+                    let tup_arg = Value::Tup(TupleItems::pair(acc, item.clone()));
+                    fold_op_call(tup_arg)
+                }),
+            },
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected Fold input to be Value::Coll, got: {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::convert::TryInto;
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::bin_op::ArithOp;
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::extract_amount::ExtractAmount;
+    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::select_field::SelectField;
+    use ergotree_ir::mir::val_use::ValUse;
+    use ergotree_ir::types::scontext;
+    use ergotree_ir::types::stuple::STuple;
+    use ergotree_ir::types::stype::SType;
+
+    use super::*;
+
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
+        #[test]
+        fn eval_fold(ctx in any::<Context>()) {
+            let data_inputs: Expr = PropertyCall {
+                obj: Box::new(Expr::Context),
+                method: scontext::DATA_INPUTS_PROPERTY.clone(),
+            }
+            .into();
+            let tuple: Expr = ValUse {
+                val_id: 1.into(),
+                tpe: SType::STuple(STuple {
+                    items: TupleItems::pair(SType::SLong, SType::SBox),
+                }),
+            }
+            .into();
+            let fold_op_body: Expr = BinOp {
+                kind: ArithOp::Plus.into(),
+                left: Box::new(Expr::SelectField(
+                    SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap(),
+                )),
+                right: Box::new(Expr::ExtractAmount(
+                    ExtractAmount::new(Expr::SelectField(
+                        SelectField::new(tuple, 2.try_into().unwrap()).unwrap(),
+                    ))
+                    .unwrap(),
+                )),
+            }
+            .into();
+            let expr: Expr = Fold::new(
+                data_inputs,
+                Expr::Const(0i64.into()),
+                FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::STuple(STuple {
+                            items: TupleItems::pair(SType::SLong, SType::SBox),
+                        }),
+                    }],
+                    fold_op_body,
+                )
+                .into(),
+            )
+            .unwrap()
+            .into();
+            let ctx = Rc::new(ctx);
+            assert_eq!(
+                eval_out::<i64>(&expr, ctx.clone()),
+                ctx.data_inputs
+                    .iter()
+                    .fold(0i64, |acc, b| acc + b.get_box(&ctx.box_arena).unwrap().value())
+            );
+        }
+
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_map.rs b/ergotree-interpreter/src/eval/coll_map.rs
new file mode 100644
index 000000000..bb651e391
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_map.rs
@@ -0,0 +1,137 @@
+use ergotree_ir::mir::coll_map::Map;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Map {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let mapper_v = self.mapper.eval(env, ctx)?;
+        let input_v_clone = input_v.clone();
+        let mut mapper_call = |arg: Value| match &mapper_v {
+            Value::FuncValue(func_value) => {
+                let func_arg = func_value.args().first().ok_or_else(|| {
+                    EvalError::NotFound(
+                        "Map: evaluated mapper has empty arguments list".to_string(),
+                    )
+                })?;
+                let env1 = env.clone().extend(func_arg.idx, arg);
+                func_value.body().eval(&env1, ctx)
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected mapper to be Value::FuncValue got: {0:?}",
+                input_v_clone
+            ))),
+        };
+        let mapper_input_tpe = self
+            .mapper_sfunc
+            .t_dom
+            .first()
+            .ok_or_else(|| {
+                EvalError::NotFound(
+                    "Map: mapper SFunc.t_dom is empty (does not have arguments)".to_string(),
+                )
+            })?
+            .clone();
+        let normalized_input_vals: Vec<Value> = match input_v {
+            Value::Coll(coll) => {
+                if *coll.elem_tpe() != mapper_input_tpe {
+                    return Err(EvalError::UnexpectedValue(format!(
+                        "expected Map input element type to be {0:?}, got: {1:?}",
+                        mapper_input_tpe,
+                        coll.elem_tpe()
+                    )));
+                };
+                Ok(coll.as_vec())
+            }
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected Map input to be Value::Coll, got: {0:?}",
+                input_v
+            ))),
+        }?;
+        normalized_input_vals
+            .iter()
+            .map(|item| mapper_call(item.clone()))
+            .collect::<Result<Vec<Value>, EvalError>>()
+            .map(|values| {
+                CollKind::from_vec(self.out_elem_tpe(), values).map_err(EvalError::TryExtractFrom)
+            })
+            .and_then(|v| v) // flatten <Result<Result<Value, _>, _>
+            .map(Value::Coll)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::bin_op::ArithOp;
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::extract_amount::ExtractAmount;
+    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::val_use::ValUse;
+    use ergotree_ir::types::scontext;
+    use ergotree_ir::types::stype::SType;
+
+    use super::*;
+
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(16))]
+
+        #[test]
+        fn eval_box_value(ctx in any::<Context>()) {
+            let data_inputs: Expr = PropertyCall {
+                obj: Box::new(Expr::Context),
+                method: scontext::DATA_INPUTS_PROPERTY.clone(),
+            }
+            .into();
+            let val_use: Expr = ValUse {
+                val_id: 1.into(),
+                tpe: SType::SBox,
+            }
+            .into();
+            let mapper_body: Expr = BinOp {
+                kind: ArithOp::Plus.into(),
+                left: Box::new(Expr::Const(1i64.into())),
+                right: Box::new(Expr::ExtractAmount(
+                        ExtractAmount::new(val_use)
+                    .unwrap(),
+                )),
+            }
+            .into();
+            let expr: Expr = Map::new(
+                data_inputs,
+                FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::SBox,
+                    }],
+                    mapper_body,
+                )
+                .into(),
+            )
+            .unwrap()
+            .into();
+            let ctx = Rc::new(ctx);
+            assert_eq!(
+                eval_out::<Vec<i64>>(&expr, ctx.clone()),
+                ctx.data_inputs
+                    .iter()
+                    .map(| b| b.get_box(&ctx.box_arena).unwrap().value() + 1).collect::<Vec<i64>>()
+            );
+        }
+
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_size.rs b/ergotree-interpreter/src/eval/coll_size.rs
new file mode 100644
index 000000000..f0df251cb
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_size.rs
@@ -0,0 +1,42 @@
+use ergotree_ir::mir::coll_size::SizeOf;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for SizeOf {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let normalized_input_vals: Vec<Value> = match input_v {
+            Value::Coll(coll) => Ok(coll.as_vec()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "SizeOf: expected input to be Value::Coll, got: {0:?}",
+                input_v
+            ))),
+        }?;
+        Ok((normalized_input_vals.len() as i32).into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    #[test]
+    fn eval() {
+        let expr: Expr = SizeOf::new(GlobalVars::Outputs.into()).unwrap().into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<i32>(&expr, ctx.clone()),
+            ctx.outputs.len() as i32
+        );
+    }
+}
diff --git a/ergotree-interpreter/src/eval/collection.rs b/ergotree-interpreter/src/eval/collection.rs
new file mode 100644
index 000000000..f846359a0
--- /dev/null
+++ b/ergotree-interpreter/src/eval/collection.rs
@@ -0,0 +1,81 @@
+use ergotree_ir::mir::collection::Collection;
+use ergotree_ir::mir::constant::TryExtractFromError;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::NativeColl;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::stype::SType;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Collection {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        Ok(match self {
+            Collection::BoolConstants(bools) => bools.clone().into(),
+            Collection::Exprs { elem_tpe, items } => {
+                let items_v: Result<Vec<Value>, EvalError> =
+                    items.iter().map(|i| i.eval(env, ctx)).collect();
+                match elem_tpe {
+                    SType::SByte => {
+                        let bytes: Result<Vec<i8>, TryExtractFromError> = items_v?
+                            .into_iter()
+                            .map(|i| i.try_extract_into::<i8>())
+                            .collect();
+                        Value::Coll(CollKind::NativeColl(NativeColl::CollByte(bytes?)))
+                    }
+                    _ => Value::Coll(CollKind::WrappedColl {
+                        elem_tpe: elem_tpe.clone(),
+                        items: items_v?,
+                    }),
+                }
+            }
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn eval_byte_coll(bytes in any::<Vec<i8>>()) {
+            let value: Value = bytes.clone().into();
+            let exprs: Vec<Expr> = bytes.into_iter().map(|b| Expr::Const(b.into())).collect();
+            let coll: Expr = Collection::new(SType::SByte, exprs).unwrap().into();
+            let res = eval_out_wo_ctx::<Value>(&coll);
+            prop_assert_eq!(res, value);
+        }
+
+        #[test]
+        fn eval_bool_coll(bools in any::<Vec<bool>>()) {
+            let exprs: Vec<Expr> = bools.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
+            let coll: Expr = Collection::new(SType::SBoolean, exprs).unwrap().into();
+            let res = eval_out_wo_ctx::<Vec<bool>>(&coll);
+            prop_assert_eq!(res, bools);
+        }
+
+        #[test]
+        fn eval_long_coll(longs in any::<Vec<i64>>()) {
+            let exprs: Vec<Expr> = longs.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
+            let coll: Expr = Collection::new(SType::SLong, exprs).unwrap().into();
+            let res = eval_out_wo_ctx::<Vec<i64>>(&coll);
+            prop_assert_eq!(res, longs);
+        }
+
+        #[test]
+        fn eval_bytes_coll_coll(bb in any::<Vec<Vec<i8>>>()) {
+            let exprs: Vec<Expr> = bb.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
+            let coll: Expr = Collection::new(SType::SColl(SType::SByte.into()), exprs).unwrap().into();
+            let res = eval_out_wo_ctx::<Vec<Vec<i8>>>(&coll);
+            prop_assert_eq!(res, bb);
+        }
+    }
+}
diff --git a/ergotree-ir/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
similarity index 90%
rename from ergotree-ir/src/eval/context.rs
rename to ergotree-interpreter/src/eval/context.rs
index 5c8ea1430..3cddb956a 100644
--- a/ergotree-ir/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -1,7 +1,7 @@
 use std::rc::Rc;
 
-use crate::ir_ergo_box::IrBoxId;
-use crate::ir_ergo_box::IrErgoBoxArena;
+use ergotree_ir::ir_ergo_box::IrBoxId;
+use ergotree_ir::ir_ergo_box::IrErgoBoxArena;
 
 #[derive(Debug)]
 pub struct Context {
@@ -16,10 +16,9 @@ pub struct Context {
 pub mod arbitrary {
     use std::collections::HashMap;
 
-    use crate::ir_ergo_box::IrErgoBoxDummy;
-    use crate::ir_ergo_box::IrErgoBoxDummyArena;
-
     use super::*;
+    use ergotree_ir::ir_ergo_box::IrErgoBoxDummy;
+    use ergotree_ir::ir_ergo_box::IrErgoBoxDummyArena;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
@@ -62,5 +61,4 @@ pub mod arbitrary {
 }
 
 #[cfg(test)]
-#[cfg(feature = "arbitrary")]
 mod tests {}
diff --git a/ergotree-ir/src/eval/cost_accum.rs b/ergotree-interpreter/src/eval/cost_accum.rs
similarity index 96%
rename from ergotree-ir/src/eval/cost_accum.rs
rename to ergotree-interpreter/src/eval/cost_accum.rs
index 99a191bf0..c47ebb56b 100644
--- a/ergotree-ir/src/eval/cost_accum.rs
+++ b/ergotree-interpreter/src/eval/cost_accum.rs
@@ -1,5 +1,5 @@
 use super::costs::{Cost, Costs};
-use crate::mir::expr::Expr;
+use ergotree_ir::mir::expr::Expr;
 use thiserror::Error;
 
 #[derive(Debug)]
diff --git a/ergotree-ir/src/eval/costs.rs b/ergotree-interpreter/src/eval/costs.rs
similarity index 91%
rename from ergotree-ir/src/eval/costs.rs
rename to ergotree-interpreter/src/eval/costs.rs
index d93da89ee..07e496199 100644
--- a/ergotree-ir/src/eval/costs.rs
+++ b/ergotree-interpreter/src/eval/costs.rs
@@ -1,4 +1,4 @@
-use crate::mir::expr::Expr;
+use ergotree_ir::mir::expr::Expr;
 
 extern crate derive_more;
 use derive_more::{From, Into};
diff --git a/ergotree-ir/src/eval/env.rs b/ergotree-interpreter/src/eval/env.rs
similarity index 90%
rename from ergotree-ir/src/eval/env.rs
rename to ergotree-interpreter/src/eval/env.rs
index 0b19740d5..70d3272a9 100644
--- a/ergotree-ir/src/eval/env.rs
+++ b/ergotree-interpreter/src/eval/env.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
-use crate::mir::val_def::ValId;
-use crate::mir::value::Value;
+use ergotree_ir::mir::val_def::ValId;
+use ergotree_ir::mir::value::Value;
 
 /// Environment for the interpreter
 #[derive(PartialEq, Eq, Debug, Clone)]
diff --git a/ergotree-ir/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs
similarity index 96%
rename from ergotree-ir/src/eval/expr.rs
rename to ergotree-interpreter/src/eval/expr.rs
index 1982ca727..01a13a537 100644
--- a/ergotree-ir/src/eval/expr.rs
+++ b/ergotree-interpreter/src/eval/expr.rs
@@ -1,5 +1,5 @@
-use crate::mir::expr::Expr;
-use crate::mir::value::Value;
+use ergotree_ir::mir::expr::Expr;
+use ergotree_ir::mir::value::Value;
 
 use super::Env;
 use super::EvalContext;
diff --git a/ergotree-interpreter/src/eval/expr.rs_ b/ergotree-interpreter/src/eval/expr.rs_
new file mode 100644
index 000000000..6fed417f3
--- /dev/null
+++ b/ergotree-interpreter/src/eval/expr.rs_
@@ -0,0 +1,405 @@
+//! IR expression
+
+use std::convert::TryFrom;
+use std::convert::TryInto;
+
+use crate::serialization::op_code::OpCode;
+use crate::types::scontext::SContext;
+use crate::types::stype::SType;
+
+use super::and::And;
+use super::apply::Apply;
+use super::bin_op::BinOp;
+use super::block::BlockValue;
+use super::bool_to_sigma::BoolToSigmaProp;
+use super::calc_blake2b256::CalcBlake2b256;
+use super::coll_by_index::ByIndex;
+use super::coll_filter::Filter;
+use super::coll_fold::Fold;
+use super::coll_map::Map;
+use super::coll_size::SizeOf;
+use super::collection::Collection;
+use super::constant::Constant;
+use super::constant::ConstantPlaceholder;
+use super::constant::TryExtractFrom;
+use super::constant::TryExtractFromError;
+use super::extract_amount::ExtractAmount;
+use super::extract_reg_as::ExtractRegisterAs;
+use super::extract_script_bytes::ExtractScriptBytes;
+use super::func_value::FuncValue;
+use super::global_vars::GlobalVars;
+use super::if_op::If;
+use super::logical_not::LogicalNot;
+use super::method_call::MethodCall;
+use super::option_get::OptionGet;
+use super::or::Or;
+use super::property_call::PropertyCall;
+use super::select_field::SelectField;
+use super::upcast::Upcast;
+use super::val_def::ValDef;
+use super::val_use::ValUse;
+
+extern crate derive_more;
+use derive_more::From;
+use derive_more::TryInto;
+
+#[derive(PartialEq, Eq, Debug, Clone, From, TryInto)]
+/// Expression in ErgoTree
+pub enum Expr {
+    /// Constant value
+    Const(Constant),
+    /// Placeholder for a constant
+    ConstPlaceholder(ConstantPlaceholder),
+    /// Collection declaration (array of expressions of the same type)
+    Collection(Collection),
+    /// Predefined functions (global)
+    /// Blake2b256 hash calculation
+    CalcBlake2b256(CalcBlake2b256),
+    /// Context variables (external)
+    Context,
+    // Global(Global),
+    /// Predefined global variables
+    GlobalVars(GlobalVars),
+    /// Function definition
+    FuncValue(FuncValue),
+    /// Function application
+    Apply(Apply),
+    /// Method call
+    MethodCall(MethodCall),
+    /// Property call
+    ProperyCall(PropertyCall),
+    /// Block (statements, followed by an expression)
+    BlockValue(BlockValue),
+    /// let-bound expression
+    ValDef(ValDef),
+    /// Reference to ValDef
+    ValUse(ValUse),
+    /// If, non-lazy - evaluate both branches
+    If(If),
+    /// Binary operation
+    BinOp(BinOp),
+    /// Logical AND
+    And(And),
+    /// Logical OR
+    Or(Or),
+    /// LogicalNot
+    LogicalNot(LogicalNot),
+    /// Option get method
+    OptionGet(OptionGet),
+    /// Extract register's value (box.RX properties)
+    ExtractRegisterAs(ExtractRegisterAs),
+    /// Extract box's guarding script serialized to bytes
+    ExtractScriptBytes(ExtractScriptBytes),
+    /// Collection, get element by index
+    ByIndex(ByIndex),
+    /// Collection size
+    SizeOf(SizeOf),
+    /// Collection fold op
+    Fold(Fold),
+    /// Collection map op
+    Map(Map),
+    /// Collection filter op
+    Filter(Filter),
+    /// Tuple field access
+    SelectField(SelectField),
+    /// Box monetary value
+    ExtractAmount(ExtractAmount),
+    /// Bool to SigmaProp
+    BoolToSigmaProp(BoolToSigmaProp),
+    /// Upcast numeric value
+    Upcast(Upcast),
+}
+
+impl Expr {
+    /// Code (used in serialization)
+    pub fn op_code(&self) -> OpCode {
+        match self {
+            Expr::Const(_) => panic!("constant does not have op code assigned"),
+            Expr::ConstPlaceholder(op) => op.op_code(),
+            Expr::Collection(op) => op.op_code(),
+            Expr::GlobalVars(op) => op.op_code(),
+            Expr::MethodCall(op) => op.op_code(),
+            Expr::ProperyCall(op) => op.op_code(),
+            Expr::Context => OpCode::CONTEXT,
+            Expr::OptionGet(op) => op.op_code(),
+            Expr::ExtractRegisterAs(op) => op.op_code(),
+            Expr::BinOp(op) => op.op_code(),
+            Expr::BlockValue(op) => op.op_code(),
+            Expr::ValUse(op) => op.op_code(),
+            Expr::FuncValue(op) => op.op_code(),
+            Expr::Apply(op) => op.op_code(),
+            Expr::ValDef(op) => op.op_code(),
+            Expr::ExtractAmount(op) => op.op_code(),
+            Expr::SelectField(op) => op.op_code(),
+            Expr::Fold(op) => op.op_code(),
+            Expr::CalcBlake2b256(op) => op.op_code(),
+            Expr::And(op) => op.op_code(),
+            Expr::Or(op) => op.op_code(),
+            Expr::LogicalNot(op) => op.op_code(),
+            Expr::Map(op) => op.op_code(),
+            Expr::Filter(op) => op.op_code(),
+            Expr::BoolToSigmaProp(op) => op.op_code(),
+            Expr::Upcast(op) => op.op_code(),
+            Expr::If(op) => op.op_code(),
+            Expr::ByIndex(op) => op.op_code(),
+            Expr::ExtractScriptBytes(op) => op.op_code(),
+            Expr::SizeOf(op) => op.op_code(),
+        }
+    }
+
+    /// Type of the expression
+    pub fn tpe(&self) -> SType {
+        match self {
+            Expr::Const(v) => v.tpe.clone(),
+            Expr::Collection(v) => v.tpe(),
+            Expr::ConstPlaceholder(v) => v.tpe.clone(),
+            Expr::CalcBlake2b256(v) => v.tpe(),
+            Expr::Context => SType::SContext(SContext()),
+            Expr::GlobalVars(v) => v.tpe(),
+            Expr::FuncValue(v) => v.tpe(),
+            Expr::Apply(v) => v.tpe(),
+            Expr::MethodCall(v) => v.tpe(),
+            Expr::ProperyCall(v) => v.tpe(),
+            Expr::BlockValue(v) => v.tpe(),
+            Expr::ValDef(v) => v.tpe(),
+            Expr::ValUse(v) => v.tpe.clone(),
+            Expr::BinOp(v) => v.tpe(),
+            Expr::OptionGet(v) => v.tpe(),
+            Expr::ExtractRegisterAs(v) => v.tpe(),
+            Expr::Fold(v) => v.tpe(),
+            Expr::SelectField(v) => v.tpe(),
+            Expr::ExtractAmount(v) => v.tpe(),
+            Expr::And(v) => v.tpe(),
+            Expr::Or(v) => v.tpe(),
+            Expr::LogicalNot(v) => v.tpe(),
+            Expr::Map(v) => v.tpe(),
+            Expr::Filter(v) => v.tpe(),
+            Expr::BoolToSigmaProp(v) => v.tpe(),
+            Expr::Upcast(v) => v.tpe(),
+            Expr::If(v) => v.tpe(),
+            Expr::ByIndex(v) => v.tpe(),
+            Expr::ExtractScriptBytes(v) => v.tpe(),
+            Expr::SizeOf(v) => v.tpe(),
+        }
+    }
+
+    /// Type expected after the evaluation
+    pub fn post_eval_tpe(&self) -> SType {
+        match self.tpe() {
+            SType::SFunc(sfunc) => *sfunc.t_range,
+            tpe => tpe,
+        }
+    }
+
+    /// Check if given expected_tpe type is the same as the expression's post-evaluation type
+    pub fn check_post_eval_tpe(&self, expected_tpe: SType) -> Result<(), InvalidExprEvalTypeError> {
+        let expr_tpe = self.post_eval_tpe();
+        if expr_tpe == expected_tpe {
+            Ok(())
+        } else {
+            Err(InvalidExprEvalTypeError(format!(
+                "expected: {0:?}, got: {1:?}",
+                expected_tpe, expr_tpe
+            )))
+        }
+    }
+
+    /// Prints the tree with newlines
+    pub fn debug_tree(&self) -> String {
+        let tree = format!("{:#?}", self);
+        tree
+    }
+}
+
+/// Unexpected argument on node construction (i.e non-Option input in OptionGet)
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct InvalidArgumentError(pub String);
+
+/// Invalid (unexpected) expr type
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct InvalidExprEvalTypeError(pub String);
+
+impl From<InvalidExprEvalTypeError> for InvalidArgumentError {
+    fn from(e: InvalidExprEvalTypeError) -> Self {
+        InvalidArgumentError(format!("{0:?}", e))
+    }
+}
+
+impl<T: TryFrom<Expr>> TryExtractFrom<Expr> for T {
+    fn try_extract_from(v: Expr) -> Result<Self, TryExtractFromError> {
+        let res: Result<Self, TryExtractFromError> = v.clone().try_into().map_err(|_| {
+            TryExtractFromError(format!(
+                "Cannot extract {0:?} from {1:?}",
+                std::any::type_name::<T>(),
+                v
+            ))
+        });
+        res
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary {
+    use super::*;
+    use crate::mir::func_value::FuncArg;
+    use crate::types::sfunc::SFunc;
+    use proptest::collection::*;
+    use proptest::prelude::*;
+
+    #[derive(PartialEq, Eq, Debug, Clone)]
+    pub struct ArbExprParams {
+        pub tpe: SType,
+        pub depth: usize,
+    }
+
+    impl Default for ArbExprParams {
+        fn default() -> Self {
+            ArbExprParams {
+                tpe: SType::SBoolean,
+                depth: 1,
+            }
+        }
+    }
+
+    fn int_nested_expr(depth: usize) -> BoxedStrategy<Expr> {
+        prop_oneof![any_with::<BinOp>(ArbExprParams {
+            tpe: SType::SInt,
+            depth
+        })
+        .prop_map_into(),]
+        .boxed()
+    }
+
+    fn bool_nested_expr(depth: usize) -> BoxedStrategy<Expr> {
+        prop_oneof![
+            any_with::<BinOp>(ArbExprParams {
+                tpe: SType::SBoolean,
+                depth
+            })
+            .prop_map_into(),
+            any_with::<And>(depth).prop_map_into(),
+            any_with::<Or>(depth).prop_map_into(),
+            any_with::<LogicalNot>(depth).prop_map_into(),
+        ]
+        .boxed()
+    }
+
+    fn coll_nested_expr(depth: usize, elem_tpe: &SType) -> BoxedStrategy<Expr> {
+        match elem_tpe {
+            SType::SBoolean => vec(bool_nested_expr(depth), 0..10)
+                .prop_map(|items| Collection::new(SType::SBoolean, items).unwrap())
+                .prop_map_into()
+                .boxed(),
+
+            SType::STypeVar(_) => prop_oneof![
+                vec(bool_nested_expr(depth), 0..10).prop_map(|items| Collection::new(
+                    SType::SBoolean,
+                    items
+                )
+                .unwrap()),
+                vec(int_nested_expr(depth), 0..10).prop_map(|items| Collection::new(
+                    SType::SInt,
+                    items
+                )
+                .unwrap())
+            ]
+            .prop_map_into()
+            .boxed(),
+            _ => todo!(),
+        }
+    }
+
+    fn any_nested_expr(depth: usize) -> BoxedStrategy<Expr> {
+        prop_oneof![bool_nested_expr(depth), int_nested_expr(depth)].boxed()
+    }
+
+    fn nested_expr(tpe: SType, depth: usize) -> BoxedStrategy<Expr> {
+        match tpe {
+            SType::SAny => any_nested_expr(depth),
+            SType::SBoolean => bool_nested_expr(depth),
+            SType::SInt => int_nested_expr(depth),
+            SType::SColl(elem_type) => coll_nested_expr(depth, elem_type.as_ref()),
+            _ => todo!(),
+        }
+        .boxed()
+    }
+
+    fn int_non_nested_expr() -> BoxedStrategy<Expr> {
+        prop_oneof![Just(GlobalVars::Height.into()),].boxed()
+    }
+
+    fn bool_non_nested_expr() -> BoxedStrategy<Expr> {
+        prop_oneof![any_with::<Constant>(SType::SBoolean.into()).prop_map_into()].boxed()
+    }
+
+    fn any_non_nested_expr() -> BoxedStrategy<Expr> {
+        prop_oneof![int_non_nested_expr(), bool_non_nested_expr()].boxed()
+    }
+
+    fn coll_non_nested_expr(elem_tpe: &SType) -> BoxedStrategy<Expr> {
+        match elem_tpe {
+            SType::SByte => any_with::<Constant>(SType::SColl(Box::new(SType::SByte)).into())
+                .prop_map(Expr::Const)
+                .boxed(),
+            SType::SBoolean => any_with::<Constant>(SType::SColl(Box::new(SType::SBoolean)).into())
+                .prop_map(Expr::Const)
+                .boxed(),
+            _ => todo!("Collection of {0:?} is not yet implemented", elem_tpe),
+        }
+    }
+
+    fn non_nested_expr(tpe: &SType) -> BoxedStrategy<Expr> {
+        match tpe {
+            SType::SAny => any_non_nested_expr(),
+            SType::SInt => int_non_nested_expr(),
+            SType::SBoolean => bool_non_nested_expr(),
+            SType::SColl(elem_type) => coll_non_nested_expr(elem_type),
+            _ => todo!("{0:?} is not yet implemented", tpe),
+        }
+    }
+
+    fn sfunc_expr(sfunc: SFunc) -> BoxedStrategy<Expr> {
+        match (sfunc.t_dom.first().unwrap(), *sfunc.t_range) {
+            (SType::SBoolean, SType::SBoolean) => any_with::<Expr>(ArbExprParams {
+                tpe: SType::SBoolean,
+                depth: 2,
+            })
+            .prop_map(|expr| {
+                Expr::FuncValue(FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::SBoolean,
+                    }],
+                    expr,
+                ))
+            })
+            .boxed(),
+            _ => todo!(),
+        }
+    }
+
+    impl Arbitrary for Expr {
+        type Parameters = ArbExprParams;
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
+            if args.depth == 0 {
+                match args.tpe {
+                    SType::SFunc(sfunc) => sfunc_expr(sfunc),
+                    _ => prop_oneof![
+                        any_with::<Constant>(args.tpe.clone().into())
+                            .prop_map(Expr::Const)
+                            .boxed(),
+                        non_nested_expr(&args.tpe)
+                    ]
+                    .boxed(),
+                }
+            } else {
+                nested_expr(args.tpe, args.depth - 1)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {}
diff --git a/ergotree-interpreter/src/eval/extract_amount.rs b/ergotree-interpreter/src/eval/extract_amount.rs
new file mode 100644
index 000000000..a33f88e7c
--- /dev/null
+++ b/ergotree-interpreter/src/eval/extract_amount.rs
@@ -0,0 +1,20 @@
+use ergotree_ir::mir::extract_amount::ExtractAmount;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ExtractAmount {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::CBox(b) => Ok(Value::Long(ctx.ctx.box_arena.get(&b)?.value())),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Expected ExtractAmount input to be Value::CBox, got {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/extract_reg_as.rs b/ergotree-interpreter/src/eval/extract_reg_as.rs
new file mode 100644
index 000000000..005555242
--- /dev/null
+++ b/ergotree-interpreter/src/eval/extract_reg_as.rs
@@ -0,0 +1,50 @@
+use ergotree_ir::ir_ergo_box::IrBoxId;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ExtractRegisterAs {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let ir_box_id = self.input.eval(env, ctx)?.try_extract_into::<IrBoxId>()?;
+        Ok(Value::Opt(Box::new(
+            ctx.ctx
+                .box_arena
+                .get(&ir_box_id)?
+                .get_register(self.register_id)
+                .map(|c| c.v),
+        )))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::option_get::OptionGet;
+    use ergotree_ir::types::stype::SType;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    #[test]
+    fn eval_box_get_reg() {
+        let get_reg_expr: Expr = ExtractRegisterAs::new(
+            GlobalVars::SelfBox.into(),
+            0,
+            SType::SOption(SType::SLong.into()),
+        )
+        .unwrap()
+        .into();
+        let option_get_expr: Expr = OptionGet::new(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/extract_script_bytes.rs b/ergotree-interpreter/src/eval/extract_script_bytes.rs
new file mode 100644
index 000000000..1599cd9b9
--- /dev/null
+++ b/ergotree-interpreter/src/eval/extract_script_bytes.rs
@@ -0,0 +1,44 @@
+use ergotree_ir::mir::extract_script_bytes::ExtractScriptBytes;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ExtractScriptBytes {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.script_bytes().into()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Expected ExtractScriptBytes input to be Value::CBox, got {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    #[test]
+    fn eval() {
+        let e: Expr = ExtractScriptBytes {
+            input: Box::new(GlobalVars::SelfBox.into()),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<Vec<u8>>(&e, ctx.clone()),
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().script_bytes()
+        );
+    }
+}
diff --git a/ergotree-interpreter/src/eval/func_value.rs b/ergotree-interpreter/src/eval/func_value.rs
new file mode 100644
index 000000000..70ba67e75
--- /dev/null
+++ b/ergotree-interpreter/src/eval/func_value.rs
@@ -0,0 +1,13 @@
+use ergotree_ir::mir::func_value::FuncValue;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for FuncValue {
+    fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        Ok(Value::FuncValue(self.clone()))
+    }
+}
diff --git a/ergotree-ir/src/eval/global_vars.rs b/ergotree-interpreter/src/eval/global_vars.rs
similarity index 76%
rename from ergotree-ir/src/eval/global_vars.rs
rename to ergotree-interpreter/src/eval/global_vars.rs
index 063e52d24..fa869d030 100644
--- a/ergotree-ir/src/eval/global_vars.rs
+++ b/ergotree-interpreter/src/eval/global_vars.rs
@@ -1,6 +1,6 @@
 use crate::eval::Env;
-use crate::mir::global_vars::GlobalVars;
-use crate::mir::value::Value;
+use ergotree_ir::mir::global_vars::GlobalVars;
+use ergotree_ir::mir::value::Value;
 
 use super::EvalContext;
 use super::EvalError;
@@ -21,24 +21,23 @@ impl Evaluable for GlobalVars {
 }
 
 #[cfg(test)]
-#[cfg(feature = "arbitrary")]
 mod tests {
     use std::rc::Rc;
 
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
-    use crate::ir_ergo_box::IrBoxId;
-    use crate::test_util::force_any_val;
+    use ergoscript_compiler::compiler::compile_expr;
+    use ergoscript_compiler::script_env::ScriptEnv;
+    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use test_util::force_any_val;
 
     use super::*;
 
     #[test]
     fn eval_height() {
         let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<i32>(&GlobalVars::Height.into(), ctx.clone()),
-            ctx.height
-        );
+        let expr = compile_expr("HEIGHT", ScriptEnv::new()).unwrap();
+        assert_eq!(eval_out::<i32>(&expr, ctx.clone()), ctx.height);
     }
 
     #[test]
diff --git a/ergotree-interpreter/src/eval/if_op.rs b/ergotree-interpreter/src/eval/if_op.rs
new file mode 100644
index 000000000..0795c7674
--- /dev/null
+++ b/ergotree-interpreter/src/eval/if_op.rs
@@ -0,0 +1,78 @@
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::if_op::If;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for If {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let condition_v = self.condition.eval(env, ctx)?;
+        if condition_v.try_extract_into::<bool>()? {
+            self.true_branch.eval(env, ctx)
+        } else {
+            self.false_branch.eval(env, ctx)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::bin_op::ArithOp;
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::expr::Expr;
+
+    #[test]
+    fn eval() {
+        let expr: Expr = If {
+            condition: Expr::Const(true.into()).into(),
+            true_branch: Expr::Const(1i64.into()).into(),
+            false_branch: Expr::Const(2i64.into()).into(),
+        }
+        .into();
+        let res = eval_out_wo_ctx::<i64>(&expr);
+        assert_eq!(res, 1);
+    }
+
+    #[test]
+    fn eval_laziness_true_branch() {
+        let expr: Expr = If {
+            condition: Expr::Const(true.into()).into(),
+            true_branch: Expr::Const(1i64.into()).into(),
+            false_branch: Box::new(
+                BinOp {
+                    kind: ArithOp::Divide.into(),
+                    left: Box::new(Expr::Const(1i64.into())),
+                    right: Box::new(Expr::Const(0i64.into())),
+                }
+                .into(),
+            ),
+        }
+        .into();
+        let res = eval_out_wo_ctx::<i64>(&expr);
+        assert_eq!(res, 1);
+    }
+
+    #[test]
+    fn eval_laziness_false_branch() {
+        let expr: Expr = If {
+            condition: Expr::Const(false.into()).into(),
+            true_branch: Box::new(
+                BinOp {
+                    kind: ArithOp::Divide.into(),
+                    left: Box::new(Expr::Const(1i64.into())),
+                    right: Box::new(Expr::Const(0i64.into())),
+                }
+                .into(),
+            ),
+            false_branch: Expr::Const(1i64.into()).into(),
+        }
+        .into();
+        let res = eval_out_wo_ctx::<i64>(&expr);
+        assert_eq!(res, 1);
+    }
+}
diff --git a/ergotree-interpreter/src/eval/logical_not.rs b/ergotree-interpreter/src/eval/logical_not.rs
new file mode 100644
index 000000000..8f9956dac
--- /dev/null
+++ b/ergotree-interpreter/src/eval/logical_not.rs
@@ -0,0 +1,38 @@
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::logical_not::LogicalNot;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for LogicalNot {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let input_v_bool = input_v.try_extract_into::<bool>()?;
+        Ok((!input_v_bool).into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::expr::Expr;
+
+    fn check(input: bool) -> bool {
+        let expr: Expr = LogicalNot {
+            input: Expr::Const(input.into()).into(),
+        }
+        .into();
+        eval_out_wo_ctx::<bool>(&expr)
+    }
+
+    #[test]
+    fn eval() {
+        assert_eq!(check(true), false);
+        assert_eq!(check(false), true);
+    }
+}
diff --git a/ergotree-ir/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs
similarity index 71%
rename from ergotree-ir/src/eval/method_call.rs
rename to ergotree-interpreter/src/eval/method_call.rs
index c1f3f0500..1d0d838f7 100644
--- a/ergotree-ir/src/eval/method_call.rs
+++ b/ergotree-interpreter/src/eval/method_call.rs
@@ -1,6 +1,7 @@
-use crate::mir::method_call::MethodCall;
-use crate::mir::value::Value;
+use ergotree_ir::mir::method_call::MethodCall;
+use ergotree_ir::mir::value::Value;
 
+use super::smethod_eval_fn;
 use super::Env;
 use super::EvalContext;
 use super::EvalError;
@@ -11,7 +12,7 @@ impl Evaluable for MethodCall {
         let ov = self.obj.eval(env, ectx)?;
         let argsv: Result<Vec<Value>, EvalError> =
             self.args.iter().map(|arg| arg.eval(env, ectx)).collect();
-        self.method.eval_fn()(ectx.ctx.clone(), ov, argsv?)
+        smethod_eval_fn(&self.method)(ectx.ctx.clone(), ov, argsv?)
     }
 }
 
@@ -20,14 +21,15 @@ impl Evaluable for MethodCall {
 mod tests {
     use std::rc::Rc;
 
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::option_get::OptionGet;
+    use ergotree_ir::types::sbox;
+    use test_util::force_any_val;
+
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
-    use crate::mir::constant::Constant;
-    use crate::mir::expr::Expr;
-    use crate::mir::global_vars::GlobalVars;
-    use crate::mir::option_get::OptionGet;
-    use crate::test_util::force_any_val;
-    use crate::types::sbox;
 
     use super::*;
 
diff --git a/ergotree-interpreter/src/eval/option_get.rs b/ergotree-interpreter/src/eval/option_get.rs
new file mode 100644
index 000000000..274872931
--- /dev/null
+++ b/ergotree-interpreter/src/eval/option_get.rs
@@ -0,0 +1,53 @@
+use ergotree_ir::mir::option_get::OptionGet;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for OptionGet {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let v = self.input.eval(env, ctx)?;
+        match v {
+            Value::Opt(opt_v) => {
+                opt_v.ok_or_else(|| EvalError::NotFound("calling Option.get on None".to_string()))
+            }
+            _ => Err(EvalError::UnexpectedExpr(format!(
+                "Don't know how to eval OptM: {0:?}",
+                self
+            ))),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::OptionGet;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::extract_reg_as::ExtractRegisterAs;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::types::stype::SType;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    #[test]
+    fn eval_get() {
+        let get_reg_expr: Expr = ExtractRegisterAs::new(
+            GlobalVars::SelfBox.into(),
+            0,
+            SType::SOption(SType::SLong.into()),
+        )
+        .unwrap()
+        .into();
+        let option_get_expr: Expr = OptionGet {
+            input: Box::new(get_reg_expr),
+        }
+        .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/or.rs b/ergotree-interpreter/src/eval/or.rs
new file mode 100644
index 000000000..887055477
--- /dev/null
+++ b/ergotree-interpreter/src/eval/or.rs
@@ -0,0 +1,39 @@
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::or::Or;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for Or {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        let input_v_bools = input_v.try_extract_into::<Vec<bool>>()?;
+        Ok(input_v_bools.iter().any(|b| *b).into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use ergotree_ir::mir::expr::Expr;
+    use proptest::collection;
+    use proptest::prelude::*;
+    use std::rc::Rc;
+    use test_util::force_any_val;
+
+    proptest! {
+
+        #[test]
+        fn eval(bools in collection::vec(any::<bool>(), 0..10)) {
+            let expr: Expr = Or {input: Expr::Const(bools.clone().into()).into()}.into();
+            let ctx = Rc::new(force_any_val::<Context>());
+            let res = eval_out::<bool>(&expr, ctx);
+            prop_assert_eq!(res, bools.iter().any(|b| *b));
+        }
+    }
+}
diff --git a/ergotree-ir/src/eval/property_call.rs b/ergotree-interpreter/src/eval/property_call.rs
similarity index 69%
rename from ergotree-ir/src/eval/property_call.rs
rename to ergotree-interpreter/src/eval/property_call.rs
index 556179717..d65b0968e 100644
--- a/ergotree-ir/src/eval/property_call.rs
+++ b/ergotree-interpreter/src/eval/property_call.rs
@@ -1,6 +1,7 @@
-use crate::mir::property_call::PropertyCall;
-use crate::mir::value::Value;
+use ergotree_ir::mir::property_call::PropertyCall;
+use ergotree_ir::mir::value::Value;
 
+use super::smethod_eval_fn;
 use super::Env;
 use super::EvalContext;
 use super::EvalError;
@@ -9,23 +10,20 @@ use super::Evaluable;
 impl Evaluable for PropertyCall {
     fn eval(&self, env: &Env, ectx: &mut EvalContext) -> Result<Value, EvalError> {
         let ov = self.obj.eval(env, ectx)?;
-        self.method.eval_fn()(ectx.ctx.clone(), ov, vec![])
+        smethod_eval_fn(&self.method)(ectx.ctx.clone(), ov, vec![])
     }
 }
 
 #[cfg(test)]
-#[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
+    use super::*;
     use crate::eval::context::Context;
     use crate::eval::tests::eval_out;
-    use crate::ir_ergo_box::IrBoxId;
-    use crate::mir::expr::Expr;
-    use crate::test_util::force_any_val;
-    use crate::types::scontext;
-
-    use super::*;
+    use ergotree_ir::ir_ergo_box::IrBoxId;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::types::scontext;
+    use std::rc::Rc;
+    use test_util::force_any_val;
 
     #[test]
     fn eval_context_data_inputs() {
diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs
new file mode 100644
index 000000000..0c0c45d3d
--- /dev/null
+++ b/ergotree-interpreter/src/eval/sbox.rs
@@ -0,0 +1,80 @@
+use crate::eval::EvalError;
+
+use ergotree_ir::ir_ergo_box::IrBoxId;
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::Value;
+
+use super::EvalFn;
+
+pub static VALUE_EVAL_FN: EvalFn = |ctx, obj, _args| {
+    Ok(Value::Long(
+        obj.try_extract_into::<IrBoxId>()?
+            .get_box(&ctx.box_arena)?
+            .value(),
+    ))
+};
+
+pub static GET_REG_EVAL_FN: EvalFn = |ctx, obj, args| {
+    Ok(Value::Opt(Box::new(
+        obj.try_extract_into::<IrBoxId>()?
+            .get_box(&ctx.box_arena)?
+            .get_register(
+                args.get(0)
+                    .cloned()
+                    .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
+                    .try_extract_into::<i8>()?,
+            )
+            .map(|c| c.v),
+    )))
+};
+
+pub static TOKENS_EVAL_FN: EvalFn = |ctx, obj, _args| {
+    let res: Value = obj
+        .try_extract_into::<IrBoxId>()?
+        .get_box(&ctx.box_arena)?
+        .tokens()
+        .into();
+    Ok(res)
+};
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+    use ergotree_ir::mir::property_call::PropertyCall;
+    use ergotree_ir::types::sbox;
+    use test_util::force_any_val;
+
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use std::rc::Rc;
+
+    #[test]
+    fn eval_box_value() {
+        let expr: Expr = PropertyCall {
+            obj: Box::new(GlobalVars::SelfBox.into()),
+            method: sbox::VALUE_METHOD.clone(),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<i64>(&expr, ctx.clone()),
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
+        );
+    }
+
+    #[test]
+    fn eval_box_tokens() {
+        let expr: Expr = PropertyCall {
+            obj: Box::new(GlobalVars::SelfBox.into()),
+            method: sbox::TOKENS_METHOD.clone(),
+        }
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<Vec<(Vec<i8>, i64)>>(&expr, ctx.clone()),
+            ctx.self_box.get_box(&ctx.box_arena).unwrap().tokens()
+        );
+    }
+}
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
new file mode 100644
index 000000000..bdb75ef1b
--- /dev/null
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -0,0 +1,18 @@
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::stype::SType;
+
+use super::EvalFn;
+
+pub static DATA_INPUTS_EVAL_FN: EvalFn = |ctx, _obj, _args| {
+    // TODO: check that obj is Value::Context
+    Ok(Value::Coll(CollKind::WrappedColl {
+        items: ctx
+            .data_inputs
+            .clone()
+            .into_iter()
+            .map(Value::CBox)
+            .collect(),
+        elem_tpe: SType::SBox,
+    }))
+};
diff --git a/ergotree-interpreter/src/eval/select_field.rs b/ergotree-interpreter/src/eval/select_field.rs
new file mode 100644
index 000000000..0b42769b3
--- /dev/null
+++ b/ergotree-interpreter/src/eval/select_field.rs
@@ -0,0 +1,25 @@
+use ergotree_ir::mir::select_field::SelectField;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for SelectField {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::Tup(items) => items.get(self.field_index).cloned().ok_or_else(|| {
+                EvalError::NotFound(format!(
+                    "SelectField field index is out of bounds. Index: {0:?}, tuple: {1:?}",
+                    self.field_index, items
+                ))
+            }),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected SelectField input to be Value::Tup, got: {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/upcast.rs b/ergotree-interpreter/src/eval/upcast.rs
new file mode 100644
index 000000000..454fbacb9
--- /dev/null
+++ b/ergotree-interpreter/src/eval/upcast.rs
@@ -0,0 +1,71 @@
+use ergotree_ir::mir::upcast::Upcast;
+use ergotree_ir::mir::value::Value;
+use ergotree_ir::types::stype::SType;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+fn upcast_to_long(in_v: Value) -> Result<Value, EvalError> {
+    match in_v {
+        Value::Byte(v) => Ok((v as i64).into()),
+        Value::Short(v) => Ok((v as i64).into()),
+        Value::Int(v) => Ok((v as i64).into()),
+        Value::Long(_) => Ok(in_v),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "Upcast: cannot upcast {0:?} to Long",
+            in_v
+        ))),
+    }
+}
+
+fn upcast_to_int(in_v: Value) -> Result<Value, EvalError> {
+    match in_v {
+        Value::Byte(v) => Ok((v as i32).into()),
+        Value::Short(v) => Ok((v as i32).into()),
+        Value::Int(_) => Ok(in_v),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "Upcast: cannot upcast {0:?} to Int",
+            in_v
+        ))),
+    }
+}
+
+fn upcast_to_short(in_v: Value) -> Result<Value, EvalError> {
+    match in_v {
+        Value::Byte(v) => Ok((v as i16).into()),
+        Value::Short(_) => Ok(in_v),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "Upcast: cannot upcast {0:?} to Short",
+            in_v
+        ))),
+    }
+}
+
+fn upcast_to_byte(in_v: Value) -> Result<Value, EvalError> {
+    match in_v {
+        Value::Byte(_) => Ok(in_v),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "Upcast: cannot upcast {0:?} to Byte",
+            in_v
+        ))),
+    }
+}
+
+impl Evaluable for Upcast {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match self.tpe {
+            SType::SBigInt => todo!("upcasting to BigInt is not yet supported"),
+            SType::SLong => upcast_to_long(input_v),
+            SType::SInt => upcast_to_int(input_v),
+            SType::SShort => upcast_to_short(input_v),
+            SType::SByte => upcast_to_byte(input_v),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Upcast: expected numeric value, got {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
diff --git a/ergotree-interpreter/src/eval/val_use.rs b/ergotree-interpreter/src/eval/val_use.rs
new file mode 100644
index 000000000..fa647df5a
--- /dev/null
+++ b/ergotree-interpreter/src/eval/val_use.rs
@@ -0,0 +1,15 @@
+use ergotree_ir::mir::val_use::ValUse;
+use ergotree_ir::mir::value::Value;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+impl Evaluable for ValUse {
+    fn eval(&self, env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        env.get(self.val_id).cloned().ok_or_else(|| {
+            EvalError::NotFound(format!("no value in env for id: {0:?}", self.val_id))
+        })
+    }
+}
diff --git a/ergotree-interpreter/src/lib.rs b/ergotree-interpreter/src/lib.rs
new file mode 100644
index 000000000..1f271f90d
--- /dev/null
+++ b/ergotree-interpreter/src/lib.rs
@@ -0,0 +1,19 @@
+// Coding conventions
+#![forbid(unsafe_code)]
+#![deny(non_upper_case_globals)]
+#![deny(non_camel_case_types)]
+#![deny(non_snake_case)]
+#![deny(unused_mut)]
+#![deny(dead_code)]
+#![deny(unused_imports)]
+// TODO: add docs and enable
+// #![deny(missing_docs)]
+// Clippy exclusions
+#![allow(clippy::unit_arg)]
+#![deny(broken_intra_doc_links)]
+
+mod contracts;
+mod util;
+
+pub mod eval;
+pub mod sigma_protocol;
diff --git a/ergotree-interpreter/src/sigma_protocol.rs b/ergotree-interpreter/src/sigma_protocol.rs
new file mode 100644
index 000000000..2b4534401
--- /dev/null
+++ b/ergotree-interpreter/src/sigma_protocol.rs
@@ -0,0 +1,134 @@
+//! Sigma protocols
+
+pub mod private_input;
+pub mod prover;
+pub mod verifier;
+
+mod challenge;
+mod dlog_protocol;
+mod fiat_shamir;
+mod sig_serializer;
+mod unchecked_tree;
+mod unproven_tree;
+
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+use k256::Scalar;
+
+use dlog_protocol::FirstDlogProverMessage;
+use std::convert::TryInto;
+use unchecked_tree::{UncheckedSigmaTree, UncheckedTree};
+use unproven_tree::{UnprovenLeaf, UnprovenSchnorr, UnprovenTree};
+
+use self::challenge::Challenge;
+
+/** The message sent by a prover to its associated verifier as part of a sigma protocol interaction. */
+pub trait ProverMessage {
+    /// serialized message
+    fn bytes(&self) -> Vec<u8>;
+}
+
+/** First message from the prover (message `a` of `SigmaProtocol`)*/
+pub enum FirstProverMessage {
+    /// Discrete log
+    FirstDlogProverMessage(FirstDlogProverMessage),
+    /// DH tupl
+    FirstDHTProverMessage,
+}
+
+impl ProverMessage for FirstProverMessage {
+    fn bytes(&self) -> Vec<u8> {
+        match self {
+            FirstProverMessage::FirstDlogProverMessage(fdpm) => fdpm.bytes(),
+            FirstProverMessage::FirstDHTProverMessage => todo!(),
+        }
+    }
+}
+
+/// Proof tree
+pub enum ProofTree {
+    /// Unchecked tree
+    UncheckedTree(UncheckedTree),
+    /// Unproven tree
+    UnprovenTree(UnprovenTree),
+}
+
+impl ProofTree {
+    /// Create a new proof tree with a new challenge
+    pub fn with_challenge(&self, challenge: Challenge) -> ProofTree {
+        match self {
+            ProofTree::UncheckedTree(_) => todo!(),
+            ProofTree::UnprovenTree(ut) => match ut {
+                UnprovenTree::UnprovenLeaf(ul) => match ul {
+                    UnprovenLeaf::UnprovenSchnorr(us) => ProofTree::UnprovenTree(
+                        UnprovenSchnorr {
+                            challenge_opt: Some(challenge),
+                            ..us.clone()
+                        }
+                        .into(),
+                    ),
+                },
+            },
+        }
+    }
+}
+
+impl<T: Into<UncheckedTree>> From<T> for ProofTree {
+    fn from(t: T) -> Self {
+        ProofTree::UncheckedTree(t.into())
+    }
+}
+
+/// Proof tree leaf
+pub trait ProofTreeLeaf {
+    /// Get proposition
+    fn proposition(&self) -> SigmaBoolean;
+
+    /// Get commitment
+    fn commitment_opt(&self) -> Option<FirstProverMessage>;
+}
+
+/** Size of the binary representation of any group element (2 ^ groupSizeBits == <number of elements in a group>) */
+pub const GROUP_SIZE_BITS: usize = 256;
+/** Number of bytes to represent any group element as byte array */
+pub const GROUP_SIZE: usize = GROUP_SIZE_BITS / 8;
+
+/// Byte array of Group size (32 bytes)
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct GroupSizedBytes(pub Box<[u8; GROUP_SIZE]>);
+
+impl From<GroupSizedBytes> for Scalar {
+    fn from(b: GroupSizedBytes) -> Self {
+        let sl: &[u8] = b.0.as_ref();
+        Scalar::from_bytes_reduced(sl.try_into().expect(""))
+    }
+}
+
+impl From<&[u8; GROUP_SIZE]> for GroupSizedBytes {
+    fn from(b: &[u8; GROUP_SIZE]) -> Self {
+        GroupSizedBytes(Box::new(*b))
+    }
+}
+
+/** A size of challenge in Sigma protocols, in bits.
+ * If this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others.
+ * We get the challenge by reducing hash function output to proper value.
+ */
+pub const SOUNDNESS_BITS: usize = 192;
+/// A size of challenge in Sigma protocols, in bytes
+pub const SOUNDNESS_BYTES: usize = SOUNDNESS_BITS / 8;
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+
+    #[allow(clippy::assertions_on_constants)]
+    #[test]
+    fn ensure_soundness_bits() {
+        // see SOUNDNESS_BITS doc comment
+        assert!(SOUNDNESS_BITS < GROUP_SIZE_BITS);
+        // blake2b hash function requirements
+        assert!(SOUNDNESS_BYTES * 8 <= 512);
+        assert!(SOUNDNESS_BYTES % 8 == 0);
+    }
+}
diff --git a/ergotree-ir/src/sigma_protocol/challenge.rs b/ergotree-interpreter/src/sigma_protocol/challenge.rs
similarity index 100%
rename from ergotree-ir/src/sigma_protocol/challenge.rs
rename to ergotree-interpreter/src/sigma_protocol/challenge.rs
diff --git a/ergotree-ir/src/sigma_protocol/dlog_protocol.rs b/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
similarity index 90%
rename from ergotree-ir/src/sigma_protocol/dlog_protocol.rs
rename to ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
index a3989db62..89bd997dc 100644
--- a/ergotree-ir/src/sigma_protocol/dlog_protocol.rs
+++ b/ergotree-interpreter/src/sigma_protocol/dlog_protocol.rs
@@ -1,7 +1,8 @@
 //! Discrete logarithm signature protocol
 
-use super::{dlog_group::EcPoint, FirstProverMessage, ProverMessage};
-use crate::serialization::SigmaSerializable;
+use super::{FirstProverMessage, ProverMessage};
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::dlog_group::EcPoint;
 use k256::Scalar;
 
 /// First message from the prover (message `a` of `SigmaProtocol`) for discrete logarithm case
@@ -42,8 +43,10 @@ impl From<Scalar> for SecondDlogProverMessage {
 /// Interactive prover
 pub mod interactive_prover {
     use super::{FirstDlogProverMessage, SecondDlogProverMessage};
-    use crate::sigma_protocol::{dlog_group, private_input::DlogProverInput, Challenge, ProveDlog};
-    use dlog_group::EcPoint;
+    use crate::sigma_protocol::{private_input::DlogProverInput, Challenge};
+    use ergotree_ir::sigma_protocol::dlog_group;
+    use ergotree_ir::sigma_protocol::dlog_group::EcPoint;
+    use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
     use k256::Scalar;
 
     /// TBD
diff --git a/ergotree-ir/src/sigma_protocol/fiat_shamir.rs b/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
similarity index 95%
rename from ergotree-ir/src/sigma_protocol/fiat_shamir.rs
rename to ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
index a56d78caa..03bacfa9f 100644
--- a/ergotree-ir/src/sigma_protocol/fiat_shamir.rs
+++ b/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
@@ -1,14 +1,16 @@
 //! Fiat-Shamir transformation
 
 use super::{
-    sigma_boolean::SigmaProp,
     unchecked_tree::{UncheckedSigmaTree, UncheckedTree},
     unproven_tree::UnprovenTree,
     ProofTree, ProofTreeLeaf, ProverMessage, GROUP_SIZE, SOUNDNESS_BYTES,
 };
-use crate::{ergo_tree::ErgoTree, mir::expr::Expr, serialization::SigmaSerializable};
 use blake2::digest::{Update, VariableOutput};
 use blake2::VarBlake2b;
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::mir::expr::Expr;
+use ergotree_ir::serialization::SigmaSerializable;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProp;
 use std::convert::{TryFrom, TryInto};
 use thiserror::Error;
 
diff --git a/ergotree-ir/src/sigma_protocol/private_input.rs b/ergotree-interpreter/src/sigma_protocol/private_input.rs
similarity index 95%
rename from ergotree-ir/src/sigma_protocol/private_input.rs
rename to ergotree-interpreter/src/sigma_protocol/private_input.rs
index 874092dee..022705376 100644
--- a/ergotree-ir/src/sigma_protocol/private_input.rs
+++ b/ergotree-interpreter/src/sigma_protocol/private_input.rs
@@ -1,9 +1,11 @@
 //! Private input types for the prover's secrets
-use super::{dlog_group, ProveDlog};
-use crate::util::IntoOption;
 use elliptic_curve::FromBytes;
+use ergotree_ir::sigma_protocol::dlog_group;
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
 use k256::Scalar;
 
+use crate::util::IntoOption;
+
 /// Secret key of discrete logarithm signature protocol
 #[derive(PartialEq, Debug, Clone)]
 pub struct DlogProverInput {
diff --git a/ergotree-ir/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
similarity index 96%
rename from ergotree-ir/src/sigma_protocol/prover.rs
rename to ergotree-interpreter/src/sigma_protocol/prover.rs
index a07864782..ba190c545 100644
--- a/ergotree-ir/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -6,6 +6,9 @@ mod prover_result;
 use std::rc::Rc;
 
 pub use context_extension::*;
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::ergo_tree::ErgoTreeParsingError;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 pub use prover_result::*;
 
 use super::{
@@ -14,10 +17,9 @@ use super::{
     private_input::PrivateInput,
     sig_serializer::serialize_sig,
     unchecked_tree::UncheckedSchnorr,
-    Challenge, ProofTree, SigmaBoolean, SigmaProofOfKnowledgeTree, UncheckedSigmaTree,
-    UncheckedTree, UnprovenLeaf, UnprovenSchnorr, UnprovenTree,
+    Challenge, ProofTree, SigmaBoolean, UncheckedSigmaTree, UncheckedTree, UnprovenLeaf,
+    UnprovenSchnorr, UnprovenTree,
 };
-use crate::ergo_tree::{ErgoTree, ErgoTreeParsingError};
 use crate::eval::context::Context;
 use crate::eval::env::Env;
 use crate::eval::{EvalError, Evaluator};
@@ -301,16 +303,15 @@ impl Prover for TestProver {
 }
 
 #[cfg(test)]
-#[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::mir::constant::Constant;
-    use crate::mir::expr::Expr;
-    use crate::mir::value::Value;
     use crate::sigma_protocol::private_input::DlogProverInput;
-    use crate::test_util::force_any_val;
-    use crate::types::stype::SType;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::value::Value;
+    use ergotree_ir::types::stype::SType;
     use std::rc::Rc;
+    use test_util::force_any_val;
 
     #[test]
     fn test_prove_true_prop() {
diff --git a/ergotree-ir/src/sigma_protocol/prover/context_extension.rs b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
similarity index 93%
rename from ergotree-ir/src/sigma_protocol/prover/context_extension.rs
rename to ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
index 66c6ce284..3b4fdada7 100644
--- a/ergotree-ir/src/sigma_protocol/prover/context_extension.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
@@ -1,9 +1,9 @@
 //! ContextExtension type
-use crate::mir::constant::Constant;
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
-    SigmaSerializable,
-};
+use ergotree_ir::mir::constant::Constant;
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 use indexmap::IndexMap;
 use std::collections::HashMap;
 use std::{convert::TryFrom, io};
@@ -134,7 +134,7 @@ mod arbitrary {
 #[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::serialization::sigma_serialize_roundtrip;
+    use ergotree_ir::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
diff --git a/ergotree-ir/src/sigma_protocol/prover/prover_result.rs b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
similarity index 92%
rename from ergotree-ir/src/sigma_protocol/prover/prover_result.rs
rename to ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
index 060d752d2..c91b5872e 100644
--- a/ergotree-ir/src/sigma_protocol/prover/prover_result.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
@@ -1,14 +1,12 @@
 //! ProverResult
-use std::convert::TryFrom;
-use std::io;
-
-// use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
-use crate::serialization::{
-    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SerializationError,
-    SigmaSerializable,
-};
+use ergotree_ir::serialization::sigma_byte_reader::SigmaByteRead;
+use ergotree_ir::serialization::sigma_byte_writer::SigmaByteWrite;
+use ergotree_ir::serialization::SerializationError;
+use ergotree_ir::serialization::SigmaSerializable;
 
 use super::ContextExtension;
+use std::convert::TryFrom;
+use std::io;
 
 /// Serialized proof generated by ['Prover']
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
diff --git a/ergotree-ir/src/sigma_protocol/sig_serializer.rs b/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
similarity index 94%
rename from ergotree-ir/src/sigma_protocol/sig_serializer.rs
rename to ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
index 1f3f726d0..a0dee2e75 100644
--- a/ergotree-ir/src/sigma_protocol/sig_serializer.rs
+++ b/ergotree-interpreter/src/sigma_protocol/sig_serializer.rs
@@ -4,10 +4,10 @@ use super::prover::ProofBytes;
 use super::{
     fiat_shamir::FiatShamirHash,
     unchecked_tree::{UncheckedLeaf, UncheckedSchnorr},
-    Challenge, GroupSizedBytes, SigmaBoolean, SigmaProofOfKnowledgeTree, UncheckedSigmaTree,
-    UncheckedTree,
+    Challenge, GroupSizedBytes, SigmaBoolean, UncheckedSigmaTree, UncheckedTree,
 };
 
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 use k256::Scalar;
 use std::convert::{TryFrom, TryInto};
 
diff --git a/ergotree-ir/src/sigma_protocol/unchecked_tree.rs b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
similarity index 92%
rename from ergotree-ir/src/sigma_protocol/unchecked_tree.rs
rename to ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
index 55ba05854..14990c35e 100644
--- a/ergotree-ir/src/sigma_protocol/unchecked_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/unchecked_tree.rs
@@ -1,8 +1,11 @@
 //! Unchecked proof tree types
 
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
+
 use super::{
     dlog_protocol::{FirstDlogProverMessage, SecondDlogProverMessage},
-    sigma_boolean::{ProveDlog, SigmaBoolean, SigmaProofOfKnowledgeTree},
     Challenge, FirstProverMessage, ProofTree, ProofTreeLeaf,
 };
 
diff --git a/ergotree-ir/src/sigma_protocol/unproven_tree.rs b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
similarity index 84%
rename from ergotree-ir/src/sigma_protocol/unproven_tree.rs
rename to ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
index 5ce932ab9..5cdd52550 100644
--- a/ergotree-ir/src/sigma_protocol/unproven_tree.rs
+++ b/ergotree-interpreter/src/sigma_protocol/unproven_tree.rs
@@ -1,10 +1,9 @@
 //! Unproven tree types
 
-use super::{
-    dlog_protocol::FirstDlogProverMessage,
-    sigma_boolean::{ProveDlog, SigmaBoolean, SigmaProofOfKnowledgeTree},
-    Challenge, FirstProverMessage, ProofTreeLeaf,
-};
+use super::{dlog_protocol::FirstDlogProverMessage, Challenge, FirstProverMessage, ProofTreeLeaf};
+use ergotree_ir::sigma_protocol::sigma_boolean::ProveDlog;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaBoolean;
+use ergotree_ir::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 use k256::Scalar;
 
 /// Unproven trees
diff --git a/ergotree-ir/src/sigma_protocol/verifier.rs b/ergotree-interpreter/src/sigma_protocol/verifier.rs
similarity index 55%
rename from ergotree-ir/src/sigma_protocol/verifier.rs
rename to ergotree-interpreter/src/sigma_protocol/verifier.rs
index f4844f9e4..ba01d76bb 100644
--- a/ergotree-ir/src/sigma_protocol/verifier.rs
+++ b/ergotree-interpreter/src/sigma_protocol/verifier.rs
@@ -10,11 +10,12 @@ use super::{
     unchecked_tree::{UncheckedLeaf, UncheckedSchnorr},
     SigmaBoolean, UncheckedSigmaTree, UncheckedTree,
 };
-use crate::ergo_tree::{ErgoTree, ErgoTreeParsingError};
 use crate::eval::context::Context;
 use crate::eval::env::Env;
 use crate::eval::{EvalError, Evaluator};
 use dlog_protocol::FirstDlogProverMessage;
+use ergotree_ir::ergo_tree::ErgoTree;
+use ergotree_ir::ergo_tree::ErgoTreeParsingError;
 
 /// Errors on proof verification
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -123,14 +124,14 @@ impl Verifier for TestVerifier {}
 #[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::mir::expr::Expr;
     use crate::sigma_protocol::{
         private_input::{DlogProverInput, PrivateInput},
         prover::{Prover, TestProver},
     };
-    use crate::test_util::force_any_val;
+    use ergotree_ir::mir::expr::Expr;
     use proptest::prelude::*;
     use std::rc::Rc;
+    use test_util::force_any_val;
 
     proptest! {
 
@@ -153,105 +154,4 @@ mod tests {
             prop_assert_eq!(ver_res.unwrap().result, true);
         }
     }
-
-    #[test]
-    #[cfg(feature = "json")]
-    fn test_proof_from_mainnet() {
-        use crate::chain::address::{AddressEncoder, NetworkPrefix};
-        use crate::chain::transaction::Transaction;
-
-        let tx_json = r#"
-         {
-      "id": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
-      "inputs": [
-        {
-          "boxId": "f353ae1b2027e40ea318e7a2673ea4bbaa281b7acee518a0994c5cbdefb05f55",
-          "spendingProof": {
-            "proofBytes":"",
-            "extension": {}
-          }
-        },
-        {
-          "boxId": "56111b039b86f71004b768d2e8b4579f1d79e28e7a617fd5add57a5239498c26",
-          "spendingProof": {
-            "proofBytes": "6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48",
-            "extension": {}
-          }
-        }
-      ],
-      "dataInputs": [
-        {
-          "boxId": "e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece"
-        }
-      ],
-      "outputs": [
-        {
-          "boxId": "55be517150fcb7f0f1661ad3ab30f1ac62084b83ad6aa772579bc06cbb52832e",
-          "value": 1000000,
-          "ergoTree": "100604000400050004000e20b662db51cf2dc39f110a021c2a31c74f0a1a18ffffbf73e8a051a7b8c0f09ebc0e2079974b2314c531e62776e6bc4babff35b37b178cebf0976fc0f416ff34ddbc4fd803d601b2a5730000d602e4c6a70407d603b2db6501fe730100ea02d1ededededed93e4c672010407720293e4c67201050ec5720391e4c672010605730293c27201c2a793db63087201db6308a7ed938cb2db6308720373030001730493cbc272037305cd7202",
-          "assets": [
-            {
-              "tokenId": "12caaacb51c89646fac9a3786eb98d0113bd57d68223ccc11754a4f67281daed",
-              "amount": 1
-            }
-          ],
-          "creationHeight": 299218,
-          "additionalRegisters": {
-            "R4": "070327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
-            "R5": "0e20e26d41ed030a30cd563681e72f0b9c07825ac983f8c253a87a43c1da21958ece",
-            "R6": "05feaff5de0f"
-          },
-          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
-          "index": 0
-        },
-        {
-          "boxId": "fa4a484c855d32a60987a4ddcf1c506aa6bab1c4cb0293c2d5ff35fcd11f2c7b",
-          "value": 1000000,
-          "ergoTree": "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304",
-          "assets": [],
-          "creationHeight": 299218,
-          "additionalRegisters": {},
-          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
-          "index": 1
-        },
-        {
-          "boxId": "3dee27d0dfb193fd6a263cf2b5b58cab99cb640d1443cd1ce63d909ad3a54197",
-          "value": 44516500000,
-          "ergoTree": "0008cd0327e65711a59378c59359c3e1d0f7abe906479eccb76094e50fe79d743ccc15e6",
-          "assets": [],
-          "creationHeight": 299218,
-          "additionalRegisters": {},
-          "transactionId": "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
-          "index": 2
-        }
-      ],
-      "size": 673
-    }
-        "#;
-
-        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
-        let decoded_addr = encoder
-            .parse_address_from_str("9gmNsqrqdSppLUBqg2UzREmmivgqh1r3jmNcLAc53hk3YCvAGWE")
-            .unwrap();
-
-        let ergo_tree = decoded_addr.script().unwrap();
-
-        // let spending_proof_input1 = Base16DecodedBytes::try_from("6542a8b8914b103dcbc36d77da3bd58e42ca35755a5190b507764b0bae330b924ce86acfa1b5f9bfc8216c3c4628738e8274d902bea06b48".to_string()).unwrap();
-        let tx: Transaction = serde_json::from_str(tx_json).unwrap();
-        let tx_id_str: String = tx.id().into();
-        assert_eq!(
-            "0e6acf3f18b95bdc5bb1b060baa1eafe53bd89fb08b0e86d6cc00fbdd9e43189",
-            tx_id_str
-        );
-        let message = tx.bytes_to_sign();
-        let verifier = TestVerifier;
-        let ver_res = verifier.verify(
-            &ergo_tree,
-            &Env::empty(),
-            Rc::new(Context::dummy()),
-            &tx.inputs.get(1).unwrap().spending_proof.proof,
-            message.as_slice(),
-        );
-        assert_eq!(ver_res.unwrap().result, true);
-    }
 }
diff --git a/ergotree-interpreter/src/util.rs b/ergotree-interpreter/src/util.rs
new file mode 100644
index 000000000..21102c911
--- /dev/null
+++ b/ergotree-interpreter/src/util.rs
@@ -0,0 +1,19 @@
+//! Utilities
+
+use elliptic_curve::subtle::CtOption;
+
+/// Convert to Option<T>
+pub(crate) trait IntoOption<T> {
+    /// Get Option<T>
+    fn into_option(self) -> Option<T>;
+}
+
+impl<T> IntoOption<T> for CtOption<T> {
+    fn into_option(self) -> Option<T> {
+        if self.is_some().into() {
+            Some(self.unwrap())
+        } else {
+            None
+        }
+    }
+}
diff --git a/ergotree-ir/Cargo.toml b/ergotree-ir/Cargo.toml
index ca1fbf342..5b5cc4ee5 100644
--- a/ergotree-ir/Cargo.toml
+++ b/ergotree-ir/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.5.1"
 license = "CC0-1.0"
 authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
 edition = "2018"
-description = "ErgoTree IR, serialization, interpreter"
+description = "ErgoTree IR, serialization"
 repository = "https://github.com/ergoplatform/sigma-rust"
 exclude = [
     "proptest-regressions/*"
@@ -18,10 +18,10 @@ sigma-ser = { version = "^0.2.0", path = "../sigma-ser" }
 sigma-util = { version = "^0.1.0", path = "../sigma-util" }
 indexmap = "1.3.2"
 k256 = { version = "0.4.2", features = ["rand", "zeroize"] }
-blake2 = "0.9"
+# blake2 = "0.9"
 elliptic-curve = {version = "0.5.0", features = ["rand", "zeroize"]}
 thiserror = "1"
-num-bigint = "0.3.0"
+# num-bigint = "0.3.0"
 rand = "0.7"
 lazy_static = "1.4"
 derive_more = "0.99"
@@ -38,4 +38,5 @@ arbitrary = ["proptest", "proptest-derive"]
 
 [dev-dependencies]
 proptest = "1.0.0"
+test-util = { version = "0.1.0", path = "../test-util" }
 
diff --git a/ergotree-ir/src/lib.rs b/ergotree-ir/src/lib.rs
index 6410806bc..fb2c55053 100644
--- a/ergotree-ir/src/lib.rs
+++ b/ergotree-ir/src/lib.rs
@@ -14,13 +14,10 @@
 
 pub mod address;
 pub mod ergo_tree;
-pub mod eval;
 pub mod ir_ergo_box;
 pub mod mir;
 pub mod serialization;
 pub mod sigma_protocol;
+pub mod type_check;
 pub mod types;
 pub mod util;
-
-#[cfg(feature = "arbitrary")]
-pub mod test_util;
diff --git a/ergotree-ir/src/mir/and.rs b/ergotree-ir/src/mir/and.rs
index da329a364..cd207cfb2 100644
--- a/ergotree-ir/src/mir/and.rs
+++ b/ergotree-ir/src/mir/and.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,13 +5,11 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
 use super::expr::Expr;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct And {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl And {
@@ -30,14 +24,6 @@ impl And {
     }
 }
 
-impl Evaluable for And {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let input_v_bools = input_v.try_extract_into::<Vec<bool>>()?;
-        Ok(input_v_bools.iter().all(|b| *b).into())
-    }
-}
-
 impl SigmaSerializable for And {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)
@@ -75,17 +61,9 @@ pub mod arbitrary {
 
 #[cfg(test)]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
-    use proptest::collection;
     use proptest::prelude::*;
 
     proptest! {
@@ -96,12 +74,5 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
-        #[test]
-        fn eval(bools in collection::vec(any::<bool>(), 0..10)) {
-            let expr: Expr = And {input: Expr::Const(bools.clone().into()).into()}.into();
-            let ctx = Rc::new(force_any_val::<Context>());
-            let res = eval_out::<bool>(&expr, ctx);
-            prop_assert_eq!(res, bools.iter().all(|b| *b));
-        }
     }
 }
diff --git a/ergotree-ir/src/mir/apply.rs b/ergotree-ir/src/mir/apply.rs
index 123476081..38a376cde 100644
--- a/ergotree-ir/src/mir/apply.rs
+++ b/ergotree-ir/src/mir/apply.rs
@@ -1,9 +1,5 @@
 use std::io;
 
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -13,13 +9,11 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::val_def::ValId;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Apply {
-    func: Box<Expr>,
-    args: Vec<Expr>,
+    pub func: Box<Expr>,
+    pub args: Vec<Expr>,
 }
 
 impl Apply {
@@ -61,29 +55,6 @@ impl Apply {
     }
 }
 
-impl Evaluable for Apply {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let func_v = self.func.eval(env, ctx)?;
-        let args_v_res: Result<Vec<Value>, EvalError> =
-            self.args.iter().map(|arg| arg.eval(env, ctx)).collect();
-        let args_v = args_v_res?;
-        match func_v {
-            Value::FuncValue(fv) => {
-                let arg_ids: Vec<ValId> = fv.args().iter().map(|a| a.idx).collect();
-                let mut cur_env = env.clone();
-                arg_ids.iter().zip(args_v).for_each(|(idx, arg_v)| {
-                    cur_env.insert(*idx, arg_v);
-                });
-                fv.body().eval(&cur_env, ctx)
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected func_v to be Value::FuncValue got: {0:?}",
-                func_v
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for Apply {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.func.sigma_serialize(w)?;
@@ -100,19 +71,9 @@ impl SigmaSerializable for Apply {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
 
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::mir::bin_op::BinOp;
-    use crate::mir::bin_op::RelationOp;
-    use crate::mir::block::BlockValue;
     use crate::mir::func_value::*;
-    use crate::mir::val_def::ValDef;
-    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-    use crate::types::stype::SType;
 
     use super::*;
 
@@ -153,50 +114,4 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
     }
-
-    #[test]
-    fn eval_user_defined_func_call() {
-        let arg = Expr::Const(1i32.into());
-        let bin_op = Expr::BinOp(BinOp {
-            kind: RelationOp::Eq.into(),
-            left: Box::new(
-                ValUse {
-                    val_id: 1.into(),
-                    tpe: SType::SInt,
-                }
-                .into(),
-            ),
-            right: Box::new(
-                ValUse {
-                    val_id: 2.into(),
-                    tpe: SType::SInt,
-                }
-                .into(),
-            ),
-        });
-        let body = Expr::BlockValue(BlockValue {
-            items: vec![ValDef {
-                id: 2.into(),
-                rhs: Box::new(Expr::Const(1i32.into())),
-            }
-            .into()],
-            result: Box::new(bin_op),
-        });
-        let apply: Expr = Apply {
-            func: Box::new(
-                FuncValue::new(
-                    vec![FuncArg {
-                        idx: 1.into(),
-                        tpe: SType::SInt,
-                    }],
-                    body,
-                )
-                .into(),
-            ),
-            args: vec![arg],
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert!(eval_out::<bool>(&apply, ctx));
-    }
 }
diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs
index bfd71006d..1ec162072 100644
--- a/ergotree-ir/src/mir/bin_op.rs
+++ b/ergotree-ir/src/mir/bin_op.rs
@@ -1,25 +1,9 @@
 //! Operators in ErgoTree
 
-use eval::costs::Costs;
-use num::CheckedAdd;
-use num::CheckedDiv;
-use num::CheckedMul;
-use num::CheckedSub;
-use num::Num;
-
-use crate::eval;
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractFrom;
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::value::Value;
-
 extern crate derive_more;
 use derive_more::From;
 
@@ -139,231 +123,6 @@ impl BinOp {
     }
 }
 
-fn eval_plus<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + CheckedAdd + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.checked_add(&rv_raw).ok_or_else(|| {
-        EvalError::ArithmeticException(format!(
-            "({0}) + ({1}) resulted in overflow",
-            lv_raw, rv_raw
-        ))
-    })?)
-    .into())
-}
-
-fn eval_minus<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + CheckedSub + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.checked_sub(&rv_raw).ok_or_else(|| {
-        EvalError::ArithmeticException(format!(
-            "({0}) - ({1}) resulted in overflow",
-            lv_raw, rv_raw
-        ))
-    })?)
-    .into())
-}
-
-fn eval_mul<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + CheckedMul + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.checked_mul(&rv_raw).ok_or_else(|| {
-        EvalError::ArithmeticException(format!(
-            "({0}) * ({1}) resulted in overflow",
-            lv_raw, rv_raw
-        ))
-    })?)
-    .into())
-}
-
-fn eval_div<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + CheckedDiv + TryExtractFrom<Value> + Into<Value> + std::fmt::Display,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.checked_div(&rv_raw).ok_or_else(|| {
-        EvalError::ArithmeticException(format!(
-            "({0}) / ({1}) resulted in exception",
-            lv_raw, rv_raw
-        ))
-    })?)
-    .into())
-}
-
-fn eval_ge(lv: Value, rv: Value) -> Result<Value, EvalError> {
-    match lv {
-        Value::Byte(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i8>()?).into()),
-        Value::Short(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i16>()?).into()),
-        Value::Int(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i32>()?).into()),
-        Value::Long(lv_raw) => Ok((lv_raw >= rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "expected BinOp::left to be numeric value, got {0:?}",
-            lv
-        ))),
-    }
-}
-
-fn eval_gt(lv: Value, rv: Value) -> Result<Value, EvalError> {
-    match lv {
-        Value::Byte(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i8>()?).into()),
-        Value::Short(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i16>()?).into()),
-        Value::Int(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i32>()?).into()),
-        Value::Long(lv_raw) => Ok((lv_raw > rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "expected BinOp::left to be numeric value, got {0:?}",
-            lv
-        ))),
-    }
-}
-
-fn eval_lt(lv: Value, rv: Value) -> Result<Value, EvalError> {
-    match lv {
-        Value::Byte(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i8>()?).into()),
-        Value::Short(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i16>()?).into()),
-        Value::Int(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i32>()?).into()),
-        Value::Long(lv_raw) => Ok((lv_raw < rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "expected BinOp::left to be numeric value, got {0:?}",
-            lv
-        ))),
-    }
-}
-
-fn eval_le(lv: Value, rv: Value) -> Result<Value, EvalError> {
-    match lv {
-        Value::Byte(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i8>()?).into()),
-        Value::Short(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i16>()?).into()),
-        Value::Int(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i32>()?).into()),
-        Value::Long(lv_raw) => Ok((lv_raw <= rv.try_extract_into::<i64>()?).into()),
-        Value::BigInt => todo!(),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "expected BinOp::left to be numeric value, got {0:?}",
-            lv
-        ))),
-    }
-}
-
-fn eval_max<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + Ord + TryExtractFrom<Value> + Into<Value>,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.max(rv_raw)).into())
-}
-
-fn eval_min<T>(lv_raw: T, rv: Value) -> Result<Value, EvalError>
-where
-    T: Num + Ord + TryExtractFrom<Value> + Into<Value>,
-{
-    let rv_raw = rv.try_extract_into::<T>()?;
-    Ok((lv_raw.min(rv_raw)).into())
-}
-
-impl Evaluable for BinOp {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        ctx.cost_accum.add(Costs::DEFAULT.eq_const_size)?;
-        let lv = self.left.eval(env, ctx)?;
-        // using closure to keep right value from evaluation (for lazy AND, OR)
-        let mut rv = || self.right.eval(env, ctx);
-        match self.kind {
-            BinOpKind::Relation(op) => match op {
-                RelationOp::Eq => Ok(Value::Boolean(lv == rv()?)),
-                RelationOp::NEq => Ok(Value::Boolean(lv != rv()?)),
-                RelationOp::GT => eval_gt(lv, rv()?),
-                RelationOp::LT => eval_lt(lv, rv()?),
-                RelationOp::GE => eval_ge(lv, rv()?),
-                RelationOp::LE => eval_le(lv, rv()?),
-                RelationOp::And => Ok(Value::Boolean(if lv.try_extract_into::<bool>()? {
-                    rv()?.try_extract_into::<bool>()?
-                } else {
-                    false
-                })),
-                RelationOp::Or => Ok(Value::Boolean(if !lv.try_extract_into::<bool>()? {
-                    rv()?.try_extract_into::<bool>()?
-                } else {
-                    true
-                })),
-            },
-            BinOpKind::Arith(op) => match op {
-                ArithOp::Plus => match lv {
-                    Value::Byte(lv_raw) => eval_plus(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_plus(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_plus(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_plus(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-                ArithOp::Minus => match lv {
-                    Value::Byte(lv_raw) => eval_minus(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_minus(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_minus(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_minus(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-                ArithOp::Multiply => match lv {
-                    Value::Byte(lv_raw) => eval_mul(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_mul(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_mul(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_mul(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-                ArithOp::Divide => match lv {
-                    Value::Byte(lv_raw) => eval_div(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_div(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_div(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_div(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-                ArithOp::Max => match lv {
-                    Value::Byte(lv_raw) => eval_max(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_max(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_max(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_max(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-                ArithOp::Min => match lv {
-                    Value::Byte(lv_raw) => eval_min(lv_raw, rv()?),
-                    Value::Short(lv_raw) => eval_min(lv_raw, rv()?),
-                    Value::Int(lv_raw) => eval_min(lv_raw, rv()?),
-                    Value::Long(lv_raw) => eval_min(lv_raw, rv()?),
-                    Value::BigInt => todo!(),
-                    _ => Err(EvalError::UnexpectedValue(format!(
-                        "expected BinOp::left to be numeric value, got {0:?}",
-                        lv
-                    ))),
-                },
-            },
-        }
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use crate::mir::expr::arbitrary::ArbExprParams;
@@ -441,226 +200,18 @@ pub mod arbitrary {
 }
 
 #[cfg(test)]
-pub mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::eval::tests::try_eval_out;
-    use crate::mir::constant::Constant;
-    use crate::mir::constant::TryExtractFrom;
-    use crate::test_util::force_any_val;
+mod tests {
 
     use super::*;
-
-    fn check_eq_neq(left: Constant, right: Constant) -> bool {
-        let eq_op: Expr = BinOp {
-            kind: BinOpKind::Relation(RelationOp::Eq),
-            left: Box::new(left.clone().into()),
-            right: Box::new(right.clone().into()),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        let neq_op: Expr = BinOp {
-            kind: BinOpKind::Relation(RelationOp::NEq),
-            left: Box::new(left.into()),
-            right: Box::new(right.into()),
-        }
-        .into();
-        let ctx1 = Rc::new(force_any_val::<Context>());
-        eval_out::<bool>(&eq_op, ctx) && !eval_out::<bool>(&neq_op, ctx1)
-    }
-
-    #[test]
-    fn num_eq() {
-        assert!(check_eq_neq(1i64.into(), 1i64.into()));
-    }
-
-    #[test]
-    fn num_neq() {
-        assert!(!check_eq_neq(2i64.into(), 1i64.into()));
-    }
-
-    #[test]
-    fn option_eq() {
-        assert!(check_eq_neq(Some(1i64).into(), Some(1i64).into()));
-        let none: Option<i64> = None;
-        assert!(check_eq_neq(none.into(), none.into()));
-        // Option<Vec<i8>>
-        assert!(check_eq_neq(
-            Some(vec![1i8, 2i8]).into(),
-            Some(vec![1i8, 2i8]).into()
-        ));
-        // Vec<Option<i64>>
-        assert!(check_eq_neq(
-            vec![Some(1i64), Some(1i64)].into(),
-            vec![Some(1i64), Some(1i64)].into()
-        ));
-    }
-
-    #[test]
-    fn option_neq() {
-        assert!(!check_eq_neq(Some(2i64).into(), Some(1i64).into()));
-        let none: Option<i64> = None;
-        assert!(!check_eq_neq(none.into(), Some(1i64).into()));
-        // Option<Vec<i8>>
-        assert!(!check_eq_neq(
-            Some(vec![1i8, 2i8]).into(),
-            Some(vec![2i8, 2i8]).into()
-        ));
-        // Vec<Option<i64>>
-        assert!(!check_eq_neq(
-            vec![Some(1i64), Some(1i64)].into(),
-            vec![Some(2i64), Some(1i64)].into()
-        ));
-    }
-
-    #[test]
-    fn tuple_eq() {
-        assert!(check_eq_neq((1i64, true).into(), (1i64, true).into()));
-    }
-
-    #[test]
-    fn bin_or_eval_laziness() {
-        let e: Expr = BinOp {
-            kind: BinOpKind::Relation(RelationOp::Or),
-            left: Box::new(Expr::Const(true.into())),
-            // something that should blow-up the evaluation
-            right: Box::new(
-                BinOp {
-                    kind: ArithOp::Divide.into(),
-                    left: Box::new(Expr::Const(1i32.into())),
-                    right: Box::new(Expr::Const(0i32.into())),
-                }
-                .into(),
-            ),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(eval_out::<bool>(&e, ctx), true);
-    }
-
-    #[test]
-    fn bin_and_eval_laziness() {
-        let e: Expr = BinOp {
-            kind: BinOpKind::Relation(RelationOp::And),
-            left: Box::new(Expr::Const(false.into())),
-            // something that should blow-up the evaluation
-            right: Box::new(
-                BinOp {
-                    kind: ArithOp::Divide.into(),
-                    left: Box::new(Expr::Const(1i32.into())),
-                    right: Box::new(Expr::Const(0i32.into())),
-                }
-                .into(),
-            ),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(eval_out::<bool>(&e, ctx), false);
-    }
-
-    fn eval_num_op<T: TryExtractFrom<Value>>(
-        op: ArithOp,
-        left: Constant,
-        right: Constant,
-    ) -> Result<T, EvalError> {
-        let expr: Expr = BinOp {
-            kind: BinOpKind::Arith(op),
-            left: Box::new(left.into()),
-            right: Box::new(right.into()),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        try_eval_out::<T>(&expr, ctx)
-    }
-
-    fn eval_relation_op(op: RelationOp, left: Constant, right: Constant) -> bool {
-        let expr: Expr = BinOp {
-            kind: BinOpKind::Relation(op),
-            left: Box::new(left.into()),
-            right: Box::new(right.into()),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        eval_out::<bool>(&expr, ctx)
-    }
-
+    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
 
-        #![proptest_config(ProptestConfig::with_cases(32))]
-
-        #[test]
-        fn test_eq(v in any::<Constant>()) {
-            prop_assert![check_eq_neq(v.clone(), v)];
-        }
-
-        #[test]
-        fn test_num_slong(l in any::<i64>(), r in any::<i64>()) {
-            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
-            prop_assert_eq!(eval_num_op::<i64>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
-            prop_assert_eq!(eval_num_op::<i64>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
-
-            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
-            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
-        }
-
-        #[test]
-        fn test_num_sint(l in any::<i32>(), r in any::<i32>()) {
-            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
-            prop_assert_eq!(eval_num_op::<i32>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
-            prop_assert_eq!(eval_num_op::<i32>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
-
-            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
-            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
-        }
-
-        #[test]
-        fn test_num_sshort(l in any::<i16>(), r in any::<i16>()) {
-            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
-            prop_assert_eq!(eval_num_op::<i16>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
-            prop_assert_eq!(eval_num_op::<i16>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
-
-            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
-            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
-        }
-
-        #[test]
-        fn test_num_sbyte(l in any::<i8>(), r in any::<i8>()) {
-            prop_assert_eq!(eval_num_op(ArithOp::Plus, l.into(), r.into()).ok(), l.checked_add(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Minus, l.into(), r.into()).ok(), l.checked_sub(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Multiply, l.into(), r.into()).ok(), l.checked_mul(r));
-            prop_assert_eq!(eval_num_op(ArithOp::Divide, l.into(), r.into()).ok(), l.checked_div(r));
-            prop_assert_eq!(eval_num_op::<i8>(ArithOp::Max, l.into(), r.into()).unwrap(), l.max(r));
-            prop_assert_eq!(eval_num_op::<i8>(ArithOp::Min, l.into(), r.into()).unwrap(), l.min(r));
-
-            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.into(), r.into()), l > r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.into(), r.into()), l < r);
-            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.into(), r.into()), l >= r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.into(), r.into()), l <= r);
-        }
-
         #[test]
-        fn test_and_or(l in any::<bool>(), r in any::<bool>()) {
-            prop_assert_eq!(eval_relation_op(RelationOp::And, l.into(), r.into()), l && r);
-            prop_assert_eq!(eval_relation_op(RelationOp::Or, l.into(), r.into()), l || r);
+        fn ser_roundtrip(v in any::<BinOp>()) {
+            let expr: Expr = v.into();
+            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
     }
diff --git a/ergotree-ir/src/mir/block.rs b/ergotree-ir/src/mir/block.rs
index eafcf0918..56940c07d 100644
--- a/ergotree-ir/src/mir/block.rs
+++ b/ergotree-ir/src/mir/block.rs
@@ -1,9 +1,6 @@
 use std::io;
 
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -11,11 +8,6 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::val_def::ValDef;
-use super::value::Value;
-
 /** The order of ValDefs in the block is used to assign ids to ValUse(id) nodes
  * For all i: items(i).id == {number of ValDefs preceded in a graph} with respect to topological order.
  * Specific topological order doesn't really matter, what is important is to preserve semantic linkage
@@ -39,18 +31,6 @@ impl BlockValue {
     }
 }
 
-impl Evaluable for BlockValue {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let mut cur_env = env.clone();
-        for i in self.items.iter() {
-            let val_def = i.clone().try_extract_into::<ValDef>()?;
-            let v: Value = val_def.rhs.eval(&cur_env, ctx)?;
-            cur_env.insert(val_def.id, v);
-        }
-        self.result.eval(&cur_env, ctx)
-    }
-}
-
 impl SigmaSerializable for BlockValue {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.items.sigma_serialize(w)?;
@@ -68,13 +48,9 @@ impl SigmaSerializable for BlockValue {
     }
 }
 
-#[cfg(test)]
 #[cfg(feature = "arbitrary")]
-mod tests {
-    use crate::mir::block::BlockValue;
-    use crate::mir::expr::Expr;
-    use crate::serialization::sigma_serialize_roundtrip;
-
+pub mod arbitrary {
+    use super::*;
     use proptest::collection::vec;
     use proptest::prelude::*;
 
@@ -91,6 +67,14 @@ mod tests {
                 .boxed()
         }
     }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use crate::mir::block::BlockValue;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
 
     proptest! {
 
diff --git a/ergotree-ir/src/mir/bool_to_sigma.rs b/ergotree-ir/src/mir/bool_to_sigma.rs
index de1611009..fbec6ad85 100644
--- a/ergotree-ir/src/mir/bool_to_sigma.rs
+++ b/ergotree-ir/src/mir/bool_to_sigma.rs
@@ -1,23 +1,15 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 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::sigma_protocol::sigma_boolean::SigmaBoolean;
-use crate::sigma_protocol::sigma_boolean::SigmaProp;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
 use super::expr::Expr;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BoolToSigmaProp {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl BoolToSigmaProp {
@@ -44,14 +36,6 @@ impl SigmaSerializable for BoolToSigmaProp {
     }
 }
 
-impl Evaluable for BoolToSigmaProp {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let input_v_bool = input_v.try_extract_into::<bool>()?;
-        Ok((SigmaProp::new(SigmaBoolean::TrivialProp(input_v_bool))).into())
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use crate::mir::expr::arbitrary::ArbExprParams;
@@ -80,7 +64,6 @@ pub mod arbitrary {
 #[cfg(feature = "arbitrary")]
 mod tests {
 
-    use crate::eval::tests::eval_out_wo_ctx;
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
 
@@ -96,11 +79,5 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
-        #[test]
-        fn eval(b in any::<bool>()) {
-            let expr: Expr = BoolToSigmaProp {input: Expr::Const(b.into()).into()}.into();
-            let res = eval_out_wo_ctx::<SigmaProp>(&expr);
-            prop_assert_eq!(res, SigmaProp::new(SigmaBoolean::TrivialProp(b)));
-        }
     }
 }
diff --git a/ergotree-ir/src/mir/calc_blake2b256.rs b/ergotree-ir/src/mir/calc_blake2b256.rs
index 07915176d..fe9b88f9d 100644
--- a/ergotree-ir/src/mir/calc_blake2b256.rs
+++ b/ergotree-ir/src/mir/calc_blake2b256.rs
@@ -1,26 +1,16 @@
-use sigma_util::hash::blake2b256_hash;
-
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 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 crate::util::AsVecU8;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::CollKind;
-use super::value::NativeColl;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct CalcBlake2b256 {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl CalcBlake2b256 {
@@ -40,23 +30,6 @@ impl CalcBlake2b256 {
     }
 }
 
-impl Evaluable for CalcBlake2b256 {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        match input_v.clone() {
-            Value::Coll(CollKind::NativeColl(NativeColl::CollByte(coll_byte))) => {
-                let expected_hash: Vec<u8> =
-                    blake2b256_hash(coll_byte.as_vec_u8().as_slice()).to_vec();
-                Ok(expected_hash.into())
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected CalcBlake2b256 input to be byte array, got: {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for CalcBlake2b256 {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)
@@ -95,31 +68,12 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-    use crate::util::AsVecU8;
-
     use super::*;
-
+    use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
 
     proptest! {
 
-        #[test]
-        fn eval(byte_array in any::<Vec<u8>>()) {
-            let expected_hash = blake2b256_hash(byte_array.as_slice()).to_vec();
-            let expr: Expr = CalcBlake2b256 {
-                input: Box::new(Expr::Const(byte_array.into())),
-            }
-            .into();
-            let ctx = Rc::new(force_any_val::<Context>());
-            assert_eq!(eval_out::<Vec<i8>>(&expr, ctx).as_vec_u8(), expected_hash);
-        }
-
         #[test]
         fn ser_roundtrip(v in any::<CalcBlake2b256>()) {
             let expr: Expr = v.into();
diff --git a/ergotree-ir/src/mir/coll_by_index.rs b/ergotree-ir/src/mir/coll_by_index.rs
index 0c39d0720..5ad84a7a5 100644
--- a/ergotree-ir/src/mir/coll_by_index.rs
+++ b/ergotree-ir/src/mir/coll_by_index.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,16 +5,14 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ByIndex {
-    input: Box<Expr>,
-    index: Box<Expr>,
-    default: Option<Box<Expr>>,
+    pub input: Box<Expr>,
+    pub index: Box<Expr>,
+    pub default: Option<Box<Expr>>,
 }
 
 impl ByIndex {
@@ -86,39 +80,6 @@ impl SigmaSerializable for ByIndex {
     }
 }
 
-impl Evaluable for ByIndex {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let index_v = self.index.eval(env, ctx)?;
-        let normalized_input_vals: Vec<Value> = match input_v {
-            Value::Coll(coll) => Ok(coll.as_vec()),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "ByIndex: expected input to be Value::Coll, got: {0:?}",
-                input_v
-            ))),
-        }?;
-        match self.default.clone() {
-            Some(default) => {
-                let default_v = default.eval(env, ctx)?;
-                Ok(normalized_input_vals
-                    .get(index_v.try_extract_into::<i32>()? as usize)
-                    .cloned()
-                    .unwrap_or(default_v))
-            }
-            None => normalized_input_vals
-                .get(index_v.clone().try_extract_into::<i32>()? as usize)
-                .cloned()
-                .ok_or_else(|| {
-                    EvalError::Misc(format!(
-                        "ByIndex: index {0:?} out of bounds for collection size {1:?}",
-                        index_v,
-                        normalized_input_vals.len()
-                    ))
-                }),
-        }
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use super::*;
@@ -161,20 +122,9 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::ir_ergo_box::IrBoxId;
+    use super::*;
     use crate::mir::expr::Expr;
-    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
     use proptest::prelude::*;
 
     proptest! {
@@ -187,28 +137,4 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
     }
-
-    #[test]
-    fn eval() {
-        let expr: Expr = ByIndex::new(GlobalVars::Outputs.into(), Expr::Const(0i32.into()), None)
-            .unwrap()
-            .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<IrBoxId>(&expr, ctx.clone()),
-            ctx.outputs.get(0).unwrap().clone()
-        );
-    }
-
-    #[test]
-    fn eval_with_default() {
-        let expr: Expr = ByIndex::new(
-            Expr::Const(vec![1i64, 2i64].into()),
-            Expr::Const(3i32.into()),
-            Some(Box::new(Expr::Const(5i64.into()))),
-        )
-        .unwrap()
-        .into();
-        assert_eq!(eval_out_wo_ctx::<i64>(&expr), 5);
-    }
 }
diff --git a/ergotree-ir/src/mir/coll_filter.rs b/ergotree-ir/src/mir/coll_filter.rs
index e65068416..f99f404b1 100644
--- a/ergotree-ir/src/mir/coll_filter.rs
+++ b/ergotree-ir/src/mir/coll_filter.rs
@@ -1,7 +1,5 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,19 +7,13 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::expr::InvalidArgumentError;
-use super::value::CollKind;
-use super::value::Value;
-
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Filter {
     /// Collection
-    input: Box<Expr>,
+    pub input: Box<Expr>,
     /// Function (lambda) to test each element
-    condition: Box<Expr>,
-    elem_tpe: SType,
+    pub condition: Box<Expr>,
+    pub elem_tpe: SType,
 }
 
 impl Filter {
@@ -75,66 +67,6 @@ impl SigmaSerializable for Filter {
     }
 }
 
-impl Evaluable for Filter {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let condition_v = self.condition.eval(env, ctx)?;
-        let input_v_clone = input_v.clone();
-        let mut condition_call = |arg: Value| match &condition_v {
-            Value::FuncValue(func_value) => {
-                let func_arg = func_value.args().first().ok_or_else(|| {
-                    EvalError::NotFound(
-                        "Filter: evaluated condition has empty arguments list".to_string(),
-                    )
-                })?;
-                let env1 = env.clone().extend(func_arg.idx, arg);
-                func_value.body().eval(&env1, ctx)
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected Filter::condition to be Value::FuncValue got: {0:?}",
-                input_v_clone
-            ))),
-        };
-        let normalized_input_vals: Vec<Value> = match input_v {
-            Value::Coll(coll) => {
-                if *coll.elem_tpe() != self.elem_tpe {
-                    return Err(EvalError::UnexpectedValue(format!(
-                        "expected Filter input element type to be {0:?}, got: {1:?}",
-                        self.elem_tpe,
-                        coll.elem_tpe()
-                    )));
-                };
-                Ok(coll.as_vec())
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected Map input to be Value::Coll, got: {0:?}",
-                input_v
-            ))),
-        }?;
-
-        let items_conditions: Vec<bool> = normalized_input_vals
-            .clone()
-            .into_iter()
-            .map(|item| {
-                condition_call(item).and_then(|res| {
-                    res.try_extract_into::<bool>()
-                        .map_err(EvalError::TryExtractFrom)
-                })
-            })
-            .collect::<Result<Vec<bool>, EvalError>>()?;
-        let filtered_items = normalized_input_vals
-            .into_iter()
-            .zip(items_conditions)
-            .filter(|(_, condition)| *condition)
-            .map(|(item, _)| item)
-            .collect();
-        Ok(Value::Coll(CollKind::from_vec(
-            self.elem_tpe.clone(),
-            filtered_items,
-        )?))
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use super::*;
@@ -170,73 +102,15 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::ir_ergo_box::IrBoxId;
-    use crate::mir::bin_op::BinOp;
-    use crate::mir::bin_op::RelationOp;
+    use super::*;
     use crate::mir::expr::Expr;
-    use crate::mir::extract_amount::ExtractAmount;
-    use crate::mir::func_value::FuncArg;
-    use crate::mir::func_value::FuncValue;
-    use crate::mir::property_call::PropertyCall;
-    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::scontext;
-
-    use super::*;
-
     use proptest::prelude::*;
 
     proptest! {
 
         #![proptest_config(ProptestConfig::with_cases(16))]
 
-        #[test]
-        fn eval_box_value(ctx in any::<Context>()) {
-            let data_inputs: Expr = PropertyCall {
-                obj: Box::new(Expr::Context),
-                method: scontext::DATA_INPUTS_PROPERTY.clone(),
-            }
-            .into();
-            let val_use: Expr = ValUse {
-                val_id: 1.into(),
-                tpe: SType::SBox,
-            }
-            .into();
-            let body: Expr = BinOp {
-                kind: RelationOp::LE.into(),
-                left: Box::new(Expr::Const(1i64.into())),
-                right: Box::new(Expr::ExtractAmount(
-                        ExtractAmount::new(val_use)
-                    .unwrap(),
-                )),
-            }
-            .into();
-            let expr: Expr = Filter::new(
-                data_inputs,
-                FuncValue::new(
-                    vec![FuncArg {
-                        idx: 1.into(),
-                        tpe: SType::SBox,
-                    }],
-                    body,
-                )
-                .into(),
-            )
-            .unwrap()
-            .into();
-            let ctx = Rc::new(ctx);
-            assert_eq!(
-                eval_out::<Vec<IrBoxId>>(&expr, ctx.clone()),
-                ctx.data_inputs.clone()
-                    .into_iter()
-                    .filter(| b| 1 <= b.get_box(&ctx.box_arena).unwrap().value()).collect::<Vec<IrBoxId>>()
-            );
-        }
-
         #[test]
         fn ser_roundtrip(v in any::<Filter>()) {
             let expr: Expr = v.into();
diff --git a/ergotree-ir/src/mir/coll_fold.rs b/ergotree-ir/src/mir/coll_fold.rs
index 0962a5e8c..a4a49d7c1 100644
--- a/ergotree-ir/src/mir/coll_fold.rs
+++ b/ergotree-ir/src/mir/coll_fold.rs
@@ -1,30 +1,22 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 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::stuple::TupleItems;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::CollKind;
-use super::value::NativeColl;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Fold {
     /// Collection
-    input: Box<Expr>,
+    pub input: Box<Expr>,
     /// Initial value for accumulator
-    zero: Box<Expr>,
+    pub zero: Box<Expr>,
     /// Function (lambda)
-    fold_op: Box<Expr>,
+    pub fold_op: Box<Expr>,
 }
 
 impl Fold {
@@ -81,73 +73,12 @@ impl SigmaSerializable for Fold {
     }
 }
 
-impl Evaluable for Fold {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let zero_v = self.zero.eval(env, ctx)?;
-        let fold_op_v = self.fold_op.eval(env, ctx)?;
-        let input_v_clone = input_v.clone();
-        let mut fold_op_call = |arg: Value| match &fold_op_v {
-            Value::FuncValue(func_value) => {
-                let func_arg = func_value
-                    .args()
-                    .first()
-                    .ok_or_else(|| EvalError::NotFound("empty argument for fold op".to_string()))?;
-                let env1 = env.clone().extend(func_arg.idx, arg);
-                func_value.body().eval(&env1, ctx)
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected fold_op to be Value::FuncValue got: {0:?}",
-                input_v_clone
-            ))),
-        };
-        match input_v {
-            Value::Coll(coll) => match coll {
-                CollKind::NativeColl(NativeColl::CollByte(coll_byte)) => {
-                    coll_byte.iter().try_fold(zero_v, |acc, byte| {
-                        let tup_arg = Value::Tup(TupleItems::pair(acc, Value::Byte(*byte)));
-                        fold_op_call(tup_arg)
-                    })
-                }
-                CollKind::WrappedColl {
-                    elem_tpe: _,
-                    items: v,
-                } => v.iter().try_fold(zero_v, |acc, item| {
-                    let tup_arg = Value::Tup(TupleItems::pair(acc, item.clone()));
-                    fold_op_call(tup_arg)
-                }),
-            },
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected Fold input to be Value::Coll, got: {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::convert::TryInto;
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::mir::bin_op::ArithOp;
-    use crate::mir::bin_op::BinOp;
+    use super::*;
     use crate::mir::expr::Expr;
-    use crate::mir::extract_amount::ExtractAmount;
-    use crate::mir::func_value::FuncArg;
-    use crate::mir::func_value::FuncValue;
-    use crate::mir::property_call::PropertyCall;
-    use crate::mir::select_field::SelectField;
-    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::scontext;
-    use crate::types::stuple::STuple;
-
-    use super::*;
-
     use proptest::prelude::*;
 
     impl Arbitrary for Fold {
@@ -169,58 +100,6 @@ mod tests {
 
         #![proptest_config(ProptestConfig::with_cases(16))]
 
-        #[test]
-        fn eval_fold(ctx in any::<Context>()) {
-            let data_inputs: Expr = PropertyCall {
-                obj: Box::new(Expr::Context),
-                method: scontext::DATA_INPUTS_PROPERTY.clone(),
-            }
-            .into();
-            let tuple: Expr = ValUse {
-                val_id: 1.into(),
-                tpe: SType::STuple(STuple {
-                    items: TupleItems::pair(SType::SLong, SType::SBox),
-                }),
-            }
-            .into();
-            let fold_op_body: Expr = BinOp {
-                kind: ArithOp::Plus.into(),
-                left: Box::new(Expr::SelectField(
-                    SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap(),
-                )),
-                right: Box::new(Expr::ExtractAmount(
-                    ExtractAmount::new(Expr::SelectField(
-                        SelectField::new(tuple, 2.try_into().unwrap()).unwrap(),
-                    ))
-                    .unwrap(),
-                )),
-            }
-            .into();
-            let expr: Expr = Fold::new(
-                data_inputs,
-                Expr::Const(0i64.into()),
-                FuncValue::new(
-                    vec![FuncArg {
-                        idx: 1.into(),
-                        tpe: SType::STuple(STuple {
-                            items: TupleItems::pair(SType::SLong, SType::SBox),
-                        }),
-                    }],
-                    fold_op_body,
-                )
-                .into(),
-            )
-            .unwrap()
-            .into();
-            let ctx = Rc::new(ctx);
-            assert_eq!(
-                eval_out::<i64>(&expr, ctx.clone()),
-                ctx.data_inputs
-                    .iter()
-                    .fold(0i64, |acc, b| acc + b.get_box(&ctx.box_arena).unwrap().value())
-            );
-        }
-
         #[test]
         fn ser_roundtrip(v in any::<Fold>()) {
             let expr: Expr = v.into();
diff --git a/ergotree-ir/src/mir/coll_map.rs b/ergotree-ir/src/mir/coll_map.rs
index 34e91b9eb..3244c730c 100644
--- a/ergotree-ir/src/mir/coll_map.rs
+++ b/ergotree-ir/src/mir/coll_map.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -12,16 +8,14 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::CollKind;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Map {
     /// Collection
-    input: Box<Expr>,
+    pub input: Box<Expr>,
     /// Function (lambda) to apply to each element
-    mapper: Box<Expr>,
-    mapper_sfunc: SFunc,
+    pub mapper: Box<Expr>,
+    pub mapper_sfunc: SFunc,
 }
 
 impl Map {
@@ -74,64 +68,6 @@ impl SigmaSerializable for Map {
     }
 }
 
-impl Evaluable for Map {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let mapper_v = self.mapper.eval(env, ctx)?;
-        let input_v_clone = input_v.clone();
-        let mut mapper_call = |arg: Value| match &mapper_v {
-            Value::FuncValue(func_value) => {
-                let func_arg = func_value.args().first().ok_or_else(|| {
-                    EvalError::NotFound(
-                        "Map: evaluated mapper has empty arguments list".to_string(),
-                    )
-                })?;
-                let env1 = env.clone().extend(func_arg.idx, arg);
-                func_value.body().eval(&env1, ctx)
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected mapper to be Value::FuncValue got: {0:?}",
-                input_v_clone
-            ))),
-        };
-        let mapper_input_tpe = self
-            .mapper_sfunc
-            .t_dom
-            .first()
-            .ok_or_else(|| {
-                EvalError::NotFound(
-                    "Map: mapper SFunc.t_dom is empty (does not have arguments)".to_string(),
-                )
-            })?
-            .clone();
-        let normalized_input_vals: Vec<Value> = match input_v {
-            Value::Coll(coll) => {
-                if *coll.elem_tpe() != mapper_input_tpe {
-                    return Err(EvalError::UnexpectedValue(format!(
-                        "expected Map input element type to be {0:?}, got: {1:?}",
-                        mapper_input_tpe,
-                        coll.elem_tpe()
-                    )));
-                };
-                Ok(coll.as_vec())
-            }
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected Map input to be Value::Coll, got: {0:?}",
-                input_v
-            ))),
-        }?;
-        normalized_input_vals
-            .iter()
-            .map(|item| mapper_call(item.clone()))
-            .collect::<Result<Vec<Value>, EvalError>>()
-            .map(|values| {
-                CollKind::from_vec(self.out_elem_tpe(), values).map_err(EvalError::TryExtractFrom)
-            })
-            .and_then(|v| v) // flatten <Result<Result<Value, _>, _>
-            .map(Value::Coll)
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use super::*;
@@ -166,72 +102,15 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::mir::bin_op::ArithOp;
-    use crate::mir::bin_op::BinOp;
+    use super::*;
     use crate::mir::expr::Expr;
-    use crate::mir::extract_amount::ExtractAmount;
-    use crate::mir::func_value::FuncArg;
-    use crate::mir::func_value::FuncValue;
-    use crate::mir::property_call::PropertyCall;
-    use crate::mir::val_use::ValUse;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::types::scontext;
-
-    use super::*;
-
     use proptest::prelude::*;
 
     proptest! {
 
         #![proptest_config(ProptestConfig::with_cases(16))]
 
-        #[test]
-        fn eval_box_value(ctx in any::<Context>()) {
-            let data_inputs: Expr = PropertyCall {
-                obj: Box::new(Expr::Context),
-                method: scontext::DATA_INPUTS_PROPERTY.clone(),
-            }
-            .into();
-            let val_use: Expr = ValUse {
-                val_id: 1.into(),
-                tpe: SType::SBox,
-            }
-            .into();
-            let mapper_body: Expr = BinOp {
-                kind: ArithOp::Plus.into(),
-                left: Box::new(Expr::Const(1i64.into())),
-                right: Box::new(Expr::ExtractAmount(
-                        ExtractAmount::new(val_use)
-                    .unwrap(),
-                )),
-            }
-            .into();
-            let expr: Expr = Map::new(
-                data_inputs,
-                FuncValue::new(
-                    vec![FuncArg {
-                        idx: 1.into(),
-                        tpe: SType::SBox,
-                    }],
-                    mapper_body,
-                )
-                .into(),
-            )
-            .unwrap()
-            .into();
-            let ctx = Rc::new(ctx);
-            assert_eq!(
-                eval_out::<Vec<i64>>(&expr, ctx.clone()),
-                ctx.data_inputs
-                    .iter()
-                    .map(| b| b.get_box(&ctx.box_arena).unwrap().value() + 1).collect::<Vec<i64>>()
-            );
-        }
-
         #[test]
         fn ser_roundtrip(v in any::<Map>()) {
             let expr: Expr = v.into();
diff --git a/ergotree-ir/src/mir/coll_size.rs b/ergotree-ir/src/mir/coll_size.rs
index 671948872..da9d3c901 100644
--- a/ergotree-ir/src/mir/coll_size.rs
+++ b/ergotree-ir/src/mir/coll_size.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -11,11 +7,10 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SizeOf {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl SizeOf {
@@ -53,20 +48,6 @@ impl SigmaSerializable for SizeOf {
     }
 }
 
-impl Evaluable for SizeOf {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let normalized_input_vals: Vec<Value> = match input_v {
-            Value::Coll(coll) => Ok(coll.as_vec()),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "SizeOf: expected input to be Value::Coll, got: {0:?}",
-                input_v
-            ))),
-        }?;
-        Ok((normalized_input_vals.len() as i32).into())
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use crate::mir::expr::arbitrary::ArbExprParams;
@@ -95,17 +76,9 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::expr::Expr;
-    use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
     use proptest::prelude::*;
 
     proptest! {
@@ -119,14 +92,4 @@ mod tests {
         }
 
     }
-
-    #[test]
-    fn eval() {
-        let expr: Expr = SizeOf::new(GlobalVars::Outputs.into()).unwrap().into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<i32>(&expr, ctx.clone()),
-            ctx.outputs.len() as i32
-        );
-    }
 }
diff --git a/ergotree-ir/src/mir/collection.rs b/ergotree-ir/src/mir/collection.rs
index 2063febfe..288821578 100644
--- a/ergotree-ir/src/mir/collection.rs
+++ b/ergotree-ir/src/mir/collection.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -14,9 +10,6 @@ use super::constant::TryExtractFromError;
 use super::constant::TryExtractInto;
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::CollKind;
-use super::value::NativeColl;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub enum Collection {
@@ -65,31 +58,6 @@ impl Collection {
     }
 }
 
-impl Evaluable for Collection {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        Ok(match self {
-            Collection::BoolConstants(bools) => bools.clone().into(),
-            Collection::Exprs { elem_tpe, items } => {
-                let items_v: Result<Vec<Value>, EvalError> =
-                    items.iter().map(|i| i.eval(env, ctx)).collect();
-                match elem_tpe {
-                    SType::SByte => {
-                        let bytes: Result<Vec<i8>, TryExtractFromError> = items_v?
-                            .into_iter()
-                            .map(|i| i.try_extract_into::<i8>())
-                            .collect();
-                        Value::Coll(CollKind::NativeColl(NativeColl::CollByte(bytes?)))
-                    }
-                    _ => Value::Coll(CollKind::WrappedColl {
-                        elem_tpe: elem_tpe.clone(),
-                        items: items_v?,
-                    }),
-                }
-            }
-        })
-    }
-}
-
 pub fn coll_sigma_serialize<W: SigmaByteWrite>(
     coll: &Collection,
     w: &mut W,
@@ -157,7 +125,6 @@ pub mod arbitrary {
 #[cfg(feature = "arbitrary")]
 mod tests {
     use super::*;
-    use crate::eval::tests::eval_out_wo_ctx;
     use crate::mir::expr::arbitrary::ArbExprParams;
     use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
@@ -178,37 +145,5 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
-        #[test]
-        fn eval_byte_coll(bytes in any::<Vec<i8>>()) {
-            let value: Value = bytes.clone().into();
-            let exprs: Vec<Expr> = bytes.into_iter().map(|b| Expr::Const(b.into())).collect();
-            let coll: Expr = Collection::new(SType::SByte, exprs).unwrap().into();
-            let res = eval_out_wo_ctx::<Value>(&coll);
-            prop_assert_eq!(res, value);
-        }
-
-        #[test]
-        fn eval_bool_coll(bools in any::<Vec<bool>>()) {
-            let exprs: Vec<Expr> = bools.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
-            let coll: Expr = Collection::new(SType::SBoolean, exprs).unwrap().into();
-            let res = eval_out_wo_ctx::<Vec<bool>>(&coll);
-            prop_assert_eq!(res, bools);
-        }
-
-        #[test]
-        fn eval_long_coll(longs in any::<Vec<i64>>()) {
-            let exprs: Vec<Expr> = longs.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
-            let coll: Expr = Collection::new(SType::SLong, exprs).unwrap().into();
-            let res = eval_out_wo_ctx::<Vec<i64>>(&coll);
-            prop_assert_eq!(res, longs);
-        }
-
-        #[test]
-        fn eval_bytes_coll_coll(bb in any::<Vec<Vec<i8>>>()) {
-            let exprs: Vec<Expr> = bb.clone().into_iter().map(|b| Expr::Const(b.into())).collect();
-            let coll: Expr = Collection::new(SType::SColl(SType::SByte.into()), exprs).unwrap().into();
-            let res = eval_out_wo_ctx::<Vec<Vec<i8>>>(&coll);
-            prop_assert_eq!(res, bb);
-        }
     }
 }
diff --git a/ergotree-ir/src/mir/extract_amount.rs b/ergotree-ir/src/mir/extract_amount.rs
index 8493cb3c1..74cad8da8 100644
--- a/ergotree-ir/src/mir/extract_amount.rs
+++ b/ergotree-ir/src/mir/extract_amount.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -11,11 +7,10 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ExtractAmount {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl ExtractAmount {
@@ -35,19 +30,6 @@ impl ExtractAmount {
     }
 }
 
-impl Evaluable for ExtractAmount {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        match input_v {
-            Value::CBox(b) => Ok(Value::Long(ctx.ctx.box_arena.get(&b)?.value())),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "Expected ExtractAmount input to be Value::CBox, got {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for ExtractAmount {
     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_reg_as.rs b/ergotree-ir/src/mir/extract_reg_as.rs
index 3511a7588..5d98ba1a0 100644
--- a/ergotree-ir/src/mir/extract_reg_as.rs
+++ b/ergotree-ir/src/mir/extract_reg_as.rs
@@ -1,8 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
-use crate::ir_ergo_box::IrBoxId;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -10,19 +5,17 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ExtractRegisterAs {
     /// Box
-    input: Box<Expr>,
+    pub input: Box<Expr>,
     /// Register id to extract value from
-    register_id: i8,
+    pub register_id: i8,
     /// Result type, to be wrapped in SOption
-    elem_tpe: SType,
+    pub elem_tpe: SType,
 }
 
 impl ExtractRegisterAs {
@@ -59,19 +52,6 @@ impl ExtractRegisterAs {
     }
 }
 
-impl Evaluable for ExtractRegisterAs {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let ir_box_id = self.input.eval(env, ctx)?.try_extract_into::<IrBoxId>()?;
-        Ok(Value::Opt(Box::new(
-            ctx.ctx
-                .box_arena
-                .get(&ir_box_id)?
-                .get_register(self.register_id)
-                .map(|c| c.v),
-        )))
-    }
-}
-
 impl SigmaSerializable for ExtractRegisterAs {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)?;
@@ -94,31 +74,9 @@ impl SigmaSerializable for ExtractRegisterAs {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::global_vars::GlobalVars;
-    use crate::mir::option_get::OptionGet;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
-    #[test]
-    fn eval_box_get_reg() {
-        let get_reg_expr: Expr = ExtractRegisterAs::new(
-            GlobalVars::SelfBox.into(),
-            0,
-            SType::SOption(SType::SLong.into()),
-        )
-        .unwrap()
-        .into();
-        let option_get_expr: Expr = OptionGet::new(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());
-    }
 
     #[test]
     fn ser_roundtrip() {
diff --git a/ergotree-ir/src/mir/extract_script_bytes.rs b/ergotree-ir/src/mir/extract_script_bytes.rs
index 9fe910604..56474a7b2 100644
--- a/ergotree-ir/src/mir/extract_script_bytes.rs
+++ b/ergotree-ir/src/mir/extract_script_bytes.rs
@@ -1,7 +1,3 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -11,11 +7,10 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ExtractScriptBytes {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl ExtractScriptBytes {
@@ -37,19 +32,6 @@ impl ExtractScriptBytes {
     }
 }
 
-impl Evaluable for ExtractScriptBytes {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        match input_v {
-            Value::CBox(b) => Ok(ctx.ctx.box_arena.get(&b)?.script_bytes().into()),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "Expected ExtractScriptBytes input to be Value::CBox, got {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for ExtractScriptBytes {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)
@@ -63,15 +45,9 @@ impl SigmaSerializable for ExtractScriptBytes {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
 
     #[test]
     fn ser_roundtrip() {
@@ -81,17 +57,4 @@ mod tests {
         .into();
         assert_eq![sigma_serialize_roundtrip(&e), e];
     }
-
-    #[test]
-    fn eval() {
-        let e: Expr = ExtractScriptBytes {
-            input: Box::new(GlobalVars::SelfBox.into()),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<Vec<u8>>(&e, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().script_bytes()
-        );
-    }
 }
diff --git a/ergotree-ir/src/mir/func_value.rs b/ergotree-ir/src/mir/func_value.rs
index 1daa15b1c..91b7b4356 100644
--- a/ergotree-ir/src/mir/func_value.rs
+++ b/ergotree-ir/src/mir/func_value.rs
@@ -1,9 +1,5 @@
 use std::io;
 
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -14,7 +10,6 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::val_def::ValId;
-use super::value::Value;
 
 #[cfg(test)]
 use proptest_derive::Arbitrary;
@@ -79,12 +74,6 @@ impl FuncValue {
     }
 }
 
-impl Evaluable for FuncValue {
-    fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        Ok(Value::FuncValue(self.clone()))
-    }
-}
-
 impl SigmaSerializable for FuncValue {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.args.sigma_serialize(w)?;
diff --git a/ergotree-ir/src/mir/if_op.rs b/ergotree-ir/src/mir/if_op.rs
index 489aec9f4..1d82ab54c 100644
--- a/ergotree-ir/src/mir/if_op.rs
+++ b/ergotree-ir/src/mir/if_op.rs
@@ -1,7 +1,4 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,16 +6,12 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::value::Value;
-
 /// If (lazy)
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct If {
-    condition: Box<Expr>,
-    true_branch: Box<Expr>,
-    false_branch: Box<Expr>,
+    pub condition: Box<Expr>,
+    pub true_branch: Box<Expr>,
+    pub false_branch: Box<Expr>,
 }
 
 impl If {
@@ -33,17 +26,6 @@ impl If {
     }
 }
 
-impl Evaluable for If {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let condition_v = self.condition.eval(env, ctx)?;
-        if condition_v.try_extract_into::<bool>()? {
-            self.true_branch.eval(env, ctx)
-        } else {
-            self.false_branch.eval(env, ctx)
-        }
-    }
-}
-
 impl SigmaSerializable for If {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.condition.sigma_serialize(w)?;
@@ -94,67 +76,11 @@ pub mod arbitrary {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-
-    use crate::eval::tests::eval_out_wo_ctx;
-    use crate::mir::bin_op::ArithOp;
-    use crate::mir::bin_op::BinOp;
+    use super::*;
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
-
-    use super::*;
-
     use proptest::prelude::*;
 
-    #[test]
-    fn eval() {
-        let expr: Expr = If {
-            condition: Expr::Const(true.into()).into(),
-            true_branch: Expr::Const(1i64.into()).into(),
-            false_branch: Expr::Const(2i64.into()).into(),
-        }
-        .into();
-        let res = eval_out_wo_ctx::<i64>(&expr);
-        assert_eq!(res, 1);
-    }
-
-    #[test]
-    fn eval_laziness_true_branch() {
-        let expr: Expr = If {
-            condition: Expr::Const(true.into()).into(),
-            true_branch: Expr::Const(1i64.into()).into(),
-            false_branch: Box::new(
-                BinOp {
-                    kind: ArithOp::Divide.into(),
-                    left: Box::new(Expr::Const(1i64.into())),
-                    right: Box::new(Expr::Const(0i64.into())),
-                }
-                .into(),
-            ),
-        }
-        .into();
-        let res = eval_out_wo_ctx::<i64>(&expr);
-        assert_eq!(res, 1);
-    }
-
-    #[test]
-    fn eval_laziness_false_branch() {
-        let expr: Expr = If {
-            condition: Expr::Const(false.into()).into(),
-            true_branch: Box::new(
-                BinOp {
-                    kind: ArithOp::Divide.into(),
-                    left: Box::new(Expr::Const(1i64.into())),
-                    right: Box::new(Expr::Const(0i64.into())),
-                }
-                .into(),
-            ),
-            false_branch: Expr::Const(1i64.into()).into(),
-        }
-        .into();
-        let res = eval_out_wo_ctx::<i64>(&expr);
-        assert_eq!(res, 1);
-    }
-
     proptest! {
 
         #[test]
diff --git a/ergotree-ir/src/mir/logical_not.rs b/ergotree-ir/src/mir/logical_not.rs
index 18cb3e4fd..f71285ecd 100644
--- a/ergotree-ir/src/mir/logical_not.rs
+++ b/ergotree-ir/src/mir/logical_not.rs
@@ -1,7 +1,4 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,13 +6,9 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::value::Value;
-
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct LogicalNot {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl LogicalNot {
@@ -42,14 +35,6 @@ impl SigmaSerializable for LogicalNot {
     }
 }
 
-impl Evaluable for LogicalNot {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let input_v_bool = input_v.try_extract_into::<bool>()?;
-        Ok((!input_v_bool).into())
-    }
-}
-
 #[cfg(feature = "arbitrary")]
 pub mod arbitrary {
     use crate::mir::expr::arbitrary::ArbExprParams;
@@ -76,9 +61,7 @@ pub mod arbitrary {
 
 #[cfg(test)]
 mod tests {
-
     use super::*;
-    use crate::eval::tests::eval_out_wo_ctx;
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
     use proptest::prelude::*;
@@ -91,11 +74,5 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
-        #[test]
-        fn eval(b in any::<bool>()) {
-            let expr: Expr = LogicalNot {input: Expr::Const(b.into()).into()}.into();
-            let res = eval_out_wo_ctx::<bool>(&expr);
-            prop_assert_eq!(res, !b);
-        }
     }
 }
diff --git a/ergotree-ir/src/mir/option_get.rs b/ergotree-ir/src/mir/option_get.rs
index 23f5120a0..6a39f0d09 100644
--- a/ergotree-ir/src/mir/option_get.rs
+++ b/ergotree-ir/src/mir/option_get.rs
@@ -1,10 +1,5 @@
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
-use crate::mir::value::Value;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -14,7 +9,7 @@ use crate::types::stype::SType;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct OptionGet {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl OptionGet {
@@ -45,21 +40,6 @@ impl OptionGet {
     }
 }
 
-impl Evaluable for OptionGet {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let v = self.input.eval(env, ctx)?;
-        match v {
-            Value::Opt(opt_v) => {
-                opt_v.ok_or_else(|| EvalError::NotFound("calling Option.get on None".to_string()))
-            }
-            _ => Err(EvalError::UnexpectedExpr(format!(
-                "Don't know how to eval OptM: {0:?}",
-                self
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for OptionGet {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)
@@ -73,37 +53,13 @@ impl SigmaSerializable for OptionGet {
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::expr::Expr;
     use crate::mir::extract_reg_as::ExtractRegisterAs;
     use crate::mir::global_vars::GlobalVars;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
     use crate::types::stype::SType;
 
-    use super::OptionGet;
-
-    #[test]
-    fn eval_get() {
-        let get_reg_expr: Expr = ExtractRegisterAs::new(
-            GlobalVars::SelfBox.into(),
-            0,
-            SType::SOption(SType::SLong.into()),
-        )
-        .unwrap()
-        .into();
-        let option_get_expr: Expr = OptionGet {
-            input: Box::new(get_reg_expr),
-        }
-        .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());
-    }
-
     #[test]
     fn ser_roundtrip() {
         let get_reg_expr: Expr = ExtractRegisterAs::new(
diff --git a/ergotree-ir/src/mir/or.rs b/ergotree-ir/src/mir/or.rs
index 316e4f51c..2c906994a 100644
--- a/ergotree-ir/src/mir/or.rs
+++ b/ergotree-ir/src/mir/or.rs
@@ -1,7 +1,4 @@
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -9,13 +6,9 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::constant::TryExtractInto;
-use super::expr::Expr;
-use super::value::Value;
-
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Or {
-    input: Box<Expr>,
+    pub input: Box<Expr>,
 }
 
 impl Or {
@@ -30,14 +23,6 @@ impl Or {
     }
 }
 
-impl Evaluable for Or {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        let input_v_bools = input_v.try_extract_into::<Vec<bool>>()?;
-        Ok(input_v_bools.iter().any(|b| *b).into())
-    }
-}
-
 impl SigmaSerializable for Or {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
         self.input.sigma_serialize(w)
@@ -77,17 +62,9 @@ pub mod arbitrary {
 
 #[cfg(test)]
 mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
+    use super::*;
     use crate::mir::expr::Expr;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
-    use proptest::collection;
     use proptest::prelude::*;
 
     proptest! {
@@ -98,12 +75,5 @@ mod tests {
             prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
         }
 
-        #[test]
-        fn eval(bools in collection::vec(any::<bool>(), 0..10)) {
-            let expr: Expr = Or {input: Expr::Const(bools.clone().into()).into()}.into();
-            let ctx = Rc::new(force_any_val::<Context>());
-            let res = eval_out::<bool>(&expr, ctx);
-            prop_assert_eq!(res, bools.iter().any(|b| *b));
-        }
     }
 }
diff --git a/ergotree-ir/src/mir/select_field.rs b/ergotree-ir/src/mir/select_field.rs
index 0f8d8b6cf..0b3c58dd3 100644
--- a/ergotree-ir/src/mir/select_field.rs
+++ b/ergotree-ir/src/mir/select_field.rs
@@ -1,9 +1,5 @@
 use std::convert::TryFrom;
 
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -15,7 +11,6 @@ use crate::types::stype::SType;
 
 use super::expr::Expr;
 use super::expr::InvalidArgumentError;
-use super::value::Value;
 
 /// Tuple field access index (1..255)
 #[derive(PartialEq, Eq, Debug, Copy, Clone)]
@@ -57,8 +52,8 @@ impl SigmaSerializable for TupleFieldIndex {
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SelectField {
-    input: Box<Expr>,
-    field_index: TupleFieldIndex,
+    pub input: Box<Expr>,
+    pub field_index: TupleFieldIndex,
 }
 
 impl SelectField {
@@ -99,24 +94,6 @@ impl SelectField {
     }
 }
 
-impl Evaluable for SelectField {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        match input_v {
-            Value::Tup(items) => items.get(self.field_index).cloned().ok_or_else(|| {
-                EvalError::NotFound(format!(
-                    "SelectField field index is out of bounds. Index: {0:?}, tuple: {1:?}",
-                    self.field_index, items
-                ))
-            }),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "expected SelectField input to be Value::Tup, got: {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 impl SigmaSerializable for SelectField {
     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/upcast.rs b/ergotree-ir/src/mir/upcast.rs
index 864ae53bb..a46c99c43 100644
--- a/ergotree-ir/src/mir/upcast.rs
+++ b/ergotree-ir/src/mir/upcast.rs
@@ -1,11 +1,6 @@
 //! Operators in ErgoTree
 
-use std::io::Error;
-
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::expr::Expr;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -13,13 +8,12 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::expr::Expr;
-use super::value::Value;
+use std::io::Error;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Upcast {
-    input: Box<Expr>,
-    tpe: SType,
+    pub input: Box<Expr>,
+    pub tpe: SType,
 }
 
 impl Upcast {
@@ -34,69 +28,6 @@ impl Upcast {
     }
 }
 
-fn upcast_to_long(in_v: Value) -> Result<Value, EvalError> {
-    match in_v {
-        Value::Byte(v) => Ok((v as i64).into()),
-        Value::Short(v) => Ok((v as i64).into()),
-        Value::Int(v) => Ok((v as i64).into()),
-        Value::Long(_) => Ok(in_v),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "Upcast: cannot upcast {0:?} to Long",
-            in_v
-        ))),
-    }
-}
-
-fn upcast_to_int(in_v: Value) -> Result<Value, EvalError> {
-    match in_v {
-        Value::Byte(v) => Ok((v as i32).into()),
-        Value::Short(v) => Ok((v as i32).into()),
-        Value::Int(_) => Ok(in_v),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "Upcast: cannot upcast {0:?} to Int",
-            in_v
-        ))),
-    }
-}
-
-fn upcast_to_short(in_v: Value) -> Result<Value, EvalError> {
-    match in_v {
-        Value::Byte(v) => Ok((v as i16).into()),
-        Value::Short(_) => Ok(in_v),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "Upcast: cannot upcast {0:?} to Short",
-            in_v
-        ))),
-    }
-}
-
-fn upcast_to_byte(in_v: Value) -> Result<Value, EvalError> {
-    match in_v {
-        Value::Byte(_) => Ok(in_v),
-        _ => Err(EvalError::UnexpectedValue(format!(
-            "Upcast: cannot upcast {0:?} to Byte",
-            in_v
-        ))),
-    }
-}
-
-impl Evaluable for Upcast {
-    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        let input_v = self.input.eval(env, ctx)?;
-        match self.tpe {
-            SType::SBigInt => todo!("upcasting to BigInt is not yet supported"),
-            SType::SLong => upcast_to_long(input_v),
-            SType::SInt => upcast_to_int(input_v),
-            SType::SShort => upcast_to_short(input_v),
-            SType::SByte => upcast_to_byte(input_v),
-            _ => Err(EvalError::UnexpectedValue(format!(
-                "Upcast: expected numeric value, got {0:?}",
-                input_v
-            ))),
-        }
-    }
-}
-
 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_use.rs b/ergotree-ir/src/mir/val_use.rs
index 69f7a4d1c..10313ba16 100644
--- a/ergotree-ir/src/mir/val_use.rs
+++ b/ergotree-ir/src/mir/val_use.rs
@@ -1,9 +1,6 @@
 use std::io;
 
-use crate::eval::env::Env;
-use crate::eval::EvalContext;
-use crate::eval::EvalError;
-use crate::eval::Evaluable;
+use super::val_def::ValId;
 use crate::serialization::op_code::OpCode;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
@@ -11,9 +8,6 @@ use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
 use crate::types::stype::SType;
 
-use super::val_def::ValId;
-use super::value::Value;
-
 /** Special node which represents a reference to ValDef in was introduced as result of CSE. */
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ValUse {
@@ -27,14 +21,6 @@ impl ValUse {
     }
 }
 
-impl Evaluable for ValUse {
-    fn eval(&self, env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        env.get(self.val_id).cloned().ok_or_else(|| {
-            EvalError::NotFound(format!("no value in env for id: {0:?}", self.val_id))
-        })
-    }
-}
-
 impl SigmaSerializable for ValUse {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         self.val_id.sigma_serialize(w)
diff --git a/ergotree-ir/src/serialization/bin_op.rs b/ergotree-ir/src/serialization/bin_op.rs
index b012a1791..71eb92c10 100644
--- a/ergotree-ir/src/serialization/bin_op.rs
+++ b/ergotree-ir/src/serialization/bin_op.rs
@@ -84,11 +84,12 @@ mod proptests {
 
 #[cfg(test)]
 mod tests {
+    use test_util::force_any_val_with;
+
     use super::*;
     use crate::mir::bin_op::RelationOp;
     use crate::mir::expr::arbitrary::ArbExprParams;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::test_util::force_any_val_with;
     use crate::types::stype::SType;
 
     fn test_ser_roundtrip(kind: BinOpKind, left: Expr, right: Expr) {
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 3bd32c849..889ab653f 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -179,13 +179,9 @@ impl SigmaSerializable for Expr {
 mod tests {
     use crate::address::AddressEncoder;
     use crate::address::NetworkPrefix;
-    use std::convert::TryInto;
-    use std::rc::Rc;
 
     use super::*;
-    use crate::eval::tests::eval_out_wo_ctx;
     use crate::serialization::sigma_serialize_roundtrip;
-    use crate::sigma_protocol::sigma_boolean::SigmaProp;
     use proptest::prelude::*;
 
     proptest! {
@@ -198,163 +194,6 @@ mod tests {
     }
 
     #[test]
-    fn simplified_age_usd_bank_contract() {
-        // simplified version of
-        // https://github.com/Emurgo/age-usd/tree/main/ageusd-smart-contracts/v0.4
-        /*
-
-          val rcDefaultPrice = 1000000L
-
-          val minStorageRent = 10000000L
-
-          val feePercent = 1
-
-          val HEIGHT = 377771
-
-          val coolingOffHeight: Int = 377770
-          val INF = 1000000000L
-
-          val longMax = 9223372036854775807L
-
-          val minReserveRatioPercent = 400L // percent
-          val defaultMaxReserveRatioPercent = 800L // percent
-
-            // val dataInput = CONTEXT.dataInputs(0)
-            // val validDataInput = dataInput.tokens(0)._1 == oraclePoolNFT
-            val validDataInput = true
-
-            // val bankBoxIn = SELF
-            // val bankBoxOut = OUTPUTS(0)
-
-            // val rateBox = dataInput
-            // val receiptBox = OUTPUTS(1)
-
-            // val rate = rateBox.R4[Long].get / 100
-            val rate = 100000000 / 100
-
-            // val scCircIn = bankBoxIn.R4[Long].get
-            val scCircIn = 100L
-            // val rcCircIn = bankBoxIn.R5[Long].get
-            val rcCircIn = 100L
-            // val bcReserveIn = bankBoxIn.value
-            val bcReserveIn = 100000000L
-
-            // val scTokensIn = bankBoxIn.tokens(0)._2
-            val scTokensIn = 100
-            // val rcTokensIn = bankBoxIn.tokens(1)._2
-            val rcTokensIn = 100
-
-            // val scCircOut = bankBoxOut.R4[Long].get
-            val scCircOut = 100
-            // val rcCircOut = bankBoxOut.R5[Long].get
-            val rcCircOut = 101
-
-            // val scTokensOut = bankBoxOut.tokens(0)._2
-            val scTokensOut = 100
-            // val rcTokensOut = bankBoxOut.tokens(1)._2
-            val rcTokensOut = 99
-
-            val totalScIn = scTokensIn + scCircIn
-            val totalScOut = scTokensOut + scCircOut
-
-            val totalRcIn = rcTokensIn + rcCircIn
-            val totalRcOut = rcTokensOut + rcCircOut
-
-            val rcExchange = rcTokensIn != rcTokensOut
-            val scExchange = scTokensIn != scTokensOut
-
-            val rcExchangeXorScExchange = (rcExchange || scExchange) && !(rcExchange && scExchange)
-
-            // val circDelta = receiptBox.R4[Long].get
-            val circDelta = 1L
-            // val bcReserveDelta = receiptBox.R5[Long].get
-            val bcReserveDelta = 1010000L
-
-            // val bcReserveOut = bankBoxOut.value
-            val bcReserveOut = 100000000L + 1010000L
-
-            val rcCircDelta = if (rcExchange) circDelta else 0L
-            val scCircDelta = if (rcExchange) 0L else circDelta
-
-            val validDeltas = (scCircIn + scCircDelta == scCircOut) &&
-                              (rcCircIn + rcCircDelta == rcCircOut) &&
-                              (bcReserveIn + bcReserveDelta == bcReserveOut)
-
-            val coinsConserved = totalRcIn == totalRcOut && totalScIn == totalScOut
-
-            // val tokenIdsConserved = bankBoxOut.tokens(0)._1 == bankBoxIn.tokens(0)._1 && // also ensures that at least one token exists
-            //                         bankBoxOut.tokens(1)._1 == bankBoxIn.tokens(1)._1 && // also ensures that at least one token exists
-            //                         bankBoxOut.tokens(2)._1 == bankBoxIn.tokens(2)._1    // also ensures that at least one token exists
-
-            val tokenIdsConserved = true
-
-            // val mandatoryRateConditions = rateBox.tokens(0)._1 == oraclePoolNFT
-            val mandatoryRateConditions = true
-            val mandatoryBankConditions = //bankBoxOut.value >= $minStorageRent &&
-                                          rcExchangeXorScExchange &&
-                                          coinsConserved &&
-                                          validDeltas &&
-                                          tokenIdsConserved
-
-            // exchange equations
-            val bcReserveNeededOut = scCircOut * rate
-            val bcReserveNeededIn = scCircIn * rate
-            val liabilitiesIn = max(min(bcReserveIn, bcReserveNeededIn), 0)
-
-
-            val maxReserveRatioPercent = if (HEIGHT > coolingOffHeight) defaultMaxReserveRatioPercent else INF
-
-            val reserveRatioPercentOut =
-                if (bcReserveNeededOut == 0) maxReserveRatioPercent else bcReserveOut * 100 / bcReserveNeededOut
-
-            val validReserveRatio = if (scExchange) {
-              if (scCircDelta > 0) {
-                reserveRatioPercentOut >= minReserveRatioPercent
-              } else true
-            } else {
-              if (rcCircDelta > 0) {
-                reserveRatioPercentOut <= maxReserveRatioPercent
-              } else {
-                reserveRatioPercentOut >= minReserveRatioPercent
-              }
-            }
-
-            val brDeltaExpected = if (scExchange) { // sc
-              val liableRate = if (scCircIn == 0) longMax else liabilitiesIn / scCircIn
-              val scNominalPrice = min(rate, liableRate)
-              scNominalPrice * scCircDelta
-            } else { // rc
-              val equityIn = bcReserveIn - liabilitiesIn
-              val equityRate = if (rcCircIn == 0) rcDefaultPrice else equityIn / rcCircIn
-              val rcNominalPrice = if (equityIn == 0) rcDefaultPrice else equityRate
-              rcNominalPrice * rcCircDelta
-            }
-
-            val fee = brDeltaExpected * feePercent / 100
-
-            val actualFee = if (fee < 0) {fee * -1} else fee
-            // actualFee is always positive, irrespective of brDeltaExpected
-
-            val brDeltaExpectedWithFee = brDeltaExpected + actualFee
-
-            mandatoryRateConditions &&
-            mandatoryBankConditions &&
-            bcReserveDelta == brDeltaExpectedWithFee &&
-            validReserveRatio &&
-            validDataInput
-        }
-                 */
-        let p2s_addr_str = "7Nq5tKsVYCgneNgEfA2BJKwGsWozezNLhCNsRBihcHVFkDTuTThd4Qt1bi7NfCK1HuuVfjksMrEftV6MEFajjuyp1TMD2PX7SYWvkg9zH4CtgpdoBjekCNXs5XawxXnW6FT7GCqXTpJUP2TkkuqBh1df99PTigehys36uZz9wQnkrJXrv3mw3Yy4CM622qe5wdqLtpEonjazEmsw8weqEYegDyfJnswDvDkLPXtcCB86i19jik4fnSTtCcYj3jpWCQ7WL5dZn1ivs5JGRsR2ioNCRiZd3Gu1zJBgbHkMg41Z6VeCRWXjGY99BUtgtQiepSHGHajFCVcFAHhVxccdVUPCxGeEL6c2dNx6qzEkVfTfHs5qBgJewR8KCZTCVTurNBHeqCSVdxnfFvhW3f72cNrae5E1UhTAXU2iX4LZMHQsKyefY24Aq1b1srTyRWLpixjbcezFqA2TKjGSn1p1ruxbR7AQpW24ByPKT9sFE9ii4qNeXDnLcGtAAGS9FC5SD1s516a4NCu6v9zZfTvRKGkCwt78J8DEVnhTbttjcsvqFsUXQrvAv7TGVsaT4mL6B7F5BhRoZwFkgRXqFUVCWvgqJrwwjFRtbc5aZz";
-        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
-        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
-        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
-        dbg!(&script);
-        let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
-            .try_into()
-            .unwrap();
-        assert!(res);
-    }
-
     #[test]
     fn full_age_usd_bank_contract() {
         // almost full version of
diff --git a/ergotree-ir/src/sigma_protocol.rs b/ergotree-ir/src/sigma_protocol.rs
index 7e4d577bc..38d0f2b8f 100644
--- a/ergotree-ir/src/sigma_protocol.rs
+++ b/ergotree-ir/src/sigma_protocol.rs
@@ -1,136 +1,2 @@
-//! Sigma protocols
-
 pub mod dlog_group;
-pub mod private_input;
-pub mod prover;
 pub mod sigma_boolean;
-pub mod verifier;
-
-mod challenge;
-mod dlog_protocol;
-mod fiat_shamir;
-mod sig_serializer;
-mod unchecked_tree;
-mod unproven_tree;
-
-use k256::Scalar;
-
-use dlog_protocol::FirstDlogProverMessage;
-use sigma_boolean::{ProveDlog, SigmaBoolean, SigmaProofOfKnowledgeTree};
-use std::convert::TryInto;
-use unchecked_tree::{UncheckedSigmaTree, UncheckedTree};
-use unproven_tree::{UnprovenLeaf, UnprovenSchnorr, UnprovenTree};
-
-use self::challenge::Challenge;
-
-/** The message sent by a prover to its associated verifier as part of a sigma protocol interaction. */
-pub trait ProverMessage {
-    /// serialized message
-    fn bytes(&self) -> Vec<u8>;
-}
-
-/** First message from the prover (message `a` of `SigmaProtocol`)*/
-pub enum FirstProverMessage {
-    /// Discrete log
-    FirstDlogProverMessage(FirstDlogProverMessage),
-    /// DH tupl
-    FirstDHTProverMessage,
-}
-
-impl ProverMessage for FirstProverMessage {
-    fn bytes(&self) -> Vec<u8> {
-        match self {
-            FirstProverMessage::FirstDlogProverMessage(fdpm) => fdpm.bytes(),
-            FirstProverMessage::FirstDHTProverMessage => todo!(),
-        }
-    }
-}
-
-/// Proof tree
-pub enum ProofTree {
-    /// Unchecked tree
-    UncheckedTree(UncheckedTree),
-    /// Unproven tree
-    UnprovenTree(UnprovenTree),
-}
-
-impl ProofTree {
-    /// Create a new proof tree with a new challenge
-    pub fn with_challenge(&self, challenge: Challenge) -> ProofTree {
-        match self {
-            ProofTree::UncheckedTree(_) => todo!(),
-            ProofTree::UnprovenTree(ut) => match ut {
-                UnprovenTree::UnprovenLeaf(ul) => match ul {
-                    UnprovenLeaf::UnprovenSchnorr(us) => ProofTree::UnprovenTree(
-                        UnprovenSchnorr {
-                            challenge_opt: Some(challenge),
-                            ..us.clone()
-                        }
-                        .into(),
-                    ),
-                },
-            },
-        }
-    }
-}
-
-impl<T: Into<UncheckedTree>> From<T> for ProofTree {
-    fn from(t: T) -> Self {
-        ProofTree::UncheckedTree(t.into())
-    }
-}
-
-/// Proof tree leaf
-pub trait ProofTreeLeaf {
-    /// Get proposition
-    fn proposition(&self) -> SigmaBoolean;
-
-    /// Get commitment
-    fn commitment_opt(&self) -> Option<FirstProverMessage>;
-}
-
-/** Size of the binary representation of any group element (2 ^ groupSizeBits == <number of elements in a group>) */
-pub const GROUP_SIZE_BITS: usize = 256;
-/** Number of bytes to represent any group element as byte array */
-pub const GROUP_SIZE: usize = GROUP_SIZE_BITS / 8;
-
-/// Byte array of Group size (32 bytes)
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct GroupSizedBytes(pub Box<[u8; GROUP_SIZE]>);
-
-impl From<GroupSizedBytes> for Scalar {
-    fn from(b: GroupSizedBytes) -> Self {
-        let sl: &[u8] = b.0.as_ref();
-        Scalar::from_bytes_reduced(sl.try_into().expect(""))
-    }
-}
-
-impl From<&[u8; GROUP_SIZE]> for GroupSizedBytes {
-    fn from(b: &[u8; GROUP_SIZE]) -> Self {
-        GroupSizedBytes(Box::new(*b))
-    }
-}
-
-/** A size of challenge in Sigma protocols, in bits.
- * If this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others.
- * We get the challenge by reducing hash function output to proper value.
- */
-pub const SOUNDNESS_BITS: usize = 192;
-/// A size of challenge in Sigma protocols, in bytes
-pub const SOUNDNESS_BYTES: usize = SOUNDNESS_BITS / 8;
-
-#[cfg(test)]
-#[cfg(feature = "arbitrary")]
-mod tests {
-    use super::*;
-
-    #[allow(clippy::assertions_on_constants)]
-    #[test]
-    fn ensure_soundness_bits() {
-        // see SOUNDNESS_BITS doc comment
-        assert!(SOUNDNESS_BITS < GROUP_SIZE_BITS);
-        // blake2b hash function requirements
-        assert!(SOUNDNESS_BYTES * 8 <= 512);
-        assert!(SOUNDNESS_BYTES % 8 == 0);
-    }
-}
diff --git a/ergotree-ir/src/sigma_protocol/dlog_group.rs b/ergotree-ir/src/sigma_protocol/dlog_group.rs
index 5aa763a85..a5d631199 100644
--- a/ergotree-ir/src/sigma_protocol/dlog_group.rs
+++ b/ergotree-ir/src/sigma_protocol/dlog_group.rs
@@ -27,7 +27,7 @@ use std::{
     ops::{Add, Mul, Neg},
 };
 
-use super::private_input::DlogProverInput;
+// use super::private_input::DlogProverInput;
 
 /// Elliptic curve point
 #[derive(PartialEq, Debug, Clone)]
@@ -87,11 +87,11 @@ pub fn exponentiate(base: &EcPoint, exponent: &Scalar) -> EcPoint {
     }
 }
 
-/// Creates a random member of this Dlog group
-pub fn random_element() -> EcPoint {
-    let sk = DlogProverInput::random();
-    exponentiate(&generator(), &sk.w)
-}
+// /// Creates a random member of this Dlog group
+// pub fn random_element() -> EcPoint {
+//     let sk = DlogProverInput::random();
+//     exponentiate(&generator(), &sk.w)
+// }
 
 /// Creates a random scalar, a big-endian integer in the range [0, n), where n is group order
 pub fn random_scalar_in_group_range() -> Scalar {
@@ -145,7 +145,11 @@ pub mod arbitrary {
         type Strategy = BoxedStrategy<Self>;
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            prop_oneof![Just(generator()), Just(identity()), Just(random_element()),].boxed()
+            prop_oneof![
+                Just(generator()),
+                Just(identity()), /*Just(random_element()),*/
+            ]
+            .boxed()
         }
     }
 }
diff --git a/ergoscript-compiler/src/mir/type_check.rs b/ergotree-ir/src/type_check.rs
similarity index 95%
rename from ergoscript-compiler/src/mir/type_check.rs
rename to ergotree-ir/src/type_check.rs
index bb7e40267..943f4f769 100644
--- a/ergoscript-compiler/src/mir/type_check.rs
+++ b/ergotree-ir/src/type_check.rs
@@ -1,4 +1,4 @@
-use ergotree_ir::mir::expr::Expr;
+use crate::mir::expr::Expr;
 
 #[derive(Debug, PartialEq)]
 pub struct TypeCheckError {
diff --git a/ergotree-ir/src/types.rs b/ergotree-ir/src/types.rs
index 786290f4e..ff6abcd8e 100644
--- a/ergotree-ir/src/types.rs
+++ b/ergotree-ir/src/types.rs
@@ -2,10 +2,10 @@
 
 pub mod stype;
 
-pub(crate) mod sbox;
-pub(crate) mod scontext;
-pub(crate) mod sfunc;
-pub(crate) mod smethod;
-pub(crate) mod stuple;
-pub(crate) mod stype_companion;
-pub(crate) mod stype_param;
+pub mod sbox;
+pub mod scontext;
+pub mod sfunc;
+pub mod smethod;
+pub mod stuple;
+pub mod stype_companion;
+pub mod stype_param;
diff --git a/ergotree-ir/src/types/sbox.rs b/ergotree-ir/src/types/sbox.rs
index c68a4b4da..ab2bdc91c 100644
--- a/ergotree-ir/src/types/sbox.rs
+++ b/ergotree-ir/src/types/sbox.rs
@@ -1,10 +1,4 @@
-use crate::eval::EvalError;
-use crate::ir_ergo_box::IrBoxId;
-use crate::mir::constant::TryExtractInto;
-use crate::mir::value::Value;
-
 use super::sfunc::SFunc;
-use super::smethod::EvalFn;
 use super::smethod::MethodId;
 use super::smethod::SMethod;
 use super::smethod::SMethodDesc;
@@ -16,8 +10,13 @@ use super::stype_companion::TypeId;
 use super::stype_param::STypeVar;
 use lazy_static::lazy_static;
 
+pub const TYPE_ID: TypeId = TypeId(99);
+pub const VALUE_METHOD_ID: MethodId = MethodId(1);
+pub const GET_REG_METHOD_ID: MethodId = MethodId(7);
+pub const TOKENS_METHOD_ID: MethodId = MethodId(8);
+
 static S_BOX_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
-    type_id: TypeId(99),
+    type_id: TYPE_ID,
     type_name: "Box",
 };
 
@@ -32,69 +31,36 @@ lazy_static! {
     );
 }
 
-static VALUE_EVAL_FN: EvalFn = |ctx, obj, _args| {
-    Ok(Value::Long(
-        obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.box_arena)?
-            .value(),
-    ))
-};
-
 lazy_static! {
     static ref VALUE_METHOD_DESC: SMethodDesc = SMethodDesc {
-        method_id: MethodId(1),
+        method_id: VALUE_METHOD_ID,
         name: "value",
         tpe: SType::SFunc(SFunc {
             t_dom: vec![SType::SBox],
             t_range: Box::new(SType::SLong),
             tpe_params: vec![],
         }),
-        eval_fn: VALUE_EVAL_FN,
     };
     pub static ref VALUE_METHOD: SMethod = SMethod::new(&S_BOX_TYPE_COMPANION, &VALUE_METHOD_DESC,);
 }
 
-static GET_REG_EVAL_FN: EvalFn = |ctx, obj, args| {
-    Ok(Value::Opt(Box::new(
-        obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.box_arena)?
-            .get_register(
-                args.get(0)
-                    .cloned()
-                    .ok_or_else(|| EvalError::NotFound("register index is missing".to_string()))?
-                    .try_extract_into::<i8>()?,
-            )
-            .map(|c| c.v),
-    )))
-};
-
 lazy_static! {
     static ref GET_REG_METHOD_DESC: SMethodDesc = SMethodDesc {
-        method_id: MethodId(7),
+        method_id: GET_REG_METHOD_ID,
         name: "getReg",
         tpe: SType::SFunc(SFunc {
             t_dom: vec![SType::SBox, SType::SByte],
             t_range: Box::new(SType::SOption(Box::new(SType::STypeVar(STypeVar::T)))),
             tpe_params: vec![],
         }),
-        eval_fn: GET_REG_EVAL_FN,
     };
     pub static ref GET_REG_METHOD: SMethod =
         SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
 }
 
-static TOKENS_EVAL_FN: EvalFn = |ctx, obj, _args| {
-    let res: Value = obj
-        .try_extract_into::<IrBoxId>()?
-        .get_box(&ctx.box_arena)?
-        .tokens()
-        .into();
-    Ok(res)
-};
-
 lazy_static! {
     static ref TOKENS_METHOD_DESC: SMethodDesc = SMethodDesc {
-        method_id: MethodId(8),
+        method_id: TOKENS_METHOD_ID,
         name: "tokens",
         tpe: SType::SFunc(SFunc {
             t_dom: vec![SType::SBox],
@@ -104,51 +70,7 @@ lazy_static! {
             ))))),
             tpe_params: vec![],
         }),
-        eval_fn: TOKENS_EVAL_FN,
     };
     pub static ref TOKENS_METHOD: SMethod =
         SMethod::new(&S_BOX_TYPE_COMPANION, &TOKENS_METHOD_DESC,);
 }
-
-#[cfg(test)]
-#[cfg(feature = "arbitrary")]
-mod tests {
-    use std::rc::Rc;
-
-    use crate::eval::context::Context;
-    use crate::eval::tests::eval_out;
-    use crate::mir::expr::Expr;
-    use crate::mir::global_vars::GlobalVars;
-    use crate::mir::property_call::PropertyCall;
-    use crate::test_util::force_any_val;
-
-    use super::*;
-
-    #[test]
-    fn eval_box_value() {
-        let expr: Expr = PropertyCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: VALUE_METHOD.clone(),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<i64>(&expr, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().value()
-        );
-    }
-
-    #[test]
-    fn eval_box_tokens() {
-        let expr: Expr = PropertyCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: TOKENS_METHOD.clone(),
-        }
-        .into();
-        let ctx = Rc::new(force_any_val::<Context>());
-        assert_eq!(
-            eval_out::<Vec<(Vec<i8>, i64)>>(&expr, ctx.clone()),
-            ctx.self_box.get_box(&ctx.box_arena).unwrap().tokens()
-        );
-    }
-}
diff --git a/ergotree-ir/src/types/scontext.rs b/ergotree-ir/src/types/scontext.rs
index 0d8f2696d..ce440a1c2 100644
--- a/ergotree-ir/src/types/scontext.rs
+++ b/ergotree-ir/src/types/scontext.rs
@@ -1,8 +1,4 @@
-use crate::mir::value::CollKind;
-use crate::mir::value::Value;
-
 use super::sfunc::SFunc;
-use super::smethod::EvalFn;
 use super::smethod::MethodId;
 use super::smethod::SMethod;
 use super::smethod::SMethodDesc;
@@ -15,34 +11,23 @@ use lazy_static::lazy_static;
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SContext();
 
+pub const TYPE_ID: TypeId = TypeId(101);
+
 static S_CONTEXT_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
-    type_id: TypeId(101),
+    type_id: TYPE_ID,
     type_name: "Context",
 };
 
-static DATA_INPUTS_EVAL_FN: EvalFn = |ctx, _obj, _args| {
-    // TODO: check that obj is Value::Context
-    Ok(Value::Coll(CollKind::WrappedColl {
-        items: ctx
-            .data_inputs
-            .clone()
-            .into_iter()
-            .map(Value::CBox)
-            .collect(),
-        elem_tpe: SType::SBox,
-    }))
-};
-
+pub const DATA_INPUTS_PROPERTY_METHOD_ID: MethodId = MethodId(1);
 lazy_static! {
     static ref DATA_INPUTS_PROPERTY_METHOD_DESC: SMethodDesc = SMethodDesc {
-        method_id: MethodId(1),
+        method_id: DATA_INPUTS_PROPERTY_METHOD_ID,
         name: "dataInputs",
         tpe: SType::SFunc(SFunc {
             t_dom: vec![SType::SContext(SContext())],
             t_range: Box::new(SType::SColl(Box::new(SType::SBox))),
             tpe_params: vec![],
         }),
-        eval_fn: DATA_INPUTS_EVAL_FN,
     };
 }
 
diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
index 78e1c3456..f1c68c5e1 100644
--- a/ergotree-ir/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -1,13 +1,8 @@
-use std::io::Error;
-use std::rc::Rc;
-
-use crate::eval::context::Context;
-use crate::eval::EvalError;
-use crate::mir::value::Value;
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
 use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
+use std::io::Error;
 
 use super::stype::SType;
 use super::stype_companion::STypeCompanion;
@@ -63,20 +58,13 @@ impl SMethod {
     pub fn method_id(&self) -> MethodId {
         self.method_raw.method_id.clone()
     }
-
-    pub fn eval_fn(&self) -> EvalFn {
-        self.method_raw.eval_fn
-    }
 }
 
-pub type EvalFn = fn(ctx: Rc<Context>, Value, Vec<Value>) -> Result<Value, EvalError>;
-
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct SMethodDesc {
     pub name: &'static str,
     pub method_id: MethodId,
     pub tpe: SType,
-    pub eval_fn: EvalFn,
 }
 
 impl SMethodDesc {


Test patch:
diff --git a/ergotree-ir/src/test_util.rs b/ergotree-ir/src/test_util.rs
deleted file mode 100644
index a6ecaa597..000000000
--- a/ergotree-ir/src/test_util.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use proptest::strategy::ValueTree;
-use proptest::test_runner::TestRunner;
-use proptest::{arbitrary::Arbitrary, prelude::*};
-
-pub fn force_any_val<T: Arbitrary>() -> T {
-    let mut runner = TestRunner::default();
-    any::<T>().new_tree(&mut runner).unwrap().current()
-}
-
-pub fn force_any_val_with<T: Arbitrary>(args: T::Parameters) -> T {
-    let mut runner = TestRunner::default();
-    any_with::<T>(args).new_tree(&mut runner).unwrap().current()
-}
diff --git a/test-util/Cargo.toml b/test-util/Cargo.toml
new file mode 100644
index 000000000..0d6a2e23f
--- /dev/null
+++ b/test-util/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "test-util"
+version = "0.1.0"
+license = "CC0-1.0"
+authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
+edition = "2018"
+description = "Test utilities for Ergo"
+repository = "https://github.com/ergoplatform/sigma-rust"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+proptest = {version = "1.0.0"}
+proptest-derive = {version = "0.3.0"}
+
+[dev-dependencies]
diff --git a/ergo-lib/src/test_util.rs b/test-util/src/lib.rs
similarity index 100%
rename from ergo-lib/src/test_util.rs
rename to test-util/src/lib.rs