refactor(abigen): Abigen, ContractBindings (#2019)

* use format_ident

* lib.rs

* refactor: ContractBindings

* refactor: from_file

* add write_fmt

* mv

* error msg

* revert mod docs
This commit is contained in:
DaniPopes 2023-01-13 19:23:59 +01:00 committed by GitHub
parent da0039aaea
commit d1e934791d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 74 deletions

View File

@ -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<Self> {
// 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
}

View File

@ -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<Path>) -> Result<Self> {
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::<syn::File>(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<W>(&self, mut w: W) -> Result<()>
where
W: Write,
{
let source = if self.format {
let syntax_tree = syn::parse2::<syn::File>(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<u8> {
self.to_string().into_bytes()
}
/// Writes the bindings to a new Vec. Panics if unable to allocate
pub fn to_vec(&self) -> Vec<u8> {
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<P>(&self, path: P) -> Result<()>
where
P: AsRef<Path>,
{
let file = File::create(path)?;
self.write(file)
pub fn write_to_file(&self, file: impl AsRef<Path>) -> 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<P>(&self, dir: P) -> Result<()>
where
P: AsRef<Path>,
{
/// Writes the bindings to a `contract_name.rs` file in the specified directory.
pub fn write_module_in_dir(&self, dir: impl AsRef<Path>) -> 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
}
}

View File

@ -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(())