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

Issue:
[500 SigmaUSD] Ban (via clippy) todo!, panic!, unimplemented! in `ergo-lib` crate
Each case should be reviewed and explicitly allowed. 

Add:
```
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
#![deny(clippy::panic)]
```
at the root `lib.rs`.

For reference and examples check out how it was done in the following creates before:
- ~sigma-ser~ #328;
- ~ergotree-ir~ #328;
  - extend ‘sigma_serialize‘ error besides io error (use type alias for Result);
- ~ergotree-interpreter~ #344;

I'll be happy to assist since some cases will be hard to figure out without the knowledge of the inner workings of Ergo chain type.

**EDIT: Not all unwraps should be eliminated. The goal is to eliminate those that are "purely runtime" by their nature and "uncheckable" at compile-time and/or in tests. It sounds a bit vague, so please check https://github.com/ergoplatform/sigma-rust/pull/461 for some examples. In cases where it's not clear feel free to ping me.**


Discussion / hints:
Starting this one.
> Starting this one.

Great! Feel free to do it in draft(WIP) PR and/or ask in case you're in doubt whether certain unwrap calls should be allowed or removed ( like in #461 ).

Will do, thanks!

Fix patch:
diff --git a/ergo-lib/src/chain/contract.rs b/ergo-lib/src/chain/contract.rs
index 8379c2bfb..53c1df5aa 100644
--- a/ergo-lib/src/chain/contract.rs
+++ b/ergo-lib/src/chain/contract.rs
@@ -38,6 +38,7 @@ impl Contract {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/chain/ergo_box/box_builder.rs b/ergo-lib/src/chain/ergo_box/box_builder.rs
index bb11a2eed..a75549b52 100644
--- a/ergo-lib/src/chain/ergo_box/box_builder.rs
+++ b/ergo-lib/src/chain/ergo_box/box_builder.rs
@@ -122,6 +122,9 @@ impl ErgoBoxCandidateBuilder {
     /// Calculate minimal box value for the current box serialized size(in bytes)
     pub fn calc_min_box_value(&self) -> Result<BoxValue, ErgoBoxCandidateBuilderError> {
         let box_size_bytes = self.calc_box_size_bytes()?;
+
+        // Won't be overflowing an i64, so unwrap is safe.
+        #[allow(clippy::unwrap_used)]
         Ok(
             BoxValue::try_from(box_size_bytes as i64 * BoxValue::MIN_VALUE_PER_BOX_BYTE as i64)
                 .unwrap(),
@@ -243,6 +246,9 @@ impl ErgoBoxCandidateBuilder {
             creation_height: self.creation_height,
         };
         let box_size_bytes = b.sigma_serialize_bytes()?.len();
+
+        // Won't be overflowing an i64, so unwrap is safe.
+        #[allow(clippy::unwrap_used)]
         let min_box_value: BoxValue = (box_size_bytes as i64 * self.min_value_per_byte as i64)
             .try_into()
             .unwrap();
@@ -263,6 +269,7 @@ impl ErgoBoxCandidateBuilder {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
 
     use ergotree_ir::base16_str::Base16Str;
diff --git a/ergo-lib/src/chain/json/context_extension.rs b/ergo-lib/src/chain/json/context_extension.rs
index d8bccd922..fbb14a226 100644
--- a/ergo-lib/src/chain/json/context_extension.rs
+++ b/ergo-lib/src/chain/json/context_extension.rs
@@ -40,6 +40,7 @@ impl From<ContextExtension> for ContextExtensionSerde {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/chain/json/transaction.rs b/ergo-lib/src/chain/json/transaction.rs
index 44d5160fa..1bb8c363c 100644
--- a/ergo-lib/src/chain/json/transaction.rs
+++ b/ergo-lib/src/chain/json/transaction.rs
@@ -36,6 +36,7 @@ pub struct UnsignedTransactionJson {
 }
 
 #[cfg(test)]
+#[allow(clippy::panic)]
 mod tests {
     use crate::chain::transaction::unsigned::UnsignedTransaction;
     use crate::chain::transaction::Transaction;
diff --git a/ergo-lib/src/chain/transaction.rs b/ergo-lib/src/chain/transaction.rs
index d1c97f9f4..9fb5ec3f4 100644
--- a/ergo-lib/src/chain/transaction.rs
+++ b/ergo-lib/src/chain/transaction.rs
@@ -169,6 +169,7 @@ where
 }
 
 impl SigmaSerializable for Transaction {
+    #[allow(clippy::unwrap_used)]
     fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
         // reference implementation - https://github.com/ScorexFoundation/sigmastate-interpreter/blob/9b20cb110effd1987ff76699d637174a4b2fb441/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala#L112-L112
         w.put_usize_as_u16_unwrapped(self.inputs.len())?;
@@ -184,6 +185,9 @@ impl SigmaSerializable for Transaction {
         // This optimization is crucial to allow up to MaxTokens (== 255) in a box.
         // Without it total size of all token ids 255 * 32 = 8160, way beyond MaxBoxSize (== 4K)
         let distinct_token_ids = distinct_token_ids(self.output_candidates.clone());
+
+        // Note that `self.output_candidates` is of type `TxIoVec` which has a max length of
+        // `u16::MAX`. Therefore the following unwrap is safe.
         w.put_u32(u32::try_from(distinct_token_ids.len()).unwrap())?;
         distinct_token_ids
             .iter()
@@ -319,6 +323,7 @@ impl TryFrom<json::transaction::TransactionJson> for Transaction {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 pub mod tests {
     use std::convert::TryInto;
 
diff --git a/ergo-lib/src/chain/transaction/data_input.rs b/ergo-lib/src/chain/transaction/data_input.rs
index 33a1d9a6c..2a0d5376b 100644
--- a/ergo-lib/src/chain/transaction/data_input.rs
+++ b/ergo-lib/src/chain/transaction/data_input.rs
@@ -37,6 +37,7 @@ impl SigmaSerializable for DataInput {
 }
 
 #[cfg(test)]
+#[allow(clippy::panic)]
 mod tests {
     use ergotree_ir::serialization::sigma_serialize_roundtrip;
 
diff --git a/ergo-lib/src/chain/transaction/input.rs b/ergo-lib/src/chain/transaction/input.rs
index 922bf43a7..d7a73f87d 100644
--- a/ergo-lib/src/chain/transaction/input.rs
+++ b/ergo-lib/src/chain/transaction/input.rs
@@ -129,6 +129,7 @@ impl SigmaSerializable for Input {
 }
 
 #[cfg(test)]
+#[allow(clippy::panic)]
 mod tests {
     use super::*;
     use ergotree_ir::serialization::sigma_serialize_roundtrip;
diff --git a/ergo-lib/src/chain/transaction/input/prover_result.rs b/ergo-lib/src/chain/transaction/input/prover_result.rs
index bed05e2cc..c200d283d 100644
--- a/ergo-lib/src/chain/transaction/input/prover_result.rs
+++ b/ergo-lib/src/chain/transaction/input/prover_result.rs
@@ -74,6 +74,7 @@ impl SigmaSerializable for ProverResult {
 }
 
 #[cfg(test)]
+#[allow(clippy::panic)]
 mod tests {
     use super::*;
     use ergotree_ir::serialization::sigma_serialize_roundtrip;
diff --git a/ergo-lib/src/chain/transaction/reduced.rs b/ergo-lib/src/chain/transaction/reduced.rs
index 30a4c10c4..bae8aee26 100644
--- a/ergo-lib/src/chain/transaction/reduced.rs
+++ b/ergo-lib/src/chain/transaction/reduced.rs
@@ -138,6 +138,7 @@ impl SigmaSerializable for ReducedTransaction {
 }
 
 #[cfg(test)]
+#[allow(clippy::panic)]
 mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/chain/transaction/unsigned.rs b/ergo-lib/src/chain/transaction/unsigned.rs
index 5bb211dfa..9852a3aca 100644
--- a/ergo-lib/src/chain/transaction/unsigned.rs
+++ b/ergo-lib/src/chain/transaction/unsigned.rs
@@ -138,6 +138,7 @@ impl TryFrom<json::transaction::UnsignedTransactionJson> for UnsignedTransaction
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 pub mod tests {
     use super::*;
 
diff --git a/ergo-lib/src/lib.rs b/ergo-lib/src/lib.rs
index c00fff865..0cfc502d4 100644
--- a/ergo-lib/src/lib.rs
+++ b/ergo-lib/src/lib.rs
@@ -13,6 +13,11 @@
 #![allow(clippy::unit_arg)]
 #![deny(rustdoc::broken_intra_doc_links)]
 #![deny(clippy::wildcard_enum_match_arm)]
+#![deny(clippy::unwrap_used)]
+#![deny(clippy::expect_used)]
+#![deny(clippy::todo)]
+#![deny(clippy::unimplemented)]
+#![deny(clippy::panic)]
 
 pub mod chain;
 pub mod constants;
diff --git a/ergo-lib/src/wallet/box_selector.rs b/ergo-lib/src/wallet/box_selector.rs
index e0dc825a4..93e13c1fe 100644
--- a/ergo-lib/src/wallet/box_selector.rs
+++ b/ergo-lib/src/wallet/box_selector.rs
@@ -11,6 +11,7 @@ use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
 use ergotree_ir::chain::token::Token;
 use ergotree_ir::chain::token::TokenAmount;
+use ergotree_ir::chain::token::TokenAmountError;
 use ergotree_ir::chain::token::TokenId;
 pub use simple::*;
 
@@ -53,6 +54,10 @@ pub enum BoxSelectorError {
     /// BoxValue out of bounds
     #[error("BoxValue out of bounds")]
     BoxValueError(BoxValueError),
+
+    /// Token amount err
+    #[error("TokenAmountError: {0:?}")]
+    TokenAmountError(#[from] TokenAmountError),
 }
 
 impl From<BoxValueError> for BoxSelectorError {
@@ -126,30 +131,40 @@ pub fn sum_value<T: ErgoBoxAssets>(bs: &[T]) -> u64 {
 }
 
 /// Returns the total token amounts (all tokens combined)
-pub fn sum_tokens(ts: Option<&[Token]>) -> HashMap<TokenId, TokenAmount> {
+pub fn sum_tokens(ts: Option<&[Token]>) -> Result<HashMap<TokenId, TokenAmount>, TokenAmountError> {
     let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
-    ts.into_iter().flatten().for_each(|t| {
-        res.entry(t.token_id.clone())
-            .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
-            .or_insert(t.amount);
-    });
-    res
+    ts.into_iter().flatten().try_for_each(|t| {
+        if let Some(amt) = res.get_mut(&t.token_id) {
+            *amt = amt.checked_add(&t.amount)?;
+        } else {
+            res.insert(t.token_id.clone(), t.amount);
+        }
+        Ok(())
+    })?;
+    Ok(res)
 }
 
 /// Returns the total token amounts (all tokens combined) of the given boxes
-pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(bs: &[T]) -> HashMap<TokenId, TokenAmount> {
+pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(
+    bs: &[T],
+) -> Result<HashMap<TokenId, TokenAmount>, TokenAmountError> {
     let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
-    bs.iter().for_each(|b| {
-        b.tokens().into_iter().flatten().for_each(|t| {
-            res.entry(t.token_id.clone())
-                .and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
-                .or_insert(t.amount);
-        });
-    });
-    res
+    bs.iter().try_for_each(|b| {
+        b.tokens().into_iter().flatten().try_for_each(|t| {
+            if let Some(amt) = res.get_mut(&t.token_id) {
+                *amt = amt.checked_add(&t.amount)?;
+            } else {
+                res.insert(t.token_id.clone(), t.amount);
+            }
+
+            Ok(())
+        })
+    })?;
+    Ok(res)
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 mod tests {
 
     use ergotree_ir::chain::ergo_box::box_value::arbitrary::ArbBoxValueRange;
@@ -193,6 +208,7 @@ mod tests {
         assert_eq!(
             u64::from(
                 *sum_tokens_from_boxes(vec![b.clone(), b].as_slice())
+                    .unwrap()
                     .get(&token.token_id)
                     .unwrap()
             ),
diff --git a/ergo-lib/src/wallet/box_selector/simple.rs b/ergo-lib/src/wallet/box_selector/simple.rs
index 551cbfd2a..c4f35aa34 100644
--- a/ergo-lib/src/wallet/box_selector/simple.rs
+++ b/ergo-lib/src/wallet/box_selector/simple.rs
@@ -45,7 +45,8 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
         let mut selected_boxes_value: u64 = 0;
         let target_balance: u64 = target_balance.into();
         // sum all target tokens into hash map (think repeating token ids)
-        let mut target_tokens_left: HashMap<TokenId, TokenAmount> = sum_tokens(Some(target_tokens));
+        let mut target_tokens_left: HashMap<TokenId, TokenAmount> =
+            sum_tokens(Some(target_tokens))?;
         let mut has_value_change = false;
         let mut has_token_change = false;
         let mut sorted_inputs = inputs;
@@ -66,7 +67,7 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
         });
         // reverse, so they'll be sorted by descending order (boxes with target tokens will be first)
         sorted_inputs.reverse();
-        sorted_inputs.into_iter().for_each(|b| {
+        for b in sorted_inputs {
             let value_change_amt: u64 = if target_balance > selected_boxes_value {
                 0
             } else {
@@ -87,38 +88,39 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
                 }
                 let mut selected_tokens_from_this_box: HashMap<TokenId, TokenAmount> =
                     HashMap::new();
-                b.tokens().into_iter().flatten().for_each(|t| {
-                    if let Some(token_amount_left_to_select) =
-                        target_tokens_left.get(&t.token_id).cloned()
-                    {
-                        let token_amount_in_box = t.amount;
-                        if token_amount_left_to_select <= token_amount_in_box {
-                            target_tokens_left.remove(&t.token_id);
-                        } else {
-                            target_tokens_left
-                                .entry(t.token_id.clone())
-                                .and_modify(|amt| {
-                                    *amt = amt.checked_sub(&token_amount_in_box).unwrap()
-                                });
+                b.tokens()
+                    .into_iter()
+                    .flatten()
+                    .try_for_each::<_, Result<(), BoxSelectorError>>(|t| {
+                        if let Some(token_amount_left_to_select) =
+                            target_tokens_left.get(&t.token_id).cloned()
+                        {
+                            let token_amount_in_box = t.amount;
+                            if token_amount_left_to_select <= token_amount_in_box {
+                                target_tokens_left.remove(&t.token_id);
+                            } else if let Some(amt) = target_tokens_left.get_mut(&t.token_id) {
+                                *amt = amt.checked_sub(&token_amount_in_box)?;
+                            }
+
+                            let selected_token_amt =
+                                min(token_amount_in_box, token_amount_left_to_select);
+                            if let Some(amt) = selected_tokens_from_this_box.get_mut(&t.token_id) {
+                                *amt = amt.checked_add(&selected_token_amt)?;
+                            } else {
+                                selected_tokens_from_this_box
+                                    .insert(t.token_id.clone(), selected_token_amt);
+                            }
                         }
-                        let selected_token_amt =
-                            min(token_amount_in_box, token_amount_left_to_select);
-                        selected_tokens_from_this_box
-                            .entry(t.token_id.clone())
-                            .and_modify(|amt| {
-                                *amt = amt.checked_add(&selected_token_amt).unwrap();
-                            })
-                            .or_insert(selected_token_amt);
-                    }
-                });
-                if sum_tokens(b.tokens().as_ref().map(BoxTokens::as_ref))
+                        Ok(())
+                    })?;
+                if sum_tokens(b.tokens().as_ref().map(BoxTokens::as_ref))?
                     != selected_tokens_from_this_box
                 {
                     has_token_change = true;
                 };
                 selected_inputs.push(b);
             };
-        });
+        }
         if selected_boxes_value < target_balance {
             return Err(BoxSelectorError::NotEnoughCoins(
                 target_balance - selected_boxes_value,
@@ -133,7 +135,7 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
             vec![]
         } else {
             let change_value: BoxValue = (selected_boxes_value - target_balance).try_into()?;
-            let mut change_tokens = sum_tokens_from_boxes(selected_inputs.as_slice());
+            let mut change_tokens = sum_tokens_from_boxes(selected_inputs.as_slice())?;
             target_tokens.iter().try_for_each(|t| {
                 match change_tokens.get(&t.token_id).cloned() {
                     Some(selected_boxes_t_amt) if selected_boxes_t_amt == t.amount => {
@@ -143,7 +145,7 @@ impl<T: ErgoBoxAssets> BoxSelector<T> for SimpleBoxSelector {
                     Some(selected_boxes_t_amt) if selected_boxes_t_amt > t.amount => {
                         change_tokens.insert(
                             t.token_id.clone(),
-                            selected_boxes_t_amt.checked_sub(&t.amount).unwrap(),
+                            selected_boxes_t_amt.checked_sub(&t.amount)?,
                         );
                         Ok(())
                     }
@@ -170,6 +172,7 @@ impl Default for SimpleBoxSelector {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 mod tests {
     use std::convert::TryFrom;
 
@@ -217,8 +220,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens")
         }
 
@@ -237,8 +240,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens")
         }
 
@@ -266,8 +269,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
         }
 
@@ -285,8 +288,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                 sum_value(change_boxes_plus_out.as_slice()),
                 "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                 "all tokens from selected boxes should equal all tokens from the change boxes + target tokens")
         }
 
@@ -298,7 +301,7 @@ mod tests {
                                     any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 800).into()),
                                     target_token_amount in 1..100u64) {
             let s = SimpleBoxSelector::new();
-            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice()).unwrap();
             prop_assume!(!all_input_tokens.is_empty());
             let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get((all_input_tokens.len() - 1) / 2)
                                                                                     .cloned().unwrap();
@@ -313,8 +316,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
@@ -330,7 +333,7 @@ mod tests {
                                                target_balance in
                                                any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 500).into())) {
             let s = SimpleBoxSelector::new();
-            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice()).unwrap();
             prop_assume!(!all_input_tokens.is_empty());
             let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get((all_input_tokens.len() - 1) / 2)
                                                                                     .cloned().unwrap();
@@ -343,8 +346,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
@@ -362,7 +365,7 @@ mod tests {
                                        target_token1_amount in 1..100u64,
                                        target_token2_amount in 2..100u64) {
             let s = SimpleBoxSelector::new();
-            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice()).unwrap();
             prop_assume!(all_input_tokens.len() >= 2);
             let all_input_tokens_keys = all_input_tokens.keys().collect::<Vec<&TokenId>>();
             let target_token1_id = all_input_tokens_keys.first().cloned().unwrap();
@@ -388,8 +391,8 @@ mod tests {
             prop_assert_eq!(sum_value(selection.boxes.as_slice()),
                             sum_value(change_boxes_plus_out.as_slice()),
                             "total value of the selected boxes should equal target balance + total value in change boxes");
-            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()),
-                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()),
+            prop_assert_eq!(sum_tokens_from_boxes(selection.boxes.as_slice()).unwrap(),
+                            sum_tokens_from_boxes(change_boxes_plus_out.as_slice()).unwrap(),
                             "all tokens from selected boxes should equal all tokens from the change boxes + target tokens");
             prop_assert!(
                 selection.boxes.iter()
@@ -405,7 +408,7 @@ mod tests {
                                          target_balance in
                                          any_with::<BoxValue>((BoxValue::MIN_RAW * 100 .. BoxValue::MIN_RAW * 1000).into())) {
             let s = SimpleBoxSelector::new();
-            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice());
+            let all_input_tokens = sum_tokens_from_boxes(inputs.as_slice()).unwrap();
             prop_assume!(!all_input_tokens.is_empty());
             let target_token_id = all_input_tokens.keys().collect::<Vec<&TokenId>>().get(0).cloned().unwrap();
             let input_token_amount = u64::from(*all_input_tokens.get(target_token_id).unwrap()) / 2;
diff --git a/ergo-lib/src/wallet/ext_pub_key.rs b/ergo-lib/src/wallet/ext_pub_key.rs
index 84634f6cf..e696f1b07 100644
--- a/ergo-lib/src/wallet/ext_pub_key.rs
+++ b/ergo-lib/src/wallet/ext_pub_key.rs
@@ -48,7 +48,10 @@ impl ExtPubKey {
         })
     }
 
+    #[allow(clippy::unwrap_used)]
     fn pub_key_bytes(&self) -> PubKeyBytes {
+        // Unwraps are fine here since `self.public_key` is valid through the checking constructor
+        // above.
         self.public_key
             .sigma_serialize_bytes()
             .unwrap()
@@ -58,7 +61,9 @@ impl ExtPubKey {
     }
 
     /// Soft derivation of the child public key with a given index
+    #[allow(clippy::unwrap_used)]
     pub fn derive(&self, index: ChildIndexNormal) -> Self {
+        // Unwrap is fine due to `ChainCode` type having fixed length of 32.
         let mut mac = HmacSha512::new_from_slice(&self.chain_code).unwrap();
         mac.update(&self.pub_key_bytes());
         mac.update(
@@ -98,6 +103,7 @@ impl From<ExtPubKey> for Address {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
     use crate::wallet::derivation_path::ChildIndexHardened;
 
diff --git a/ergo-lib/src/wallet/secret_key.rs b/ergo-lib/src/wallet/secret_key.rs
index 08bb1e77f..f51208251 100644
--- a/ergo-lib/src/wallet/secret_key.rs
+++ b/ergo-lib/src/wallet/secret_key.rs
@@ -51,6 +51,7 @@ impl From<DlogProverInput> for SecretKey {
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
     use super::*;
     use std::convert::TryInto;
diff --git a/ergo-lib/src/wallet/signing.rs b/ergo-lib/src/wallet/signing.rs
index 8410ee203..f9f1a1865 100644
--- a/ergo-lib/src/wallet/signing.rs
+++ b/ergo-lib/src/wallet/signing.rs
@@ -219,6 +219,7 @@ pub fn sign_reduced_transaction(
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 mod tests {
     use super::*;
     use ergotree_interpreter::sigma_protocol::private_input::DlogProverInput;
diff --git a/ergo-lib/src/wallet/tx_builder.rs b/ergo-lib/src/wallet/tx_builder.rs
index 37f29c792..2da351461 100644
--- a/ergo-lib/src/wallet/tx_builder.rs
+++ b/ergo-lib/src/wallet/tx_builder.rs
@@ -1,6 +1,7 @@
 //! Builder for an UnsignedTransaction
 
 use ergotree_ir::chain::ergo_box::box_value::BoxValueError;
+use ergotree_ir::chain::token::TokenAmountError;
 use std::collections::HashSet;
 use std::convert::TryInto;
 
@@ -199,8 +200,8 @@ 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 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
@@ -248,12 +249,13 @@ impl<S: ErgoBoxAssets + ErgoBoxId + Clone> TxBuilder<S> {
 }
 
 /// Suggested transaction fee (1100000 nanoERGs, semi-default value used across wallets and dApps as of Oct 2020)
-#[allow(non_snake_case)]
+#[allow(non_snake_case, clippy::unwrap_used)]
 pub fn SUGGESTED_TX_FEE() -> BoxValue {
     BoxValue::new(1100000u64).unwrap()
 }
 
 /// Create a box with miner's contract and a given value
+#[allow(clippy::unwrap_used)]
 pub fn new_miner_fee_box(
     fee_amount: BoxValue,
     creation_height: u32,
@@ -301,9 +303,13 @@ pub enum TxBuilderError {
     /// Input box was unable to be retrieved
     #[error("Empty input box")]
     EmptyInputBoxSelection,
+    /// Token amount err
+    #[error("TokenAmountError: {0:?}")]
+    TokenAmountError(#[from] TokenAmountError),
 }
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::panic)]
 mod tests {
 
     use std::convert::TryInto;
@@ -586,7 +592,7 @@ mod tests {
                          change_address in any::<Address>(),
                          miners_fee in any_with::<BoxValue>((BoxValue::MIN_RAW * 100..BoxValue::MIN_RAW * 200).into()),
                          data_inputs in vec(any::<DataInput>(), 0..2)) {
-            prop_assume!(sum_tokens_from_boxes(outputs.as_slice()).is_empty());
+            prop_assume!(sum_tokens_from_boxes(outputs.as_slice()).unwrap().is_empty());
             let min_change_value = BoxValue::SAFE_USER_MIN;
             let all_outputs = checked_sum(outputs.iter().map(|b| b.value)).unwrap()
                                                                              .checked_add(&miners_fee)


Test patch: