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 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 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 }
26 }
27 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 }
33 }
34 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 }
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 let name = input.ident;
56 let path = syn::LitStr::new(&path, proc_macro2::Span::call_site());
58 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 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 output.into()
165}
166
167fn 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 None
175 }
176 }
177 } else {
178 None
179 }
180}