fix(abigen): contract names can be reserve words (#1498)

* fix(abigen): contract names can be reserve words

* update changelog

* clippy warning

* module names consistent with other safe_*

* update refs to generated abis

* move reserved words tests to their own fn

* added note to changelog re: module name changes
This commit is contained in:
Clifton King 2022-07-23 20:18:24 -05:00 committed by GitHub
parent a600acb4f5
commit aa008727ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 8 deletions

View File

@ -90,6 +90,10 @@
[#1030](https://github.com/gakonst/ethers-rs/pull/1030). [#1030](https://github.com/gakonst/ethers-rs/pull/1030).
- Generate correct bindings of struct's field names that are reserved words - Generate correct bindings of struct's field names that are reserved words
[#989](https://github.com/gakonst/ethers-rs/pull/989). [#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 ### 0.6.0

View File

@ -41,4 +41,4 @@ rustls = ["reqwest/rustls-tls"]
[dev-dependencies] [dev-dependencies]
tempfile = "3.2.0" 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"] }

View File

@ -101,8 +101,7 @@ impl Context {
/// Expands the whole rust contract /// Expands the whole rust contract
pub fn expand(&self) -> Result<ExpandedContract> { pub fn expand(&self) -> Result<ExpandedContract> {
let name = &self.contract_ident; let name = &self.contract_ident;
let name_mod = let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
util::ident(&format!("{}_mod", self.contract_ident.to_string().to_lowercase()));
let abi_name = self.inline_abi_ident(); let abi_name = self.inline_abi_ident();
// 0. Imports // 0. Imports

View File

@ -29,7 +29,6 @@ pub use util::parse_address;
use crate::contract::ExpandedContract; use crate::contract::ExpandedContract;
use eyre::Result; use eyre::Result;
use inflector::Inflector;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use std::{collections::HashMap, fs::File, io::Write, path::Path}; 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) /// Generate the default module name (snake case of the contract name)
pub fn module_name(&self) -> String { pub fn module_name(&self) -> String {
self.name.to_snake_case() util::safe_module_name(&self.name)
} }
/// Generate the default filename of the module /// Generate the default filename of the module

View File

@ -559,7 +559,7 @@ serde_json = "1.0.79"
/// Append module declarations to the `lib.rs` or `mod.rs` /// Append module declarations to the `lib.rs` or `mod.rs`
fn append_module_names(&self, mut buf: impl Write) -> Result<()> { fn append_module_names(&self, mut buf: impl Write) -> Result<()> {
let mut mod_names: BTreeSet<_> = 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 { if let Some(ref shared) = self.shared_types {
mod_names.insert(shared.name.to_snake_case()); 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 Inner"));
assert!(content.contains("pub struct Stuff")); 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_ {"));
}
} }

View File

@ -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 /// Expands an identifier as snakecase and preserve any leading or trailing underscores
pub fn safe_snake_case_ident(name: &str) -> Ident { pub fn safe_snake_case_ident(name: &str) -> Ident {
let i = name.to_snake_case(); 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]); 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); 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");
}
} }

View File

@ -366,7 +366,7 @@ async fn can_handle_underscore_functions() {
// Manual call construction // Manual call construction
use ethers_providers::Middleware; use ethers_providers::Middleware;
// TODO: How do we handle underscores for calls here? // 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 = Eip1559TransactionRequest::new().data(data).to(addr);
let tx = TypedTransaction::Eip1559(tx); let tx = TypedTransaction::Eip1559(tx);
let res5 = client.call(&tx, None).await.unwrap(); let res5 = client.call(&tx, None).await.unwrap();

View File

@ -601,7 +601,7 @@ mod eth_tests {
out: Address::from([0; 20]), 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, foo: foo_bar.foo,
bar: foo_bar.bar, bar: foo_bar.bar,
fizz: foo_bar.fizz.clone(), fizz: foo_bar.fizz.clone(),