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:
parent
da0039aaea
commit
d1e934791d
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
Loading…
Reference in New Issue