Repository: gakonst/ethers-rs
Pull request #613 (2021-11-23 18:57:19)

fix(codec)!: ambiguity unit8[] and bytes
<!--
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
* There was an ambiguity for how to tokenized `Vec<u8>` since it was used for `bytes` and `unit8[]` alike. 
<!--
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
* solidity `bytes` is `ethers::types::Bytes` and
* solidity `unit8[]` is `Vec<u8>`

This is a breaking change but can we fixed easily with `into()` conventions

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [ ] Added Documentation
- [x] Updated the changelog



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df2a976b..471af9cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ## ethers-core
 
+- Change types mapping for solidity `bytes` to rust `ethers::core::Bytes` and solidity `uint8[]` to rust `Vec<u8>`.
+  [613](https://github.com/gakonst/ethers-rs/pull/613)
 - Fix `format_units` to return a `String` of representing a decimal point float
   such that the decimal places don't get truncated.
   [597](https://github.com/gakonst/ethers-rs/pull/597)
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
index e44def58..4100c35f 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
@@ -8,7 +8,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
 
     match kind {
         ParamType::Address => Ok(quote! { #ethers_core::types::Address }),
-        ParamType::Bytes => Ok(quote! { Vec<u8> }),
+        ParamType::Bytes => Ok(quote! { #ethers_core::types::Bytes }),
         ParamType::Int(n) => match n / 8 {
             1 => Ok(quote! { i8 }),
             2 => Ok(quote! { i16 }),
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 86affacc..096969d6 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -297,7 +297,7 @@ impl<M: Middleware> Multicall<M> {
             .iter()
             .zip(&return_data)
             .map(|(call, bytes)| {
-                let mut tokens: Vec<Token> = call.function.decode_output(bytes)?;
+                let mut tokens: Vec<Token> = call.function.decode_output(bytes.as_ref())?;
 
                 Ok(match tokens.len() {
                     0 => Token::Tuple(vec![]),
@@ -343,10 +343,10 @@ impl<M: Middleware> Multicall<M> {
         Ok(tx_hash)
     }
 
-    fn as_contract_call(&self) -> ContractCall<M, (U256, Vec<Vec<u8>>)> {
+    fn as_contract_call(&self) -> ContractCall<M, (U256, Vec<Bytes>)> {
         // Map the Multicall struct into appropriate types for `aggregate` function
-        let calls: Vec<(Address, Vec<u8>)> =
-            self.calls.iter().map(|call| (call.target, call.data.to_vec())).collect();
+        let calls: Vec<(Address, Bytes)> =
+            self.calls.iter().map(|call| (call.target, call.data.clone())).collect();
 
         // Construct the ContractCall for `aggregate` function to broadcast the transaction
         let mut contract_call = self.contract.aggregate(calls);
diff --git a/ethers-contract/src/multicall/multicall_contract.rs b/ethers-contract/src/multicall/multicall_contract.rs
index 87c22faa..5706736f 100644
--- a/ethers-contract/src/multicall/multicall_contract.rs
+++ b/ethers-contract/src/multicall/multicall_contract.rs
@@ -41,8 +41,8 @@ mod multicallcontract_mod {
         #[doc = "Calls the contract's `aggregate` (0x252dba42) function"]
         pub fn aggregate(
             &self,
-            calls: Vec<(Address, Vec<u8>)>,
-        ) -> ContractCall<M, (U256, Vec<Vec<u8>>)> {
+            calls: Vec<(Address, Bytes)>,
+        ) -> ContractCall<M, (U256, Vec<Bytes>)> {
             self.0
                 .method_hash([37, 45, 186, 66], calls)
                 .expect("method not found (this should never happen)")
diff --git a/ethers-core/ethers-derive-eip712/Cargo.toml b/ethers-core/ethers-derive-eip712/Cargo.toml
index 912b9b21..ee6a1209 100644
--- a/ethers-core/ethers-derive-eip712/Cargo.toml
+++ b/ethers-core/ethers-derive-eip712/Cargo.toml
@@ -18,6 +18,6 @@ serde_json = "1.0.68"
 proc-macro2 = "1.0.29"
 
 [dev-dependencies]
-ethers-contract = { version = "^0.6.0", path = "../../ethers-contract"}
+ethers-contract = { version = "^0.6.0", path = "../../ethers-contract", features = ["abigen"]}
 ethers-contract-derive = { version = "^0.6.0", path = "../../ethers-contract/ethers-contract-derive" }
 ethers-signers = { version = "^0.6.0", path = "../../ethers-signers" }
diff --git a/ethers-core/ethers-derive-eip712/src/lib.rs b/ethers-core/ethers-derive-eip712/src/lib.rs
index 5fe9594e..f42a6cbe 100644
--- a/ethers-core/ethers-derive-eip712/src/lib.rs
+++ b/ethers-core/ethers-derive-eip712/src/lib.rs
@@ -18,7 +18,7 @@
 //!
 //! # Example Usage
 //!
-//! ```rust
+//! ```ignore
 //! use ethers_contract::EthAbiType;
 //! use ethers_derive_eip712::*;
 //! use ethers_core::types::{transaction::eip712::Eip712, H160};
@@ -50,7 +50,7 @@
 //!     project: "radicle-reward".to_string(),
 //! };
 //!
-//! let hash = puzzle.encode_eip712()?;
+//! let hash = puzzle.encode_eip712().unwrap();
 //! ```
 //!
 //! # Limitations
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index 29f60512..cc169e40 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -1,6 +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::Selector, utils::id};
+use crate::{
+    types::{Bytes, Selector},
+    utils::id,
+};
 
 pub use ethabi::{Contract as Abi, *};
 
@@ -117,7 +120,8 @@ macro_rules! impl_abi_type {
 }
 
 impl_abi_type!(
-    Vec<u8> => Bytes,
+    Bytes => Bytes,
+    Vec<u8> =>  Array(Box::new(ParamType::Uint(8))),
     Address => Address,
     bool => Bool,
     String => String,
@@ -231,10 +235,17 @@ mod tests {
 
     #[test]
     fn abi_type_works() {
-        assert_eq!(ParamType::Bytes, Vec::<u8>::param_type());
-        assert_eq!(ParamType::Array(Box::new(ParamType::Bytes)), Vec::<Vec<u8>>::param_type());
+        assert_eq!(ParamType::Bytes, Bytes::param_type());
+        assert_eq!(ParamType::Array(Box::new(ParamType::Uint(8))), Vec::<u8>::param_type());
+        assert_eq!(ParamType::Array(Box::new(ParamType::Bytes)), Vec::<Bytes>::param_type());
         assert_eq!(
-            ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Bytes)))),
+            ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Uint(8))))),
+            Vec::<Vec<u8>>::param_type()
+        );
+        assert_eq!(
+            ParamType::Array(Box::new(ParamType::Array(Box::new(ParamType::Array(Box::new(
+                ParamType::Uint(8)
+            )))))),
             Vec::<Vec<Vec<u8>>>::param_type()
         );
 
@@ -242,7 +253,7 @@ mod tests {
 
         assert_eq!(
             ParamType::Tuple(vec![ParamType::Bytes, ParamType::Address]),
-            <(Vec<u8>, Address)>::param_type()
+            <(Bytes, Address)>::param_type()
         );
 
         assert_eq!(ParamType::FixedBytes(32), <[u8; 32]>::param_type());
diff --git a/ethers-core/src/abi/tokens.rs b/ethers-core/src/abi/tokens.rs
index b32ea32f..ca567078 100644
--- a/ethers-core/src/abi/tokens.rs
+++ b/ethers-core/src/abi/tokens.rs
@@ -287,7 +287,7 @@ macro_rules! tokenizable_item {
 
 tokenizable_item! {
     Token, String, Address, H256, U256, I256, U128, bool, Vec<u8>,
-    i8, i16, i32, i64, i128, u16, u32, u64, u128,
+    i8, i16, i32, i64, i128, u16, u32, u64, u128, Bytes,
 }
 
 macro_rules! impl_tokenizable_item_tuple {
@@ -321,12 +321,13 @@ impl Tokenizable for Vec<u8> {
     fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
         match token {
             Token::Bytes(data) => Ok(data),
+            Token::Array(data) => data.into_iter().map(u8::from_token).collect(),
             Token::FixedBytes(data) => Ok(data),
             other => Err(InvalidOutputType(format!("Expected `bytes`, got {:?}", other))),
         }
     }
     fn into_token(self) -> Token {
-        Token::Bytes(self)
+        Token::Array(self.into_iter().map(Tokenizable::into_token).collect())
     }
 }
 
diff --git a/ethers-core/src/types/bytes.rs b/ethers-core/src/types/bytes.rs
index 485052d8..f2179711 100644
--- a/ethers-core/src/types/bytes.rs
+++ b/ethers-core/src/types/bytes.rs
@@ -34,6 +34,18 @@ impl From<Vec<u8>> for Bytes {
     }
 }
 
+impl<const N: usize> From<[u8; N]> for Bytes {
+    fn from(src: [u8; N]) -> Self {
+        src.to_vec().into()
+    }
+}
+
+impl<'a, const N: usize> From<&'a [u8; N]> for Bytes {
+    fn from(src: &'a [u8; N]) -> Self {
+        src.to_vec().into()
+    }
+}
+
 pub fn serialize_bytes<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
 where
     S: Serializer,


Test patch:
diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs
index 606a5f34..8748af2b 100644
--- a/ethers-contract/tests/contract.rs
+++ b/ethers-contract/tests/contract.rs
@@ -9,7 +9,7 @@ mod eth_tests {
     use super::*;
     use ethers_contract::{LogMeta, Multicall};
     use ethers_core::{
-        types::{transaction::eip712::Eip712, Address, BlockId, I256, U256},
+        types::{transaction::eip712::Eip712, Address, BlockId, Bytes, I256, U256},
         utils::{keccak256, Ganache},
     };
     use ethers_derive_eip712::*;
@@ -525,7 +525,7 @@ mod eth_tests {
         struct FooBar {
             foo: I256,
             bar: U256,
-            fizz: Vec<u8>,
+            fizz: Bytes,
             buzz: [u8; 32],
             far: String,
             out: Address,
@@ -563,7 +563,7 @@ mod eth_tests {
         let foo_bar = FooBar {
             foo: I256::from(10),
             bar: U256::from(20),
-            fizz: b"fizz".to_vec(),
+            fizz: b"fizz".into(),
             buzz: keccak256("buzz"),
             far: String::from("space"),
             out: Address::from([0; 20]),