1#![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)]
11#![warn(unreachable_pub)]
12#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
13
14#[cfg(test)]
15#[allow(missing_docs)]
16#[macro_use]
17#[path = "test/macros.rs"]
18mod test_macros;
19
20pub mod contract;
21pub use contract::structs::InternalStructs;
22
23pub mod filter;
24pub use filter::{ContractFilter, ExcludeContracts, SelectContracts};
25
26pub mod multi;
27pub use multi::MultiAbigen;
28
29mod source;
30#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
31pub use source::Explorer;
32pub use source::Source;
33
34mod util;
35mod verbatim;
36
37pub use ethers_core::types::Address;
38
39use contract::{Context, ExpandedContract};
40use eyre::{Result, WrapErr};
41use proc_macro2::{Ident, TokenStream};
42use quote::ToTokens;
43use std::{collections::HashMap, fmt, fs, io, path::Path};
44
45#[derive(Clone, Debug)]
67#[must_use = "Abigen does nothing unless you generate or expand it."]
68pub struct Abigen {
69 abi_source: Source,
71
72 contract_name: Ident,
74
75 format: bool,
77
78 emit_cargo_directives: bool,
82
83 method_aliases: HashMap<String, String>,
85
86 event_aliases: HashMap<String, String>,
88
89 error_aliases: HashMap<String, String>,
91
92 derives: Vec<syn::Path>,
94}
95
96impl Default for Abigen {
97 fn default() -> Self {
98 Self {
99 abi_source: Source::default(),
100 contract_name: Ident::new("DefaultContract", proc_macro2::Span::call_site()),
101 format: true,
102 emit_cargo_directives: false,
103 method_aliases: HashMap::new(),
104 derives: Vec::new(),
105 event_aliases: HashMap::new(),
106 error_aliases: HashMap::new(),
107 }
108 }
109}
110
111impl Abigen {
112 pub fn new<T: AsRef<str>, S: AsRef<str>>(contract_name: T, abi_source: S) -> Result<Self> {
119 let abi_source: Source = abi_source.as_ref().parse()?;
120 Ok(Self {
121 emit_cargo_directives: abi_source.is_local() && in_build_script(),
122 abi_source,
123 contract_name: syn::parse_str(contract_name.as_ref())?,
124 ..Default::default()
125 })
126 }
127
128 pub fn new_raw(contract_name: Ident, abi_source: Source) -> Self {
130 Self {
131 emit_cargo_directives: abi_source.is_local() && in_build_script(),
132 abi_source,
133 contract_name,
134 ..Default::default()
135 }
136 }
137
138 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
140 let path = path.as_ref();
141 let path = path
142 .to_str()
143 .ok_or_else(|| eyre::eyre!("path is not valid UTF-8: {}", path.display()))?;
144 let source = Source::local(path)?;
145 let name = source.as_local().unwrap().file_name().unwrap().to_str().unwrap();
147 let name = name.split('.').next().unwrap();
149
150 let name = name.replace('-', "_").replace(|c: char| c.is_whitespace(), "");
152
153 Ok(Self::new_raw(
154 syn::parse_str(&util::safe_identifier_name(name)).wrap_err_with(|| {
155 format!("failed convert file name to contract identifier {}", path)
156 })?,
157 source,
158 ))
159 }
160
161 pub fn add_event_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
166 where
167 S1: Into<String>,
168 S2: Into<String>,
169 {
170 self.event_aliases.insert(signature.into(), alias.into());
171 self
172 }
173
174 pub fn add_method_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
178 where
179 S1: Into<String>,
180 S2: Into<String>,
181 {
182 self.method_aliases.insert(signature.into(), alias.into());
183 self
184 }
185
186 pub fn add_error_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
190 where
191 S1: Into<String>,
192 S2: Into<String>,
193 {
194 self.error_aliases.insert(signature.into(), alias.into());
195 self
196 }
197
198 #[deprecated = "Use add_derive instead"]
199 #[doc(hidden)]
200 pub fn add_event_derive<S: AsRef<str>>(self, derive: S) -> Result<Self> {
201 self.add_derive(derive)
202 }
203
204 pub fn add_derive<S: AsRef<str>>(mut self, derive: S) -> Result<Self> {
208 self.derives.push(syn::parse_str(derive.as_ref())?);
209 Ok(self)
210 }
211
212 #[deprecated = "Use format instead"]
213 #[doc(hidden)]
214 pub fn rustfmt(mut self, rustfmt: bool) -> Self {
215 self.format = rustfmt;
216 self
217 }
218
219 pub fn format(mut self, format: bool) -> Self {
224 self.format = format;
225 self
226 }
227
228 pub fn emit_cargo_directives(mut self, emit_cargo_directives: bool) -> Self {
233 self.emit_cargo_directives = emit_cargo_directives;
234 self
235 }
236
237 pub fn generate(self) -> Result<ContractBindings> {
239 let format = self.format;
240 let emit = self.emit_cargo_directives;
241 let path = self.abi_source.as_local().cloned();
242 let name = self.contract_name.to_string();
243
244 let (expanded, _) = self.expand()?;
245
246 let path = if let (true, Some(path)) = (emit, &path) {
248 println!("cargo:rerun-if-changed={}", path.display());
249 None
250 } else {
251 path.as_deref()
252 };
253
254 Ok(ContractBindings { tokens: expanded.into_tokens_with_path(path), format, name })
255 }
256
257 pub fn expand(self) -> Result<(ExpandedContract, Context)> {
260 let ctx = Context::from_abigen(self)?;
261 Ok((ctx.expand()?, ctx))
262 }
263}
264
265impl Abigen {
266 pub fn source(&self) -> &Source {
268 &self.abi_source
269 }
270
271 pub fn source_mut(&mut self) -> &mut Source {
273 &mut self.abi_source
274 }
275
276 pub fn name(&self) -> &Ident {
278 &self.contract_name
279 }
280
281 pub fn name_mut(&mut self) -> &mut Ident {
283 &mut self.contract_name
284 }
285
286 pub fn method_aliases(&self) -> &HashMap<String, String> {
288 &self.method_aliases
289 }
290
291 pub fn method_aliases_mut(&mut self) -> &mut HashMap<String, String> {
293 &mut self.method_aliases
294 }
295
296 pub fn event_aliases(&self) -> &HashMap<String, String> {
298 &self.event_aliases
299 }
300
301 pub fn error_aliases_mut(&mut self) -> &mut HashMap<String, String> {
303 &mut self.error_aliases
304 }
305
306 pub fn derives(&self) -> &Vec<syn::Path> {
308 &self.derives
309 }
310
311 pub fn derives_mut(&mut self) -> &mut Vec<syn::Path> {
313 &mut self.derives
314 }
315}
316
317#[derive(Clone)]
321pub struct ContractBindings {
322 pub name: String,
324
325 pub tokens: TokenStream,
327
328 pub format: bool,
330}
331
332impl ToTokens for ContractBindings {
333 fn into_token_stream(self) -> TokenStream {
334 self.tokens
335 }
336
337 fn to_tokens(&self, tokens: &mut TokenStream) {
338 tokens.extend(std::iter::once(self.tokens.clone()))
339 }
340
341 fn to_token_stream(&self) -> TokenStream {
342 self.tokens.clone()
343 }
344}
345
346impl fmt::Display for ContractBindings {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 if self.format {
349 let syntax_tree = syn::parse2::<syn::File>(self.tokens.clone()).unwrap();
350 let s = prettyplease::unparse(&syntax_tree);
351 f.write_str(&s)
352 } else {
353 fmt::Display::fmt(&self.tokens, f)
354 }
355 }
356}
357
358impl fmt::Debug for ContractBindings {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 f.debug_struct("ContractBindings")
361 .field("name", &self.name)
362 .field("format", &self.format)
363 .finish()
364 }
365}
366
367impl ContractBindings {
368 pub fn to_vec(&self) -> Vec<u8> {
370 self.to_string().into_bytes()
371 }
372
373 pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
375 let tokens = self.to_string();
376 w.write_all(tokens.as_bytes())
377 }
378
379 pub fn write_fmt(&self, w: &mut impl fmt::Write) -> fmt::Result {
381 let tokens = self.to_string();
382 w.write_str(&tokens)
383 }
384
385 pub fn write_to_file(&self, file: impl AsRef<Path>) -> io::Result<()> {
387 fs::write(file.as_ref(), self.to_string())
388 }
389
390 pub fn write_module_in_dir(&self, dir: impl AsRef<Path>) -> io::Result<()> {
392 let file = dir.as_ref().join(self.module_filename());
393 self.write_to_file(file)
394 }
395
396 #[deprecated = "Use ::quote::ToTokens::into_token_stream instead"]
397 #[doc(hidden)]
398 pub fn into_tokens(self) -> TokenStream {
399 self.tokens
400 }
401
402 pub fn module_name(&self) -> String {
404 util::safe_module_name(&self.name)
405 }
406
407 pub fn module_filename(&self) -> String {
409 let mut name = self.module_name();
410 name.push_str(".rs");
411 name
412 }
413}
414
415fn in_build_script() -> bool {
417 std::env::var("TARGET").is_ok()
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn can_generate_structs() {
426 let greeter = include_str!("../../tests/solidity-contracts/greeter_with_struct.json");
427 let abigen = Abigen::new("Greeter", greeter).unwrap();
428 let gen = abigen.generate().unwrap();
429 let out = gen.tokens.to_string();
430 assert!(out.contains("pub struct Stuff"));
431 }
432
433 #[test]
434 fn can_generate_constructor_params() {
435 let contract = include_str!("../../tests/solidity-contracts/StructConstructor.json");
436 let abigen = Abigen::new("MyContract", contract).unwrap();
437 let gen = abigen.generate().unwrap();
438 let out = gen.tokens.to_string();
439 assert!(out.contains("pub struct ConstructorParams"));
440 }
441
442 #[test]
444 fn parse_empty_abigen() {
445 let path = Path::new(env!("CARGO_MANIFEST_DIR"))
446 .join("../tests/solidity-contracts/draft-IERCPermit.json");
447 let abigen = Abigen::from_file(path).unwrap();
448 assert_eq!(abigen.name().to_string(), "draft_IERCPermit");
449 }
450}