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

Issue:
Expose API to decode int, long, byte array values in WASM



Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-wallet-lib-wasm/Cargo.toml b/bindings/ergo-wallet-lib-wasm/Cargo.toml
index 78ccac501..cb6ddaf3e 100644
--- a/bindings/ergo-wallet-lib-wasm/Cargo.toml
+++ b/bindings/ergo-wallet-lib-wasm/Cargo.toml
@@ -14,6 +14,7 @@ default = ["console_error_panic_hook"]
 [dependencies]
 serde = { version = "1.0", features = ["derive"]}
 serde_json = "1.0"
+js-sys = "0.3"
 
 sigma-tree = { path = "../../sigma-tree", features = ["with-serde"] }
 # used in elliptic-curve(in sigma-tree), compiled here with WASM support
@@ -45,3 +46,6 @@ features = ["std"]
 [profile.release]
 # Tell `rustc` to optimize for small code size.
 opt-level = "s"
+
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = ["-O", "--enable-mutable-globals"]
diff --git a/bindings/ergo-wallet-lib-wasm/src/ast.rs b/bindings/ergo-wallet-lib-wasm/src/ast.rs
new file mode 100644
index 000000000..beaf19934
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/src/ast.rs
@@ -0,0 +1,88 @@
+//! Ergo constant values
+
+use std::convert::TryFrom;
+
+use js_sys::Uint8Array;
+use wasm_bindgen::prelude::*;
+
+use crate::utils::I64;
+
+/// Ergo constant(evaluated) values
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct Constant(sigma_tree::ast::Constant);
+
+#[wasm_bindgen]
+impl Constant {
+    /// Decode from Base16-encoded ErgoTree serialized value
+    pub fn decode_from_base16(base16_bytes_str: String) -> Result<Constant, JsValue> {
+        let bytes = sigma_tree::chain::Base16DecodedBytes::try_from(base16_bytes_str.clone())
+            .map_err(|_| {
+                JsValue::from_str(&format!(
+                    "failed to decode base16 from: {}",
+                    base16_bytes_str.clone()
+                ))
+            })?;
+        sigma_tree::ast::Constant::try_from(bytes)
+            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map(Constant)
+    }
+
+    /// Encode as Base16-encoded ErgoTree serialized value
+    pub fn encode_to_base16(&self) -> String {
+        self.0.base16_str()
+    }
+
+    /// Create from i32 value
+    pub fn from_i32(v: i32) -> Constant {
+        Constant(v.into())
+    }
+
+    /// Extract i32 value, returning error if wrong type
+    pub fn as_i32(&self) -> Result<i32, JsValue> {
+        match self.0.v {
+            sigma_tree::ast::ConstantVal::Int(v) => Ok(v),
+            _ => Err(JsValue::from_str(&format!(
+                "expected i32, found: {:?}",
+                self.0.v
+            ))),
+        }
+    }
+
+    /// Create from i64
+    pub fn from_i64(v: &I64) -> Constant {
+        Constant(i64::from((*v).clone()).into())
+    }
+
+    /// Extract i64 value, returning error if wrong type
+    pub fn as_i64(&self) -> Result<I64, JsValue> {
+        match self.0.v {
+            sigma_tree::ast::ConstantVal::Long(v) => Ok(v.into()),
+            _ => Err(JsValue::from_str(&format!(
+                "expected i64, found: {:?}",
+                self.0.v
+            ))),
+        }
+    }
+
+    /// Create from byte array
+    pub fn from_byte_array(v: &[u8]) -> Constant {
+        Constant(v.to_vec().into())
+    }
+
+    /// Extract byte array, returning error if wrong type
+    pub fn as_byte_array(&self) -> Result<Uint8Array, JsValue> {
+        match self.0.v.clone() {
+            sigma_tree::ast::ConstantVal::Coll(sigma_tree::ast::ConstantColl::Primitive(
+                sigma_tree::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
+            ))),
+        }
+    }
+}
diff --git a/bindings/ergo-wallet-lib-wasm/src/lib.rs b/bindings/ergo-wallet-lib-wasm/src/lib.rs
index 1b569cf3f..a54530db8 100644
--- a/bindings/ergo-wallet-lib-wasm/src/lib.rs
+++ b/bindings/ergo-wallet-lib-wasm/src/lib.rs
@@ -12,6 +12,7 @@
 #![allow(unused_variables)]
 
 pub mod address;
+pub mod ast;
 pub mod box_coll;
 pub mod box_selector;
 pub mod contract;
diff --git a/bindings/ergo-wallet-lib-wasm/src/utils.rs b/bindings/ergo-wallet-lib-wasm/src/utils.rs
index 1287e621d..482492e3c 100644
--- a/bindings/ergo-wallet-lib-wasm/src/utils.rs
+++ b/bindings/ergo-wallet-lib-wasm/src/utils.rs
@@ -1,4 +1,41 @@
 //! Utilities
+
+use wasm_bindgen::prelude::*;
+
+/// Wrapper for i64 for JS/TS
+#[wasm_bindgen]
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct I64(i64);
+
+#[wasm_bindgen]
+impl I64 {
+    /// Create from a standard rust string representation
+    #[allow(clippy::should_implement_trait)]
+    pub fn from_str(string: &str) -> Result<I64, JsValue> {
+        string
+            .parse::<i64>()
+            .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
+            .map(I64)
+    }
+
+    /// String representation of the value for use from environments that don't support i64
+    pub fn to_str(&self) -> String {
+        format!("{}", self.0)
+    }
+}
+
+impl From<i64> for I64 {
+    fn from(v: i64) -> Self {
+        I64(v)
+    }
+}
+
+impl From<I64> for i64 {
+    fn from(v: I64) -> Self {
+        v.0
+    }
+}
+
 #[allow(dead_code, missing_docs)]
 pub fn set_panic_hook() {
     // When the `console_error_panic_hook` feature is enabled, we can call the
diff --git a/sigma-tree/src/ast/constant.rs b/sigma-tree/src/ast/constant.rs
index 0dbe6887d..1e1714f73 100644
--- a/sigma-tree/src/ast/constant.rs
+++ b/sigma-tree/src/ast/constant.rs
@@ -251,6 +251,17 @@ impl<T: LiftIntoSType + StoredNonPrimitive + Into<ConstantVal>> Into<Constant> f
     }
 }
 
+impl Into<Constant> for Vec<u8> {
+    fn into(self) -> Constant {
+        Constant {
+            tpe: SType::SColl(Box::new(SType::SByte)),
+            v: ConstantVal::Coll(ConstantColl::Primitive(CollPrim::CollByte(
+                self.into_iter().map(|b| b as i8).collect(),
+            ))),
+        }
+    }
+}
+
 impl Into<Constant> for Vec<i8> {
     fn into(self) -> Constant {
         Constant {


Test patch:
diff --git a/bindings/ergo-wallet-lib-wasm/tests/test_constant.js b/bindings/ergo-wallet-lib-wasm/tests/test_constant.js
new file mode 100644
index 000000000..745c604fc
--- /dev/null
+++ b/bindings/ergo-wallet-lib-wasm/tests/test_constant.js
@@ -0,0 +1,43 @@
+import { expect, assert } from 'chai';
+
+import {
+    Constant, I64
+} from '../pkg/ergo_wallet_lib_wasm';
+
+it('decode Constant i32', async () => {
+  let enc_v = '048ce5d4e505';
+  let c = Constant.decode_from_base16(enc_v);
+  let c_value = c.as_i32();
+  expect(c_value).equal(777689414);
+});
+
+it('roundtrip Constant i32', async () => {
+  let value = 999999999;
+  let c = Constant.from_i32(value);
+  let encoded = c.encode_to_base16();
+  let decoded_c = Constant.decode_from_base16(encoded);
+  let decoded_c_value = decoded_c.as_i32();
+  expect(decoded_c_value).equal(value);
+});
+
+it('roundtrip Constant i64', async () => {
+  let value_str = '9223372036854775807'; // i64 max value
+  let c = Constant.from_i64(I64.from_str(value_str));
+  let encoded = c.encode_to_base16();
+  let decoded_c = Constant.decode_from_base16(encoded);
+  let decoded_c_value = decoded_c.as_i64();
+  let decoded_c_value_str = decoded_c_value.to_str();
+  expect(decoded_c_value_str).equal(value_str);
+});
+
+it('roundtrip Constant byte array', async () => {
+  let value = new Uint8Array([1, 1, 2, 255]);
+  let c = Constant.from_byte_array(value);
+  let encoded = c.encode_to_base16();
+  let decoded_c = Constant.decode_from_base16(encoded);
+  let decoded_c_value = decoded_c.as_byte_array();
+  expect(decoded_c_value.toString()).equal(value.toString());
+});
+
+
+