1pub(crate) mod utils;
2
3extern crate proc_macro;
4
5use darling::{ast, FromAttributes, FromMeta};
6use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::{format_ident, quote, ToTokens};
9use syn::{
10 parse_macro_input, Fields, ItemEnum, ItemFn, ItemImpl, ItemStruct, ItemTrait, ItemUnion,
11 LitStr, Type,
12};
13
14use casper_executor_wasm_common::flags::EntryPointFlags;
15const CASPER_RESERVED_FALLBACK_EXPORT: &str = "__casper_fallback";
16
17#[derive(Debug, FromAttributes)]
18#[darling(attributes(casper))]
19struct MethodAttribute {
20 #[darling(default)]
21 constructor: bool,
22 #[darling(default)]
23 ignore_state: bool,
24 #[darling(default)]
25 revert_on_error: bool,
26 #[darling(default)]
28 private: bool,
29 #[darling(default)]
30 payable: bool,
31 #[darling(default)]
32 fallback: bool,
33}
34
35#[derive(Debug, FromMeta)]
36struct StructMeta {
37 #[darling(default)]
38 path: Option<syn::Path>,
39 #[darling(default)]
41 contract_state: bool,
42 #[darling(default)]
44 message: bool,
45}
46
47#[derive(Debug, FromMeta)]
48struct EnumMeta {
49 #[darling(default)]
50 path: Option<syn::Path>,
51}
52
53#[derive(Debug, FromMeta)]
54struct TraitMeta {
55 path: Option<syn::Path>,
56 export: Option<bool>,
57}
58
59#[derive(Debug, FromMeta)]
60enum ItemFnMeta {
61 Export,
62}
63
64#[derive(Debug, FromMeta)]
65struct ImplTraitForContractMeta {
66 #[darling(default)]
68 path: Option<syn::Path>,
69 #[darling(default)]
71 compile_as_dependency: bool,
72}
73
74fn generate_call_data_return(output: &syn::ReturnType) -> proc_macro2::TokenStream {
75 match output {
76 syn::ReturnType::Default => {
77 quote! { () }
78 }
79 syn::ReturnType::Type(_, ty) => match ty.as_ref() {
80 Type::Never(_) => {
81 quote! { () }
82 }
83 Type::Reference(reference) => {
84 let mut new_ref = reference.clone();
86 new_ref.lifetime = Some(syn::Lifetime::new("'a", Span::call_site()));
87 quote! { <<#new_ref as core::ops::Deref>::Target as casper_contract_sdk::prelude::borrow::ToOwned>::Owned }
88 }
89 _ => {
90 quote! { #ty }
91 }
92 },
93 }
94}
95
96#[proc_macro_attribute]
97pub fn casper(attrs: TokenStream, item: TokenStream) -> TokenStream {
98 let attr_args = match ast::NestedMeta::parse_meta_list(attrs.into()) {
100 Ok(v) => v,
101 Err(e) => {
102 return TokenStream::from(e.to_compile_error());
103 }
104 };
105
106 let has_fallback_selector = false;
107
108 if let Ok(item_struct) = syn::parse::<ItemStruct>(item.clone()) {
109 let struct_meta = StructMeta::from_list(&attr_args).unwrap();
110 if struct_meta.message {
111 process_casper_message_for_struct(&item_struct, struct_meta)
112 } else if struct_meta.contract_state {
113 process_casper_contract_state_for_struct(&item_struct, struct_meta)
115 } else {
116 let partial = generate_casper_state_for_struct(&item_struct, struct_meta);
119 quote! {
120 #partial
121 }
122 .into()
123 }
124 } else if let Ok(item_enum) = syn::parse::<ItemEnum>(item.clone()) {
125 let enum_meta = EnumMeta::from_list(&attr_args).unwrap();
126 let partial = generate_casper_state_for_enum(&item_enum, enum_meta);
127 quote! {
128 #partial
129 }
130 .into()
131 } else if let Ok(item_trait) = syn::parse::<ItemTrait>(item.clone()) {
132 let trait_meta = TraitMeta::from_list(&attr_args).unwrap();
133 casper_trait_definition(item_trait, trait_meta)
134 } else if let Ok(entry_points) = syn::parse::<ItemImpl>(item.clone()) {
135 if let Some((_not, trait_path, _for)) = entry_points.trait_.as_ref() {
136 let impl_meta = ImplTraitForContractMeta::from_list(&attr_args).unwrap();
137 generate_impl_trait_for_contract(&entry_points, trait_path, impl_meta)
138 } else {
139 generate_impl_for_contract(entry_points, has_fallback_selector)
140 }
141 } else if let Ok(func) = syn::parse::<ItemFn>(item.clone()) {
142 let func_meta = ItemFnMeta::from_list(&attr_args).unwrap();
143 match func_meta {
144 ItemFnMeta::Export => generate_export_function(&func),
145 }
146 } else {
147 let err = syn::Error::new(
148 Span::call_site(),
149 "State attribute can only be applied to struct or enum",
150 );
151 TokenStream::from(err.to_compile_error())
152 }
153}
154
155fn process_casper_message_for_struct(
156 item_struct: &ItemStruct,
157 struct_meta: StructMeta,
158) -> TokenStream {
159 let struct_name = &item_struct.ident;
160
161 let crate_path = match &struct_meta.path {
162 Some(path) => quote! { #path },
163 None => quote! { casper_contract_sdk },
164 };
165
166 let borsh_path = {
167 let crate_path_str = match &struct_meta.path {
168 Some(path) => path.to_token_stream().to_string(),
169 None => "casper_contract_sdk".to_string(),
170 };
171 syn::LitStr::new(
172 &format!("{}::serializers::borsh", crate_path_str),
173 Span::call_site(),
174 )
175 };
176
177 let maybe_derive_abi = get_maybe_derive_abi(crate_path.clone());
178
179 let maybe_abi_collectors;
180 let maybe_entrypoint_defs;
181
182 #[cfg(feature = "__abi_generator")]
183 {
184 maybe_abi_collectors = quote! {
185 const _: () = {
186 #[#crate_path::linkme::distributed_slice(#crate_path::abi_generator::ABI_COLLECTORS)]
187 #[linkme(crate = #crate_path::linkme)]
188 static COLLECTOR: fn(&mut #crate_path::abi::Definitions) = |defs| {
189 defs.populate_one::<#struct_name>();
190 };
191 };
192 };
193
194 maybe_entrypoint_defs = quote! {
195 const _: () = {
196 #[#crate_path::linkme::distributed_slice(#crate_path::abi_generator::MESSAGES)]
197 #[linkme(crate = #crate_path::linkme)]
198 static MESSAGE: #crate_path::abi_generator::Message = #crate_path::abi_generator::Message {
199 name: <#struct_name as #crate_path::Message>::TOPIC,
200 decl: concat!(module_path!(), "::", stringify!(#struct_name)),
201 };
202 };
203 }
204 }
205 #[cfg(not(feature = "__abi_generator"))]
206 {
207 maybe_abi_collectors = quote! {};
208 maybe_entrypoint_defs = quote! {};
209 }
210
211 quote! {
212 #[derive(#crate_path::serializers::borsh::BorshSerialize)]
213 #[borsh(crate = #borsh_path)]
214 #maybe_derive_abi
215 #item_struct
216
217 impl #crate_path::Message for #struct_name {
218 const TOPIC: &'static str = stringify!(#struct_name);
219
220 #[inline]
221 fn payload(&self) -> Vec<u8> {
222 #crate_path::serializers::borsh::to_vec(self).unwrap()
223 }
224 }
225
226 #maybe_abi_collectors
227 #maybe_entrypoint_defs
228
229 }
230 .into()
231}
232
233fn generate_export_function(func: &ItemFn) -> TokenStream {
234 let func_name = &func.sig.ident;
235 let mut arg_names = Vec::new();
236 let mut args_attrs = Vec::new();
237 for input in &func.sig.inputs {
238 let (name, ty) = match input {
239 syn::FnArg::Receiver(receiver) => {
240 todo!("{receiver:?}")
241 }
242 syn::FnArg::Typed(typed) => match typed.pat.as_ref() {
243 syn::Pat::Ident(ident) => (&ident.ident, &typed.ty),
244 _ => todo!("export: other typed variant"),
245 },
246 };
247 arg_names.push(name);
248 args_attrs.push(quote! {
249 #name: #ty
250 });
251 }
252 let _ctor_name = format_ident!("{func_name}_ctor");
253
254 let exported_func_name = format_ident!("__casper_export_{func_name}");
255 quote! {
256 #[export_name = stringify!(#func_name)]
257 #[no_mangle]
258 pub extern "C" fn #exported_func_name() {
259 #[cfg(target_arch = "wasm32")]
260 {
261 casper_contract_sdk::set_panic_hook();
262 }
263
264 #func
265
266 #[derive(casper_contract_sdk::serializers::borsh::BorshDeserialize)]
267 #[borsh(crate = "casper_contract_sdk::serializers::borsh")]
268 struct Arguments {
269 #(#args_attrs,)*
270 }
271 let input = casper_contract_sdk::prelude::casper::copy_input();
272 let args: Arguments = casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
273 let _ret = #func_name(#(args.#arg_names,)*);
274 }
275
276 #[cfg(not(target_arch = "wasm32"))]
277 #func
278
279 #[cfg(not(target_arch = "wasm32"))]
280 const _: () = {
281 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::casper::native::ENTRY_POINTS)]
282 #[linkme(crate = casper_contract_sdk::linkme)]
283 pub static EXPORTS: casper_contract_sdk::casper::native::EntryPoint = casper_contract_sdk::casper::native::EntryPoint {
284 kind: casper_contract_sdk::casper::native::EntryPointKind::Function { name: stringify!(#func_name) },
285 fptr: || { #exported_func_name(); },
286 module_path: module_path!(),
287 file: file!(),
288 line: line!(),
289 };
290 };
291 }.into()
292}
293
294fn generate_impl_for_contract(
295 mut entry_points: ItemImpl,
296 _has_fallback_selector: bool,
297) -> TokenStream {
298 #[cfg(feature = "__abi_generator")]
299 let mut populate_definitions_linkme = Vec::new();
300 let impl_trait = match entry_points.trait_.as_ref() {
301 Some((None, path, _for)) => Some(path),
302 Some((Some(_not), _path, _for)) => {
303 panic!("Exclamation mark not supported");
304 }
305 None => None,
306 };
307 let struct_name = match entry_points.self_ty.as_ref() {
308 Type::Path(ref path) => &path.path,
309
310 other => todo!("Unsupported {other:?}"),
311 };
312 let defs = vec![quote! {}]; #[cfg(feature = "__abi_generator")]
314 let mut defs = defs;
315 #[cfg(feature = "__abi_generator")]
316 let mut defs_linkme = Vec::new();
317 let mut names = Vec::new();
318 let mut extern_entry_points = Vec::new();
319 let _abi_generator_entry_points = [quote! {}]; let mut manifest_entry_point_enum_variants = Vec::new();
321 let mut manifest_entry_point_enum_match_name = Vec::new();
322 let mut manifest_entry_point_input_data = Vec::new();
323 let mut extra_code = Vec::new();
324
325 for entry_point in &mut entry_points.items {
326 let mut populate_definitions = Vec::new();
327
328 let method_attribute;
329 let mut flag_value = EntryPointFlags::empty();
330
331 let func = match entry_point {
334 syn::ImplItem::Const(_) => todo!("Const"),
335 syn::ImplItem::Fn(ref mut func) => {
336 let vis = &func.vis;
337 match vis {
338 syn::Visibility::Public(_) => {}
339 syn::Visibility::Inherited => {
340 continue;
342 }
343 syn::Visibility::Restricted(_restricted) => {}
344 }
345
346 let never_returns = match &func.sig.output {
348 syn::ReturnType::Default => false,
349 syn::ReturnType::Type(_, ty) => matches!(ty.as_ref(), Type::Never(_)),
350 };
351
352 method_attribute = MethodAttribute::from_attributes(&func.attrs).unwrap();
353
354 func.attrs.clear();
355
356 let func_name = func.sig.ident.clone();
357 if func_name.to_string().starts_with("__casper_") {
358 return TokenStream::from(
359 syn::Error::new(
360 Span::call_site(),
361 "Function names starting with '__casper_' are reserved",
362 )
363 .to_compile_error(),
364 );
365 }
366
367 let export_name = if method_attribute.fallback {
368 format_ident!("{}", CASPER_RESERVED_FALLBACK_EXPORT)
369 } else {
370 format_ident!("{}", &func_name)
371 };
372
373 names.push(func_name.clone());
374
375 let arg_names_and_types = func
376 .sig
377 .inputs
378 .iter()
379 .filter_map(|arg| match arg {
380 syn::FnArg::Receiver(_) => None,
381 syn::FnArg::Typed(typed) => match typed.pat.as_ref() {
382 syn::Pat::Ident(ident) => Some((&ident.ident, &typed.ty)),
383 _ => todo!(),
384 },
385 })
386 .collect::<Vec<_>>();
387
388 let arg_names: Vec<_> =
389 arg_names_and_types.iter().map(|(name, _ty)| name).collect();
390 let arg_types: Vec<_> = arg_names_and_types.iter().map(|(_name, ty)| ty).collect();
391 let arg_attrs: Vec<_> = arg_names_and_types
392 .iter()
393 .map(|(name, ty)| quote! { #name: #ty })
394 .collect();
395
396 let mut entry_point_requires_state: bool = false;
398
399 let handle_write_state = match func.sig.inputs.first() {
400 Some(syn::FnArg::Receiver(receiver)) if receiver.mutability.is_some() => {
401 entry_point_requires_state = true;
402
403 if !never_returns && receiver.reference.is_some() {
404 Some(quote! {
406 casper_contract_sdk::casper::write_state(&instance).unwrap();
407 })
408 } else {
409 None
413 }
414 }
415 Some(syn::FnArg::Receiver(receiver)) if receiver.mutability.is_none() => {
416 entry_point_requires_state = true;
417
418 None
420 }
421 Some(syn::FnArg::Receiver(receiver)) if receiver.lifetime().is_some() => {
422 panic!("Lifetimes are currently not supported");
423 }
424 Some(_) | None => {
425 if !never_returns && method_attribute.constructor {
426 Some(quote! {
427 casper_contract_sdk::casper::write_state(&_ret).unwrap();
428 })
429 } else {
430 None
431 }
432 }
433 };
434
435 let call_data_return_lifetime = if method_attribute.constructor {
436 quote! {
437 #struct_name
438 }
439 } else {
440 generate_call_data_return(&func.sig.output)
441 };
442 let _func_sig_output = match &func.sig.output {
443 syn::ReturnType::Default => {
444 quote! { () }
445 }
446 syn::ReturnType::Type(_, ty) => {
447 quote! { #ty }
448 }
449 };
450
451 let handle_ret = if never_returns {
452 None
453 } else {
454 match func.sig.output {
455 syn::ReturnType::Default => {
456 None
458 }
459 _ if method_attribute.constructor => {
460 Some(quote! {
465 let _ = flags; })
467 }
468 syn::ReturnType::Type(..) => {
469 Some(quote! {
471 let ret_bytes = casper_contract_sdk::serializers::borsh::to_vec(&_ret).unwrap();
472 casper_contract_sdk::casper::ret(flags, Some(&ret_bytes));
473 })
474 }
475 }
476 };
477
478 assert_eq!(arg_names.len(), arg_types.len());
479
480 let mut prelude = Vec::new();
481
482 prelude.push(quote! {
483 #[derive(casper_contract_sdk::serializers::borsh::BorshDeserialize)]
484 #[borsh(crate = "casper_contract_sdk::serializers::borsh")]
485 struct Arguments {
486 #(#arg_attrs,)*
487 }
488
489
490 let input = casper_contract_sdk::prelude::casper::copy_input();
491 let args: Arguments = casper_contract_sdk::serializers::borsh::from_slice(&input).unwrap();
492 });
493
494 if method_attribute.constructor {
495 prelude.push(quote! {
496 if casper_contract_sdk::casper::has_state().unwrap() {
497 panic!("State of the contract is already present; unable to proceed with the constructor");
498 }
499 });
500 }
501
502 if !method_attribute.payable {
503 let panic_msg = format!(
504 r#"Entry point "{func_name}" is not payable and does not accept tokens"#
505 );
506 prelude.push(quote! {
507 if casper_contract_sdk::casper::transferred_value() != 0 {
508 panic!(#panic_msg);
510 }
511 });
512 }
513
514 let handle_err = if !never_returns && method_attribute.revert_on_error {
515 if let syn::ReturnType::Default = func.sig.output {
516 panic!("Cannot revert on error if there is no return value");
517 }
518
519 quote! {
520 let _ret: &Result<_, _> = &_ret;
521 if _ret.is_err() {
522 flags |= casper_contract_sdk::casper_executor_wasm_common::flags::ReturnFlags::REVERT;
523 }
524
525 }
526 } else {
527 quote! {}
528 };
529
530 let handle_call = if entry_point_requires_state {
531 quote! {
532 let mut instance: #struct_name = casper_contract_sdk::casper::read_state().unwrap();
533 let _ret = instance.#func_name(#(args.#arg_names,)*);
534 }
535 } else if method_attribute.constructor {
536 quote! {
537 let _ret = <#struct_name>::#func_name(#(args.#arg_names,)*);
538 }
539 } else {
540 quote! {
541 let _ret = <#struct_name>::#func_name(#(args.#arg_names,)*);
542 }
543 };
544 if method_attribute.constructor {
545 flag_value |= EntryPointFlags::CONSTRUCTOR;
546 }
547
548 if method_attribute.fallback {
549 flag_value |= EntryPointFlags::FALLBACK;
550 }
551
552 let _bits = flag_value.bits();
553
554 let extern_func_name = format_ident!("__casper_export_{func_name}");
555
556 extern_entry_points.push(quote! {
557
558 #[export_name = stringify!(#export_name)]
559 #vis extern "C" fn #extern_func_name() {
560 #[cfg(target_arch = "wasm32")]
562 {
563 casper_contract_sdk::set_panic_hook();
564 }
565
566 #(#prelude;)*
567
568 let mut flags = casper_contract_sdk::casper_executor_wasm_common::flags::ReturnFlags::empty();
569
570 #handle_call;
571
572 #handle_err;
573
574 #handle_write_state;
575
576 #handle_ret;
577 }
578
579 #[cfg(not(target_arch = "wasm32"))]
580 const _: () = {
581 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::casper::native::ENTRY_POINTS)]
582 #[linkme(crate = casper_contract_sdk::linkme)]
583 pub static EXPORTS: casper_contract_sdk::casper::native::EntryPoint = casper_contract_sdk::casper::native::EntryPoint {
584 kind: casper_contract_sdk::casper::native::EntryPointKind::SmartContract { name: stringify!(#export_name), struct_name: stringify!(#struct_name) },
585 fptr: || -> () { #extern_func_name(); },
586 module_path: module_path!(),
587 file: file!(),
588 line: line!(),
589 };
590 };
591
592 });
593
594 manifest_entry_point_enum_variants.push(quote! {
595 #func_name {
596 #(#arg_names: #arg_types,)*
597 }
598 });
599
600 manifest_entry_point_enum_match_name.push(quote! {
601 #func_name
602 });
603
604 manifest_entry_point_input_data.push(quote! {
605 Self::#func_name { #(#arg_names,)* } => {
606 let into_tuple = (#(#arg_names,)*);
607 into_tuple.serialize(writer)
608 }
609 });
610
611 match entry_points.self_ty.as_ref() {
612 Type::Path(ref path) => {
613 let ident = syn::Ident::new(
614 &format!("{}_{}", path.path.get_ident().unwrap(), func_name),
615 Span::call_site(),
616 );
617
618 let input_data_content = if arg_names.is_empty() {
619 quote! {
620 None
621 }
622 } else {
623 quote! {
624 Some(casper_contract_sdk::serializers::borsh::to_vec(&self).expect("Serialization to succeed"))
625 }
626 };
627
628 let self_ty =
629 if method_attribute.constructor || method_attribute.ignore_state {
630 None
631 } else {
632 Some(quote! {
633 &self,
634 })
635 };
636
637 if !method_attribute.fallback {
638 extra_code.push(quote! {
639 pub fn #func_name<'a>(#self_ty #(#arg_names: #arg_types,)*) -> impl casper_contract_sdk::ToCallData<Return<'a> = #call_data_return_lifetime> {
640 #[derive(casper_contract_sdk::serializers::borsh::BorshSerialize, PartialEq, Debug)]
641 #[borsh(crate = "casper_contract_sdk::serializers::borsh")]
642 struct #ident {
643 #(#arg_names: #arg_types,)*
644 }
645
646 impl casper_contract_sdk::ToCallData for #ident {
647 type Return<'a> = #call_data_return_lifetime;
650
651 fn entry_point(&self) -> &str { stringify!(#func_name) }
652
653 fn input_data(&self) -> Option<casper_contract_sdk::serializers::borsh::__private::maybestd::vec::Vec<u8>> {
654 #input_data_content
655 }
656 }
657
658 #ident {
659 #(#arg_names,)*
660 }
661 }
662 });
663 }
664 }
665
666 _ => todo!("Different self_ty currently unsupported"),
667 }
668
669 func.clone()
670 }
671 syn::ImplItem::Type(_) => todo!(),
672 syn::ImplItem::Macro(_) => todo!(),
673 syn::ImplItem::Verbatim(_) => todo!(),
674 _ => todo!(),
675 };
676
677 let mut args = Vec::new();
678
679 for input in &func.sig.inputs {
680 let typed = match input {
681 syn::FnArg::Receiver(_receiver) => continue,
682 syn::FnArg::Typed(typed) => typed,
683 };
684 let name = match &typed.pat.as_ref() {
686 syn::Pat::Const(_) => todo!("Const"),
687 syn::Pat::Ident(ident) => ident,
688 syn::Pat::Lit(_) => todo!("Lit"),
689 syn::Pat::Macro(_) => todo!("Macro"),
690 syn::Pat::Or(_) => todo!("Or"),
691 syn::Pat::Paren(_) => todo!("Paren"),
692 syn::Pat::Path(_) => todo!("Path"),
693 syn::Pat::Range(_) => todo!("Range"),
694 syn::Pat::Reference(_) => todo!("Reference"),
695 syn::Pat::Rest(_) => todo!("Rest"),
696 syn::Pat::Slice(_) => todo!("Slice"),
697 syn::Pat::Struct(_) => todo!("Struct"),
698 syn::Pat::Tuple(_) => todo!("Tuple"),
699 syn::Pat::TupleStruct(_) => todo!("TupleStruct"),
700 syn::Pat::Type(_) => todo!("Type"),
701 syn::Pat::Verbatim(_) => todo!("Verbatim"),
702 syn::Pat::Wild(_) => todo!("Wild"),
703 _ => todo!(),
704 };
705 let ty = &typed.ty;
706
707 populate_definitions.push(quote! {
708 definitions.populate_one::<#ty>();
709 });
710
711 args.push(quote! {
712 casper_contract_sdk::schema::SchemaArgument {
713 name: stringify!(#name).into(),
714 decl: <#ty as casper_contract_sdk::abi::CasperABI>::declaration(),
715 }
716 });
717 }
718
719 #[cfg(feature = "__abi_generator")]
720 {
721 let bits = flag_value.bits();
722
723 let result = match &func.sig.output {
724 syn::ReturnType::Default => {
725 populate_definitions.push(quote! {
726 definitions.populate_one::<()>();
727 });
728
729 quote! { <() as casper_contract_sdk::abi::CasperABI>::declaration() }
730 }
731 syn::ReturnType::Type(_, ty) => match ty.as_ref() {
732 Type::Never(_) => {
733 populate_definitions.push(quote! {
734 definitions.populate_one::<()>();
735 });
736
737 quote! { <() as casper_contract_sdk::abi::CasperABI>::declaration() }
738 }
739 _ => {
740 populate_definitions.push(quote! {
741 definitions.populate_one::<#ty>();
742 });
743
744 quote! { <#ty as casper_contract_sdk::abi::CasperABI>::declaration() }
745 }
746 },
747 };
748
749 let func_name = &func.sig.ident;
750
751 let linkme_schema_entry_point_ident =
752 format_ident!("__casper_schema_entry_point_{func_name}");
753
754 defs.push(quote! {
755 fn #linkme_schema_entry_point_ident() -> casper_contract_sdk::schema::SchemaEntryPoint {
756 casper_contract_sdk::schema::SchemaEntryPoint {
757 name: stringify!(#func_name).into(),
758 arguments: vec![ #(#args,)* ],
759 result: #result,
760 flags: casper_contract_sdk::casper_executor_wasm_common::flags::EntryPointFlags::from_bits(#bits).unwrap(),
761 }
762 }
763 });
764 defs_linkme.push(linkme_schema_entry_point_ident);
765
766 let linkme_abi_populate_defs_ident =
767 format_ident!("__casper_populate_definitions_{func_name}");
768
769 defs.push(quote! {
770 fn #linkme_abi_populate_defs_ident(definitions: &mut casper_contract_sdk::abi::Definitions) {
771 #(#populate_definitions)*;
772 }
773 });
774
775 populate_definitions_linkme.push(linkme_abi_populate_defs_ident);
776 }
777 }
778 let st_name = struct_name.get_ident().unwrap();
780 let maybe_abi_collectors;
781 let maybe_entrypoint_defs;
782 #[cfg(feature = "__abi_generator")]
783 {
784 maybe_abi_collectors = quote! {
785 #(
786 const _: () = {
787 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::abi_generator::ABI_COLLECTORS)]
788 #[linkme(crate = casper_contract_sdk::linkme)]
789 static COLLECTOR: fn(&mut casper_contract_sdk::abi::Definitions) = <#struct_name>::#populate_definitions_linkme;
790 };
791 )*
792 };
793
794 maybe_entrypoint_defs = quote! {
795 #(
796
797 const _: () = {
798 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::abi_generator::ENTRYPOINTS)]
799 #[linkme(crate = casper_contract_sdk::linkme)]
800 static ENTRY_POINTS: fn() -> casper_contract_sdk::schema::SchemaEntryPoint = <#struct_name>::#defs_linkme;
801 };
802 )*
803 }
804 }
805 #[cfg(not(feature = "__abi_generator"))]
806 {
807 maybe_abi_collectors = quote! {};
808 maybe_entrypoint_defs = quote! {};
809 }
810 let handle_manifest = match impl_trait {
811 Some(_path) => {
812 None
816 }
817 None => Some(quote! {
818
819 #[doc(hidden)]
820 impl #struct_name {
821 #(#defs)*
822 }
823
824 #maybe_abi_collectors
825
826 #maybe_entrypoint_defs
827 #(#extern_entry_points)*
828
829 }),
830 };
831 let ref_struct_name = format_ident!("{st_name}Ref");
832
833 quote! {
834 #entry_points
835
836 #handle_manifest
837
838 impl #ref_struct_name {
839 #(#extra_code)*
840 }
841 }
842 .into()
843}
844
845fn generate_impl_trait_for_contract(
846 entry_points: &ItemImpl,
847 trait_path: &syn::Path,
848 impl_meta: ImplTraitForContractMeta,
849) -> TokenStream {
850 let self_ty = match entry_points.self_ty.as_ref() {
851 Type::Path(ref path) => &path.path,
852 other => todo!("Unsupported {other:?}"),
853 };
854 let self_ty = quote! { #self_ty };
855 let mut code = Vec::new();
856
857 let trait_name = trait_path
858 .segments
859 .last()
860 .expect("Expected non-empty path")
861 .ident
862 .clone();
863
864 let path_to_macro = match &impl_meta.path {
865 Some(path) => quote! { #path },
866 None => {
867 quote! { self }
868 }
869 };
870
871 let path_to_crate: proc_macro2::TokenStream = match &impl_meta.path {
872 Some(path) => {
873 let crate_name = path
874 .segments
875 .first()
876 .expect("Expected non-empty path")
877 .ident
878 .clone();
879
880 if crate_name == "crate" {
881 quote! { #path }
883 } else {
884 quote! { #crate_name }
885 }
886 }
887 None => {
888 quote! { self }
889 }
890 };
891
892 let macro_name = format_ident!("enumerate_{trait_name}_symbols");
893 let ref_trait = format_ident!("{}Ext", trait_path.segments.last().unwrap().ident);
894 let ref_name = format_ident!("{}Ref", self_ty.to_token_stream().to_string());
895
896 let visitor = if impl_meta.compile_as_dependency {
897 quote! {
898 const _: () = {
899 macro_rules! visitor {
900 ($( $vis:vis $name:ident as $export_name:ident => $dispatch:ident , $schema:ident , )*) => {
901 $(
902 $vis fn $name() {
903 #path_to_macro::$dispatch::<#self_ty>();
904 }
905 )*
906 }
907 }
908
909 #path_to_crate::#macro_name!(visitor);
910 };
911 }
912 } else {
913 quote! {
914 const _: () = {
915 macro_rules! visitor {
916 ($( $vis:vis $name:ident as $export_name:ident => $dispatch:ident , $schema:ident , )*) => {
917 $(
918 #[export_name = stringify!($export_name)]
919 $vis extern "C" fn $name() {
920 #path_to_macro::$dispatch::<#self_ty>();
921 }
922
923 #[cfg(not(target_arch = "wasm32"))]
924 const _: () = {
925 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::casper::native::ENTRY_POINTS)]
926 #[linkme(crate = casper_contract_sdk::linkme)]
927 pub static EXPORTS: casper_contract_sdk::casper::native::EntryPoint = casper_contract_sdk::casper::native::EntryPoint {
928 kind: casper_contract_sdk::casper::native::EntryPointKind::TraitImpl { trait_name: stringify!(#trait_name), impl_name: stringify!(#self_ty), name: stringify!($export_name) },
929 fptr: || -> () { $name(); },
930 module_path: module_path!(),
931 file: file!(),
932 line: line!(),
933 };
934 };
935
936 const _: () = {
937 #[casper_contract_sdk::linkme::distributed_slice(casper_contract_sdk::abi_generator::ENTRYPOINTS)]
938 #[linkme(crate = casper_contract_sdk::linkme)]
939 static ENTRY_POINTS: fn() -> casper_contract_sdk::schema::SchemaEntryPoint = <#ref_name as #ref_trait>::$schema;
940 };
941 )*
942 }
943 }
944
945 #path_to_crate::#macro_name!(visitor);
946 };
947 }
948 };
949
950 code.push(visitor);
951
952 let ref_trait = format_ident!("{}Ext", trait_path.require_ident().unwrap());
953
954 let ref_name = format_ident!("{self_ty}Ref");
955
956 code.push(quote! {
957 impl #ref_trait for #ref_name {}
958 });
959
960 quote! {
961 #entry_points
962
963 #(#code)*
964 }
965 .into()
966}
967
968fn casper_trait_definition(mut item_trait: ItemTrait, trait_meta: TraitMeta) -> TokenStream {
969 let crate_path = match &trait_meta.path {
970 Some(path) => quote! { #path },
971 None => quote! { casper_contract_sdk },
972 };
973
974 let borsh_path = {
975 let crate_path_str = match &trait_meta.path {
976 Some(path) => path.to_token_stream().to_string(),
977 None => "casper_contract_sdk".to_string(),
978 };
979 syn::LitStr::new(
980 &format!("{}::serializers::borsh", crate_path_str),
981 Span::call_site(),
982 )
983 };
984
985 let trait_name = &item_trait.ident;
986
987 let vis = &item_trait.vis;
988 let mut dispatch_functions = Vec::new();
989 let mut extra_code = Vec::new();
991 let mut populate_definitions = Vec::new();
993 let mut macro_symbols = Vec::new();
994 for entry_point in &mut item_trait.items {
995 match entry_point {
996 syn::TraitItem::Const(_) => todo!("Const"),
997 syn::TraitItem::Fn(func) => {
998 let method_attribute = MethodAttribute::from_attributes(&func.attrs).unwrap();
1000 func.attrs.clear();
1001
1002 if method_attribute.private {
1003 continue;
1004 }
1005
1006 let func_name = func.sig.ident.clone();
1007 let func_name_str = func_name.to_string();
1008
1009 if func_name.to_string().starts_with("__casper_") {
1010 return TokenStream::from(
1011 syn::Error::new(
1012 Span::call_site(),
1013 "Function names starting with '__casper_' are reserved",
1014 )
1015 .to_compile_error(),
1016 );
1017 }
1018
1019 let export_name = if method_attribute.fallback {
1020 CASPER_RESERVED_FALLBACK_EXPORT.to_string()
1021 } else {
1022 format!("{}_{}", trait_name, func_name_str)
1023 };
1024
1025 let export_ident = format_ident!("{}", &func_name_str);
1026
1027 let result = match &func.sig.output {
1028 syn::ReturnType::Default => {
1029 populate_definitions.push(quote! {
1030 definitions.populate_one::<()>();
1031 });
1032
1033 quote! { <() as #crate_path::abi::CasperABI>::declaration() }
1034 }
1035 syn::ReturnType::Type(_, ty) => {
1036 if let Type::Never(_) = ty.as_ref() {
1037 populate_definitions.push(quote! {
1038 definitions.populate_one::<()>();
1039 });
1040
1041 quote! { <() as #crate_path::abi::CasperABI>::declaration() }
1042 } else {
1043 populate_definitions.push(quote! {
1044 definitions.populate_one::<#ty>();
1045 });
1046
1047 quote! { <#ty as #crate_path::abi::CasperABI>::declaration() }
1048 }
1049 }
1050 };
1051
1052 let call_data_return_lifetime = generate_call_data_return(&func.sig.output);
1053
1054 let dispatch_func_name = format_ident!("{trait_name}_{func_name}_dispatch");
1055
1056 let arg_names_and_types = func
1057 .sig
1058 .inputs
1059 .iter()
1060 .filter_map(|arg| match arg {
1061 syn::FnArg::Receiver(_) => None,
1062 syn::FnArg::Typed(typed) => match typed.pat.as_ref() {
1063 syn::Pat::Ident(ident) => Some((&ident.ident, &typed.ty)),
1064 _ => todo!(),
1065 },
1066 })
1067 .collect::<Vec<_>>();
1068
1069 let arg_names: Vec<_> =
1070 arg_names_and_types.iter().map(|(name, _ty)| name).collect();
1071 let arg_types: Vec<_> = arg_names_and_types.iter().map(|(_name, ty)| ty).collect();
1072 let args_attrs: Vec<_> = arg_names_and_types
1074 .iter()
1075 .map(|(name, ty)| {
1076 quote! {
1077 #name: #ty
1078 }
1079 })
1080 .collect();
1081
1082 let mut args = Vec::new();
1083 for (name, ty) in &arg_names_and_types {
1084 populate_definitions.push(quote! {
1085 definitions.populate_one::<()>();
1086 });
1087 args.push(quote! {
1088 casper_contract_sdk::schema::SchemaArgument {
1089 name: stringify!(#name).into(),
1090 decl: <#ty as #crate_path::abi::CasperABI>::declaration(),
1091 }
1092 });
1093 }
1094
1095 let flags = EntryPointFlags::empty();
1096
1097 let _flags = flags.bits();
1098
1099 let handle_dispatch = match func.sig.inputs.first() {
1100 Some(syn::FnArg::Receiver(_receiver)) => {
1101 assert!(
1102 !method_attribute.private,
1103 "can't make dispatcher for private method"
1104 );
1105 quote! {
1106 #vis extern "C" fn #dispatch_func_name<T>()
1107 where
1108 T: #trait_name
1109 + #crate_path::serializers::borsh::BorshDeserialize
1110 + #crate_path::serializers::borsh::BorshSerialize
1111 + Default
1112 {
1113 #[derive(#crate_path::serializers::borsh::BorshDeserialize)]
1114 #[borsh(crate = #borsh_path)]
1115 struct Arguments {
1116 #(#args_attrs,)*
1117 }
1118
1119 let mut flags = #crate_path::casper_executor_wasm_common::flags::ReturnFlags::empty();
1120 let mut instance: T = #crate_path::casper::read_state().unwrap();
1121 let input = #crate_path::prelude::casper::copy_input();
1122 let args: Arguments = #crate_path::serializers::borsh::from_slice(&input).unwrap();
1123
1124 let ret = instance.#func_name(#(args.#arg_names,)*);
1125
1126 #crate_path::casper::write_state(&instance).unwrap();
1127
1128 let ret_bytes = #crate_path::serializers::borsh::to_vec(&ret).unwrap();
1129 #crate_path::casper::ret(flags, Some(&ret_bytes));
1130 }
1131 }
1132 }
1133
1134 None | Some(syn::FnArg::Typed(_)) => {
1135 assert!(
1136 !method_attribute.private,
1137 "can't make dispatcher for private static method"
1138 );
1139 quote! {
1140 #vis extern "C" fn #dispatch_func_name<T: #trait_name>() {
1141 #[derive(#crate_path::serializers::borsh::BorshDeserialize)]
1142 #[borsh(crate = #borsh_path)]
1143 struct Arguments {
1144 #(#args_attrs,)*
1145 }
1146
1147
1148 let input = #crate_path::prelude::casper::copy_input();
1149 let args: Arguments = #crate_path::serializers::borsh::from_slice(&input).unwrap();
1150
1151
1152 let _ret = <T as #trait_name>::#func_name(#(args.#arg_names,)*);
1153 }
1154 }
1155 }
1156 };
1157
1158 let schema_helper_ident = format_ident!("__casper_schema_entry_point_{func_name}");
1159 extra_code.push(quote! {
1160 fn #schema_helper_ident () -> casper_contract_sdk::schema::SchemaEntryPoint {
1161 casper_contract_sdk::schema::SchemaEntryPoint {
1162 name: stringify!(#export_name).into(),
1163 arguments: vec![ #(#args,)* ],
1164 result: #result,
1165 flags: casper_contract_sdk::casper_executor_wasm_common::flags::EntryPointFlags::from_bits(#_flags).unwrap(),
1166 }
1167 }
1168 });
1169
1170 macro_symbols.push(quote! {
1171 #vis #func_name as #export_ident => #dispatch_func_name , #schema_helper_ident
1172 });
1173
1174 dispatch_functions.push(quote! { #handle_dispatch });
1175
1176 let input_data_content = if arg_names.is_empty() {
1177 quote! {
1178 None
1179 }
1180 } else {
1181 quote! {
1182 Some(#crate_path::serializers::borsh::to_vec(&self).expect("Serialization to succeed"))
1183 }
1184 };
1185 let self_ty = if method_attribute.constructor || method_attribute.ignore_state {
1186 None
1187 } else {
1188 Some(quote! {
1189 self,
1190 })
1191 };
1192
1193 let is_fallback = method_attribute.fallback;
1194
1195 if !is_fallback {
1196 let entry_point_lit = LitStr::new(&export_name, Span::call_site());
1197 extra_code.push(quote! {
1198 fn #func_name<'a>(#self_ty #(#arg_names: #arg_types,)*) -> impl #crate_path::ToCallData<Return<'a> = #call_data_return_lifetime> {
1199 #[derive(#crate_path::serializers::borsh::BorshSerialize)]
1200 #[borsh(crate = #borsh_path)]
1201 struct CallData {
1202 #(pub #arg_names: #arg_types,)*
1203 }
1204
1205 impl #crate_path::ToCallData for CallData {
1206 type Return<'a> = #call_data_return_lifetime;
1209
1210 fn entry_point(&self) -> &str { #entry_point_lit }
1211 fn input_data(&self) -> Option<Vec<u8>> {
1212 #input_data_content
1213 }
1214 }
1215
1216 CallData {
1217 #(#arg_names,)*
1218 }
1219 }
1220 });
1221 }
1222 }
1223 syn::TraitItem::Type(_) => {
1224 return syn::Error::new(Span::call_site(), "Unsupported generic associated types")
1225 .to_compile_error()
1226 .into();
1227 }
1228 syn::TraitItem::Macro(_) => todo!("Macro"),
1229 syn::TraitItem::Verbatim(_) => todo!("Verbatim"),
1230 other => todo!("Other {other:?}"),
1231 }
1232 }
1233 let ref_struct = format_ident!("{trait_name}Ref");
1234 let ref_struct_trait = format_ident!("{trait_name}Ext");
1235
1236 let macro_name = format_ident!("enumerate_{trait_name}_symbols");
1237
1238 let maybe_exported_macro = if !trait_meta.export.unwrap_or(false) {
1239 quote! {
1240 #[allow(non_snake_case, unused_macros)]
1241 macro_rules! #macro_name {
1242 ($mac:ident) => {
1243 $mac! {
1244 #(#macro_symbols,)*
1245 }
1246 }
1247 }
1248 pub(crate) use #macro_name;
1249 }
1250 } else {
1251 quote! {
1252 #[allow(non_snake_case, unused_macros)]
1253 #[macro_export]
1254 macro_rules! #macro_name {
1255 ($mac:ident) => {
1256 $mac! {
1257 #(#macro_symbols,)*
1258 }
1259 }
1260 }
1261 }
1262 };
1263
1264 let extension_struct = quote! {
1265 #vis trait #ref_struct_trait: Sized {
1266 #(#extra_code)*
1267 }
1268
1269 #vis struct #ref_struct;
1270
1271 impl #ref_struct {
1272
1273 }
1274
1275 #maybe_exported_macro
1276
1277 #(#dispatch_functions)*
1278
1279 impl #ref_struct_trait for #ref_struct {}
1281 impl #crate_path::ContractRef for #ref_struct {
1282 fn new() -> Self {
1283 #ref_struct
1284 }
1285 }
1286 };
1287 quote! {
1288 #item_trait
1289
1290 #extension_struct
1291 }
1292 .into()
1293}
1294
1295fn generate_casper_state_for_struct(
1296 item_struct: &ItemStruct,
1297 struct_meta: StructMeta,
1298) -> impl quote::ToTokens {
1299 let crate_path = match &struct_meta.path {
1300 Some(path) => quote! { #path },
1301 None => quote! { casper_contract_sdk },
1302 };
1303
1304 let borsh_path = {
1305 let crate_path_str = match &struct_meta.path {
1306 Some(path) => path.to_token_stream().to_string(),
1307 None => "casper_contract_sdk".to_string(),
1308 };
1309 syn::LitStr::new(
1310 &format!("{}::serializers::borsh", crate_path_str),
1311 Span::call_site(),
1312 )
1313 };
1314 let maybe_derive_abi = get_maybe_derive_abi(crate_path.clone());
1315
1316 quote! {
1317 #[derive(#crate_path::serializers::borsh::BorshSerialize, #crate_path::serializers::borsh::BorshDeserialize)]
1318 #[borsh(crate = #borsh_path)]
1319 #maybe_derive_abi
1320 #item_struct
1321 }
1322}
1323
1324fn generate_casper_state_for_enum(
1325 item_enum: &ItemEnum,
1326 enum_meta: EnumMeta,
1327) -> impl quote::ToTokens {
1328 let crate_path = match &enum_meta.path {
1329 Some(path) => quote! { #path },
1330 None => quote! { casper_contract_sdk },
1331 };
1332
1333 let borsh_path = {
1334 let crate_path_str = match &enum_meta.path {
1335 Some(path) => path.to_token_stream().to_string(),
1336 None => "casper_contract_sdk".to_string(),
1337 };
1338 syn::LitStr::new(
1339 &format!("{}::serializers::borsh", crate_path_str),
1340 Span::call_site(),
1341 )
1342 };
1343
1344 let maybe_derive_abi = get_maybe_derive_abi(crate_path.clone());
1345
1346 quote! {
1347 #[derive(#crate_path::serializers::borsh::BorshSerialize, #crate_path::serializers::borsh::BorshDeserialize)]
1348 #[borsh(use_discriminant = true, crate = #borsh_path)]
1349 #[repr(u32)]
1350 #maybe_derive_abi
1351 #item_enum
1352 }
1353}
1354
1355fn get_maybe_derive_abi(_crate_path: impl ToTokens) -> impl ToTokens {
1356 #[cfg(feature = "__abi_generator")]
1357 {
1358 quote! {
1359 #[derive(#_crate_path::macros::CasperABI)]
1360 }
1361 }
1362
1363 #[cfg(not(feature = "__abi_generator"))]
1364 {
1365 quote! {}
1366 }
1367}
1368
1369fn process_casper_contract_state_for_struct(
1370 contract_struct: &ItemStruct,
1371 struct_meta: StructMeta,
1372) -> TokenStream {
1373 let struct_name = &contract_struct.ident;
1374 let ref_name = format_ident!("{struct_name}Ref");
1375 let vis = &contract_struct.vis;
1376
1377 let crate_path = match &struct_meta.path {
1378 Some(path) => quote! { #path },
1379 None => quote! { casper_contract_sdk },
1380 };
1381 let borsh_path = {
1382 let crate_path_str = match &struct_meta.path {
1383 Some(path) => path.to_token_stream().to_string(),
1384 None => "casper_contract_sdk".to_string(),
1385 };
1386 syn::LitStr::new(
1387 &format!("{}::serializers::borsh", crate_path_str),
1388 Span::call_site(),
1389 )
1390 };
1391
1392 let maybe_derive_abi = get_maybe_derive_abi(crate_path.clone());
1393
1394 let maybe_casper_schema = {
1397 #[cfg(feature = "__embed_schema")]
1398 quote! {
1399 const SCHEMA: Option<&str> = option_env!("__CARGO_CASPER_INJECT_SCHEMA_MARKER");
1400
1401 #[no_mangle]
1402 pub extern "C" fn __casper_schema() {
1403 use #crate_path::casper::ret;
1404 use #crate_path::casper_executor_wasm_common::flags::ReturnFlags;
1405 let bytes = SCHEMA.unwrap_or_default().as_bytes();
1406 ret(ReturnFlags::empty(), Some(bytes));
1407 }
1408 }
1409 #[cfg(not(feature = "__embed_schema"))]
1410 quote! {}
1411 };
1412
1413 quote! {
1414 #[derive(#crate_path::serializers::borsh::BorshSerialize, #crate_path::serializers::borsh::BorshDeserialize)]
1415 #[borsh(crate = #borsh_path)]
1416 #maybe_derive_abi
1417 #contract_struct
1418
1419 #vis struct #ref_name;
1420
1421 impl #crate_path::ContractRef for #ref_name {
1422 fn new() -> Self {
1423 #ref_name
1424 }
1425 }
1426
1427 #maybe_casper_schema
1428 }
1429 .into()
1430}
1431
1432#[proc_macro_attribute]
1433pub fn entry_point(_attr: TokenStream, item: TokenStream) -> TokenStream {
1434 let func = parse_macro_input!(item as ItemFn);
1435
1436 let vis = &func.vis;
1437 let _sig = &func.sig;
1438 let func_name = &func.sig.ident;
1439
1440 let block = &func.block;
1441
1442 let mut handle_args = Vec::new();
1443 let mut params = Vec::new();
1444
1445 for arg in &func.sig.inputs {
1446 let typed = match arg {
1447 syn::FnArg::Receiver(_) => todo!(),
1448 syn::FnArg::Typed(typed) => typed,
1449 };
1450
1451 let name = match typed.pat.as_ref() {
1452 syn::Pat::Ident(ident) => &ident.ident,
1453 _ => todo!(),
1454 };
1455
1456 let ty = &typed.ty;
1457
1458 let tok = quote! {
1459 let #typed = casper_contract_sdk::get_named_arg(stringify!(#name)).expect("should get named arg");
1460 };
1461 handle_args.push(tok);
1462
1463 let tok2 = quote! {
1464 (stringify!(#name), <#ty>::cl_type())
1465 };
1466 params.push(tok2);
1467 }
1468
1469 let output = &func.sig.output;
1472
1473 let gen = quote! {
1476 #vis fn #func_name() {
1479 #(#handle_args)*;
1480
1481 let closure = || #output {
1482 #block
1483 };
1484
1485 let result = closure();
1486
1487 result.expect("should work")
1496 }
1497 };
1498
1499 println!("{gen}");
1500
1501 gen.into()
1504}
1505
1506#[proc_macro_derive(CasperABI, attributes(casper))]
1564pub fn derive_casper_abi(input: TokenStream) -> TokenStream {
1565 let res = if let Ok(input) = syn::parse::<ItemStruct>(input.clone()) {
1566 let mut populate_definitions = Vec::new();
1567 let name = input.ident.clone();
1568 let mut items = Vec::new();
1569 for field in &input.fields {
1570 match &field.ty {
1571 Type::Path(path) => {
1572 for segment in &path.path.segments {
1573 let field_name = &field.ident;
1574
1575 populate_definitions.push(quote! {
1576 definitions.populate_one::<#segment>();
1577 });
1578
1579 items.push(quote! {
1580 casper_contract_sdk::abi::StructField {
1581 name: stringify!(#field_name).into(),
1582 decl: <#segment>::declaration(),
1583 }
1584 });
1585 }
1586 }
1587 other_ty => todo!("Unsupported type {other_ty:?}"),
1588 }
1589 }
1590
1591 Ok(quote! {
1592 impl casper_contract_sdk::abi::CasperABI for #name {
1593 fn populate_definitions(definitions: &mut casper_contract_sdk::abi::Definitions) {
1594 #(#populate_definitions)*;
1595 }
1596
1597 fn declaration() -> casper_contract_sdk::abi::Declaration {
1598 const DECL: &str = concat!(module_path!(), "::", stringify!(#name));
1599 DECL.into()
1600 }
1601
1602 fn definition() -> casper_contract_sdk::abi::Definition {
1603 casper_contract_sdk::abi::Definition::Struct {
1604 items: vec![
1605 #(#items,)*
1606 ]
1607 }
1608 }
1609 }
1610 })
1611 } else if let Ok(input) = syn::parse::<ItemEnum>(input.clone()) {
1612 let name = input.ident.clone();
1614
1615 let mut all_definitions = Vec::new();
1616 let mut all_variants = Vec::new();
1617 let mut populate_definitions = Vec::new();
1618 let mut has_unit_definition = false;
1619
1620 all_definitions.push(quote! {
1625 casper_contract_sdk::abi::Definition::Enum {
1626 name: stringify!(#name).into(),
1627 }
1628 });
1629
1630 let mut current_discriminant = 0;
1631
1632 for variant in &input.variants {
1633 if let Some(discriminant) = &variant.discriminant {
1634 match &discriminant.1 {
1635 syn::Expr::Lit(lit) => match &lit.lit {
1636 syn::Lit::Int(int) => {
1637 current_discriminant = int.base10_parse::<u64>().unwrap();
1638 }
1639 _ => todo!(),
1640 },
1641 _ => todo!(),
1642 }
1643 }
1644
1645 let variant_name = &variant.ident;
1646
1647 let variant_decl = match &variant.fields {
1648 Fields::Unit => {
1649 if !has_unit_definition {
1651 populate_definitions.push(quote! {
1652 definitions.populate_one::<()>();
1653 });
1654 has_unit_definition = true;
1655 }
1656
1657 quote! {
1658 <()>::declaration()
1659 }
1660 }
1661 Fields::Named(named) => {
1662 let mut fields = Vec::new();
1663
1664 let variant_name = format_ident!("{name}_{variant_name}");
1665
1666 for field in &named.named {
1667 let field_name = &field.ident;
1668 match &field.ty {
1669 Type::Path(path) => {
1670 populate_definitions.push(quote! {
1671 definitions.populate_one::<#path>();
1672 });
1673
1674 fields.push(quote! {
1675 casper_contract_sdk::abi::StructField {
1676 name: stringify!(#field_name).into(),
1677 decl: <#path as casper_contract_sdk::abi::CasperABI>::declaration()
1678 }
1679 });
1680 }
1681 other_ty => todo!("Unsupported type {other_ty:?}"),
1682 }
1683 }
1684
1685 populate_definitions.push(quote! {
1686 definitions.populate_custom(
1687 stringify!(#variant_name).into(),
1688 casper_contract_sdk::abi::Definition::Struct {
1689 items: vec![
1690 #(#fields,)*
1691 ],
1692 });
1693 });
1694
1695 quote! {
1696 stringify!(#variant_name).into()
1697 }
1698 }
1699 Fields::Unnamed(unnamed_fields) => {
1700 let mut fields = Vec::new();
1701
1702 let variant_name = format_ident!("{name}_{variant_name}");
1703
1704 for field in &unnamed_fields.unnamed {
1705 match &field.ty {
1706 Type::Path(path) => {
1707 for segment in &path.path.segments {
1708 let type_name = &segment.ident;
1709 populate_definitions.push(quote! {
1710 definitions.populate_one::<#type_name>();
1711 });
1712
1713 fields.push(quote! {
1714 <#type_name as casper_contract_sdk::abi::CasperABI>::declaration()
1715 });
1716 }
1717 }
1718 other_ty => todo!("Unsupported type {other_ty:?}"),
1719 }
1720 }
1721
1722 populate_definitions.push(quote! {
1723 definitions.populate_custom(
1724 stringify!(#variant_name).into(),
1725 casper_contract_sdk::abi::Definition::Tuple {
1726 items: vec![
1727 #(#fields,)*
1728 ],
1729 });
1730 });
1731
1732 quote! {
1733 stringify!(#variant_name).into()
1734 }
1735 }
1736 };
1737
1738 all_variants.push(quote! {
1739 casper_contract_sdk::abi::EnumVariant {
1740 name: stringify!(#variant_name).into(),
1741 discriminant: #current_discriminant,
1742 decl: #variant_decl,
1743 }
1744 });
1745
1746 current_discriminant += 1;
1747 }
1748
1749 Ok(quote! {
1750 impl casper_contract_sdk::abi::CasperABI for #name {
1751 fn populate_definitions(definitions: &mut casper_contract_sdk::abi::Definitions) {
1752 #(#populate_definitions)*;
1753 }
1754
1755 fn declaration() -> casper_contract_sdk::abi::Declaration {
1756 const DECL: &str = concat!(module_path!(), "::", stringify!(#name));
1757 DECL.into()
1758 }
1759
1760 fn definition() -> casper_contract_sdk::abi::Definition {
1761 casper_contract_sdk::abi::Definition::Enum {
1762 items: vec![
1763 #(#all_variants,)*
1764 ],
1765 }
1766 }
1767 }
1768 })
1769 } else if syn::parse::<ItemUnion>(input).is_ok() {
1770 Err(syn::Error::new(
1771 Span::call_site(),
1772 "Borsh schema does not support unions yet.",
1773 ))
1774 } else {
1775 unreachable!()
1777 };
1778 TokenStream::from(match res {
1779 Ok(res) => res,
1780 Err(err) => err.to_compile_error(),
1781 })
1782}
1783
1784#[proc_macro]
1785pub fn blake2b256(input: TokenStream) -> TokenStream {
1786 let input = parse_macro_input!(input as LitStr);
1787 let bytes = input.value();
1788
1789 let hash = utils::compute_blake2b256(bytes.as_bytes());
1790
1791 TokenStream::from(quote! {
1792 [ #(#hash),* ]
1793 })
1794}
1795
1796#[proc_macro]
1797pub fn test(item: TokenStream) -> TokenStream {
1798 let input = parse_macro_input!(item as ItemFn);
1799 TokenStream::from(quote! {
1800 #[test]
1801 #input
1802 })
1803}
1804
1805#[proc_macro_derive(PanicOnDefault)]
1811pub fn derive_no_default(item: TokenStream) -> TokenStream {
1812 if let Ok(input) = syn::parse::<ItemStruct>(item) {
1813 let name = &input.ident;
1814 TokenStream::from(quote! {
1815 impl ::core::default::Default for #name {
1816 fn default() -> Self {
1817 panic!("The contract is not initialized");
1818 }
1819 }
1820 })
1821 } else {
1822 TokenStream::from(
1823 syn::Error::new(
1824 Span::call_site(),
1825 "PanicOnDefault can only be used on type declarations sections.",
1826 )
1827 .to_compile_error(),
1828 )
1829 }
1830}