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

Issue:
Add API to extract values from ErgoTree constants
Extract 
https://github.com/ergoplatform/sigma-rust/blob/i27-publish-crates/bindings/ergo-lib-wasm/src/ast.rs#L56-L66
to ergo-lib crate.


Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index e8d00d86c..f8d710582 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -72,7 +72,7 @@ matrix:
 
     - name: build for iOS
       os: osx
-      osx_image: xcode11.5
+      osx_image: xcode12
       rust: stable
       before_install:
         - rustup target add aarch64-apple-ios x86_64-apple-ios
diff --git a/README.md b/README.md
index d37080bbc..413d9642a 100644
--- a/README.md
+++ b/README.md
@@ -16,5 +16,6 @@ Bindings:
 
 ## Contributing
 Contributions are very welcome! Checkout issues labeled ["help wanted" and "good first issue"](https://github.com/ergoplatform/sigma-rust/labels/help%20wanted)
-We check code formatting and linter(`clippy`) as part of the CI process. It's better to set up your editor to run `rustfmt` on file save.
- Feel free to disable specific linter rules in the source code when appropriate.
+We check code formatting and linter(`clippy`) as part of the CI process. It's better to set up your editor to run `rustfmt` on file save. Feel free to disable specific linter rules in the source code when appropriate.
+
+You are welcome to join the [Ergo Discord](https://discord.gg/kj7s7nb) and ask questions on `#sigma-rust` channel.
diff --git a/bindings/ergo-lib-c-core/Cargo.toml b/bindings/ergo-lib-c-core/Cargo.toml
index f97e2517c..10ae11a51 100644
--- a/bindings/ergo-lib-c-core/Cargo.toml
+++ b/bindings/ergo-lib-c-core/Cargo.toml
@@ -12,6 +12,5 @@ crate-type = ["lib"]
 
 [dependencies]
 ergo-lib = { version = "0.2.0", path = "../../ergo-lib" }
-
 thiserror = "1"
 
diff --git a/bindings/ergo-lib-c/Cargo.toml b/bindings/ergo-lib-c/Cargo.toml
index a28baf5cc..ff237d08d 100644
--- a/bindings/ergo-lib-c/Cargo.toml
+++ b/bindings/ergo-lib-c/Cargo.toml
@@ -16,10 +16,9 @@ exclude = [
 crate-type = ["staticlib"]
 
 [dependencies]
-ffi_helpers = "0.2"
 ergo-lib = {version = "0.2.0", path = "../../ergo-lib"}
 ergo-lib-c-core = {version = "0.1.0", path = "../ergo-lib-c-core"}
 
-
 [build-dependencies]
-cbindgen = "0.14"
+serde = { version = "1.0", features = ["derive"] }
+cbindgen = "0.15"
diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs
index f84ed7bfc..853d54c0c 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -6,6 +6,7 @@ use js_sys::Uint8Array;
 use wasm_bindgen::prelude::*;
 
 use crate::utils::I64;
+use ergo_lib::ast::TryExtractFrom;
 
 /// Ergo constant(evaluated) values
 #[wasm_bindgen]
@@ -40,13 +41,7 @@ impl Constant {
 
     /// Extract i32 value, returning error if wrong type
     pub fn to_i32(&self) -> Result<i32, JsValue> {
-        match self.0.v {
-            ergo_lib::ast::ConstantVal::Int(v) => Ok(v),
-            _ => Err(JsValue::from_str(&format!(
-                "expected i32, found: {:?}",
-                self.0.v
-            ))),
-        }
+        i32::try_extract_from(self.0.clone()).map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
     }
 
     /// Create from i64
@@ -56,13 +51,9 @@ impl Constant {
 
     /// Extract i64 value, returning error if wrong type
     pub fn to_i64(&self) -> Result<I64, JsValue> {
-        match self.0.v {
-            ergo_lib::ast::ConstantVal::Long(v) => Ok(v.into()),
-            _ => Err(JsValue::from_str(&format!(
-                "expected i64, found: {:?}",
-                self.0.v
-            ))),
-        }
+        i64::try_extract_from(self.0.clone())
+            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map(I64::from)
     }
 
     /// Create from byte array
@@ -72,17 +63,8 @@ impl Constant {
 
     /// Extract byte array, returning error if wrong type
     pub fn to_byte_array(&self) -> Result<Uint8Array, JsValue> {
-        match self.0.v.clone() {
-            ergo_lib::ast::ConstantVal::Coll(ergo_lib::ast::ConstantColl::Primitive(
-                ergo_lib::ast::CollPrim::CollByte(coll_bytes),
-            )) => {
-                let u8_bytes: Vec<u8> = coll_bytes.into_iter().map(|b| b as u8).collect();
-                Ok(Uint8Array::from(u8_bytes.as_slice()))
-            }
-            _ => Err(JsValue::from_str(&format!(
-                "expected byte array, found: {:?}",
-                self.0.v
-            ))),
-        }
+        Vec::<u8>::try_extract_from(self.0.clone())
+            .map(|v| Uint8Array::from(v.as_slice()))
+            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
     }
 }
diff --git a/ergo-lib/src/ast/constant.rs b/ergo-lib/src/ast/constant.rs
index 9874eafb0..8a88a4ad4 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergo-lib/src/ast/constant.rs
@@ -1,7 +1,7 @@
 use crate::chain::{Base16DecodedBytes, Base16EncodedBytes};
 use crate::{
     chain::ergo_box::ErgoBox,
-    serialization::{op_code::OpCode, SerializationError, SigmaSerializable},
+    serialization::{SerializationError, SigmaSerializable},
     sigma_protocol::{dlog_group::EcPoint, sigma_boolean::SigmaProp},
     types::{LiftIntoSType, SType},
 };
@@ -9,6 +9,10 @@ use crate::{
 use serde::{Deserialize, Serialize};
 use std::convert::TryFrom;
 
+mod constant_placeholder;
+
+pub use constant_placeholder::*;
+
 #[derive(PartialEq, Eq, Debug, Clone)]
 /// Collection for primitive values (i.e byte array)
 pub enum CollPrim {
@@ -271,22 +275,128 @@ impl Into<Constant> for Vec<i8> {
     }
 }
 
-/// Placeholder for a constant in ErgoTree.
+/// Underlying type is different from requested value type
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub struct ConstantPlaceholder {
-    /// Zero based index in ErgoTree.constants array.
-    pub id: u32,
-    /// Type of the constant value
-    pub tpe: SType,
+pub struct TryExtractFromError(String);
+
+/// Extract underlying value if type matches
+pub trait TryExtractFrom<T>: Sized {
+    /// Extract the value or return an error if type does not match
+    fn try_extract_from(c: T) -> Result<Self, TryExtractFromError>;
+}
+
+impl TryExtractFrom<ConstantVal> for bool {
+    fn try_extract_from(cv: ConstantVal) -> Result<bool, TryExtractFromError> {
+        match cv {
+            ConstantVal::Boolean(v) => Ok(v),
+            _ => Err(TryExtractFromError(format!(
+                "expected bool, found {:?}",
+                cv
+            ))),
+        }
+    }
+}
+
+impl TryExtractFrom<ConstantVal> for i8 {
+    fn try_extract_from(cv: ConstantVal) -> Result<i8, TryExtractFromError> {
+        match cv {
+            ConstantVal::Byte(v) => Ok(v),
+            _ => Err(TryExtractFromError(format!("expected i8, found {:?}", cv))),
+        }
+    }
+}
+
+impl TryExtractFrom<ConstantVal> for i16 {
+    fn try_extract_from(cv: ConstantVal) -> Result<i16, TryExtractFromError> {
+        match cv {
+            ConstantVal::Short(v) => Ok(v),
+            _ => Err(TryExtractFromError(format!("expected i16, found {:?}", cv))),
+        }
+    }
+}
+
+impl TryExtractFrom<ConstantVal> for i32 {
+    fn try_extract_from(cv: ConstantVal) -> Result<i32, TryExtractFromError> {
+        match cv {
+            ConstantVal::Int(v) => Ok(v),
+            _ => Err(TryExtractFromError(format!("expected i32, found {:?}", cv))),
+        }
+    }
+}
+
+impl TryExtractFrom<ConstantVal> for i64 {
+    fn try_extract_from(cv: ConstantVal) -> Result<i64, TryExtractFromError> {
+        match cv {
+            ConstantVal::Long(v) => Ok(v),
+            _ => Err(TryExtractFromError(format!("expected i64, found {:?}", cv))),
+        }
+    }
 }
 
-impl ConstantPlaceholder {
-    /// OpCode value
-    pub const OP_CODE: OpCode = OpCode::CONSTANT_PLACEHOLDER;
+impl TryExtractFrom<ConstantVal> for EcPoint {
+    fn try_extract_from(cv: ConstantVal) -> Result<EcPoint, TryExtractFromError> {
+        match cv {
+            ConstantVal::GroupElement(v) => Ok(*v),
+            _ => Err(TryExtractFromError(format!(
+                "expected EcPoint, found {:?}",
+                cv
+            ))),
+        }
+    }
+}
 
-    /// OpCode for the serialization
-    pub fn op_code(&self) -> OpCode {
-        OpCode::CONSTANT_PLACEHOLDER
+impl TryExtractFrom<ConstantVal> for SigmaProp {
+    fn try_extract_from(cv: ConstantVal) -> Result<SigmaProp, TryExtractFromError> {
+        match cv {
+            ConstantVal::SigmaProp(v) => Ok(*v),
+            _ => Err(TryExtractFromError(format!(
+                "expected SigmaProp, found {:?}",
+                cv
+            ))),
+        }
+    }
+}
+
+impl<T: TryExtractFrom<ConstantVal>> TryExtractFrom<Constant> for T {
+    fn try_extract_from(cv: Constant) -> Result<Self, TryExtractFromError> {
+        T::try_extract_from(cv.v)
+    }
+}
+
+impl<T: TryExtractFrom<ConstantVal> + StoredNonPrimitive + LiftIntoSType> TryExtractFrom<Constant>
+    for Vec<T>
+{
+    fn try_extract_from(c: Constant) -> Result<Self, TryExtractFromError> {
+        match c.v {
+            ConstantVal::Coll(ConstantColl::NonPrimitive { elem_tpe: _, v }) => {
+                v.into_iter().map(T::try_extract_from).collect()
+            }
+            _ => Err(TryExtractFromError(format!(
+                "expected {:?}, found {:?}",
+                std::any::type_name::<Self>(),
+                c.v
+            ))),
+        }
+    }
+}
+
+impl TryExtractFrom<Constant> for Vec<i8> {
+    fn try_extract_from(c: Constant) -> Result<Self, TryExtractFromError> {
+        match c.v {
+            ConstantVal::Coll(ConstantColl::Primitive(CollPrim::CollByte(bs))) => Ok(bs),
+            _ => Err(TryExtractFromError(format!(
+                "expected {:?}, found {:?}",
+                std::any::type_name::<Self>(),
+                c.v
+            ))),
+        }
+    }
+}
+
+impl TryExtractFrom<Constant> for Vec<u8> {
+    fn try_extract_from(cv: Constant) -> Result<Self, TryExtractFromError> {
+        use crate::util::FromVecI8;
+        Vec::<i8>::try_extract_from(cv).map(Vec::<u8>::from_vec_i8)
     }
 }
 
@@ -317,4 +427,45 @@ mod tests {
             .boxed()
         }
     }
+
+    proptest! {
+
+        #[test]
+        fn test_try_extract_from(c in any::<Constant>()) {
+            // let c = force_any_val::<Constant>();
+            match c.clone().tpe {
+                SType::SBoolean => {
+                    let _ = bool::try_extract_from(c).unwrap();
+                }
+                SType::SByte => {
+                    let _ = i8::try_extract_from(c).unwrap();
+                }
+                SType::SShort => {
+                    let _ = i16::try_extract_from(c).unwrap();
+                }
+                SType::SInt => {
+                    let _ = i32::try_extract_from(c).unwrap();
+                }
+                SType::SLong => {
+                    let _ = i64::try_extract_from(c).unwrap();
+                }
+                SType::SGroupElement => {
+                    let _ = EcPoint::try_extract_from(c).unwrap();
+                }
+                SType::SSigmaProp => {
+                    let _ = SigmaProp::try_extract_from(c).unwrap();
+                }
+                SType::SColl(elem_type) => {
+                    match *elem_type {
+                        SType::SByte => { let _ = Vec::<i8>::try_extract_from(c).unwrap(); }
+                        SType::SShort => { let _ = Vec::<i16>::try_extract_from(c).unwrap(); }
+                        SType::SInt => { let _ = Vec::<i32>::try_extract_from(c).unwrap(); }
+                        SType::SLong => { let _ = Vec::<i64>::try_extract_from(c).unwrap(); }
+                        _ => todo!()
+                    }
+                }
+                _ => todo!(),
+            };
+        }
+    }
 }
diff --git a/ergo-lib/src/ast/constant/constant_placeholder.rs b/ergo-lib/src/ast/constant/constant_placeholder.rs
new file mode 100644
index 000000000..ddbf364ea
--- /dev/null
+++ b/ergo-lib/src/ast/constant/constant_placeholder.rs
@@ -0,0 +1,21 @@
+use crate::serialization::op_code::OpCode;
+use crate::types::SType;
+
+/// Placeholder for a constant in ErgoTree.
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ConstantPlaceholder {
+    /// Zero based index in ErgoTree.constants array.
+    pub id: u32,
+    /// Type of the constant value
+    pub tpe: SType,
+}
+
+impl ConstantPlaceholder {
+    /// OpCode value
+    pub const OP_CODE: OpCode = OpCode::CONSTANT_PLACEHOLDER;
+
+    /// OpCode for the serialization
+    pub fn op_code(&self) -> OpCode {
+        OpCode::CONSTANT_PLACEHOLDER
+    }
+}
diff --git a/ergo-lib/src/serialization/data.rs b/ergo-lib/src/serialization/data.rs
index 2951df7bb..782481d97 100644
--- a/ergo-lib/src/serialization/data.rs
+++ b/ergo-lib/src/serialization/data.rs
@@ -1,6 +1,7 @@
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
+use crate::util::AsVecU8;
 use crate::{
     ast::ConstantVal,
     ast::ConstantVal::*,
@@ -33,8 +34,7 @@ impl DataSerializer {
             Coll(ct) => match ct {
                 ConstantColl::Primitive(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[..])
+                    w.write_all(b.clone().as_vec_u8().as_slice())
                 }
                 ConstantColl::NonPrimitive { elem_tpe: _, v } => {
                     w.put_usize_as_u16(v.len())?;
diff --git a/ergo-lib/src/util.rs b/ergo-lib/src/util.rs
index 1df837099..d2bbf274b 100644
--- a/ergo-lib/src/util.rs
+++ b/ergo-lib/src/util.rs
@@ -17,3 +17,27 @@ impl<T> IntoOption<T> for CtOption<T> {
         }
     }
 }
+
+/// Vec<i8> to Vec<u8> conversion
+pub trait FromVecI8 {
+    /// Convert Vec<i8> to Vec<u8>
+    fn from_vec_i8(bs: Vec<i8>) -> Self;
+}
+
+impl FromVecI8 for Vec<u8> {
+    fn from_vec_i8(bs: Vec<i8>) -> Self {
+        bs.iter().map(|b| *b as u8).collect()
+    }
+}
+
+/// Convert Vec<i8> to Vec<u8>
+pub trait AsVecU8 {
+    /// Returns as Vec<u8>
+    fn as_vec_u8(self) -> Vec<u8>;
+}
+
+impl AsVecU8 for Vec<i8> {
+    fn as_vec_u8(self) -> Vec<u8> {
+        Vec::<u8>::from_vec_i8(self)
+    }
+}


Test patch: