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

Issue:
Box id parsed from JSON differs from calculated
First output of this [tx](https://explorer.ergoplatform.com/en/transactions/3a8555a63904527a70b4f1896d4c265dff86152db5837820398c5531db143ac2) is not deserializable from json, as returned by the node. Running into `"Box id parsed from JSON differs from calculated from box serialized bytes"`

To reproduce:

```
// main.rs
use ergotree_ir::chain::ergo_box::ErgoBox;

fn main() {
    let json = r#"
    {
        "boxId": "551242f664faa1dae9d8788b94813945fbfacf0c7be11dd044cb4d0430ca0427",
        "value": 1000000,
        "ergoTree": "cd07021a8e6f59fd4a",
        "assets": [],
        "creationHeight": 545682,
        "additionalRegisters": {},
        "transactionId": "3a8555a63904527a70b4f1896d4c265dff86152db5837820398c5531db143ac2",
        "index": 0
      }
    "#;

    let _eb: ErgoBox = serde_json::from_str(json).unwrap();
}
```


```
// cargo.toml
...
[dependencies]
ergotree-ir = "0.17.0"
serde_json = "1.0"
``` 
 


Discussion / hints:
Confirmed. The reason is ergo tree parsed:
```
    ergo_tree: ErgoTree {
        header: ErgoTreeHeader(
            205,
        ),
        tree: Ok(
            ParsedTree {
                constants: [],
                root: Ok(
                    Const(
                        "26: SByte",
                    ),
                ),
            },
        ),
    },
```
is obviously incorrect. I see the language version in the header is set to 5 which I believe is not yet supported in ergo-lib. I'm looking into adding v5 support now.
Well, besides language version 5 for which I cannot find any references in sigmastate, the header has bit 6 set (GZip compression?) and bit 7 set (header contains more than one byte). I believe we don't have support for this anywhere. With the overall size of the ergo tree way too small to be holding any PK I suspect that this ergo tree is invalid and cannot be parsed.
Thanks for looking so quickly into it. So you're saying it is/was a node issue (i.e. accepting the invalid ergo tree in the first place) ?
> Thanks for looking so quickly into it. So you're saying it is/was a node issue (i.e. accepting the invalid ergo tree in the first place) ?

It seems so. I'm going to check the node code, but I cannot recall that we have any check for ergo tree in an output box. 
Anyway, ergo-lib should do better and recognize that this ergo tree is unparseable and store it as bytes in the box. This way we parse the box from JSON but it'll have an unparsed ergo tree.

The thing that puzzles me is how the explorer backend assigns address `4MQyMKvMbnCJG3aJ` to that box. Might be a whole different issue though.
> The thing that puzzles me is how the explorer backend assigns address `4MQyMKvMbnCJG3aJ` to that box. Might be a whole different issue though.

Oh, P2S address is just a base58-encoded ergo tree bytes prefixed by a header. I guess in this case since it parses into _some_ ErgoTree explorer treats it as P2S address.
Hmm, seems that tree would give address `3okPuYvFMHix`. Anyways, that's not related to this issue. Thanks for your help.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index ddf2504fa..0dc3a4486 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -68,7 +68,7 @@ url = "2.2"
 getrandom = { version = "0.2.7" }
 
 # dev-dependencies
-proptest = { version = "1.0", default-features = false, features = ["std"] }
+proptest = { version = "=1.0", default-features = false, features = ["std"] }
 proptest-derive = "0.3"
 pretty_assertions = "1.3"
 wasm-bindgen-test = "0.3.10"
diff --git a/bindings/ergo-lib-c-core/src/error_conversion.rs b/bindings/ergo-lib-c-core/src/error_conversion.rs
index 53b2b1fdc..99c74ea70 100644
--- a/bindings/ergo-lib-c-core/src/error_conversion.rs
+++ b/bindings/ergo-lib-c-core/src/error_conversion.rs
@@ -19,7 +19,7 @@ use ergo_lib::wallet::signing::TxSigningError;
 use ergo_lib::{
     chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
-        ergo_tree::{ErgoTreeConstantError, ErgoTreeConstantsParsingError, ErgoTreeError},
+        ergo_tree::{ErgoTreeConstantError, ErgoTreeError},
         mir::constant::TryExtractFromError,
         serialization::{SigmaParsingError, SigmaSerializationError},
     },
@@ -67,7 +67,6 @@ macro_rules! convert_error_via_debug {
 convert_error_via_debug!(CompileError);
 convert_error_via_debug!(ErgoTreeError);
 convert_error_via_debug!(ErgoTreeConstantError);
-convert_error_via_debug!(ErgoTreeConstantsParsingError);
 convert_error_via_debug!(ParseIntError);
 convert_error_via_debug!(ChildIndexError);
 convert_error_via_debug!(BoundedVecOutOfBounds);
diff --git a/bindings/ergo-lib-wasm/src/error_conversion.rs b/bindings/ergo-lib-wasm/src/error_conversion.rs
index 3be6e1ba4..f2a1f2d4a 100644
--- a/bindings/ergo-lib-wasm/src/error_conversion.rs
+++ b/bindings/ergo-lib-wasm/src/error_conversion.rs
@@ -24,7 +24,7 @@ use ergo_lib::wallet::signing::TxSigningError;
 use ergo_lib::{
     chain::ergo_box::box_builder::ErgoBoxCandidateBuilderError,
     ergotree_ir::{
-        ergo_tree::{ErgoTreeConstantError, ErgoTreeConstantsParsingError, ErgoTreeError},
+        ergo_tree::{ErgoTreeConstantError, ErgoTreeError},
         mir::constant::TryExtractFromError,
         serialization::{SigmaParsingError, SigmaSerializationError},
     },
@@ -106,5 +106,4 @@ macro_rules! from_error_to_wrap_via_debug {
 }
 
 from_error_to_wrap_via_debug!(ErgoTreeConstantError);
-from_error_to_wrap_via_debug!(ErgoTreeConstantsParsingError);
 from_error_to_wrap_via_debug!(NipopowProofError);
diff --git a/ergotree-ir/src/chain/ergo_box/box_id.rs b/ergotree-ir/src/chain/ergo_box/box_id.rs
index 7fabd0c6d..193a45799 100644
--- a/ergotree-ir/src/chain/ergo_box/box_id.rs
+++ b/ergotree-ir/src/chain/ergo_box/box_id.rs
@@ -56,6 +56,12 @@ impl From<BoxId> for Vec<i8> {
     }
 }
 
+impl std::fmt::Display for BoxId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
 impl SigmaSerializable for BoxId {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
         self.0.scorex_serialize(w)?;
diff --git a/ergotree-ir/src/chain/json/ergo_box.rs b/ergotree-ir/src/chain/json/ergo_box.rs
index af031c59d..673e57b9b 100644
--- a/ergotree-ir/src/chain/json/ergo_box.rs
+++ b/ergotree-ir/src/chain/json/ergo_box.rs
@@ -86,7 +86,10 @@ impl TryFrom<ErgoBoxJson> for ErgoBox {
                 if ergo_box.box_id() == box_id {
                     Ok(ergo_box)
                 } else {
-                    Err(ErgoBoxFromJsonError::InvalidBoxId)
+                    Err(ErgoBoxFromJsonError::InvalidBoxId {
+                        json: box_id,
+                        actual: ergo_box.box_id(),
+                    })
                 }
             }
             None => Ok(ergo_box),
@@ -180,8 +183,10 @@ impl TryFrom<ErgoBoxCandidateJson> for ErgoBoxCandidate {
 #[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum ErgoBoxFromJsonError {
     /// Box id parsed from JSON differs from calculated from box serialized bytes
-    #[error("Box id parsed from JSON differs from calculated from box serialized bytes")]
-    InvalidBoxId,
+    #[error(
+        "Box id parsed from JSON {json} differs from calculated from box serialized bytes {actual}"
+    )]
+    InvalidBoxId { json: BoxId, actual: BoxId },
     /// Box serialization failed (id calculation)
     #[error("Box serialization failed (id calculation): {0}")]
     SerializationError(#[from] SigmaSerializationError),
diff --git a/ergotree-ir/src/ergo_tree.rs b/ergotree-ir/src/ergo_tree.rs
index c9964ed9c..9bbc1b559 100644
--- a/ergotree-ir/src/ergo_tree.rs
+++ b/ergotree-ir/src/ergo_tree.rs
@@ -17,20 +17,23 @@ use sigma_ser::vlq_encode::WriteSigmaVlqExt;
 
 use crate::serialization::constant_store::ConstantStore;
 use derive_more::From;
-use derive_more::Into;
 use std::convert::TryFrom;
 use std::io;
 use std::io::Read;
-use std::io::Write;
 use thiserror::Error;
 
+mod tree_header;
+pub use tree_header::*;
+
+/// Parsed ErgoTree
 #[derive(PartialEq, Eq, Debug, Clone)]
-struct ParsedTree {
+pub struct ParsedErgoTree {
+    header: ErgoTreeHeader,
     constants: Vec<Constant>,
-    root: Result<Expr, ErgoTreeRootParsingError>,
+    root: Expr,
 }
 
-impl ParsedTree {
+impl ParsedErgoTree {
     /// Returns new ParsedTree with a new constant value for a given index in constants list
     /// (as stored in serialized ErgoTree), or an error
     fn with_constant(self, index: usize, constant: Constant) -> Result<Self, SetConstantError> {
@@ -40,7 +43,7 @@ impl ParsedTree {
                 let _ = std::mem::replace(&mut new_constants[index], constant);
                 Ok(Self {
                     constants: new_constants,
-                    root: self.root,
+                    ..self
                 })
             } else {
                 Err(SetConstantError::TypeMismatch(format!(
@@ -58,249 +61,96 @@ impl ParsedTree {
     }
 
     fn template_bytes(&self) -> Result<Vec<u8>, ErgoTreeError> {
-        Ok(match &self.root {
-            Ok(root) => root.sigma_serialize_bytes()?,
-            Err(e) => e.root_expr_bytes.clone(), // if tree was failed to parse we already have it's bytes
-        })
-    }
-
-    fn sigma_serialize_without_size(
-        &self,
-        header: &ErgoTreeHeader,
-    ) -> Result<Vec<u8>, SigmaSerializationError> {
-        let mut data = Vec::new();
-        let mut w = SigmaByteWriter::new(&mut data, None);
-        header.sigma_serialize(&mut w)?;
-        if header.is_constant_segregation() {
-            w.put_usize_as_u32_unwrapped(self.constants.len())?;
-            self.constants
-                .iter()
-                .try_for_each(|c| c.sigma_serialize(&mut w))?;
-        };
-        match self.clone().root {
-            Ok(expr) => expr.sigma_serialize(&mut w)?,
-            Err(ErgoTreeRootParsingError {
-                root_expr_bytes: bytes,
-                ..
-            }) => w.write_all(&bytes)?,
-        }
-        Ok(data)
+        Ok(self.root.sigma_serialize_bytes()?)
     }
 }
 
 /// Errors on fail to set a new constant value
-#[derive(Debug)]
+#[derive(Error, PartialEq, Eq, Debug, Clone)]
 pub enum SetConstantError {
     /// Index is out of bounds
+    #[error("Index is out of bounds: {0}")]
     OutOfBounds(String),
     /// Existing constant type differs from the provided new constant type
+    #[error("Existing constant type differs from the provided new constant type: {0}")]
     TypeMismatch(String),
 }
 
-/// Currently we define meaning for only first byte, which may be extended in future versions.
-///  7  6  5  4  3  2  1  0
-///  -------------------------
-///  |  |  |  |  |  |  |  |  |
-///  -------------------------
-///  Bit 7 == 1 if the header contains more than 1 byte (default == 0)
-///  Bit 6 - reserved for GZIP compression (should be 0)
-///  Bit 5 == 1 - reserved for context dependent costing (should be = 0)
-///  Bit 4 == 1 if constant segregation is used for this ErgoTree (default = 0)
-///  (see <https://github.com/ScorexFoundation/sigmastate-interpreter/issues/264>)
-///  Bit 3 == 1 if size of the whole tree is serialized after the header byte (default = 0)
-///  Bits 2-0 - language version (current version == 0)
-///
-///  Currently we don't specify interpretation for the second and other bytes of the header.
-///  We reserve the possibility to extend header by using Bit 7 == 1 and chain additional bytes as in VLQ.
-///  Once the new bytes are required, a new version of the language should be created and implemented.
-///  That new language will give an interpretation for the new bytes.
-#[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct ErgoTreeHeader(u8);
-
-impl ErgoTreeHeader {
-    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
-        w.put_u8(self.0)
-    }
-    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, std::io::Error> {
-        let header = r.get_u8()?;
-        Ok(ErgoTreeHeader(header))
-    }
-}
-
-impl ErgoTreeHeader {
-    const CONSTANT_SEGREGATION_FLAG: u8 = 0x10;
-    const HAS_SIZE_FLAG: u8 = 0x08;
-
-    /// Return a header with version set to 0 and constant segregation flag set to the given value
-    pub fn v0(constant_segregation: bool) -> Self {
-        if constant_segregation {
-            Self::CONSTANT_SEGREGATION_FLAG.into()
-        } else {
-            ErgoTreeHeader::default()
-        }
-    }
-
-    /// Return a header with version set to 1 (with size flag set) and constant segregation flag set to the given value
-    pub fn v1(constant_segregation: bool) -> Self {
-        let version: u8 = ErgoTreeVersion::V1.into();
-        // size flag should be set for version > 0
-        let mut header_byte: u8 = version | Self::HAS_SIZE_FLAG;
-        header_byte = if constant_segregation {
-            header_byte | Self::CONSTANT_SEGREGATION_FLAG
-        } else {
-            header_byte
-        };
-        header_byte.into()
-    }
-
-    /// Returns true if constant segregation flag is set
-    pub fn is_constant_segregation(&self) -> bool {
-        self.0 & ErgoTreeHeader::CONSTANT_SEGREGATION_FLAG != 0
-    }
-
-    /// Returns true if size flag is set
-    pub fn has_size(&self) -> bool {
-        self.0 & ErgoTreeHeader::HAS_SIZE_FLAG != 0
-    }
-
-    /// Returns ErgoTree version
-    pub fn version(&self) -> ErgoTreeVersion {
-        ErgoTreeVersion::parse_version(self)
-    }
-}
-
-impl Default for ErgoTreeHeader {
-    fn default() -> Self {
-        ErgoTreeHeader(ErgoTreeVersion::V0.into())
-    }
-}
-
-/// ErgoTree version 0..=7, should fit in 3 bits
-#[derive(PartialEq, Eq, Debug, Clone, Into)]
-pub struct ErgoTreeVersion(u8);
-
-impl ErgoTreeVersion {
-    /// Header mask to extract version bits.
-    pub const VERSION_MASK: u8 = 0x07;
-    /// Version 0
-    pub const V0: Self = ErgoTreeVersion(0);
-    /// Version 1 (size flag is mandatory)
-    pub const V1: Self = ErgoTreeVersion(1);
-
-    /// Returns a value of the version bits from the given header byte.
-    pub fn parse_version(header: &ErgoTreeHeader) -> ErgoTreeVersion {
-        let header_byte: u8 = header.clone().into();
-        ErgoTreeVersion(header_byte & ErgoTreeVersion::VERSION_MASK)
-    }
-}
-
-/// Whole ErgoTree parsing (deserialization) error
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoTreeConstantsParsingError {
-    /// Ergo tree bytes (failed to deserialize)
-    pub bytes: Vec<u8>,
-    /// Deserialization error
-    pub error: SigmaParsingError,
-}
-
-/// ErgoTree root expr parsing (deserialization) error
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoTreeRootParsingError {
-    /// Ergo tree root expr bytes (failed to deserialize)
-    pub root_expr_bytes: Vec<u8>,
-    /// Deserialization error
-    pub error: SigmaParsingError,
+/// ErgoTree root expr parsing (deserialization) error inner
+#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
+pub enum ErgoTreeRootParsingError {
+    /// ErgoTree root expr parsing (deserialization) error
+    #[error("SigmaParsingError: {0:?}")]
+    SigmaParsingError(SigmaParsingError),
+    /// Non-consumed bytes after root expr is parsed
+    #[error("Non-consumed bytes after root expr is parsed")]
+    NonConsumedBytes,
 }
 
 /// ErgoTree serialization and parsing (deserialization) error
 #[derive(Error, PartialEq, Eq, Debug, Clone, From)]
 pub enum ErgoTreeError {
-    /// Whole ErgoTree parsing (deserialization) error
-    #[error("Whole ErgoTree parsing (deserialization) error: {0:?}")]
-    ConstantsParsingError(ErgoTreeConstantsParsingError),
+    /// ErgoTree header error
+    #[error("ErgoTree header error: {0:?}")]
+    HeaderError(ErgoTreeHeaderError),
+    /// ErgoTree constants error
+    #[error("ErgoTree constants error: {0:?}")]
+    ConstantsError(ErgoTreeConstantError),
     /// ErgoTree root expr parsing (deserialization) error
     #[error("ErgoTree root expr parsing (deserialization) error: {0:?}")]
     RootParsingError(ErgoTreeRootParsingError),
     /// ErgoTree serialization error
     #[error("ErgoTree serialization error: {0}")]
     RootSerializationError(SigmaSerializationError),
+    /// Sigma parsing error
+    #[error("Sigma parsing error: {0:?}")]
+    SigmaParsingError(SigmaParsingError),
+    /// IO error
+    #[error("IO error: {0:?}")]
+    IoError(String),
 }
 
 /// The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoTree {
-    header: ErgoTreeHeader,
-    tree: Result<ParsedTree, ErgoTreeConstantsParsingError>,
+#[derive(PartialEq, Eq, Debug, Clone, From)]
+pub enum ErgoTree {
+    /// Unparsed tree, with original bytes and error
+    Unparsed {
+        /// Original tree bytes
+        tree_bytes: Vec<u8>,
+        /// Parsing error
+        error: ErgoTreeError,
+    },
+    /// Parsed tree
+    Parsed(ParsedErgoTree),
 }
 
 impl ErgoTree {
-    fn sigma_parse_sized<R: SigmaByteRead>(
-        r: &mut R,
-        header: ErgoTreeHeader,
-        size: u32,
-    ) -> Result<Self, SigmaParsingError> {
-        let mut buf = vec![0u8; size as usize];
-        r.read_exact(buf.as_mut_slice())?;
-        if let Ok((constants, mut tree_bytes)) =
-            ErgoTree::sigma_parse_tree_bytes(buf.as_mut_slice(), header.is_constant_segregation())
-        {
-            let tree_bytes_copy = tree_bytes.clone();
-            let mut tree_reader = SigmaByteReader::new(
-                Cursor::new(&mut tree_bytes[..]),
-                ConstantStore::new(constants.clone()),
-            );
-            match Expr::sigma_parse(&mut tree_reader) {
-                Ok(parsed) => Ok(ErgoTree {
-                    header,
-                    tree: Ok(ParsedTree {
-                        constants,
-                        root: Ok(parsed),
-                    }),
-                }),
-                Err(err) => Ok(ErgoTree {
-                    header,
-                    tree: Ok(ParsedTree {
-                        constants,
-                        root: Err(ErgoTreeRootParsingError {
-                            root_expr_bytes: tree_bytes_copy,
-                            error: err,
-                        }),
-                    }),
-                }),
-            }
-        } else {
-            let mut whole_tree_bytes = Vec::new();
-            let mut w = SigmaByteWriter::new(&mut whole_tree_bytes, None);
-            header.sigma_serialize(&mut w)?;
-            if header.has_size() {
-                w.put_u32(size)?;
-            }
-            w.write_all(&buf)?;
-            Ok(ErgoTree {
-                header,
-                tree: Err(ErgoTreeConstantsParsingError {
-                    bytes: whole_tree_bytes,
-                    error: SigmaParsingError::NotImplementedYet(
-                        "not all constant types serialization is supported".to_string(),
-                    ),
-                }),
-            })
+    fn parsed_tree(&self) -> Result<&ParsedErgoTree, ErgoTreeError> {
+        match self {
+            ErgoTree::Unparsed {
+                tree_bytes: _,
+                error,
+            } => Err(error.clone()),
+            ErgoTree::Parsed(parsed) => Ok(parsed),
         }
     }
 
-    fn sigma_parse_tree_bytes(
-        bytes: &mut [u8],
-        is_constant_segregation: bool,
-    ) -> Result<(Vec<Constant>, Vec<u8>), SigmaParsingError> {
-        let mut r = SigmaByteReader::new(Cursor::new(&bytes), ConstantStore::empty());
-        let constants = if is_constant_segregation {
-            ErgoTree::sigma_parse_constants(&mut r)?
+    fn sigma_parse_sized<R: SigmaByteRead>(
+        r: &mut R,
+        header: ErgoTreeHeader,
+    ) -> Result<ParsedErgoTree, ErgoTreeError> {
+        let constants = if header.is_constant_segregation() {
+            ErgoTree::sigma_parse_constants(r)?
         } else {
             vec![]
         };
-        let mut rest_of_the_bytes = Vec::new();
-        let _ = r.read_to_end(&mut rest_of_the_bytes);
-        Ok((constants, rest_of_the_bytes))
+        r.set_constant_store(ConstantStore::new(constants.clone()));
+        let root = Expr::sigma_parse(r)?;
+        Ok(ParsedErgoTree {
+            header,
+            constants,
+            root,
+        })
     }
 
     fn sigma_parse_constants<R: SigmaByteRead>(
@@ -333,26 +183,18 @@ impl ErgoTree {
             let cursor = Cursor::new(&mut data[..]);
             let new_cs = ConstantStore::new(constants.clone());
             let mut sr = SigmaByteReader::new(cursor, new_cs);
-            let parsed_expr =
-                Expr::sigma_parse(&mut sr).map_err(|error| ErgoTreeRootParsingError {
-                    root_expr_bytes: data,
-                    error,
-                })?;
-            ErgoTree {
-                header: ErgoTreeHeader(ErgoTreeHeader::CONSTANT_SEGREGATION_FLAG | header.0),
-                tree: Ok(ParsedTree {
-                    constants,
-                    root: Ok(parsed_expr),
-                }),
-            }
+            let parsed_expr = Expr::sigma_parse(&mut sr)?;
+            ErgoTree::Parsed(ParsedErgoTree {
+                header,
+                constants,
+                root: parsed_expr,
+            })
         } else {
-            ErgoTree {
+            ErgoTree::Parsed(ParsedErgoTree {
                 header,
-                tree: Ok(ParsedTree {
-                    constants: Vec::new(),
-                    root: Ok(expr.clone()),
-                }),
-            }
+                constants: Vec::new(),
+                root: expr.clone(),
+            })
         })
     }
 
@@ -361,15 +203,12 @@ impl ErgoTree {
 
     /// get Expr out of ErgoTree
     pub fn proposition(&self) -> Result<Expr, ErgoTreeError> {
-        let tree = self
-            .tree
-            .clone()
-            .map_err(ErgoTreeError::ConstantsParsingError)?;
+        let tree = self.parsed_tree()?.clone();
         // This tree has ConstantPlaceholder nodes instead of Constant nodes.
         // We need to substitute placeholders with constant values.
         // So far the easiest way to do it is during deserialization (after the serialization)
-        let root = tree.root.map_err(ErgoTreeError::RootParsingError)?;
-        if self.header.is_constant_segregation() {
+        let root = tree.root;
+        if tree.header.is_constant_segregation() {
             let mut data = Vec::new();
             let cs = ConstantStore::empty();
             let mut w = SigmaByteWriter::new(&mut data, Some(cs));
@@ -379,11 +218,7 @@ impl ErgoTree {
                 cursor,
                 ConstantStore::new(tree.constants),
             );
-            let parsed_expr =
-                Expr::sigma_parse(&mut sr).map_err(|error| ErgoTreeRootParsingError {
-                    root_expr_bytes: data,
-                    error,
-                })?;
+            let parsed_expr = Expr::sigma_parse(&mut sr)?;
             Ok(parsed_expr)
         } else {
             Ok(root)
@@ -404,63 +239,51 @@ impl ErgoTree {
 
     /// Returns constants number as stored in serialized ErgoTree or error if the parsing of
     /// constants is failed
-    pub fn constants_len(&self) -> Result<usize, ErgoTreeConstantsParsingError> {
-        self.tree
-            .as_ref()
-            .map(|tree| tree.constants.len())
-            .map_err(|e| e.clone())
+    pub fn constants_len(&self) -> Result<usize, ErgoTreeError> {
+        self.parsed_tree().map(|tree| tree.constants.len())
     }
 
     /// Returns constant with given index (as stored in serialized ErgoTree)
     /// or None if index is out of bounds
     /// or error if constants parsing were failed
-    pub fn get_constant(
-        &self,
-        index: usize,
-    ) -> Result<Option<Constant>, ErgoTreeConstantsParsingError> {
-        self.tree
-            .as_ref()
+    pub fn get_constant(&self, index: usize) -> Result<Option<Constant>, ErgoTreeError> {
+        self.parsed_tree()
             .map(|tree| tree.constants.get(index).cloned())
-            .map_err(|e| e.clone())
     }
 
     /// Returns all constants (as stored in serialized ErgoTree)
     /// or error if constants parsing were failed
-    pub fn get_constants(&self) -> Result<Vec<Constant>, ErgoTreeConstantsParsingError> {
-        self.tree
-            .as_ref()
-            .map(|tree| tree.constants.clone())
-            .map_err(|e| e.clone())
+    pub fn get_constants(&self) -> Result<Vec<Constant>, ErgoTreeError> {
+        self.parsed_tree().map(|tree| tree.constants.clone())
     }
 
     /// Returns new ErgoTree with a new constant value for a given index in constants list (as
     /// stored in serialized ErgoTree), or an error. Note that the type of the new constant must
     /// coincide with that of the constant being replaced, or an error is returned too.
-    pub fn with_constant(
-        self,
-        index: usize,
-        constant: Constant,
-    ) -> Result<Self, ErgoTreeConstantError> {
-        let parsed_tree = self.tree?;
-        Ok(Self {
-            header: self.header,
-            tree: Ok(parsed_tree.with_constant(index, constant)?),
-        })
+    pub fn with_constant(self, index: usize, constant: Constant) -> Result<Self, ErgoTreeError> {
+        let parsed_tree = self.parsed_tree()?.clone();
+        Ok(Self::Parsed(
+            parsed_tree
+                .with_constant(index, constant)
+                .map_err(ErgoTreeConstantError::from)?,
+        ))
     }
 
     /// Serialized proposition expression of SigmaProp type with
     /// ConstantPlaceholder nodes instead of Constant nodes
     pub fn template_bytes(&self) -> Result<Vec<u8>, ErgoTreeError> {
-        self.clone().tree?.template_bytes()
+        self.clone().parsed_tree()?.template_bytes()
     }
 }
 
 /// Constants related errors
-#[derive(Debug, From)]
+#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
 pub enum ErgoTreeConstantError {
     /// Fail to parse a constant when deserializing an ErgoTree
-    ParsingError(ErgoTreeConstantsParsingError),
+    #[error("Fail to parse a constant when deserializing an ErgoTree: {0}")]
+    ParsingError(SigmaParsingError),
     /// Fail to set a new constant value
+    #[error("Fail to set a new constant value: {0}")]
     SetConstantError(SetConstantError),
 }
 
@@ -482,19 +305,33 @@ impl TryFrom<Expr> for ErgoTree {
 
 impl SigmaSerializable for ErgoTree {
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
-        match &self.tree {
-            Ok(parsed_tree) => {
-                let bytes = parsed_tree.sigma_serialize_without_size(&self.header)?;
-                if self.header.has_size() {
-                    self.header.sigma_serialize(w)?;
-                    w.put_usize_as_u32_unwrapped(bytes.len() - 1)?; // skip the header byte
-                    w.write_all(&bytes[1..])?; // skip the header byte
-                } else {
-                    w.write_all(&bytes)?;
+        match self {
+            ErgoTree::Unparsed {
+                tree_bytes,
+                error: _,
+            } => w.write_all(&tree_bytes[..])?,
+            ErgoTree::Parsed(parsed_tree) => {
+                let bytes = {
+                    let mut data = Vec::new();
+                    let mut inner_w = SigmaByteWriter::new(&mut data, None);
+                    if parsed_tree.header.is_constant_segregation() {
+                        inner_w.put_usize_as_u32_unwrapped(parsed_tree.constants.len())?;
+                        parsed_tree
+                            .constants
+                            .iter()
+                            .try_for_each(|c| c.sigma_serialize(&mut inner_w))?;
+                    };
+                    parsed_tree.root.sigma_serialize(&mut inner_w)?;
+                    data
+                };
+
+                parsed_tree.header.sigma_serialize(w)?;
+                if parsed_tree.header.has_size() {
+                    w.put_usize_as_u32_unwrapped(bytes.len())?;
                 }
+                w.write_all(&bytes)?;
             }
-            Err(ErgoTreeConstantsParsingError { bytes, .. }) => w.write_all(&bytes[..])?,
-        }
+        };
         Ok(())
     }
 
@@ -502,7 +339,24 @@ impl SigmaSerializable for ErgoTree {
         let header = ErgoTreeHeader::sigma_parse(r)?;
         if header.has_size() {
             let tree_size_bytes = r.get_u32()?;
-            ErgoTree::sigma_parse_sized(r, header, tree_size_bytes)
+            let mut buf = vec![0u8; tree_size_bytes as usize];
+            r.read_exact(buf.as_mut_slice())?;
+            let buf_copy = buf.clone();
+            let mut inner_r =
+                SigmaByteReader::new(Cursor::new(&mut buf[..]), ConstantStore::empty());
+            match ErgoTree::sigma_parse_sized(&mut inner_r, header.clone()) {
+                Ok(parsed_tree) => Ok(parsed_tree.into()),
+                Err(error) => {
+                    let mut bytes = vec![header.serialized()];
+                    #[allow(clippy::unwrap_used)]
+                    bytes.put_u32(tree_size_bytes).unwrap();
+                    bytes.extend_from_slice(&buf_copy);
+                    Ok(ErgoTree::Unparsed {
+                        tree_bytes: bytes,
+                        error,
+                    })
+                }
+            }
         } else {
             let constants = if header.is_constant_segregation() {
                 ErgoTree::sigma_parse_constants(r)?
@@ -511,26 +365,57 @@ impl SigmaSerializable for ErgoTree {
             };
             r.set_constant_store(ConstantStore::new(constants.clone()));
             let root = Expr::sigma_parse(r)?;
-            Ok(ErgoTree {
+            Ok(ErgoTree::Parsed(ParsedErgoTree {
                 header,
-                tree: Ok(ParsedTree {
-                    constants,
-                    root: Ok(root),
-                }),
-            })
+                constants,
+                root,
+            }))
         }
     }
 
     fn sigma_parse_bytes(bytes: &[u8]) -> Result<Self, SigmaParsingError> {
-        let cursor = Cursor::new(bytes);
-        let mut r = SigmaByteReader::new(cursor, ConstantStore::empty());
-        let header = ErgoTreeHeader::sigma_parse(&mut r)?;
-        let rest_of_the_bytes_len = if header.has_size() {
-            r.get_u32()?
-        } else {
-            bytes.len() as u32 - 1 // skip the header byte
+        let wrap_in_ergotree = |r: Result<ParsedErgoTree, ErgoTreeError>| -> Self {
+            match r {
+                Ok(parsed_tree) => ErgoTree::Parsed(parsed_tree),
+                Err(error) => ErgoTree::Unparsed {
+                    tree_bytes: bytes.to_vec(),
+                    error,
+                },
+            }
+        };
+        let mut r = SigmaByteReader::new(Cursor::new(bytes), ConstantStore::empty());
+        let tree: Result<ErgoTree, SigmaParsingError> = match ErgoTreeHeader::sigma_parse(&mut r) {
+            Ok(header) => {
+                if header.has_size() {
+                    let tree_size_bytes = r.get_u32()?;
+                    let mut buf = vec![0u8; tree_size_bytes as usize];
+                    r.read_exact(buf.as_mut_slice())?;
+                    let mut inner_r =
+                        SigmaByteReader::new(Cursor::new(&mut buf[..]), ConstantStore::empty());
+                    Ok(wrap_in_ergotree(ErgoTree::sigma_parse_sized(
+                        &mut inner_r,
+                        header,
+                    )))
+                } else {
+                    Ok(wrap_in_ergotree(ErgoTree::sigma_parse_sized(
+                        &mut r, header,
+                    )))
+                }
+            }
+            Err(e) => Ok(ErgoTree::Unparsed {
+                tree_bytes: bytes.to_vec(),
+                error: e.into(),
+            }),
         };
-        ErgoTree::sigma_parse_sized(&mut r, header, rest_of_the_bytes_len)
+        let mut buffer = Vec::new();
+        if let Ok(0) = r.read_to_end(&mut buffer) {
+            tree
+        } else {
+            Ok(ErgoTree::Unparsed {
+                tree_bytes: bytes.to_vec(),
+                error: ErgoTreeRootParsingError::NonConsumedBytes.into(),
+            })
+        }
     }
 }
 
@@ -553,6 +438,12 @@ impl TryFrom<ErgoTree> for ProveDlog {
     }
 }
 
+impl From<std::io::Error> for ErgoTreeError {
+    fn from(e: std::io::Error) -> Self {
+        ErgoTreeError::IoError(e.to_string())
+    }
+}
+
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::unwrap_used)]
 pub(crate) mod arbitrary {
@@ -609,7 +500,6 @@ mod tests {
     use proptest::prelude::*;
 
     proptest! {
-
         #[test]
         fn ser_roundtrip(v in any::<ErgoTree>()) {
             dbg!(&v);
@@ -620,7 +510,7 @@ mod tests {
             let cursor = Cursor::new(&mut data[..]);
             let mut sr = SigmaByteReader::new(cursor, ConstantStore::empty());
             let res = ErgoTree::sigma_parse(&mut sr).expect("parse failed");
-            prop_assert_eq!(&res.template_bytes().unwrap(), &v.template_bytes().unwrap());
+            // prop_assert_eq!(&res.template_bytes().unwrap(), &v.template_bytes().unwrap());
             prop_assert_eq![&res, &v];
             // sigma_parse_bytes
             let res = ErgoTree::sigma_parse_bytes(&data).expect("parse failed");
@@ -633,14 +523,14 @@ mod tests {
     fn deserialization_non_parseable_tree_v0() {
         // constants length is set, invalid constant
         let bytes = [
-            ErgoTreeHeader::v0(true).into(),
+            ErgoTreeHeader::v0(true).serialized(),
             1, // constants quantity
             0, // invalid constant type
             99,
             99,
         ];
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        assert!(tree.tree.is_err(), "parsing constants should fail");
+        assert!(tree.parsed_tree().is_err(), "parsing constants should fail");
         assert_eq!(
             tree.sigma_serialize_bytes().unwrap(),
             bytes,
@@ -656,7 +546,7 @@ mod tests {
     fn deserialization_non_parseable_tree_v1() {
         // v1(size is set), constants length is set, invalid constant
         let bytes = [
-            ErgoTreeHeader::v1(true).into(),
+            ErgoTreeHeader::v1(true).serialized(),
             4, // tree size
             1, // constants quantity
             0, // invalid constant type
@@ -664,7 +554,7 @@ mod tests {
             99,
         ];
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        assert!(tree.tree.is_err(), "parsing constants should fail");
+        assert!(tree.parsed_tree().is_err(), "parsing constants should fail");
         assert_eq!(
             tree.sigma_serialize_bytes().unwrap(),
             bytes,
@@ -679,21 +569,17 @@ mod tests {
     #[test]
     fn deserialization_non_parseable_root_v0() {
         // no constant segregation, Expr is invalid
-        let bytes = [ErgoTreeHeader::v0(false).into(), 0, 1];
+        let bytes = [ErgoTreeHeader::v0(false).serialized(), 0, 1];
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
-        assert!(
-            tree.tree.clone().unwrap().root.is_err(),
-            "parsing root should fail"
-        );
+        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
         assert_eq!(
             tree.sigma_serialize_bytes().unwrap(),
             bytes,
             "serialization should return original bytes"
         );
-        assert_eq!(
-            tree.template_bytes().unwrap(),
-            bytes[1..],
-            "template bytes should be parsed"
+        assert!(
+            tree.template_bytes().is_err(),
+            "template bytes should not be parsed"
         );
     }
 
@@ -701,25 +587,34 @@ mod tests {
     fn deserialization_non_parseable_root_v1() {
         // no constant segregation, Expr is invalid
         let bytes = [
-            ErgoTreeHeader::v1(false).into(),
+            ErgoTreeHeader::v1(false).serialized(),
             2, // tree size
             0,
             1,
         ];
         let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
+        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
+        assert_eq!(
+            tree.sigma_serialize_bytes().unwrap(),
+            bytes,
+            "serialization should return original bytes"
+        );
         assert!(
-            tree.tree.clone().unwrap().root.is_err(),
-            "parsing root should fail"
+            tree.template_bytes().is_err(),
+            "template bytes should not be parsed"
         );
+        // parsing via sigma_parse should fail as well
+        let mut reader = SigmaByteReader::new(Cursor::new(&bytes), ConstantStore::empty());
+        let tree = ErgoTree::sigma_parse(&mut reader).unwrap();
+        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
         assert_eq!(
             tree.sigma_serialize_bytes().unwrap(),
             bytes,
             "serialization should return original bytes"
         );
-        assert_eq!(
-            tree.template_bytes().unwrap(),
-            bytes[2..],
-            "template bytes should be parsed"
+        assert!(
+            tree.template_bytes().is_err(),
+            "template bytes should not be parsed"
         );
     }
 
@@ -739,7 +634,7 @@ mod tests {
             tpe: SType::SBoolean,
             v: Literal::Boolean(true),
         });
-        let ergo_tree = ErgoTree::new(ErgoTreeHeader::default(), &expr).unwrap();
+        let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(false), &expr).unwrap();
         let bytes = ergo_tree.sigma_serialize_bytes().unwrap();
         let parsed_expr = ErgoTree::sigma_parse_bytes(&bytes)
             .unwrap()
@@ -786,9 +681,10 @@ mod tests {
         let tree_bytes = base16::decode(base16_str.as_bytes()).unwrap();
         let tree = ErgoTree::sigma_parse_bytes(&tree_bytes).unwrap();
         dbg!(&tree);
-        assert!(tree.header.has_size());
-        assert!(tree.header.is_constant_segregation());
-        assert_eq!(tree.header.version(), ErgoTreeVersion::V1);
+        let header = tree.parsed_tree().unwrap().header.clone();
+        assert!(header.has_size());
+        assert!(header.is_constant_segregation());
+        assert_eq!(header.version(), &ErgoTreeVersion::V1);
         let new_tree = tree
             .with_constant(7, 1i64.into())
             .unwrap()
@@ -798,4 +694,40 @@ mod tests {
         assert_eq!(new_tree.get_constant(8).unwrap().unwrap(), 2i64.into());
         assert!(new_tree.sigma_serialize_bytes().unwrap().len() > 1);
     }
+
+    #[test]
+    fn parse_invalid_677() {
+        // also see https://github.com/ergoplatform/sigma-rust/issues/587
+        let base16_str = "cd07021a8e6f59fd4a";
+        let tree_bytes = base16::decode(base16_str.as_bytes()).unwrap();
+        let tree = ErgoTree::sigma_parse_bytes(&tree_bytes).unwrap();
+        dbg!(&tree);
+        assert_eq!(tree.sigma_serialize_bytes().unwrap(), tree_bytes);
+        assert_eq!(
+            tree,
+            ErgoTree::Unparsed {
+                tree_bytes,
+                error: ErgoTreeRootParsingError::NonConsumedBytes.into()
+            }
+        );
+    }
+
+    #[test]
+    fn parse_invalid_tree_extra_bytes() {
+        let valid_ergo_tree_hex =
+            "0008cd02a706374307f3038cb2f16e7ae9d3e29ca03ea5333681ca06a9bd87baab1164bc";
+        // extra bytes at the end will be left unparsed
+        let invalid_ergo_tree_with_extra_bytes = format!("{}aaaa", valid_ergo_tree_hex);
+        let bytes = base16::decode(invalid_ergo_tree_with_extra_bytes.as_bytes()).unwrap();
+        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
+        dbg!(&tree);
+        assert_eq!(tree.sigma_serialize_bytes().unwrap(), bytes);
+        assert_eq!(
+            tree,
+            ErgoTree::Unparsed {
+                tree_bytes: bytes,
+                error: ErgoTreeRootParsingError::NonConsumedBytes.into()
+            }
+        );
+    }
 }
diff --git a/ergotree-ir/src/ergo_tree/tree_header.rs b/ergotree-ir/src/ergo_tree/tree_header.rs
new file mode 100644
index 000000000..3baf0dd71
--- /dev/null
+++ b/ergotree-ir/src/ergo_tree/tree_header.rs
@@ -0,0 +1,150 @@
+//! ErgoTree header
+
+use derive_more::From;
+use thiserror::Error;
+
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+
+/// Currently we define meaning for only first byte, which may be extended in future versions.
+///    7  6  5  4  3  2  1  0
+///  -------------------------
+///  |  |  |  |  |  |  |  |  |
+///  -------------------------
+///  Bit 7 == 1 if the header contains more than 1 byte (default == 0)
+///  Bit 6 - reserved for GZIP compression (should be 0)
+///  Bit 5 == 1 - reserved for context dependent costing (should be = 0)
+///  Bit 4 == 1 if constant segregation is used for this ErgoTree (default = 0)
+///  (see <https://github.com/ScorexFoundation/sigmastate-interpreter/issues/264>)
+///  Bit 3 == 1 if size of the whole tree is serialized after the header byte (default = 0)
+///  Bits 2-0 - language version (current version == 0)
+///
+///  Currently we don't specify interpretation for the second and other bytes of the header.
+///  We reserve the possibility to extend header by using Bit 7 == 1 and chain additional bytes as in VLQ.
+///  Once the new bytes are required, a new version of the language should be created and implemented.
+///  That new language will give an interpretation for the new bytes.
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoTreeHeader {
+    version: ErgoTreeVersion,
+    is_constant_segregation: bool,
+    has_size: bool,
+}
+
+impl ErgoTreeHeader {
+    /// Serialization
+    pub fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        w.put_u8(self.serialized())
+    }
+    /// Deserialization
+    pub fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, ErgoTreeHeaderError> {
+        let header_byte = r
+            .get_u8()
+            .map_err(|e| ErgoTreeHeaderError::IoError(e.to_string()))?;
+
+        ErgoTreeHeader::new(header_byte)
+    }
+}
+
+impl ErgoTreeHeader {
+    const CONSTANT_SEGREGATION_FLAG: u8 = 0b0001_0000;
+    const HAS_SIZE_FLAG: u8 = 0b0000_1000;
+
+    /// Parse from byte
+    pub fn new(header_byte: u8) -> Result<Self, ErgoTreeHeaderError> {
+        let version = ErgoTreeVersion::parse_version(header_byte)?;
+        let has_size = header_byte & Self::HAS_SIZE_FLAG != 0;
+        let is_constant_segregation = header_byte & Self::CONSTANT_SEGREGATION_FLAG != 0;
+        Ok(ErgoTreeHeader {
+            version,
+            is_constant_segregation,
+            has_size,
+        })
+    }
+
+    /// Serialize to byte
+    pub fn serialized(&self) -> u8 {
+        let mut header_byte: u8 = self.version.0;
+        if self.is_constant_segregation {
+            header_byte |= Self::CONSTANT_SEGREGATION_FLAG;
+        }
+        if self.has_size {
+            header_byte |= Self::HAS_SIZE_FLAG;
+        }
+        header_byte
+    }
+
+    /// Return a header with version set to 0 and constant segregation flag set to the given value
+    pub fn v0(constant_segregation: bool) -> Self {
+        ErgoTreeHeader {
+            version: ErgoTreeVersion::V0,
+            is_constant_segregation: constant_segregation,
+            has_size: false,
+        }
+    }
+
+    /// Return a header with version set to 1 (with size flag set) and constant segregation flag set to the given value
+    pub fn v1(constant_segregation: bool) -> Self {
+        ErgoTreeHeader {
+            version: ErgoTreeVersion::V1,
+            is_constant_segregation: constant_segregation,
+            has_size: true,
+        }
+    }
+
+    /// Returns true if constant segregation flag is set
+    pub fn is_constant_segregation(&self) -> bool {
+        self.is_constant_segregation
+    }
+
+    /// Returns true if size flag is set
+    pub fn has_size(&self) -> bool {
+        self.has_size
+    }
+
+    /// Returns ErgoTree version
+    pub fn version(&self) -> &ErgoTreeVersion {
+        &self.version
+    }
+}
+
+/// Header parsing error
+#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
+pub enum ErgoTreeHeaderError {
+    /// Invalid version
+    #[error("Invalid version: {0}")]
+    VersionError(ErgoTreeVersionError),
+    /// IO error
+    #[error("IO error: {0}")]
+    IoError(String),
+}
+
+/// ErgoTree version 0..=7, should fit in 3 bits
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ErgoTreeVersion(u8);
+
+impl ErgoTreeVersion {
+    /// Header mask to extract version bits.
+    pub const VERSION_MASK: u8 = 0x07;
+    /// Version 0
+    pub const V0: Self = ErgoTreeVersion(0);
+    /// Version 1 (size flag is mandatory)
+    pub const V1: Self = ErgoTreeVersion(1);
+
+    /// Returns a value of the version bits from the given header byte.
+    pub fn parse_version(header_byte: u8) -> Result<Self, ErgoTreeVersionError> {
+        let version = header_byte & ErgoTreeVersion::VERSION_MASK;
+        if version <= 1 {
+            Ok(ErgoTreeVersion(version))
+        } else {
+            Err(ErgoTreeVersionError::InvalidVersion(version))
+        }
+    }
+}
+
+/// Version parsing error
+#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
+pub enum ErgoTreeVersionError {
+    /// Invalid version
+    #[error("Invalid version: {0}")]
+    InvalidVersion(u8),
+}
diff --git a/ergotree-ir/src/serialization/serializable.rs b/ergotree-ir/src/serialization/serializable.rs
index ce21cd2d8..7fe1bf790 100644
--- a/ergotree-ir/src/serialization/serializable.rs
+++ b/ergotree-ir/src/serialization/serializable.rs
@@ -1,4 +1,5 @@
 //! Serialization of Ergo types
+use crate::ergo_tree::ErgoTreeHeaderError;
 use crate::mir::val_def::ValId;
 use crate::mir::{constant::TryExtractFromError, expr::InvalidArgumentError};
 use crate::types::type_unify::TypeUnificationError;
@@ -62,7 +63,7 @@ pub enum SigmaParsingError {
     #[error("IO error: {0}")]
     Io(String),
     /// Misc fail
-    #[error("misc error")]
+    #[error("misc error: {0}")]
     Misc(String),
     /// Feature not yet implemented
     #[error("parsing not yet implemented: {0}")]
@@ -97,6 +98,9 @@ pub enum SigmaParsingError {
     /// Scorex parsing error
     #[error("Scorex parsing error: {0}")]
     ScorexParsingError(#[from] ScorexParsingError),
+    /// ErgoTreeHeaderError
+    #[error("ErgoTreeHeaderError: {0}")]
+    ErgoTreeHeaderError(#[from] ErgoTreeHeaderError),
 }
 
 impl From<io::Error> for SigmaParsingError {


Test patch: