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 common;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod events;
|
mod events;
|
||||||
|
@ -15,7 +16,7 @@ use ethers_core::{
|
||||||
};
|
};
|
||||||
use eyre::{eyre, Context as _, Result};
|
use eyre::{eyre, Context as _, Result};
|
||||||
use proc_macro2::{Ident, Literal, TokenStream};
|
use proc_macro2::{Ident, Literal, TokenStream};
|
||||||
use quote::quote;
|
use quote::{format_ident, quote};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use syn::Path;
|
use syn::Path;
|
||||||
|
@ -183,7 +184,7 @@ impl Context {
|
||||||
pub fn from_abigen(args: Abigen) -> Result<Self> {
|
pub fn from_abigen(args: Abigen) -> Result<Self> {
|
||||||
// get the actual ABI string
|
// get the actual ABI string
|
||||||
let mut abi_str =
|
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
|
// holds the bytecode parsed from the abi_str, if present
|
||||||
let mut contract_bytecode = None;
|
let mut contract_bytecode = None;
|
||||||
|
@ -234,7 +235,7 @@ impl Context {
|
||||||
};
|
};
|
||||||
|
|
||||||
if method_aliases.insert(signature.clone(), alias).is_some() {
|
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 {
|
pub(crate) fn contract_name(&self) -> &str {
|
||||||
&self.contract_name
|
&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 {
|
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 {
|
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 {
|
pub fn internal_structs(&self) -> &InternalStructs {
|
||||||
&self.internal_structs
|
&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 {
|
pub fn internal_structs_mut(&mut self) -> &mut InternalStructs {
|
||||||
&mut self.internal_structs
|
&mut self.internal_structs
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,28 @@
|
||||||
#[path = "test/macros.rs"]
|
#[path = "test/macros.rs"]
|
||||||
mod test_macros;
|
mod test_macros;
|
||||||
|
|
||||||
/// Contains types to generate rust bindings for solidity contracts
|
|
||||||
pub mod contract;
|
pub mod contract;
|
||||||
pub use contract::structs::InternalStructs;
|
pub use contract::structs::InternalStructs;
|
||||||
use contract::Context;
|
|
||||||
|
|
||||||
mod source;
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub use filter::{ContractFilter, ExcludeContracts, SelectContracts};
|
pub use filter::{ContractFilter, ExcludeContracts, SelectContracts};
|
||||||
|
|
||||||
pub mod multi;
|
pub mod multi;
|
||||||
pub use multi::MultiAbigen;
|
pub use multi::MultiAbigen;
|
||||||
|
|
||||||
pub use ethers_core::types::Address;
|
mod source;
|
||||||
pub use source::Source;
|
pub use source::Source;
|
||||||
|
|
||||||
|
mod util;
|
||||||
pub use util::parse_address;
|
pub use util::parse_address;
|
||||||
|
|
||||||
use crate::contract::ExpandedContract;
|
pub use ethers_core::types::Address;
|
||||||
use eyre::Result;
|
|
||||||
|
use contract::{Context, ExpandedContract};
|
||||||
|
use eyre::{Context as _, Result};
|
||||||
use proc_macro2::TokenStream;
|
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.
|
/// 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.
|
/// 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> {
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
let name = path
|
let path = dunce::canonicalize(path).wrap_err("File does not exist")?;
|
||||||
.as_ref()
|
// this shouldn't error when the path is canonicalized
|
||||||
.file_stem()
|
let file_name = path.file_name().ok_or_else(|| eyre::eyre!("Invalid path"))?;
|
||||||
.ok_or_else(|| eyre::format_err!("Missing file stem in path"))?
|
let name = file_name
|
||||||
.to_str()
|
.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
|
Self::new(name, contents)
|
||||||
// after the first `.`
|
|
||||||
let name = name.split('.').next().expect("name not empty.");
|
|
||||||
|
|
||||||
Self::new(name, std::fs::read_to_string(path.as_ref())?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually adds a solidity event alias to specify what the event struct and function name will
|
/// 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
|
/// Type-safe contract bindings generated by `Abigen`.
|
||||||
/// either written to file or into a token stream for use in a procedural macro.
|
///
|
||||||
|
/// This type can be either written to file or converted to a token stream for a procedural macro.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ContractBindings {
|
pub struct ContractBindings {
|
||||||
/// The TokenStream representing the contract bindings.
|
/// The contract's name.
|
||||||
tokens: TokenStream,
|
pub name: String,
|
||||||
/// The output options used for serialization.
|
|
||||||
format: bool,
|
/// The generated bindings as a `TokenStream`.
|
||||||
/// The contract name
|
pub tokens: TokenStream,
|
||||||
name: String,
|
|
||||||
|
/// 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 {
|
impl ContractBindings {
|
||||||
/// Writes the bindings to a given `Write`.
|
/// Writes the bindings to a new Vec.
|
||||||
pub fn write<W>(&self, mut w: W) -> Result<()>
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
where
|
self.to_string().into_bytes()
|
||||||
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. Panics if unable to allocate
|
/// Writes the bindings to a given `io::Write`.
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
|
||||||
let mut bindings = vec![];
|
let tokens = self.to_string();
|
||||||
self.write(&mut bindings).expect("allocations don't fail");
|
w.write_all(tokens.as_bytes())
|
||||||
bindings
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Writes the bindings to the specified file.
|
||||||
pub fn write_to_file<P>(&self, path: P) -> Result<()>
|
pub fn write_to_file(&self, file: impl AsRef<Path>) -> io::Result<()> {
|
||||||
where
|
fs::write(file.as_ref(), self.to_string())
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = File::create(path)?;
|
|
||||||
self.write(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the bindings to a `contract_name.rs` file in the specified
|
/// Writes the bindings to a `contract_name.rs` file in the specified directory.
|
||||||
/// directory. The filename is the snake_case transformation of the contract
|
pub fn write_module_in_dir(&self, dir: impl AsRef<Path>) -> io::Result<()> {
|
||||||
/// name.
|
|
||||||
pub fn write_module_in_dir<P>(&self, dir: P) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let file = dir.as_ref().join(self.module_filename());
|
let file = dir.as_ref().join(self.module_filename());
|
||||||
self.write_to_file(file)
|
self.write_to_file(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the bindings into its underlying token stream. This allows it
|
#[deprecated = "Use ::quote::ToTokens::into_token_stream instead"]
|
||||||
/// to be used within a procedural macro.
|
#[doc(hidden)]
|
||||||
pub fn into_tokens(self) -> TokenStream {
|
pub fn into_tokens(self) -> TokenStream {
|
||||||
self.tokens
|
self.tokens
|
||||||
}
|
}
|
||||||
|
@ -269,7 +294,7 @@ impl ContractBindings {
|
||||||
/// Generate the default file name of the module.
|
/// Generate the default file name of the module.
|
||||||
pub fn module_filename(&self) -> String {
|
pub fn module_filename(&self) -> String {
|
||||||
let mut name = self.module_name();
|
let mut name = self.module_name();
|
||||||
name.extend([".rs"]);
|
name.push_str(".rs");
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ fn main() -> Result<()> {
|
||||||
if let Some(output_path) = args.next() {
|
if let Some(output_path) = args.next() {
|
||||||
bindings.write_to_file(output_path)?;
|
bindings.write_to_file(output_path)?;
|
||||||
} else {
|
} else {
|
||||||
bindings.write(std::io::stdout())?;
|
bindings.write(&mut std::io::stdout())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue