From d1e934791d8099e546f078f149a7548ed355a885 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 13 Jan 2023 19:23:59 +0100 Subject: [PATCH] refactor(abigen): Abigen, ContractBindings (#2019) * use format_ident * lib.rs * refactor: ContractBindings * refactor: from_file * add write_fmt * mv * error msg * revert mod docs --- .../ethers-contract-abigen/src/contract.rs | 23 +-- .../ethers-contract-abigen/src/lib.rs | 149 ++++++++++-------- examples/contracts/examples/compile.rs | 2 +- 3 files changed, 100 insertions(+), 74 deletions(-) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 371861be..95492294 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -1,4 +1,5 @@ -#![deny(missing_docs)] +//! Contains types to generate Rust bindings for Solidity contracts. + mod common; mod errors; mod events; @@ -15,7 +16,7 @@ use ethers_core::{ }; use eyre::{eyre, Context as _, Result}; use proc_macro2::{Ident, Literal, TokenStream}; -use quote::quote; +use quote::{format_ident, quote}; use serde::Deserialize; use std::collections::BTreeMap; use syn::Path; @@ -183,7 +184,7 @@ impl Context { pub fn from_abigen(args: Abigen) -> Result { // get the actual ABI string let mut abi_str = - args.abi_source.get().map_err(|e| eyre!("failed to get ABI JSON: {}", e))?; + args.abi_source.get().map_err(|e| eyre!("failed to get ABI JSON: {e}"))?; // holds the bytecode parsed from the abi_str, if present let mut contract_bytecode = None; @@ -234,7 +235,7 @@ impl Context { }; if method_aliases.insert(signature.clone(), alias).is_some() { - eyre::bail!("duplicate method signature '{}' in method aliases", signature) + eyre::bail!("duplicate method signature {signature:?} in method aliases") } } @@ -293,27 +294,27 @@ impl Context { }) } - /// The initial name fo the contract + /// The name of the contract. pub(crate) fn contract_name(&self) -> &str { &self.contract_name } - /// name of the `Lazy` that stores the ABI + /// Name of the `Lazy` that stores the ABI. pub(crate) fn inline_abi_ident(&self) -> Ident { - util::safe_ident(&format!("{}_ABI", self.contract_ident.to_string().to_uppercase())) + format_ident!("{}_ABI", self.contract_name.to_uppercase()) } - /// name of the `Lazy` that stores the Bytecode + /// Name of the `Lazy` that stores the Bytecode. pub(crate) fn inline_bytecode_ident(&self) -> Ident { - util::safe_ident(&format!("{}_BYTECODE", self.contract_ident.to_string().to_uppercase())) + format_ident!("{}_BYTECODE", self.contract_name.to_uppercase()) } - /// The internal abi struct mapping table + /// Returns a reference to the internal ABI struct mapping table. pub fn internal_structs(&self) -> &InternalStructs { &self.internal_structs } - /// The internal mutable abi struct mapping table + /// Returns a mutable reference to the internal ABI struct mapping table. pub fn internal_structs_mut(&mut self) -> &mut InternalStructs { &mut self.internal_structs } diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 3be23832..676b42fe 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -15,27 +15,28 @@ #[path = "test/macros.rs"] mod test_macros; -/// Contains types to generate rust bindings for solidity contracts pub mod contract; pub use contract::structs::InternalStructs; -use contract::Context; - -mod source; -mod util; pub mod filter; pub use filter::{ContractFilter, ExcludeContracts, SelectContracts}; + pub mod multi; pub use multi::MultiAbigen; -pub use ethers_core::types::Address; +mod source; pub use source::Source; + +mod util; pub use util::parse_address; -use crate::contract::ExpandedContract; -use eyre::Result; +pub use ethers_core::types::Address; + +use contract::{Context, ExpandedContract}; +use eyre::{Context as _, Result}; use proc_macro2::TokenStream; -use std::{collections::HashMap, fs::File, io::Write, path::Path}; +use quote::ToTokens; +use std::{collections::HashMap, fmt, fs, io, path::Path}; /// Programmatically generate type-safe Rust bindings for an Ethereum smart contract from its ABI. /// @@ -101,18 +102,18 @@ impl Abigen { /// Attempts to load a new builder from an ABI JSON file at the specific path. pub fn from_file(path: impl AsRef) -> Result { - let name = path - .as_ref() - .file_stem() - .ok_or_else(|| eyre::format_err!("Missing file stem in path"))? + let path = dunce::canonicalize(path).wrap_err("File does not exist")?; + // this shouldn't error when the path is canonicalized + let file_name = path.file_name().ok_or_else(|| eyre::eyre!("Invalid path"))?; + let name = file_name .to_str() - .ok_or_else(|| eyre::format_err!("Unable to convert file stem to string"))?; + .ok_or_else(|| eyre::eyre!("File name contains invalid UTF-8"))? + .split('.') // ignore everything after the first `.` + .next() + .unwrap(); // file_name is not empty as asserted by .file_name() already + let contents = fs::read_to_string(&path).wrap_err("Could not read file")?; - // test,script files usually end with `.t.sol` or `.s.sol`, we simply cut off everything - // after the first `.` - let name = name.split('.').next().expect("name not empty."); - - Self::new(name, std::fs::read_to_string(path.as_ref())?) + Self::new(name, contents) } /// Manually adds a solidity event alias to specify what the event struct and function name will @@ -200,63 +201,87 @@ impl Abigen { } } -/// Type-safe contract bindings generated by a `Builder`. This type can be -/// either written to file or into a token stream for use in a procedural macro. +/// Type-safe contract bindings generated by `Abigen`. +/// +/// This type can be either written to file or converted to a token stream for a procedural macro. +#[derive(Clone)] pub struct ContractBindings { - /// The TokenStream representing the contract bindings. - tokens: TokenStream, - /// The output options used for serialization. - format: bool, - /// The contract name - name: String, + /// The contract's name. + pub name: String, + + /// The generated bindings as a `TokenStream`. + pub tokens: TokenStream, + + /// Whether to format the generated bindings using [`prettyplease`]. + pub format: bool, +} + +impl ToTokens for ContractBindings { + fn into_token_stream(self) -> TokenStream { + self.tokens + } + + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(Some(self.tokens.clone())) + } + + fn to_token_stream(&self) -> TokenStream { + self.tokens.clone() + } +} + +impl fmt::Display for ContractBindings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.format { + let syntax_tree = syn::parse2::(self.tokens.clone()).unwrap(); + let s = prettyplease::unparse(&syntax_tree); + f.write_str(&s) + } else { + fmt::Display::fmt(&self.tokens, f) + } + } +} + +impl fmt::Debug for ContractBindings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ContractBindings") + .field("name", &self.name) + .field("format", &self.format) + .finish() + } } impl ContractBindings { - /// Writes the bindings to a given `Write`. - pub fn write(&self, mut w: W) -> Result<()> - where - W: Write, - { - let source = if self.format { - let syntax_tree = syn::parse2::(self.tokens.clone()).unwrap(); - prettyplease::unparse(&syntax_tree) - } else { - self.tokens.to_string() - }; - - w.write_all(source.as_bytes())?; - Ok(()) + /// Writes the bindings to a new Vec. + pub fn to_vec(&self) -> Vec { + self.to_string().into_bytes() } - /// Writes the bindings to a new Vec. Panics if unable to allocate - pub fn to_vec(&self) -> Vec { - let mut bindings = vec![]; - self.write(&mut bindings).expect("allocations don't fail"); - bindings + /// Writes the bindings to a given `io::Write`. + pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> { + let tokens = self.to_string(); + w.write_all(tokens.as_bytes()) + } + + /// Writes the bindings to a given `fmt::Write`. + pub fn write_fmt(&self, w: &mut impl fmt::Write) -> fmt::Result { + let tokens = self.to_string(); + w.write_str(&tokens) } /// Writes the bindings to the specified file. - pub fn write_to_file

(&self, path: P) -> Result<()> - where - P: AsRef, - { - let file = File::create(path)?; - self.write(file) + pub fn write_to_file(&self, file: impl AsRef) -> io::Result<()> { + fs::write(file.as_ref(), self.to_string()) } - /// Writes the bindings to a `contract_name.rs` file in the specified - /// directory. The filename is the snake_case transformation of the contract - /// name. - pub fn write_module_in_dir

(&self, dir: P) -> Result<()> - where - P: AsRef, - { + /// Writes the bindings to a `contract_name.rs` file in the specified directory. + pub fn write_module_in_dir(&self, dir: impl AsRef) -> io::Result<()> { let file = dir.as_ref().join(self.module_filename()); self.write_to_file(file) } - /// Converts the bindings into its underlying token stream. This allows it - /// to be used within a procedural macro. + #[deprecated = "Use ::quote::ToTokens::into_token_stream instead"] + #[doc(hidden)] pub fn into_tokens(self) -> TokenStream { self.tokens } @@ -269,7 +294,7 @@ impl ContractBindings { /// Generate the default file name of the module. pub fn module_filename(&self) -> String { let mut name = self.module_name(); - name.extend([".rs"]); + name.push_str(".rs"); name } } diff --git a/examples/contracts/examples/compile.rs b/examples/contracts/examples/compile.rs index 21b7586a..b5a76c74 100644 --- a/examples/contracts/examples/compile.rs +++ b/examples/contracts/examples/compile.rs @@ -27,7 +27,7 @@ fn main() -> Result<()> { if let Some(output_path) = args.next() { bindings.write_to_file(output_path)?; } else { - bindings.write(std::io::stdout())?; + bindings.write(&mut std::io::stdout())?; } Ok(())