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

Issue:
Ban `unwrap` in ergo-lib-wasm
Add the following to `lib.rs`:
```rust
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::todo)]
#![deny(clippy::unimplemented)]
```
Most of the errors can be eliminated with error handling. The cases where `unwrap` is necessary should be enabled with `allow` attribute with a comment explaining the reason.


Discussion / hints:

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/ast.rs b/bindings/ergo-lib-wasm/src/ast.rs
index dc84417a0..f36061b22 100644
--- a/bindings/ergo-lib-wasm/src/ast.rs
+++ b/bindings/ergo-lib-wasm/src/ast.rs
@@ -73,9 +73,9 @@ impl Constant {
     }
 
     /// Create BigInt constant from byte array (signed bytes bit-endian)
-    pub fn from_bigint_signed_bytes_be(num: &[u8]) -> Constant {
-        Constant(ergo_lib::ergotree_ir::mir::constant::Constant::from(
-            BigInt256::try_from(num).unwrap(),
+    pub fn from_bigint_signed_bytes_be(num: &[u8]) -> Result<Constant, JsValue> {
+        Ok(Constant(
+            ergo_lib::ergotree_ir::mir::constant::Constant::from(BigInt256::try_from(num)?),
         ))
     }
 
diff --git a/bindings/ergo-lib-wasm/src/block_header.rs b/bindings/ergo-lib-wasm/src/block_header.rs
index 9c674e95e..8ea93dff9 100644
--- a/bindings/ergo-lib-wasm/src/block_header.rs
+++ b/bindings/ergo-lib-wasm/src/block_header.rs
@@ -8,7 +8,7 @@ use derive_more::{From, Into};
 
 use crate::error_conversion::to_js;
 use ergo_lib::chain::ergo_state_context::Headers;
-use std::convert::TryInto;
+use std::convert::{TryFrom, TryInto};
 
 /// Block header
 #[wasm_bindgen]
@@ -100,10 +100,19 @@ impl From<BlockHeaders> for Vec<Header> {
     }
 }
 
-impl From<BlockHeaders> for Headers {
-    fn from(bs: BlockHeaders) -> Self {
+impl TryFrom<BlockHeaders> for Headers {
+    type Error = JsValue;
+    fn try_from(bs: BlockHeaders) -> Result<Self, Self::Error> {
         let headers: Vec<Header> = bs.0.into_iter().map(Header::from).collect();
-
-        headers.try_into().unwrap()
+        if headers.len() == 10 {
+            #[allow(clippy::unwrap_used)]
+            Ok(headers.try_into().unwrap())
+        } else {
+            Err(js_sys::Error::new(&format!(
+                "Incorrect number of block headers, expected 10 but got {}",
+                headers.len()
+            ))
+            .into())
+        }
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/context_extension.rs b/bindings/ergo-lib-wasm/src/context_extension.rs
index a72e0cdf7..18db6544f 100644
--- a/bindings/ergo-lib-wasm/src/context_extension.rs
+++ b/bindings/ergo-lib-wasm/src/context_extension.rs
@@ -34,10 +34,15 @@ impl ContextExtension {
         wrapped.values.len()
     }
     /// get from map or fail if key is missing
-    pub fn get(&self, key: u8) -> Constant {
+    pub fn get(&self, key: u8) -> Result<Constant, JsValue> {
         let wrapped: ergo_lib::ergotree_interpreter::sigma_protocol::prover::ContextExtension =
             self.0.clone();
-        wrapped.values.get(&key).unwrap().clone().into()
+        Ok(wrapped
+            .values
+            .get(&key)
+            .ok_or_else::<JsValue, _>(|| "err".into())?
+            .clone()
+            .into())
     }
 
     /// Returns all keys in the map
diff --git a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
index 469467eed..680f0352d 100644
--- a/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
+++ b/bindings/ergo-lib-wasm/src/ergo_state_ctx.rs
@@ -1,5 +1,7 @@
 //! Ergo blockchain state (for ErgoTree evaluation)
 use ergo_lib::chain;
+use ergo_lib::chain::ergo_state_context::Headers;
+use std::convert::TryFrom;
 use wasm_bindgen::prelude::*;
 
 extern crate derive_more;
@@ -18,17 +20,7 @@ impl ErgoStateContext {
     /// Create new context from pre-header
     #[wasm_bindgen(constructor)]
     pub fn new(pre_header: PreHeader, headers: BlockHeaders) -> Result<ErgoStateContext, JsValue> {
-        match headers.len() {
-            10 => Ok(chain::ergo_state_context::ErgoStateContext::new(
-                pre_header.into(),
-                headers.into(),
-            )
-            .into()),
-            h => Err(js_sys::Error::new(&format!(
-                "Not enough block headers, expected 10 but got {}",
-                h
-            ))
-            .into()),
-        }
+        let headers = Headers::try_from(headers)?;
+        Ok(chain::ergo_state_context::ErgoStateContext::new(pre_header.into(), headers).into())
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/json.rs b/bindings/ergo-lib-wasm/src/json.rs
index 95f945c48..fceae18c5 100644
--- a/bindings/ergo-lib-wasm/src/json.rs
+++ b/bindings/ergo-lib-wasm/src/json.rs
@@ -39,10 +39,10 @@ impl From<Transaction> for TransactionJsonEip12 {
     fn from(t: Transaction) -> Self {
         TransactionJsonEip12 {
             tx_id: t.id(),
-            inputs: t.inputs.try_into().unwrap(),
+            inputs: t.inputs.as_vec().clone(),
             data_inputs: t
                 .data_inputs
-                .map(|di| di.try_into().unwrap())
+                .map(|di| di.as_vec().clone())
                 .unwrap_or_else(Vec::new),
             outputs: t.outputs.into_iter().map(|b| b.into()).collect(),
         }
@@ -66,6 +66,8 @@ pub(crate) struct UnsignedTransactionJsonEip12 {
 
 impl From<UnsignedTransaction> for UnsignedTransactionJsonEip12 {
     fn from(t: UnsignedTransaction) -> Self {
+        // Following unwraps are fine since we're converting from BoundedVec to Vec.
+        #[allow(clippy::unwrap_used)]
         UnsignedTransactionJsonEip12 {
             inputs: t.inputs.try_into().unwrap(),
             data_inputs: t
@@ -203,6 +205,7 @@ pub struct TokenAmountJsonEip12(
 );
 
 #[cfg(test)]
+#[allow(clippy::unwrap_used)]
 mod tests {
     use super::*;
 
diff --git a/bindings/ergo-lib-wasm/src/lib.rs b/bindings/ergo-lib-wasm/src/lib.rs
index ab7855ace..24bb417d5 100644
--- a/bindings/ergo-lib-wasm/src/lib.rs
+++ b/bindings/ergo-lib-wasm/src/lib.rs
@@ -15,6 +15,10 @@
 #![allow(clippy::len_without_is_empty)]
 #![allow(clippy::unused_unit)]
 #![deny(clippy::wildcard_enum_match_arm)]
+#![deny(clippy::unwrap_used)]
+#![deny(clippy::expect_used)]
+#![deny(clippy::todo)]
+#![deny(clippy::unimplemented)]
 
 pub mod address;
 pub mod ast;
diff --git a/bindings/ergo-lib-wasm/src/token.rs b/bindings/ergo-lib-wasm/src/token.rs
index 352459cb9..c2c536344 100644
--- a/bindings/ergo-lib-wasm/src/token.rs
+++ b/bindings/ergo-lib-wasm/src/token.rs
@@ -152,19 +152,34 @@ impl Tokens {
     }
 
     /// Returns the element of the collection with a given index
-    pub fn get(&self, index: usize) -> Token {
-        self.0.as_ref().unwrap().get(index).unwrap().clone()
+    pub fn get(&self, index: usize) -> Result<Token, JsValue> {
+        Ok(self
+            .0
+            .as_ref()
+            .ok_or_else::<JsValue, _>(|| "Tokens::get: no tokens available".into())?
+            .get(index)
+            .ok_or_else::<JsValue, _>(|| "".into())?
+            .clone())
     }
 
     /// Adds an elements to the collection
-    pub fn add(&mut self, elem: &Token) {
+    pub fn add(&mut self, elem: &Token) -> Result<(), JsValue> {
         if self.0.is_some() {
+            #[allow(clippy::unwrap_used)]
             let mut new_vec = self.0.as_ref().unwrap().as_vec().clone();
+
+            if new_vec.len() >= 255 {
+                return Err("Tokens::add: can't have more than 255 tokens".into());
+            }
+
             new_vec.push(elem.clone());
-            self.0 = Some(BoxTokens::from_vec(new_vec).unwrap()); // TODO: BoundedVec does not have any way to try to push elements. Right now the best way seems to be to just panic if someone tries to add > 255 tokens
+            #[allow(clippy::unwrap_used)]
+            let box_tokens = BoxTokens::from_vec(new_vec).unwrap();
+            self.0 = Some(box_tokens);
         } else {
             self.0 = Some(BoxTokens::from([elem.clone()]));
         }
+        Ok(())
     }
 }
 
@@ -178,10 +193,6 @@ impl From<Tokens> for Option<chain::ergo_box::BoxTokens> {
 }
 impl From<Option<chain::ergo_box::BoxTokens>> for Tokens {
     fn from(v: Option<chain::ergo_box::BoxTokens>) -> Self {
-        let mut tokens = Tokens::new();
-        for token in v.iter().flatten() {
-            tokens.add(&Token(token.clone()))
-        }
-        tokens
+        Tokens(v.map(|t| t.mapped(Token)))
     }
 }
diff --git a/bindings/ergo-lib-wasm/src/transaction.rs b/bindings/ergo-lib-wasm/src/transaction.rs
index 7ea7abbcb..f5452ac13 100644
--- a/bindings/ergo-lib-wasm/src/transaction.rs
+++ b/bindings/ergo-lib-wasm/src/transaction.rs
@@ -129,7 +129,7 @@ pub fn extract_hints(
             real_propositions.0,
             simulated_propositions.0,
         )
-        .unwrap(),
+        .map_err(to_js)?,
     ))
 }
 
diff --git a/bindings/ergo-lib-wasm/src/transaction/reduced.rs b/bindings/ergo-lib-wasm/src/transaction/reduced.rs
index 24c08b00a..5b1d82616 100644
--- a/bindings/ergo-lib-wasm/src/transaction/reduced.rs
+++ b/bindings/ergo-lib-wasm/src/transaction/reduced.rs
@@ -25,12 +25,10 @@ impl Propositions {
     }
 
     /// Adding new proposition
-    pub fn add_proposition_from_byte(&mut self, proposition: Vec<u8>) {
-        self.0.push(
-            SigmaBoolean::sigma_parse_bytes(&proposition)
-                .map_err(to_js)
-                .unwrap(),
-        );
+    pub fn add_proposition_from_byte(&mut self, proposition: Vec<u8>) -> Result<(), JsValue> {
+        self.0
+            .push(SigmaBoolean::sigma_parse_bytes(&proposition).map_err(to_js)?);
+        Ok(())
     }
 }
 


Test patch: