use cfg_if::cfg_if;
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::{quote};
use syn::spanned::Spanned;
use syn::{Result};
macro_rules! generate_panic {
($spanned:expr, $($msg:tt)*) => {
return Err(::syn::Error::new(
<_ as ::syn::spanned::Spanned>::span(&$spanned),
format_args!($($msg)*)
))
}
}
pub(crate) fn generate_cross_call(attr: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
let attr = TokenStream2::from(attr);
let tokens: Vec<TokenTree> = attr.clone().into_iter().collect();
if tokens.len() < 1 {
generate_panic!(attr.span(), "cross argument is not enough");
}
let mut is_cns: bool = false;
let addr_token = match &tokens[0] {
TokenTree::Literal(l) => {
let s = l.to_string().trim_matches('"').to_lowercase();
let addr_hex = s.trim_start_matches("0x");
if addr_hex.len() != 40 {
generate_panic!(attr.span(), "contract address length error: {}", addr_hex.len())
}
let address = hex::decode(addr_hex).expect("contract address error");
quote! {
fvm_std::types::Address::new([#(#address), *])
}
}
TokenTree::Ident(l) if l.to_string() == "cns_name" => {
match &tokens[2] {
TokenTree::Literal(l) => {
is_cns = true;
quote! {
#l.to_string().trim_matches('"')
}
}
_ => generate_panic!(attr.span(), "the argument must be string of CNS")
}
}
_ => generate_panic!(attr.span(), "the first token must be string of address or ident of `cns_name`")
};
let it = syn::parse2::<syn::ItemTrait>(item)
.map_err(|err| syn::Error::new(attr.span(), err))?;
let name = it.ident;
let methods = it.items.iter()
.filter_map(|item| {
match item {
syn::TraitItem::Method(m) => Some(m),
_ => None
}
})
.map(|item| {
let m_name = &item.sig.ident;
let m_name_str = format!("{}", m_name);
let m_input = &item.sig.inputs
.iter()
.filter_map(|input| { match input {
syn::FnArg::Typed(tp) => Some(tp),
_ => None
}
})
.collect::<Vec<_>>();
let m_output = &item.sig.output;
let input_encode = {
let params = m_input.iter()
.map(|input| {
let v = &input.pat;
quote! {input.append(&mut scale::Encode::encode(&#v));}
})
.collect::<Vec<_>>();
quote! {
let mut input = scale::Encode::encode(&#m_name_str);
#(#params)*
}
};
let output_decode = match m_output {
syn::ReturnType::Type(_, tp) => quote! {
<#tp as scale::Decode>::decode(&mut &res[..]).expect("func return type error")
},
_ => quote! {}
};
let mut cross_func = quote! {call_contract(&#addr_token, input.as_slice())};
if is_cns {
cross_func = quote! {cns_call_contract(#addr_token.to_string().trim_matches('"').as_bytes(), input.as_slice())}
}
cfg_if! {
if #[cfg(feature = "advance")] {
quote! {
pub fn #m_name(#(#m_input), *) #m_output {
#input_encode
let mut res = fvm_std::advance::#cross_func;
#output_decode
}
}
} else {
quote! {
pub fn #m_name(#(#m_input), *) #m_output {
#input_encode
let res = fvm_std::normal::#cross_func;
#output_decode
}
}
}
}
})
.collect::<Vec<_>>();
Ok(quote! {
struct #name {}
impl #name {
#(#methods)*
}
})
}