tool_derive/
lib.rs

1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4
5use proc_macro::TokenStream;
6use quote::quote;
7use std::collections::HashSet;
8use syn::DeriveInput;
9
10#[proc_macro_derive(ContractExt, attributes(contract))]
11pub fn contract(input: TokenStream) -> TokenStream {
12    // Parse the string representation
13    let input: DeriveInput = syn::parse(input).unwrap();
14    let mut trait_name = "".to_string();
15    let mut address = "".to_string();
16    let mut path = "".to_string();
17    for meta_items in input.attrs.iter().filter_map(get_contract_meta_items) {
18        for meta_item in meta_items {
19            match meta_item {
20                // parse #[contract(name = "foo")]
21                syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("name") => {
22                    if let syn::Lit::Str(ref lit) = m.lit {
23                        trait_name = lit.value();
24                        // println!("{}", lit.value());
25                    }
26                }
27                // parse #[contract(addr = "foo")]
28                syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("addr") => {
29                    if let syn::Lit::Str(ref lit) = m.lit {
30                        address = lit.value();
31                        // println!("{}", lit.value());
32                    }
33                }
34                // parse #[contract(path = "foo")]
35                syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("path") => {
36                    if let syn::Lit::Str(ref lit) = m.lit {
37                        path = lit.value();
38                        // println!("{}", lit.value());
39                    }
40                }
41                _ => {}
42            }
43        }
44    }
45    if path.is_empty() {
46        panic!("path must set");
47    }
48    if address.is_empty() {
49        panic!("contract address must set");
50    }
51    if trait_name.is_empty() {
52        panic!("trait name must set");
53    }
54    // struct name
55    let name = input.ident;
56    // parse str to LitStr
57    let path = syn::LitStr::new(&path, proc_macro2::Span::call_site());
58    // parse str to Ident
59    let trait_name = syn::Ident::new(&trait_name, proc_macro2::Span::call_site());
60    let address = syn::LitStr::new(&address, proc_macro2::Span::call_site());
61
62    let output = if let syn::Data::Struct(data) = input.data {
63        let mut field = vec![
64            Some(syn::Ident::new("client", proc_macro2::Span::call_site())),
65            Some(syn::Ident::new("address", proc_macro2::Span::call_site())),
66            Some(syn::Ident::new("contract", proc_macro2::Span::call_site())),
67        ]
68        .into_iter()
69        .collect::<HashSet<Option<syn::Ident>>>();
70
71        if let syn::Fields::Named(ref x) = data.fields {
72            if x.named.len() < 3 {
73                panic!("Must have 3 more field");
74            }
75        }
76
77        data.fields.iter().for_each(|i| {
78            field.remove(&i.ident);
79        });
80        if !field.is_empty() {
81            panic!("Contract client must have client/address/contract");
82        }
83        quote!(
84            impl<T> #name<T>
85                where T: ClientExt<JsonRpcResponse, ToolError>
86            {
87                /// Create a Contract Client
88                pub fn new(client: T, address_str: &str, contract_json: &str) -> Self {
89                    let address = Address::from_str(remove_0x(address_str)).unwrap_or_else(|_| Address::default());
90                    let contract = Contract::load(contract_json.as_bytes()).unwrap();
91                    #name {
92                        client,
93                        address,
94                        contract,
95                    }
96                }
97            }
98            impl<T> ContractCall<JsonRpcResponse, ToolError> for #name<T>
99                where T: ClientExt<JsonRpcResponse, ToolError>
100            {
101                fn prepare_call_args(
102                    &self,
103                    name: &str,
104                    values: &[&str],
105                    to_addr: Option<Address>,
106                ) -> Result<(String, String), ToolError> {
107                    let values = values.iter().map(ToString::to_string).collect::<Vec<_>>();
108                    let code = contract_encode_input(&self.contract, name, values.as_slice(), false)?;
109                    let code = format!("0x{}", code);
110                    let to_address = to_addr.unwrap_or(self.address);
111                    let to_address = format!("{:?}", to_address);
112                    Ok((code, to_address))
113                }
114
115                fn contract_send_tx(
116                    &mut self,
117                    name: &str,
118                    values: &[&str],
119                    quota: Option<u64>,
120                    to_addr: Option<Address>,
121                ) -> Result<JsonRpcResponse, ToolError> {
122                    let (code, to_address) = self.prepare_call_args(name, values, to_addr)?;
123                    let tx_options = TransactionOptions::new()
124                        .set_code(code.as_str())
125                        .set_address(to_address.as_str())
126                        .set_quota(quota);
127                    self.client.send_raw_transaction(
128                        tx_options,
129                    )
130                }
131
132                fn contract_call(
133                    &self,
134                    name: &str,
135                    values: &[&str],
136                    to_addr: Option<Address>,
137                    height: Option<&str>,
138                ) -> Result<JsonRpcResponse, ToolError> {
139                    let (code, to_address) = self.prepare_call_args(name, values, to_addr)?;
140                    self.client.call(
141                        None,
142                        to_address.as_str(),
143                        Some(code.as_str()),
144                        height.unwrap_or_else(|| "latest"),
145                        false,
146                    )
147                }
148            }
149            impl<T> #trait_name<T, JsonRpcResponse, ToolError> for #name<T>
150                 where T: ClientExt<JsonRpcResponse, ToolError>,
151             {
152                    fn create(client: T) -> Self {
153                        static ABI: &str = include_str!(#path);
154                        static ADDRESS: &str = #address;
155                        Self::new(client, ADDRESS, ABI)
156                    }
157            }
158        )
159    } else {
160        panic!("Only impl to struct")
161    };
162
163    // Return the generated impl
164    output.into()
165}
166
167/// Filter contract attribute like #[contract(foo = bar)]
168fn get_contract_meta_items(attr: &syn::Attribute) -> Option<Vec<syn::NestedMeta>> {
169    if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "contract" {
170        match attr.parse_meta() {
171            Ok(syn::Meta::List(ref meta)) => Some(meta.nested.iter().cloned().collect()),
172            _ => {
173                // TODO: produce an error
174                None
175            }
176        }
177    } else {
178        None
179    }
180}