seda_sdk_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, spanned::Spanned, ItemImpl};
4
5fn validate_fn(item: &syn::ImplItemFn, errors: &mut Vec<syn::Error>) {
6    if item.sig.unsafety.is_some() {
7        errors.push(syn::Error::new(item.sig.span(), "Function must not be unsafe."));
8    }
9
10    if item.sig.abi.is_some() {
11        errors.push(syn::Error::new(
12            item.sig.span(),
13            "Function must not have an ABI(extern C).",
14        ));
15    }
16
17    if item.sig.constness.is_some() {
18        errors.push(syn::Error::new(item.sig.span(), "Function must not be const."));
19    }
20
21    if item.sig.asyncness.is_some() {
22        errors.push(syn::Error::new(item.sig.span(), "Function must not be async."));
23    }
24
25    if !item.sig.generics.params.is_empty() {
26        errors.push(syn::Error::new(
27            item.sig.generics.span(),
28            "Function must not have generics or lifetimes.",
29        ));
30    }
31
32    if !item.sig.inputs.is_empty() {
33        errors.push(syn::Error::new(
34            item.sig.inputs.span(),
35            "Function must not take any arguments.",
36        ));
37    }
38
39    if item.sig.variadic.is_some() {
40        errors.push(syn::Error::new(
41            item.sig.variadic.span(),
42            "Function must not be variadic.",
43        ));
44    }
45
46    match &item.sig.output {
47        syn::ReturnType::Default => {}
48        _ => {
49            errors.push(syn::Error::new(
50                item.sig.output.span(),
51                "Function must not have a return type.",
52            ));
53        }
54    }
55}
56
57#[proc_macro_attribute]
58pub fn oracle_program(_attr: TokenStream, item: TokenStream) -> TokenStream {
59    let input = parse_macro_input!(item as ItemImpl);
60
61    // Ensure only the execute and tally methods are present
62    let mut execute_fn = None;
63    let mut tally_fn = None;
64
65    let mut errors = Vec::new();
66    let input_span = input.span();
67    for item in input.items {
68        match item {
69            syn::ImplItem::Const(item) => errors.push(syn::Error::new(
70                item.span(),
71                "Consts are not allowed to be defined in this macro.",
72            )),
73            syn::ImplItem::Fn(item) => {
74                if item.sig.ident == "execute" {
75                    execute_fn = Some(item);
76                } else if item.sig.ident == "tally" {
77                    tally_fn = Some(item);
78                } else {
79                    errors.push(syn::Error::new(
80                        item.span(),
81                        "Only the functions tally and execute are allowed.",
82                    ));
83                }
84            }
85            syn::ImplItem::Type(item) => errors.push(syn::Error::new(
86                item.span(),
87                "Only the type Error is allowed to be defined in this macro.",
88            )),
89            syn::ImplItem::Macro(item) => errors.push(syn::Error::new(
90                item.span(),
91                "Macros are not allowed to be defined in this macro.",
92            )),
93            syn::ImplItem::Verbatim(item) => errors.push(syn::Error::new(
94                item.span(),
95                "Verbatims are not allowed to be defined in this macro.",
96            )),
97            _ => unreachable!(),
98        }
99    }
100
101    // Ensure there is either an execute or tally or both methods
102    // replace missing ones with a dummy function
103    let (execute_fn, tally_fn) = match (execute_fn, tally_fn) {
104        (Some(execute_fn), Some(tally_fn)) => {
105            validate_fn(&execute_fn, &mut errors);
106            validate_fn(&tally_fn, &mut errors);
107            (execute_fn, tally_fn)
108        }
109        (Some(execute_fn), None) => {
110            let tally_fn: syn::ImplItemFn = syn::parse_quote! {
111                fn tally() {
112                    seda_sdk_rs::Process::error(&"Tally is not implemented.".to_bytes());
113                }
114            };
115            validate_fn(&execute_fn, &mut errors);
116            (execute_fn, tally_fn)
117        }
118        (None, Some(tally_fn)) => {
119            let execute_fn: syn::ImplItemFn = syn::parse_quote! {
120                fn execute() {
121                    seda_sdk_rs::Process::error(&"Execute is not implemented.".to_bytes());
122                }
123            };
124            validate_fn(&tally_fn, &mut errors);
125            (execute_fn, tally_fn)
126        }
127        (None, None) => {
128            let mut combined_error = syn::Error::new(
129                input_span,
130                "At least one of the functions execute or tally must be defined.",
131            );
132            for e in errors {
133                combined_error.combine(e);
134            }
135            return combined_error.to_compile_error().into();
136        }
137    };
138
139    if !errors.is_empty() {
140        let mut combined_error = errors.remove(0);
141        for e in errors {
142            combined_error.combine(e);
143        }
144        return combined_error.to_compile_error().into();
145    }
146
147    let expanded = quote! {
148
149        fn main() {
150
151            #execute_fn
152            #tally_fn
153
154            if seda_sdk_rs::Process::is_dr_vm_mode() {
155                return execute();
156            }
157            return tally();
158        }
159    };
160
161    TokenStream::from(expanded)
162}