Repository: gakonst/ethers-rs (rust)

Issue:
`method not found` error for nested struct argument
**Version**

ethers v0.5.1
│   ├── ethers-contract v0.5.1
│   │   ├── ethers-contract-abigen v0.5.1
│   │   │   ├── ethers-core v0.5.1
│   │   ├── ethers-contract-derive v0.5.1 (proc-macro)
│   │   │   ├── ethers-contract-abigen v0.5.1
│   │   │   ├── ethers-core v0.5.1
│   │   ├── ethers-core v0.5.1
│   │   ├── ethers-providers v0.5.1
│   │   │   ├── ethers-core v0.5.1
│   ├── ethers-core v0.5.1
│   ├── ethers-middleware v0.5.1
│   │   ├── ethers-contract v0.5.1
│   │   ├── ethers-core v0.5.1
│   │   ├── ethers-providers v0.5.1
│   │   ├── ethers-signers v0.5.1
│   │   │   ├── ethers-core v0.5.1
│   ├── ethers-providers v0.5.1
│   └── ethers-signers v0.5.1


**Description**

Relates to #363.

When calling a solidity method using `abigen!` generated bindings, a `method not found (this should never happen): DecodingError(InvalidData)` error is thrown.

The method being called accepts a single struct, which itself contains an array of another struct.
```solidity
struct Inner {
    bytes32 id;
    bytes data;
}
struct Outer {
     Inner[] innerStructs;
}

function useStruct(Outer calldata foo) external;
```

Pull request:
fix: always treat abi type structs as tuples
<!--
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
structs with a single field weren't properly endcoded into a `Token::Tuple`
<!--
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
- always treat `EthAbiType` as tuples 
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

- Closes #416 

Patch:
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index ec112741..cf8f5646 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -414,7 +414,7 @@ fn derive_decode_from_log_impl(
         .filter(|f| !f.is_indexed())
         .map(|f| param_type_quote(&f.param.kind));
 
-    let data_types_init = quote! {let data_types = ::std::vec![#( #data_types ),*];};
+    let data_types_init = quote! {let data_types = [#( #data_types ),*];};
 
     // decode
     let (signature_check, flat_topics_init, topic_tokens_len_check) = if event.anonymous {
@@ -478,7 +478,6 @@ fn derive_decode_from_log_impl(
             #( tokens.push(#swap_tokens); )*
         }
     };
-
     Ok(quote! {
 
         let #core_crate::abi::RawLog {data, topics} = log;
@@ -492,7 +491,7 @@ fn derive_decode_from_log_impl(
 
         #tokens_init
 
-        #core_crate::abi::Detokenize::from_tokens(tokens).map_err(|_|#core_crate::abi::Error::InvalidData)
+        #core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData)
     })
 }
 
@@ -808,26 +807,6 @@ fn derive_tokenizeable_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
                 #core_crate::abi::Token::Tuple(Vec::new())
             },
         ),
-        1 => {
-            // This is a hacky solution in order to keep the same tokenstream as for tuples
-            let from_token = quote! {
-                let mut iter = Some(token).into_iter();
-                Ok(#init_struct_impl)
-            };
-
-            // This is a hack to get rid of the trailing comma introduced in the macro that concatenates all the fields
-            if let Ok(into_token) = into_token_impl
-                .to_string()
-                .as_str()
-                .trim_end_matches(',')
-                .parse()
-            {
-                (from_token, into_token)
-            } else {
-                return Error::new(input.span(), "Failed to derive Tokenizeable implementation")
-                    .to_compile_error();
-            }
-        }
         _ => {
             let from_token = quote! {
                 if let #core_crate::abi::Token::Tuple(tokens) = token {
diff --git a/ethers-core/src/utils/solc.rs b/ethers-core/src/utils/solc.rs
index 175c295d..e3886169 100644
--- a/ethers-core/src/utils/solc.rs
+++ b/ethers-core/src/utils/solc.rs
@@ -45,7 +45,7 @@ pub struct CompiledContract {
 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
 /// // Give it a glob
 /// let contracts = Solc::new("./contracts/*")
-///     .optimizer(200)
+///     .optimizer(Some(200))
 ///     .build()?;
 ///
 /// // this will return None if the specified contract did not exist in the compiled
@@ -58,14 +58,17 @@ pub struct Solc {
     /// The path where contracts will be read from
     pub paths: Vec<String>,
 
-    /// Number of runs
-    pub optimizer: usize,
+    /// Number of optimizer runs. None for no optimization
+    pub optimizer: Option<usize>,
 
     /// Evm Version
     pub evm_version: EvmVersion,
 
     /// Paths for importing other libraries
     pub allowed_paths: Vec<PathBuf>,
+
+    /// Additional arguments to pass to solc
+    pub args: Vec<String>,
 }
 
 impl Solc {
@@ -80,9 +83,10 @@ impl Solc {
 
         Self {
             paths,
-            optimizer: 200, // default optimizer runs = 200
+            optimizer: Some(200), // default optimizer runs = 200
             evm_version: EvmVersion::Istanbul,
             allowed_paths: Vec::new(),
+            args: Vec::new(),
         }
     }
 
@@ -96,6 +100,15 @@ impl Solc {
             .arg("--combined-json")
             .arg("abi,bin");
 
+        if let Some(runs) = self.optimizer {
+            command
+                .arg("--optimize")
+                .arg("--optimize-runs")
+                .arg(runs.to_string());
+        }
+
+        command.args(self.args);
+
         for path in self.paths {
             command.arg(path);
         }
@@ -196,8 +209,8 @@ impl Solc {
         self
     }
 
-    /// Sets the optimizer runs (default = 200)
-    pub fn optimizer(mut self, runs: usize) -> Self {
+    /// Sets the optimizer runs (default = 200). None indicates no optimization
+    pub fn optimizer(mut self, runs: Option<usize>) -> Self {
         self.optimizer = runs;
         self
     }
@@ -208,6 +221,24 @@ impl Solc {
         self.allowed_paths = paths;
         self
     }
+
+    /// Adds an argument to pass to solc
+    pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
+        self.args.push(arg.into());
+        self
+    }
+
+    /// Adds multiple arguments to pass to solc
+    pub fn args<I, S>(mut self, args: I) -> Self
+    where
+        I: IntoIterator<Item = S>,
+        S: Into<String>,
+    {
+        for arg in args {
+            self = self.arg(arg);
+        }
+        self
+    }
 }
 
 #[derive(Clone, Debug)]


Test patch:
diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs
index b9d04aa6..be1d87df 100644
--- a/ethers-contract/tests/common/derive.rs
+++ b/ethers-contract/tests/common/derive.rs
@@ -24,6 +24,11 @@ struct ValueChangedTuple(Address, Address, String, String);
 #[derive(Debug, Clone, PartialEq, EthAbiType)]
 struct ValueChangedTupleWrapper(ValueChangedTuple, String);
 
+#[derive(Debug, Clone, PartialEq, EthAbiType)]
+struct ValueChangedVecWrapper {
+    inner: Vec<ValueChanged>,
+}
+
 #[test]
 fn can_detokenize_struct() {
     let value = ValueChanged {
@@ -82,6 +87,26 @@ fn can_detokenize_nested_tuple_struct() {
     assert_eq!(value, ValueChangedTupleWrapper::from_token(token).unwrap());
 }
 
+#[test]
+fn can_detokenize_single_field() {
+    let value = ValueChangedVecWrapper { inner: vec![] };
+
+    let token = value.clone().into_token();
+    assert_eq!(value, ValueChangedVecWrapper::from_token(token).unwrap());
+
+    let value = ValueChangedVecWrapper {
+        inner: vec![ValueChanged {
+            old_author: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap(),
+            new_author: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(),
+            old_value: "50".to_string(),
+            new_value: "100".to_string(),
+        }],
+    };
+
+    let token = value.clone().into_token();
+    assert_eq!(value, ValueChangedVecWrapper::from_token(token).unwrap());
+}
+
 #[test]
 fn can_derive_eth_event() {
     #[derive(Debug, Clone, PartialEq, EthEvent)]