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

Issue:
Add register data serialization/deserialization in WASM API
Use cases:
- read box register values;
- set box register values;


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/Cargo.toml b/bindings/ergo-lib-wasm/Cargo.toml
index 213b3f7fc..95495ac9f 100644
--- a/bindings/ergo-lib-wasm/Cargo.toml
+++ b/bindings/ergo-lib-wasm/Cargo.toml
@@ -45,4 +45,5 @@ default-features = false
 features = ["std"]
 
 [package.metadata.wasm-pack.profile.release]
-wasm-opt = ["-O", "--enable-mutable-globals"]
+# wasm-opt = ["-O", "--enable-mutable-globals"]
+wasm-opt = false
diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs
index 853d54c0c..7daf190b3 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -68,3 +68,15 @@ impl Constant {
             .map_err(|e| JsValue::from_str(&format! {"{:?}", e}))
     }
 }
+
+impl From<ergo_lib::ast::Constant> for Constant {
+    fn from(c: ergo_lib::ast::Constant) -> Self {
+        Constant(c)
+    }
+}
+
+impl From<Constant> for ergo_lib::ast::Constant {
+    fn from(c: Constant) -> Self {
+        c.0
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/ergo_box.rs b/bindings/ergo-lib-wasm/src/ergo_box.rs
index fd57c1f3f..088f34d60 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box.rs
@@ -22,6 +22,7 @@ use chain::ergo_box::register::NonMandatoryRegisters;
 use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
+use crate::ast::Constant;
 use crate::{contract::Contract, transaction::TxId};
 
 pub mod box_builder;
@@ -32,7 +33,16 @@ pub mod box_builder;
 pub struct ErgoBoxCandidate(chain::ergo_box::ErgoBoxCandidate);
 
 #[wasm_bindgen]
-impl ErgoBoxCandidate {}
+impl ErgoBoxCandidate {
+    /// Returns value (ErgoTree constant) stored in the register or None if the register is empty
+    pub fn register_value(&self, register_id: NonMandatoryRegisterId) -> Option<Constant> {
+        self.0
+            .additional_registers
+            .get(register_id.into())
+            .cloned()
+            .map(Constant::from)
+    }
+}
 
 impl Into<chain::ergo_box::ErgoBoxCandidate> for ErgoBoxCandidate {
     fn into(self) -> chain::ergo_box::ErgoBoxCandidate {
@@ -76,6 +86,15 @@ impl ErgoBox {
         ErgoBox(b)
     }
 
+    /// Returns value (ErgoTree constant) stored in the register or None if the register is empty
+    pub fn register_value(&self, register_id: NonMandatoryRegisterId) -> Option<Constant> {
+        self.0
+            .additional_registers
+            .get(register_id.into())
+            .cloned()
+            .map(Constant::from)
+    }
+
     // JSON representation
     // pub fn to_json(&self) -> Result<JsValue, JsValue> {
     //     JsValue::from_serde(&self.0).map_err(|e| JsValue::from_str(&format!("{}", e)))
@@ -122,3 +141,52 @@ impl From<chain::ergo_box::box_value::BoxValue> for BoxValue {
         BoxValue(v)
     }
 }
+
+/// newtype for box registers R4 - R9
+#[wasm_bindgen]
+#[repr(u8)]
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub enum NonMandatoryRegisterId {
+    /// id for R4 register
+    R4 = 4,
+    /// id for R5 register
+    R5 = 5,
+    /// id for R6 register
+    R6 = 6,
+    /// id for R7 register
+    R7 = 7,
+    /// id for R8 register
+    R8 = 8,
+    /// id for R9 register
+    R9 = 9,
+}
+
+impl NonMandatoryRegisterId {}
+
+impl From<NonMandatoryRegisterId> for chain::ergo_box::register::NonMandatoryRegisterId {
+    fn from(v: NonMandatoryRegisterId) -> Self {
+        use chain::ergo_box::register::NonMandatoryRegisterId::*;
+        match v {
+            NonMandatoryRegisterId::R4 => R4,
+            NonMandatoryRegisterId::R5 => R5,
+            NonMandatoryRegisterId::R6 => R6,
+            NonMandatoryRegisterId::R7 => R7,
+            NonMandatoryRegisterId::R8 => R8,
+            NonMandatoryRegisterId::R9 => R9,
+        }
+    }
+}
+
+impl From<chain::ergo_box::register::NonMandatoryRegisterId> for NonMandatoryRegisterId {
+    fn from(v: chain::ergo_box::register::NonMandatoryRegisterId) -> Self {
+        use NonMandatoryRegisterId::*;
+        match v {
+            chain::ergo_box::register::NonMandatoryRegisterId::R4 => R4,
+            chain::ergo_box::register::NonMandatoryRegisterId::R5 => R5,
+            chain::ergo_box::register::NonMandatoryRegisterId::R6 => R6,
+            chain::ergo_box::register::NonMandatoryRegisterId::R7 => R7,
+            chain::ergo_box::register::NonMandatoryRegisterId::R8 => R8,
+            chain::ergo_box::register::NonMandatoryRegisterId::R9 => R9,
+        }
+    }
+}
diff --git a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
index 811bfde00..1473f2fbb 100644
--- a/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_box/box_builder.rs
@@ -2,10 +2,12 @@
 use ergo_lib::chain;
 use wasm_bindgen::prelude::*;
 
+use crate::ast::Constant;
 use crate::contract::Contract;
 
 use super::BoxValue;
 use super::ErgoBoxCandidate;
+use super::NonMandatoryRegisterId;
 
 /// ErgoBoxCandidate builder
 #[wasm_bindgen]
@@ -29,8 +31,8 @@ impl ErgoBoxCandidateBuilder {
     }
 
     /// Set minimal value (per byte of the serialized box size)
-    pub fn set_min_box_value_per_byte(self, new_min_value_per_byte: u32) -> Self {
-        ErgoBoxCandidateBuilder(self.0.set_min_box_value_per_byte(new_min_value_per_byte))
+    pub fn set_min_box_value_per_byte(&mut self, new_min_value_per_byte: u32) {
+        self.0.set_min_box_value_per_byte(new_min_value_per_byte);
     }
 
     /// Get minimal value (per byte of the serialized box size)
@@ -39,8 +41,8 @@ impl ErgoBoxCandidateBuilder {
     }
 
     /// Set new box value
-    pub fn set_value(self, new_value: BoxValue) -> Self {
-        ErgoBoxCandidateBuilder(self.0.set_value(new_value.into()))
+    pub fn set_value(&mut self, new_value: BoxValue) {
+        self.0.set_value(new_value.into());
     }
 
     /// Get box value
@@ -49,13 +51,37 @@ impl ErgoBoxCandidateBuilder {
     }
 
     /// Calculate serialized box size(in bytes)
-    pub fn calc_box_size_bytes(&self) -> usize {
-        self.0.calc_box_size_bytes()
+    pub fn calc_box_size_bytes(&self) -> Result<usize, JsValue> {
+        self.0
+            .calc_box_size_bytes()
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
     }
 
     /// Calculate minimal box value for the current box serialized size(in bytes)
-    pub fn calc_min_box_value(&self) -> BoxValue {
-        self.0.calc_min_box_value().into()
+    pub fn calc_min_box_value(&self) -> Result<BoxValue, JsValue> {
+        self.0
+            .calc_min_box_value()
+            .map(BoxValue::from)
+            .map_err(|e| JsValue::from_str(&format!("{}", e)))
+    }
+
+    /// Set register with a given id (R4-R9) to the given value
+    pub fn set_register_value(&mut self, register_id: NonMandatoryRegisterId, value: &Constant) {
+        self.0
+            .set_register_value(register_id.into(), value.clone().into());
+    }
+
+    /// Returns register value for the given register id (R4-R9), or None if the register is empty
+    pub fn register_value(&self, register_id: NonMandatoryRegisterId) -> Option<Constant> {
+        self.0
+            .register_value(&register_id.into())
+            .cloned()
+            .map(Constant::from)
+    }
+
+    /// Delete register value(make register empty) for the given register id (R4-R9)
+    pub fn delete_register_value(&mut self, register_id: NonMandatoryRegisterId) {
+        self.0.delete_register_value(&register_id.into());
     }
 
     /// Build the box candidate
diff --git a/bindings/ergo-lib-wasm/src/tx_builder.rs b/bindings/ergo-lib-wasm/src/tx_builder.rs
index d92d9d2ec..b6ada9af6 100644
--- a/bindings/ergo-lib-wasm/src/tx_builder.rs
+++ b/bindings/ergo-lib-wasm/src/tx_builder.rs
@@ -47,8 +47,8 @@ impl TxBuilder {
     }
 
     /// Set transaction's data inputs
-    pub fn set_data_inputs(self, data_inputs: &DataInputs) -> TxBuilder {
-        TxBuilder(self.0.set_data_inputs(data_inputs.into()))
+    pub fn set_data_inputs(&mut self, data_inputs: &DataInputs) {
+        self.0.set_data_inputs(data_inputs.into())
     }
 
     /// Build the unsigned transaction
diff --git a/ergo-lib/src/chain/ergo_box.rs b/ergo-lib/src/chain/ergo_box.rs
index 4d80834cd..076a26e03 100644
--- a/ergo-lib/src/chain/ergo_box.rs
+++ b/ergo-lib/src/chain/ergo_box.rs
@@ -12,6 +12,7 @@ use super::{
     token::{TokenAmount, TokenId},
     transaction::TxId,
 };
+
 use crate::{
     ergo_tree::ErgoTree,
     serialization::{
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index ecd5dba5d..589ece6b1 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -1,14 +1,18 @@
 //! ErgoBoxCandidate builder
 
+use std::collections::HashMap;
 use std::convert::TryFrom;
 use std::convert::TryInto;
 
+use crate::ast::Constant;
 use crate::chain::token::TokenAmount;
 use crate::serialization::SigmaSerializable;
 use crate::ErgoTree;
 
 use super::box_value::BoxValue;
+use super::register::NonMandatoryRegisterId;
 use super::register::NonMandatoryRegisters;
+use super::register::NonMandatoryRegistersError;
 use super::ErgoBoxCandidate;
 use thiserror::Error;
 
@@ -23,19 +27,30 @@ pub enum ErgoBoxCandidateBuilderError {
         /// box size in bytes
         box_size_bytes: usize,
     },
+
+    /// NonMandatoryRegisters error
+    #[error("NonMandatoryRegisters error: {0}")]
+    NonMandatoryRegistersError(#[from] NonMandatoryRegistersError),
 }
 
 /// ErgoBoxCandidate builder
+#[derive(Debug, Clone)]
 pub struct ErgoBoxCandidateBuilder {
     min_value_per_byte: u32,
     value: BoxValue,
     ergo_tree: ErgoTree,
     tokens: Vec<TokenAmount>,
-    additional_registers: NonMandatoryRegisters,
+    additional_registers: HashMap<NonMandatoryRegisterId, Constant>,
     creation_height: u32,
 }
 
 impl ErgoBoxCandidateBuilder {
+    fn build_registers(&self) -> Result<NonMandatoryRegisters, ErgoBoxCandidateBuilderError> {
+        Ok(NonMandatoryRegisters::new(
+            self.additional_registers.clone(),
+        )?)
+    }
+
     /// Create builder with required box parameters:
     /// `value` - box value in nanoErgs,
     /// `ergo_tree` - ErgoTree to guard this box,
@@ -46,15 +61,14 @@ impl ErgoBoxCandidateBuilder {
             value,
             ergo_tree,
             tokens: vec![],
-            additional_registers: NonMandatoryRegisters::empty(),
+            additional_registers: HashMap::new(),
             creation_height,
         }
     }
 
     /// Set minimal value (per byte of the serialized box size)
-    pub fn set_min_box_value_per_byte(mut self, new_min_value_per_byte: u32) -> Self {
+    pub fn set_min_box_value_per_byte(&mut self, new_min_value_per_byte: u32) {
         self.min_value_per_byte = new_min_value_per_byte;
-        self
     }
 
     /// Get minimal value (per byte of the serialized box size)
@@ -63,9 +77,8 @@ impl ErgoBoxCandidateBuilder {
     }
 
     /// Set new box value
-    pub fn set_value(mut self, new_value: BoxValue) -> Self {
+    pub fn set_value(&mut self, new_value: BoxValue) {
         self.value = new_value;
-        self
     }
 
     /// Get box value
@@ -74,30 +87,50 @@ impl ErgoBoxCandidateBuilder {
     }
 
     /// Calculate serialized box size(in bytes)
-    pub fn calc_box_size_bytes(&self) -> usize {
+    pub fn calc_box_size_bytes(&self) -> Result<usize, ErgoBoxCandidateBuilderError> {
+        let regs = self.build_registers()?;
         let b = ErgoBoxCandidate {
             value: self.value,
             ergo_tree: self.ergo_tree.clone(),
             tokens: self.tokens.clone(),
-            additional_registers: self.additional_registers.clone(),
+            additional_registers: regs,
             creation_height: self.creation_height,
         };
-        b.sigma_serialise_bytes().len()
+        Ok(b.sigma_serialise_bytes().len())
     }
 
     /// Calculate minimal box value for the current box serialized size(in bytes)
-    pub fn calc_min_box_value(&self) -> BoxValue {
-        let box_size_bytes = self.calc_box_size_bytes();
-        BoxValue::try_from(box_size_bytes as i64 * BoxValue::MIN_VALUE_PER_BOX_BYTE as i64).unwrap()
+    pub fn calc_min_box_value(&self) -> Result<BoxValue, ErgoBoxCandidateBuilderError> {
+        let box_size_bytes = self.calc_box_size_bytes()?;
+        Ok(
+            BoxValue::try_from(box_size_bytes as i64 * BoxValue::MIN_VALUE_PER_BOX_BYTE as i64)
+                .unwrap(),
+        )
+    }
+
+    /// Set register with a given id (R4-R9) to the given value
+    pub fn set_register_value(&mut self, register_id: NonMandatoryRegisterId, value: Constant) {
+        self.additional_registers.insert(register_id, value);
+    }
+
+    /// Returns register value for the given register id (R4-R9), or None if the register is empty
+    pub fn register_value(&self, register_id: &NonMandatoryRegisterId) -> Option<&Constant> {
+        self.additional_registers.get(register_id)
+    }
+
+    /// Delete register value(make register empty) for the given register id (R4-R9)
+    pub fn delete_register_value(&mut self, register_id: &NonMandatoryRegisterId) {
+        self.additional_registers.remove(register_id);
     }
 
     /// Build the box candidate
     pub fn build(self) -> Result<ErgoBoxCandidate, ErgoBoxCandidateBuilderError> {
+        let regs = self.build_registers()?;
         let b = ErgoBoxCandidate {
             value: self.value,
             ergo_tree: self.ergo_tree,
             tokens: self.tokens,
-            additional_registers: self.additional_registers,
+            additional_registers: regs,
             creation_height: self.creation_height,
         };
         let box_size_bytes = b.sigma_serialise_bytes().len();
@@ -118,6 +151,8 @@ impl ErgoBoxCandidateBuilder {
 #[cfg(test)]
 mod tests {
 
+    use NonMandatoryRegisterId::*;
+
     use crate::test_util::force_any_val;
 
     use super::*;
@@ -137,9 +172,9 @@ mod tests {
     #[test]
     fn test_set_value() {
         let new_value = BoxValue::SAFE_USER_MIN.checked_mul_u32(10).unwrap();
-        let builder =
-            ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1)
-                .set_value(new_value);
+        let mut builder =
+            ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
+        builder.set_value(new_value);
         assert_eq!(builder.value(), &new_value);
         let b = builder.build().unwrap();
         assert_eq!(b.value, new_value);
@@ -159,7 +194,7 @@ mod tests {
     fn test_box_size_estimation() {
         let builder =
             ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
-        let estimated_box_size = builder.calc_box_size_bytes();
+        let estimated_box_size = builder.calc_box_size_bytes().unwrap();
         let b = builder.build().unwrap();
         assert_eq!(b.sigma_serialise_bytes().len(), estimated_box_size);
     }
@@ -168,7 +203,7 @@ mod tests {
     fn test_calc_min_box_value() {
         let builder =
             ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
-        assert!(builder.calc_min_box_value() > BoxValue::MIN);
+        assert!(builder.calc_min_box_value().unwrap() > BoxValue::MIN);
     }
 
     #[test]
@@ -176,4 +211,28 @@ mod tests {
         let builder = ErgoBoxCandidateBuilder::new(BoxValue::MIN, force_any_val::<ErgoTree>(), 1);
         assert!(builder.build().is_err());
     }
+
+    #[test]
+    fn test_set_get_register_value() {
+        let reg_value: Constant = 1i32.into();
+        let mut builder =
+            ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
+        assert!(builder.register_value(&R4).is_none());
+        builder.set_register_value(R4, reg_value.clone());
+        assert_eq!(builder.register_value(&R4).unwrap(), &reg_value);
+        let b = builder.build().unwrap();
+        assert_eq!(b.additional_registers.get(R4).unwrap(), &reg_value);
+    }
+
+    #[test]
+    fn test_delete_register_value() {
+        let reg_value: Constant = 1i32.into();
+        let mut builder =
+            ErgoBoxCandidateBuilder::new(BoxValue::SAFE_USER_MIN, force_any_val::<ErgoTree>(), 1);
+        builder.set_register_value(R4, reg_value);
+        builder.delete_register_value(&R4);
+        assert!(builder.register_value(&R4).is_none());
+        let b = builder.build().unwrap();
+        assert!(b.additional_registers.get(R4).is_none());
+    }
 }
diff --git a/ergo-lib/src/chain/ergo_box/register.rs b/ergo-lib/src/chain/ergo_box/register.rs
index b100c4c6b..da2d0ecf0 100644
--- a/ergo-lib/src/chain/ergo_box/register.rs
+++ b/ergo-lib/src/chain/ergo_box/register.rs
@@ -7,10 +7,24 @@ use std::{collections::HashMap, convert::TryFrom};
 use thiserror::Error;
 
 /// newtype for additional registers R4 - R9
-#[derive(PartialEq, Eq, Hash, Debug, Clone)]
+#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
 #[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "json", serde(into = "String", try_from = "String"))]
-pub struct NonMandatoryRegisterId(u8);
+#[repr(u8)]
+pub enum NonMandatoryRegisterId {
+    /// id for R4 register
+    R4 = 4,
+    /// id for R5 register
+    R5 = 5,
+    /// id for R6 register
+    R6 = 6,
+    /// id for R7 register
+    R7 = 7,
+    /// id for R8 register
+    R8 = 8,
+    /// id for R9 register
+    R9 = 9,
+}
 
 impl NonMandatoryRegisterId {
     /// starting index for non-mandatory registers
@@ -21,19 +35,6 @@ impl NonMandatoryRegisterId {
     /// max number of registers
     pub const NUM_REGS: usize = 6;
 
-    /// register R4
-    pub const R4: NonMandatoryRegisterId = NonMandatoryRegisterId(4);
-    /// register R5
-    pub const R5: NonMandatoryRegisterId = NonMandatoryRegisterId(5);
-    /// register R6
-    pub const R6: NonMandatoryRegisterId = NonMandatoryRegisterId(6);
-    /// register R7
-    pub const R7: NonMandatoryRegisterId = NonMandatoryRegisterId(7);
-    /// register R8
-    pub const R8: NonMandatoryRegisterId = NonMandatoryRegisterId(8);
-    /// register R9
-    pub const R9: NonMandatoryRegisterId = NonMandatoryRegisterId(9);
-
     /// all register ids
     pub const REG_IDS: [NonMandatoryRegisterId; NonMandatoryRegisterId::NUM_REGS] = [
         NonMandatoryRegisterId::R4,
@@ -46,15 +47,15 @@ impl NonMandatoryRegisterId {
 
     /// get register by it's index starting from 0
     /// `i` is expected to be in range 0..[`NUM_REGS`] , otherwise panic
-    pub fn get_by_index(i: usize) -> NonMandatoryRegisterId {
+    pub fn get_by_zero_index(i: usize) -> NonMandatoryRegisterId {
         assert!(i < NonMandatoryRegisterId::NUM_REGS);
-        NonMandatoryRegisterId::REG_IDS[i].clone()
+        NonMandatoryRegisterId::REG_IDS[i]
     }
 }
 
 impl Into<String> for NonMandatoryRegisterId {
     fn into(self) -> String {
-        format!("R{}", self.0)
+        format!("R{}", self as u8)
     }
 }
 
@@ -68,7 +69,7 @@ impl TryFrom<String> for NonMandatoryRegisterId {
             if index >= NonMandatoryRegisterId::START_INDEX
                 && index <= NonMandatoryRegisterId::END_INDEX
             {
-                Ok(NonMandatoryRegisterId::get_by_index(
+                Ok(NonMandatoryRegisterId::get_by_zero_index(
                     index - NonMandatoryRegisterId::START_INDEX,
                 ))
             } else {
@@ -135,9 +136,9 @@ impl NonMandatoryRegisters {
     }
 
     /// Get register value
-    pub fn get(&self, reg_id: &NonMandatoryRegisterId) -> Option<&Constant> {
+    pub fn get(&self, reg_id: NonMandatoryRegisterId) -> Option<&Constant> {
         self.0
-            .get(reg_id.0 as usize - NonMandatoryRegisterId::START_INDEX)
+            .get(reg_id as usize - NonMandatoryRegisterId::START_INDEX)
     }
 
     /// Get ordered register values (first is R4, and so on, up to R9)
@@ -147,7 +148,7 @@ impl NonMandatoryRegisters {
 }
 
 /// Possible errors when building NonMandatoryRegisters
-#[derive(Error, Debug)]
+#[derive(Error, PartialEq, Eq, Clone, Debug)]
 pub enum NonMandatoryRegistersError {
     /// Set of register has invalid size(maximum [`NonMandatoryRegisters::MAX_SIZE`])
     #[error("invalid non-mandatory registers size ({0})")]
@@ -162,7 +163,7 @@ impl Into<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegisters {
         self.0
             .into_iter()
             .enumerate()
-            .map(|(i, c)| (NonMandatoryRegisterId::get_by_index(i), c))
+            .map(|(i, c)| (NonMandatoryRegisterId::get_by_zero_index(i), c))
             .collect()
     }
 }
@@ -180,7 +181,7 @@ impl TryFrom<HashMap<NonMandatoryRegisterId, Constant>> for NonMandatoryRegister
                 .take(regs_num)
                 .try_for_each(|reg_id| match reg_map.get(reg_id) {
                     Some(v) => Ok(res.push(v.clone())),
-                    None => Err(NonMandatoryRegistersError::NonDenselyPacked(reg_id.0)),
+                    None => Err(NonMandatoryRegistersError::NonDenselyPacked(*reg_id as u8)),
                 })?;
             Ok(NonMandatoryRegisters(res))
         }
@@ -203,7 +204,7 @@ mod tests {
         type Strategy = BoxedStrategy<Self>;
 
         fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
-            vec(any::<Constant>(), 0..7)
+            vec(any::<Constant>(), 0..=NonMandatoryRegisterId::NUM_REGS)
                 .prop_map(|constants| {
                     NonMandatoryRegisters::from_ordered_values(constants)
                         .expect("error building registers")
@@ -226,9 +227,23 @@ mod tests {
         fn get(regs in any::<NonMandatoryRegisters>()) {
             let hash_map: HashMap<NonMandatoryRegisterId, Constant> = regs.clone().into();
             hash_map.keys().try_for_each(|reg_id| {
-                prop_assert_eq![regs.get(reg_id), hash_map.get(reg_id)];
+                prop_assert_eq![regs.get(*reg_id), hash_map.get(reg_id)];
                 Ok(())
             })?;
         }
     }
+
+    #[test]
+    fn test_empty() {
+        assert!(NonMandatoryRegisters::empty().is_empty());
+    }
+
+    #[test]
+    fn test_non_densely_packed_error() {
+        let mut hash_map: HashMap<NonMandatoryRegisterId, Constant> = HashMap::new();
+        hash_map.insert(NonMandatoryRegisterId::R4, 1i32.into());
+        // gap, missing R5
+        hash_map.insert(NonMandatoryRegisterId::R6, 1i32.into());
+        assert!(NonMandatoryRegisters::try_from(hash_map).is_err());
+    }
 }
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index ea879a5d6..ec2904c24 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -67,9 +67,8 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
     }
 
     /// Set transaction's data inputs
-    pub fn set_data_inputs(mut self, data_inputs: Vec<DataInput>) -> Self {
+    pub fn set_data_inputs(&mut self, data_inputs: Vec<DataInput>) {
         self.data_inputs = data_inputs;
-        self
     }
 
     /// Build the unsigned transaction
@@ -239,7 +238,7 @@ mod tests {
 
             prop_assume!(all_outputs < all_inputs);
 
-            let tx_builder = TxBuilder::new(
+            let mut tx_builder = TxBuilder::new(
                 SimpleBoxSelector::new(),
                 inputs.clone(),
                 outputs.clone(),
@@ -247,8 +246,8 @@ mod tests {
                 miners_fee,
                 change_address.clone(),
                 min_change_value,
-            )
-            .set_data_inputs(data_inputs.clone());
+            );
+            tx_builder.set_data_inputs(data_inputs.clone());
             let tx = tx_builder.build().unwrap();
             prop_assert!(outputs.into_iter().all(|i| tx.output_candidates.iter().any(|o| *o == i)),
                          "tx.output_candidates is missing some outputs");


Test patch:
diff --git a/bindings/ergo-lib-wasm/tests/test_box_builder.js b/bindings/ergo-lib-wasm/tests/test_box_builder.js
new file mode 100644
index 000000000..f03f68a71
--- /dev/null
+++ b/bindings/ergo-lib-wasm/tests/test_box_builder.js
@@ -0,0 +1,36 @@
+import { expect, assert } from 'chai';
+
+import {
+  Address, Wallet, ErgoBox, ErgoBoxCandidateBuilder, Contract,
+  ErgoBoxes, ErgoBoxCandidates,
+  ErgoStateContext, TxBuilder, BoxValue, BoxSelector, SecretKey, TxId, DataInputs, NonMandatoryRegisterId, Constant
+} from '../pkg/ergo_lib_wasm';
+
+const recipient = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
+const contract = Contract.pay_to_address(recipient);
+
+it('ErgoBoxCandidateBuilder test', async () => {
+  const b = new ErgoBoxCandidateBuilder(BoxValue.from_u32(10000000), contract, 0).build();
+  assert(b != null);
+});
+
+it('ErgoBoxCandidateBuilder set register value test', async () => {
+  let builder = new ErgoBoxCandidateBuilder(BoxValue.from_u32(10000000), contract, 0);
+  assert(builder.register_value(NonMandatoryRegisterId.R4) == null);
+  const c = Constant.from_i32(1);
+  builder.set_register_value(NonMandatoryRegisterId.R4, c);
+  assert(builder.register_value(NonMandatoryRegisterId.R4).to_i32() == c.to_i32());
+  const b = builder.build();
+  assert(b.register_value(NonMandatoryRegisterId.R4).to_i32 = c.to_i32);
+});
+
+it('ErgoBoxCandidateBuilder delete register value test', async () => {
+  let builder = new ErgoBoxCandidateBuilder(BoxValue.from_u32(10000000), contract, 0);
+  const c = Constant.from_i32(1);
+  builder.set_register_value(NonMandatoryRegisterId.R4, c);
+  assert(builder.register_value(NonMandatoryRegisterId.R4).to_i32() == c.to_i32());
+  builder.delete_register_value(NonMandatoryRegisterId.R4);
+  assert(builder.register_value(NonMandatoryRegisterId.R4) == null);
+  const b = builder.build();
+  assert(b.register_value(NonMandatoryRegisterId.R4) == null);
+});
diff --git a/bindings/ergo-lib-wasm/tests/test_transaction.js b/bindings/ergo-lib-wasm/tests/test_transaction.js
index c7fe5599d..a835f80e1 100644
--- a/bindings/ergo-lib-wasm/tests/test_transaction.js
+++ b/bindings/ergo-lib-wasm/tests/test_transaction.js
@@ -3,7 +3,7 @@ import { expect, assert } from 'chai';
 import {
   Address, Wallet, ErgoBox, ErgoBoxCandidateBuilder, Contract,
   ErgoBoxes, ErgoBoxCandidates,
-  ErgoStateContext, TxBuilder, BoxValue, UnsignedTransaction, BoxSelector, SecretKey, TxId, DataInputs
+  ErgoStateContext, TxBuilder, BoxValue, BoxSelector, SecretKey, TxId, DataInputs
 } from '../pkg/ergo_lib_wasm';
 
 it('TxBuilder test', async () => {
@@ -27,13 +27,12 @@ it('TxBuilder test', async () => {
   const change_address = Address.from_testnet_str('3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN');
   const min_change_value = BoxValue.SAFE_USER_MIN();
   const data_inputs = new DataInputs();
-  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value)
-                              .set_data_inputs(data_inputs);
+  const tx_builder = TxBuilder.new(BoxSelector.SelectAll, unspent_boxes, tx_outputs, 0, fee, change_address, min_change_value);
+  tx_builder.set_data_inputs(data_inputs);
   const tx = tx_builder.build();
   assert(tx != null);
 });
 
-
 it('sign transaction', async () => {
   const sk = SecretKey.random_dlog();
   // simulate existing box guarded by the sk key