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

Issue:
Eval/deser tests for AMM contracts from EIP-14
https://github.com/ergoplatform/eips/pull/27/files


Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 2e70d8e47..66bb5f1e7 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ### Added 
 - `Coll.indexOf`, `flatMap`, `forall` IR nodes (evaluation, serialization) [#220](https://github.com/ergoplatform/sigma-rust/pull/220);
+- Complete types serialization implementation [#223](https://github.com/ergoplatform/sigma-rust/pull/223);
+- `Tuple` constructor evaluation and serialization [#223](https://github.com/ergoplatform/sigma-rust/pull/223);
 
 ## [0.8.0] - 2021-03-22
 
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 1b153bd9e..f8ca4d192 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -52,12 +52,20 @@ pub struct ErgoBoxArena(HashMap<BoxId, ErgoBox>);
 
 impl ErgoBoxArena {
     /// Create new arena and store given boxes
-    pub fn new(self_box: ErgoBox, outputs: Vec<ErgoBox>, data_inputs: Vec<ErgoBox>) -> Self {
+    pub fn new(
+        self_box: ErgoBox,
+        outputs: Vec<ErgoBox>,
+        data_inputs: Vec<ErgoBox>,
+        inputs: Vec<ErgoBox>,
+    ) -> Self {
         let mut m = HashMap::new();
         m.insert(self_box.box_id(), self_box);
         outputs.into_iter().for_each(|b| {
             m.insert(b.box_id(), b);
         });
+        inputs.into_iter().for_each(|b| {
+            m.insert(b.box_id(), b);
+        });
         data_inputs.into_iter().for_each(|b| {
             m.insert(b.box_id(), b);
         });
@@ -97,14 +105,25 @@ pub fn make_context(
     let data_inputs: Vec<ErgoBox> = tx_ctx.data_boxes.clone();
     let self_box_ir = self_box.box_id().into();
     let outputs_ir = outputs.iter().map(|b| b.box_id().into()).collect();
+    let inputs_ir = tx_ctx
+        .boxes_to_spend
+        .iter()
+        .map(|b| b.box_id().into())
+        .collect();
     let data_inputs_ir = data_inputs.iter().map(|b| b.box_id().into()).collect();
-    let box_arena = Rc::new(ErgoBoxArena::new(self_box, outputs, data_inputs));
+    let box_arena = Rc::new(ErgoBoxArena::new(
+        self_box,
+        outputs,
+        data_inputs,
+        tx_ctx.boxes_to_spend.clone(),
+    ));
     Ok(Context {
         box_arena,
         height,
         self_box: self_box_ir,
         outputs: outputs_ir,
         data_inputs: data_inputs_ir,
+        inputs: inputs_ir,
     })
 }
 
diff --git a/ergotree-interpreter/src/contracts.rs b/ergotree-interpreter/src/contracts.rs
index e50c234fa..6633ebcd1 100644
--- a/ergotree-interpreter/src/contracts.rs
+++ b/ergotree-interpreter/src/contracts.rs
@@ -166,4 +166,75 @@ mod tests {
             .unwrap();
         assert!(res);
     }
+
+    #[test]
+    fn amm_simple_pool() {
+        // from eip-14 https://github.com/ergoplatform/eips/pull/27/files
+        let p2s_addr_str = "k6fD5ht5e1itDejPFV2VzAoHv478KQCbDnLAL6XUVeEu8KDaboCVZAoFz2AtMoLqM3CgQfr2TZhpwz7K96AgwTXDvBVeTchJ31jjD46Di1W67H8wwFcivnY62UB6L7HWzCkbYuiZaAq2qSJta5Twt4A2Aaoy7xViWcyLUVNAyQYDJXKhVBAGwp76i2too5yWUmEU4zt9XnjJAUt1FFfurNtTNHNPDbqmTRE4crz347q6rfbvkMmg9Jtk9rSiPCQpKjdbZVzUnP4CUw6AvQH6rZXxgNMktAtjQdHhCnrCmf78FwCKqYS54asKd1MFgYNT4NzPwmdZF6JtQt1vvkjZXqpGkjy33xxDNYy8JZS8eeqVgZErPeJ1aj4aaK8gvmApUgGStMDFeFYjuQqZiZxEAHNdAXDg7hyGnmfzA6Hj9zcB7p9nKCDNhEQEMPL1kMG5aXvt2HUPXqiCkLrv596DaGmRMN3gMJaj1T1AfMYNwZozcJ9uUSK4i6Xham28HWAekTtDPhobnmjvkubwLVTtvUumWHtDWFxYSJPF7vqzgZqg6Y5unMF";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        assert!(addr.script().unwrap().proposition().is_ok());
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        // let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+        //     .try_into()
+        //     .unwrap();
+        // assert!(!res);
+    }
+
+    #[test]
+    fn amm_simple_swap() {
+        // from eip-14 https://github.com/ergoplatform/eips/pull/27/files
+        let p2s_addr_str = "cLPHJ3MHuKAHoCUwGhcEFw5sWJqvPwFyKxTRj1aUoMwgAz78Fg3zLXRhBup9Te1WLau1gZXNmXvUmeXGCd7QLeqB7ArrT3v5cg26piEtqymM6j2SkgYVCobgoAGKeTf6nMLxv1uVrLdjt1GnPxG1MuWj7Es7Dfumotbx9YEaxwqtTUC5SKsJc9LCpAmNWRAQbU6tVVEvmfwWivrGoZ3L5C4DMisxN3U";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        // let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+        //     .try_into()
+        //     .unwrap();
+        // assert!(!res);
+    }
+
+    #[test]
+    fn amm_conc_pool_root() {
+        // from eip-14 https://github.com/ergoplatform/eips/pull/27/files
+        let p2s_addr_str = "3STRfQWC9Xb5wAxBiEQ74uTFSemk1oHn43mwj9tMCeu2a3A4kie1bY2qsCdRaEmdQoq3B4tXQuzq9nm84A8PmBgCzgGDEZf2pgYoAUc6krZxUY3rvKWW44ZpzN3u5bFRpKDo6rxKtxX2tw99xmfyfaVBejgDaTfsib2PSVsu9hrLQ3SouECWHQMjDA3Pi8ZuCvQeW8GDkZfHPr3SgwaxY1jpY2njsmf3JBASMoVZ6Mfpg63Q6mBno7mKUSCE7vNHHUZe2V7JEikwjPkaxSWxnwy3J17faGtiEHZLKiNQ9WNtsJLbdVp56dQGfC2zaiXjhx1XJK6m4Nh2M8yEvSuBzanRBAJqrNseGS97tk2iLqqfHrqqmmDsHY3mujCURky4SLr7YLk4B";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        // let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+        //     .try_into()
+        //     .unwrap();
+        // assert!(!res);
+    }
+
+    #[test]
+    fn amm_conc_pool_boot() {
+        // from eip-14 https://github.com/ergoplatform/eips/pull/27/files
+        let p2s_addr_str = "6Mv73vd1MnJp6AQg5vHGP9nujFc3Y1PL5gzeUt9PzCaUiQug7ueQGU1bDkmFkCspq4LU8j3T8yY6UyJQKSfah5qEDzjx8QCJF47NBG5jxgPxmBHkM6cUgnYa5ngzn9jrpAn379UC7o5nugTg3HYWZGk3APMcRftkrC3EgroiVMEmSkDcDwaebkNWKfKe3JXgewoTrgZ2YLMafr3JfX47C1zddoWDhS8TWryQYEprkP334eisuh1Fr2iNTW9ruV6m38cRkfRfzSBHYq45mvNLH7JQo6uQZ4NFPx4t27Q5A3mSqCpk7ATThFcQmc2w3Pp2F6xL87c94gxk83G8UEqkAhmaNfoj19zji9rxqRzq9gJeTLBraHR2DchKtahH8HhFPg5DZ4SjwJ4MHqTDF";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        // let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+        //     .try_into()
+        //     .unwrap();
+        // assert!(!res);
+    }
+
+    #[test]
+    fn amm_conc_pool() {
+        // from eip-14 https://github.com/ergoplatform/eips/pull/27/files
+        let p2s_addr_str = "AhCu1UkNT4c9q3B2Lb7gNgvZWCdXL8iYgmNxTYiy4S3wgKWFFW6kz9v7pvY8NqC7g4wgXXwzJY1fQVn2xrLkiyiQWsorq5dR7d5KnDAY43H4GvSVjaDciadXCSHCb8jgk8mFSQCwoZHweLmMJ25312wT85AySJgYUuzdUxMz4EnQpiwZR2XVZq3M81gycuqP9gUryryjN4J1cAF3yL3kZR3rREubBvJ2CY5hF74Xaj2jwajivkESkqq22ieWWG2sK7dk1A7KHr1MmiXGcUBAMMGPAu3mVCeFW9SongxP9hodnJThLknjWRBBBC6wq5jNkSdHrMbdaQM3XesXqGTk9KwWpnSL92E96muU2k8FQbo5isps1r5ciYVrFptfEAC3tWbwcVmRKtrgxtCex6bP5aBZYjaH6L9QQbkYriDAcQ1iZcpf3hHCqURjRXL7i72C3aGBwzzspQvhLof6x4f4gPxTCtF1bNUxddUL6DJ1PbQWzVH8taivjhHohis6sRn3Akvv4xaZRJdKZ8rDuiounRKNXi8VoNgVEZbSFYtfweRSdsiXJCkhtehLWdtFTk1eg7djASdBGKaguvtEBcGaAALVDUoH479VskPUQ6hrfS7KcWrATBdb8sf4W5MFpx7UNitzq2fzSKC96mQRUzy5uELe7Y7vexm5ArNEyr6ARkypZypSzJ2CEifjVxxRBEWVtbdqHrwP4gWv6cMdbqFWwuXAw2BZQnWpZFtKAGQ9m";
+        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
+        let addr = encoder.parse_address_from_str(p2s_addr_str).unwrap();
+        let script: Rc<Expr> = addr.script().unwrap().proposition().unwrap();
+        dbg!(&script);
+        // let res: bool = eval_out_wo_ctx::<SigmaProp>(script.as_ref())
+        //     .try_into()
+        //     .unwrap();
+        // assert!(!res);
+    }
 }
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index 1d4583743..c66d0b482 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -59,6 +59,7 @@ pub(crate) mod scoll;
 pub(crate) mod scontext;
 pub(crate) mod select_field;
 pub(crate) mod sigma_prop_bytes;
+pub(crate) mod tuple;
 pub(crate) mod upcast;
 pub(crate) mod val_use;
 
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index baf7578a4..c08b6cf90 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -18,6 +18,8 @@ pub struct Context {
     pub outputs: Vec<IrBoxId>,
     /// Spending transaction data inputs
     pub data_inputs: Vec<IrBoxId>,
+    /// Spending transaction inputs
+    pub inputs: Vec<IrBoxId>,
 }
 
 #[cfg(feature = "arbitrary")]
@@ -38,17 +40,22 @@ mod arbitrary {
                 0..i32::MAX,
                 any::<IrErgoBoxDummy>(),
                 vec(any::<IrErgoBoxDummy>(), 1..3),
+                vec(any::<IrErgoBoxDummy>(), 1..3),
                 vec(any::<IrErgoBoxDummy>(), 0..3),
             )
-                .prop_map(|(height, self_box, outputs, data_inputs)| {
+                .prop_map(|(height, self_box, outputs, inputs, data_inputs)| {
                     let self_box_id = self_box.id();
                     let outputs_ids = outputs.iter().map(|b| b.id()).collect();
+                    let inputs_ids = inputs.iter().map(|b| b.id()).collect();
                     let data_inputs_ids = data_inputs.iter().map(|b| b.id()).collect();
                     let mut m = HashMap::new();
                     m.insert(self_box_id.clone(), self_box);
                     outputs.into_iter().for_each(|b| {
                         m.insert(b.id(), b);
                     });
+                    inputs.into_iter().for_each(|b| {
+                        m.insert(b.id(), b);
+                    });
                     data_inputs.into_iter().for_each(|b| {
                         m.insert(b.id(), b);
                     });
@@ -59,6 +66,7 @@ mod arbitrary {
                         self_box: self_box_id,
                         outputs: outputs_ids,
                         data_inputs: data_inputs_ids,
+                        inputs: inputs_ids,
                     }
                 })
                 .boxed()
diff --git a/ergotree-interpreter/src/eval/expr.rs b/ergotree-interpreter/src/eval/expr.rs
index 8b1aba4b1..8d47278ac 100644
--- a/ergotree-interpreter/src/eval/expr.rs
+++ b/ergotree-interpreter/src/eval/expr.rs
@@ -49,6 +49,7 @@ impl Evaluable for Expr {
             Expr::OptionGetOrElse(op) => op.eval(env, ctx),
             Expr::Negation(op) => op.eval(env, ctx),
             Expr::ForAll(op) => op.eval(env, ctx),
+            Expr::Tuple(op) => op.eval(env, ctx),
         }
     }
 }
diff --git a/ergotree-interpreter/src/eval/global_vars.rs b/ergotree-interpreter/src/eval/global_vars.rs
index dbabca4c1..84808a90f 100644
--- a/ergotree-interpreter/src/eval/global_vars.rs
+++ b/ergotree-interpreter/src/eval/global_vars.rs
@@ -12,10 +12,7 @@ impl Evaluable for GlobalVars {
             GlobalVars::Height => Ok(ectx.ctx.height.clone().into()),
             GlobalVars::SelfBox => Ok(ectx.ctx.self_box.clone().into()),
             GlobalVars::Outputs => Ok(ectx.ctx.outputs.clone().into()),
-            _ => Err(EvalError::UnexpectedExpr(format!(
-                "Don't know how to eval GlobalVars: {0:?}",
-                self
-            ))),
+            GlobalVars::Inputs => Ok(ectx.ctx.inputs.clone().into()),
         }
     }
 }
@@ -57,4 +54,13 @@ mod tests {
             ctx.outputs
         );
     }
+
+    #[test]
+    fn eval_inputs() {
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<Vec<IrBoxId>>(&GlobalVars::Inputs.into(), ctx.clone()),
+            ctx.inputs
+        );
+    }
 }
diff --git a/ergotree-interpreter/src/eval/scoll.rs b/ergotree-interpreter/src/eval/scoll.rs
index 7b32c353c..7cb975eb4 100644
--- a/ergotree-interpreter/src/eval/scoll.rs
+++ b/ergotree-interpreter/src/eval/scoll.rs
@@ -114,7 +114,7 @@ mod tests {
             coll_const.into(),
             scoll::INDEX_OF_METHOD
                 .clone()
-                .with_concrete_types(&[(STypeVar::T, SType::SLong)].iter().cloned().collect()),
+                .with_concrete_types(&[(STypeVar::t(), SType::SLong)].iter().cloned().collect()),
             vec![2i64.into(), 0i32.into()],
         )
         .unwrap()
@@ -130,7 +130,7 @@ mod tests {
             coll_const.into(),
             scoll::INDEX_OF_METHOD
                 .clone()
-                .with_concrete_types(&[(STypeVar::T, SType::SLong)].iter().cloned().collect()),
+                .with_concrete_types(&[(STypeVar::t(), SType::SLong)].iter().cloned().collect()),
             vec![3i64.into(), 0i32.into()],
         )
         .unwrap()
@@ -158,8 +158,8 @@ mod tests {
             coll_const.into(),
             scoll::FLATMAP_METHOD.clone().with_concrete_types(
                 &[
-                    (STypeVar::IV, SType::SLong),
-                    (STypeVar::OV, SType::SBoolean),
+                    (STypeVar::iv(), SType::SLong),
+                    (STypeVar::ov(), SType::SBoolean),
                 ]
                 .iter()
                 .cloned()
diff --git a/ergotree-interpreter/src/eval/tuple.rs b/ergotree-interpreter/src/eval/tuple.rs
new file mode 100644
index 000000000..796875caa
--- /dev/null
+++ b/ergotree-interpreter/src/eval/tuple.rs
@@ -0,0 +1,35 @@
+use std::convert::TryInto;
+
+use ergotree_ir::mir::tuple::Tuple;
+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 Tuple {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let items_v: Result<Vec<Value>, EvalError> =
+            self.items.iter().map(|i| i.eval(env, ctx)).collect();
+        Ok(Value::Tup(items_v?.try_into().unwrap()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::eval::tests::eval_out_wo_ctx;
+    use ergotree_ir::mir::expr::Expr;
+    use ergotree_ir::mir::global_vars::GlobalVars;
+
+    #[test]
+    fn eval() {
+        let e1: Expr = 1i64.into();
+        let e2: Expr = GlobalVars::Height.into();
+        let exprs = vec![e1, e2];
+        let tuple: Expr = Tuple::new(exprs).unwrap().into();
+        let res = eval_out_wo_ctx::<Value>(&tuple);
+        assert!(matches!(res, Value::Tup(_)));
+    }
+}
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index 8d7e2a95b..ce9df7a77 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -62,6 +62,8 @@ pub mod property_call;
 pub mod select_field;
 /// Extract serialized bytes of a SigmaProp value
 pub mod sigma_prop_bytes;
+/// Tuple of elements
+pub mod tuple;
 pub mod upcast;
 /// Variable definition
 pub mod val_def;
diff --git a/ergotree-ir/src/mir/coll_size.rs b/ergotree-ir/src/mir/coll_size.rs
index 4fa5bfab5..5c13e6810 100644
--- a/ergotree-ir/src/mir/coll_size.rs
+++ b/ergotree-ir/src/mir/coll_size.rs
@@ -67,7 +67,7 @@ mod arbitrary {
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             any_with::<Expr>(ArbExprParams {
-                tpe: SType::SColl(SType::STypeVar(STypeVar::T).into()),
+                tpe: SType::SColl(SType::STypeVar(STypeVar::t()).into()),
                 depth: 1,
             })
             .prop_map(|input| Self {
diff --git a/ergotree-ir/src/mir/expr.rs b/ergotree-ir/src/mir/expr.rs
index 4e603f707..60f660e82 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::stuple::STupleItemsOutOfBoundsError;
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
 
@@ -44,6 +45,7 @@ use super::or::Or;
 use super::property_call::PropertyCall;
 use super::select_field::SelectField;
 use super::sigma_prop_bytes::SigmaPropBytes;
+use super::tuple::Tuple;
 use super::upcast::Upcast;
 use super::val_def::ValDef;
 use super::val_use::ValUse;
@@ -62,6 +64,8 @@ pub enum Expr {
     ConstPlaceholder(ConstantPlaceholder),
     /// Collection declaration (array of expressions of the same type)
     Collection(Collection),
+    /// Tuple declaration
+    Tuple(Tuple),
     /// Predefined functions (global)
     /// Blake2b256 hash calculation
     CalcBlake2b256(CalcBlake2b256),
@@ -182,6 +186,7 @@ impl Expr {
             Expr::OptionGetOrElse(op) => op.op_code(),
             Expr::Negation(op) => op.op_code(),
             Expr::ForAll(op) => op.op_code(),
+            Expr::Tuple(op) => op.op_code(),
         }
     }
 
@@ -227,6 +232,7 @@ impl Expr {
             Expr::OptionGetOrElse(v) => v.tpe(),
             Expr::Negation(v) => v.tpe(),
             Expr::ForAll(v) => v.tpe(),
+            Expr::Tuple(v) => v.tpe(),
         }
     }
 
@@ -281,6 +287,12 @@ impl From<InvalidExprEvalTypeError> for InvalidArgumentError {
     }
 }
 
+impl From<STupleItemsOutOfBoundsError> for InvalidArgumentError {
+    fn from(e: STupleItemsOutOfBoundsError) -> Self {
+        InvalidArgumentError(format!("{0:?}", e))
+    }
+}
+
 impl<T: TryFrom<Expr>> TryExtractFrom<Expr> for T {
     fn try_extract_from(v: Expr) -> Result<Self, TryExtractFromError> {
         let res: Result<Self, TryExtractFromError> = v.clone().try_into().map_err(|_| {
diff --git a/ergotree-ir/src/mir/tuple.rs b/ergotree-ir/src/mir/tuple.rs
new file mode 100644
index 000000000..a47862e9f
--- /dev/null
+++ b/ergotree-ir/src/mir/tuple.rs
@@ -0,0 +1,104 @@
+use std::convert::TryInto;
+
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stuple::STuple;
+use crate::types::stuple::TupleItems;
+use crate::types::stype::SType;
+
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+
+/// Tuple of elements
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Tuple {
+    /// Tuple items
+    pub items: TupleItems<Expr>,
+}
+
+impl Tuple {
+    pub(crate) const OP_CODE: OpCode = OpCode::TUPLE;
+
+    /// Create new object
+    pub fn new(items: Vec<Expr>) -> Result<Self, InvalidArgumentError> {
+        Ok(Tuple {
+            items: items.try_into()?,
+        })
+    }
+
+    /// Type
+    pub fn tpe(&self) -> SType {
+        let types: Vec<SType> = self.items.iter().map(|it| it.tpe()).collect();
+        SType::STuple(STuple {
+            items: types.try_into().unwrap(),
+        })
+    }
+
+    pub(crate) fn op_code(&self) -> OpCode {
+        Self::OP_CODE
+    }
+}
+
+impl SigmaSerializable for Tuple {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        w.put_u8(self.items.len() as u8)?;
+        self.items.iter().try_for_each(|i| i.sigma_serialize(w))
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let items_count = r.get_u8()?;
+        let mut items = Vec::with_capacity(items_count as usize);
+        for _ in 0..items_count {
+            items.push(Expr::sigma_parse(r)?);
+        }
+        Ok(Tuple {
+            items: items.try_into()?,
+        })
+    }
+}
+
+#[cfg(feature = "arbitrary")]
+/// Arbitrary impl
+mod arbitrary {
+
+    use super::*;
+    use crate::mir::constant::Constant;
+    use proptest::collection::*;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Tuple {
+        type Strategy = BoxedStrategy<Self>;
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                vec(any::<Expr>(), 2..4),
+                vec(any::<Constant>().prop_map_into(), 2..4)
+            ]
+            .prop_map(move |items| Self::new(items).unwrap())
+            .boxed()
+        }
+    }
+}
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+mod tests {
+    use super::*;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use proptest::prelude::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<Tuple>()) {
+            dbg!(&v);
+            let expr: Expr = v.into();
+            prop_assert_eq![sigma_serialize_roundtrip(&expr), expr];
+        }
+
+    }
+}
diff --git a/ergotree-ir/src/serialization/expr.rs b/ergotree-ir/src/serialization/expr.rs
index 6e63b8e48..1b4c79fba 100644
--- a/ergotree-ir/src/serialization/expr.rs
+++ b/ergotree-ir/src/serialization/expr.rs
@@ -40,6 +40,7 @@ use crate::mir::or::Or;
 use crate::mir::property_call::PropertyCall;
 use crate::mir::select_field::SelectField;
 use crate::mir::sigma_prop_bytes::SigmaPropBytes;
+use crate::mir::tuple::Tuple;
 use crate::mir::upcast::Upcast;
 use crate::mir::val_def::ValDef;
 use crate::mir::val_use::ValUse;
@@ -103,6 +104,7 @@ impl SigmaSerializable for Expr {
                     Expr::OptionGetOrElse(op) => op.sigma_serialize(w),
                     Expr::Negation(op) => op.sigma_serialize(w),
                     Expr::ForAll(op) => op.sigma_serialize(w),
+                    Expr::Tuple(op) => op.sigma_serialize(w),
                 }
             }
         }
@@ -189,6 +191,7 @@ impl SigmaSerializable for Expr {
                 SizeOf::OP_CODE => Ok(SizeOf::sigma_parse(r)?.into()),
                 CreateProveDlog::OP_CODE => Ok(CreateProveDlog::sigma_parse(r)?.into()),
                 SigmaPropBytes::OP_CODE => Ok(SigmaPropBytes::sigma_parse(r)?.into()),
+                Tuple::OP_CODE => Ok(Tuple::sigma_parse(r)?.into()),
                 o => Err(SerializationError::NotImplementedOpCode(format!(
                     "{0}(shift {1})",
                     o.value(),
diff --git a/ergotree-ir/src/serialization/serializable.rs b/ergotree-ir/src/serialization/serializable.rs
index 5df5cfa3b..2c85dcc70 100644
--- a/ergotree-ir/src/serialization/serializable.rs
+++ b/ergotree-ir/src/serialization/serializable.rs
@@ -1,6 +1,7 @@
 //! Serialization of Ergo types
 use crate::mir::expr::InvalidArgumentError;
 use crate::mir::val_def::ValId;
+use crate::types::stuple::STupleItemsOutOfBoundsError;
 
 use super::{
     constant_store::ConstantStore,
@@ -77,6 +78,12 @@ impl From<InvalidArgumentError> for SerializationError {
     }
 }
 
+impl From<STupleItemsOutOfBoundsError> for SerializationError {
+    fn from(e: STupleItemsOutOfBoundsError) -> Self {
+        SerializationError::ValueOutOfBounds(format!("{:?}", e))
+    }
+}
+
 /// Consensus-critical serialization for Ergo
 pub trait SigmaSerializable: Sized {
     /// Write `self` to the given `writer`.
diff --git a/ergotree-ir/src/serialization/types.rs b/ergotree-ir/src/serialization/types.rs
index 292309e1f..18922203f 100644
--- a/ergotree-ir/src/serialization/types.rs
+++ b/ergotree-ir/src/serialization/types.rs
@@ -6,6 +6,7 @@ use crate::serialization::{
 };
 use crate::types::stuple::STuple;
 use crate::types::stype::SType;
+use crate::types::stype_param::STypeVar;
 use sigma_ser::vlq_encode;
 use std::convert::TryInto;
 use std::{io, ops::Add};
@@ -33,7 +34,10 @@ impl TypeCode {
 
     pub const SANY: TypeCode = Self::new(97);
     pub const SBOX: TypeCode = Self::new(99);
+    pub const SAVL_TREE: TypeCode = Self::new(100);
     pub const SCONTEXT: TypeCode = Self::new(101);
+    pub const SSTRING: TypeCode = Self::new(102);
+    pub const STYPE_VAR: TypeCode = Self::new(103);
 
     const COLLECTION_CONSTR_ID: u8 = 1;
     pub const COLLECTION: TypeCode =
@@ -142,8 +146,8 @@ impl SigmaSerializable for SType {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala#L25-L25
         match self {
+            SType::SFunc(_) => todo!(),
             SType::SAny => self.type_code().sigma_serialize(w),
-
             SType::SBoolean => self.type_code().sigma_serialize(w),
             SType::SByte => self.type_code().sigma_serialize(w),
             SType::SShort => self.type_code().sigma_serialize(w),
@@ -152,9 +156,9 @@ impl SigmaSerializable for SType {
             SType::SBigInt => self.type_code().sigma_serialize(w),
             SType::SGroupElement => self.type_code().sigma_serialize(w),
             SType::SSigmaProp => self.type_code().sigma_serialize(w),
-
-            SType::SBox => todo!(),
-            SType::SAvlTree => todo!(),
+            SType::SBox => self.type_code().sigma_serialize(w),
+            SType::SAvlTree => self.type_code().sigma_serialize(w),
+            SType::SContext => self.type_code().sigma_serialize(w),
             SType::SOption(elem_type) if is_stype_embeddable(elem_type) => {
                 let code = TypeCode::OPTION + elem_type.type_code();
                 code.sigma_serialize(w)
@@ -226,9 +230,10 @@ impl SigmaSerializable for SType {
                     }
                 },
             },
-            SType::SFunc(_) => todo!(),
-            SType::SContext => todo!(),
-            SType::STypeVar(_) => todo!(),
+            SType::STypeVar(tv) => {
+                TypeCode::STYPE_VAR.sigma_serialize(w)?;
+                tv.sigma_serialize(w)
+            }
         }
     }
 
@@ -304,9 +309,10 @@ impl SigmaSerializable for SType {
                     }
                 }
                 _ => {
-                    return Err(SerializationError::NotImplementedYet(
-                        "parsing type is not yet implemented".to_string(),
-                    ))
+                    return Err(SerializationError::NotImplementedYet(format!(
+                        "case 1: parsing type is not yet implemented(constr_id == {:?})",
+                        constr_id
+                    )))
                 }
             }
         } else {
@@ -321,9 +327,15 @@ impl SigmaSerializable for SType {
                         SerializationError::TupleItemsOutOfBounds(len as usize)
                     })?))
                 }
-                _ => Err(SerializationError::NotImplementedYet(
-                    "parsing type is not yet implemented".to_string(),
-                )),
+                TypeCode::SANY => Ok(SType::SAny),
+                TypeCode::SBOX => Ok(SType::SBox),
+                TypeCode::SAVL_TREE => Ok(SType::SAvlTree),
+                TypeCode::SCONTEXT => Ok(SType::SContext),
+                TypeCode::STYPE_VAR => Ok(SType::STypeVar(STypeVar::sigma_parse(r)?)),
+                _ => Err(SerializationError::NotImplementedYet(format!(
+                    "case 2: parsing type is not yet implemented(c == {:?})",
+                    c
+                ))),
             }?
         };
         Ok(tpe)
@@ -349,7 +361,7 @@ mod tests {
 
         #[test]
         fn ser_roundtrip(v in any::<SType>()) {
-            dbg!(v.clone());
+            dbg!(&v);
             prop_assert_eq![sigma_serialize_roundtrip(&v), v];
         }
     }
diff --git a/ergotree-ir/src/types/sbox.rs b/ergotree-ir/src/types/sbox.rs
index 6b4d2efc4..d21318511 100644
--- a/ergotree-ir/src/types/sbox.rs
+++ b/ergotree-ir/src/types/sbox.rs
@@ -57,7 +57,7 @@ lazy_static! {
         name: "getReg",
         tpe: SFunc {
             t_dom: vec![SType::SBox, SType::SByte],
-            t_range: Box::new(SType::SOption(Box::new(SType::STypeVar(STypeVar::T)))),
+            t_range: Box::new(SType::SOption(Box::new(SType::STypeVar(STypeVar::t())))),
             tpe_params: vec![],
         },
     };
diff --git a/ergotree-ir/src/types/scoll.rs b/ergotree-ir/src/types/scoll.rs
index 6fecd9e88..13dff2046 100644
--- a/ergotree-ir/src/types/scoll.rs
+++ b/ergotree-ir/src/types/scoll.rs
@@ -38,7 +38,7 @@ lazy_static! {
         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_dom: vec![SType::SColl(SType::STypeVar(STypeVar::t()).into()), SType::STypeVar(STypeVar::t()), SType::SInt],
             t_range: SType::SInt.into(),
             tpe_params: vec![],
         },
@@ -52,11 +52,11 @@ lazy_static! {
         method_id: FLATMAP_METHOD_ID,
         name: "flatMap",
         tpe: SFunc {
-            t_dom: vec![SType::SColl(SType::STypeVar(STypeVar::IV).into()),
+            t_dom: vec![SType::SColl(SType::STypeVar(STypeVar::iv()).into()),
             SType::SFunc(
-                SFunc::new(vec![SType::STypeVar(STypeVar::IV)], SType::STypeVar(STypeVar::OV), vec![] ))
+                SFunc::new(vec![SType::STypeVar(STypeVar::iv())], SType::STypeVar(STypeVar::ov()), vec![] ))
             ],
-            t_range: SType::SColl(SType::STypeVar(STypeVar::OV).into()).into(),
+            t_range: SType::SColl(SType::STypeVar(STypeVar::ov()).into()).into(),
             tpe_params: vec![],
         },
     };
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index 1f78f2500..e7acc68cb 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -62,7 +62,8 @@ impl SType {
     /// Type code used in serialization of SType values.
     pub fn type_code(&self) -> TypeCode {
         match self {
-            SType::SAny => todo!(),
+            SType::SFunc(_) => todo!(),
+            SType::SAny => TypeCode::SANY,
             SType::SBoolean => TypeCode::SBOOLEAN,
             SType::SByte => TypeCode::SBYTE,
             SType::SShort => TypeCode::SSHORT,
@@ -71,14 +72,13 @@ impl SType {
             SType::SBigInt => TypeCode::SBIGINT,
             SType::SGroupElement => TypeCode::SGROUP_ELEMENT,
             SType::SSigmaProp => TypeCode::SSIGMAPROP,
-            SType::SBox => todo!(),
-            SType::SAvlTree => todo!(),
+            SType::SBox => TypeCode::SBOX,
+            SType::SAvlTree => TypeCode::SAVL_TREE,
             SType::SOption(_) => TypeCode::OPTION,
             SType::SColl(_) => TypeCode::COLLECTION,
             SType::STuple(_) => TypeCode::TUPLE,
-            SType::SFunc(_) => todo!(),
-            SType::SContext => todo!(),
-            SType::STypeVar(_) => todo!(),
+            SType::SContext => TypeCode::SCONTEXT,
+            SType::STypeVar(_) => TypeCode::STYPE_VAR,
         }
     }
 
@@ -219,6 +219,7 @@ mod tests {
 
     fn primitive_type() -> BoxedStrategy<SType> {
         prop_oneof![
+            Just(SType::SAny),
             Just(SType::SBoolean),
             Just(SType::SByte),
             Just(SType::SShort),
@@ -227,6 +228,10 @@ mod tests {
             Just(SType::SBigInt),
             Just(SType::SGroupElement),
             Just(SType::SSigmaProp),
+            Just(SType::SBox),
+            Just(SType::SAvlTree),
+            Just(SType::SContext),
+            Just(SType::STypeVar(STypeVar::t())),
         ]
         .boxed()
     }
diff --git a/ergotree-ir/src/types/stype_param.rs b/ergotree-ir/src/types/stype_param.rs
index a6a1f1f27..143bbfc9d 100644
--- a/ergotree-ir/src/types/stype_param.rs
+++ b/ergotree-ir/src/types/stype_param.rs
@@ -1,19 +1,63 @@
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+
 use super::stype::SType;
 
 /// Type variable for generic signatures
 #[derive(PartialEq, Eq, Debug, Clone, Hash)]
 pub struct STypeVar {
     /// Type variable name (e.g. "T")
-    pub name: &'static str,
+    pub name: String,
 }
 
 impl STypeVar {
     /// "T" type variable
-    pub const T: STypeVar = STypeVar { name: "T" };
+    pub fn t() -> Self {
+        STypeVar {
+            name: "T".to_string(),
+        }
+    }
+
     /// "IV"(Input Value) type variable
-    pub const IV: STypeVar = STypeVar { name: "IV" };
+    pub fn iv() -> STypeVar {
+        STypeVar {
+            name: "IV".to_string(),
+        }
+    }
     /// "OV"(Input Value) type variable
-    pub const OV: STypeVar = STypeVar { name: "OV" };
+    pub fn ov() -> STypeVar {
+        STypeVar {
+            name: "OV".to_string(),
+        }
+    }
+}
+
+impl SigmaSerializable for STypeVar {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        let bytes = self.name.as_bytes();
+        assert!(
+            bytes.len() < u8::MAX as usize,
+            "STypeVar::name exceeds 255 bytes"
+        );
+        w.put_u8(bytes.len() as u8)?;
+        w.write_all(bytes)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let name_len = r.get_u8()?;
+        let mut bytes = vec![0; name_len as usize];
+        r.read_exact(&mut bytes)?;
+        Ok(STypeVar {
+            name: String::from_utf8(bytes).map_err(|err| {
+                SerializationError::ValueOutOfBounds(format!(
+                    "cannot parse UTF-8 STypeVar::name from bytes with error: {:?}",
+                    err
+                ))
+            })?,
+        })
+    }
 }
 
 /// Type parameter


Test patch: