#![recursion_limit = "128"]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use std::collections::HashSet;
use syn::DeriveInput;
#[proc_macro_derive(ContractExt, attributes(contract))]
pub fn contract(input: TokenStream) -> TokenStream {
let input: DeriveInput = syn::parse(input).unwrap();
let mut trait_name = "".to_string();
let mut address = "".to_string();
let mut path = "".to_string();
for meta_items in input.attrs.iter().filter_map(get_contract_meta_items) {
for meta_item in meta_items {
match meta_item {
syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("name") => {
if let syn::Lit::Str(ref lit) = m.lit {
trait_name = lit.value();
}
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("addr") => {
if let syn::Lit::Str(ref lit) = m.lit {
address = lit.value();
}
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ref m)) if m.path.is_ident("path") => {
if let syn::Lit::Str(ref lit) = m.lit {
path = lit.value();
}
}
_ => {}
}
}
}
if path.is_empty() {
panic!("path must set");
}
if address.is_empty() {
panic!("contract address must set");
}
if trait_name.is_empty() {
panic!("trait name must set");
}
let name = input.ident;
let path = syn::LitStr::new(&path, proc_macro2::Span::call_site());
let trait_name = syn::Ident::new(&trait_name, proc_macro2::Span::call_site());
let address = syn::LitStr::new(&address, proc_macro2::Span::call_site());
let output = if let syn::Data::Struct(data) = input.data {
let mut field = vec![
Some(syn::Ident::new("client", proc_macro2::Span::call_site())),
Some(syn::Ident::new("address", proc_macro2::Span::call_site())),
Some(syn::Ident::new("contract", proc_macro2::Span::call_site())),
]
.into_iter()
.collect::<HashSet<Option<syn::Ident>>>();
if let syn::Fields::Named(ref x) = data.fields {
if x.named.len() < 3 {
panic!("Must have 3 more field");
}
}
data.fields.iter().for_each(|i| {
field.remove(&i.ident);
});
if !field.is_empty() {
panic!("Contract client must have client/address/contract");
}
quote!(
impl<T> #name<T>
where T: ClientExt<JsonRpcResponse, ToolError>
{
pub fn new(client: T, address_str: &str, contract_json: &str) -> Self {
let address = Address::from_str(remove_0x(address_str)).unwrap_or_else(|_| Address::default());
let contract = Contract::load(contract_json.as_bytes()).unwrap();
#name {
client,
address,
contract,
}
}
}
impl<T> ContractCall<JsonRpcResponse, ToolError> for #name<T>
where T: ClientExt<JsonRpcResponse, ToolError>
{
fn prepare_call_args(
&self,
name: &str,
values: &[&str],
to_addr: Option<Address>,
) -> Result<(String, String), ToolError> {
let values = values.iter().map(ToString::to_string).collect::<Vec<_>>();
let code = contract_encode_input(&self.contract, name, values.as_slice(), false)?;
let code = format!("0x{}", code);
let to_address = to_addr.unwrap_or(self.address);
let to_address = format!("{:?}", to_address);
Ok((code, to_address))
}
fn contract_send_tx(
&mut self,
name: &str,
values: &[&str],
quota: Option<u64>,
to_addr: Option<Address>,
) -> Result<JsonRpcResponse, ToolError> {
let (code, to_address) = self.prepare_call_args(name, values, to_addr)?;
let tx_options = TransactionOptions::new()
.set_code(code.as_str())
.set_address(to_address.as_str())
.set_quota(quota);
self.client.send_raw_transaction(
tx_options,
)
}
fn contract_call(
&self,
name: &str,
values: &[&str],
to_addr: Option<Address>,
height: Option<&str>,
) -> Result<JsonRpcResponse, ToolError> {
let (code, to_address) = self.prepare_call_args(name, values, to_addr)?;
self.client.call(
None,
to_address.as_str(),
Some(code.as_str()),
height.unwrap_or_else(|| "latest"),
false,
)
}
}
impl<T> #trait_name<T, JsonRpcResponse, ToolError> for #name<T>
where T: ClientExt<JsonRpcResponse, ToolError>,
{
fn create(client: T) -> Self {
static ABI: &str = include_str!(#path);
static ADDRESS: &str = #address;
Self::new(client, ADDRESS, ABI)
}
}
)
} else {
panic!("Only impl to struct")
};
output.into()
}
fn get_contract_meta_items(attr: &syn::Attribute) -> Option<Vec<syn::NestedMeta>> {
if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "contract" {
match attr.parse_meta() {
Ok(syn::Meta::List(ref meta)) => Some(meta.nested.iter().cloned().collect()),
_ => {
None
}
}
} else {
None
}
}