reweb3_macros/
lib.rs

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}