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

Issue:
Make ErgoBoxCandidate.value a wrapped type
Implement checked arithmetic ops (catch overflow, negative results, etc.).


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-wallet-wasm/src/lib.rs b/bindings/ergo-wallet-wasm/src/lib.rs
index 771f2ee31..1706c0115 100644
--- a/bindings/ergo-wallet-wasm/src/lib.rs
+++ b/bindings/ergo-wallet-wasm/src/lib.rs
@@ -14,6 +14,7 @@ use sigma_tree::chain;
 
 mod utils;
 
+use chain::BoxValue;
 use wasm_bindgen::prelude::*;
 
 /**
@@ -153,7 +154,11 @@ impl ErgoBoxCandidate {
     pub fn new(value: u32, creation_height: u32, contract: Contract) -> ErgoBoxCandidate {
         // value is u32, because u64 makes in BigInt in JS
         let ergo_tree = contract.0.get_ergo_tree();
-        let b = chain::ErgoBoxCandidate::new(value as u64, ergo_tree, creation_height);
+        let b = chain::ErgoBoxCandidate::new(
+            BoxValue::new(value as u64).expect("value out of bounds"),
+            ergo_tree,
+            creation_height,
+        );
         ErgoBoxCandidate(b)
     }
 
diff --git a/sigma-ser/src/serializer.rs b/sigma-ser/src/serializer.rs
index 62b7623f5..e03019e3b 100644
--- a/sigma-ser/src/serializer.rs
+++ b/sigma-ser/src/serializer.rs
@@ -22,7 +22,7 @@ pub enum SerializationError {
     Io(io::Error),
     /// Misc fail
     #[error("misc error")]
-    Misc(&'static str),
+    Misc(String),
 }
 
 impl From<vlq_encode::VlqEncodingError> for SerializationError {
diff --git a/sigma-tree/src/ast/constant.rs b/sigma-tree/src/ast/constant.rs
index 77058b119..fa811351a 100644
--- a/sigma-tree/src/ast/constant.rs
+++ b/sigma-tree/src/ast/constant.rs
@@ -39,6 +39,48 @@ pub struct Constant {
 }
 
 impl Constant {
+    pub fn bool(v: bool) -> Constant {
+        Constant {
+            tpe: SType::SBoolean,
+            v: ConstantVal::Boolean(v),
+        }
+    }
+
+    pub fn byte(v: i8) -> Constant {
+        Constant {
+            tpe: SType::SByte,
+            v: ConstantVal::Byte(v),
+        }
+    }
+
+    pub fn short(v: i16) -> Constant {
+        Constant {
+            tpe: SType::SShort,
+            v: ConstantVal::Short(v),
+        }
+    }
+
+    pub fn int(v: i32) -> Constant {
+        Constant {
+            tpe: SType::SInt,
+            v: ConstantVal::Int(v),
+        }
+    }
+
+    pub fn long(v: i64) -> Constant {
+        Constant {
+            tpe: SType::SLong,
+            v: ConstantVal::Long(v),
+        }
+    }
+
+    pub fn byte_array(v: Vec<i8>) -> Constant {
+        Constant {
+            tpe: SType::SColl(Box::new(SType::SByte)),
+            v: ConstantVal::CollPrim(CollPrim::CollByte(v)),
+        }
+    }
+
     pub fn sigma_prop(prop: SigmaProp) -> Constant {
         Constant {
             tpe: SType::SSigmaProp,
@@ -46,3 +88,27 @@ impl Constant {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::collection::vec;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Constant {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                any::<bool>().prop_map(|v| Constant::bool(v)),
+                any::<i8>().prop_map(|v| Constant::byte(v)),
+                any::<i16>().prop_map(|v| Constant::short(v)),
+                any::<i32>().prop_map(|v| Constant::int(v)),
+                any::<i64>().prop_map(|v| Constant::long(v)),
+                (vec(any::<i8>(), 0..100)).prop_map(|v| Constant::byte_array(v)),
+            ]
+            .boxed()
+        }
+    }
+}
diff --git a/sigma-tree/src/chain/ergo_box.rs b/sigma-tree/src/chain/ergo_box.rs
index f9d1b5832..d63eefbac 100644
--- a/sigma-tree/src/chain/ergo_box.rs
+++ b/sigma-tree/src/chain/ergo_box.rs
@@ -1,24 +1,180 @@
 //! Ergo box
 use super::token::{TokenAmount, TokenId};
-use crate::ergo_tree::ErgoTree;
+use crate::{ast::Constant, ergo_tree::ErgoTree};
 use indexmap::IndexSet;
 #[cfg(feature = "with-serde")]
-use serde::{Deserialize, Serialize};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
 use sigma_ser::vlq_encode;
 use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::io;
-
-#[allow(dead_code)]
-const STARTING_NON_MANDATORY_INDEX: u8 = 4;
+use NonMandatoryRegistersError::{InvalidSize, NonDenselyPacked};
 
 /// newtype for additional registers R4 - R9
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
 #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
 pub struct NonMandatoryRegisterId(u8);
 
+impl NonMandatoryRegisterId {
+    /// starting index for non-mandatory registers
+    pub const START_INDEX: u8 = 4;
+    /// end index for non-mandatory registers
+    pub const END_INDEX: u8 = 9;
+
+    /// register R4
+    pub const R4: NonMandatoryRegisterId = NonMandatoryRegisterId(4);
+    /// register R5
+    pub const R5: NonMandatoryRegisterId = NonMandatoryRegisterId(5);
+    /// register R6
+    pub const R6: NonMandatoryRegisterId = NonMandatoryRegisterId(6);
+    /// register R7
+    pub const R7: NonMandatoryRegisterId = NonMandatoryRegisterId(7);
+    /// register R8
+    pub const R8: NonMandatoryRegisterId = NonMandatoryRegisterId(8);
+    /// register R9
+    pub const R9: NonMandatoryRegisterId = NonMandatoryRegisterId(9);
+
+    const REG_IDS: [NonMandatoryRegisterId; 6] = [
+        NonMandatoryRegisterId::R4,
+        NonMandatoryRegisterId::R5,
+        NonMandatoryRegisterId::R6,
+        NonMandatoryRegisterId::R7,
+        NonMandatoryRegisterId::R8,
+        NonMandatoryRegisterId::R9,
+    ];
+
+    /// get register by it's index
+    /// `i` is expected to be in range [`START_INDEX`] to [`END_INDEX`] , otherwise panic
+    pub fn get_by_index(i: usize) -> NonMandatoryRegisterId {
+        assert!(
+            i >= NonMandatoryRegisterId::START_INDEX as usize
+                && i <= NonMandatoryRegisterId::END_INDEX as usize
+        );
+        NonMandatoryRegisterId::REG_IDS[i - NonMandatoryRegisterId::START_INDEX as usize].clone()
+    }
+}
+
+/// Stores non-mandatory registers for the box
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct NonMandatoryRegisters(Vec<Constant>);
+
+/// Possible errors when building NonMandatoryRegisters
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub enum NonMandatoryRegistersError {
+    /// Set of register has invalid size(maximum [`NonMandatoryRegisters::MAX_SIZE`])
+    InvalidSize(usize),
+    /// Set of non-mandatory indexes are not densely packed
+    NonDenselyPacked(u8),
+}
+
+impl NonMandatoryRegistersError {
+    /// get detailed error message
+    pub fn error_msg(&self) -> String {
+        match self {
+            InvalidSize(size) => format!(
+                "invalid non-mandatory registers size {} (expected {})",
+                size,
+                NonMandatoryRegisters::MAX_SIZE
+            ),
+            NonDenselyPacked(reg_id) => format!(
+                "non-mandatory registers are not densely packed, {} is missing in range [{} .. {}]",
+                reg_id,
+                NonMandatoryRegisterId::START_INDEX,
+                NonMandatoryRegisterId::END_INDEX
+            ),
+        }
+    }
+}
+
+impl NonMandatoryRegisters {
+    /// Maximum number of non-mandatory registers
+    pub const MAX_SIZE: usize = 6;
+
+    /// Empty non-mandatory registers
+    pub fn empty() -> NonMandatoryRegisters {
+        NonMandatoryRegisters(vec![])
+    }
+
+    /// Create new from map
+    pub fn new(
+        _regs: HashMap<NonMandatoryRegisterId, Box<Constant>>,
+    ) -> Result<NonMandatoryRegisters, NonMandatoryRegistersError> {
+        // return error if size is incorrect and/or there is a gap
+        // we assume non-mandatory indexes are densely packed from startingNonMandatoryIndex
+        // this convention allows to save 1 byte for each register
+        todo!()
+    }
+
+    /// Create new from ordered values (first element will be R4, and so on)
+    pub fn from_ordered_values(
+        values: Vec<Constant>,
+    ) -> Result<NonMandatoryRegisters, NonMandatoryRegistersError> {
+        if values.len() > NonMandatoryRegisters::MAX_SIZE {
+            Err(NonMandatoryRegistersError::InvalidSize(values.len()))
+        } else {
+            Ok(NonMandatoryRegisters(values))
+        }
+    }
+
+    /// Size of non-mandatory registers set
+    pub fn len(&self) -> u8 {
+        self.0.len() as u8
+    }
+
+    /// Return true if non-mandatory registers set is empty
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    /// Get register value
+    pub fn get(&self, _reg_id: &NonMandatoryRegisterId) -> Option<Box<Constant>> {
+        todo!()
+    }
+
+    /// Get ordered register values (first is R4, and so on, up to R9)
+    pub fn get_ordered_values(&self) -> Vec<Constant> {
+        self.0.clone()
+    }
+}
+
+impl From<NonMandatoryRegistersError> for SerializationError {
+    fn from(error: NonMandatoryRegistersError) -> Self {
+        SerializationError::Misc(error.error_msg())
+    }
+}
+
+/// Box value
+#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+pub struct BoxValue(u64);
+
+impl BoxValue {
+    /// Create new value (with bounds check)
+    pub fn new(v: u64) -> Option<BoxValue> {
+        if BoxValue::within_bounds(v) {
+            Some(BoxValue(v))
+        } else {
+            None
+        }
+    }
+
+    /// Check if a value is in bounds
+    pub fn within_bounds(v: u64) -> bool {
+        v >= 1 && v <= i64::MAX as u64
+    }
+}
+
+impl SigmaSerializable for BoxValue {
+    fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
+        w.put_u64(self.0)
+    }
+    fn sigma_parse<R: vlq_encode::ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
+        let v = r.get_u64()?;
+        Ok(BoxValue(v))
+    }
+}
+
 /// Transaction id (ModifierId in sigmastate)
 #[derive(PartialEq, Eq, Hash, Debug, Clone)]
 #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
@@ -44,13 +200,13 @@ pub struct TxId(String);
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct ErgoBox {
     /// amount of money associated with the box
-    pub value: u64,
+    pub value: BoxValue,
     /// guarding script, which should be evaluated to true in order to open this box
     pub ergo_tree: ErgoTree,
     /// secondary tokens the box contains
     pub tokens: Vec<TokenAmount>,
     ///  additional registers the box can carry over
-    pub additional_registers: HashMap<NonMandatoryRegisterId, Box<[u8]>>,
+    pub additional_registers: NonMandatoryRegisters,
     /// height when a transaction containing the box was created.
     /// This height is declared by user and should not exceed height of the block,
     /// containing the transaction with this box.
@@ -64,16 +220,15 @@ pub struct ErgoBox {
 /// Contains the same fields as `ErgoBox`, except if transaction id and index,
 /// that will be calculated after full transaction formation.
 #[derive(PartialEq, Eq, Debug)]
-#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))]
 pub struct ErgoBoxCandidate {
     /// amount of money associated with the box
-    pub value: u64,
+    pub value: BoxValue,
     /// guarding script, which should be evaluated to true in order to open this box
     pub ergo_tree: ErgoTree,
     /// secondary tokens the box contains
     pub tokens: Vec<TokenAmount>,
     ///  additional registers the box can carry over
-    pub additional_registers: HashMap<NonMandatoryRegisterId, Box<[u8]>>,
+    pub additional_registers: NonMandatoryRegisters,
     /// height when a transaction containing the box was created.
     /// This height is declared by user and should not exceed height of the block,
     /// containing the transaction with this box.
@@ -82,12 +237,12 @@ pub struct ErgoBoxCandidate {
 
 impl ErgoBoxCandidate {
     /// create box with value guarded by ErgoTree
-    pub fn new(value: u64, ergo_tree: ErgoTree, creation_height: u32) -> ErgoBoxCandidate {
+    pub fn new(value: BoxValue, ergo_tree: ErgoTree, creation_height: u32) -> ErgoBoxCandidate {
         ErgoBoxCandidate {
             value,
             ergo_tree,
             tokens: vec![],
-            additional_registers: HashMap::new(),
+            additional_registers: NonMandatoryRegisters::empty(),
             creation_height,
         }
     }
@@ -100,7 +255,7 @@ impl ErgoBoxCandidate {
         w: &mut W,
     ) -> Result<(), io::Error> {
         // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L95-L95
-        w.put_u64(self.value)?;
+        self.value.sigma_serialize(w)?;
         self.ergo_tree.sigma_serialize(w)?;
         w.put_u32(self.creation_height)?;
         w.put_u8(u8::try_from(self.tokens.len()).unwrap())?;
@@ -111,6 +266,8 @@ impl ErgoBoxCandidate {
                     u32::try_from(
                         token_ids
                             .get_full(&t.token_id)
+                            // this is not a true runtime error it just means that
+                            // calling site messed up the token ids
                             .expect("failed to find token id in tx's digest index")
                             .0,
                     )
@@ -121,39 +278,14 @@ impl ErgoBoxCandidate {
             .and_then(|()| w.put_u64(t.amount))
         })?;
 
-        assert!(
-            self.additional_registers.is_empty(),
-            "register serialization is not yet implemented"
-        );
-        /*
-            let regs_num = self.additional_registers.keys().len();
-            assert!(
-                (regs_num + STARTING_NON_MANDATORY_INDEX as usize) <= 255,
-                "The number of non-mandatory indexes exceeds 251 limit."
-            );
-            w.put_u8(regs_num as u8)?;
-        */
-
-        /*
-          val nRegs = obj.additionalRegisters.keys.size
-          if (nRegs + ErgoBox.startingNonMandatoryIndex > 255)
-            sys.error(s"The number of non-mandatory indexes $nRegs exceeds ${255 - ErgoBox.startingNonMandatoryIndex} limit.")
-          w.putUByte(nRegs)
-          // we assume non-mandatory indexes are densely packed from startingNonMandatoryIndex
-          // this convention allows to save 1 bite for each register
-          val startReg = ErgoBox.startingNonMandatoryIndex
-          val endReg = ErgoBox.startingNonMandatoryIndex + nRegs - 1
-          cfor(startReg: Int)(_ <= endReg, _ + 1) { regId =>
-            val reg = ErgoBox.findRegisterByIndex(regId.toByte).get
-            obj.get(reg) match {
-              case Some(v) =>
-                w.putValue(v)
-              case None =>
-                sys.error(s"Set of non-mandatory indexes is not densely packed: " +
-                  s"register R$regId is missing in the range [$startReg .. $endReg]")
-            }
-          }
-        */
+        let regs_num = self.additional_registers.len();
+        w.put_u8(regs_num as u8)?;
+
+        self.additional_registers
+            .get_ordered_values()
+            .into_iter()
+            .try_for_each(|c| c.sigma_serialize(w))?;
+
         Ok(())
     }
 
@@ -164,7 +296,7 @@ impl ErgoBoxCandidate {
     ) -> Result<ErgoBoxCandidate, SerializationError> {
         // reference implementation -https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala#L144-L144
 
-        let value = r.get_u64()?;
+        let value = BoxValue::sigma_parse(r)?;
         let ergo_tree = ErgoTree::sigma_parse(r)?;
         let creation_height = r.get_u32()?;
         let tokens_count = r.get_u8()?;
@@ -174,17 +306,25 @@ impl ErgoBoxCandidate {
                 None => TokenId::sigma_parse(r)?,
                 Some(digests) => {
                     let digest_index = r.get_u32()?;
-                    *digests
-                        .get_index(digest_index as usize)
-                        .expect("failed to find token id in tx digests")
+                    match digests.get_index(digest_index as usize) {
+                        Some(i) => Ok(*i),
+                        None => Err(SerializationError::Misc(
+                            "failed to find token id in tx digests".to_string(),
+                        )),
+                    }?
                 }
             };
             let amount = r.get_u64()?;
             tokens.push(TokenAmount { token_id, amount })
         }
 
-        let additional_registers = HashMap::new();
-
+        let regs_num = r.get_u8()?;
+        let mut additional_regs = Vec::with_capacity(regs_num as usize);
+        for _ in 0..regs_num {
+            let v = Constant::sigma_parse(r)?;
+            additional_regs.push(v);
+        }
+        let additional_registers = NonMandatoryRegisters::from_ordered_values(additional_regs)?;
         Ok(ErgoBoxCandidate {
             value,
             ergo_tree,
@@ -204,29 +344,63 @@ impl SigmaSerializable for ErgoBoxCandidate {
     }
 }
 
+#[cfg(feature = "with-serde")]
+impl serde::Serialize for ErgoBoxCandidate {
+    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        // not implmented
+        s.serialize_str("")
+    }
+}
+
+#[cfg(feature = "with-serde")]
+impl<'de> serde::Deserialize<'de> for ErgoBoxCandidate {
+    fn deserialize<D>(_: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        todo!()
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
     use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
     use sigma_ser::test_helpers::*;
 
+    impl Arbitrary for BoxValue {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            // TODO: should be in 1 - i64.max range
+            any::<u64>().prop_map(|v| BoxValue(v)).boxed()
+        }
+    }
+
     impl Arbitrary for ErgoBoxCandidate {
         type Parameters = ();
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             (
-                any::<u64>(),
+                any::<BoxValue>(),
                 any::<ErgoTree>(),
                 vec(any::<TokenAmount>(), 0..10),
                 any::<u32>(),
+                vec(any::<Constant>(), 0..7),
             )
-                .prop_map(|(value, ergo_tree, tokens, creation_height)| Self {
-                    value,
-                    ergo_tree,
-                    tokens,
-                    additional_registers: HashMap::new(),
-                    creation_height,
-                })
+                .prop_map(
+                    |(value, ergo_tree, tokens, creation_height, constants)| Self {
+                        value,
+                        ergo_tree,
+                        tokens,
+                        additional_registers: NonMandatoryRegisters::from_ordered_values(constants)
+                            .expect("error building registers"),
+                        creation_height,
+                    },
+                )
                 .boxed()
         }
         type Strategy = BoxedStrategy<Self>;
diff --git a/sigma-tree/src/chain/prover_result.rs b/sigma-tree/src/chain/prover_result.rs
index 4ad56d21b..584f8cb49 100644
--- a/sigma-tree/src/chain/prover_result.rs
+++ b/sigma-tree/src/chain/prover_result.rs
@@ -5,8 +5,6 @@ use sigma_ser::vlq_encode;
 use std::io;
 
 use super::context_extension::ContextExtension;
-#[cfg(test)]
-use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
 #[cfg(feature = "with-serde")]
 use serde::{Deserialize, Serialize};
 
@@ -36,27 +34,26 @@ impl SigmaSerializable for ProverResult {
     }
 }
 
-#[cfg(test)]
-impl Arbitrary for ProverResult {
-    type Parameters = ();
-
-    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-        (vec(any::<u8>(), 0..100))
-            .prop_map(|v| Self {
-                proof: v,
-                extension: ContextExtension::empty(),
-            })
-            .boxed()
-    }
-
-    type Strategy = BoxedStrategy<Self>;
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
+    use proptest::{collection::vec, prelude::*};
     use sigma_ser::test_helpers::*;
 
+    impl Arbitrary for ProverResult {
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (vec(any::<u8>(), 0..100))
+                .prop_map(|v| Self {
+                    proof: v,
+                    extension: ContextExtension::empty(),
+                })
+                .boxed()
+        }
+
+        type Strategy = BoxedStrategy<Self>;
+    }
     proptest! {
 
         #[test]
diff --git a/sigma-tree/src/ecpoint.rs b/sigma-tree/src/ecpoint.rs
index 33ac73f1f..59ebe0396 100644
--- a/sigma-tree/src/ecpoint.rs
+++ b/sigma-tree/src/ecpoint.rs
@@ -63,12 +63,13 @@ impl SigmaSerializable for EcPoint {
         let mut buf = [0; EcPoint::GROUP_SIZE];
         r.read_exact(&mut buf[..])?;
         if buf[0] != 0 {
-            let pubkey = PublicKey::from_bytes(&buf[..])
-                .ok_or(SerializationError::Misc("failed to parse PK from bytes"))?;
+            let pubkey = PublicKey::from_bytes(&buf[..]).ok_or_else(|| {
+                SerializationError::Misc("failed to parse PK from bytes".to_string())
+            })?;
             let cp = AffinePoint::from_pubkey(&pubkey);
             if bool::from(cp.is_none()) {
                 Err(SerializationError::Misc(
-                    "failed to get affine point from PK",
+                    "failed to get affine point from PK".to_string(),
                 ))
             } else {
                 Ok(EcPoint(ProjectivePoint::from(cp.unwrap())))
@@ -92,9 +93,9 @@ mod tests {
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             prop_oneof![
-                prop::num::u8::ANY.prop_map(|_| EcPoint(ProjectivePoint::generator())),
-                prop::num::u8::ANY.prop_map(|_| EcPoint(ProjectivePoint::identity())),
-                prop::num::u8::ANY.prop_map(|_| EcPoint::random()),
+                Just(EcPoint(ProjectivePoint::generator())),
+                Just(EcPoint(ProjectivePoint::identity())),
+                Just(EcPoint::random()),
             ]
             .boxed()
         }
diff --git a/sigma-tree/src/serialization/constant.rs b/sigma-tree/src/serialization/constant.rs
index ba8daa6dc..8488ea836 100644
--- a/sigma-tree/src/serialization/constant.rs
+++ b/sigma-tree/src/serialization/constant.rs
@@ -19,3 +19,18 @@ impl SigmaSerializable for Constant {
         Ok(Constant { tpe, v })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+    use sigma_ser::test_helpers::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<Constant>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/sigma-tree/src/serialization/data.rs b/sigma-tree/src/serialization/data.rs
index 4bafa7868..e8359fd14 100644
--- a/sigma-tree/src/serialization/data.rs
+++ b/sigma-tree/src/serialization/data.rs
@@ -11,7 +11,6 @@ use std::io;
 
 pub struct DataSerializer {}
 
-// TODO: convert to SigmaSerializable impl for ConstantVal?
 impl DataSerializer {
     pub fn sigma_serialize<W: WriteSigmaVlqExt>(
         c: &ConstantVal,
@@ -19,17 +18,27 @@ impl DataSerializer {
     ) -> Result<(), io::Error> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala#L26-L26
         match c {
-            Boolean(_) => todo!(),
-            Byte(b) => w.put_i8(*b),
-            Short(_) => todo!(),
-            Int(_) => todo!(),
-            Long(_) => todo!(),
+            Boolean(v) => w.put_u8(if *v { 1 } else { 0 }),
+            Byte(v) => w.put_i8(*v),
+            Short(v) => w.put_i16(*v),
+            Int(v) => w.put_i32(*v),
+            Long(v) => w.put_i64(*v),
             BigInt => todo!(),
             GroupElement => todo!(),
             SigmaProp(s) => s.value().sigma_serialize(w),
             CBox(_) => todo!(),
             AvlTree => todo!(),
-            ConstantVal::CollPrim(_) => todo!(),
+            ConstantVal::CollPrim(c) => match c {
+                CollPrim::CollByte(b) => {
+                    w.put_usize_as_u16(b.len())?;
+                    let ba: Vec<u8> = b.iter().map(|v| *v as u8).collect();
+                    w.write_all(&ba[..])
+                }
+                CollPrim::CollBoolean(_) => todo!(),
+                CollPrim::CollShort(_) => todo!(),
+                CollPrim::CollInt(_) => todo!(),
+                CollPrim::CollLong(_) => todo!(),
+            },
             Coll(_) => todo!(),
             Tup(_) => todo!(),
         }
@@ -42,7 +51,11 @@ impl DataSerializer {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala#L84-L84
         let c = match tpe {
             SAny => todo!(),
+            SBoolean => Boolean(r.get_u8()? != 0),
             SByte => Byte(r.get_i8()?),
+            SShort => Short(r.get_i16()?),
+            SInt => Int(r.get_i32()?),
+            SLong => Long(r.get_i64()?),
             SSigmaProp => ConstantVal::sigma_prop(SigmaProp::new(SigmaBoolean::sigma_parse(r)?)),
             SColl(elem_type) => {
                 let len = r.get_u16()? as usize;
diff --git a/sigma-tree/src/serialization/types.rs b/sigma-tree/src/serialization/types.rs
index 6aa057b27..0b8977f2e 100644
--- a/sigma-tree/src/serialization/types.rs
+++ b/sigma-tree/src/serialization/types.rs
@@ -3,7 +3,7 @@ use sigma_ser::{
     serializer::{SerializationError, SigmaSerializable},
     vlq_encode,
 };
-use std::io;
+use std::{io, ops::Add};
 use vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -17,10 +17,21 @@ impl TypeCode {
     pub const MAX_PRIM_TYPECODE: u8 = 11;
     pub const PRIM_RANGE: u8 = (TypeCode::MAX_PRIM_TYPECODE + 1);
 
-    pub const SBOOLEAN: TypeCode = Self::new_type_code(1);
-    pub const SSIGMAPROP: TypeCode = Self::new_type_code(8);
+    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);
 
-    const fn new_type_code(c: u8) -> TypeCode {
+    pub const COLLECTION_TYPE_CONSTR_ID: u8 = 1;
+    pub const COLLECTION_TYPE_CODE: TypeCode =
+        Self::new((TypeCode::MAX_PRIM_TYPECODE + 1) * TypeCode::COLLECTION_TYPE_CONSTR_ID);
+
+    const fn new(c: u8) -> TypeCode {
         TypeCode(c)
     }
 
@@ -29,6 +40,13 @@ impl TypeCode {
     }
 }
 
+impl Add for TypeCode {
+    type Output = TypeCode;
+    fn add(self, rhs: Self) -> TypeCode {
+        TypeCode::new(self.0 + rhs.0)
+    }
+}
+
 impl SigmaSerializable for TypeCode {
     fn sigma_serialize<W: WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
         w.put_u8(self.value())
@@ -43,28 +61,92 @@ impl SigmaSerializable for TypeCode {
     }
 }
 
-fn get_embeddable_type(code: TypeCode) -> Result<SType, SerializationError> {
-    Ok(match code {
-        TypeCode::SBOOLEAN => SType::SBoolean,
-        TypeCode::SSIGMAPROP => SType::SSigmaProp,
-        _ => todo!(),
-    })
+fn get_embeddable_type(code: u8) -> Result<SType, SerializationError> {
+    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(SerializationError::InvalidOpCode),
+    }
+}
+
+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 SigmaSerializable for SType {
     fn sigma_serialize<W: WriteSigmaVlqExt>(&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
-        self.type_code().sigma_serialize(w)
+        match self {
+            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 => todo!(),
+            SType::SAvlTree => todo!(),
+            SType::SOption(_) => todo!(),
+            SType::SColl(elem_type) if is_stype_embeddable(elem_type) => {
+                let code = TypeCode::COLLECTION_TYPE_CODE + elem_type.type_code();
+                code.sigma_serialize(w)
+            }
+            SType::SColl(_) => todo!(),
+            SType::STup(_) => todo!(),
+            SType::SFunc(_) => todo!(),
+        }
     }
 
     fn sigma_parse<R: ReadSigmaVlqExt>(r: &mut R) -> Result<Self, SerializationError> {
         // for reference see http://github.com/ScorexFoundation/sigmastate-interpreter/blob/25251c1313b0131835f92099f02cef8a5d932b5e/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala#L118-L118
         let type_code = TypeCode::sigma_parse(r)?;
         let constr_id = type_code.value() / TypeCode::PRIM_RANGE;
+        let prim_id = type_code.value() % TypeCode::PRIM_RANGE;
         let tpe = match constr_id {
-            0 => get_embeddable_type(type_code)?,
+            // primitive
+            0 => get_embeddable_type(type_code.value())?,
+            // Coll[_]
+            1 => {
+                let t_elem = get_embeddable_type(prim_id)?;
+                SType::SColl(Box::new(t_elem))
+            }
             _ => todo!(),
         };
         Ok(tpe)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+    use sigma_ser::test_helpers::*;
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<SType>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/sigma-tree/src/types.rs b/sigma-tree/src/types.rs
index 226ca47be..b740f2050 100644
--- a/sigma-tree/src/types.rs
+++ b/sigma-tree/src/types.rs
@@ -30,13 +30,13 @@ impl SType {
     pub fn type_code(&self) -> TypeCode {
         match self {
             SType::SAny => todo!(),
-            SType::SBoolean => todo!(),
-            SType::SByte => todo!(),
-            SType::SShort => todo!(),
-            SType::SInt => todo!(),
-            SType::SLong => todo!(),
-            SType::SBigInt => todo!(),
-            SType::SGroupElement => todo!(),
+            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 => todo!(),
             SType::SAvlTree => todo!(),
@@ -50,6 +50,10 @@ impl SType {
     pub fn type_companion(&self) -> Option<STypeCompanion> {
         todo!()
     }
+
+    pub fn new_scoll(elem_type: SType) -> SType {
+        SType::SColl(Box::new(elem_type))
+    }
 }
 
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -85,3 +89,36 @@ pub struct SMethod {
     pub method_id: MethodId,
     pub tpe: SType,
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+
+    fn primitive_type() -> BoxedStrategy<SType> {
+        prop_oneof![
+            Just(SType::SBoolean),
+            Just(SType::SByte),
+            Just(SType::SShort),
+            Just(SType::SInt),
+            Just(SType::SLong),
+            Just(SType::SBigInt),
+            Just(SType::SGroupElement),
+            Just(SType::SSigmaProp),
+        ]
+        .boxed()
+    }
+
+    impl Arbitrary for SType {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                primitive_type(),
+                primitive_type().prop_map(|prim| SType::new_scoll(prim)),
+            ]
+            .boxed()
+        }
+    }
+}


Test patch:
diff --git a/sigma-tree/proptest-regressions/serialization/types.txt b/sigma-tree/proptest-regressions/serialization/types.txt
new file mode 100644
index 000000000..0b2b679d6
--- /dev/null
+++ b/sigma-tree/proptest-regressions/serialization/types.txt
@@ -0,0 +1,7 @@
+# Seeds for failure cases proptest has generated in the past. It is
+# automatically read and these particular cases re-run before any
+# novel cases are generated.
+#
+# It is recommended to check this file in to source control so that
+# everyone who runs the test benefits from these saved cases.
+cc 58bcbf7b861738aff14f4701ef03030282c41fda56f2897d3159dedaa0175908 # shrinks to v = SColl(SByte)