#![deny(missing_docs)] mod common; mod errors; mod events; mod methods; pub(crate) mod structs; mod types; use super::{util, Abigen}; use crate::contract::{methods::MethodAlias, structs::InternalStructs}; use ethers_core::{ abi::{Abi, AbiParser, ErrorExt, EventExt, JsonAbi}, macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate}, types::Bytes, }; use eyre::{eyre, Context as _, Result}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::quote; use serde::Deserialize; use std::collections::BTreeMap; use syn::Path; /// The result of `Context::expand` #[derive(Debug)] pub struct ExpandedContract { /// The name of the contract module pub module: Ident, /// The contract module's imports pub imports: TokenStream, /// Contract, Middle related implementations pub contract: TokenStream, /// All event impls of the contract pub events: TokenStream, /// All error impls of the contract pub errors: TokenStream, /// All contract call struct related types pub call_structs: TokenStream, /// The contract's internal structs pub abi_structs: TokenStream, } impl ExpandedContract { /// Merges everything into a single module pub fn into_tokens(self) -> TokenStream { let ExpandedContract { module, imports, contract, events, call_structs, abi_structs, errors, } = self; quote! { // export all the created data types pub use #module::*; #[allow(clippy::too_many_arguments, non_camel_case_types)] pub mod #module { #imports #contract #errors #events #call_structs #abi_structs } } } } /// Internal shared context for generating smart contract bindings. pub struct Context { /// The ABI string pre-parsing. abi_str: Literal, /// The parsed ABI. abi: Abi, /// The parser used for human readable format abi_parser: AbiParser, /// Contains all the solidity structs extracted from the JSON ABI. internal_structs: InternalStructs, /// Was the ABI in human readable format? human_readable: bool, /// The contract name as an identifier. contract_ident: Ident, /// The contract name as string contract_name: String, /// Manually specified method aliases. method_aliases: BTreeMap, /// Manually specified method aliases. error_aliases: BTreeMap, /// Derives added to event structs and enums. event_derives: Vec, /// Manually specified event aliases. event_aliases: BTreeMap, /// Bytecode extracted from the abi string input, if present. contract_bytecode: Option, } impl Context { /// Expands the whole rust contract pub fn expand(&self) -> Result { let name = &self.contract_ident; let name_mod = util::ident(&util::safe_module_name(&self.contract_name)); let abi_name = self.inline_abi_ident(); // 0. Imports let imports = common::imports(&name.to_string()); // 1. Declare Contract struct let struct_decl = common::struct_declaration(self); // 2. Declare events structs & impl FromTokens for each event let events_decl = self.events_declaration()?; // 3. impl block for the event functions let contract_events = self.event_methods()?; // 4. impl block for the contract methods and their corresponding types let (contract_methods, call_structs) = self.methods_and_call_structs()?; // 5. generate deploy function if let deployment_methods = self.deployment_methods(); // 6. Declare the structs parsed from the human readable abi let abi_structs_decl = self.abi_structs()?; // 7. declare all error types let errors_decl = self.errors()?; let ethers_core = ethers_core_crate(); let ethers_contract = ethers_contract_crate(); let ethers_providers = ethers_providers_crate(); let contract = quote! { #struct_decl impl #name { /// Creates a new contract instance with the specified `ethers` /// client at the given `Address`. The contract derefs to a `ethers::Contract` /// object pub fn new>(address: T, client: ::std::sync::Arc) -> Self { Self(#ethers_contract::Contract::new(address.into(), #abi_name.clone(), client)) } #deployment_methods #contract_methods #contract_events } impl From<#ethers_contract::Contract> for #name { fn from(contract: #ethers_contract::Contract) -> Self { Self::new(contract.address(), contract.client()) } } }; Ok(ExpandedContract { module: name_mod, imports, contract, events: events_decl, errors: errors_decl, call_structs, abi_structs: abi_structs_decl, }) } /// Create a context from the code generation arguments. 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))?; // holds the bytecode parsed from the abi_str, if present let mut contract_bytecode = None; let (abi, human_readable, abi_parser) = parse_abi(&abi_str).wrap_err_with(|| { eyre::eyre!("error parsing abi for contract: {}", args.contract_name) })?; // try to extract all the solidity structs from the normal JSON ABI // we need to parse the json abi again because we need the internalType fields which are // omitted by ethabi. If the ABI was defined as human readable we use the `internal_structs` // from the Abi Parser let internal_structs = if human_readable { let mut internal_structs = InternalStructs::default(); // the types in the abi_parser are already valid rust types so simply clone them to make // it consistent with the `RawAbi` variant internal_structs .rust_type_names .extend(abi_parser.function_params.values().map(|ty| (ty.clone(), ty.clone()))); internal_structs.function_params = abi_parser.function_params.clone(); internal_structs.event_params = abi_parser.event_params.clone(); internal_structs.outputs = abi_parser.outputs.clone(); internal_structs } else { match serde_json::from_str::(&abi_str)? { JsonAbi::Object(obj) => { // need to update the `abi_str` here because we only want the `"abi": [...]` // part of the json object in the contract binding abi_str = serde_json::to_string(&obj.abi)?; contract_bytecode = obj.bytecode; InternalStructs::new(obj.abi) } JsonAbi::Array(abi) => InternalStructs::new(abi), } }; let contract_ident = util::ident(&args.contract_name); // NOTE: We only check for duplicate signatures here, since if there are // duplicate aliases, the compiler will produce a warning because a // method will be re-defined. let mut method_aliases = BTreeMap::new(); for (signature, alias) in args.method_aliases.into_iter() { let alias = MethodAlias { function_name: util::safe_ident(&alias), struct_name: util::safe_pascal_case_ident(&alias), }; if method_aliases.insert(signature.clone(), alias).is_some() { eyre::bail!("duplicate method signature '{}' in method aliases", signature) } } let mut event_aliases = BTreeMap::new(); for (signature, alias) in args.event_aliases.into_iter() { let alias = syn::parse_str(&alias)?; event_aliases.insert(signature, alias); } // also check for overloaded events not covered by aliases, in which case we simply // numerate them for events in abi.events.values() { insert_alias_names( &mut event_aliases, events.iter().map(|e| (e.abi_signature(), e.name.as_str())), events::event_struct_alias, ); } let mut error_aliases = BTreeMap::new(); for (signature, alias) in args.error_aliases.into_iter() { let alias = syn::parse_str(&alias)?; error_aliases.insert(signature, alias); } // also check for overloaded errors not covered by aliases, in which case we simply // numerate them for errors in abi.errors.values() { insert_alias_names( &mut error_aliases, errors.iter().map(|e| (e.abi_signature(), e.name.as_str())), errors::error_struct_alias, ); } let event_derives = args .derives .iter() .map(|derive| syn::parse_str::(derive)) .collect::, _>>() .context("failed to parse event derives")?; Ok(Context { abi, human_readable, abi_str: Literal::string(&abi_str), abi_parser, internal_structs, contract_ident, contract_name: args.contract_name, contract_bytecode, method_aliases, error_aliases: Default::default(), event_derives, event_aliases, }) } /// The initial name fo the contract pub(crate) fn contract_name(&self) -> &str { &self.contract_name } /// 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())) } /// 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())) } /// The internal abi struct mapping table pub fn internal_structs(&self) -> &InternalStructs { &self.internal_structs } /// The internal mutable abi struct mapping table pub fn internal_structs_mut(&mut self) -> &mut InternalStructs { &mut self.internal_structs } } /// Solidity supports overloading as long as the signature of an event, error, function is unique, /// which results in a mapping `(name -> Vec)` /// /// /// This will populate the alias map for the value in the mapping (`Vec`) via `abi /// signature -> name` using the given aliases and merge it with all names not yet aliased. /// /// If the iterator yields more than one element, this will simply numerate them fn insert_alias_names<'a, I, F>(aliases: &mut BTreeMap, elements: I, get_ident: F) where I: IntoIterator, F: Fn(&str) -> Ident, { let not_aliased = elements.into_iter().filter(|(sig, _name)| !aliases.contains_key(sig)).collect::>(); if not_aliased.len() > 1 { let mut overloaded_aliases = Vec::new(); for (idx, (sig, name)) in not_aliased.into_iter().enumerate() { let unique_name = format!("{name}{}", idx + 1); overloaded_aliases.push((sig, get_ident(&unique_name))); } aliases.extend(overloaded_aliases); } } /// Parse the abi via `Source::parse` and return if the abi defined as human readable fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> { let mut abi_parser = AbiParser::default(); let res = if let Ok(abi) = abi_parser.parse_str(abi_str) { (abi, true, abi_parser) } else { // a best-effort coercion of an ABI or an artifact JSON into an artifact JSON. let contract: JsonContract = serde_json::from_str(abi_str) .wrap_err_with(|| eyre::eyre!("failed deserializing abi:\n{}", abi_str))?; (contract.into_abi(), false, abi_parser) }; Ok(res) } #[derive(Deserialize)] struct ContractObject { abi: Abi, } #[derive(Deserialize)] #[serde(untagged)] enum JsonContract { /// json object input as `{"abi": [..], "bin": "..."}` Object(ContractObject), /// json array input as `[]` Array(Abi), } impl JsonContract { fn into_abi(self) -> Abi { match self { JsonContract::Object(o) => o.abi, JsonContract::Array(abi) => abi, } } }