1use std::{env, fs::read_to_string, path::PathBuf};
2
3use ethbind::{
4 binder::{bind, BinderContext},
5 mapping::BinderTypeMapping,
6 rustgen::RustBinder,
7 typedef::{AbiField, HardhatArtifact},
8};
9use heck::ToSnekCase;
10use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12use syn::{parse::Parse, parse_macro_input, Ident, LitStr, Token};
13
14struct Contract {
15 pub contract_name: Option<String>,
16 pub abi_data: String,
17}
18
19impl Parse for Contract {
20 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
21 let contract_name: Option<Ident> = input.parse()?;
22
23 if contract_name.is_some() {
24 input.parse::<Token!(,)>()?;
25 }
26
27 let abi_data: LitStr = input.parse()?;
28
29 Ok(Self {
30 contract_name: contract_name.map(|c| c.to_string()),
31
32 abi_data: abi_data.value(),
33 })
34 }
35}
36
37fn load_json_file(path: &str) -> String {
38 let dir = env::var("CARGO_MANIFEST_DIR").expect("Find CARGO_MANIFEST_DIR");
39
40 let path = PathBuf::from(dir).join(path);
41
42 read_to_string(path.clone()).expect(&format!("Read json file: {:?}", path))
43}
44
45#[proc_macro]
46pub fn contract(item: TokenStream) -> TokenStream {
47 let contract = parse_macro_input!(item as Contract);
48
49 let mapping: BinderTypeMapping =
50 serde_json::from_str(include_str!("./mapping.json")).expect("Parse mapping file.");
51
52 let abi_data = load_json_file(&contract.abi_data);
53
54 if let Some(contract_name) = contract.contract_name {
55 let abi: Vec<AbiField> = serde_json::from_str(&abi_data).expect("Load abi data");
56
57 let cx = BinderContext::new(&contract_name, &abi, None);
58
59 let token_stream = bind(&cx, RustBinder::new(mapping)).expect("Generate rust code");
60
61 let mod_name = format_ident!("{}", contract_name.to_snek_case());
62
63 quote! {
64 pub mod #mod_name {
65 #token_stream
66 }
67 }
68 .into()
69 } else {
70 let abi: HardhatArtifact = serde_json::from_str(&abi_data).expect("Load abi data");
71
72 let cx = BinderContext::new(&abi.contract_name, &abi.abi, Some(&abi.bytecode));
73
74 let mod_name = format_ident!("{}", abi.contract_name.to_snek_case());
75
76 let token_stream = bind(&cx, RustBinder::new(mapping)).expect("Generate rust code");
77
78 quote! {
79 pub mod #mod_name {
80 #token_stream
81 }
82 }
83 .into()
84 }
85}