use super::config::Config;
use super::contract::{
ConfiguredContract, ContractSrc, Method, effective_interface_name,
method_anchor_tokens, method_arg_spec_tokens, method_return_spec_token,
throws_anchor_tokens, throws_spec_tokens,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Result;
pub fn generate_impl_raw_spec_fragments(
item: &syn::ItemImpl,
config: &Config,
) -> Result<TokenStream> {
let self_ty = &item.self_ty;
if let Some((trait_path, _)) = &item.trait_ {
let declared_by =
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_DECLARED_BY };
let interface_fragment = generate_interface_fragment_tokens(
&entrypoints_cfg(),
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_NAME },
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_FULL_VERSION },
declared_by.clone(),
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_THROWS },
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_EMITS },
);
let method_fragments =
generate_trait_method_fragments_tokens(self_ty, trait_path);
let event_fragments =
generate_trait_event_fragments_tokens(self_ty, trait_path);
let interface_anchor = generate_interface_anchor_tokens(
entrypoints_cfg(),
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_NAME },
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_FULL_VERSION },
declared_by,
);
let anchor_roots = vec![
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_THROW_ANCHOR_ROOT },
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_EVENT_ANCHOR_ROOT },
quote! { <#self_ty as #trait_path>::__THRUPUT_IFACE_METHOD_ANCHOR_ROOT },
];
let anchor_root =
emit_root_anchor_tokens(&entrypoints_cfg(), &anchor_roots)
.unwrap_or_else(|| quote! {});
return Ok(quote! {
#interface_fragment
#method_fragments
#event_fragments
#interface_anchor
#anchor_root
});
}
let contract = ConfiguredContract::new(config.clone(), item);
let methods = contract.methods_sorted().into_iter().collect::<Vec<_>>();
let iface_name = effective_interface_name(&contract);
let iface_vers = contract.defaulted_interface_vers();
let throws = throws_spec_tokens(&contract)?;
let declared_by = crate::common::spec_id::crate_id_tokens(
&crate::common::contract::sdk_crate_path(),
Some(&iface_vers),
);
let emits_names = config_emits_name_tokens(config);
let interface_fragment = generate_interface_fragment_tokens(
&entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
quote! { &[#(#throws),*] },
quote! { &[#(#emits_names),*] },
);
let event_fragments =
generate_config_event_fragments_tokens(&entrypoints_cfg(), config);
let interface_anchor = generate_interface_anchor_tokens(
entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
);
let mut anchor_roots = throws_anchor_tokens(&contract)?;
anchor_roots.extend(config_event_anchor_tokens(config));
let method_fragments = methods
.iter()
.map(|method| {
generate_method_fragment_tokens(
&entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
method,
)
})
.collect::<Result<Vec<_>>>()?;
for method in &methods {
anchor_roots.extend(method_anchor_tokens(method)?);
}
let anchor_root =
emit_root_anchor_tokens(&entrypoints_cfg(), &anchor_roots)
.unwrap_or_else(|| quote! {});
Ok(quote! {
#interface_fragment
#(#method_fragments)*
#event_fragments
#interface_anchor
#anchor_root
})
}
pub fn generate_free_function_raw_spec_fragments(
item: &syn::ItemFn,
config: &Config,
) -> Result<TokenStream> {
let contract = ConfiguredContract::new(config.clone(), item);
let iface_name = effective_interface_name(&contract);
let iface_vers = contract.defaulted_interface_vers();
let throws = throws_spec_tokens(&contract)?;
let declared_by = crate::common::spec_id::crate_id_tokens(
&crate::common::contract::sdk_crate_path(),
Some(&iface_vers),
);
let emits_names = config_emits_name_tokens(config);
let interface_fragment = generate_interface_fragment_tokens(
&entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
quote! { &[#(#throws),*] },
quote! { &[#(#emits_names),*] },
);
let method_fragment = generate_method_fragment_tokens(
&entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
item,
)?;
let event_fragments =
generate_config_event_fragments_tokens(&entrypoints_cfg(), config);
let interface_anchor = generate_interface_anchor_tokens(
entrypoints_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
declared_by.clone(),
);
let mut anchor_roots = throws_anchor_tokens(&contract)?;
anchor_roots.extend(method_anchor_tokens(item)?);
anchor_roots.extend(config_event_anchor_tokens(config));
let anchor_root =
emit_root_anchor_tokens(&entrypoints_cfg(), &anchor_roots)
.unwrap_or_else(|| quote! {});
Ok(quote! {
#interface_fragment
#method_fragment
#event_fragments
#interface_anchor
#anchor_root
})
}
pub fn generate_trait_raw_spec_fragments(
item: &syn::ItemTrait,
config: &Config,
) -> Result<TokenStream> {
let contract = ConfiguredContract::new(config.clone(), item);
let methods = contract.methods_sorted().into_iter().collect::<Vec<_>>();
let iface_name = effective_interface_name(&contract);
let iface_vers = contract.defaulted_interface_vers();
let throws = throws_spec_tokens(&contract)?;
let anchor_const =
quote::format_ident!("__THRUPUT_IFACE_ANCHOR_{}", item.ident);
let emits_names = config_emits_name_tokens(config);
let interface_fragment = generate_interface_fragment_tokens(
&target_cfg(),
quote! { #iface_name },
quote! { #iface_vers },
quote! { #anchor_const.declared_by },
quote! { &[#(#throws),*] },
quote! { &[#(#emits_names),*] },
);
let event_fragments =
generate_config_event_fragments_tokens(&target_cfg(), config);
let mut anchor_roots = throws_anchor_tokens(&contract)?;
anchor_roots.extend(config_event_anchor_tokens(config));
for method in &methods {
anchor_roots.extend(method_anchor_tokens(method)?);
}
let anchor_root = emit_root_anchor_tokens(&target_cfg(), &anchor_roots)
.unwrap_or_else(|| quote! {});
Ok(quote! {
#interface_fragment
#event_fragments
#anchor_root
})
}
fn generate_trait_method_fragments_tokens(
self_ty: &syn::Type,
trait_path: &syn::Path,
) -> TokenStream {
quote! {
#[cfg(all(target_arch = "riscv32", feature = "entrypoints"))]
const _: () = {
const METHODS: &[::thruput_sdk::__contract_spec::ContractSpecMethodView<'static>] =
<#self_ty as #trait_path>::__THRUPUT_IFACE_METHODS;
const NAME: &str = <#self_ty as #trait_path>::__THRUPUT_IFACE_NAME;
const VERS: &str =
<#self_ty as #trait_path>::__THRUPUT_IFACE_FULL_VERSION;
const DECLARED_BY:
::thruput_sdk::__contract_spec::ContractSpecIdView<'static> =
<#self_ty as #trait_path>::__THRUPUT_IFACE_DECLARED_BY;
const RAW_LEN: usize = {
let mut total = 0usize;
let mut i = 0;
while i < METHODS.len() {
total += ::thruput_sdk::__contract_spec::SpecFragmentView::Method(
::thruput_sdk::__contract_spec::SpecMethodFragmentView {
interface_name: NAME,
interface_version: VERS,
declared_by: DECLARED_BY,
method: METHODS[i],
},
)
.const_txs_size();
i += 1;
}
total
};
#[cfg_attr(
target_arch = "riscv32",
unsafe(link_section = "thruputspecrawv5")
)]
#[cfg_attr(target_arch = "riscv32", used)]
pub static RAW_SPEC_METHOD_FRAGMENTS: [u8; RAW_LEN] = {
let mut w =
::thruput_sdk::__xdrs::txs::ConstWriter::<RAW_LEN>::new();
let mut i = 0;
while i < METHODS.len() {
::thruput_sdk::__contract_spec::SpecFragmentView::Method(
::thruput_sdk::__contract_spec::SpecMethodFragmentView {
interface_name: NAME,
interface_version: VERS,
declared_by: DECLARED_BY,
method: METHODS[i],
},
)
.const_write_txs(&mut w);
i += 1;
}
w.into_bytes()
};
};
}
}
fn generate_trait_event_fragments_tokens(
self_ty: &syn::Type,
trait_path: &syn::Path,
) -> TokenStream {
quote! {
#[cfg(all(target_arch = "riscv32", feature = "entrypoints"))]
const _: () = {
const EVENTS: &[::thruput_sdk::__contract_spec::ContractEventDefView<'static>] =
<#self_ty as #trait_path>::__THRUPUT_IFACE_EVENTS;
const RAW_LEN: usize = {
let mut total = 0usize;
let mut i = 0;
while i < EVENTS.len() {
total += ::thruput_sdk::__contract_spec::SpecFragmentView::Event(
EVENTS[i],
)
.const_txs_size();
i += 1;
}
total
};
#[cfg_attr(
target_arch = "riscv32",
unsafe(link_section = "thruputspecrawv5")
)]
#[cfg_attr(target_arch = "riscv32", used)]
pub static RAW_SPEC_EVENT_FRAGMENTS: [u8; RAW_LEN] = {
let mut w =
::thruput_sdk::__xdrs::txs::ConstWriter::<RAW_LEN>::new();
let mut i = 0;
while i < EVENTS.len() {
::thruput_sdk::__contract_spec::SpecFragmentView::Event(EVENTS[i])
.const_write_txs(&mut w);
i += 1;
}
w.into_bytes()
};
};
}
}
fn target_cfg() -> TokenStream {
quote! { target_arch = "riscv32" }
}
fn entrypoints_cfg() -> TokenStream {
quote! {
all(target_arch = "riscv32", feature = "entrypoints")
}
}
pub fn generate_interface_anchor_for_trait(path: &syn::Path) -> TokenStream {
let trait_const = |prefix: &str| {
let mut const_path = path.clone();
if let Some(last) = const_path.segments.last_mut() {
last.ident = quote::format_ident!("{prefix}{}", last.ident);
last.arguments = syn::PathArguments::None;
}
const_path
};
let anchor_path = trait_const("__THRUPUT_IFACE_ANCHOR_");
let methods_path = trait_const("__THRUPUT_IFACE_METHODS_");
let anchor = emit_raw_fragment_tokens_with_cfg(
target_cfg(),
quote! {
::thruput_sdk::__contract_spec::SpecFragmentView::InterfaceAnchor(#anchor_path)
},
);
let methods =
generate_carrier_method_fragments_tokens(&anchor_path, &methods_path);
quote! {
#anchor
#methods
}
}
fn generate_carrier_method_fragments_tokens(
anchor_path: &syn::Path,
methods_path: &syn::Path,
) -> TokenStream {
let cfg = target_cfg();
quote! {
#[cfg(#cfg)]
const _: () = {
const METHODS: &[::thruput_sdk::__contract_spec::ContractSpecMethodView<'static>] =
#methods_path;
const NAME: &str = #anchor_path.interface_name;
const VERS: &str = #anchor_path.interface_version;
const DECLARED_BY:
::thruput_sdk::__contract_spec::ContractSpecIdView<'static> =
#anchor_path.declared_by;
const RAW_LEN: usize = {
let mut total = 0usize;
let mut i = 0;
while i < METHODS.len() {
total += ::thruput_sdk::__contract_spec::SpecFragmentView::Method(
::thruput_sdk::__contract_spec::SpecMethodFragmentView {
interface_name: NAME,
interface_version: VERS,
declared_by: DECLARED_BY,
method: METHODS[i],
},
)
.const_txs_size();
i += 1;
}
total
};
#[cfg_attr(
target_arch = "riscv32",
unsafe(link_section = "thruputspecrawv5")
)]
#[cfg_attr(target_arch = "riscv32", used)]
pub static RAW_SPEC_CARRIER_METHOD_FRAGMENTS: [u8; RAW_LEN] = {
let mut w = ::thruput_sdk::__xdrs::txs::ConstWriter::<RAW_LEN>::new();
let mut i = 0;
while i < METHODS.len() {
::thruput_sdk::__contract_spec::SpecFragmentView::Method(
::thruput_sdk::__contract_spec::SpecMethodFragmentView {
interface_name: NAME,
interface_version: VERS,
declared_by: DECLARED_BY,
method: METHODS[i],
},
)
.const_write_txs(&mut w);
i += 1;
}
w.into_bytes()
};
};
}
}
fn generate_interface_anchor_tokens(
cfg: TokenStream,
interface_name: TokenStream,
interface_version: TokenStream,
declared_by: TokenStream,
) -> TokenStream {
emit_raw_fragment_tokens_with_cfg(
cfg,
quote! {
::thruput_sdk::__contract_spec::SpecFragmentView::InterfaceAnchor(
::thruput_sdk::__contract_spec::SpecInterfaceAnchorView {
interface_name: #interface_name,
interface_version: #interface_version,
declared_by: #declared_by,
}
)
},
)
}
fn generate_config_event_fragments_tokens(
cfg: &TokenStream,
config: &Config,
) -> TokenStream {
let fragments = config.emits_list().into_iter().map(|path| {
emit_raw_fragment_tokens_with_cfg(cfg.clone(), quote! {
::thruput_sdk::__contract_spec::SpecFragmentView::Event(
<#path as ::thruput_sdk::__contract_spec::ContractEventSpec>::EVENT_DEF
)
})
});
quote! { #(#fragments)* }
}
fn config_event_anchor_tokens(config: &Config) -> Vec<TokenStream> {
config
.emits_list()
.into_iter()
.map(|path| {
quote! {
<#path as ::thruput_sdk::__contract_spec::ContractEventSpec>::__THRUPUT_SPEC_ANCHOR_ROOT
}
})
.collect()
}
fn config_emits_name_tokens(config: &Config) -> Vec<TokenStream> {
config
.emits_list()
.into_iter()
.map(|path| {
quote! {
<#path as ::thruput_sdk::__contract_spec::ContractEventSpec>::EVENT_DEF.name
}
})
.collect()
}
fn generate_interface_fragment_tokens(
cfg: &TokenStream,
interface_name: TokenStream,
interface_version: TokenStream,
declared_by: TokenStream,
throws: TokenStream,
emits: TokenStream,
) -> TokenStream {
emit_raw_fragment_tokens_with_cfg(
cfg.clone(),
quote! {
::thruput_sdk::__contract_spec::SpecFragmentView::Interface(
::thruput_sdk::__contract_spec::SpecInterfaceFragmentView {
interface_name: #interface_name,
interface_version: #interface_version,
declared_by: #declared_by,
throws: #throws,
emits: #emits,
}
)
},
)
}
fn generate_method_fragment_tokens(
cfg: &TokenStream,
interface_name: TokenStream,
interface_version: TokenStream,
declared_by: TokenStream,
method: &impl Method,
) -> Result<TokenStream> {
let method_name = method.sig().ident.to_string();
let args = method_arg_spec_tokens(method)?;
let return_type = method_return_spec_token(method)?;
let effect = super::contract::method_effect_tokens(method)?;
Ok(emit_raw_fragment_tokens_with_cfg(
cfg.clone(),
quote! {{
const METHOD_ARGS: &[::thruput_sdk::__contract_spec::ContractFieldView<'static>] =
&[#(#args),*];
::thruput_sdk::__contract_spec::SpecFragmentView::Method(
::thruput_sdk::__contract_spec::SpecMethodFragmentView {
interface_name: #interface_name,
interface_version: #interface_version,
declared_by: #declared_by,
method: ::thruput_sdk::__contract_spec::ContractSpecMethodView {
name: #method_name,
args: METHOD_ARGS,
return_type: #return_type,
effect: #effect,
},
}
)
}},
))
}
pub fn emit_raw_fragment_tokens_with_cfg(
cfg_guard: TokenStream,
fragment: TokenStream,
) -> TokenStream {
quote! {
#[cfg(#cfg_guard)]
const _: () = {
const FRAGMENT: ::thruput_sdk::__contract_spec::SpecFragmentView<'static> =
#fragment;
const RAW_LEN: usize = FRAGMENT.const_txs_size();
const RAW_FRAGMENT: [u8; RAW_LEN] = {
let mut w = ::thruput_sdk::__xdrs::txs::ConstWriter::<RAW_LEN>::new();
FRAGMENT.const_write_txs(&mut w);
w.into_bytes()
};
#[cfg_attr(
target_arch = "riscv32",
unsafe(link_section = "thruputspecrawv5")
)]
#[cfg_attr(target_arch = "riscv32", used)]
pub static RAW_SPEC_FRAGMENT: [u8; RAW_LEN] = RAW_FRAGMENT;
};
}
}
fn emit_root_anchor_tokens(
cfg: &TokenStream,
anchor_roots: &[TokenStream],
) -> Option<TokenStream> {
if anchor_roots.is_empty() {
return None;
}
let count = anchor_roots.len();
Some(quote! {
#[cfg(#cfg)]
const _: () = {
#[cfg_attr(
target_arch = "riscv32",
unsafe(link_section = "thruputspecanchorsv0")
)]
#[used]
static ROOT_SPEC_ANCHORS: [fn() -> usize; #count] = [
#(#anchor_roots),*
];
};
})
}
#[cfg(test)]
mod tests {
use super::*;
use darling::FromMeta;
use darling::ast::NestedMeta;
fn parse_config(attrs: &str) -> Config {
let tokens: proc_macro2::TokenStream = attrs.parse().unwrap();
let meta_list = NestedMeta::parse_meta_list(tokens).unwrap();
Config::from_list(&meta_list).unwrap()
}
#[test]
fn trait_impl_fragments_include_interface_and_method_records() {
let item: syn::ItemImpl = syn::parse_quote! {
impl named_interface_callee::NamedInterfaceCallee for Demo {
fn add_one(x: u32) -> u32 { x + 1 }
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("thruputspecrawv5"));
assert!(tokens.contains("target_arch = \"riscv32\""));
assert!(tokens.contains("used)] pub static RAW_SPEC_FRAGMENT"));
assert!(tokens.contains("SpecInterfaceFragmentView"));
assert!(tokens.contains("SpecMethodFragmentView"));
assert!(tokens.contains("__THRUPUT_IFACE_NAME"));
assert!(tokens.contains("__THRUPUT_IFACE_FULL_VERSION"));
assert!(tokens.contains("__THRUPUT_IFACE_THROWS"));
assert!(tokens.contains("const_txs_size"));
assert!(tokens.contains("ConstWriter"));
assert!(tokens.contains("__THRUPUT_IFACE_EMITS"), "{tokens}");
}
#[test]
fn free_function_fragments_include_method_record() {
let item: syn::ItemFn = syn::parse_quote! {
fn transfer(to: &thruput_sdk::ContractAddress, amount: u32) -> u32 { amount }
};
let config = parse_config(r#"interface = "ERC20", version = "0.1.0""#);
let tokens = generate_free_function_raw_spec_fragments(&item, &config)
.unwrap()
.to_string();
assert!(tokens.contains("thruputspecrawv5"));
assert!(tokens.contains("target_arch = \"riscv32\""));
assert!(tokens.contains("used)] pub static RAW_SPEC_FRAGMENT"));
assert!(tokens.contains("SpecInterfaceFragmentView"));
assert!(tokens.contains("SpecMethodFragmentView"));
assert!(tokens.contains("const_txs_size"));
assert!(tokens.contains("ConstWriter"));
assert!(tokens.contains("ERC20"));
assert!(tokens.contains("0.1.0"));
assert!(tokens.contains("transfer"));
}
#[test]
fn byte_composite_shapes_lower_without_trait_specialization() {
let item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
pub fn maybe_bytes(data: Option<&[u8]>) -> Option<Vec<u8>> {
data.map(Into::into)
}
pub fn tuple_bytes(data: (Vec<u8>, bool)) -> (Vec<u8>, bool) {
data
}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(
tokens.contains("type_ref :: < Option < & [u8] > >"),
"{tokens}"
);
assert!(
tokens.contains("type_ref :: < Option < Vec < u8 > > >"),
"{tokens}"
);
assert!(
tokens.contains("type_ref :: < (Vec < u8 > , bool) >"),
"{tokens}"
);
}
#[test]
fn array_shapes_lower_via_trait_resolution() {
let item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
pub fn digest(data: [u8; 32], flags: [bool; 4]) -> [u8; 32] {
data
}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("type_ref :: < [u8 ; 32] >"), "{tokens}");
assert!(tokens.contains("type_ref :: < [bool ; 4] >"), "{tokens}");
}
#[test]
fn alias_byte_collections_emit_trait_calls() {
let item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
pub fn echo_blob(blob: Blob) -> Blob {
blob
}
pub fn echo_hash(hash: Hash) -> Hash {
hash
}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("type_ref :: < Blob >"), "{tokens}");
assert!(tokens.contains("type_ref :: < Hash >"), "{tokens}");
}
#[test]
fn impl_fragments_include_signature_anchor_roots() {
let item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
pub fn echo(payload: Option<Vec<Payload>>) -> Payload {
payload.unwrap().pop().unwrap()
}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("__THRUPUT_SPEC_ANCHOR_ROOT"), "{tokens}");
assert!(tokens.contains("Option < Vec < Payload > >"), "{tokens}");
assert!(tokens.contains("thruputspecanchorsv0"), "{tokens}");
assert!(tokens.contains("target_arch = \"riscv32\""), "{tokens}");
}
#[test]
fn trait_impl_fragments_reference_throw_anchor_root() {
let item: syn::ItemImpl = syn::parse_quote! {
impl imported_error_callee::ImportedErrorCallee for Demo {
fn throw_imported_error() {}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(
tokens.contains("__THRUPUT_IFACE_THROW_ANCHOR_ROOT"),
"{tokens}"
);
}
#[test]
fn trait_fragments_carry_the_interface_but_not_its_methods() {
let item: syn::ItemTrait = syn::parse_quote! {
pub trait ERC20 {
fn transfer(&mut self, receiver: &thruput_sdk::ContractAddress, amount: u128);
fn total_supply(&self) -> u128;
}
};
let tokens = generate_trait_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("thruputspecrawv5"), "{tokens}");
assert!(tokens.contains("target_arch = \"riscv32\""), "{tokens}");
assert!(
tokens.contains("used)] pub static RAW_SPEC_FRAGMENT"),
"{tokens}"
);
assert!(tokens.contains("SpecInterfaceFragmentView"), "{tokens}");
assert!(!tokens.contains("SpecMethodFragmentView"), "{tokens}");
assert!(!tokens.contains("\"total_supply\""), "{tokens}");
assert!(!tokens.contains("\"transfer\""), "{tokens}");
assert!(!tokens.contains("\"receiver\""), "{tokens}");
assert!(tokens.contains("ROOT_SPEC_ANCHORS"), "{tokens}");
}
#[test]
fn carrier_anchor_emits_the_traits_methods() {
let path: syn::Path = syn::parse_quote!(thruput_erc20_if::ERC20);
let tokens = generate_interface_anchor_for_trait(&path).to_string();
assert!(tokens.contains("__THRUPUT_IFACE_ANCHOR_ERC20"), "{tokens}");
assert!(tokens.contains("__THRUPUT_IFACE_METHODS_ERC20"), "{tokens}");
assert!(tokens.contains("SpecMethodFragmentView"), "{tokens}");
assert!(
tokens.contains(
"used)] pub static RAW_SPEC_CARRIER_METHOD_FRAGMENTS"
),
"{tokens}"
);
assert!(
tokens.contains("__THRUPUT_IFACE_ANCHOR_ERC20 . interface_name"),
"{tokens}"
);
}
#[test]
fn trait_fragments_carry_config_throws_and_emits() {
let item: syn::ItemTrait = syn::parse_quote! {
pub trait Token { fn transfer(&mut self); }
};
let config = parse_config(
r#"throws(err::TokenError), emits(events::TransferEvent)"#,
);
let tokens = generate_trait_raw_spec_fragments(&item, &config)
.unwrap()
.to_string();
assert!(tokens.contains("ContractExceptionTypeView"), "{tokens}");
assert!(
tokens.contains("events :: TransferEvent as :: thruput_sdk :: __contract_spec :: ContractEventSpec > :: EVENT_DEF"),
"{tokens}"
);
assert!(
tokens.contains("EVENT_DEF . name"),
"interface fragment must carry emits names: {tokens}"
);
}
#[test]
fn trait_impl_methods_come_from_trait_const_not_impl_signatures() {
let item: syn::ItemImpl = syn::parse_quote! {
impl pair_if::Pair for Demo {
fn initialize(&mut self, _factory: u32) {}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("__THRUPUT_IFACE_METHODS"), "{tokens}");
assert!(tokens.contains("RAW_SPEC_METHOD_FRAGMENTS"), "{tokens}");
assert!(
tokens.contains("__THRUPUT_IFACE_METHOD_ANCHOR_ROOT"),
"{tokens}"
);
assert!(!tokens.contains("_factory"), "{tokens}");
}
}
#[cfg(test)]
mod emits_tests {
use super::*;
use darling::FromMeta;
use darling::ast::NestedMeta;
fn parse_config(attrs: &str) -> Config {
let tokens: proc_macro2::TokenStream = attrs.parse().unwrap();
let meta_list = NestedMeta::parse_meta_list(tokens).unwrap();
Config::from_list(&meta_list).unwrap()
}
#[test]
fn trait_impl_emits_declared_event_fragments_and_anchor_root() {
let item: syn::ItemImpl = syn::parse_quote! {
impl token_if::Token for Demo {
fn transfer(&mut self) {}
}
};
let tokens = generate_impl_raw_spec_fragments(
&item,
&Config::from_list(&[]).unwrap(),
)
.unwrap()
.to_string();
assert!(tokens.contains("__THRUPUT_IFACE_EVENTS"), "{tokens}");
assert!(
tokens.contains("__THRUPUT_IFACE_EVENT_ANCHOR_ROOT"),
"{tokens}"
);
assert!(tokens.contains("RAW_SPEC_EVENT_FRAGMENTS"), "{tokens}");
assert!(tokens.contains("thruputspecrawv5"), "{tokens}");
}
#[test]
fn bare_impl_emits_config_declared_event_fragments() {
let item: syn::ItemImpl = syn::parse_quote! {
impl Demo {
pub fn touch(&mut self) {}
}
};
let config = parse_config(r#"emits(events::TouchEvent)"#);
let tokens = generate_impl_raw_spec_fragments(&item, &config)
.unwrap()
.to_string();
assert!(tokens.contains("SpecFragmentView :: Event"), "{tokens}");
assert!(
tokens.contains(
"events :: TouchEvent as :: thruput_sdk :: __contract_spec :: ContractEventSpec > :: EVENT_DEF"
),
"{tokens}"
);
assert!(
tokens
.contains("ContractEventSpec > :: __THRUPUT_SPEC_ANCHOR_ROOT"),
"{tokens}"
);
}
#[test]
fn free_function_emits_config_declared_event_fragments() {
let item: syn::ItemFn = syn::parse_quote! {
fn ping() {}
};
let config = parse_config(r#"emits(PingEvent)"#);
let tokens = generate_free_function_raw_spec_fragments(&item, &config)
.unwrap()
.to_string();
assert!(tokens.contains("SpecFragmentView :: Event"), "{tokens}");
assert!(
tokens.contains(
"PingEvent as :: thruput_sdk :: __contract_spec :: ContractEventSpec"
),
"{tokens}"
);
}
}