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}