1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use proc_macro2::TokenStream as TokenStream2;
4use quote::quote;
5use syn::ItemImpl;
6use syn::Signature;
7use syn::Visibility;
8
9fn input_struct_deser(sig: &Signature) -> TokenStream2 {
10 let mut fields = TokenStream2::new();
11 for arg in &sig.inputs {
12 match arg {
13 syn::FnArg::Receiver(_) => todo!(),
14 syn::FnArg::Typed(typed) => {
15 let ident = &typed.pat;
16 let ty = &typed.ty;
17 fields.extend(quote! {
18 #ident: #ty,
19 });
20 }
21 }
22 }
23 quote! {
24 #[derive(serde::Deserialize)]
25 struct Input {
26 #fields
27 }
28 }
29}
30
31#[proc_macro_attribute]
50pub fn contract(_attr: TokenStream, item: TokenStream) -> TokenStream {
51 if let Ok(input) = syn::parse::<ItemImpl>(item) {
52 let struct_type = &input.self_ty;
53 let mut generated_code = TokenStream2::new();
54 for item in &input.items {
55 match item {
56 syn::ImplItem::Method(method) => {
57 if !matches!(method.vis, Visibility::Public(_)) {
58 continue;
59 }
60 let ident = &method.sig.ident;
61 let arg_struct = input_struct_deser(&method.sig);
62 let mut arg_list = TokenStream2::new();
63 for arg in &method.sig.inputs {
64 match arg {
65 syn::FnArg::Receiver(_) => todo!(),
66 syn::FnArg::Typed(typed) => {
67 let ident = &typed.pat;
68 arg_list.extend(quote! {
69 #ident,
70 });
71 }
72 }
73 }
74 let ouput_serialization = match method.sig.output {
75 syn::ReturnType::Default => quote! {},
76 syn::ReturnType::Type(_, _) => quote! {
77 let result = serde_json::to_vec(&result).expect("Failed to serialize the return value using JSON.");
78 l1x_sdk::output(&result);
79 },
80 };
81 generated_code.extend(quote! {
82 #[cfg(target_arch = "wasm32")]
83 #[no_mangle]
84 pub extern "C" fn #ident() {
85 let REENTRANCY_GUARD_KEY: &[u8] = b"__REENTRANCY_GUARD__";
86 let REENTRANCY_GUARD: &[u8] = b"";
87 l1x_sdk::setup_panic_hook();
88 let write_perm = l1x_sdk::storage_write_perm();
89 if write_perm {
90 if l1x_sdk::storage_write(&REENTRANCY_GUARD_KEY, REENTRANCY_GUARD) {
91 panic!("Found a cross-contract call loop");
92 }
93 } else {
94 if l1x_sdk::storage_read(&REENTRANCY_GUARD_KEY).is_some() {
95 panic!("Found a cross-contract call loop");
96 }
97 }
98 #arg_struct
99 let Input {
100 #arg_list
101 } = serde_json::from_slice(
102 &l1x_sdk::input().expect("Expected input since method has arguments.")
103 ).expect("Failed to deserialize input from JSON.");
104 let result = #struct_type::#ident(#arg_list);
105 #ouput_serialization
106 if write_perm {
107 l1x_sdk::storage_remove(&REENTRANCY_GUARD_KEY);
108 }
109 }
110 })
111 }
112 _ => {
113 return TokenStream::from(
114 syn::Error::new(
115 Span::call_site(),
116 "#[contract] only supports methods for now.",
117 )
118 .to_compile_error(),
119 )
120 }
121 }
122 }
123
124 TokenStream::from(quote! {
125 #input
126 #generated_code
127 })
128 } else {
129 TokenStream::from(
130 syn::Error::new(
131 Span::call_site(),
132 "#[contract] can only be used on impl sections.",
133 )
134 .to_compile_error(),
135 )
136 }
137}