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

Issue:
Consider making `TypeCode` an enum
### Motivation
Enjoy Rustc exhaust check on matching.


Discussion / hints:

Fix patch:
diff --git a/ergotree-ir/src/serialization/serializable.rs b/ergotree-ir/src/serialization/serializable.rs
index 51e70a306..a0c28df72 100644
--- a/ergotree-ir/src/serialization/serializable.rs
+++ b/ergotree-ir/src/serialization/serializable.rs
@@ -8,7 +8,6 @@ use super::{
     sigma_byte_reader::{SigmaByteRead, SigmaByteReader},
     sigma_byte_writer::{SigmaByteWrite, SigmaByteWriter},
 };
-use crate::serialization::types::TypeCode;
 use crate::types::smethod::MethodId;
 use bounded_vec::BoundedVec;
 use bounded_vec::BoundedVecOutOfBounds;
@@ -58,7 +57,7 @@ pub enum SigmaParsingError {
     NotImplementedOpCode(String),
     /// Failed to parse type
     #[error("type parsing error")]
-    InvalidTypePrefix,
+    InvalidTypeCode(u8),
     /// Failed to decode VLQ
     #[error("vlq encode error: {0}")]
     VlqEncode(#[from] vlq_encode::VlqEncodingError),
@@ -88,7 +87,7 @@ pub enum SigmaParsingError {
     InvalidArgument(#[from] InvalidArgumentError),
     /// Unknown method ID for given type code
     #[error("No method id {0:?} found in type companion with type id {1:?} ")]
-    UnknownMethodId(MethodId, TypeCode),
+    UnknownMethodId(MethodId, u8),
     /// Feature not supported
     #[error("parsing not supported: {0}")]
     NotSupported(&'static str),
diff --git a/ergotree-ir/src/serialization/types.rs b/ergotree-ir/src/serialization/types.rs
index 74c267772..2b79f34f1 100644
--- a/ergotree-ir/src/serialization/types.rs
+++ b/ergotree-ir/src/serialization/types.rs
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
 use super::op_code::OpCode;
 use super::sigma_byte_writer::SigmaByteWrite;
 use super::SigmaSerializationError;
@@ -7,99 +5,156 @@ use crate::serialization::SigmaSerializeResult;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SigmaParsingError, SigmaSerializable,
 };
-use crate::types::stuple::STuple;
+use crate::types::stuple;
 use crate::types::stype::SType;
-use crate::types::stype_param::STypeVar;
+use crate::types::stype_param;
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
 use std::convert::TryInto;
-use std::ops::Add;
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct TypeCode(u8);
+#[allow(non_camel_case_types)]
+#[allow(clippy::upper_case_acronyms)] // to differentiate from similarly named SType enum variants
+#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive)]
+#[repr(u8)]
+pub enum TypeCode {
+    SBOOLEAN = 1,
+    SBYTE = 2,
+    SSHORT = 3,
+    SINT = 4,
+    SLONG = 5,
+    SBIGINT = 6,
+    SGROUP_ELEMENT = 7,
+    SSIGMAPROP = 8,
+
+    COLL = (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::COLLECTION_CONSTR_ID, // 12 * 1
+    COLL_BOOL = TypeCode::COLL as u8 + TypeCode::SBOOLEAN as u8,               // 13
+    COLL_BYTE = TypeCode::COLL as u8 + TypeCode::SBYTE as u8,                  // 14
+    COLL_SHORT = TypeCode::COLL as u8 + TypeCode::SSHORT as u8,                // 15
+    COLL_INT = TypeCode::COLL as u8 + TypeCode::SINT as u8,                    // 16
+    COLL_LONG = TypeCode::COLL as u8 + TypeCode::SLONG as u8,                  // 17
+    COLL_BIGINT = TypeCode::COLL as u8 + TypeCode::SBIGINT as u8,              // 18
+    COLL_GROUP_ELEMENT = TypeCode::COLL as u8 + TypeCode::SGROUP_ELEMENT as u8, // 19
+    COLL_SIGMAPROP = TypeCode::COLL as u8 + TypeCode::SSIGMAPROP as u8,        // 20
+
+    NESTED_COLL_BOOL = TypeCode::NESTED_COLL as u8 + TypeCode::SBOOLEAN as u8, // 25
+    NESTED_COLL_BYTE = TypeCode::NESTED_COLL as u8 + TypeCode::SBYTE as u8,    // 26
+    NESTED_COLL_SHORT = TypeCode::NESTED_COLL as u8 + TypeCode::SSHORT as u8,  // 27
+    NESTED_COLL_INT = TypeCode::NESTED_COLL as u8 + TypeCode::SINT as u8,      // 28
+    NESTED_COLL_LONG = TypeCode::NESTED_COLL as u8 + TypeCode::SLONG as u8,    // 29
+    NESTED_COLL_BIGINT = TypeCode::NESTED_COLL as u8 + TypeCode::SBIGINT as u8, // 30
+    NESTED_COLL_GROUP_ELEMENT = TypeCode::NESTED_COLL as u8 + TypeCode::SGROUP_ELEMENT as u8, // 31
+    NESTED_COLL_SIGMAPROP = TypeCode::NESTED_COLL as u8 + TypeCode::SSIGMAPROP as u8, // 32
+
+    OPTION = (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_CONSTR_ID, // 12 * 3 = 36
+    OPTION_BOOL = TypeCode::OPTION as u8 + TypeCode::SBOOLEAN as u8,         // 37
+    OPTION_BYTE = TypeCode::OPTION as u8 + TypeCode::SBYTE as u8,            // 38
+    OPTION_SHORT = TypeCode::OPTION as u8 + TypeCode::SSHORT as u8,          // 39
+    OPTION_INT = TypeCode::OPTION as u8 + TypeCode::SINT as u8,              // 40
+    OPTION_LONG = TypeCode::OPTION as u8 + TypeCode::SLONG as u8,            // 41
+    OPTION_BIGINT = TypeCode::OPTION as u8 + TypeCode::SBIGINT as u8,        // 42
+    OPTION_GROUP_ELEMENT = TypeCode::OPTION as u8 + TypeCode::SGROUP_ELEMENT as u8, // 43
+    OPTION_SIGMAPROP = TypeCode::OPTION as u8 + TypeCode::SSIGMAPROP as u8,  // 44
+
+    OPTION_COLL_BOOL = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SBOOLEAN as u8, // 49
+    OPTION_COLL_BYTE = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SBYTE as u8,    // 50
+    OPTION_COLL_SHORT = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SSHORT as u8,  // 51
+    OPTION_COLL_INT = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SINT as u8,      // 52
+    OPTION_COLL_LONG = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SLONG as u8,    // 53
+    OPTION_COLL_BIGINT = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SBIGINT as u8, // 54
+    OPTION_COLL_GROUP_ELEMENT = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SGROUP_ELEMENT as u8, // 55
+    OPTION_COLL_SIGMAPROP = TypeCode::OPTION_COLLECTION as u8 + TypeCode::SSIGMAPROP as u8, // 56
+
+    TUPLE_PAIR1 = (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR1_CONSTR_ID, // 12 * 5 = 60
+    TUPLE_PAIR1_BOOL = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SBOOLEAN as u8,         // 61
+    TUPLE_PAIR1_BYTE = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SBYTE as u8,            // 62
+    TUPLE_PAIR1_SHORT = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SSHORT as u8,          // 63
+    TUPLE_PAIR1_INT = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SINT as u8,              // 64
+    TUPLE_PAIR1_LONG = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SLONG as u8,            // 65
+    TUPLE_PAIR1_BIGINT = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SBIGINT as u8,        // 66
+    TUPLE_PAIR1_GROUP_ELEMENT = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SGROUP_ELEMENT as u8, // 67
+    TUPLE_PAIR1_SIGMAPROP = TypeCode::TUPLE_PAIR1 as u8 + TypeCode::SSIGMAPROP as u8,  // 68
+
+    TUPLE_TRIPLE = Self::TUPLE_PAIR2, // 72
+
+    TUPLE_PAIR2_BOOL = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SBOOLEAN as u8, // 73
+    TUPLE_PAIR2_BYTE = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SBYTE as u8,    // 74
+    TUPLE_PAIR2_SHORT = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SSHORT as u8,  // 75
+    TUPLE_PAIR2_INT = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SINT as u8,      // 76
+    TUPLE_PAIR2_LONG = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SLONG as u8,    // 77
+    TUPLE_PAIR2_BIGINT = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SBIGINT as u8, // 78
+    TUPLE_PAIR2_GROUP_ELEMENT = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SGROUP_ELEMENT as u8, // 79
+    TUPLE_PAIR2_SIGMAPROP = TypeCode::TUPLE_PAIR2 as u8 + TypeCode::SSIGMAPROP as u8, // 80
+
+    TUPLE_QUADRUPLE = Self::TUPLE_PAIR_SYMMETRIC, // 84
+
+    TUPLE_PAIR_SYMMETRIC_BOOL = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SBOOLEAN as u8, // 85
+    TUPLE_PAIR_SYMMETRIC_BYTE = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SBYTE as u8, // 86
+    TUPLE_PAIR_SYMMETRIC_SHORT = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SSHORT as u8, // 87
+    TUPLE_PAIR_SYMMETRIC_INT = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SINT as u8, // 88
+    TUPLE_PAIR_SYMMETRIC_LONG = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SLONG as u8, // 89
+    TUPLE_PAIR_SYMMETRIC_BIGINT = TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SBIGINT as u8, // 90
+    TUPLE_PAIR_SYMMETRIC_GROUP_ELEMENT =
+        TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SGROUP_ELEMENT as u8, // 91
+    TUPLE_PAIR_SYMMETRIC_SIGMAPROP =
+        TypeCode::TUPLE_PAIR_SYMMETRIC as u8 + TypeCode::SSIGMAPROP as u8, // 92
+
+    TUPLE = (TypeCode::MAX_PRIM_TYPECODE + 1) * 8, // 12 * 8 = 96
+
+    SANY = 97,
+    SBOX = 99,
+    SAVL_TREE = 100,
+    SCONTEXT = 101,
+    // SSTRING = 102,
+    STYPE_VAR = 103,
+    SHEADER = 104,
+    SPRE_HEADER = 105,
+    SGLOBAL = 106,
+}
 
 impl TypeCode {
     /// SFunc types occupy remaining space of byte values [FirstFuncType .. 255]
-    pub const FIRST_FUNC_TYPE: TypeCode = Self::new(OpCode::LAST_DATA_TYPE.value());
-    pub const LAST_FUNC_TYPE: TypeCode = Self::new(255);
+    #[allow(dead_code)]
+    const FIRST_FUNC_TYPE: u8 = OpCode::LAST_DATA_TYPE.value();
+    #[allow(dead_code)]
+    const LAST_FUNC_TYPE: u8 = 255;
 
     /// Type code of the last valid prim type so that (1 to LastPrimTypeCode) is a range of valid codes.
-    pub const LAST_PRIM_TYPECODE: u8 = 8;
+    #[allow(dead_code)]
+    const LAST_PRIM_TYPECODE: u8 = 8;
 
     /// Upper limit of the interval of valid type codes for primitive types
-    pub const MAX_PRIM_TYPECODE: u8 = 11;
-    pub const PRIM_RANGE: u8 = (TypeCode::MAX_PRIM_TYPECODE + 1);
-
-    pub const SBOOLEAN: TypeCode = Self::new(1);
-    pub const SBYTE: TypeCode = Self::new(2);
-    pub const SSHORT: TypeCode = Self::new(3);
-    pub const SINT: TypeCode = Self::new(4);
-    pub const SLONG: TypeCode = Self::new(5);
-    pub const SBIGINT: TypeCode = Self::new(6);
-    pub const SGROUP_ELEMENT: TypeCode = Self::new(7);
-    pub const SSIGMAPROP: TypeCode = Self::new(8);
-
-    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);
-    pub const SHEADER: TypeCode = Self::new(104);
-    pub const SPRE_HEADER: TypeCode = Self::new(105);
-    pub const SGLOBAL: TypeCode = Self::new(106);
+    const MAX_PRIM_TYPECODE: u8 = 11;
 
     const COLLECTION_CONSTR_ID: u8 = 1;
-    pub const COLLECTION: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::COLLECTION_CONSTR_ID);
+
     const NESTED_COLLECTION_CONSTS_ID: u8 = 2;
-    pub const NESTED_COLLECTION: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::NESTED_COLLECTION_CONSTS_ID);
+    const NESTED_COLL: u8 =
+        (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::NESTED_COLLECTION_CONSTS_ID; // 12 * 2 = 24
+
+    const OPTION_CONSTR_ID: u8 = 3;
+    const OPTION_COLLECTION_TYPE_CONSTR_ID: u8 = 4;
+    const OPTION_COLLECTION: u8 =
+        (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_COLLECTION_TYPE_CONSTR_ID; // 12 * 4 = 48
 
     const TUPLE_PAIR1_CONSTR_ID: u8 = 5;
-    pub const TUPLE_PAIR1: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR1_CONSTR_ID);
 
     const TUPLE_PAIR2_CONSTR_ID: u8 = 6;
-    pub const TUPLE_PAIR2: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR2_CONSTR_ID);
-
-    pub const TUPLE_TRIPLE: TypeCode = Self::TUPLE_PAIR2;
+    const TUPLE_PAIR2: u8 = (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR2_CONSTR_ID; // 12 * 6 = 72
 
     const TUPLE_PAIR_SYMMETRIC_TYPE_CONSTR_ID: u8 = 7;
-    pub const TUPLE_PAIR_SYMMETRIC: TypeCode = Self::new(
-        (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR_SYMMETRIC_TYPE_CONSTR_ID,
-    );
-
-    pub const TUPLE_QUADRUPLE: TypeCode = Self::TUPLE_PAIR_SYMMETRIC;
-
-    pub const TUPLE: TypeCode = Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * 8);
-
-    const OPTION_CONSTR_ID: u8 = 3;
-    const OPTION_COLLECTION_TYPE_CONSTR_ID: u8 = 4;
-    pub const OPTION: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_CONSTR_ID);
-    pub const OPTION_COLLECTION: TypeCode =
-        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::OPTION_COLLECTION_TYPE_CONSTR_ID);
-
-    const fn new(c: u8) -> TypeCode {
-        TypeCode(c)
-    }
+    const TUPLE_PAIR_SYMMETRIC: u8 =
+        (TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::TUPLE_PAIR_SYMMETRIC_TYPE_CONSTR_ID; // 12 * 7 = 84
 
-    /// Parse type code from single byte.
-    pub fn parse(b: u8) -> Result<Self, SigmaParsingError> {
-        match b {
-            0 => Err(SigmaParsingError::InvalidTypePrefix),
-            _ => Ok(Self(b)),
+    /// Parse type code from byte
+    pub(crate) fn parse(b: u8) -> Result<Self, SigmaParsingError> {
+        match FromPrimitive::from_u8(b) {
+            Some(t) => Ok(t),
+            None => Err(SigmaParsingError::InvalidTypeCode(b)),
         }
     }
-    pub const fn value(&self) -> u8 {
-        self.0
-    }
-}
 
-impl Add for TypeCode {
-    type Output = TypeCode;
-    fn add(self, rhs: Self) -> TypeCode {
-        TypeCode::new(self.0 + rhs.0)
+    pub(crate) const fn value(&self) -> u8 {
+        *self as u8
     }
 }
 
@@ -115,251 +170,373 @@ impl SigmaSerializable for TypeCode {
     }
 }
 
-fn get_embeddable_type(code: u8) -> Result<SType, SigmaParsingError> {
-    match TypeCode::new(code) {
-        TypeCode::SBOOLEAN => Ok(SType::SBoolean),
-        TypeCode::SBYTE => Ok(SType::SByte),
-        TypeCode::SSHORT => Ok(SType::SShort),
-        TypeCode::SINT => Ok(SType::SInt),
-        TypeCode::SLONG => Ok(SType::SLong),
-        TypeCode::SBIGINT => Ok(SType::SBigInt),
-        TypeCode::SGROUP_ELEMENT => Ok(SType::SGroupElement),
-        TypeCode::SSIGMAPROP => Ok(SType::SSigmaProp),
-        _ => Err(SigmaParsingError::InvalidOpCode(code)),
-    }
-}
-
-#[allow(clippy::match_like_matches_macro)]
-fn is_stype_embeddable(tpe: &SType) -> bool {
-    match tpe {
-        SType::SBoolean => true,
-        SType::SByte => true,
-        SType::SShort => true,
-        SType::SInt => true,
-        SType::SLong => true,
-        SType::SBigInt => true,
-        SType::SGroupElement => true,
-        SType::SSigmaProp => true,
-        _ => false,
-    }
-}
-
 impl SType {
     /// Parse type from byte stream. This function should be used instead of
     /// `sigma_parse` when type code is already read for look-ahead
-    pub fn parse_with_type_code<R: SigmaByteRead>(
+    pub(crate) fn parse_with_type_code<R: SigmaByteRead>(
         r: &mut R,
         c: TypeCode,
     ) -> Result<Self, SigmaParsingError> {
-        let tpe = if c.value() < TypeCode::TUPLE.value() {
-            let constr_id = c.value() / TypeCode::PRIM_RANGE;
-            let prim_id = c.value() % TypeCode::PRIM_RANGE;
-            match constr_id {
-                // primitive
-                0 => get_embeddable_type(c.value())?,
-                // Coll[_]
-                1 => {
-                    let t_elem = get_arg_type(r, prim_id)?;
-                    SType::SColl(Box::new(t_elem))
-                }
-                // Coll[Coll[_]]
-                2 => {
-                    let t_elem = get_arg_type(r, prim_id)?;
-                    SType::SColl(Box::new(SType::SColl(Box::new(t_elem))))
-                }
-                // Option[_]
-                3 => {
-                    let t_elem = get_arg_type(r, prim_id)?;
-                    SType::SOption(Box::new(t_elem))
-                }
-                // Option[Coll[_]]
-                4 => {
-                    let t_elem = get_arg_type(r, prim_id)?;
-                    SType::SOption(SType::SColl(t_elem.into()).into())
-                }
-                TypeCode::TUPLE_PAIR1_CONSTR_ID => {
-                    // (_, t2)
-                    let (t1, t2) = if prim_id == 0 {
-                        // Pair of non-primitive types (`((Int, Byte), (Boolean,Box))`, etc.)
-                        (Self::sigma_parse(r)?, Self::sigma_parse(r)?)
-                    } else {
-                        // Pair of types where first is primitive (`(_, Int)`)
-                        (get_embeddable_type(prim_id)?, Self::sigma_parse(r)?)
-                    };
-                    STuple::pair(t1, t2).into()
-                }
-                TypeCode::TUPLE_PAIR2_CONSTR_ID => {
-                    // (t1, _)
-                    if prim_id == 0 {
-                        // Triple of types
-                        let t1 = Self::sigma_parse(r)?;
-                        let t2 = Self::sigma_parse(r)?;
-                        let t3 = Self::sigma_parse(r)?;
-                        #[allow(clippy::unwrap_used)]
-                        SType::STuple(vec![t1, t2, t3].try_into().unwrap())
-                    } else {
-                        // Pair of types where second is primitive (`(Int, _)`)
-                        let t2 = get_embeddable_type(prim_id)?;
-                        let t1 = Self::sigma_parse(r)?;
-                        STuple::pair(t1, t2).into()
-                    }
-                }
-                TypeCode::TUPLE_PAIR_SYMMETRIC_TYPE_CONSTR_ID => {
-                    // (_, _)
-                    if prim_id == 0 {
-                        // Quadriple of types
-                        let t1 = Self::sigma_parse(r)?;
-                        let t2 = Self::sigma_parse(r)?;
-                        let t3 = Self::sigma_parse(r)?;
-                        let t4 = Self::sigma_parse(r)?;
-                        #[allow(clippy::unwrap_used)]
-                        SType::STuple(vec![t1, t2, t3, t4].try_into().unwrap())
-                    } else {
-                        // Symmetric pair of primitive types (`(Int, Int)`, `(Byte,Byte)`, etc.)
-                        let t = get_embeddable_type(prim_id)?;
-                        STuple::pair(t.clone(), t).into()
-                    }
-                }
-                _ => {
-                    return Err(SigmaParsingError::NotImplementedYet(format!(
-                        "case 1: parsing type is not yet implemented(constr_id == {:?})",
-                        constr_id
-                    )))
-                }
+        use SType::*;
+        Ok(match c {
+            TypeCode::SBOOLEAN => SBoolean,
+            TypeCode::SBYTE => SByte,
+            TypeCode::SSHORT => SShort,
+            TypeCode::SINT => SInt,
+            TypeCode::SLONG => SLong,
+            TypeCode::SBIGINT => SBigInt,
+            TypeCode::SGROUP_ELEMENT => SGroupElement,
+            TypeCode::SSIGMAPROP => SSigmaProp,
+
+            TypeCode::COLL => SColl(SType::sigma_parse(r)?.into()),
+            TypeCode::COLL_BOOL => SColl(SBoolean.into()),
+            TypeCode::COLL_BYTE => SColl(SByte.into()),
+            TypeCode::COLL_SHORT => SColl(SShort.into()),
+            TypeCode::COLL_INT => SColl(SInt.into()),
+            TypeCode::COLL_LONG => SColl(SLong.into()),
+            TypeCode::COLL_BIGINT => SColl(SBigInt.into()),
+            TypeCode::COLL_GROUP_ELEMENT => SColl(SGroupElement.into()),
+            TypeCode::COLL_SIGMAPROP => SColl(SSigmaProp.into()),
+
+            TypeCode::NESTED_COLL_BOOL => SColl(SColl(SBoolean.into()).into()),
+            TypeCode::NESTED_COLL_BYTE => SColl(SColl(SByte.into()).into()),
+            TypeCode::NESTED_COLL_SHORT => SColl(SColl(SShort.into()).into()),
+            TypeCode::NESTED_COLL_INT => SColl(SColl(SInt.into()).into()),
+            TypeCode::NESTED_COLL_LONG => SColl(SColl(SLong.into()).into()),
+            TypeCode::NESTED_COLL_BIGINT => SColl(SColl(SBigInt.into()).into()),
+            TypeCode::NESTED_COLL_GROUP_ELEMENT => SColl(SColl(SGroupElement.into()).into()),
+            TypeCode::NESTED_COLL_SIGMAPROP => SColl(SColl(SSigmaProp.into()).into()),
+
+            TypeCode::OPTION => SOption(SType::sigma_parse(r)?.into()),
+            TypeCode::OPTION_BOOL => SOption(SBoolean.into()),
+            TypeCode::OPTION_BYTE => SOption(SByte.into()),
+            TypeCode::OPTION_SHORT => SOption(SShort.into()),
+            TypeCode::OPTION_INT => SOption(SInt.into()),
+            TypeCode::OPTION_LONG => SOption(SLong.into()),
+            TypeCode::OPTION_BIGINT => SOption(SBigInt.into()),
+            TypeCode::OPTION_GROUP_ELEMENT => SOption(SGroupElement.into()),
+            TypeCode::OPTION_SIGMAPROP => SOption(SSigmaProp.into()),
+
+            TypeCode::OPTION_COLL_BOOL => SOption(SColl(SBoolean.into()).into()),
+            TypeCode::OPTION_COLL_BYTE => SOption(SColl(SByte.into()).into()),
+            TypeCode::OPTION_COLL_SHORT => SOption(SColl(SShort.into()).into()),
+            TypeCode::OPTION_COLL_INT => SOption(SColl(SInt.into()).into()),
+            TypeCode::OPTION_COLL_LONG => SOption(SColl(SLong.into()).into()),
+            TypeCode::OPTION_COLL_BIGINT => SOption(SColl(SBigInt.into()).into()),
+            TypeCode::OPTION_COLL_GROUP_ELEMENT => SOption(SColl(SGroupElement.into()).into()),
+            TypeCode::OPTION_COLL_SIGMAPROP => SOption(SColl(SSigmaProp.into()).into()),
+
+            TypeCode::TUPLE_PAIR1 => STuple(stuple::STuple::pair(
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+            )),
+            TypeCode::TUPLE_PAIR1_BOOL => {
+                STuple(stuple::STuple::pair(SBoolean, SType::sigma_parse(r)?))
             }
-        } else {
-            match c {
-                TypeCode::TUPLE => {
-                    let len = r.get_u8()?;
-                    let mut items = Vec::with_capacity(len as usize);
-                    for _ in 0..len {
-                        items.push(SType::sigma_parse(r)?);
-                    }
-                    Ok(SType::STuple(items.try_into().map_err(|_| {
-                        SigmaParsingError::TupleItemsOutOfBounds(len as usize)
-                    })?))
+            TypeCode::TUPLE_PAIR1_BYTE => {
+                STuple(stuple::STuple::pair(SByte, SType::sigma_parse(r)?))
+            }
+            TypeCode::TUPLE_PAIR1_SHORT => {
+                STuple(stuple::STuple::pair(SShort, SType::sigma_parse(r)?))
+            }
+            TypeCode::TUPLE_PAIR1_INT => STuple(stuple::STuple::pair(SInt, SType::sigma_parse(r)?)),
+            TypeCode::TUPLE_PAIR1_LONG => {
+                STuple(stuple::STuple::pair(SLong, SType::sigma_parse(r)?))
+            }
+            TypeCode::TUPLE_PAIR1_BIGINT => {
+                STuple(stuple::STuple::pair(SBigInt, SType::sigma_parse(r)?))
+            }
+            TypeCode::TUPLE_PAIR1_GROUP_ELEMENT => {
+                STuple(stuple::STuple::pair(SGroupElement, SType::sigma_parse(r)?))
+            }
+            TypeCode::TUPLE_PAIR1_SIGMAPROP => {
+                STuple(stuple::STuple::pair(SSigmaProp, SType::sigma_parse(r)?))
+            }
+
+            TypeCode::TUPLE_TRIPLE => STuple(stuple::STuple::triple(
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+            )),
+
+            TypeCode::TUPLE_PAIR2_BOOL => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SBoolean))
+            }
+            TypeCode::TUPLE_PAIR2_BYTE => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SByte))
+            }
+            TypeCode::TUPLE_PAIR2_SHORT => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SShort))
+            }
+            TypeCode::TUPLE_PAIR2_INT => STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SInt)),
+            TypeCode::TUPLE_PAIR2_LONG => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SLong))
+            }
+            TypeCode::TUPLE_PAIR2_BIGINT => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SBigInt))
+            }
+            TypeCode::TUPLE_PAIR2_GROUP_ELEMENT => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SGroupElement))
+            }
+            TypeCode::TUPLE_PAIR2_SIGMAPROP => {
+                STuple(stuple::STuple::pair(SType::sigma_parse(r)?, SSigmaProp))
+            }
+
+            TypeCode::TUPLE_QUADRUPLE => STuple(stuple::STuple::quadruple(
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+                SType::sigma_parse(r)?,
+            )),
+
+            TypeCode::TUPLE_PAIR_SYMMETRIC_BOOL => STuple(stuple::STuple::pair(SBoolean, SBoolean)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_BYTE => STuple(stuple::STuple::pair(SByte, SByte)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_SHORT => STuple(stuple::STuple::pair(SShort, SShort)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_INT => STuple(stuple::STuple::pair(SInt, SInt)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_LONG => STuple(stuple::STuple::pair(SLong, SLong)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_BIGINT => STuple(stuple::STuple::pair(SBigInt, SBigInt)),
+            TypeCode::TUPLE_PAIR_SYMMETRIC_GROUP_ELEMENT => {
+                STuple(stuple::STuple::pair(SGroupElement, SGroupElement))
+            }
+            TypeCode::TUPLE_PAIR_SYMMETRIC_SIGMAPROP => {
+                STuple(stuple::STuple::pair(SSigmaProp, SSigmaProp))
+            }
+
+            TypeCode::TUPLE => {
+                let len = r.get_u8()?;
+                let mut items = Vec::with_capacity(len as usize);
+                for _ in 0..len {
+                    items.push(SType::sigma_parse(r)?);
                 }
-                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)?)),
-                TypeCode::SHEADER => Ok(SType::SHeader),
-                TypeCode::SPRE_HEADER => Ok(SType::SPreHeader),
-                TypeCode::SGLOBAL => Ok(SType::SGlobal),
-                _ => Err(SigmaParsingError::NotImplementedYet(format!(
-                    // FIXME: should we just tell that type code is malforled?
-                    "case 2: parsing type is not yet implemented(c == {:?})",
-                    c
-                ))),
-            }?
-        };
-        Ok(tpe)
+                SType::STuple(
+                    items
+                        .try_into()
+                        .map_err(|_| SigmaParsingError::TupleItemsOutOfBounds(len as usize))?,
+                )
+            }
+
+            TypeCode::SANY => SAny,
+            TypeCode::SBOX => SBox,
+            TypeCode::SAVL_TREE => SAvlTree,
+            TypeCode::SCONTEXT => SContext,
+            TypeCode::STYPE_VAR => STypeVar(stype_param::STypeVar::sigma_parse(r)?),
+            TypeCode::SHEADER => SHeader,
+            TypeCode::SPRE_HEADER => SPreHeader,
+            TypeCode::SGLOBAL => SGlobal,
+        })
     }
 }
 
-/**
- * Each SType is serialized to array of bytes by:
- * - emitting typeCode of each node (see special case for collections below)
- * - then recursively serializing subtrees from left to right on each level
- * - for each collection of primitive type there is special type code to emit single byte instead of two bytes
- * Types code intervals
- * - (1 .. MaxPrimTypeCode)  // primitive types
- * - (CollectionTypeCode .. CollectionTypeCode + MaxPrimTypeCode) // collections of primitive types
- * - (MaxCollectionTypeCode ..)  // Other types
- * Collection of non-primitive type is serialized as (CollectionTypeCode, serialize(elementType))
- */
+/// Each SType is serialized to array of bytes by:
+/// - emitting typeCode of each node (see special case for collections below)
+/// - then recursively serializing subtrees from left to right on each level
+/// - for each collection of primitive type there is special type code to emit single byte instead of two bytes
+/// Types code intervals
+/// - (1 .. MaxPrimTypeCode)  // primitive types
+/// - (CollectionTypeCode .. CollectionTypeCode + MaxPrimTypeCode) // collections of primitive types
+/// - (MaxCollectionTypeCode ..)  // Other types
+/// Collection of non-primitive type is serialized as (CollectionTypeCode, serialize(elementType))
 impl SigmaSerializable for SType {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala#L25-L25
+        use SType::*;
         match self {
             SType::SFunc(_) => Err(SigmaSerializationError::NotSupported("SFunc")),
-            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),
-            SType::SInt => self.type_code().sigma_serialize(w),
-            SType::SLong => self.type_code().sigma_serialize(w),
-            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 => self.type_code().sigma_serialize(w),
-            SType::SAvlTree => self.type_code().sigma_serialize(w),
-            SType::SContext => self.type_code().sigma_serialize(w),
-            SType::SHeader => self.type_code().sigma_serialize(w),
-            SType::SPreHeader => self.type_code().sigma_serialize(w),
-            SType::SGlobal => 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)
-            }
-            SType::SOption(elem_type) => match &**elem_type {
-                SType::SColl(elem_type) if is_stype_embeddable(elem_type.as_ref()) => {
-                    let code = TypeCode::OPTION_COLLECTION + elem_type.type_code();
-                    code.sigma_serialize(w)
-                }
-                _ => {
+            SType::SAny => TypeCode::SANY.sigma_serialize(w),
+            SType::SBoolean => TypeCode::SBOOLEAN.sigma_serialize(w),
+            SType::SByte => TypeCode::SBYTE.sigma_serialize(w),
+            SType::SShort => TypeCode::SSHORT.sigma_serialize(w),
+            SType::SInt => TypeCode::SINT.sigma_serialize(w),
+            SType::SLong => TypeCode::SLONG.sigma_serialize(w),
+            SType::SBigInt => TypeCode::SBIGINT.sigma_serialize(w),
+            SType::SGroupElement => TypeCode::SGROUP_ELEMENT.sigma_serialize(w),
+            SType::SSigmaProp => TypeCode::SSIGMAPROP.sigma_serialize(w),
+            SType::SBox => TypeCode::SBOX.sigma_serialize(w),
+            SType::SAvlTree => TypeCode::SAVL_TREE.sigma_serialize(w),
+            SType::SContext => TypeCode::SCONTEXT.sigma_serialize(w),
+            SType::SHeader => TypeCode::SHEADER.sigma_serialize(w),
+            SType::SPreHeader => TypeCode::SPRE_HEADER.sigma_serialize(w),
+            SType::SGlobal => TypeCode::SGLOBAL.sigma_serialize(w),
+            SOption(elem_type) => match &**elem_type {
+                SBoolean => TypeCode::OPTION_BOOL.sigma_serialize(w),
+                SByte => TypeCode::OPTION_BYTE.sigma_serialize(w),
+                SShort => TypeCode::OPTION_SHORT.sigma_serialize(w),
+                SInt => TypeCode::OPTION_INT.sigma_serialize(w),
+                SLong => TypeCode::OPTION_LONG.sigma_serialize(w),
+                SBigInt => TypeCode::OPTION_BIGINT.sigma_serialize(w),
+                SGroupElement => TypeCode::OPTION_GROUP_ELEMENT.sigma_serialize(w),
+                SSigmaProp => TypeCode::OPTION_SIGMAPROP.sigma_serialize(w),
+                SColl(inner_elem_type) => match &**inner_elem_type {
+                    SBoolean => TypeCode::OPTION_COLL_BOOL.sigma_serialize(w),
+                    SByte => TypeCode::OPTION_COLL_BYTE.sigma_serialize(w),
+                    SShort => TypeCode::OPTION_COLL_SHORT.sigma_serialize(w),
+                    SInt => TypeCode::OPTION_COLL_INT.sigma_serialize(w),
+                    SLong => TypeCode::OPTION_COLL_LONG.sigma_serialize(w),
+                    SBigInt => TypeCode::OPTION_COLL_BIGINT.sigma_serialize(w),
+                    SGroupElement => TypeCode::OPTION_COLL_GROUP_ELEMENT.sigma_serialize(w),
+                    SSigmaProp => TypeCode::OPTION_COLL_SIGMAPROP.sigma_serialize(w),
+                    STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | SColl(_) | STuple(_)
+                    | SFunc(_) | SContext | SHeader | SPreHeader | SGlobal => {
+                        // if not "embeddable" type fallback to generic Option type code following
+                        // elem type code
+                        TypeCode::OPTION.sigma_serialize(w)?;
+                        elem_type.sigma_serialize(w)
+                    }
+                },
+                STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | STuple(_) | SFunc(_)
+                | SContext | SHeader | SPreHeader | SGlobal => {
+                    // if not "embeddable" type fallback to generic Option type code following
+                    // elem type code
                     TypeCode::OPTION.sigma_serialize(w)?;
                     elem_type.sigma_serialize(w)
                 }
             },
-            SType::SColl(elem_type) if is_stype_embeddable(elem_type) => {
-                let code = TypeCode::COLLECTION + elem_type.type_code();
-                code.sigma_serialize(w)
-            }
+
             SType::SColl(elem_type) => match &**elem_type {
-                SType::SColl(inner_elem_type) if is_stype_embeddable(inner_elem_type.as_ref()) => {
-                    let code = TypeCode::NESTED_COLLECTION + inner_elem_type.type_code();
-                    code.sigma_serialize(w)
-                }
-                _ => {
-                    TypeCode::COLLECTION.sigma_serialize(w)?;
+                SBoolean => TypeCode::COLL_BOOL.sigma_serialize(w),
+                SByte => TypeCode::COLL_BYTE.sigma_serialize(w),
+                SShort => TypeCode::COLL_SHORT.sigma_serialize(w),
+                SInt => TypeCode::COLL_INT.sigma_serialize(w),
+                SLong => TypeCode::COLL_LONG.sigma_serialize(w),
+                SBigInt => TypeCode::COLL_BIGINT.sigma_serialize(w),
+                SGroupElement => TypeCode::COLL_GROUP_ELEMENT.sigma_serialize(w),
+                SSigmaProp => TypeCode::COLL_SIGMAPROP.sigma_serialize(w),
+                SColl(inner_elem_type) => match &**inner_elem_type {
+                    SBoolean => TypeCode::NESTED_COLL_BOOL.sigma_serialize(w),
+                    SByte => TypeCode::NESTED_COLL_BYTE.sigma_serialize(w),
+                    SShort => TypeCode::NESTED_COLL_SHORT.sigma_serialize(w),
+                    SInt => TypeCode::NESTED_COLL_INT.sigma_serialize(w),
+                    SLong => TypeCode::NESTED_COLL_LONG.sigma_serialize(w),
+                    SBigInt => TypeCode::NESTED_COLL_BIGINT.sigma_serialize(w),
+                    SGroupElement => TypeCode::NESTED_COLL_GROUP_ELEMENT.sigma_serialize(w),
+                    SSigmaProp => TypeCode::NESTED_COLL_SIGMAPROP.sigma_serialize(w),
+                    STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | SColl(_) | STuple(_)
+                    | SFunc(_) | SContext | SHeader | SPreHeader | SGlobal => {
+                        // if not "embeddable" type fallback to generic Coll type code following
+                        // elem type code
+                        TypeCode::COLL.sigma_serialize(w)?;
+                        elem_type.sigma_serialize(w)
+                    }
+                },
+                STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | STuple(_) | SFunc(_)
+                | SContext | SHeader | SPreHeader | SGlobal => {
+                    // if not "embeddable" type fallback to generic Coll type code following
+                    // elem type code
+                    TypeCode::COLL.sigma_serialize(w)?;
                     elem_type.sigma_serialize(w)
                 }
             },
-            SType::STuple(STuple { items }) => match items.clone().as_slice() {
+            SType::STuple(stuple::STuple { items }) => match items.clone().as_slice() {
                 [t1, t2] => match (t1, t2) {
-                    (p, _) if is_stype_embeddable(p) => {
-                        if p == t2 {
-                            // Symmetric pair of primitive types (`(Int, Int)`, `(Byte,Byte)`, etc.)
-                            let code = TypeCode::TUPLE_PAIR_SYMMETRIC + p.type_code();
-                            code.sigma_serialize(w)
-                        } else {
-                            // Pair of types where first is primitive (`(_, Int)`)
-                            let code = TypeCode::TUPLE_PAIR1 + p.type_code();
-                            code.sigma_serialize(w)?;
-                            t2.sigma_serialize(w)
-                        }
+                    (SBoolean, SBoolean) => TypeCode::TUPLE_PAIR_SYMMETRIC_BOOL.sigma_serialize(w),
+                    (SByte, SByte) => TypeCode::TUPLE_PAIR_SYMMETRIC_BYTE.sigma_serialize(w),
+                    (SInt, SInt) => TypeCode::TUPLE_PAIR_SYMMETRIC_INT.sigma_serialize(w),
+                    (SLong, SLong) => TypeCode::TUPLE_PAIR_SYMMETRIC_LONG.sigma_serialize(w),
+                    (SBigInt, SBigInt) => TypeCode::TUPLE_PAIR_SYMMETRIC_BIGINT.sigma_serialize(w),
+                    (SGroupElement, SGroupElement) => {
+                        TypeCode::TUPLE_PAIR_SYMMETRIC_GROUP_ELEMENT.sigma_serialize(w)
                     }
-                    (_, p) if is_stype_embeddable(p) => {
-                        // Pair of types where second is primitive (`(Int, _)`)
-                        let code = TypeCode::TUPLE_PAIR2 + p.type_code();
-                        code.sigma_serialize(w)?;
-                        t1.sigma_serialize(w)
+                    (SSigmaProp, SSigmaProp) => {
+                        TypeCode::TUPLE_PAIR_SYMMETRIC_SIGMAPROP.sigma_serialize(w)
                     }
-                    (_, _) => {
-                        // Pair of non-primitive types (`((Int, Byte), (Boolean,Box))`, etc.)
-                        TypeCode::TUPLE_PAIR1.sigma_serialize(w)?;
-                        t1.sigma_serialize(w)?;
+
+                    (SBoolean, t2) => {
+                        TypeCode::TUPLE_PAIR1_BOOL.sigma_serialize(w)?;
                         t2.sigma_serialize(w)
                     }
-                },
-                _ => match items.len() {
-                    3 => {
-                        TypeCode::TUPLE_TRIPLE.sigma_serialize(w)?;
-                        items.iter().try_for_each(|i| i.sigma_serialize(w))
+                    (SByte, t2) => {
+                        TypeCode::TUPLE_PAIR1_BYTE.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SShort, t2) => {
+                        TypeCode::TUPLE_PAIR1_SHORT.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SInt, t2) => {
+                        TypeCode::TUPLE_PAIR1_INT.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SLong, t2) => {
+                        TypeCode::TUPLE_PAIR1_LONG.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SBigInt, t2) => {
+                        TypeCode::TUPLE_PAIR1_BIGINT.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SGroupElement, t2) => {
+                        TypeCode::TUPLE_PAIR1_GROUP_ELEMENT.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+                    (SSigmaProp, t2) => {
+                        TypeCode::TUPLE_PAIR1_SIGMAPROP.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
+                    }
+
+                    (t1, SBoolean) => {
+                        TypeCode::TUPLE_PAIR2_BOOL.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SByte) => {
+                        TypeCode::TUPLE_PAIR2_BYTE.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SShort) => {
+                        TypeCode::TUPLE_PAIR2_SHORT.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SInt) => {
+                        TypeCode::TUPLE_PAIR2_INT.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SLong) => {
+                        TypeCode::TUPLE_PAIR2_LONG.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
                     }
-                    4 => {
-                        TypeCode::TUPLE_QUADRUPLE.sigma_serialize(w)?;
-                        items.iter().try_for_each(|i| i.sigma_serialize(w))
+                    (t1, SBigInt) => {
+                        TypeCode::TUPLE_PAIR2_BIGINT.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SGroupElement) => {
+                        TypeCode::TUPLE_PAIR2_GROUP_ELEMENT.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
+                    }
+                    (t1, SSigmaProp) => {
+                        TypeCode::TUPLE_PAIR2_SIGMAPROP.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)
                     }
-                    _ => {
-                        TypeCode::TUPLE.sigma_serialize(w)?;
-                        w.put_u8(items.len() as u8)?;
-                        items.iter().try_for_each(|i| i.sigma_serialize(w))
+                    (
+                        STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | SColl(_) | STuple(_)
+                        | SFunc(_) | SContext | SHeader | SPreHeader | SGlobal,
+                        STypeVar(_) | SAny | SBox | SAvlTree | SOption(_) | SColl(_) | STuple(_)
+                        | SFunc(_) | SContext | SHeader | SPreHeader | SGlobal,
+                    ) => {
+                        // Pair of non-primitive types (`(SBox, SAvlTree)`, `((Int, Byte), (Boolean,Box))`, etc.)
+                        TypeCode::TUPLE_PAIR1.sigma_serialize(w)?;
+                        t1.sigma_serialize(w)?;
+                        t2.sigma_serialize(w)
                     }
                 },
+                [t1, t2, t3] => {
+                    TypeCode::TUPLE_TRIPLE.sigma_serialize(w)?;
+                    t1.sigma_serialize(w)?;
+                    t2.sigma_serialize(w)?;
+                    t3.sigma_serialize(w)
+                }
+                [t1, t2, t3, t4] => {
+                    TypeCode::TUPLE_QUADRUPLE.sigma_serialize(w)?;
+                    t1.sigma_serialize(w)?;
+                    t2.sigma_serialize(w)?;
+                    t3.sigma_serialize(w)?;
+                    t4.sigma_serialize(w)
+                }
+                _ => {
+                    TypeCode::TUPLE.sigma_serialize(w)?;
+                    w.put_u8(items.len() as u8)?;
+                    items.iter().try_for_each(|i| i.sigma_serialize(w))
+                }
             },
+
             SType::STypeVar(tv) => {
                 TypeCode::STYPE_VAR.sigma_serialize(w)?;
                 tv.sigma_serialize(w)
@@ -374,14 +551,6 @@ impl SigmaSerializable for SType {
     }
 }
 
-fn get_arg_type<R: SigmaByteRead>(r: &mut R, prim_id: u8) -> Result<SType, SigmaParsingError> {
-    if prim_id == 0 {
-        SType::sigma_parse(r)
-    } else {
-        get_embeddable_type(prim_id)
-    }
-}
-
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::panic)]
diff --git a/ergotree-ir/src/types/scoll.rs b/ergotree-ir/src/types/scoll.rs
index 9ffc77663..ed48ee684 100644
--- a/ergotree-ir/src/types/scoll.rs
+++ b/ergotree-ir/src/types/scoll.rs
@@ -12,7 +12,7 @@ use super::stype_param::STypeVar;
 use lazy_static::lazy_static;
 
 /// type id
-pub const TYPE_ID: TypeCode = TypeCode::COLLECTION;
+pub const TYPE_ID: TypeCode = TypeCode::COLL;
 /// Coll.indexOf
 pub const INDEX_OF_METHOD_ID: MethodId = MethodId(26);
 /// Coll.flatmap
diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
index 7a60901ae..194a58fb4 100644
--- a/ergotree-ir/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -44,11 +44,14 @@ impl SMethod {
     }
 
     /// Get method from type and method ids
-    pub fn from_ids(type_id: TypeCode, method_id: MethodId) -> Result<Self, SigmaParsingError> {
+    pub(crate) fn from_ids(
+        type_id: TypeCode,
+        method_id: MethodId,
+    ) -> Result<Self, SigmaParsingError> {
         let obj_type = STypeCompanion::type_by_id(type_id)?;
         match obj_type.method_by_id(&method_id) {
             Some(m) => Ok(m),
-            None => Err(UnknownMethodId(method_id, type_id)),
+            None => Err(UnknownMethodId(method_id, type_id.value())),
         }
     }
 
diff --git a/ergotree-ir/src/types/stuple.rs b/ergotree-ir/src/types/stuple.rs
index 04715d6a0..963c84d33 100644
--- a/ergotree-ir/src/types/stuple.rs
+++ b/ergotree-ir/src/types/stuple.rs
@@ -36,6 +36,22 @@ impl STuple {
         }
     }
 
+    /// Create a triple type
+    pub fn triple(t1: SType, t2: SType, t3: SType) -> Self {
+        #[allow(clippy::unwrap_used)]
+        STuple {
+            items: vec![t1, t2, t3].try_into().unwrap(),
+        }
+    }
+
+    /// Create a quadruple type
+    pub fn quadruple(t1: SType, t2: SType, t3: SType, t4: SType) -> Self {
+        #[allow(clippy::unwrap_used)]
+        STuple {
+            items: vec![t1, t2, t3, t4].try_into().unwrap(),
+        }
+    }
+
     pub(crate) fn with_subst(self, subst: &HashMap<STypeVar, SType>) -> Self {
         STuple {
             items: self.items.mapped(|a| a.with_subst(subst)),
diff --git a/ergotree-ir/src/types/stype.rs b/ergotree-ir/src/types/stype.rs
index 4f23e7812..3a354f6c8 100644
--- a/ergotree-ir/src/types/stype.rs
+++ b/ergotree-ir/src/types/stype.rs
@@ -7,7 +7,6 @@ use impl_trait_for_tuples::impl_for_tuples;
 
 use crate::bigint256::BigInt256;
 use crate::ir_ergo_box::IrBoxId;
-use crate::serialization::types::TypeCode;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
@@ -64,32 +63,6 @@ pub enum SType {
 }
 
 impl SType {
-    /// Type code used in serialization of SType values.
-    pub fn type_code(&self) -> TypeCode {
-        match self {
-            SType::SFunc(_) => TypeCode::FIRST_FUNC_TYPE,
-            SType::SAny => TypeCode::SANY,
-            SType::SBoolean => TypeCode::SBOOLEAN,
-            SType::SByte => TypeCode::SBYTE,
-            SType::SShort => TypeCode::SSHORT,
-            SType::SInt => TypeCode::SINT,
-            SType::SLong => TypeCode::SLONG,
-            SType::SBigInt => TypeCode::SBIGINT,
-            SType::SGroupElement => TypeCode::SGROUP_ELEMENT,
-            SType::SSigmaProp => TypeCode::SSIGMAPROP,
-            SType::SBox => TypeCode::SBOX,
-            SType::SAvlTree => TypeCode::SAVL_TREE,
-            SType::SOption(_) => TypeCode::OPTION,
-            SType::SColl(_) => TypeCode::COLLECTION,
-            SType::STuple(_) => TypeCode::TUPLE,
-            SType::SContext => TypeCode::SCONTEXT,
-            SType::STypeVar(_) => TypeCode::STYPE_VAR,
-            SType::SHeader => TypeCode::SHEADER,
-            SType::SPreHeader => TypeCode::SPRE_HEADER,
-            SType::SGlobal => TypeCode::SGLOBAL,
-        }
-    }
-
     /// Check if type is numeric
     pub fn is_numeric(&self) -> bool {
         matches!(
@@ -294,7 +267,7 @@ pub(crate) mod tests {
                     16, // each collection max size
                     |elem| {
                         prop_oneof![
-                            prop::collection::vec(elem.clone(), 2..=4)
+                            prop::collection::vec(elem.clone(), 2..=5)
                                 .prop_map(|elems| SType::STuple(elems.try_into().unwrap())),
                             elem.clone().prop_map(|tpe| SType::SColl(Box::new(tpe))),
                             elem.prop_map(|tpe| SType::SOption(Box::new(tpe))),


Test patch: