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

Issue:
[50 SigmaUSD] `BoxSelection::boxes` cannot be empty
### Motivation
In the error-less box selection `BoxSelection::boxes` cannot be empty. At least one box is required. 

### Solution
Use `BoundedVec` to ensure non-empty `BoxSelection::boxes`.
`pub type SelectedBoxes<T> = BoundedVec<T, 1, { u16::MAX as usize }>;` should suffice since tx inputs are limited to `u16::MAX`.



Discussion / hints:
So I need to change boxes type to SelectedBoxes<T> which is a BoundedVec and then I also need to change where BoxSelection is being created to include `.try_into().unwrap()`. Is there a better way without try_into?
> So I need to change boxes type to SelectedBoxes which is a BoundedVec and then I also need to change where BoxSelection is being created to include `.try_into().unwrap()`. Is there a better way without try_into?

The reason for having try_into is to force error handling, so unwrap should only be used in tests or in compile-time defined constants that are checked in tests.

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/box_selector.rs b/bindings/ergo-lib-wasm/src/box_selector.rs
index 4b10d4aef..6b90ec2a5 100644
--- a/bindings/ergo-lib-wasm/src/box_selector.rs
+++ b/bindings/ergo-lib-wasm/src/box_selector.rs
@@ -9,6 +9,7 @@ use crate::ergo_box::BoxValue;
 use crate::ergo_box::ErgoBoxAssetsDataList;
 use crate::error_conversion::to_js;
 use crate::token::Tokens;
+use bounded_vec::BoundedVec;
 
 extern crate derive_more;
 use derive_more::{From, Into};
@@ -24,18 +25,18 @@ pub struct BoxSelection(
 impl BoxSelection {
     /// Create a selection to easily inject custom selection algorithms
     #[wasm_bindgen(constructor)]
-    pub fn new(boxes: &ErgoBoxes, change: &ErgoBoxAssetsDataList) -> Self {
-        BoxSelection(wallet::box_selector::BoxSelection::<
+    pub fn new(boxes: &ErgoBoxes, change: &ErgoBoxAssetsDataList) -> Result<BoxSelection, JsValue> {
+        Ok(BoxSelection(wallet::box_selector::BoxSelection::<
             ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox,
         > {
-            boxes: boxes.clone().into(),
+            boxes: BoundedVec::from_vec(boxes.clone().into()).map_err(to_js)?,
             change_boxes: change.clone().into(),
-        })
+        }))
     }
 
     /// Selected boxes to spend as transaction inputs
     pub fn boxes(&self) -> ErgoBoxes {
-        self.0.boxes.clone().into()
+        self.0.boxes.as_vec().clone().into()
     }
 
     /// Selected boxes to use as change
diff --git a/ergo-lib/src/wallet/box_selector.rs b/ergo-lib/src/wallet/box_selector.rs
index 93e13c1fe..8cd5d97fb 100644
--- a/ergo-lib/src/wallet/box_selector.rs
+++ b/ergo-lib/src/wallet/box_selector.rs
@@ -3,6 +3,7 @@
 mod simple;
 use std::collections::HashMap;
 
+use bounded_vec::{BoundedVec, BoundedVecOutOfBounds};
 use ergotree_ir::chain::ergo_box::box_value::BoxValue;
 use ergotree_ir::chain::ergo_box::box_value::BoxValueError;
 use ergotree_ir::chain::ergo_box::BoxId;
@@ -17,11 +18,14 @@ pub use simple::*;
 
 use thiserror::Error;
 
+/// Bounded vec with minimum 1 element and max u16::MAX elements
+pub type SelectedBoxes<T> = BoundedVec<T, 1, { u16::MAX as usize }>;
+
 /// Selected boxes (by [`BoxSelector`])
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BoxSelection<T: ErgoBoxAssets> {
     /// Selected boxes to spend as transaction inputs
-    pub boxes: Vec<T>,
+    pub boxes: SelectedBoxes<T>,
     /// box assets with returning change amounts (to be put in tx outputs)
     pub change_boxes: Vec<ErgoBoxAssetsData>,
 }
@@ -58,6 +62,10 @@ pub enum BoxSelectorError {
     /// Token amount err
     #[error("TokenAmountError: {0:?}")]
     TokenAmountError(#[from] TokenAmountError),
+
+    /// Boxes out of bounds
+    #[error("Boxes is out of bounds")]
+    OutOfBounds(#[from] BoundedVecOutOfBounds),
 }
 
 impl From<BoxValueError> for BoxSelectorError {
diff --git a/ergo-lib/src/wallet/box_selector/simple.rs b/ergo-lib/src/wallet/box_selector/simple.rs
index c4f35aa34..e1eebd096 100644
--- a/ergo-lib/src/wallet/box_selector/simple.rs
+++ b/ergo-lib/src/wallet/box_selector/simple.rs
@@ -159,7 +159,7 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
             }]
         };
         Ok(BoxSelection {
-            boxes: selected_inputs,
+            boxes: selected_inputs.try_into()?,
             change_boxes,
         })
     }
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index 2da351461..406dbea60 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -202,13 +202,7 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
         // check that inputs have enough tokens
         let input_tokens = sum_tokens_from_boxes(self.box_selection.boxes.as_slice())?;
         let output_tokens = sum_tokens_from_boxes(output_candidates.as_slice())?;
-        let first_input_box_id: TokenId = self
-            .box_selection
-            .boxes
-            .first()
-            .ok_or(TxBuilderError::EmptyInputBoxSelection)?
-            .box_id()
-            .into();
+        let first_input_box_id: TokenId = self.box_selection.boxes.first().box_id().into();
         let output_tokens_len = output_tokens.len();
         let output_tokens_without_minted: Vec<Token> = output_tokens
             .into_iter()
@@ -329,28 +323,11 @@ mod tests {
 
     use super::*;
 
-    #[test]
-    fn test_empty_inputs() {
-        let box_selection: BoxSelection<ErgoBox> = BoxSelection {
-            boxes: vec![],
-            change_boxes: vec![],
-        };
-        let r = TxBuilder::new(
-            box_selection,
-            vec![force_any_val::<ErgoBoxCandidate>()],
-            1,
-            force_any_val::<BoxValue>(),
-            force_any_val::<Address>(),
-            BoxValue::SAFE_USER_MIN,
-        );
-        assert!(r.build().is_err());
-    }
-
     #[test]
     fn test_duplicate_inputs() {
         let input_box = force_any_val::<ErgoBox>();
         let box_selection: BoxSelection<ErgoBox> = BoxSelection {
-            boxes: vec![input_box.clone(), input_box],
+            boxes: vec![input_box.clone(), input_box].try_into().unwrap(),
             change_boxes: vec![],
         };
         let r = TxBuilder::new(
@@ -532,7 +509,7 @@ mod tests {
         let inputs: Vec<ErgoBox> = vec![input_box];
         let tx_fee = BoxValue::SAFE_USER_MIN;
         let box_selection = BoxSelection {
-            boxes: inputs,
+            boxes: inputs.try_into().unwrap(),
             change_boxes: vec![],
         };
         let outputs = vec![out_box];
@@ -570,7 +547,7 @@ mod tests {
         let outputs = vec![out_box];
         let tx_builder = TxBuilder::new(
             BoxSelection {
-                boxes: vec![input],
+                boxes: vec![input].try_into().unwrap(),
                 change_boxes: vec![],
             },
             outputs,


Test patch: