Repository: gakonst/ethers-rs (rust)

Issue:
Parsing of contract input for fixed uint8 array (uint8[8]) fails
**Version**
ethers v0.17.0

**Platform**
Mac

**Description**
I tried this code:
```rust
use ethers::prelude::*;
use ethers::abi::AbiDecode;

abigen!(
    WyvernExchangeV1,
    r#"[
        function atomicMatch_(address[14] addrs, uint[18] uints, uint8[8] feeMethodsSidesKindsHowToCalls, bytes calldataBuy, bytes calldataSell, bytes replacementPatternBuy, bytes replacementPatternSell, bytes staticExtradataBuy, bytes staticExtradataSell, uint8[2] vs, bytes32[5] rssMetadata) public payable
    ]"#,
);

fn main() {
    let tx_input = "0xab834bab0000000000000000000000007be8076f4ea4a4ad08075c2508e481d6c946d12b000000000000000000000000fb945f53de110bb7a1f333800a2667193487d9b9000000000000000000000000bdd95abe8a7694ccd77143376b0fbea183e6a7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000baf2127b49fc93cbca6269fade0f7f31df4c88a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007be8076f4ea4a4ad08075c2508e481d6c946d12b000000000000000000000000bdd95abe8a7694ccd77143376b0fbea183e6a74000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b3256965e7c3cf26e11fcaf296dfc8807c01073000000000000000000000000baf2127b49fc93cbca6269fade0f7f31df4c88a700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca8132b0328000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000620ff04f0000000000000000000000000000000000000000000000000000000000000000b6ec24daa9e002e859d07bb2537e1f7ffaa3d166e97d9fc906256ae79100757e000000000000000000000000000000000000000000000000000000000000035200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ca8132b0328000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000620fdb5100000000000000000000000000000000000000000000000000000000621915d100000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000007c000000000000000000000000000000000000000000000000000000000000008e00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b200000000000000000000000000000000000000000000000000000000000000b40000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001bd11a707df92259abf886f9324ace4e6c83d29e2c223d794c1972b88fb047fe923111bae93153b5aed4c898fca68037ac454abf2bd805c6cfbb17ffc68eb0b144d11a707df92259abf886f9324ace4e6c83d29e2c223d794c1972b88fb047fe923111bae93153b5aed4c898fca68037ac454abf2bd805c6cfbb17ffc68eb0b144000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4fb16a5950000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb945f53de110bb7a1f333800a2667193487d9b9000000000000000000000000f1ee7548c02f6f53b41c3397bd052131a3472cc70000000000000000000000000000000000000000000000000000000000001af5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4fb16a595000000000000000000000000bdd95abe8a7694ccd77143376b0fbea183e6a7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f1ee7548c02f6f53b41c3397bd052131a3472cc70000000000000000000000000000000000000000000000000000000000001af5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e400000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
    let call_data: Bytes = tx_input.parse().unwrap();
    let decoded = AtomicMatchCall::decode(&call_data).unwrap();
}
```

Results in:
```
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: DetokenizationError(InvalidOutputType("Expected `FixedBytes(8)`, got FixedArray([Uint(1), Uint(0), Uint(0), Uint(1), Uint(1), Uint(1), Uint(0), Uint(1)])"))',
```
It seems that the field `uint8[8] feeMethodsSidesKindsHowToCalls` can not be parsed correctly. I would expect no error.

I guess the error is based in `ethers-core/src/abi/tokens.rs`. The `impl<const N: usize> Tokenizable for [u8; N] {` should parse maybe as well FixedArray? 

I copy/pasted very quick a test case that should pass I guess in `tokens.rs`. Hope I am not leading in a wrong direction:
```rust
    #[test]
    fn should_decode_array_of_fixed_uint8() {
        // uint8[8]
        let tokens = vec![Token::FixedArray(vec![
            Token::Uint(1.into()),
            Token::Uint(2.into()),
            Token::Uint(3.into()),
            Token::Uint(4.into()),
            Token::Uint(5.into()),
            Token::Uint(6.into()),
            Token::Uint(7.into()),
            Token::Uint(8.into()),
        ])];
        let data: [u8; 8] = Detokenize::from_tokens(tokens).unwrap();
        assert_eq!(data[0], 1);
        assert_eq!(data[1], 2);
        assert_eq!(data[2], 3);
        assert_eq!(data[7], 8);
    }
```

Pull request:
feat: add uint8 type
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
Closes #1636
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
* add `Uint8` wrapper type, that's only used for `[Uint8; <>] arrays
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65ed438c..30ed61aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Unreleased
 
+- Add `Unit8` helper type [#1639](https://github.com/gakonst/ethers-rs/pull/1639)
 - Add `evm.deployedBytecode.immutableReferences` output selector [#1523](https://github.com/gakonst/ethers-rs/pull/1523)
 - Added `get_erc1155_token_transfer_events` function for etherscan client [#1503](https://github.com/gakonst/ethers-rs/pull/1503)
 - Add support for Geth `debug_traceTransaction` [#1469](https://github.com/gakonst/ethers-rs/pull/1469)
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 314d03df..58a2aaff 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -356,6 +356,7 @@ impl Context {
         param: &str,
         kind: &ParamType,
     ) -> Result<TokenStream> {
+        let ethers_core = ethers_core_crate();
         match kind {
             ParamType::Array(ty) => {
                 let ty = self.expand_input_param_type(fun, param, ty)?;
@@ -364,7 +365,18 @@ impl Context {
                 })
             }
             ParamType::FixedArray(ty, size) => {
-                let ty = self.expand_input_param_type(fun, param, ty)?;
+                let ty = match **ty {
+                    ParamType::Uint(size) => {
+                        if size / 8 == 1 {
+                            // this prevents type ambiguity with `FixedBytes`
+                            quote! { #ethers_core::types::Uint8}
+                        } else {
+                            self.expand_input_param_type(fun, param, ty)?
+                        }
+                    }
+                    _ => self.expand_input_param_type(fun, param, ty)?,
+                };
+
                 let size = *size;
                 Ok(quote! {[#ty; #size]})
             }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
index acde39b3..78d66501 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
@@ -41,7 +41,17 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
         }
         ParamType::FixedArray(t, n) => {
             // TODO(nlordell): see above
-            let inner = expand(t)?;
+            let inner = match **t {
+                ParamType::Uint(size) => {
+                    if size / 8 == 1 {
+                        // this prevents type ambiguity with `FixedBytes`
+                        quote! { #ethers_core::types::Uint8}
+                    } else {
+                        expand(t)?
+                    }
+                }
+                _ => expand(t)?,
+            };
             let size = Literal::usize_unsuffixed(*n);
             Ok(quote! { [#inner; #size] })
         }
diff --git a/ethers-core/src/abi/codec.rs b/ethers-core/src/abi/codec.rs
index 45b0ad2d..47970e0e 100644
--- a/ethers-core/src/abi/codec.rs
+++ b/ethers-core/src/abi/codec.rs
@@ -2,7 +2,7 @@ use crate::{
     abi::{
         AbiArrayType, AbiError, AbiType, Detokenize, Token, Tokenizable, TokenizableItem, Tokenize,
     },
-    types::{Address, Bytes, H256, I256, U128, U256},
+    types::{Address, Bytes, Uint8, H256, I256, U128, U256},
 };
 
 /// Trait for ABI encoding
@@ -65,6 +65,7 @@ impl_abi_codec!(
     U128,
     U256,
     I256,
+    Uint8,
     u8,
     u16,
     u32,
@@ -279,4 +280,24 @@ mod tests {
         let encoded = value.encode();
         assert_eq!(value, String::decode(encoded).unwrap());
     }
+
+    #[test]
+    fn should_decode_array_of_fixed_uint8() {
+        // uint8[8]
+        let tokens = vec![Token::FixedArray(vec![
+            Token::Uint(1.into()),
+            Token::Uint(2.into()),
+            Token::Uint(3.into()),
+            Token::Uint(4.into()),
+            Token::Uint(5.into()),
+            Token::Uint(6.into()),
+            Token::Uint(7.into()),
+            Token::Uint(8.into()),
+        ])];
+        let data: [Uint8; 8] = Detokenize::from_tokens(tokens).unwrap();
+        assert_eq!(data[0], 1);
+        assert_eq!(data[1], 2);
+        assert_eq!(data[2], 3);
+        assert_eq!(data[7], 8);
+    }
 }
diff --git a/ethers-core/src/abi/human_readable/lexer.rs b/ethers-core/src/abi/human_readable/lexer.rs
index 4c7fea58..6347565a 100644
--- a/ethers-core/src/abi/human_readable/lexer.rs
+++ b/ethers-core/src/abi/human_readable/lexer.rs
@@ -1218,4 +1218,11 @@ mod tests {
             event
         );
     }
+
+    #[test]
+    fn parse_large_function() {
+        let f = "function atomicMatch_(address[14] addrs, uint[18] uints, uint8[8] feeMethodsSidesKindsHowToCalls, bytes calldataBuy, bytes calldataSell, bytes replacementPatternBuy, bytes replacementPatternSell, bytes staticExtradataBuy, bytes staticExtradataSell, uint8[2] vs, bytes32[5] rssMetadata) public payable";
+
+        let _fun = HumanReadableParser::parse_function(f).unwrap();
+    }
 }
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index bf31b833..a13e729b 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -1,10 +1,9 @@
 //! This module implements extensions to the [`ethabi`](https://docs.rs/ethabi) API.
 // Adapted from [Gnosis' ethcontract](https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs)
 use crate::{
-    types::{Bytes, Selector},
+    types::{Bytes, Selector, Uint8, H256, H512, I256, U128, U256, U64},
     utils::id,
 };
-
 pub use ethabi::{self, Contract as Abi, *};
 
 mod tokens;
@@ -23,9 +22,6 @@ mod human_readable;
 pub use human_readable::{
     lexer::HumanReadableParser, parse as parse_abi, parse_str as parse_abi_str, AbiParser,
 };
-
-use crate::types::{H256, H512, I256, U128, U256, U64};
-
 mod sealed {
     use ethabi::{Event, Function};
 
@@ -166,6 +162,7 @@ impl_abi_type!(
     str => String,
     H256 => FixedBytes(32),
     H512 => FixedBytes(64),
+    Uint8 => Uint(8),
     U64 => Uint(64),
     U128 => Uint(128),
     U256 => Uint(256),
diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs
index 9c10f25f..c97dfef3 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -24,6 +24,9 @@ pub use path_or_string::PathOrString;
 mod u256;
 pub use u256::*;
 
+mod uint8;
+pub use uint8::*;
+
 mod i256;
 pub use i256::{Sign, I256};
 
diff --git a/ethers-core/src/types/uint8.rs b/ethers-core/src/types/uint8.rs
new file mode 100644
index 00000000..3c4ca0e7
--- /dev/null
+++ b/ethers-core/src/types/uint8.rs
@@ -0,0 +1,109 @@
+//! This module contains a helper type for `uint8`
+//!
+//! The reason this exists is to circumvent ambiguity with fixed bytes arrays
+
+use crate::abi::{InvalidOutputType, Tokenizable, TokenizableItem};
+use ethabi::{ethereum_types::U256, Token};
+use serde::{Deserialize, Serialize};
+use std::ops::{Add, Sub};
+
+/// A wrapper for `u8`
+///
+/// Note: this type is only necessary in conjunction with `FixedBytes` so that `[Uint8; 8]` is
+/// recognized as `uint8[8]` and not fixed bytes.
+///
+/// See also <https://github.com/gakonst/ethers-rs/issues/1636>
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default, Ord, PartialOrd)]
+#[repr(transparent)]
+#[serde(transparent)]
+pub struct Uint8(u8);
+
+impl From<u8> for Uint8 {
+    fn from(val: u8) -> Self {
+        Uint8(val)
+    }
+}
+
+impl From<Uint8> for u8 {
+    fn from(val: Uint8) -> Self {
+        val.0
+    }
+}
+
+impl From<Uint8> for U256 {
+    fn from(val: Uint8) -> Self {
+        U256::from(val.0)
+    }
+}
+
+impl PartialEq<u8> for Uint8 {
+    fn eq(&self, other: &u8) -> bool {
+        self.0 == *other
+    }
+}
+
+impl Add for Uint8 {
+    type Output = Self;
+
+    fn add(self, rhs: Self) -> Self::Output {
+        Uint8(self.0 + rhs.0)
+    }
+}
+
+impl Sub for Uint8 {
+    type Output = Self;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        Uint8(self.0 - rhs.0)
+    }
+}
+
+impl Add<u8> for Uint8 {
+    type Output = Self;
+
+    fn add(self, rhs: u8) -> Self::Output {
+        Uint8(self.0 + rhs)
+    }
+}
+
+impl Sub<u8> for Uint8 {
+    type Output = Self;
+
+    fn sub(self, rhs: u8) -> Self::Output {
+        Uint8(self.0 - rhs)
+    }
+}
+
+impl Tokenizable for Uint8 {
+    fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
+        match token {
+            Token::Int(data) | Token::Uint(data) => {
+                if data > U256::from(u8::MAX) {
+                    return Err(InvalidOutputType("Integer overflow when casting to u8".to_string()))
+                }
+                Ok(Uint8(data.low_u32() as u8))
+            }
+            other => Err(InvalidOutputType(format!("Expected `uint8`, got {:?}", other))),
+        }
+    }
+    fn into_token(self) -> Token {
+        Token::Uint(self.into())
+    }
+}
+
+impl TokenizableItem for Uint8 {}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::abi::AbiType;
+    use ethabi::ParamType;
+
+    #[test]
+    fn uint8_array() {
+        assert_eq!(
+            <[Uint8; 8usize]>::param_type(),
+            ParamType::FixedArray(Box::new(ParamType::Uint(8),), 8)
+        );
+    }
+}


Test patch:
diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs
index e8d2c441..77922d1a 100644
--- a/ethers-contract/tests/it/abigen.rs
+++ b/ethers-contract/tests/it/abigen.rs
@@ -679,3 +679,13 @@ fn can_generate_large_output_struct() {
 
     let r = GetByIdReturn(Info::default());
 }
+
+#[test]
+fn gen_complex_function() {
+    abigen!(
+        WyvernExchangeV1,
+        r#"[
+        function atomicMatch_(address[14] addrs, uint[18] uints, uint8[8] feeMethodsSidesKindsHowToCalls, bytes calldataBuy, bytes calldataSell, bytes replacementPatternBuy, bytes replacementPatternSell, bytes staticExtradataBuy, bytes staticExtradataSell, uint8[2] vs, bytes32[5] rssMetadata) public payable
+    ]"#,
+    );
+}