diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d57870..84da1688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,10 @@ [#1030](https://github.com/gakonst/ethers-rs/pull/1030). - Generate correct bindings of struct's field names that are reserved words [#989](https://github.com/gakonst/ethers-rs/pull/989). +- Generate correct binding module names that are reserved words + [#1498](https://github.com/gakonst/ethers-rs/pull/1498). Note: this changes + generated module names to snake case. For example, `MyContract` is now + `my_contract` rather than `mycontract_mod`. ### 0.6.0 diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml index d011b1e3..8cf9b72e 100644 --- a/ethers-contract/ethers-contract-abigen/Cargo.toml +++ b/ethers-contract/ethers-contract-abigen/Cargo.toml @@ -41,4 +41,4 @@ rustls = ["reqwest/rustls-tls"] [dev-dependencies] tempfile = "3.2.0" -ethers-solc = { version = "^0.13.0", path = "../../ethers-solc", default-features = false, features = ["project-util"] } +ethers-solc = { version = "^0.13.0", path = "../../ethers-solc", default-features = false, features = ["project-util", "svm-solc"] } diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 1b33ff86..df31a3ec 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -101,8 +101,7 @@ impl Context { /// Expands the whole rust contract pub fn expand(&self) -> Result { let name = &self.contract_ident; - let name_mod = - util::ident(&format!("{}_mod", self.contract_ident.to_string().to_lowercase())); + let name_mod = util::ident(&util::safe_module_name(&self.contract_name)); let abi_name = self.inline_abi_ident(); // 0. Imports diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index a9b7e76e..a538df71 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -29,7 +29,6 @@ pub use util::parse_address; use crate::contract::ExpandedContract; use eyre::Result; -use inflector::Inflector; use proc_macro2::TokenStream; use std::{collections::HashMap, fs::File, io::Write, path::Path}; @@ -233,7 +232,7 @@ impl ContractBindings { /// Generate the default module name (snake case of the contract name) pub fn module_name(&self) -> String { - self.name.to_snake_case() + util::safe_module_name(&self.name) } /// Generate the default filename of the module diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs index 594a5797..0023f583 100644 --- a/ethers-contract/ethers-contract-abigen/src/multi.rs +++ b/ethers-contract/ethers-contract-abigen/src/multi.rs @@ -559,7 +559,7 @@ serde_json = "1.0.79" /// Append module declarations to the `lib.rs` or `mod.rs` fn append_module_names(&self, mut buf: impl Write) -> Result<()> { let mut mod_names: BTreeSet<_> = - self.bindings.keys().map(|name| name.to_snake_case()).collect(); + self.bindings.keys().map(|name| util::safe_module_name(name)).collect(); if let Some(ref shared) = self.shared_types { mod_names.insert(shared.name.to_snake_case()); } @@ -1093,4 +1093,63 @@ contract Greeter2 { assert!(content.contains("pub struct Inner")); assert!(content.contains("pub struct Stuff")); } + + #[test] + fn can_sanitize_reserved_words() { + let tmp = TempProject::dapptools().unwrap(); + + tmp.add_source( + "ReservedWords", + r#" +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +contract Mod { + function greet() public pure returns (uint256) { + return 1; + } +} + +// from a gnosis contract +contract Enum { + enum Operation {Call, DelegateCall} +} +"#, + ) + .unwrap(); + + let _ = tmp.compile().unwrap(); + + let gen = MultiAbigen::from_json_files(tmp.artifacts_path()).unwrap(); + let bindings = gen.build().unwrap(); + let single_file_dir = tmp.root().join("single_bindings"); + bindings.write_to_module(&single_file_dir, true).unwrap(); + + let single_file_mod = single_file_dir.join("mod.rs"); + assert!(single_file_mod.exists()); + let content = fs::read_to_string(&single_file_mod).unwrap(); + assert!(content.contains("pub mod mod_ {")); + assert!(content.contains("pub mod enum_ {")); + + // multiple files + let gen = MultiAbigen::from_json_files(tmp.artifacts_path()).unwrap(); + let bindings = gen.build().unwrap(); + let multi_file_dir = tmp.root().join("multi_bindings"); + bindings.write_to_module(&multi_file_dir, false).unwrap(); + let multi_file_mod = multi_file_dir.join("mod.rs"); + assert!(multi_file_mod.exists()); + let content = fs::read_to_string(&multi_file_mod).unwrap(); + assert!(content.contains("pub mod enum_;")); + assert!(content.contains("pub mod mod_;")); + + let enum_ = multi_file_dir.join("enum_.rs"); + assert!(enum_.exists()); + let content = fs::read_to_string(&enum_).unwrap(); + assert!(content.contains("pub mod enum_ {")); + + let mod_ = multi_file_dir.join("mod_.rs"); + assert!(mod_.exists()); + let content = fs::read_to_string(&mod_).unwrap(); + assert!(content.contains("pub mod mod_ {")); + } } diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs index d9c812e0..33936e09 100644 --- a/ethers-contract/ethers-contract-abigen/src/util.rs +++ b/ethers-contract/ethers-contract-abigen/src/util.rs @@ -40,6 +40,12 @@ fn safe_identifier_name(name: String) -> String { } } +/// converts invalid rust module names to valid ones +pub fn safe_module_name(name: &str) -> String { + // handle reserve words used in contracts (eg Enum is a gnosis contract) + safe_ident(&safe_snake_case(name)).to_string() +} + /// Expands an identifier as snakecase and preserve any leading or trailing underscores pub fn safe_snake_case_ident(name: &str) -> Ident { let i = name.to_snake_case(); @@ -228,4 +234,12 @@ mod tests { Address::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]); assert_eq!(parse_address("0x000102030405060708090a0b0c0d0e0f10111213").unwrap(), expected); } + + #[test] + fn test_safe_module_name() { + assert_eq!(safe_module_name("Valid"), "valid"); + assert_eq!(safe_module_name("Enum"), "enum_"); + assert_eq!(safe_module_name("Mod"), "mod_"); + assert_eq!(safe_module_name("2Two"), "_2_two"); + } } diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 3271c551..ecf805f9 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -366,7 +366,7 @@ async fn can_handle_underscore_functions() { // Manual call construction use ethers_providers::Middleware; // TODO: How do we handle underscores for calls here? - let data = simplestorage_mod::HashPuzzleCall.encode(); + let data = simple_storage::HashPuzzleCall.encode(); let tx = Eip1559TransactionRequest::new().data(data).to(addr); let tx = TypedTransaction::Eip1559(tx); let res5 = client.call(&tx, None).await.unwrap(); diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index acf63042..4d119048 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -601,7 +601,7 @@ mod eth_tests { out: Address::from([0; 20]), }; - let derived_foo_bar = deriveeip712test_mod::FooBar { + let derived_foo_bar = derive_eip_712_test::FooBar { foo: foo_bar.foo, bar: foo_bar.bar, fizz: foo_bar.fizz.clone(),