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

Issue:
Parsing support for new explorer format for box's additionalRegisters
New explorer format for additional regs breaks the code:
```
        "R4": {
            "decodedValue": "Coll(-89,30,-127,32,-20,-100,-42,0,-25,-9,-25,107,-100,27,10,-97,127,127,-93,109,-48,70,51,-111,27,85,107,-116,97,102,87,45)",
            "valueType": "Coll[Byte]",
            "rawValue": "0e20a71e8120ec9cd600e7f7e76b9c1b0a9f7f7fa36dd04633911b556b8c6166572d"
        }
```

vs. old(node):
```
\"R4\": \"0e20a71e8120ec9cd600e7f7e76b9c1b0a9f7f7fa36dd04633911b556b8c6166572d\"
```

Full box example:
```
{
    "mainChain": true,
    "spentTransactionId": null,
    "additionalRegisters": {
        "R4": {
            "decodedValue": "Coll(-89,30,-127,32,-20,-100,-42,0,-25,-9,-25,107,-100,27,10,-97,127,127,-93,109,-48,70,51,-111,27,85,107,-116,97,102,87,45)",
            "valueType": "Coll[Byte]",
            "rawValue": "0e20a71e8120ec9cd600e7f7e76b9c1b0a9f7f7fa36dd04633911b556b8c6166572d"
        }
    },
    "assets": [
        {
            "amount": 1,
            "index": 0,
            "tokenId": "77d14a018507949d1a88a631f76663e8e5101f57305dd5ebd319a41028d80456"
        }
    ],
    "address": "6Vs43fLottAzin3EiEiswbSD31ETscqBLy9i3zTWCwUVuG79fWuP7S3Kko5PEK56UEBWSTE8GuuXq3ZYzWKCGmzPQ9y5AU4hvwGcTsYPH74qLsm3kmXctHLRnEVAxvsviB5aTRJo41adKHZ4EJSdWJpNUuJM4BJXGpM7BJnjT9cNB63QMtMUrfqaq8Ku8aJ7jM1VtXZFQiNH1pzwNFRQzU4fD4Dg8VqTtaAVWw98zKgGZmm35pq8QbAb5je796CWnQQRzDuGdPxwfBzzVCPjCf5hpFev56odduByacWPGYnd671A7CLrF7iSqae2ZHs4YmeXvAbBUW6s3A7U8YZAdPvaugBjNEkP9eVYZjDR4ppWcGukcrnjWBSqcv9nrBMudcLQLZwS653mVCbD8rqki2u2DjR4PMMtSQLAnH4HkeZFnz3w8Nav7YXkgviAFYR5AEdse8sUUKmb34AhU2uCxCJw6thqRfcMsWwaRHetzkhp6YoAy66EXmpVgM7pDo9RxpE9aRwPbxY6BwZ8",
    "ergoTree": "100c04000402040204000580dac40904000e20dd26438230986cfe7305ad958451b69e55ad5ac37c8a355bfb08d810edd7a20f05000400050a04040e207bd873b8a886daa7a8bfacdad11d36aeee36c248aaf5779bcd8d41a13e4c1604d80bd601c5a7d602b2a4730000d603b2a4730100d604db63087203d605b2a5730200d606c27205d607e4c67202040ed608b2a5730300d609e4c67208040ed60ac27202d60bc27208d1ed937201c57202ecedededed93720b720a92c17208730493db63087208db63087202947209720792b0b5db6501fed9010c63eded938cb2db6308720c73050001730693e4c6720c040e720993e4c6720c050e72017307d9010c41639a8c720c018cb2db63088c720c02730800027309ededededededed938cb27204730a0001730b937204db6308720593e4c6a7040ecb720694c27203720693e4c672030405e4c67205040593e4c672030505e4c67205050593c17203c17205ededed937207720993720a720b93db63087202db6308720893c17202c17208",
    "creationHeight": 377423,
    "index": 0,
    "value": 10000000,
    "transactionId": "bfafb4a736ce21412a6da0ce10952d73808764648a1eae3adebc97fc5b00aa22",
    "boxId": "8134d857446810eb8db0e1e13cc4025d8c7babfca363a36c3549fe651ad67956"
}
```


Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 003ca292a..b6a7c06e8 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Added 
 
 - Interpreter: added global vars (`INPUTS`, `OUTPUTS`, `SELF`, `HEIGHT`), `Context` properties (`dataInputs`) [#155](https://github.com/ergoplatform/sigma-rust/pull/155);
+- Explorer API v2 format parsing for box.additionalRegisters [#161](https://github.com/ergoplatform/sigma-rust/pull/161);
 
 ## [0.4.1] - 2020-11-19
 
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergo-lib/src/chain/ergo_box/register.rs
index e7c07d891..525f003e4 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergo-lib/src/chain/ergo_box/register.rs
@@ -1,5 +1,7 @@
 //! Box registers
 
+#[cfg(feature = "json")]
+use crate::chain::json::ergo_box::ConstantHolder;
 use crate::{ast::constant::Constant, serialization::SerializationError};
 #[cfg(feature = "json")]
 use serde::{Deserialize, Serialize};
@@ -93,7 +95,7 @@ pub struct NonMandatoryRegisterIdParsingError();
     feature = "json",
     serde(
         into = "HashMap<NonMandatoryRegisterId, Constant>",
-        try_from = "HashMap<NonMandatoryRegisterId, Constant>"
+        try_from = "HashMap<NonMandatoryRegisterId, crate::chain::json::ergo_box::ConstantHolder>"
     )
 )]
 pub struct NonMandatoryRegisters(Vec<Constant>);
@@ -188,6 +190,18 @@ impl TryFrom<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegister
     }
 }
 
+#[cfg(feature = "json")]
+impl TryFrom<HashMap<NonMandatoryRegisterId, ConstantHolder>> for NonMandatoryRegisters {
+    type Error = NonMandatoryRegistersError;
+    fn try_from(
+        value: HashMap<NonMandatoryRegisterId, ConstantHolder>,
+    ) -> Result<Self, Self::Error> {
+        let cm: HashMap<NonMandatoryRegisterId, Constant> =
+            value.into_iter().map(|(k, v)| (k, v.into())).collect();
+        NonMandatoryRegisters::try_from(cm)
+    }
+}
+
 impl From<NonMandatoryRegistersError> for SerializationError {
     fn from(error: NonMandatoryRegistersError) -> Self {
         SerializationError::Misc(error.to_string())
diff --git a/ergo-lib/src/chain/json.rs b/ergo-lib/src/chain/json.rs
index f243fcf9b..224f7416f 100644
--- a/ergo-lib/src/chain/json.rs
+++ b/ergo-lib/src/chain/json.rs
@@ -39,6 +39,20 @@ pub mod ergo_tree {
 }
 
 pub mod ergo_box {
+    use core::fmt;
+    use serde::de::{self, MapAccess, Visitor};
+    use serde::Deserializer;
+    use std::convert::TryFrom;
+    use std::marker::PhantomData;
+    use std::str::FromStr;
+
+    extern crate derive_more;
+    use derive_more::From;
+
+    use crate::ast::constant::Constant;
+    use crate::chain::Base16DecodedBytes;
+    use crate::serialization::SerializationError;
+    use crate::serialization::SigmaSerializable;
     use crate::{
         chain::{
             ergo_box::{BoxId, BoxValue, NonMandatoryRegisters},
@@ -77,6 +91,88 @@ pub mod ergo_box {
         #[serde(rename = "index")]
         pub index: u16,
     }
+
+    #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
+    pub struct ConstantHolder(
+        #[serde(deserialize_with = "constant_as_string_or_struct")] RichConstant,
+    );
+
+    impl From<ConstantHolder> for Constant {
+        fn from(ch: ConstantHolder) -> Self {
+            ch.0.raw_value
+        }
+    }
+
+    #[derive(Deserialize, PartialEq, Eq, Debug, Clone)]
+    struct RichConstant {
+        #[serde(rename = "rawValue")]
+        raw_value: Constant,
+    }
+
+    use thiserror::Error;
+
+    #[derive(Error, PartialEq, Eq, Debug, Clone, From)]
+    pub enum ConstantParsingError {
+        #[error("Base16 decoding error: {0}")]
+        DecodeError(base16::DecodeError),
+        #[error("Deserialization error: {0}")]
+        DeserializationError(SerializationError),
+    }
+
+    impl FromStr for RichConstant {
+        type Err = ConstantParsingError;
+        fn from_str(s: &str) -> Result<Self, Self::Err> {
+            let bytes = Base16DecodedBytes::try_from(s)?;
+            let c = Constant::sigma_parse_bytes(bytes.into())?;
+            Ok(RichConstant { raw_value: c })
+        }
+    }
+
+    // via https://serde.rs/string-or-struct.html
+    pub fn constant_as_string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+    where
+        T: Deserialize<'de> + FromStr<Err = ConstantParsingError>,
+        D: Deserializer<'de>,
+    {
+        // This is a Visitor that forwards string types to T's `FromStr` impl and
+        // forwards map types to T's `Deserialize` impl. The `PhantomData` is to
+        // keep the compiler from complaining about T being an unused generic type
+        // parameter. We need T in order to know the Value type for the Visitor
+        // impl.
+        struct StringOrStruct<T>(PhantomData<fn() -> T>);
+
+        impl<'de, T> Visitor<'de> for StringOrStruct<T>
+        where
+            T: Deserialize<'de> + FromStr<Err = ConstantParsingError>,
+        {
+            type Value = T;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("string or map")
+            }
+
+            fn visit_str<E>(self, value: &str) -> Result<T, E>
+            where
+                E: de::Error,
+            {
+                FromStr::from_str(value)
+                    .map_err(|_| de::Error::custom("error parsing constant from string: {value}"))
+            }
+
+            fn visit_map<M>(self, map: M) -> Result<T, M::Error>
+            where
+                M: MapAccess<'de>,
+            {
+                // `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
+                // into a `Deserializer`, allowing it to be used as the input to T's
+                // `Deserialize` implementation. T then deserializes itself using
+                // the entries from the map visitor.
+                Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
+            }
+        }
+
+        deserializer.deserialize_any(StringOrStruct(PhantomData))
+    }
 }
 
 pub mod transaction {
@@ -169,6 +265,30 @@ mod tests {
         assert_eq!(regs.get_ordered_values().len(), 2)
     }
 
+    #[test]
+    fn parse_registers_explorer_api_v2() {
+        let json = r#"
+            {
+                "R4": {
+                    "decodedValue": "Coll(-89,30,-127,32,-20,-100,-42,0,-25,-9,-25,107,-100,27,10,-97,127,127,-93,109,-48,70,51,-111,27,85,107,-116,97,102,87,45)",
+                    "valueType": "Coll[Byte]",
+                    "rawValue": "0e20a71e8120ec9cd600e7f7e76b9c1b0a9f7f7fa36dd04633911b556b8c6166572d"
+                }
+            }
+        "#;
+        let regs: NonMandatoryRegisters = serde_json::from_str(json).unwrap();
+        assert!(regs.get(NonMandatoryRegisterId::R4).is_some());
+    }
+
+    #[test]
+    fn parse_registers_error() {
+        let json = r#"
+        {"R4":"0"}
+        "#;
+        let regs: Result<NonMandatoryRegisters, _> = serde_json::from_str(json);
+        assert!(regs.is_err());
+    }
+
     #[test]
     fn parse_ergo_tree_with_constants() {
         let json = r#"


Test patch: