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

Issue:
Interpreter: Frequently used `Collection` methods
indexOf
flatMap
forall



Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-jni/src/main/rust/exception.rs b/bindings/ergo-lib-jni/src/main/rust/exception.rs
index 6ce9f88bf..b54acc8d8 100644
--- a/bindings/ergo-lib-jni/src/main/rust/exception.rs
+++ b/bindings/ergo-lib-jni/src/main/rust/exception.rs
@@ -90,6 +90,7 @@ mod tests {
     use super::*;
     use std::error::Error;
     use std::panic;
+    use std::panic::panic_any;
 
     #[test]
     fn str_any() {
@@ -120,6 +121,6 @@ mod tests {
     }
 
     fn panic_error<T: Send + 'static>(val: T) -> Box<dyn Any + Send> {
-        panic::catch_unwind(panic::AssertUnwindSafe(|| panic!(val))).unwrap_err()
+        panic::catch_unwind(panic::AssertUnwindSafe(|| panic_any(val))).unwrap_err()
     }
 }
diff --git a/bindings/ergo-lib-wasm/Cargo.toml b/bindings/ergo-lib-wasm/Cargo.toml
index a9771b403..0ae37c53b 100644
--- a/bindings/ergo-lib-wasm/Cargo.toml
+++ b/bindings/ergo-lib-wasm/Cargo.toml
@@ -18,6 +18,7 @@ serde = { version = "1.0", features = ["derive"]}
 ergo-lib = { version = "^0.8.0", path = "../../ergo-lib" }
 serde_json = "1.0"
 js-sys = "0.3"
+syn = "=1.0.65" # workaround for https://github.com/rustwasm/wasm-bindgen/issues/2508
 
 # used in elliptic-curve(in ergo-lib), compiled here with WASM support
 getrandom = {version = "0.1", features = ["wasm-bindgen"]}
@@ -30,21 +31,20 @@ console_error_panic_hook = { version = "0.1.6", optional = true }
 derive_more = "0.99"
 
 [dependencies.wasm-bindgen]
-version = "0.2"
+version = "0.2.72"
 features = ["serde-serialize"]
 
 [dev-dependencies]
-wasm-bindgen-test = "0.3.10"
-proptest-derive = "0.2"
-
-[dev-dependencies.proptest]
-# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
-version = "0.10"
-# The default feature set includes things like process forking which are not
-# supported in Web Assembly.
-default-features = false
-# Enable using the `std` crate.
-features = ["std"]
+wasm-bindgen-test = "0.3.22"
+
+# [dev-dependencies.proptest]
+# # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
+# version = "0.10"
+# # The default feature set includes things like process forking which are not
+# # supported in Web Assembly.
+# default-features = false
+# # Enable using the `std` crate.
+# features = ["std"]
 
 [package.metadata.wasm-pack.profile.release]
 # wasm-opt = ["-O", "--enable-mutable-globals"]
diff --git a/bindings/ergo-lib-wasm/src/address.rs b/bindings/ergo-lib-wasm/src/address.rs
index 132116463..b275a6e6c 100644
--- a/bindings/ergo-lib-wasm/src/address.rs
+++ b/bindings/ergo-lib-wasm/src/address.rs
@@ -7,6 +7,9 @@ use wasm_bindgen::prelude::*;
 
 use crate::ergo_tree::ErgoTree;
 
+extern crate derive_more;
+use derive_more::{From, Into};
+
 /// Network type
 #[wasm_bindgen]
 #[repr(u8)]
@@ -43,9 +46,9 @@ impl From<ergo_lib::ergotree_ir::address::NetworkPrefix> for NetworkPrefix {
 #[repr(u8)]
 pub enum AddressTypePrefix {
     /// 0x01 - Pay-to-PublicKey(P2PK) address
-    P2PK = 1,
+    P2Pk = 1,
     /// 0x02 - Pay-to-Script-Hash(P2SH)
-    Pay2SH = 2,
+    Pay2Sh = 2,
     /// 0x03 - Pay-to-Script(P2S)
     Pay2S = 3,
 }
@@ -54,8 +57,8 @@ impl From<AddressTypePrefix> for ergo_lib::ergotree_ir::address::AddressTypePref
     fn from(v: AddressTypePrefix) -> Self {
         use ergo_lib::ergotree_ir::address::AddressTypePrefix::*;
         match v {
-            AddressTypePrefix::P2PK => P2PK,
-            AddressTypePrefix::Pay2SH => Pay2SH,
+            AddressTypePrefix::P2Pk => P2Pk,
+            AddressTypePrefix::Pay2Sh => Pay2Sh,
             AddressTypePrefix::Pay2S => Pay2S,
         }
     }
@@ -65,8 +68,8 @@ impl From<ergo_lib::ergotree_ir::address::AddressTypePrefix> for AddressTypePref
     fn from(v: ergo_lib::ergotree_ir::address::AddressTypePrefix) -> Self {
         use AddressTypePrefix::*;
         match v {
-            ergo_lib::ergotree_ir::address::AddressTypePrefix::P2PK => P2PK,
-            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2SH => Pay2SH,
+            ergo_lib::ergotree_ir::address::AddressTypePrefix::P2Pk => P2Pk,
+            ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2Sh => Pay2Sh,
             ergo_lib::ergotree_ir::address::AddressTypePrefix::Pay2S => Pay2S,
         }
     }
@@ -116,7 +119,7 @@ impl From<ergo_lib::ergotree_ir::address::AddressTypePrefix> for AddressTypePref
  *
  */
 #[wasm_bindgen]
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct Address(ergo_lib::ergotree_ir::address::Address);
 
 #[wasm_bindgen]
@@ -198,7 +201,7 @@ impl Address {
     /// Create an address from a public key
     pub fn from_public_key(bytes: &[u8]) -> Result<Address, JsValue> {
         EcPoint::sigma_parse_bytes(bytes.to_vec())
-            .map(|point| ergo_lib::ergotree_ir::address::Address::P2PK(ProveDlog::new(point)))
+            .map(|point| ergo_lib::ergotree_ir::address::Address::P2Pk(ProveDlog::new(point)))
             .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
@@ -212,18 +215,6 @@ impl Address {
     }
 }
 
-impl Into<ergo_lib::ergotree_ir::address::Address> for Address {
-    fn into(self) -> ergo_lib::ergotree_ir::address::Address {
-        self.0
-    }
-}
-
-impl From<ergo_lib::ergotree_ir::address::Address> for Address {
-    fn from(a: ergo_lib::ergotree_ir::address::Address) -> Self {
-        Address(a)
-    }
-}
-
 /// Combination of an Address with a network
 /// These two combined together form a base58 encoding
 #[wasm_bindgen]
diff --git a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
index 6e654e7a7..b4f89b1cd 100644
--- a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
@@ -2,9 +2,12 @@
 use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
+extern crate derive_more;
+use derive_more::{From, Into};
+
 /// TBD
 #[wasm_bindgen]
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct ErgoStateContext(chain::ergo_state_context::ErgoStateContext);
 
 #[wasm_bindgen]
@@ -14,9 +17,3 @@ impl ErgoStateContext {
         ErgoStateContext(chain::ergo_state_context::ErgoStateContext::dummy())
     }
 }
-
-impl Into<chain::ergo_state_context::ErgoStateContext> for ErgoStateContext {
-    fn into(self) -> chain::ergo_state_context::ErgoStateContext {
-        self.0
-    }
-}
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 86cad7529..2e70d8e47 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
+
+### Added 
+- `Coll.indexOf`, `flatMap`, `forall` IR nodes (evaluation, serialization) [#220](https://github.com/ergoplatform/sigma-rust/pull/220);
+
 ## [0.8.0] - 2021-03-22
 
 ### Added 
diff --git a/ergo-lib/src/chain/base16_bytes.rs b/ergo-lib/src/chain/base16_bytes.rs
index 9f23e0199..4ad55f851 100644
--- a/ergo-lib/src/chain/base16_bytes.rs
+++ b/ergo-lib/src/chain/base16_bytes.rs
@@ -6,11 +6,13 @@ use ergotree_ir::serialization::SigmaSerializable;
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
+extern crate derive_more;
+use derive_more::{From, Into};
 
 /// Transitioning type for Base16 encoded bytes
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "json", serde(into = "String"))]
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
 pub struct Base16EncodedBytes(String);
 
 impl Base16EncodedBytes {
@@ -20,12 +22,6 @@ impl Base16EncodedBytes {
     }
 }
 
-impl Into<String> for Base16EncodedBytes {
-    fn into(self) -> String {
-        self.0
-    }
-}
-
 /// Transitioning type for Base16 decoded bytes
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "json", serde(try_from = "String"))]
diff --git a/ergo-lib/src/chain/digest32.rs b/ergo-lib/src/chain/digest32.rs
index 2ba90a884..25523bc61 100644
--- a/ergo-lib/src/chain/digest32.rs
+++ b/ergo-lib/src/chain/digest32.rs
@@ -44,9 +44,9 @@ impl From<[u8; Digest32::SIZE]> for Digest32 {
     }
 }
 
-impl Into<Base16EncodedBytes> for Digest32 {
-    fn into(self) -> Base16EncodedBytes {
-        Base16EncodedBytes::new(self.0.as_ref())
+impl From<Digest32> for Base16EncodedBytes {
+    fn from(v: Digest32) -> Self {
+        Base16EncodedBytes::new(v.0.as_ref())
     }
 }
 
@@ -70,9 +70,9 @@ impl TryFrom<Base16DecodedBytes> for Digest32 {
     }
 }
 
-impl Into<String> for Digest32 {
-    fn into(self) -> String {
-        let bytes: Base16EncodedBytes = self.into();
+impl From<Digest32> for String {
+    fn from(v: Digest32) -> Self {
+        let bytes: Base16EncodedBytes = v.into();
         bytes.into()
     }
 }
diff --git a/ergo-lib/src/chain/ergo_box/box_id.rs b/ergo-lib/src/chain/ergo_box/box_id.rs
index 6d66849f0..1a52e0285 100644
--- a/ergo-lib/src/chain/ergo_box/box_id.rs
+++ b/ergo-lib/src/chain/ergo_box/box_id.rs
@@ -38,9 +38,9 @@ impl From<Digest32> for BoxId {
 }
 
 #[cfg(feature = "json")]
-impl Into<String> for BoxId {
-    fn into(self) -> String {
-        self.0.into()
+impl From<BoxId> for String {
+    fn from(v: BoxId) -> Self {
+        v.0.into()
     }
 }
 
diff --git a/ergo-lib/src/chain/ergo_box/box_value.rs b/ergo-lib/src/chain/ergo_box/box_value.rs
index 3bd1728bc..03f5ef908 100644
--- a/ergo-lib/src/chain/ergo_box/box_value.rs
+++ b/ergo-lib/src/chain/ergo_box/box_value.rs
@@ -204,6 +204,10 @@ pub mod tests {
     use super::*;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 
+    extern crate derive_more;
+    use derive_more::{From, Into};
+
+    #[derive(Debug, From, Into)]
     pub struct ArbBoxValueRange(Range<u64>);
 
     impl Default for ArbBoxValueRange {
@@ -212,12 +216,6 @@ pub mod tests {
         }
     }
 
-    impl Into<ArbBoxValueRange> for Range<u64> {
-        fn into(self) -> ArbBoxValueRange {
-            ArbBoxValueRange(self)
-        }
-    }
-
     impl Arbitrary for BoxValue {
         type Parameters = ArbBoxValueRange;
         type Strategy = BoxedStrategy<Self>;
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergo-lib/src/chain/ergo_box/register.rs
index 79be12768..b39cabf90 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergo-lib/src/chain/ergo_box/register.rs
@@ -101,9 +101,9 @@ impl NonMandatoryRegisterId {
     }
 }
 
-impl Into<String> for NonMandatoryRegisterId {
-    fn into(self) -> String {
-        format!("R{}", self as u8)
+impl From<NonMandatoryRegisterId> for String {
+    fn from(v: NonMandatoryRegisterId) -> Self {
+        format!("R{}", v as u8)
     }
 }
 
@@ -223,20 +223,18 @@ pub enum NonMandatoryRegistersError {
     NonDenselyPacked(u8),
 }
 
-impl Into<HashMap<NonMandatoryRegisterId, Base16EncodedBytes>> for NonMandatoryRegisters {
-    fn into(self) -> HashMap<NonMandatoryRegisterId, Base16EncodedBytes> {
-        self.0
-            .into_iter()
+impl From<NonMandatoryRegisters> for HashMap<NonMandatoryRegisterId, Base16EncodedBytes> {
+    fn from(v: NonMandatoryRegisters) -> Self {
+        v.0.into_iter()
             .enumerate()
             .map(|(i, c)| (NonMandatoryRegisterId::get_by_zero_index(i), c.into()))
             .collect()
     }
 }
 
-impl Into<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegisters {
-    fn into(self) -> HashMap<NonMandatoryRegisterId, Constant> {
-        self.0
-            .into_iter()
+impl From<NonMandatoryRegisters> for HashMap<NonMandatoryRegisterId, Constant> {
+    fn from(v: NonMandatoryRegisters) -> Self {
+        v.0.into_iter()
             .enumerate()
             .map(|(i, c)| (NonMandatoryRegisterId::get_by_zero_index(i), c))
             .collect()
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index 1f91ecc26..71b48fb13 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -55,9 +55,9 @@ impl SigmaSerializable for TxId {
 }
 
 #[cfg(feature = "json")]
-impl Into<String> for TxId {
-    fn into(self) -> String {
-        self.0.into()
+impl From<TxId> for String {
+    fn from(v: TxId) -> Self {
+        v.0.into()
     }
 }
 
@@ -223,13 +223,13 @@ impl SigmaSerializable for Transaction {
 }
 
 #[cfg(feature = "json")]
-impl Into<json::transaction::TransactionJson> for Transaction {
-    fn into(self) -> json::transaction::TransactionJson {
+impl From<Transaction> for json::transaction::TransactionJson {
+    fn from(v: Transaction) -> Self {
         json::transaction::TransactionJson {
-            tx_id: self.tx_id.clone(),
-            inputs: self.inputs.clone(),
-            data_inputs: self.data_inputs.clone(),
-            outputs: self.outputs(),
+            tx_id: v.tx_id.clone(),
+            inputs: v.inputs.clone(),
+            data_inputs: v.data_inputs.clone(),
+            outputs: v.outputs(),
         }
     }
 }
diff --git a/ergo-lib/src/wallet/secret_key.rs b/ergo-lib/src/wallet/secret_key.rs
index 58ba89fdd..f24887125 100644
--- a/ergo-lib/src/wallet/secret_key.rs
+++ b/ergo-lib/src/wallet/secret_key.rs
@@ -24,7 +24,7 @@ impl SecretKey {
     /// Address (encoded public image)
     pub fn get_address_from_public_image(&self) -> Address {
         match self {
-            SecretKey::DlogSecretKey(dpi) => Address::P2PK(dpi.public_image()),
+            SecretKey::DlogSecretKey(dpi) => Address::P2Pk(dpi.public_image()),
         }
     }
 
diff --git a/ergoscript-compiler/src/hir/rewrite.rs b/ergoscript-compiler/src/hir/rewrite.rs
index 5ebe999f3..f1a38fd9c 100644
--- a/ergoscript-compiler/src/hir/rewrite.rs
+++ b/ergoscript-compiler/src/hir/rewrite.rs
@@ -22,6 +22,8 @@ pub fn rewrite<E, F: Fn(&Expr) -> Result<Option<Expr>, E>>(e: Expr, f: F) -> Res
                 ..e
             },
         },
-        _ => f(&e)?.unwrap_or(e),
+        ExprKind::Ident(_) => f(&e)?.unwrap_or(e),
+        ExprKind::GlobalVars(_) => f(&e)?.unwrap_or(e),
+        ExprKind::Literal(_) => f(&e)?.unwrap_or(e),
     })
 }
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index e75ef236d..1d4583743 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -30,6 +30,7 @@ pub(crate) mod coll_by_index;
 pub(crate) mod coll_exists;
 pub(crate) mod coll_filter;
 pub(crate) mod coll_fold;
+pub(crate) mod coll_forall;
 pub(crate) mod coll_map;
 pub(crate) mod coll_size;
 pub(crate) mod collection;
@@ -54,6 +55,7 @@ pub(crate) mod option_is_defined;
 pub(crate) mod or;
 pub(crate) mod property_call;
 pub(crate) mod sbox;
+pub(crate) mod scoll;
 pub(crate) mod scontext;
 pub(crate) mod select_field;
 pub(crate) mod sigma_prop_bytes;
@@ -150,7 +152,7 @@ pub(crate) 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>;
+type EvalFn = fn(env: &Env, ctx: &mut EvalContext, Value, Vec<Value>) -> Result<Value, EvalError>;
 
 fn smethod_eval_fn(method: &SMethod) -> EvalFn {
     use ergotree_ir::types::*;
@@ -158,12 +160,18 @@ fn smethod_eval_fn(method: &SMethod) -> EvalFn {
         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),
+        sbox::TYPE_ID => match method.method_id() {
+            sbox::VALUE_METHOD_ID => self::sbox::VALUE_EVAL_FN,
+            sbox::GET_REG_METHOD_ID => self::sbox::GET_REG_EVAL_FN,
+            sbox::TOKENS_METHOD_ID => self::sbox::TOKENS_EVAL_FN,
+            method_id => panic!("Eval fn: unknown method id in SBox: {:?}", method_id),
+        },
+        scoll::TYPE_ID => match method.method_id() {
+            scoll::INDEX_OF_METHOD_ID => self::scoll::INDEX_OF_EVAL_FN,
+            scoll::FLATMAP_METHOD_ID => self::scoll::FLATMAP_EVAL_FN,
+            method_id => panic!("Eval fn: unknown method id in SCollection: {:?}", method_id),
+        },
+        type_id => todo!("Eval fn: unknown type id {:?}", type_id),
     }
 }
 
diff --git a/ergotree-interpreter/src/eval/apply.rs b/ergotree-interpreter/src/eval/apply.rs
index 7d5b94be5..f0fafa3b8 100644
--- a/ergotree-interpreter/src/eval/apply.rs
+++ b/ergotree-interpreter/src/eval/apply.rs
@@ -14,13 +14,13 @@ impl Evaluable for Apply {
             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();
+            Value::Lambda(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)
+                fv.body.eval(&cur_env, ctx)
             }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "expected func_v to be Value::FuncValue got: {0:?}",
diff --git a/ergotree-interpreter/src/eval/bin_op.rs b/ergotree-interpreter/src/eval/bin_op.rs
index b703160c6..7efd19267 100644
--- a/ergotree-interpreter/src/eval/bin_op.rs
+++ b/ergotree-interpreter/src/eval/bin_op.rs
@@ -159,10 +159,10 @@ impl Evaluable for BinOp {
             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::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 {
@@ -410,10 +410,10 @@ mod tests {
             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);
+            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]
@@ -425,10 +425,10 @@ mod tests {
             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);
+            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]
@@ -440,10 +440,10 @@ mod tests {
             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);
+            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]
@@ -455,10 +455,10 @@ mod tests {
             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);
+            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]
@@ -473,10 +473,10 @@ mod tests {
                     r.clone().into()).unwrap(), l.clone().max(r.clone()));
             prop_assert_eq!(eval_num_op::<BigInt>(ArithOp::Min, l.clone().into(),
                     r.clone().into()).unwrap(), l.clone().min(r.clone()));
-            prop_assert_eq!(eval_relation_op(RelationOp::GT, l.clone().into(), r.clone().into()), l > r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LT, l.clone().into(), r.clone().into()), l < r);
-            prop_assert_eq!(eval_relation_op(RelationOp::GE, l.clone().into(), r.clone().into()), l >= r);
-            prop_assert_eq!(eval_relation_op(RelationOp::LE, l.clone().into(), r.clone().into()), l <= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::Gt, l.clone().into(), r.clone().into()), l > r);
+            prop_assert_eq!(eval_relation_op(RelationOp::Lt, l.clone().into(), r.clone().into()), l < r);
+            prop_assert_eq!(eval_relation_op(RelationOp::Ge, l.clone().into(), r.clone().into()), l >= r);
+            prop_assert_eq!(eval_relation_op(RelationOp::Le, l.clone().into(), r.clone().into()), l <= r);
         }
 
         #[test]
diff --git a/ergotree-interpreter/src/eval/coll_exists.rs b/ergotree-interpreter/src/eval/coll_exists.rs
index dfa1fbf4c..779ef80d5 100644
--- a/ergotree-interpreter/src/eval/coll_exists.rs
+++ b/ergotree-interpreter/src/eval/coll_exists.rs
@@ -13,14 +13,14 @@ impl Evaluable for Exists {
         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(|| {
+            Value::Lambda(func_value) => {
+                let func_arg = func_value.args.first().ok_or_else(|| {
                     EvalError::NotFound(
                         "Exists: evaluated condition has empty arguments list".to_string(),
                     )
                 })?;
                 let env1 = env.clone().extend(func_arg.idx, arg);
-                func_value.body().eval(&env1, ctx)
+                func_value.body.eval(&env1, ctx)
             }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "expected Exists::condition to be Value::FuncValue got: {0:?}",
@@ -56,70 +56,63 @@ impl Evaluable for Exists {
 
 #[cfg(test)]
 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 super::*;
 
     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 = Exists::new(
-                data_inputs,
-                FuncValue::new(
-                    vec![FuncArg {
-                        idx: 1.into(),
-                        tpe: SType::SBox,
-                    }],
-                    body,
-                )
+    fn check(coll: Vec<i32>) {
+        let body: Expr = BinOp {
+            kind: RelationOp::Le.into(),
+            left: Box::new(Expr::Const(1i32.into())),
+            right: Box::new(
+                ValUse {
+                    val_id: 1.into(),
+                    tpe: SType::SBox,
+                }
                 .into(),
-            )
-            .unwrap()
-            .into();
-            let ctx = Rc::new(ctx);
-            assert_eq!(
-                eval_out::<bool>(&expr, ctx.clone()),
-                ctx.data_inputs.clone()
-                    .into_iter()
-                    .any(| b| 1 <= b.get_box(&ctx.box_arena).unwrap().value())
-            );
+            ),
         }
+        .into();
+        let expr: Expr = Exists::new(
+            coll.clone().into(),
+            FuncValue::new(
+                vec![FuncArg {
+                    idx: 1.into(),
+                    tpe: SType::SInt,
+                }],
+                body,
+            )
+            .into(),
+        )
+        .unwrap()
+        .into();
+        assert_eq!(
+            eval_out_wo_ctx::<bool>(&expr),
+            coll.iter().any(|it| 1 <= *it)
+        );
+    }
+
+    #[test]
+    fn eval_emty_coll() {
+        check(Vec::<i32>::new());
+    }
+
+    #[test]
+    fn eval_true() {
+        check(vec![1, 2]);
+    }
+
+    #[test]
+    fn eval_false() {
+        check(vec![2, 2]);
     }
 }
diff --git a/ergotree-interpreter/src/eval/coll_filter.rs b/ergotree-interpreter/src/eval/coll_filter.rs
index 2b07764b6..21a053cae 100644
--- a/ergotree-interpreter/src/eval/coll_filter.rs
+++ b/ergotree-interpreter/src/eval/coll_filter.rs
@@ -14,14 +14,14 @@ impl Evaluable for Filter {
         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(|| {
+            Value::Lambda(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)
+                func_value.body.eval(&env1, ctx)
             }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "expected Filter::condition to be Value::FuncValue got: {0:?}",
@@ -96,10 +96,7 @@ mod tests {
 
         #[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(),
-            }
+            let data_inputs: Expr = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone()).unwrap()
             .into();
             let val_use: Expr = ValUse {
                 val_id: 1.into(),
@@ -107,7 +104,7 @@ mod tests {
             }
             .into();
             let body: Expr = BinOp {
-                kind: RelationOp::LE.into(),
+                kind: RelationOp::Le.into(),
                 left: Box::new(Expr::Const(1i64.into())),
                 right: Box::new(Expr::ExtractAmount(
                         ExtractAmount::new(val_use)
diff --git a/ergotree-interpreter/src/eval/coll_fold.rs b/ergotree-interpreter/src/eval/coll_fold.rs
index 77a4d6829..33d410c5f 100644
--- a/ergotree-interpreter/src/eval/coll_fold.rs
+++ b/ergotree-interpreter/src/eval/coll_fold.rs
@@ -16,13 +16,13 @@ impl Evaluable for Fold {
         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) => {
+            Value::Lambda(func_value) => {
                 let func_arg = func_value
-                    .args()
+                    .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)
+                func_value.body.eval(&env1, ctx)
             }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "expected fold_op to be Value::FuncValue got: {0:?}",
@@ -83,10 +83,7 @@ mod tests {
 
         #[test]
         fn eval_fold(ctx in any::<Context>()) {
-            let data_inputs: Expr = PropertyCall {
-                obj: Box::new(Expr::Context),
-                method: scontext::DATA_INPUTS_PROPERTY.clone(),
-            }
+            let data_inputs: Expr = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone()).unwrap()
             .into();
             let tuple: Expr = ValUse {
                 val_id: 1.into(),
diff --git a/ergotree-interpreter/src/eval/coll_forall.rs b/ergotree-interpreter/src/eval/coll_forall.rs
new file mode 100644
index 000000000..808ef4d67
--- /dev/null
+++ b/ergotree-interpreter/src/eval/coll_forall.rs
@@ -0,0 +1,118 @@
+use ergotree_ir::mir::coll_forall::ForAll;
+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 ForAll {
+    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::Lambda(func_value) => {
+                let func_arg = func_value.args.first().ok_or_else(|| {
+                    EvalError::NotFound(
+                        "ForAll: 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 ForAll::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 ForAll 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
+            ))),
+        }?;
+
+        for item in normalized_input_vals.into_iter() {
+            let res = condition_call(item)?.try_extract_into::<bool>()?;
+            if !res {
+                return Ok(false.into());
+            }
+        }
+        Ok(true.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use crate::eval::tests::eval_out_wo_ctx;
+
+    use super::*;
+
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::bin_op::RelationOp;
+    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_use::ValUse;
+    use ergotree_ir::types::stype::SType;
+
+    fn check(coll: Vec<i32>) {
+        let body: Expr = BinOp {
+            kind: RelationOp::Le.into(),
+            left: Box::new(Expr::Const(1i32.into())),
+            right: Box::new(
+                ValUse {
+                    val_id: 1.into(),
+                    tpe: SType::SBox,
+                }
+                .into(),
+            ),
+        }
+        .into();
+        let expr: Expr = ForAll::new(
+            coll.clone().into(),
+            FuncValue::new(
+                vec![FuncArg {
+                    idx: 1.into(),
+                    tpe: SType::SInt,
+                }],
+                body,
+            )
+            .into(),
+        )
+        .unwrap()
+        .into();
+        assert_eq!(
+            eval_out_wo_ctx::<bool>(&expr),
+            coll.iter().all(|it| 1 <= *it)
+        );
+    }
+
+    #[test]
+    fn eval_emty_coll() {
+        check(Vec::<i32>::new());
+    }
+
+    #[test]
+    fn eval_true() {
+        check(vec![1, 1]);
+    }
+
+    #[test]
+    fn eval_false() {
+        check(vec![1, 2]);
+    }
+}
diff --git a/ergotree-interpreter/src/eval/coll_map.rs b/ergotree-interpreter/src/eval/coll_map.rs
index bb651e391..7ad036098 100644
--- a/ergotree-interpreter/src/eval/coll_map.rs
+++ b/ergotree-interpreter/src/eval/coll_map.rs
@@ -13,14 +13,14 @@ impl Evaluable for Map {
         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(|| {
+            Value::Lambda(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)
+                func_value.body.eval(&env1, ctx)
             }
             _ => Err(EvalError::UnexpectedValue(format!(
                 "expected mapper to be Value::FuncValue got: {0:?}",
@@ -92,10 +92,7 @@ mod tests {
 
         #[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(),
-            }
+            let data_inputs: Expr = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone()).unwrap()
             .into();
             let val_use: Expr = ValUse {
                 val_id: 1.into(),
diff --git a/ergotree-interpreter/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs
index 7299e8539..8b1aba4b1 100644
--- a/ergotree-interpreter/src/eval/expr.rs
+++ b/ergotree-interpreter/src/eval/expr.rs
@@ -48,6 +48,7 @@ impl Evaluable for Expr {
             Expr::OptionIsDefined(op) => op.eval(env, ctx),
             Expr::OptionGetOrElse(op) => op.eval(env, ctx),
             Expr::Negation(op) => op.eval(env, ctx),
+            Expr::ForAll(op) => op.eval(env, ctx),
         }
     }
 }
diff --git a/ergotree-interpreter/src/eval/func_value.rs b/ergotree-interpreter/src/eval/func_value.rs
index 70ba67e75..f342bb1e6 100644
--- a/ergotree-interpreter/src/eval/func_value.rs
+++ b/ergotree-interpreter/src/eval/func_value.rs
@@ -1,4 +1,5 @@
 use ergotree_ir::mir::func_value::FuncValue;
+use ergotree_ir::mir::value::Lambda;
 use ergotree_ir::mir::value::Value;
 
 use crate::eval::env::Env;
@@ -8,6 +9,9 @@ use crate::eval::Evaluable;
 
 impl Evaluable for FuncValue {
     fn eval(&self, _env: &Env, _ctx: &mut EvalContext) -> Result<Value, EvalError> {
-        Ok(Value::FuncValue(self.clone()))
+        Ok(Value::Lambda(Lambda {
+            args: self.args().to_vec(),
+            body: self.body().clone().into(),
+        }))
     }
 }
diff --git a/ergotree-interpreter/src/eval/method_call.rs b/ergotree-interpreter/src/eval/method_call.rs
index fd8e17655..d61b1d511 100644
--- a/ergotree-interpreter/src/eval/method_call.rs
+++ b/ergotree-interpreter/src/eval/method_call.rs
@@ -12,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();
-        smethod_eval_fn(&self.method)(ectx.ctx.clone(), ov, argsv?)
+        smethod_eval_fn(&self.method)(env, ectx, ov, argsv?)
     }
 }
 
@@ -35,11 +35,12 @@ mod tests {
 
     #[test]
     fn eval_box_get_reg() {
-        let mc: Expr = MethodCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: sbox::GET_REG_METHOD.clone(),
-            args: vec![Constant::from(0i8).into()],
-        }
+        let mc: Expr = MethodCall::new(
+            GlobalVars::SelfBox.into(),
+            sbox::GET_REG_METHOD.clone(),
+            vec![Constant::from(0i8).into()],
+        )
+        .unwrap()
         .into();
         let option_get_expr: Expr = OptionGet::new(mc).unwrap().into();
         let ctx = Rc::new(force_any_val::<Context>());
diff --git a/ergotree-interpreter/src/eval/property_call.rs b/ergotree-interpreter/src/eval/property_call.rs
index e620e8d75..b80b7e5c1 100644
--- a/ergotree-interpreter/src/eval/property_call.rs
+++ b/ergotree-interpreter/src/eval/property_call.rs
@@ -10,7 +10,7 @@ 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)?;
-        smethod_eval_fn(&self.method)(ectx.ctx.clone(), ov, vec![])
+        smethod_eval_fn(&self.method)(env, ectx, ov, vec![])
     }
 }
 
@@ -27,11 +27,9 @@ mod tests {
 
     #[test]
     fn eval_context_data_inputs() {
-        let pc: Expr = PropertyCall {
-            obj: Box::new(Expr::Context),
-            method: scontext::DATA_INPUTS_PROPERTY.clone(),
-        }
-        .into();
+        let pc: Expr = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone())
+            .unwrap()
+            .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(eval_out::<Vec<IrBoxId>>(&pc, ctx.clone()), ctx.data_inputs);
     }
diff --git a/ergotree-interpreter/src/eval/sbox.rs b/ergotree-interpreter/src/eval/sbox.rs
index fd7dcb3e0..0e03b295b 100644
--- a/ergotree-interpreter/src/eval/sbox.rs
+++ b/ergotree-interpreter/src/eval/sbox.rs
@@ -6,18 +6,18 @@ use ergotree_ir::mir::value::Value;
 
 use super::EvalFn;
 
-pub static VALUE_EVAL_FN: EvalFn = |ctx, obj, _args| {
+pub(crate) static VALUE_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     Ok(Value::Long(
         obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.box_arena)?
+            .get_box(&ctx.ctx.box_arena)?
             .value(),
     ))
 };
 
-pub static GET_REG_EVAL_FN: EvalFn = |ctx, obj, args| {
+pub(crate) static GET_REG_EVAL_FN: EvalFn = |_env, ctx, obj, args| {
     Ok(Value::Opt(Box::new(
         obj.try_extract_into::<IrBoxId>()?
-            .get_box(&ctx.box_arena)?
+            .get_box(&ctx.ctx.box_arena)?
             .get_register(
                 args.get(0)
                     .cloned()
@@ -28,10 +28,10 @@ pub static GET_REG_EVAL_FN: EvalFn = |ctx, obj, args| {
     )))
 };
 
-pub static TOKENS_EVAL_FN: EvalFn = |ctx, obj, _args| {
+pub(crate) static TOKENS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     let res: Value = obj
         .try_extract_into::<IrBoxId>()?
-        .get_box(&ctx.box_arena)?
+        .get_box(&ctx.ctx.box_arena)?
         .tokens_raw()
         .into();
     Ok(res)
@@ -52,11 +52,9 @@ mod tests {
 
     #[test]
     fn eval_box_value() {
-        let expr: Expr = PropertyCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: sbox::VALUE_METHOD.clone(),
-        }
-        .into();
+        let expr: Expr = PropertyCall::new(GlobalVars::SelfBox.into(), sbox::VALUE_METHOD.clone())
+            .unwrap()
+            .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<i64>(&expr, ctx.clone()),
@@ -66,11 +64,9 @@ mod tests {
 
     #[test]
     fn eval_box_tokens() {
-        let expr: Expr = PropertyCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: sbox::TOKENS_METHOD.clone(),
-        }
-        .into();
+        let expr: Expr = PropertyCall::new(GlobalVars::SelfBox.into(), sbox::TOKENS_METHOD.clone())
+            .unwrap()
+            .into();
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(
             eval_out::<Vec<(Vec<i8>, i64)>>(&expr, ctx.clone()),
diff --git a/ergotree-interpreter/src/eval/scoll.rs b/ergotree-interpreter/src/eval/scoll.rs
new file mode 100644
index 000000000..7b32c353c
--- /dev/null
+++ b/ergotree-interpreter/src/eval/scoll.rs
@@ -0,0 +1,182 @@
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+
+use ergotree_ir::mir::constant::TryExtractInto;
+use ergotree_ir::mir::value::CollKind;
+use ergotree_ir::mir::value::Value;
+
+use super::EvalFn;
+
+pub(crate) static INDEX_OF_EVAL_FN: EvalFn = |_env, _ctx, obj, args| {
+    Ok(Value::Int({
+        let normalized_input_vals: Vec<Value> = match obj {
+            Value::Coll(coll) => Ok(coll.as_vec()),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected obj to be Value::Coll, got: {0:?}",
+                obj
+            ))),
+        }?;
+        let target_element = args
+            .get(0)
+            .cloned()
+            .ok_or_else(|| EvalError::NotFound("indexOf: missing first arg".to_string()))?;
+        let fallback_index = args
+            .get(1)
+            .cloned()
+            .ok_or_else(|| EvalError::NotFound("indexOf: missing second arg".to_string()))?;
+        let index_of = normalized_input_vals
+            .into_iter()
+            .position(|it| it == target_element)
+            .unwrap_or(fallback_index.try_extract_into::<i32>()? as usize);
+        index_of as i32
+    }))
+};
+
+pub(crate) static FLATMAP_EVAL_FN: EvalFn = |env, ctx, obj, args| {
+    let input_v = obj;
+    let lambda_v = args
+        .get(0)
+        .cloned()
+        .ok_or_else(|| EvalError::NotFound("flatmap: eval is missing first arg".to_string()))?;
+    let input_v_clone = input_v.clone();
+    let lambda = match &lambda_v {
+        Value::Lambda(l) => Ok(l),
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected lambda to be Value::FuncValue got: {0:?}",
+            input_v_clone
+        ))),
+    }?;
+    let mut lambda_call = |arg: Value| {
+        let func_arg = lambda.args.first().ok_or_else(|| {
+            EvalError::NotFound("flatmap: lambda has empty arguments list".to_string())
+        })?;
+        let env1 = env.clone().extend(func_arg.idx, arg);
+        lambda.body.eval(&env1, ctx)
+    };
+    let mapper_input_tpe = lambda
+        .args
+        .first()
+        .cloned()
+        .map(|arg| arg.tpe)
+        .ok_or_else(|| {
+            EvalError::NotFound(
+                "flatmap: lambda args are empty (does not have arguments)".to_string(),
+            )
+        })?;
+    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 Flatmap input element type to be {0:?}, got: {1:?}",
+                    mapper_input_tpe,
+                    coll.elem_tpe()
+                )));
+            };
+            Ok(coll.as_vec())
+        }
+        _ => Err(EvalError::UnexpectedValue(format!(
+            "expected Flatmap input to be Value::Coll, got: {0:?}",
+            input_v
+        ))),
+    }?;
+    normalized_input_vals
+        .iter()
+        .map(|item| lambda_call(item.clone()))
+        .collect::<Result<Vec<Value>, EvalError>>()
+        .map(|values| {
+            CollKind::from_vec(lambda.body.tpe(), values).map_err(EvalError::TryExtractFrom)
+        })
+        .and_then(|v| v) // flatten <Result<Result<Value, _>, _>
+        .map(Value::Coll)
+};
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use ergotree_ir::mir::bin_op::BinOp;
+    use ergotree_ir::mir::bin_op::RelationOp;
+    use ergotree_ir::mir::constant::Constant;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::func_value::FuncArg;
+    use ergotree_ir::mir::func_value::FuncValue;
+    use ergotree_ir::mir::method_call::MethodCall;
+    use ergotree_ir::mir::val_use::ValUse;
+    use ergotree_ir::types::scoll;
+    use ergotree_ir::types::stype::SType;
+    use ergotree_ir::types::stype_param::STypeVar;
+
+    use crate::eval::tests::eval_out_wo_ctx;
+
+    #[test]
+    fn eval_index_of() {
+        let coll_const: Constant = vec![1i64, 2i64].into();
+        let expr: Expr = MethodCall::new(
+            coll_const.into(),
+            scoll::INDEX_OF_METHOD
+                .clone()
+                .with_concrete_types(&[(STypeVar::T, SType::SLong)].iter().cloned().collect()),
+            vec![2i64.into(), 0i32.into()],
+        )
+        .unwrap()
+        .into();
+        let res = eval_out_wo_ctx::<i32>(&expr);
+        assert_eq!(res, 1);
+    }
+
+    #[test]
+    fn eval_index_of_default() {
+        let coll_const: Constant = vec![1i64, 2i64].into();
+        let expr: Expr = MethodCall::new(
+            coll_const.into(),
+            scoll::INDEX_OF_METHOD
+                .clone()
+                .with_concrete_types(&[(STypeVar::T, SType::SLong)].iter().cloned().collect()),
+            vec![3i64.into(), 0i32.into()],
+        )
+        .unwrap()
+        .into();
+        let res = eval_out_wo_ctx::<i32>(&expr);
+        assert_eq!(res, 0);
+    }
+
+    #[test]
+    fn eval_flatmap() {
+        let coll_const: Constant = vec![1i64, 2i64].into();
+        let body: Expr = BinOp {
+            kind: RelationOp::Ge.into(),
+            left: Box::new(Expr::Const(1i64.into())),
+            right: Box::new(
+                ValUse {
+                    val_id: 1.into(),
+                    tpe: SType::SBox,
+                }
+                .into(),
+            ),
+        }
+        .into();
+        let expr: Expr = MethodCall::new(
+            coll_const.into(),
+            scoll::FLATMAP_METHOD.clone().with_concrete_types(
+                &[
+                    (STypeVar::IV, SType::SLong),
+                    (STypeVar::OV, SType::SBoolean),
+                ]
+                .iter()
+                .cloned()
+                .collect(),
+            ),
+            vec![FuncValue::new(
+                vec![FuncArg {
+                    idx: 1.into(),
+                    tpe: SType::SLong,
+                }],
+                body,
+            )
+            .into()],
+        )
+        .unwrap()
+        .into();
+        let res = eval_out_wo_ctx::<Vec<bool>>(&expr);
+        assert_eq!(res, vec![true, false]);
+    }
+}
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
index 6c1b87017..85ce8246e 100644
--- a/ergotree-interpreter/src/eval/scontext.rs
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -5,7 +5,7 @@ use ergotree_ir::types::stype::SType;
 use super::EvalError;
 use super::EvalFn;
 
-pub static DATA_INPUTS_EVAL_FN: EvalFn = |ctx, obj, _args| {
+pub(crate) static DATA_INPUTS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     if obj != Value::Context {
         return Err(EvalError::UnexpectedValue(format!(
             "Context.dataInputs: expected object of Value::Context, got {:?}",
@@ -14,6 +14,7 @@ pub static DATA_INPUTS_EVAL_FN: EvalFn = |ctx, obj, _args| {
     }
     Ok(Value::Coll(CollKind::WrappedColl {
         items: ctx
+            .ctx
             .data_inputs
             .clone()
             .into_iter()
diff --git a/ergotree-interpreter/src/sigma_protocol.rs b/ergotree-interpreter/src/sigma_protocol.rs
index 2b4534401..961a48ae7 100644
--- a/ergotree-interpreter/src/sigma_protocol.rs
+++ b/ergotree-interpreter/src/sigma_protocol.rs
@@ -32,14 +32,14 @@ pub enum FirstProverMessage {
     /// Discrete log
     FirstDlogProverMessage(FirstDlogProverMessage),
     /// DH tupl
-    FirstDHTProverMessage,
+    FirstDhtProverMessage,
 }
 
 impl ProverMessage for FirstProverMessage {
     fn bytes(&self) -> Vec<u8> {
         match self {
             FirstProverMessage::FirstDlogProverMessage(fdpm) => fdpm.bytes(),
-            FirstProverMessage::FirstDHTProverMessage => todo!(),
+            FirstProverMessage::FirstDhtProverMessage => todo!(),
         }
     }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/challenge.rs b/ergotree-interpreter/src/sigma_protocol/challenge.rs
index 48a6ba06b..f7344d05d 100644
--- a/ergotree-interpreter/src/sigma_protocol/challenge.rs
+++ b/ergotree-interpreter/src/sigma_protocol/challenge.rs
@@ -9,9 +9,9 @@ use std::convert::TryInto;
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct Challenge(FiatShamirHash);
 
-impl Into<Scalar> for Challenge {
-    fn into(self) -> Scalar {
-        let v: [u8; SOUNDNESS_BYTES] = self.0.into();
+impl From<Challenge> for Scalar {
+    fn from(v: Challenge) -> Self {
+        let v: [u8; SOUNDNESS_BYTES] = v.0.into();
         // prepend zeroes to 32 bytes (big-endian)
         let mut prefix = vec![0u8; 8];
         prefix.append(&mut v.to_vec());
@@ -19,9 +19,9 @@ impl Into<Scalar> for Challenge {
     }
 }
 
-impl Into<Vec<u8>> for Challenge {
-    fn into(self) -> Vec<u8> {
-        let arr: [u8; SOUNDNESS_BYTES] = self.0.into();
+impl From<Challenge> for Vec<u8> {
+    fn from(v: Challenge) -> Self {
+        let arr: [u8; SOUNDNESS_BYTES] = v.0.into();
         arr.to_vec()
     }
 }
diff --git a/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs b/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
index 03bacfa9f..7b874d02d 100644
--- a/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
+++ b/ergotree-interpreter/src/sigma_protocol/fiat_shamir.rs
@@ -33,9 +33,9 @@ pub fn fiat_shamir_hash_fn(input: &[u8]) -> FiatShamirHash {
     FiatShamirHash(taken.into_boxed_slice().try_into().unwrap())
 }
 
-impl Into<[u8; SOUNDNESS_BYTES]> for FiatShamirHash {
-    fn into(self) -> [u8; SOUNDNESS_BYTES] {
-        *self.0
+impl From<FiatShamirHash> for [u8; SOUNDNESS_BYTES] {
+    fn from(v: FiatShamirHash) -> Self {
+        *v.0
     }
 }
 
diff --git a/ergotree-interpreter/src/sigma_protocol/prover.rs b/ergotree-interpreter/src/sigma_protocol/prover.rs
index ca4fef8b6..564345023 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover.rs
@@ -275,7 +275,7 @@ fn convert_to_unproven(ergo_lib: SigmaBoolean) -> UnprovenTree {
         // TODO: why it's even here? Make another SigmaBoolean without trivial props?
         SigmaBoolean::TrivialProp(_) => todo!(),
         SigmaBoolean::ProofOfKnowledge(pok) => match pok {
-            SigmaProofOfKnowledgeTree::ProveDHTuple(_) => todo!(),
+            SigmaProofOfKnowledgeTree::ProveDhTuple(_) => todo!(),
             SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog) => UnprovenSchnorr {
                 proposition: prove_dlog,
                 commitment_opt: None,
@@ -285,7 +285,7 @@ fn convert_to_unproven(ergo_lib: SigmaBoolean) -> UnprovenTree {
             }
             .into(),
         },
-        SigmaBoolean::CAND(_) => todo!(),
+        SigmaBoolean::Cand(_) => todo!(),
     }
 }
 
diff --git a/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
index 3b4fdada7..08fcec440 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/context_extension.rs
@@ -53,9 +53,9 @@ impl SigmaSerializable for ContextExtension {
 }
 
 // for JSON encoding in ergo-lib
-impl Into<HashMap<String, String>> for ContextExtension {
-    fn into(self) -> HashMap<String, String> {
-        self.values
+impl From<ContextExtension> for HashMap<String, String> {
+    fn from(v: ContextExtension) -> Self {
+        v.values
             .into_iter()
             .map(|(k, v)| {
                 (
diff --git a/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
index a86ca2064..4a7c62e14 100644
--- a/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
+++ b/ergotree-interpreter/src/sigma_protocol/prover/prover_result.rs
@@ -17,9 +17,9 @@ pub enum ProofBytes {
     Some(Vec<u8>),
 }
 
-impl Into<Vec<u8>> for ProofBytes {
-    fn into(self) -> Vec<u8> {
-        match self {
+impl From<ProofBytes> for Vec<u8> {
+    fn from(v: ProofBytes) -> Self {
+        match v {
             ProofBytes::Empty => Vec::new(),
             ProofBytes::Some(bytes) => bytes,
         }
@@ -37,9 +37,9 @@ impl From<Vec<u8>> for ProofBytes {
 }
 
 // for JSON encoding in ergo-lib as Base16-encoded string
-impl Into<String> for ProofBytes {
-    fn into(self) -> String {
-        match self {
+impl From<ProofBytes> for String {
+    fn from(v: ProofBytes) -> Self {
+        match v {
             ProofBytes::Empty => "".to_string(),
             ProofBytes::Some(bytes) => base16::encode_lower(&bytes),
         }
diff --git a/ergotree-ir/README.md b/ergotree-ir/README.md
index e70d4de62..7af432389 100644
--- a/ergotree-ir/README.md
+++ b/ergotree-ir/README.md
@@ -161,17 +161,17 @@ Descriptions for the operations can be found in [ErgoTree Specification](https:/
 - :heavy_check_mark: map
 - :heavy_check_mark: exists
 - :heavy_check_mark: fold
-- forall
+- :heavy_check_mark: forall
 - slice
 - :heavy_check_mark: filter
 - append
 - :heavy_check_mark: apply
 - indices
-- flatMap
+- :heavy_check_mark: flatMap
 - patch
 - updated
 - updateMany
-- indexOf
+- :heavy_check_mark: indexOf
 - zip
 
 #### Option
diff --git a/ergotree-ir/src/address.rs b/ergotree-ir/src/address.rs
index 0fff0e6e9..37d2ffc87 100644
--- a/ergotree-ir/src/address.rs
+++ b/ergotree-ir/src/address.rs
@@ -62,7 +62,7 @@ use thiserror::Error;
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub enum Address {
     /// serialized (compressed) public key
-    P2PK(ProveDlog),
+    P2Pk(ProveDlog),
     /// serialized script
     P2S(Vec<u8>),
     // P2SH([u8; 24]),
@@ -73,7 +73,7 @@ impl Address {
     pub fn p2pk_from_pk_bytes(bytes: &[u8]) -> Result<Address, SerializationError> {
         EcPoint::sigma_parse_bytes(bytes.to_vec())
             .map(ProveDlog::from)
-            .map(Address::P2PK)
+            .map(Address::P2Pk)
     }
 
     /// Re-create the address from ErgoTree that was built from the address
@@ -88,7 +88,7 @@ impl Address {
                     tpe: SType::SSigmaProp,
                     v,
                 }) => ProveDlog::try_from(v.clone())
-                    .map(Address::P2PK)
+                    .map(Address::P2Pk)
                     .unwrap_or_else(|_| Address::P2S(tree.sigma_serialize_bytes())),
                 _ => Address::P2S(tree.sigma_serialize_bytes()),
             }),
@@ -99,7 +99,7 @@ impl Address {
     /// address type prefix (for encoding)
     pub fn address_type_prefix(&self) -> AddressTypePrefix {
         match self {
-            Address::P2PK(_) => AddressTypePrefix::P2PK,
+            Address::P2Pk(_) => AddressTypePrefix::P2Pk,
             Address::P2S(_) => AddressTypePrefix::Pay2S,
             //Address::P2SH(_) => AddressTypePrefix::P2SH,
         }
@@ -108,7 +108,7 @@ impl Address {
     /// byte array
     pub fn content_bytes(&self) -> Vec<u8> {
         match self {
-            Address::P2PK(prove_dlog) => prove_dlog.h.sigma_serialize_bytes(),
+            Address::P2Pk(prove_dlog) => prove_dlog.h.sigma_serialize_bytes(),
             Address::P2S(bytes) => bytes.clone(),
         }
     }
@@ -116,7 +116,7 @@ impl Address {
     /// script encoded in the address
     pub fn script(&self) -> Result<ErgoTree, SerializationError> {
         match self {
-            Address::P2PK(prove_dlog) => Ok(ErgoTree::from(Expr::Const(
+            Address::P2Pk(prove_dlog) => Ok(ErgoTree::from(Expr::Const(
                 SigmaProp::new(SigmaBoolean::ProofOfKnowledge(
                     SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog.clone()),
                 ))
@@ -174,9 +174,9 @@ pub enum AddressError {
 /// Address types
 pub enum AddressTypePrefix {
     /// 0x01 - Pay-to-PublicKey(P2PK) address
-    P2PK = 1,
+    P2Pk = 1,
     /// 0x02 - Pay-to-Script-Hash(P2SH)
-    Pay2SH = 2,
+    Pay2Sh = 2,
     /// 0x03 - Pay-to-Script(P2S)
     Pay2S = 3,
 }
@@ -185,8 +185,8 @@ impl TryFrom<u8> for AddressTypePrefix {
     type Error = AddressEncoderError;
     fn try_from(value: u8) -> Result<Self, Self::Error> {
         match value {
-            v if v == AddressTypePrefix::P2PK as u8 => Ok(AddressTypePrefix::P2PK),
-            v if v == AddressTypePrefix::Pay2SH as u8 => Ok(AddressTypePrefix::Pay2SH),
+            v if v == AddressTypePrefix::P2Pk as u8 => Ok(AddressTypePrefix::P2Pk),
+            v if v == AddressTypePrefix::Pay2Sh as u8 => Ok(AddressTypePrefix::Pay2Sh),
             v if v == AddressTypePrefix::Pay2S as u8 => Ok(AddressTypePrefix::Pay2S),
             v => Err(AddressEncoderError::InvalidAddressType(v)),
         }
@@ -360,11 +360,11 @@ impl AddressEncoder {
         let content_bytes: Vec<u8> = without_checksum[1..].to_vec(); // without head_byte
         let address_type = AddressTypePrefix::try_from(bytes[0] & 0xF_u8)?;
         Ok(match address_type {
-            AddressTypePrefix::P2PK => {
-                Address::P2PK(ProveDlog::new(EcPoint::sigma_parse_bytes(content_bytes)?))
+            AddressTypePrefix::P2Pk => {
+                Address::P2Pk(ProveDlog::new(EcPoint::sigma_parse_bytes(content_bytes)?))
             }
             AddressTypePrefix::Pay2S => Address::P2S(content_bytes),
-            AddressTypePrefix::Pay2SH => todo!(),
+            AddressTypePrefix::Pay2Sh => todo!(),
         })
     }
 
@@ -407,7 +407,7 @@ pub(crate) mod arbitrary {
             let non_parseable_tree = "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301";
             prop_oneof![
                 any::<ErgoTree>().prop_map(|t| match ProveDlog::try_from(t.clone()) {
-                    Ok(dlog) => Address::P2PK(dlog),
+                    Ok(dlog) => Address::P2Pk(dlog),
                     Err(_) => Address::P2S(t.sigma_serialize_bytes()),
                 }),
                 Just(Address::P2S(base16::decode(non_parseable_tree).unwrap()))
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index 367200883..8d7e2a95b 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -15,6 +15,8 @@ pub mod coll_exists;
 pub mod coll_filter;
 /// Collection.fold
 pub mod coll_fold;
+/// Tests whether a predicate holds for all elements of this collection
+pub mod coll_forall;
 /// Collection.map
 pub mod coll_map;
 /// Collection.size
diff --git a/ergotree-ir/src/mir/bin_op.rs b/ergotree-ir/src/mir/bin_op.rs
index 5510524de..bba1f3f07 100644
--- a/ergotree-ir/src/mir/bin_op.rs
+++ b/ergotree-ir/src/mir/bin_op.rs
@@ -50,13 +50,13 @@ pub enum RelationOp {
     /// Non-equality
     NEq,
     /// Greater of equal
-    GE,
+    Ge,
     /// Greater than..
-    GT,
+    Gt,
     /// Less or equal
-    LE,
+    Le,
     /// Less then
-    LT,
+    Lt,
     /// Logical AND
     And,
     /// Logical OR
@@ -68,10 +68,10 @@ impl From<RelationOp> for OpCode {
         match op {
             RelationOp::Eq => OpCode::EQ,
             RelationOp::NEq => OpCode::NEQ,
-            RelationOp::GE => OpCode::GE,
-            RelationOp::GT => OpCode::GT,
-            RelationOp::LE => OpCode::LE,
-            RelationOp::LT => OpCode::LT,
+            RelationOp::Ge => OpCode::GE,
+            RelationOp::Gt => OpCode::GT,
+            RelationOp::Le => OpCode::LE,
+            RelationOp::Lt => OpCode::LT,
             RelationOp::And => OpCode::BIN_AND,
             RelationOp::Or => OpCode::BIN_OR,
         }
diff --git a/ergotree-ir/src/mir/coll_exists.rs b/ergotree-ir/src/mir/coll_exists.rs
index b3236438e..b586150b7 100644
--- a/ergotree-ir/src/mir/coll_exists.rs
+++ b/ergotree-ir/src/mir/coll_exists.rs
@@ -26,7 +26,7 @@ impl Exists {
         let input_elem_type: SType = *match input.post_eval_tpe() {
             SType::SColl(elem_type) => Ok(elem_type),
             _ => Err(InvalidArgumentError(format!(
-                "Expected Map input to be SColl, got {0:?}",
+                "Expected Exists input to be SColl, got {0:?}",
                 input.tpe()
             ))),
         }?;
diff --git a/ergotree-ir/src/mir/coll_forall.rs b/ergotree-ir/src/mir/coll_forall.rs
new file mode 100644
index 000000000..4fe87acc3
--- /dev/null
+++ b/ergotree-ir/src/mir/coll_forall.rs
@@ -0,0 +1,125 @@
+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;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stype::SType;
+
+/// Tests whether a predicate holds for all elements of this collection.
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ForAll {
+    /// Collection
+    pub input: Box<Expr>,
+    /// Function (lambda) to test each element
+    pub condition: Box<Expr>,
+    /// Collection element type
+    pub elem_tpe: SType,
+}
+
+impl ForAll {
+    pub(crate) const OP_CODE: OpCode = OpCode::FOR_ALL;
+
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(input: Expr, condition: Expr) -> Result<Self, InvalidArgumentError> {
+        let input_elem_type: SType = *match input.post_eval_tpe() {
+            SType::SColl(elem_type) => Ok(elem_type),
+            _ => Err(InvalidArgumentError(format!(
+                "Expected ForAll input to be SColl, got {0:?}",
+                input.tpe()
+            ))),
+        }?;
+        match condition.tpe() {
+            SType::SFunc(sfunc)
+                if sfunc.t_dom == vec![input_elem_type.clone()]
+                    && *sfunc.t_range == SType::SBoolean =>
+            {
+                Ok(ForAll {
+                    input: input.into(),
+                    condition: condition.into(),
+                    elem_tpe: input_elem_type,
+                })
+            }
+            _ => Err(InvalidArgumentError(format!(
+                "Invalid condition tpe: {0:?}",
+                condition.tpe()
+            ))),
+        }
+    }
+
+    /// Type
+    pub fn tpe(&self) -> SType {
+        SType::SBoolean
+    }
+
+    pub(crate) fn op_code(&self) -> OpCode {
+        Self::OP_CODE
+    }
+}
+
+impl SigmaSerializable for ForAll {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)?;
+        self.condition.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let input = Expr::sigma_parse(r)?;
+        let condition = Expr::sigma_parse(r)?;
+        Ok(ForAll::new(input, condition)?)
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+/// Arbitrary impl
+mod arbitrary {
+    use super::*;
+    use crate::mir::expr::arbitrary::ArbExprParams;
+    use crate::types::sfunc::SFunc;
+    use proptest::prelude::*;
+
+    impl Arbitrary for ForAll {
+        type Strategy = BoxedStrategy<Self>;
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SColl(SType::SBoolean.into()),
+                    depth: 1,
+                }),
+                any_with::<Expr>(ArbExprParams {
+                    tpe: SType::SFunc(SFunc {
+                        t_dom: vec![SType::SBoolean],
+                        t_range: SType::SBoolean.into(),
+                        tpe_params: vec![],
+                    }),
+                    depth: 0,
+                }),
+            )
+                .prop_map(|(input, mapper)| ForAll::new(input, mapper).unwrap())
+                .boxed()
+        }
+    }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::mir::expr::Expr;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #![proptest_config(ProptestConfig::with_cases(4))]
+
+        #[test]
+        fn ser_roundtrip(v in any::<ForAll>()) {
+            let expr: Expr = v.into();
+            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
+        }
+    }
+}
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index 9d5688962..ef82b340f 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -158,6 +158,7 @@ impl From<BigInt> for Constant {
     }
 }
 
+#[allow(clippy::clippy::from_over_into)]
 #[impl_for_tuples(2, 4)]
 impl Into<Constant> for Tuple {
     fn into(self) -> Constant {
diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs
index f02b1c4de..4e603f707 100644
--- a/ergotree-ir/src/mir/expr.rs
+++ b/ergotree-ir/src/mir/expr.rs
@@ -4,6 +4,7 @@ use std::convert::TryFrom;
 use std::convert::TryInto;
 
 use crate::serialization::op_code::OpCode;
+use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
 
 use super::and::And;
@@ -16,6 +17,7 @@ use super::coll_by_index::ByIndex;
 use super::coll_exists::Exists;
 use super::coll_filter::Filter;
 use super::coll_fold::Fold;
+use super::coll_forall::ForAll;
 use super::coll_map::Map;
 use super::coll_size::SizeOf;
 use super::collection::Collection;
@@ -45,6 +47,7 @@ use super::sigma_prop_bytes::SigmaPropBytes;
 use super::upcast::Upcast;
 use super::val_def::ValDef;
 use super::val_use::ValUse;
+use super::value::Value;
 
 extern crate derive_more;
 use derive_more::From;
@@ -122,6 +125,8 @@ pub enum Expr {
     Filter(Filter),
     /// Tests whether a predicate holds for at least one element of this collection
     Exists(Exists),
+    /// Tests whether a predicate holds for all elements of this collection.
+    ForAll(ForAll),
     /// Tuple field access
     SelectField(SelectField),
     /// Bool to SigmaProp
@@ -176,6 +181,7 @@ impl Expr {
             Expr::OptionIsDefined(op) => op.op_code(),
             Expr::OptionGetOrElse(op) => op.op_code(),
             Expr::Negation(op) => op.op_code(),
+            Expr::ForAll(op) => op.op_code(),
         }
     }
 
@@ -220,6 +226,7 @@ impl Expr {
             Expr::OptionIsDefined(v) => v.tpe(),
             Expr::OptionGetOrElse(v) => v.tpe(),
             Expr::Negation(v) => v.tpe(),
+            Expr::ForAll(v) => v.tpe(),
         }
     }
 
@@ -251,6 +258,15 @@ impl Expr {
     }
 }
 
+impl<T: Into<Value> + LiftIntoSType> From<T> for Expr {
+    fn from(t: T) -> Self {
+        Expr::Const(Constant {
+            tpe: T::stype(),
+            v: t.into(),
+        })
+    }
+}
+
 /// Unexpected argument on node construction (i.e non-Option input in OptionGet)
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct InvalidArgumentError(pub String);
diff --git a/ergotree-ir/src/mir/method_call.rs b/ergotree-ir/src/mir/method_call.rs
index 8bed42c50..e5e52dcb9 100644
--- a/ergotree-ir/src/mir/method_call.rs
+++ b/ergotree-ir/src/mir/method_call.rs
@@ -3,6 +3,7 @@ use crate::types::smethod::SMethod;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
 
 /** Represents in ErgoTree an invocation of method of the object `obj` with arguments `args`.
  * The SMethod instances in STypeCompanions may have type STypeIdent in methods types,
@@ -25,12 +26,39 @@ pub struct MethodCall {
 }
 
 impl MethodCall {
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(obj: Expr, method: SMethod, args: Vec<Expr>) -> Result<Self, InvalidArgumentError> {
+        if method.tpe().t_dom.len() != args.len() + 1 {
+            return Err(InvalidArgumentError(format!(
+                "MethodCall: expected arguments count {} does not match provided arguments count {}",
+                method.tpe().t_dom.len(), args.len() + 1)));
+        }
+        let mut expected_types = vec![obj.tpe()];
+        let arg_types: Vec<SType> = args.clone().into_iter().map(|a| a.tpe()).collect();
+        expected_types.extend(arg_types);
+        if !method
+            .tpe()
+            .t_dom
+            .iter()
+            .zip(&expected_types)
+            .all(|(expected, actual)| expected == actual)
+        {
+            return Err(InvalidArgumentError(format!(
+                "MethodCall: expected types {:?} do not match provided obj and args types {:?}",
+                method.tpe().t_dom,
+                expected_types
+            )));
+        }
+        Ok(Self {
+            obj: obj.into(),
+            method,
+            args,
+        })
+    }
+
     /// Type
     pub fn tpe(&self) -> SType {
-        match self.method.tpe() {
-            SType::SFunc(sfunc) => *sfunc.t_range.clone(),
-            tpe => tpe.clone(),
-        }
+        *self.method.tpe().t_range.clone()
     }
 
     pub(crate) fn op_code(&self) -> OpCode {
diff --git a/ergotree-ir/src/mir/property_call.rs b/ergotree-ir/src/mir/property_call.rs
index 68e11b407..fa2383696 100644
--- a/ergotree-ir/src/mir/property_call.rs
+++ b/ergotree-ir/src/mir/property_call.rs
@@ -3,6 +3,7 @@ use crate::types::smethod::SMethod;
 use crate::types::stype::SType;
 
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
 
 /// Invocation of object's property
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -14,9 +15,37 @@ pub struct PropertyCall {
 }
 
 impl PropertyCall {
+    /// Create new object, returns an error if any of the requirements failed
+    pub fn new(obj: Expr, method: SMethod) -> Result<Self, InvalidArgumentError> {
+        if method.tpe().t_dom.len() != 1 {
+            return Err(InvalidArgumentError(format!(
+                "PropertyCall: expected method.t_dom to have 1 element, got {}",
+                method.tpe().t_dom.len()
+            )));
+        }
+        let expected_types = vec![obj.tpe()];
+        if !method
+            .tpe()
+            .t_dom
+            .iter()
+            .zip(&expected_types)
+            .all(|(expected, actual)| expected == actual)
+        {
+            return Err(InvalidArgumentError(format!(
+                "MethodCall: expected types {:?} do not match provided obj and args types {:?}",
+                method.tpe().t_dom,
+                expected_types
+            )));
+        }
+        Ok(Self {
+            obj: obj.into(),
+            method,
+        })
+    }
+
     /// Type
     pub fn tpe(&self) -> SType {
-        self.method.tpe().clone()
+        *self.method.tpe().t_range.clone()
     }
 
     pub(crate) fn op_code(&self) -> OpCode {
diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs
index 209d198e2..91090af56 100644
--- a/ergotree-ir/src/mir/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -20,7 +20,8 @@ use crate::util::AsVecI8;
 use super::constant::TryExtractFrom;
 use super::constant::TryExtractFromError;
 use super::constant::TryExtractInto;
-use super::func_value::FuncValue;
+use super::expr::Expr;
+use super::func_value::FuncArg;
 
 extern crate derive_more;
 use derive_more::From;
@@ -92,6 +93,15 @@ impl CollKind {
     }
 }
 
+/// Lambda
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Lambda {
+    /// Argument placeholders
+    pub args: Vec<FuncArg>,
+    /// Body
+    pub body: Box<Expr>,
+}
+
 /// Runtime value
 #[derive(PartialEq, Eq, Debug, Clone, From)]
 pub enum Value {
@@ -124,7 +134,7 @@ pub enum Value {
     /// Optional value
     Opt(Box<Option<Value>>),
     /// lambda
-    FuncValue(FuncValue),
+    Lambda(Lambda),
 }
 
 impl Value {
@@ -140,9 +150,9 @@ impl<T: Into<SigmaProp>> From<T> for Value {
     }
 }
 
-impl Into<Value> for EcPoint {
-    fn into(self) -> Value {
-        Value::GroupElement(Box::new(self))
+impl From<EcPoint> for Value {
+    fn from(v: EcPoint) -> Self {
+        Value::GroupElement(Box::new(v))
     }
 }
 
@@ -190,6 +200,7 @@ impl<T: LiftIntoSType + StoreWrapped + Into<Value>> From<Vec<T>> for Value {
     }
 }
 
+#[allow(clippy::clippy::from_over_into)]
 #[impl_for_tuples(2, 4)]
 impl Into<Value> for Tuple {
     fn into(self) -> Value {
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index cc7b72f56..6e63b8e48 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -12,6 +12,7 @@ use crate::mir::coll_by_index::ByIndex;
 use crate::mir::coll_exists::Exists;
 use crate::mir::coll_filter::Filter;
 use crate::mir::coll_fold::Fold;
+use crate::mir::coll_forall::ForAll;
 use crate::mir::coll_map::Map;
 use crate::mir::coll_size::SizeOf;
 use crate::mir::collection::bool_const_coll_sigma_parse;
@@ -101,6 +102,7 @@ impl SigmaSerializable for Expr {
                     Expr::OptionIsDefined(op) => op.sigma_serialize(w),
                     Expr::OptionGetOrElse(op) => op.sigma_serialize(w),
                     Expr::Negation(op) => op.sigma_serialize(w),
+                    Expr::ForAll(op) => op.sigma_serialize(w),
                 }
             }
         }
@@ -154,10 +156,10 @@ impl SigmaSerializable for Expr {
                 OpCode::LOGICAL_NOT => Ok(LogicalNot::sigma_parse(r)?.into()),
                 OpCode::BIN_AND => Ok(bin_op_sigma_parse(RelationOp::And.into(), r)?),
                 OpCode::BIN_OR => Ok(bin_op_sigma_parse(RelationOp::Or.into(), r)?),
-                OpCode::GT => Ok(bin_op_sigma_parse(RelationOp::GT.into(), r)?),
-                OpCode::LT => Ok(bin_op_sigma_parse(RelationOp::LT.into(), r)?),
-                OpCode::GE => Ok(bin_op_sigma_parse(RelationOp::GE.into(), r)?),
-                OpCode::LE => Ok(bin_op_sigma_parse(RelationOp::LE.into(), r)?),
+                OpCode::GT => Ok(bin_op_sigma_parse(RelationOp::Gt.into(), r)?),
+                OpCode::LT => Ok(bin_op_sigma_parse(RelationOp::Lt.into(), r)?),
+                OpCode::GE => Ok(bin_op_sigma_parse(RelationOp::Ge.into(), r)?),
+                OpCode::LE => Ok(bin_op_sigma_parse(RelationOp::Le.into(), r)?),
                 OpCode::PLUS => Ok(bin_op_sigma_parse(ArithOp::Plus.into(), r)?),
                 OpCode::MINUS => Ok(bin_op_sigma_parse(ArithOp::Minus.into(), r)?),
                 OpCode::MULTIPLY => Ok(bin_op_sigma_parse(ArithOp::Multiply.into(), r)?),
@@ -179,6 +181,7 @@ impl SigmaSerializable for Expr {
                 Map::OP_CODE => Ok(Map::sigma_parse(r)?.into()),
                 Filter::OP_CODE => Ok(Filter::sigma_parse(r)?.into()),
                 Exists::OP_CODE => Ok(Exists::sigma_parse(r)?.into()),
+                ForAll::OP_CODE => Ok(ForAll::sigma_parse(r)?.into()),
                 BoolToSigmaProp::OP_CODE => Ok(BoolToSigmaProp::sigma_parse(r)?.into()),
                 Upcast::OP_CODE => Ok(Upcast::sigma_parse(r)?.into()),
                 If::OP_CODE => Ok(If::sigma_parse(r)?.into()),
diff --git a/ergotree-ir/src/serialization/method_call.rs b/ergotree-ir/src/serialization/method_call.rs
index 5774e2a1c..8603bd56d 100644
--- a/ergotree-ir/src/serialization/method_call.rs
+++ b/ergotree-ir/src/serialization/method_call.rs
@@ -4,10 +4,10 @@ use crate::mir::expr::Expr;
 use crate::mir::method_call::MethodCall;
 use crate::types::smethod::MethodId;
 use crate::types::smethod::SMethod;
-use crate::types::stype_companion::TypeId;
 
 use super::sigma_byte_reader::SigmaByteRead;
 use super::sigma_byte_writer::SigmaByteWrite;
+use super::types::TypeCode;
 use super::SerializationError;
 use super::SigmaSerializable;
 
@@ -21,16 +21,12 @@ impl SigmaSerializable for MethodCall {
     }
 
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let type_id = TypeId::sigma_parse(r)?;
+        let type_id = TypeCode::sigma_parse(r)?;
         let method_id = MethodId::sigma_parse(r)?;
         let obj = Expr::sigma_parse(r)?;
         let args = Vec::<Expr>::sigma_parse(r)?;
         let method = SMethod::from_ids(type_id, method_id);
-        Ok(MethodCall {
-            obj: Box::new(obj),
-            method,
-            args,
-        })
+        Ok(MethodCall::new(obj, method, args)?)
     }
 }
 
@@ -46,11 +42,12 @@ mod tests {
 
     #[test]
     fn ser_roundtrip_property() {
-        let mc: Expr = MethodCall {
-            obj: Box::new(GlobalVars::SelfBox.into()),
-            method: sbox::GET_REG_METHOD.clone(),
-            args: vec![Constant::from(0i8).into()],
-        }
+        let mc: Expr = MethodCall::new(
+            GlobalVars::SelfBox.into(),
+            sbox::GET_REG_METHOD.clone(),
+            vec![Constant::from(0i8).into()],
+        )
+        .unwrap()
         .into();
         assert_eq![sigma_serialize_roundtrip(&mc), mc];
     }
diff --git a/ergotree-ir/src/serialization/property_call.rs b/ergotree-ir/src/serialization/property_call.rs
index 2ee3497ca..1157dccf6 100644
--- a/ergotree-ir/src/serialization/property_call.rs
+++ b/ergotree-ir/src/serialization/property_call.rs
@@ -4,10 +4,10 @@ use crate::mir::expr::Expr;
 use crate::mir::property_call::PropertyCall;
 use crate::types::smethod::MethodId;
 use crate::types::smethod::SMethod;
-use crate::types::stype_companion::TypeId;
 
 use super::sigma_byte_reader::SigmaByteRead;
 use super::sigma_byte_writer::SigmaByteWrite;
+use super::types::TypeCode;
 use super::SerializationError;
 use super::SigmaSerializable;
 
@@ -20,13 +20,10 @@ impl SigmaSerializable for PropertyCall {
     }
 
     fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        let type_id = TypeId::sigma_parse(r)?;
+        let type_id = TypeCode::sigma_parse(r)?;
         let method_id = MethodId::sigma_parse(r)?;
         let obj = Expr::sigma_parse(r)?;
-        Ok(PropertyCall {
-            obj: Box::new(obj),
-            method: SMethod::from_ids(type_id, method_id),
-        })
+        Ok(PropertyCall::new(obj, SMethod::from_ids(type_id, method_id)).unwrap())
     }
 }
 
@@ -40,10 +37,7 @@ mod tests {
 
     #[test]
     fn ser_roundtrip_property() {
-        let mc = PropertyCall {
-            obj: Box::new(Expr::Context),
-            method: scontext::DATA_INPUTS_PROPERTY.clone(),
-        };
+        let mc = PropertyCall::new(Expr::Context, scontext::DATA_INPUTS_PROPERTY.clone()).unwrap();
         let expr = Expr::ProperyCall(mc);
         assert_eq![sigma_serialize_roundtrip(&expr), expr];
     }
diff --git a/ergotree-ir/src/serialization/sigmaboolean.rs b/ergotree-ir/src/serialization/sigmaboolean.rs
index 16574f89b..ecbc50d03 100644
--- a/ergotree-ir/src/serialization/sigmaboolean.rs
+++ b/ergotree-ir/src/serialization/sigmaboolean.rs
@@ -14,10 +14,10 @@ impl SigmaSerializable for SigmaBoolean {
         self.op_code().sigma_serialize(w)?;
         match self {
             SigmaBoolean::ProofOfKnowledge(proof) => match proof {
-                SigmaProofOfKnowledgeTree::ProveDHTuple { .. } => todo!(),
+                SigmaProofOfKnowledgeTree::ProveDhTuple { .. } => todo!(),
                 SigmaProofOfKnowledgeTree::ProveDlog(v) => v.sigma_serialize(w),
             },
-            SigmaBoolean::CAND(_) => todo!(),
+            SigmaBoolean::Cand(_) => todo!(),
             SigmaBoolean::TrivialProp(_) => Ok(()), // besides opCode no additional bytes
         }
     }
diff --git a/ergotree-ir/src/serialization/types.rs b/ergotree-ir/src/serialization/types.rs
index 95fee03bc..292309e1f 100644
--- a/ergotree-ir/src/serialization/types.rs
+++ b/ergotree-ir/src/serialization/types.rs
@@ -11,7 +11,7 @@ use std::convert::TryInto;
 use std::{io, ops::Add};
 use vlq_encode::WriteSigmaVlqExt;
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub struct TypeCode(u8);
 
 impl TypeCode {
@@ -32,6 +32,8 @@ impl TypeCode {
     pub const SSIGMAPROP: TypeCode = Self::new(8);
 
     pub const SANY: TypeCode = Self::new(97);
+    pub const SBOX: TypeCode = Self::new(99);
+    pub const SCONTEXT: TypeCode = Self::new(101);
 
     const COLLECTION_CONSTR_ID: u8 = 1;
     pub const COLLECTION: TypeCode =
diff --git a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
index feddb59bb..5b78de6dd 100644
--- a/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
+++ b/ergotree-ir/src/sigma_protocol/sigma_boolean.rs
@@ -38,7 +38,7 @@ impl From<EcPoint> for ProveDlog {
 /// Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol.
 /// Common input: (g,h,u,v)
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ProveDHTuple {
+pub struct ProveDhTuple {
     gv: Box<EcPoint>,
     hv: Box<EcPoint>,
     uv: Box<EcPoint>,
@@ -49,7 +49,7 @@ pub struct ProveDHTuple {
 #[derive(PartialEq, Eq, Debug, Clone, From)]
 pub enum SigmaProofOfKnowledgeTree {
     /// public key of Diffie Hellman signature protocol
-    ProveDHTuple(ProveDHTuple),
+    ProveDhTuple(ProveDhTuple),
     /// public key of discrete logarithm signature protocol
     ProveDlog(ProveDlog),
 }
@@ -63,7 +63,7 @@ pub enum SigmaBoolean {
     /// Sigma proposition
     ProofOfKnowledge(SigmaProofOfKnowledgeTree),
     /// AND conjunction for sigma propositions
-    CAND(Vec<SigmaBoolean>),
+    Cand(Vec<SigmaBoolean>),
 }
 
 impl SigmaBoolean {
diff --git a/ergotree-ir/src/types.rs b/ergotree-ir/src/types.rs
index 150513efa..983859b28 100644
--- a/ergotree-ir/src/types.rs
+++ b/ergotree-ir/src/types.rs
@@ -2,8 +2,10 @@
 
 pub mod stype;
 
-/// Type representing a box
+/// Box object type companion
 pub mod sbox;
+/// Collection object type companion
+pub mod scoll;
 pub mod scontext;
 /// Function signature type
 pub mod sfunc;
diff --git a/ergotree-ir/src/types/sbox.rs b/ergotree-ir/src/types/sbox.rs
index 7b678c3f2..6b4d2efc4 100644
--- a/ergotree-ir/src/types/sbox.rs
+++ b/ergotree-ir/src/types/sbox.rs
@@ -1,4 +1,4 @@
-#![allow(missing_docs)]
+use crate::serialization::types::TypeCode;
 
 use super::sfunc::SFunc;
 use super::smethod::MethodId;
@@ -8,12 +8,11 @@ use super::stuple::STuple;
 use super::stype::SType;
 use super::stype_companion::STypeCompanion;
 use super::stype_companion::STypeCompanionHead;
-use super::stype_companion::TypeId;
 use super::stype_param::STypeVar;
 use lazy_static::lazy_static;
 
 /// SBox type id
-pub const TYPE_ID: TypeId = TypeId(99);
+pub const TYPE_ID: TypeCode = TypeCode::SBOX;
 /// Box.value property
 pub const VALUE_METHOD_ID: MethodId = MethodId(1);
 /// Box.Rx property
@@ -27,6 +26,7 @@ static S_BOX_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
 };
 
 lazy_static! {
+    /// Box object type companion
     pub static ref S_BOX_TYPE_COMPANION: STypeCompanion = STypeCompanion::new(
         &S_BOX_TYPE_COMPANION_HEAD,
         vec![
@@ -41,42 +41,57 @@ lazy_static! {
     static ref VALUE_METHOD_DESC: SMethodDesc = SMethodDesc {
         method_id: VALUE_METHOD_ID,
         name: "value",
-        tpe: SType::SFunc(SFunc {
+        tpe: SFunc {
             t_dom: vec![SType::SBox],
             t_range: Box::new(SType::SLong),
             tpe_params: vec![],
-        }),
+        },
     };
-    pub static ref VALUE_METHOD: SMethod = SMethod::new(&S_BOX_TYPE_COMPANION, &VALUE_METHOD_DESC,);
+    /// Box.value
+    pub static ref VALUE_METHOD: SMethod = SMethod::new(&S_BOX_TYPE_COMPANION, VALUE_METHOD_DESC.clone(),);
 }
 
 lazy_static! {
     static ref GET_REG_METHOD_DESC: SMethodDesc = SMethodDesc {
         method_id: GET_REG_METHOD_ID,
         name: "getReg",
-        tpe: SType::SFunc(SFunc {
+        tpe: SFunc {
             t_dom: vec![SType::SBox, SType::SByte],
             t_range: Box::new(SType::SOption(Box::new(SType::STypeVar(STypeVar::T)))),
             tpe_params: vec![],
-        }),
+        },
     };
+    /// Box.getReg
     pub static ref GET_REG_METHOD: SMethod =
-        SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
+        SMethod::new(&S_BOX_TYPE_COMPANION, GET_REG_METHOD_DESC.clone(),);
 }
 
 lazy_static! {
     static ref TOKENS_METHOD_DESC: SMethodDesc = SMethodDesc {
         method_id: TOKENS_METHOD_ID,
         name: "tokens",
-        tpe: SType::SFunc(SFunc {
+        tpe: SFunc {
             t_dom: vec![SType::SBox],
             t_range: Box::new(SType::SColl(Box::new(SType::STuple(STuple::pair(
-                SType::SColl(Box::new(SType::SByte)),
-                SType::SLong
+                                SType::SColl(Box::new(SType::SByte)),
+                                SType::SLong
             ))))),
             tpe_params: vec![],
-        }),
+        },
     };
+    /// Box.tokens
     pub static ref TOKENS_METHOD: SMethod =
-        SMethod::new(&S_BOX_TYPE_COMPANION, &TOKENS_METHOD_DESC,);
+        SMethod::new(&S_BOX_TYPE_COMPANION, TOKENS_METHOD_DESC.clone(),);
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_from_ids() {
+        assert!(SMethod::from_ids(TYPE_ID, VALUE_METHOD_ID).name() == "value");
+        assert!(SMethod::from_ids(TYPE_ID, GET_REG_METHOD_ID).name() == "getReg");
+        assert!(SMethod::from_ids(TYPE_ID, TOKENS_METHOD_ID).name() == "tokens");
+    }
 }
diff --git a/ergotree-ir/src/types/scoll.rs b/ergotree-ir/src/types/scoll.rs
new file mode 100644
index 000000000..6fecd9e88
--- /dev/null
+++ b/ergotree-ir/src/types/scoll.rs
@@ -0,0 +1,76 @@
+use crate::serialization::types::TypeCode;
+
+use super::sfunc::SFunc;
+use super::smethod::MethodId;
+use super::smethod::SMethod;
+use super::smethod::SMethodDesc;
+use super::stype::SType;
+use super::stype_companion::STypeCompanion;
+use super::stype_companion::STypeCompanionHead;
+use super::stype_param::STypeVar;
+use lazy_static::lazy_static;
+
+/// type id
+pub const TYPE_ID: TypeCode = TypeCode::COLLECTION;
+/// Coll.indexOf
+pub const INDEX_OF_METHOD_ID: MethodId = MethodId(26);
+/// Coll.flatmap
+pub const FLATMAP_METHOD_ID: MethodId = MethodId(1);
+
+static S_COLL_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
+    type_id: TYPE_ID,
+    type_name: "Coll",
+};
+
+lazy_static! {
+    /// Coll object type companion
+    pub static ref S_COLL_TYPE_COMPANION: STypeCompanion = STypeCompanion::new(
+        &S_COLL_TYPE_COMPANION_HEAD,
+        vec![
+            &INDEX_OF_METHOD_DESC,
+            &FLATMAP_METHOD_DESC
+        ]
+    );
+}
+
+lazy_static! {
+    static ref INDEX_OF_METHOD_DESC: SMethodDesc = SMethodDesc {
+        method_id: INDEX_OF_METHOD_ID,
+        name: "indexOf",
+        tpe: SFunc {
+            t_dom: vec![SType::SColl(SType::STypeVar(STypeVar::T).into()), SType::STypeVar(STypeVar::T), SType::SInt],
+            t_range: SType::SInt.into(),
+            tpe_params: vec![],
+        },
+    };
+    /// Coll.indexOf
+    pub static ref INDEX_OF_METHOD: SMethod = SMethod::new(&S_COLL_TYPE_COMPANION, INDEX_OF_METHOD_DESC.clone());
+}
+
+lazy_static! {
+    static ref FLATMAP_METHOD_DESC: SMethodDesc = SMethodDesc {
+        method_id: FLATMAP_METHOD_ID,
+        name: "flatMap",
+        tpe: SFunc {
+            t_dom: vec![SType::SColl(SType::STypeVar(STypeVar::IV).into()),
+            SType::SFunc(
+                SFunc::new(vec![SType::STypeVar(STypeVar::IV)], SType::STypeVar(STypeVar::OV), vec![] ))
+            ],
+            t_range: SType::SColl(SType::STypeVar(STypeVar::OV).into()).into(),
+            tpe_params: vec![],
+        },
+    };
+    /// Coll.flatMap
+    pub static ref FLATMAP_METHOD: SMethod = SMethod::new(&S_COLL_TYPE_COMPANION, FLATMAP_METHOD_DESC.clone());
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_from_ids() {
+        assert!(SMethod::from_ids(TYPE_ID, INDEX_OF_METHOD_ID).name() == "indexOf");
+        assert!(SMethod::from_ids(TYPE_ID, FLATMAP_METHOD_ID).name() == "flatMap");
+    }
+}
diff --git a/ergotree-ir/src/types/scontext.rs b/ergotree-ir/src/types/scontext.rs
index 5ab04ca11..e5f9ef3e6 100644
--- a/ergotree-ir/src/types/scontext.rs
+++ b/ergotree-ir/src/types/scontext.rs
@@ -1,5 +1,7 @@
 #![allow(missing_docs)]
 
+use crate::serialization::types::TypeCode;
+
 use super::sfunc::SFunc;
 use super::smethod::MethodId;
 use super::smethod::SMethod;
@@ -7,10 +9,9 @@ use super::smethod::SMethodDesc;
 use super::stype::SType;
 use super::stype_companion::STypeCompanion;
 use super::stype_companion::STypeCompanionHead;
-use super::stype_companion::TypeId;
 use lazy_static::lazy_static;
 
-pub const TYPE_ID: TypeId = TypeId(101);
+pub const TYPE_ID: TypeCode = TypeCode::SCONTEXT;
 
 static S_CONTEXT_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
     type_id: TYPE_ID,
@@ -22,11 +23,11 @@ lazy_static! {
     static ref DATA_INPUTS_PROPERTY_METHOD_DESC: SMethodDesc = SMethodDesc {
         method_id: DATA_INPUTS_PROPERTY_METHOD_ID,
         name: "dataInputs",
-        tpe: SType::SFunc(SFunc {
+        tpe: SFunc {
             t_dom: vec![SType::SContext],
             t_range: Box::new(SType::SColl(Box::new(SType::SBox))),
             tpe_params: vec![],
-        }),
+        },
     };
 }
 
@@ -38,6 +39,18 @@ lazy_static! {
 }
 
 lazy_static! {
-    pub static ref DATA_INPUTS_PROPERTY: SMethod =
-        SMethod::new(&S_CONTEXT_TYPE_COMPANION, &DATA_INPUTS_PROPERTY_METHOD_DESC,);
+    pub static ref DATA_INPUTS_PROPERTY: SMethod = SMethod::new(
+        &S_CONTEXT_TYPE_COMPANION,
+        DATA_INPUTS_PROPERTY_METHOD_DESC.clone(),
+    );
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_from_ids() {
+        assert!(SMethod::from_ids(TYPE_ID, DATA_INPUTS_PROPERTY_METHOD_ID).name() == "dataInputs");
+    }
 }
diff --git a/ergotree-ir/src/types/sfunc.rs b/ergotree-ir/src/types/sfunc.rs
index 3048eee1e..719a02404 100644
--- a/ergotree-ir/src/types/sfunc.rs
+++ b/ergotree-ir/src/types/sfunc.rs
@@ -1,5 +1,8 @@
+use std::collections::HashMap;
+
 use super::stype::SType;
 use super::stype_param::STypeParam;
+use super::stype_param::STypeVar;
 
 /// Function signature type
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -11,3 +14,31 @@ pub struct SFunc {
     /// Type parameters if the function is generic
     pub tpe_params: Vec<STypeParam>,
 }
+
+impl SFunc {
+    /// Create new SFunc
+    pub fn new(t_dom: Vec<SType>, t_range: SType, tpe_params: Vec<STypeParam>) -> Self {
+        Self {
+            t_dom,
+            t_range: t_range.into(),
+            tpe_params,
+        }
+    }
+
+    pub(crate) fn with_subst(self, subst: &HashMap<STypeVar, SType>) -> Self {
+        let remaining_vars = self
+            .tpe_params
+            .into_iter()
+            .filter(|v| !subst.contains_key(&v.ident))
+            .collect();
+        SFunc {
+            t_dom: self
+                .t_dom
+                .iter()
+                .map(|a| a.clone().with_subst(subst))
+                .collect(),
+            t_range: Box::new(self.t_range.with_subst(subst)),
+            tpe_params: remaining_vars,
+        }
+    }
+}
diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
index 63579ad5d..5b8434a56 100644
--- a/ergotree-ir/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -1,12 +1,15 @@
 use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::types::TypeCode;
 use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
+use std::collections::HashMap;
 use std::io::Error;
 
+use super::sfunc::SFunc;
 use super::stype::SType;
 use super::stype_companion::STypeCompanion;
-use super::stype_companion::TypeId;
+use super::stype_param::STypeVar;
 
 /// Method id unique among the methods of the same object
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -27,15 +30,12 @@ impl SigmaSerializable for MethodId {
 pub struct SMethod {
     /// Object type companion
     pub obj_type: &'static STypeCompanion,
-    method_raw: &'static SMethodDesc,
+    method_raw: SMethodDesc,
 }
 
 impl SMethod {
     /// Create new SMethod
-    pub(crate) fn new(
-        obj_type: &'static STypeCompanion,
-        method_raw: &'static SMethodDesc,
-    ) -> SMethod {
+    pub(crate) const fn new(obj_type: &'static STypeCompanion, method_raw: SMethodDesc) -> SMethod {
         SMethod {
             obj_type,
             method_raw,
@@ -43,7 +43,7 @@ impl SMethod {
     }
 
     /// Get method from type and method ids
-    pub fn from_ids(type_id: TypeId, method_id: MethodId) -> Self {
+    pub fn from_ids(type_id: TypeCode, method_id: MethodId) -> Self {
         let obj_type = STypeCompanion::type_by_id(type_id);
         match obj_type.method_by_id(&method_id) {
             Some(m) => m,
@@ -55,7 +55,7 @@ impl SMethod {
     }
 
     /// Type
-    pub fn tpe(&self) -> &SType {
+    pub fn tpe(&self) -> &SFunc {
         &self.method_raw.tpe
     }
 
@@ -68,20 +68,33 @@ impl SMethod {
     pub fn method_id(&self) -> MethodId {
         self.method_raw.method_id.clone()
     }
+
+    /// Return new SMethod with type variables substituted
+    pub fn with_concrete_types(self, subst: &HashMap<STypeVar, SType>) -> Self {
+        let new_tpe = self.method_raw.tpe.clone().with_subst(subst);
+        Self {
+            method_raw: self.method_raw.with_tpe(new_tpe),
+            ..self
+        }
+    }
 }
 
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub(crate) struct SMethodDesc {
     pub(crate) name: &'static str,
     pub(crate) method_id: MethodId,
-    pub(crate) tpe: SType,
+    pub(crate) tpe: SFunc,
 }
 
 impl SMethodDesc {
-    pub(crate) fn as_method(&'static self, obj_type: &'static STypeCompanion) -> SMethod {
+    pub(crate) fn as_method(&self, obj_type: &'static STypeCompanion) -> SMethod {
         SMethod {
             obj_type,
-            method_raw: self,
+            method_raw: self.clone(),
         }
     }
+
+    pub(crate) fn with_tpe(self, tpe: SFunc) -> Self {
+        Self { tpe, ..self }
+    }
 }
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index d49c9627a..1f78f2500 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -1,5 +1,6 @@
 //! SType hierarchy
 
+use std::collections::HashMap;
 use std::convert::TryInto;
 
 use impl_trait_for_tuples::impl_for_tuples;
@@ -93,6 +94,17 @@ impl SType {
             SType::SByte | SType::SShort | SType::SInt | SType::SLong | SType::SBigInt
         )
     }
+
+    pub(crate) fn with_subst(self, subst: &HashMap<STypeVar, SType>) -> Self {
+        match self {
+            SType::STypeVar(ref tpe_var) => subst.get(&tpe_var).cloned().unwrap_or(self),
+            SType::SOption(tpe) => SType::SOption(tpe.with_subst(subst).into()),
+            SType::SColl(tpe) => SType::SColl(tpe.with_subst(subst).into()),
+            SType::STuple(_) => todo!("type substitution in STuple is not yet implemented"),
+            SType::SFunc(sfunc) => SType::SFunc(sfunc.with_subst(subst)),
+            _ => self,
+        }
+    }
 }
 
 impl From<STuple> for SType {
diff --git a/ergotree-ir/src/types/stype_companion.rs b/ergotree-ir/src/types/stype_companion.rs
index d8c930a55..5dda01bd7 100644
--- a/ergotree-ir/src/types/stype_companion.rs
+++ b/ergotree-ir/src/types/stype_companion.rs
@@ -1,34 +1,17 @@
 use std::fmt::Debug;
-use std::io::Error;
 
-use crate::serialization::sigma_byte_reader::SigmaByteRead;
-use crate::serialization::sigma_byte_writer::SigmaByteWrite;
-use crate::serialization::SerializationError;
-use crate::serialization::SigmaSerializable;
+use crate::serialization::types::TypeCode;
 
 use super::sbox;
+use super::scoll;
 use super::scontext;
 use super::smethod::MethodId;
 use super::smethod::SMethod;
 use super::smethod::SMethodDesc;
 
-/// Object type id
-#[derive(PartialEq, Eq, Debug, Clone, Copy)]
-pub struct TypeId(pub u8);
-
-impl SigmaSerializable for TypeId {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), Error> {
-        w.put_u8(self.0)
-    }
-
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
-        Ok(Self(r.get_u8()?))
-    }
-}
-
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub(crate) struct STypeCompanionHead {
-    pub type_id: TypeId,
+    pub type_id: TypeCode,
     pub type_name: &'static str,
 }
 
@@ -40,7 +23,7 @@ pub struct STypeCompanion {
 }
 
 impl STypeCompanion {
-    pub(crate) fn new(
+    pub(crate) const fn new(
         head: &'static STypeCompanionHead,
         methods: Vec<&'static SMethodDesc>,
     ) -> Self {
@@ -48,11 +31,13 @@ impl STypeCompanion {
     }
 
     /// Get type companion for a givec type id
-    pub fn type_by_id(type_id: TypeId) -> &'static STypeCompanion {
+    pub fn type_by_id(type_id: TypeCode) -> &'static STypeCompanion {
         if type_id == scontext::S_CONTEXT_TYPE_COMPANION.type_id() {
             &scontext::S_CONTEXT_TYPE_COMPANION
         } else if type_id == sbox::S_BOX_TYPE_COMPANION.type_id() {
             &sbox::S_BOX_TYPE_COMPANION
+        } else if type_id == scoll::S_COLL_TYPE_COMPANION.type_id() {
+            &scoll::S_COLL_TYPE_COMPANION
         } else {
             todo!("cannot find STypeCompanion for {0:?} type id", type_id)
         }
@@ -72,7 +57,7 @@ impl STypeCompanion {
     }
 
     /// Get object type id for this type companion
-    pub fn type_id(&'static self) -> TypeId {
+    pub fn type_id(&'static self) -> TypeCode {
         self.head.type_id
     }
 
diff --git a/ergotree-ir/src/types/stype_param.rs b/ergotree-ir/src/types/stype_param.rs
index 4faf23633..a6a1f1f27 100644
--- a/ergotree-ir/src/types/stype_param.rs
+++ b/ergotree-ir/src/types/stype_param.rs
@@ -1,7 +1,7 @@
 use super::stype::SType;
 
 /// Type variable for generic signatures
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
 pub struct STypeVar {
     /// Type variable name (e.g. "T")
     pub name: &'static str,
@@ -10,12 +10,16 @@ pub struct STypeVar {
 impl STypeVar {
     /// "T" type variable
     pub const T: STypeVar = STypeVar { name: "T" };
+    /// "IV"(Input Value) type variable
+    pub const IV: STypeVar = STypeVar { name: "IV" };
+    /// "OV"(Input Value) type variable
+    pub const OV: STypeVar = STypeVar { name: "OV" };
 }
 
 /// Type parameter
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct STypeParam {
-    ident: STypeVar,
+    pub(crate) ident: STypeVar,
     upper_bound: Option<SType>,
     lower_bound: Option<SType>,
 }


Test patch: