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

Issue:
Converts chain::*Address types to be an enum



Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-wallet-c/src/lib.rs b/bindings/ergo-wallet-c/src/lib.rs
index c7bc7ea57..c0ef5e6a5 100644
--- a/bindings/ergo-wallet-c/src/lib.rs
+++ b/bindings/ergo-wallet-c/src/lib.rs
@@ -59,7 +59,7 @@ pub extern "C" fn ergo_wallet_ergo_box_candidate_delete(
     todo!()
 }
 
-pub struct Address(Box<dyn chain::Address>);
+pub struct Address(chain::Address);
 pub type AddressPtr = *mut Address;
 
 #[no_mangle]
diff --git a/bindings/ergo-wallet-wasm/src/lib.rs b/bindings/ergo-wallet-wasm/src/lib.rs
index 5a17b6ab0..e2be4d241 100644
--- a/bindings/ergo-wallet-wasm/src/lib.rs
+++ b/bindings/ergo-wallet-wasm/src/lib.rs
@@ -61,7 +61,7 @@ use wasm_bindgen::prelude::*;
  *
  */
 #[wasm_bindgen]
-pub struct Address(Box<dyn chain::Address>);
+pub struct Address(chain::Address);
 
 #[wasm_bindgen]
 impl Address {
@@ -69,7 +69,7 @@ impl Address {
     pub fn from_testnet_str(s: &str) -> Result<Address, JsValue> {
         chain::AddressEncoder::new(chain::NetworkPrefix::Testnet)
             .parse_address_from_str(s)
-            .map(|a| Address(a))
+            .map(Address)
             .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 }
@@ -172,8 +172,10 @@ pub struct Contract(chain::Contract);
 #[wasm_bindgen]
 impl Contract {
     /// create new contract that allow spending of the guarded box by a given recipient ([`Address`])
-    pub fn pay_to_address(recipient: Address) -> Contract {
-        Contract(chain::Contract::pay_to_address(&*recipient.0))
+    pub fn pay_to_address(recipient: Address) -> Result<Contract, JsValue> {
+        chain::Contract::pay_to_address(recipient.0)
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+            .map(Contract)
     }
 }
 
diff --git a/sigma-tree/Cargo.toml b/sigma-tree/Cargo.toml
index f38c86ac2..4ac0e79b4 100644
--- a/sigma-tree/Cargo.toml
+++ b/sigma-tree/Cargo.toml
@@ -17,6 +17,7 @@ elliptic-curve = {version = "0.4.0", features = ["getrandom"]}
 serde = { version = "1.0", features = ["derive"], optional = true }
 serde_json = "1.0"
 thiserror = "1"
+bs58 = "0.3.1"
 
 [features]
 default = []
diff --git a/sigma-tree/src/chain/address.rs b/sigma-tree/src/chain/address.rs
index 560b3334f..db917d424 100644
--- a/sigma-tree/src/chain/address.rs
+++ b/sigma-tree/src/chain/address.rs
@@ -1,21 +1,16 @@
+use super::digest32;
 use crate::{
     ast::{Constant, Expr},
     ecpoint::EcPoint,
     sigma_protocol::{ProveDlog, SigmaBoolean, SigmaProofOfKnowledgeTree, SigmaProp},
     ErgoTree,
 };
-use std::fmt;
-use std::rc::Rc;
-
-/// Address types
-pub enum AddressTypePrefix {
-    /// 0x01 - Pay-to-PublicKey(P2PK) address
-    P2PKAddress = 1,
-    /// 0x02 - Pay-to-Script-Hash(P2SH)
-    Pay2SHAddress = 2,
-    /// 0x03 - Pay-to-Script(P2S)
-    Pay2SAddress = 3,
-}
+use sigma_ser::serializer::{SerializationError, SigmaSerializable};
+use std::{
+    convert::{TryFrom, TryInto},
+    rc::Rc,
+};
+use thiserror::Error;
 
 /**
  * An address is a short string corresponding to some script used to protect a box. Unlike (string-encoded) binary
@@ -60,41 +55,70 @@ pub enum AddressTypePrefix {
  * address = prefix byte ++ content bytes ++ checksum
  *
  */
-pub trait Address {
-    /// address type (P2PK, P2SH or P2S)
-    fn address_type_prefix(&self) -> AddressTypePrefix;
-
-    /// script encoded in the address
-    fn script(&self) -> ErgoTree;
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub enum Address {
+    /// serialized (compressed) public key
+    P2PK(ProveDlog),
+    /// serialized script
+    P2S(Vec<u8>),
+    // P2SH([u8; 24]),
 }
 
-/// P2PK - serialized (compressed) public key
-pub struct P2PKAddress {
-    /// pubkey (encoded in the address)
-    pubkey: ProveDlog,
-}
+impl Address {
+    /// address type prefix (for encoding)
+    pub fn address_type_prefix(&self) -> AddressTypePrefix {
+        match self {
+            Address::P2PK(_) => AddressTypePrefix::P2PK,
+            Address::P2S(_) => AddressTypePrefix::Pay2S,
+            //Address::P2SH(_) => AddressTypePrefix::P2SH,
+        }
+    }
+
+    /// byte array
+    pub fn content_bytes(&self) -> Vec<u8> {
+        match self {
+            Address::P2PK(prove_dlog) => prove_dlog.h.sigma_serialise_bytes(),
+            Address::P2S(bytes) => bytes.clone(),
+        }
+    }
 
-impl P2PKAddress {
-    /// create from ProveDlog
-    pub fn new(pubkey: ProveDlog) -> P2PKAddress {
-        P2PKAddress { pubkey }
+    /// script encoded in the address
+    pub fn script(&self) -> Result<ErgoTree, SerializationError> {
+        match self {
+            Address::P2PK(prove_dlog) => Ok(ErgoTree::from(Rc::new(Expr::Const(
+                Constant::sigma_prop(SigmaProp::new(SigmaBoolean::ProofOfKnowledge(
+                    SigmaProofOfKnowledgeTree::ProveDlog(prove_dlog.clone()),
+                ))),
+            )))),
+            Address::P2S(bytes) => ErgoTree::sigma_parse_bytes(bytes.to_vec()),
+        }
     }
 }
 
-impl Address for P2PKAddress {
-    fn address_type_prefix(&self) -> AddressTypePrefix {
-        AddressTypePrefix::P2PKAddress
-    }
-    fn script(&self) -> ErgoTree {
-        ErgoTree::from(Rc::new(Expr::Const(Constant::sigma_prop(SigmaProp::new(
-            SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(
-                self.pubkey.clone(),
-            )),
-        )))))
+/// Address types
+pub enum AddressTypePrefix {
+    /// 0x01 - Pay-to-PublicKey(P2PK) address
+    P2PK = 1,
+    /// 0x02 - Pay-to-Script-Hash(P2SH)
+    Pay2SH = 2,
+    /// 0x03 - Pay-to-Script(P2S)
+    Pay2S = 3,
+}
+
+impl TryFrom<u8> for AddressTypePrefix {
+    type Error = AddressEncoderError;
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            v if v == AddressTypePrefix::P2PK as u8 => Ok(AddressTypePrefix::P2PK),
+            v if v == AddressTypePrefix::Pay2SH as u8 => Ok(AddressTypePrefix::Pay2SH),
+            v if v == AddressTypePrefix::Pay2S as u8 => Ok(AddressTypePrefix::Pay2S),
+            v => Err(AddressEncoderError::InvalidAddressType(v)),
+        }
     }
 }
 
 /// Network type
+#[derive(PartialEq, Eq, Debug, Clone, Copy)]
 pub enum NetworkPrefix {
     /// Mainnet
     Mainnet = 0,
@@ -103,34 +127,157 @@ pub enum NetworkPrefix {
 }
 
 /// Errors on encoding/decoding of addresses
-#[derive(Debug)]
+#[derive(Error, Eq, PartialEq, Debug, Clone)]
 pub enum AddressEncoderError {
-    /// Failed to decode Base58
+    /// Base58 decoding error
+    #[error("Base58 decoding error: {0}")]
     Base58DecodingError(String),
+
+    /// Invalid byte array size
+    #[error("Invalid size of the decoded byte array")]
+    InvalidSize,
+
+    /// address network type does not match network_prefix of this encoder
+    #[error("address network type does not match network_prefix of this encoder: {0}")]
+    InvalidNetwork(String),
+
+    /// invalid checksum
+    #[error("invalid checksum")]
+    InvalidChecksum,
+
+    /// invalid address type
+    #[error("invalid address type {0}")]
+    InvalidAddressType(u8),
+
+    /// deserialization failed
+    #[error("deserialization failed {0}")]
+    DeserializationFailed(SerializationError),
 }
 
-impl fmt::Display for AddressEncoderError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            AddressEncoderError::Base58DecodingError(e) => write!(f, "Error({:?}, {})", self, e),
-        }
+impl From<bs58::decode::Error> for AddressEncoderError {
+    fn from(err: bs58::decode::Error) -> Self {
+        AddressEncoderError::Base58DecodingError(err.to_string())
+    }
+}
+
+impl From<SerializationError> for AddressEncoderError {
+    fn from(err: SerializationError) -> Self {
+        AddressEncoderError::DeserializationFailed(err)
     }
 }
 
 /// Encodes/Decodes address to/from string
-pub struct AddressEncoder(NetworkPrefix);
+#[derive(PartialEq, Eq, Debug)]
+pub struct AddressEncoder {
+    /// Network prefix (network type) of the encoder
+    pub network_prefix: NetworkPrefix,
+}
 
 impl AddressEncoder {
+    const CHECKSUM_LENGTH: usize = 4;
+
     /// create a new AddressEncoder for a given network type
     pub fn new(network_prefix: NetworkPrefix) -> AddressEncoder {
-        AddressEncoder(network_prefix)
+        AddressEncoder { network_prefix }
+    }
+
+    fn is_mainnet_address(head_byte: u8) -> bool {
+        head_byte < NetworkPrefix::Testnet as u8
+    }
+
+    fn is_testnet_address(head_byte: u8) -> bool {
+        head_byte > NetworkPrefix::Testnet as u8
+    }
+
+    fn check_head_byte(&self, adr_prefix: u8) -> Result<u8, AddressEncoderError> {
+        match self.network_prefix {
+            NetworkPrefix::Mainnet if AddressEncoder::is_testnet_address(adr_prefix) => {
+                Err(AddressEncoderError::InvalidNetwork(
+                    "Expected mainnet address, found testnet".to_string(),
+                ))
+            }
+            NetworkPrefix::Testnet if AddressEncoder::is_mainnet_address(adr_prefix) => {
+                Err(AddressEncoderError::InvalidNetwork(
+                    "Expected testnet address, found mainnet".to_string(),
+                ))
+            }
+            _ => Ok(adr_prefix),
+        }
+    }
+
+    fn calc_checksum(bytes: &[u8]) -> [u8; AddressEncoder::CHECKSUM_LENGTH] {
+        let v: Vec<u8> = digest32::blake2b256_hash(bytes)
+            .0
+            .to_vec()
+            .into_iter()
+            .take(AddressEncoder::CHECKSUM_LENGTH)
+            .collect();
+        v.as_slice().try_into().unwrap()
     }
 
     /// parse address from Base58 encoded string
-    pub fn parse_address_from_str(&self, _: &str) -> Result<Box<dyn Address>, AddressEncoderError> {
-        // not implemented, see https://github.com/ergoplatform/sigma-rust/issues/35
-        Ok(Box::new(P2PKAddress::new(
-            ProveDlog::new(EcPoint::random()),
-        )))
+    pub fn parse_address_from_str(&self, str: &str) -> Result<Address, AddressEncoderError> {
+        let bytes = bs58::decode(str).into_vec()?;
+        if bytes.is_empty() {
+            return Err(AddressEncoderError::InvalidSize);
+        };
+        let head_byte = self.check_head_byte(bytes[0])?;
+        let (without_checksum, checksum) =
+            bytes.split_at(bytes.len() - AddressEncoder::CHECKSUM_LENGTH);
+        let calculated_checksum = AddressEncoder::calc_checksum(without_checksum);
+        if checksum != calculated_checksum {
+            return Err(AddressEncoderError::InvalidChecksum);
+        };
+
+        let content_bytes: Vec<u8> = without_checksum[1..].to_vec(); // without head_byte
+        let address_type = AddressTypePrefix::try_from(head_byte - self.network_prefix as u8)?;
+        Ok(match address_type {
+            AddressTypePrefix::P2PK => {
+                Address::P2PK(ProveDlog::new(EcPoint::sigma_parse_bytes(content_bytes)?))
+            }
+            AddressTypePrefix::Pay2S => Address::P2S(content_bytes),
+            AddressTypePrefix::Pay2SH => todo!(),
+        })
+    }
+
+    /// encode address as Base58 encoded string
+    pub fn address_to_str(&self, address: &Address) -> String {
+        let prefix_byte = self.network_prefix as u8 + address.address_type_prefix() as u8;
+        let mut address_bytes = address.content_bytes();
+        let mut bytes = vec![prefix_byte];
+        bytes.append(&mut address_bytes);
+        let mut calculated_checksum = AddressEncoder::calc_checksum(&bytes[..]).to_vec();
+        bytes.append(&mut calculated_checksum);
+        bs58::encode(bytes).into_string()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use proptest::prelude::*;
+
+    impl Arbitrary for Address {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            prop_oneof![
+                any::<ProveDlog>().prop_map(Address::P2PK),
+                any::<ErgoTree>().prop_map(|t| Address::P2S(t.sigma_serialise_bytes())),
+            ]
+            .boxed()
+        }
+    }
+
+    proptest! {
+
+        #[test]
+        fn str_roundtrip(v in any::<Address>()) {
+            let encoder = AddressEncoder::new(NetworkPrefix::Testnet);
+            let encoded_addr = encoder.address_to_str(&v);
+            let decoded_addr = encoder.parse_address_from_str(&encoded_addr).unwrap();
+            prop_assert_eq![decoded_addr, v];
+        }
     }
 }
diff --git a/sigma-tree/src/chain/contract.rs b/sigma-tree/src/chain/contract.rs
index 6a02cf182..c4a0de476 100644
--- a/sigma-tree/src/chain/contract.rs
+++ b/sigma-tree/src/chain/contract.rs
@@ -2,6 +2,7 @@
 
 use super::Address;
 use crate::ErgoTree;
+use sigma_ser::serializer::SerializationError;
 
 /// High-level wrapper for ErgoTree
 #[allow(dead_code)]
@@ -16,8 +17,8 @@ impl Contract {
     }
 
     /// create new contract that allow spending for a given Address
-    pub fn pay_to_address(address: &dyn Address) -> Contract {
-        Contract::new(address.script())
+    pub fn pay_to_address(address: Address) -> Result<Contract, SerializationError> {
+        Ok(Contract::new(address.script()?))
     }
 
     /// get ErgoTree for this contract
diff --git a/sigma-tree/src/chain/ergo_box.rs b/sigma-tree/src/chain/ergo_box.rs
index de52e140d..c0112ff3c 100644
--- a/sigma-tree/src/chain/ergo_box.rs
+++ b/sigma-tree/src/chain/ergo_box.rs
@@ -13,7 +13,6 @@ use super::{
 use crate::{
     ergo_tree::ErgoTree,
     serialization::ergo_box::{parse_box_with_indexed_digests, serialize_box_with_indexed_digests},
-    ErgoTreeParsingError,
 };
 use box_value::BoxValue;
 use indexmap::IndexSet;
@@ -61,7 +60,7 @@ pub struct ErgoBox {
         feature = "with-serde",
         serde(rename = "ergoTree", with = "json::ergo_tree")
     )]
-    pub ergo_tree: Result<ErgoTree, ErgoTreeParsingError>,
+    pub ergo_tree: ErgoTree,
     /// secondary tokens the box contains
     #[cfg_attr(feature = "with-serde", serde(rename = "assets"))]
     pub tokens: Vec<TokenAmount>,
@@ -95,7 +94,7 @@ impl ErgoBox {
         let box_with_zero_id = ErgoBox {
             box_id: BoxId::zero(),
             value,
-            ergo_tree: Ok(ergo_tree),
+            ergo_tree,
             tokens,
             additional_registers,
             creation_height,
@@ -124,7 +123,7 @@ impl ErgoBox {
         let box_with_zero_id = ErgoBox {
             box_id: BoxId::zero(),
             value: box_candidate.value.clone(),
-            ergo_tree: Ok(box_candidate.ergo_tree.clone()),
+            ergo_tree: box_candidate.ergo_tree.clone(),
             tokens: box_candidate.tokens.clone(),
             additional_registers: box_candidate.additional_registers.clone(),
             creation_height: box_candidate.creation_height,
@@ -173,10 +172,7 @@ impl TryFrom<json::ergo_box::ErgoBoxFromJson> for ErgoBox {
 
 impl SigmaSerializable for ErgoBox {
     fn sigma_serialize<W: vlq_encode::WriteSigmaVlqExt>(&self, w: &mut W) -> Result<(), io::Error> {
-        let ergo_tree_bytes = match &self.ergo_tree {
-            Ok(ergo_tree) => ergo_tree.sigma_serialise_bytes(),
-            Err(ErgoTreeParsingError { bytes, .. }) => bytes.clone(),
-        };
+        let ergo_tree_bytes = self.ergo_tree.sigma_serialise_bytes();
         serialize_box_with_indexed_digests(
             &self.value,
             ergo_tree_bytes,
diff --git a/sigma-tree/src/chain/json.rs b/sigma-tree/src/chain/json.rs
index f2ed286a6..0535af8de 100644
--- a/sigma-tree/src/chain/json.rs
+++ b/sigma-tree/src/chain/json.rs
@@ -18,36 +18,27 @@ where
 pub mod ergo_tree {
 
     use super::*;
-    use crate::{ErgoTree, ErgoTreeParsingError};
+    use crate::ErgoTree;
     use serde::{Deserialize, Deserializer, Serializer};
     use sigma_ser::serializer::SigmaSerializable;
 
-    pub fn serialize<S>(
-        maybe_ergo_tree: &Result<ErgoTree, ErgoTreeParsingError>,
-        serializer: S,
-    ) -> Result<S::Ok, S::Error>
+    pub fn serialize<S>(ergo_tree: &ErgoTree, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        let bytes = match maybe_ergo_tree {
-            Ok(ergo_tree) => ergo_tree.sigma_serialise_bytes(),
-            Err(err) => err.bytes.clone(),
-        };
+        let bytes = ergo_tree.sigma_serialise_bytes();
         serialize_bytes(&bytes[..], serializer)
     }
 
-    pub fn deserialize<'de, D>(
-        deserializer: D,
-    ) -> Result<Result<ErgoTree, ErgoTreeParsingError>, D::Error>
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<ErgoTree, D::Error>
     where
         D: Deserializer<'de>,
     {
         use serde::de::Error;
         String::deserialize(deserializer)
             .and_then(|str| base16::decode(&str).map_err(|err| Error::custom(err.to_string())))
-            .map(|bytes| {
-                ErgoTree::sigma_parse_bytes(bytes.clone())
-                    .map_err(|error| ErgoTreeParsingError { bytes, error })
+            .and_then(|bytes| {
+                ErgoTree::sigma_parse_bytes(bytes).map_err(|error| Error::custom(error.to_string()))
             })
     }
 }
@@ -55,7 +46,7 @@ pub mod ergo_tree {
 pub mod ergo_box {
     use crate::{
         chain::{box_value::BoxValue, register::NonMandatoryRegisters, BoxId, TokenAmount, TxId},
-        ErgoTree, ErgoTreeParsingError,
+        ErgoTree,
     };
     use serde::Deserialize;
     use thiserror::Error;
@@ -69,7 +60,7 @@ pub mod ergo_box {
         pub value: BoxValue,
         /// guarding script, which should be evaluated to true in order to open this box
         #[serde(rename = "ergoTree", with = "super::ergo_tree")]
-        pub ergo_tree: Result<ErgoTree, ErgoTreeParsingError>,
+        pub ergo_tree: ErgoTree,
         /// secondary tokens the box contains
         #[serde(rename = "assets")]
         pub tokens: Vec<TokenAmount>,
diff --git a/sigma-tree/src/ergo_tree.rs b/sigma-tree/src/ergo_tree.rs
index 4896d167e..c0e234bae 100644
--- a/sigma-tree/src/ergo_tree.rs
+++ b/sigma-tree/src/ergo_tree.rs
@@ -3,9 +3,10 @@ use crate::{
     ast::{Constant, Expr},
     types::SType,
 };
+use io::{Cursor, Read};
 use sigma_ser::serializer::SerializationError;
 use sigma_ser::serializer::SigmaSerializable;
-use sigma_ser::vlq_encode;
+use sigma_ser::{peekable_reader::PeekableReader, vlq_encode};
 use std::io;
 use std::rc::Rc;
 use vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
@@ -17,7 +18,7 @@ use vlq_encode::{ReadSigmaVlqExt, WriteSigmaVlqExt};
 pub struct ErgoTree {
     header: ErgoTreeHeader,
     constants: Vec<Constant>,
-    root: Rc<Expr>,
+    root: Result<Rc<Expr>, ErgoTreeRootParsingError>,
 }
 
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -25,7 +26,7 @@ struct ErgoTreeHeader(u8);
 
 /// ErgoTree parsing (deserialization) error
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ErgoTreeParsingError {
+pub struct ErgoTreeRootParsingError {
     /// Ergo tree bytes (faild to deserialize)
     pub bytes: Vec<u8>,
     /// Deserialization error
@@ -36,7 +37,7 @@ impl ErgoTree {
     const DEFAULT_HEADER: ErgoTreeHeader = ErgoTreeHeader(0);
 
     /// get Expr out of ErgoTree
-    pub fn proposition(&self) -> Rc<Expr> {
+    pub fn proposition(&self) -> Result<Rc<Expr>, ErgoTreeRootParsingError> {
         self.root.clone()
     }
 }
@@ -47,7 +48,7 @@ impl From<Rc<Expr>> for ErgoTree {
             Expr::Const(c) if c.tpe == SType::SSigmaProp => ErgoTree {
                 header: ErgoTree::DEFAULT_HEADER,
                 constants: Vec::new(),
-                root: expr,
+                root: Ok(expr),
             },
             _ => panic!("not yet supported"),
         }
@@ -73,7 +74,10 @@ impl SigmaSerializable for ErgoTree {
             self.constants.is_empty(),
             "separate constants serialization is not yet supported"
         );
-        self.root.sigma_serialize(w)?;
+        match &self.root {
+            Ok(tree) => tree.sigma_serialize(w)?,
+            Err(ErgoTreeRootParsingError { bytes, .. }) => w.write_all(&bytes[..])?,
+        }
         Ok(())
     }
 
@@ -90,10 +94,42 @@ impl SigmaSerializable for ErgoTree {
             Ok(ErgoTree {
                 header,
                 constants,
-                root: Rc::new(root),
+                root: Ok(Rc::new(root)),
             })
         }
     }
+
+    fn sigma_parse_bytes(mut bytes: Vec<u8>) -> Result<Self, SerializationError> {
+        let cursor = Cursor::new(&mut bytes[..]);
+        let mut r = PeekableReader::new(cursor);
+        let header = ErgoTreeHeader::sigma_parse(&mut r)?;
+        let constants_len = r.get_u32()?;
+        if constants_len != 0 {
+            Err(SerializationError::NotImplementedYet(
+                "separate constants serialization is not yet supported".to_string(),
+            ))
+        } else {
+            let constants = Vec::new();
+            let mut rest_of_the_bytes = Vec::new();
+            let _ = r.read_to_end(&mut rest_of_the_bytes);
+            let rest_of_the_bytes_copy = rest_of_the_bytes.clone();
+            match Expr::sigma_parse_bytes(rest_of_the_bytes) {
+                Ok(parsed) => Ok(ErgoTree {
+                    header,
+                    constants,
+                    root: Ok(Rc::new(parsed)),
+                }),
+                Err(err) => Ok(ErgoTree {
+                    header,
+                    constants,
+                    root: Err(ErgoTreeRootParsingError {
+                        bytes: rest_of_the_bytes_copy,
+                        error: err,
+                    }),
+                }),
+            }
+        }
+    }
 }
 
 #[cfg(test)]
@@ -131,7 +167,11 @@ mod tests {
     fn deserialization_fail() {
         // constants length is set
         assert!(ErgoTree::sigma_parse_bytes(vec![0, 1]).is_err());
+    }
+
+    #[test]
+    fn deserialization_non_parseable_root_ok() {
         // constants length is zero, but Expr is invalid
-        assert!(ErgoTree::sigma_parse_bytes(vec![0, 0, 1]).is_err());
+        assert!(ErgoTree::sigma_parse_bytes(vec![0, 0, 1]).is_ok());
     }
 }
diff --git a/sigma-tree/src/sigma_protocol.rs b/sigma-tree/src/sigma_protocol.rs
index dbae9277a..6bf884685 100644
--- a/sigma-tree/src/sigma_protocol.rs
+++ b/sigma-tree/src/sigma_protocol.rs
@@ -133,16 +133,23 @@ mod tests {
     use super::*;
     use proptest::prelude::*;
 
+    impl Arbitrary for ProveDlog {
+        type Parameters = ();
+        type Strategy = BoxedStrategy<Self>;
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<EcPoint>()).prop_map(ProveDlog::new).boxed()
+        }
+    }
+
     impl Arbitrary for SigmaBoolean {
         type Parameters = ();
         type Strategy = BoxedStrategy<Self>;
 
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
-            (any::<EcPoint>())
-                .prop_map(|ecp| {
-                    SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(
-                        ProveDlog::new(ecp),
-                    ))
+            (any::<ProveDlog>())
+                .prop_map(|p| {
+                    SigmaBoolean::ProofOfKnowledge(SigmaProofOfKnowledgeTree::ProveDlog(p))
                 })
                 .boxed()
         }


Test patch:
diff --git a/bindings/ergo-wallet-wasm/tests/test_transaction.js b/bindings/ergo-wallet-wasm/tests/test_transaction.js
index abdc886a9..939c3fd27 100644
--- a/bindings/ergo-wallet-wasm/tests/test_transaction.js
+++ b/bindings/ergo-wallet-wasm/tests/test_transaction.js
@@ -7,12 +7,13 @@ import {
 
 it('new signed transaction', async () => {
 
-  const recipient = Address.from_testnet_str('test');
+  const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
   const unspent_boxes = UnspentBoxes.from_boxes([]);
   const tx_data_inputs = TxDataInputs.from_boxes([]);
-  const send_change_to = Address.from_testnet_str('');
+  const send_change_to = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
 
-  let outbox = new ErgoBoxCandidate(1, 0, Contract.pay_to_address(recipient));
+  let contract = Contract.pay_to_address(recipient);
+  let outbox = new ErgoBoxCandidate(1, 0, contract);
   let tx_outputs = new TxOutputCandidates(outbox);
   let dummy_ctx = ErgoStateContext.dummy();
   let wallet = Wallet.from_mnemonic("", "");
diff --git a/bindings/ergo-wallet-wasm/tests/transaction.rs b/bindings/ergo-wallet-wasm/tests/transaction.rs
index 39b7f8c5e..27bb82329 100644
--- a/bindings/ergo-wallet-wasm/tests/transaction.rs
+++ b/bindings/ergo-wallet-wasm/tests/transaction.rs
@@ -11,10 +11,16 @@ wasm_bindgen_test_configure!(run_in_browser);
 fn test_signed_p2pk_transaction() {
     let tx_inputs = UnspentBoxes::from_boxes(Box::new([]));
     let tx_data_inputs = TxDataInputs::from_boxes(Box::new([]));
-    let send_change_to = Address::from_testnet_str("").expect("failed");
-    let recipient = Address::from_testnet_str("").expect("failed");
+    let send_change_to =
+        Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
+            .expect("failed");
+    let recipient =
+        Address::from_testnet_str("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN")
+            .expect("failed");
 
-    let outbox = ErgoBoxCandidate::new(1, 0, Contract::pay_to_address(recipient));
+    let contract = Contract::pay_to_address(recipient).expect("failed");
+
+    let outbox = ErgoBoxCandidate::new(1, 0, contract);
     let tx_outputs = TxOutputCandidates::new(outbox);
     let dummy_ctx = ErgoStateContext::dummy();
     let wallet = Wallet::from_mnemonic("", "");