2023-01-13 18:17:32 +00:00
|
|
|
//! # Abigen
|
|
|
|
//!
|
|
|
|
//! Programmatically generate type-safe Rust bindings for Ethereum smart contracts.
|
|
|
|
//!
|
|
|
|
//! This crate is intended to be used either indirectly with the [`abigen` procedural macro][abigen]
|
|
|
|
//! or directly from a build script / CLI.
|
|
|
|
//!
|
|
|
|
//! [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html
|
2020-05-26 09:37:31 +00:00
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
#![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)]
|
2020-05-26 09:37:31 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
#[allow(missing_docs)]
|
|
|
|
#[macro_use]
|
|
|
|
#[path = "test/macros.rs"]
|
|
|
|
mod test_macros;
|
|
|
|
|
2021-10-11 14:18:09 +00:00
|
|
|
/// Contains types to generate rust bindings for solidity contracts
|
|
|
|
pub mod contract;
|
2022-09-08 16:07:38 +00:00
|
|
|
pub use contract::structs::InternalStructs;
|
2020-05-26 18:57:59 +00:00
|
|
|
use contract::Context;
|
|
|
|
|
2020-05-26 09:37:31 +00:00
|
|
|
mod source;
|
|
|
|
mod util;
|
|
|
|
|
2022-08-04 15:22:00 +00:00
|
|
|
pub mod filter;
|
|
|
|
pub use filter::{ContractFilter, ExcludeContracts, SelectContracts};
|
2022-02-02 13:57:31 +00:00
|
|
|
pub mod multi;
|
|
|
|
pub use multi::MultiAbigen;
|
|
|
|
|
2020-05-31 16:01:34 +00:00
|
|
|
pub use ethers_core::types::Address;
|
2020-05-26 18:57:59 +00:00
|
|
|
pub use source::Source;
|
2021-11-05 13:00:01 +00:00
|
|
|
pub use util::parse_address;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2022-02-24 20:09:08 +00:00
|
|
|
use crate::contract::ExpandedContract;
|
2022-02-02 20:44:53 +00:00
|
|
|
use eyre::Result;
|
2020-05-26 09:37:31 +00:00
|
|
|
use proc_macro2::TokenStream;
|
2022-02-02 13:57:31 +00:00
|
|
|
use std::{collections::HashMap, fs::File, io::Write, path::Path};
|
2020-05-26 09:37:31 +00:00
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Programmatically generate type-safe Rust bindings for an Ethereum smart contract from its ABI.
|
2020-06-10 19:34:39 +00:00
|
|
|
///
|
2023-01-13 18:17:32 +00:00
|
|
|
/// For all the supported ABI sources, see [Source].
|
2020-06-16 12:08:42 +00:00
|
|
|
///
|
2023-01-13 18:17:32 +00:00
|
|
|
/// To generate bindings for *multiple* contracts at once, see [`MultiAbigen`].
|
|
|
|
///
|
|
|
|
/// To generate bindings at compile time, see [the abigen! macro][abigen], or use in a `build.rs`
|
|
|
|
/// file.
|
|
|
|
///
|
|
|
|
/// [abigen]: https://docs.rs/ethers/latest/ethers/contract/macro.abigen.html
|
2022-02-23 10:36:14 +00:00
|
|
|
///
|
2020-06-10 19:34:39 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Running the code below will generate a file called `token.rs` containing the bindings inside,
|
|
|
|
/// which exports an `ERC20Token` struct, along with all its events.
|
2020-06-10 19:34:39 +00:00
|
|
|
///
|
|
|
|
/// ```no_run
|
2020-06-11 09:16:36 +00:00
|
|
|
/// # use ethers_contract_abigen::Abigen;
|
2020-06-10 19:34:39 +00:00
|
|
|
/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
/// Abigen::new("ERC20Token", "./abi.json")?.generate()?.write_to_file("token.rs")?;
|
|
|
|
/// # Ok(())
|
|
|
|
/// # }
|
2023-01-13 18:17:32 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
#[must_use = "Abigen does nothing unless you generate or expand it."]
|
2020-06-03 20:09:46 +00:00
|
|
|
pub struct Abigen {
|
2023-01-13 18:17:32 +00:00
|
|
|
/// The source of the ABI JSON for the contract whose bindings are being generated.
|
2020-05-26 18:57:59 +00:00
|
|
|
abi_source: Source,
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// The contract's name to use for the generated type.
|
2020-05-26 18:57:59 +00:00
|
|
|
contract_name: String,
|
|
|
|
|
2020-05-26 09:37:31 +00:00
|
|
|
/// Manually specified contract method aliases.
|
|
|
|
method_aliases: HashMap<String, String>,
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Manually specified `derive` macros added to all structs and enums.
|
|
|
|
derives: Vec<String>,
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Whether to format the generated bindings using [`prettyplease`].
|
2023-01-09 05:17:22 +00:00
|
|
|
format: bool,
|
2021-09-03 15:57:40 +00:00
|
|
|
|
|
|
|
/// Manually specified event name aliases.
|
|
|
|
event_aliases: HashMap<String, String>,
|
2022-08-02 18:03:52 +00:00
|
|
|
|
|
|
|
/// Manually specified error name aliases.
|
|
|
|
error_aliases: HashMap<String, String>,
|
2020-05-26 09:37:31 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 20:09:46 +00:00
|
|
|
impl Abigen {
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Creates a new builder with the given [ABI Source][Source].
|
|
|
|
pub fn new<T: Into<String>, S: AsRef<str>>(contract_name: T, abi_source: S) -> Result<Self> {
|
2020-06-03 20:09:46 +00:00
|
|
|
let abi_source = abi_source.as_ref().parse()?;
|
|
|
|
Ok(Self {
|
|
|
|
abi_source,
|
2023-01-13 18:17:32 +00:00
|
|
|
contract_name: contract_name.into(),
|
2023-01-09 05:17:22 +00:00
|
|
|
format: true,
|
2023-01-13 18:17:32 +00:00
|
|
|
method_aliases: Default::default(),
|
|
|
|
derives: Default::default(),
|
|
|
|
event_aliases: Default::default(),
|
2022-08-02 18:03:52 +00:00
|
|
|
error_aliases: Default::default(),
|
2020-06-03 20:09:46 +00:00
|
|
|
})
|
2020-05-26 09:37:31 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Attempts to load a new builder from an ABI JSON file at the specific path.
|
2022-02-02 13:57:31 +00:00
|
|
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
|
|
|
|
let name = path
|
|
|
|
.as_ref()
|
|
|
|
.file_stem()
|
2022-02-02 20:44:53 +00:00
|
|
|
.ok_or_else(|| eyre::format_err!("Missing file stem in path"))?
|
2022-02-02 13:57:31 +00:00
|
|
|
.to_str()
|
2022-02-02 20:44:53 +00:00
|
|
|
.ok_or_else(|| eyre::format_err!("Unable to convert file stem to string"))?;
|
2022-02-02 13:57:31 +00:00
|
|
|
|
2022-10-11 17:48:30 +00:00
|
|
|
// 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.");
|
|
|
|
|
2022-02-02 13:57:31 +00:00
|
|
|
Self::new(name, std::fs::read_to_string(path.as_ref())?)
|
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Manually adds a solidity event alias to specify what the event struct and function name will
|
|
|
|
/// be in Rust.
|
|
|
|
///
|
|
|
|
/// For events without an alias, the `PascalCase` event name will be used.
|
2021-09-03 15:57:40 +00:00
|
|
|
pub fn add_event_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
|
|
|
|
where
|
|
|
|
S1: Into<String>,
|
|
|
|
S2: Into<String>,
|
|
|
|
{
|
|
|
|
self.event_aliases.insert(signature.into(), alias.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Add a Solidity method error alias to specify the generated method name.
|
|
|
|
///
|
|
|
|
/// For methods without an alias, the `snake_case` method name will be used.
|
2020-05-26 09:37:31 +00:00
|
|
|
pub fn add_method_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
|
|
|
|
where
|
|
|
|
S1: Into<String>,
|
|
|
|
S2: Into<String>,
|
|
|
|
{
|
2020-06-03 20:09:46 +00:00
|
|
|
self.method_aliases.insert(signature.into(), alias.into());
|
2020-05-26 09:37:31 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Add a Solidity custom error alias to specify the generated struct's name.
|
|
|
|
///
|
|
|
|
/// For errors without an alias, the `PascalCase` error name will be used.
|
2022-08-02 18:03:52 +00:00
|
|
|
pub fn add_error_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
|
|
|
|
where
|
|
|
|
S1: Into<String>,
|
|
|
|
S2: Into<String>,
|
|
|
|
{
|
|
|
|
self.error_aliases.insert(signature.into(), alias.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-19 04:28:38 +00:00
|
|
|
#[must_use]
|
2023-01-09 05:17:22 +00:00
|
|
|
#[deprecated = "Use format instead"]
|
|
|
|
#[doc(hidden)]
|
2020-05-26 18:57:59 +00:00
|
|
|
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
2023-01-09 05:17:22 +00:00
|
|
|
self.format = rustfmt;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Specify whether to format the code or not. True by default.
|
|
|
|
///
|
|
|
|
/// This will use [`prettyplease`], so the resulting formatted code **will not** be affected by
|
|
|
|
/// the local `rustfmt` version or config.
|
|
|
|
pub fn format(mut self, format: bool) -> Self {
|
|
|
|
self.format = format;
|
2020-05-26 09:37:31 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
#[deprecated = "Use add_derive instead"]
|
|
|
|
#[doc(hidden)]
|
|
|
|
pub fn add_event_derive<S: Into<String>>(mut self, derive: S) -> Self {
|
|
|
|
self.derives.push(derive.into());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add a custom derive to the derives for all structs and enums.
|
2020-05-26 09:37:31 +00:00
|
|
|
///
|
2023-01-13 18:17:32 +00:00
|
|
|
/// For example, this makes it possible to derive serde::Serialize and serde::Deserialize.
|
|
|
|
pub fn add_derive<S: Into<String>>(mut self, derive: S) -> Self {
|
|
|
|
self.derives.push(derive.into());
|
2020-05-26 09:37:31 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates the contract bindings.
|
|
|
|
pub fn generate(self) -> Result<ContractBindings> {
|
2023-01-09 05:17:22 +00:00
|
|
|
let format = self.format;
|
2022-02-02 13:57:31 +00:00
|
|
|
let name = self.contract_name.clone();
|
2022-02-24 20:09:08 +00:00
|
|
|
let (expanded, _) = self.expand()?;
|
2023-01-09 05:17:22 +00:00
|
|
|
Ok(ContractBindings { tokens: expanded.into_tokens(), format, name })
|
2022-02-24 20:09:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Expands the `Abigen` and returns the [`ExpandedContract`] that holds all tokens and the
|
|
|
|
/// [`Context`] that holds the state used during expansion.
|
|
|
|
pub fn expand(self) -> Result<(ExpandedContract, Context)> {
|
|
|
|
let ctx = Context::from_abigen(self)?;
|
|
|
|
Ok((ctx.expand()?, ctx))
|
2020-05-26 09:37:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
pub struct ContractBindings {
|
|
|
|
/// The TokenStream representing the contract bindings.
|
|
|
|
tokens: TokenStream,
|
|
|
|
/// The output options used for serialization.
|
2023-01-09 05:17:22 +00:00
|
|
|
format: bool,
|
2022-02-02 13:57:31 +00:00
|
|
|
/// The contract name
|
|
|
|
name: String,
|
2020-05-26 09:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ContractBindings {
|
|
|
|
/// Writes the bindings to a given `Write`.
|
|
|
|
pub fn write<W>(&self, mut w: W) -> Result<()>
|
|
|
|
where
|
|
|
|
W: Write,
|
|
|
|
{
|
2023-01-09 05:17:22 +00:00
|
|
|
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()
|
2020-05-26 09:37:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
w.write_all(source.as_bytes())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-02-02 13:57:31 +00:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2020-05-26 09:37:31 +00:00
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
|
2022-02-02 13:57:31 +00:00
|
|
|
/// 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<()>
|
2021-12-23 14:38:07 +00:00
|
|
|
where
|
2022-02-02 13:57:31 +00:00
|
|
|
P: AsRef<Path>,
|
2021-12-23 14:38:07 +00:00
|
|
|
{
|
2022-02-02 13:57:31 +00:00
|
|
|
let file = dir.as_ref().join(self.module_filename());
|
|
|
|
self.write_to_file(file)
|
2021-12-23 14:38:07 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 13:57:31 +00:00
|
|
|
/// Converts the bindings into its underlying token stream. This allows it
|
|
|
|
/// to be used within a procedural macro.
|
|
|
|
pub fn into_tokens(self) -> TokenStream {
|
|
|
|
self.tokens
|
2021-12-23 14:38:07 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Generate the default module name (snake case of the contract name).
|
2022-02-02 13:57:31 +00:00
|
|
|
pub fn module_name(&self) -> String {
|
2022-07-24 01:18:24 +00:00
|
|
|
util::safe_module_name(&self.name)
|
2021-12-23 14:38:07 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 18:17:32 +00:00
|
|
|
/// Generate the default file name of the module.
|
2022-02-02 13:57:31 +00:00
|
|
|
pub fn module_filename(&self) -> String {
|
|
|
|
let mut name = self.module_name();
|
|
|
|
name.extend([".rs"]);
|
|
|
|
name
|
2021-12-23 14:38:07 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-22 18:26:21 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2022-02-23 10:46:52 +00:00
|
|
|
use ethers_solc::project_util::TempProject;
|
2022-02-22 18:26:21 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_generate_structs() {
|
|
|
|
let greeter = include_str!("../../tests/solidity-contracts/greeter_with_struct.json");
|
|
|
|
let abigen = Abigen::new("Greeter", greeter).unwrap();
|
|
|
|
let gen = abigen.generate().unwrap();
|
|
|
|
let out = gen.tokens.to_string();
|
|
|
|
assert!(out.contains("pub struct Stuff"));
|
|
|
|
}
|
2022-02-23 10:46:52 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_compile_and_generate() {
|
|
|
|
let tmp = TempProject::dapptools().unwrap();
|
|
|
|
|
|
|
|
tmp.add_source(
|
|
|
|
"Greeter",
|
|
|
|
r#"
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity >=0.8.0;
|
|
|
|
|
|
|
|
contract Greeter {
|
|
|
|
|
|
|
|
struct Inner {
|
|
|
|
bool a;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Stuff {
|
|
|
|
Inner inner;
|
|
|
|
}
|
|
|
|
|
|
|
|
function greet(Stuff calldata stuff) public view returns (Stuff memory) {
|
|
|
|
return stuff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let _ = tmp.compile().unwrap();
|
|
|
|
|
|
|
|
let abigen =
|
|
|
|
Abigen::from_file(tmp.artifacts_path().join("Greeter.sol/Greeter.json")).unwrap();
|
|
|
|
let gen = abigen.generate().unwrap();
|
|
|
|
let out = gen.tokens.to_string();
|
|
|
|
assert!(out.contains("pub struct Stuff"));
|
|
|
|
assert!(out.contains("pub struct Inner"));
|
|
|
|
}
|
2022-10-11 17:48:30 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_compile_and_generate_with_punctuation() {
|
|
|
|
let tmp = TempProject::dapptools().unwrap();
|
|
|
|
|
|
|
|
tmp.add_source(
|
|
|
|
"Greeter.t.sol",
|
|
|
|
r#"
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity >=0.8.0;
|
|
|
|
|
|
|
|
contract Greeter {
|
|
|
|
struct Inner {
|
|
|
|
bool a;
|
|
|
|
}
|
|
|
|
struct Stuff {
|
|
|
|
Inner inner;
|
|
|
|
}
|
|
|
|
function greet(Stuff calldata stuff) public view returns (Stuff memory) {
|
|
|
|
return stuff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let _ = tmp.compile().unwrap();
|
|
|
|
|
|
|
|
let abigen =
|
|
|
|
Abigen::from_file(tmp.artifacts_path().join("Greeter.t.sol/Greeter.json")).unwrap();
|
|
|
|
let gen = abigen.generate().unwrap();
|
|
|
|
let out = gen.tokens.to_string();
|
|
|
|
assert!(out.contains("pub struct Stuff"));
|
|
|
|
assert!(out.contains("pub struct Inner"));
|
|
|
|
}
|
2022-02-22 18:26:21 +00:00
|
|
|
}
|