ethcontract_generate/
lib.rs

1#![deny(missing_docs, unsafe_code)]
2
3//! Crate for generating type-safe bindings to Ethereum smart contracts. This
4//! crate is intended to be used either indirectly with the `ethcontract`
5//! crate's `contract` procedural macro or directly from a build script.
6
7#[cfg(test)]
8#[allow(missing_docs)]
9#[macro_use]
10#[path = "test/macros.rs"]
11mod test_macros;
12
13pub mod source;
14
15mod generate;
16mod rustfmt;
17mod util;
18
19pub use crate::source::Source;
20pub use crate::util::parse_address;
21
22pub use ethcontract_common::artifact::{Artifact, ContractMut, InsertResult};
23
24/// Convenience re-imports so that you don't have to add `ethcontract-common`
25/// as a dependency.
26pub mod loaders {
27    pub use ethcontract_common::artifact::hardhat::{
28        Format as HardHatFormat, HardHatLoader, NetworkEntry,
29    };
30    pub use ethcontract_common::artifact::truffle::TruffleLoader;
31}
32
33use anyhow::Result;
34use ethcontract_common::contract::Network;
35use ethcontract_common::Contract;
36use proc_macro2::TokenStream;
37use std::collections::HashMap;
38use std::fs::File;
39use std::io::{BufWriter, Write};
40use std::path::Path;
41
42/// Builder for generating contract code. Note that no code is generated until
43/// the builder is finalized with `generate` or `output`.
44#[must_use = "contract builders do nothing unless you generate bindings"]
45pub struct ContractBuilder {
46    /// The runtime crate name to use.
47    pub runtime_crate_name: String,
48
49    /// The visibility modifier to use for the generated module and contract
50    /// re-export.
51    pub visibility_modifier: Option<String>,
52
53    /// Override the contract module name that contains the generated code.
54    pub contract_mod_override: Option<String>,
55
56    /// Override the contract name to use for the generated type.
57    pub contract_name_override: Option<String>,
58
59    /// Manually specified deployed contract address and transaction hash.
60    pub networks: HashMap<String, Network>,
61
62    /// Manually specified contract method aliases.
63    pub method_aliases: HashMap<String, String>,
64
65    /// Derives added to event structs and enums.
66    pub event_derives: Vec<String>,
67
68    /// Format generated code sing locally installed copy of `rustfmt`.
69    pub rustfmt: bool,
70}
71
72impl ContractBuilder {
73    /// Creates a new contract builder with default settings.
74    pub fn new() -> Self {
75        ContractBuilder {
76            runtime_crate_name: "ethcontract".to_string(),
77            visibility_modifier: None,
78            contract_mod_override: None,
79            contract_name_override: None,
80            networks: Default::default(),
81            method_aliases: Default::default(),
82            event_derives: vec![],
83            rustfmt: true,
84        }
85    }
86
87    /// Sets the crate name for the runtime crate. This setting is usually only
88    /// needed if the crate was renamed in the Cargo manifest.
89    pub fn runtime_crate_name(mut self, name: impl Into<String>) -> Self {
90        self.runtime_crate_name = name.into();
91        self
92    }
93
94    /// Sets an optional visibility modifier for the generated module and
95    /// contract re-export.
96    pub fn visibility_modifier(mut self, vis: impl Into<String>) -> Self {
97        self.visibility_modifier = Some(vis.into());
98        self
99    }
100
101    /// Sets the optional contract module name override.
102    pub fn contract_mod_override(mut self, name: impl Into<String>) -> Self {
103        self.contract_mod_override = Some(name.into());
104        self
105    }
106
107    /// Sets the optional contract name override. This setting is needed when
108    /// using an artifact JSON source that does not provide a contract name such
109    /// as Etherscan.
110    pub fn contract_name_override(mut self, name: impl Into<String>) -> Self {
111        self.contract_name_override = Some(name.into());
112        self
113    }
114
115    /// Adds a deployed address and deployment transaction
116    /// hash or block of a contract for a given network. Note that manually
117    /// specified deployments take precedence over deployments in the artifact.
118    ///
119    /// This is useful for integration test scenarios where the address of a
120    /// contract on the test node is deterministic, but the contract address
121    /// is not in the artifact.
122    pub fn add_network(mut self, chain_id: impl Into<String>, network: Network) -> Self {
123        self.networks.insert(chain_id.into(), network);
124        self
125    }
126
127    /// Adds a deployed address. Parses address from string.
128    /// See [`add_deployment`] for more information.
129    ///
130    /// # Panics
131    ///
132    /// This method panics if the specified address string is invalid. See
133    /// [`parse_address`] for more information on the address string format.
134    pub fn add_network_str(self, chain_id: impl Into<String>, address: &str) -> Self {
135        self.add_network(
136            chain_id,
137            Network {
138                address: parse_address(address).expect("failed to parse address"),
139                deployment_information: None,
140            },
141        )
142    }
143
144    /// Adds a solidity method alias to specify what the method name
145    /// will be in Rust. For solidity methods without an alias, the snake cased
146    /// method name will be used.
147    pub fn add_method_alias(
148        mut self,
149        signature: impl Into<String>,
150        alias: impl Into<String>,
151    ) -> Self {
152        self.method_aliases.insert(signature.into(), alias.into());
153        self
154    }
155
156    /// Specifies whether or not to format the code using a locally installed
157    /// copy of `rustfmt`.
158    ///
159    /// Note that in case `rustfmt` does not exist or produces an error, the
160    /// un-formatted code will be used.
161    pub fn rustfmt(mut self, rustfmt: bool) -> Self {
162        self.rustfmt = rustfmt;
163        self
164    }
165
166    /// Adds a custom derive to the derives for event structs and enums.
167    ///
168    /// This makes it possible to, for example, derive `serde::Serialize` and
169    /// `serde::Deserialize` for events.
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// # use ethcontract_generate::ContractBuilder;
175    /// let builder = ContractBuilder::new()
176    ///     .add_event_derive("serde::Serialize")
177    ///     .add_event_derive("serde::Deserialize");
178    /// ```
179    pub fn add_event_derive(mut self, derive: impl Into<String>) -> Self {
180        self.event_derives.push(derive.into());
181        self
182    }
183
184    /// Generates the contract bindings.
185    pub fn generate(self, contract: &Contract) -> Result<ContractBindings> {
186        let rustfmt = self.rustfmt;
187        Ok(ContractBindings {
188            tokens: generate::expand(contract, self)?,
189            rustfmt,
190        })
191    }
192}
193
194impl Default for ContractBuilder {
195    fn default() -> Self {
196        ContractBuilder::new()
197    }
198}
199
200/// Type-safe contract bindings generated by a `Builder`. This type can be
201/// either written to file or into a token stream for use in a procedural macro.
202pub struct ContractBindings {
203    /// The TokenStream representing the contract bindings.
204    pub tokens: TokenStream,
205
206    /// Format generated code using locally installed copy of `rustfmt`.
207    pub rustfmt: bool,
208}
209
210impl ContractBindings {
211    /// Specifies whether or not to format the code using a locally installed
212    /// copy of `rustfmt`.
213    ///
214    /// Note that in case `rustfmt` does not exist or produces an error, the
215    /// un-formatted code will be used.
216    #[must_use = "specifying rustfmt does nothing unless you write bindings"]
217    pub fn rustfmt(mut self, rustfmt: bool) -> Self {
218        self.rustfmt = rustfmt;
219        self
220    }
221
222    /// Writes the bindings to a given `Write`.
223    pub fn write(&self, mut w: impl Write) -> Result<()> {
224        let source = {
225            let raw = self.tokens.to_string();
226
227            if self.rustfmt {
228                rustfmt::format(&raw).unwrap_or(raw)
229            } else {
230                raw
231            }
232        };
233
234        w.write_all(source.as_bytes())?;
235        Ok(())
236    }
237
238    /// Writes the bindings to the specified file.
239    pub fn write_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
240        let file = File::create(path)?;
241        let writer = BufWriter::new(file);
242        self.write(writer)
243    }
244
245    /// Converts the bindings into its underlying token stream. This allows it
246    /// to be used within a procedural macro.
247    pub fn into_tokens(self) -> TokenStream {
248        self.tokens
249    }
250}