1use core::cmp::Reverse;
24use proc_macro::TokenStream;
25use proc_macro2::{Span, TokenStream as TokenStream2};
26use quote::{quote, quote_spanned, ToTokens};
27use syn::{
28 parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DeriveInput,
29 Fields, FnArg, Ident,
30};
31
32#[proc_macro_derive(WeightDebug)]
36pub fn derive_weight_debug(input: TokenStream) -> TokenStream {
37 let input = parse_macro_input!(input as DeriveInput);
38 let name = &input.ident;
39 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
40 let data = if let Data::Struct(data) = &input.data {
41 data
42 } else {
43 return quote_spanned! {
44 name.span() =>
45 compile_error!("WeightDebug is only supported for structs.");
46 }
47 .into();
48 };
49
50 let fields = match &data.fields {
51 Fields::Named(fields) => {
52 let recurse = fields.named.iter().filter_map(|f| {
53 let name = f.ident.as_ref()?;
54 if name.to_string().starts_with('_') {
55 return None;
56 }
57 let ret = quote_spanned! { f.span() =>
58 formatter.field(stringify!(#name), &HumanWeight(self.#name));
59 };
60 Some(ret)
61 });
62 quote! {
63 #( #recurse )*
64 }
65 },
66 Fields::Unnamed(fields) => quote_spanned! {
67 fields.span() =>
68 compile_error!("Unnamed fields are not supported")
69 },
70 Fields::Unit => quote!(),
71 };
72
73 let tokens = quote! {
74 impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause {
75 fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result {
76 use ::sp_runtime::{FixedPointNumber, FixedU128 as Fixed};
77 use ::core::{fmt, write};
78
79 struct HumanWeight(Weight);
80
81 impl fmt::Debug for HumanWeight {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 if self.0.ref_time() > 1_000_000_000 {
84 write!(
85 formatter,
86 "{} ms, {} bytes",
87 Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000_000).into_inner() / Fixed::accuracy(),
88 self.0.proof_size()
89 )
90 } else if self.0.ref_time() > 1_000_000 {
91 write!(
92 formatter,
93 "{} µs, {} bytes",
94 Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000).into_inner() / Fixed::accuracy(),
95 self.0.proof_size()
96 )
97 } else if self.0.ref_time() > 1_000 {
98 write!(
99 formatter,
100 "{} ns, {} bytes",
101 Fixed::saturating_from_rational(self.0.ref_time(), 1_000).into_inner() / Fixed::accuracy(),
102 self.0.proof_size()
103 )
104 } else {
105 write!(formatter, "{} ps, {} bytes", self.0.ref_time(), self.0.proof_size())
106 }
107 }
108 }
109
110 let mut formatter = formatter.debug_struct(stringify!(#name));
111 #fields
112 formatter.finish()
113 }
114 }
115 };
116
117 tokens.into()
118}
119
120struct EnvDef {
122 host_funcs: Vec<HostFn>,
123}
124
125struct HostFn {
127 item: syn::ItemFn,
128 version: u8,
129 name: String,
130 returns: HostFnReturn,
131 is_stable: bool,
132 alias_to: Option<String>,
133 not_deprecated: bool,
135 cfg: Option<syn::Attribute>,
136}
137
138enum HostFnReturn {
139 Unit,
140 U32,
141 U64,
142 ReturnCode,
143}
144
145impl HostFnReturn {
146 fn to_wasm_sig(&self) -> TokenStream2 {
147 let ok = match self {
148 Self::Unit => quote! { () },
149 Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 },
150 Self::U64 => quote! { ::core::primitive::u64 },
151 };
152 quote! {
153 ::core::result::Result<#ok, ::wasmi::Error>
154 }
155 }
156}
157
158impl ToTokens for HostFn {
159 fn to_tokens(&self, tokens: &mut TokenStream2) {
160 self.item.to_tokens(tokens);
161 }
162}
163
164impl HostFn {
165 pub fn try_from(mut item: syn::ItemFn) -> syn::Result<Self> {
166 let err = |span, msg| {
167 let msg = format!("Invalid host function definition.\n{}", msg);
168 syn::Error::new(span, msg)
169 };
170
171 let msg =
173 "Only #[version(<u8>)], #[unstable], #[prefixed_alias], #[cfg], #[mutating] and #[deprecated] attributes are allowed.";
174 let span = item.span();
175 let mut attrs = item.attrs.clone();
176 attrs.retain(|a| !a.path().is_ident("doc"));
177 let mut maybe_version = None;
178 let mut is_stable = true;
179 let mut alias_to = None;
180 let mut not_deprecated = true;
181 let mut mutating = false;
182 let mut cfg = None;
183 while let Some(attr) = attrs.pop() {
184 let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string();
185 match ident.as_str() {
186 "version" => {
187 if maybe_version.is_some() {
188 return Err(err(span, "#[version] can only be specified once"));
189 }
190 maybe_version =
191 Some(attr.parse_args::<syn::LitInt>().and_then(|lit| lit.base10_parse())?);
192 },
193 "unstable" => {
194 if !is_stable {
195 return Err(err(span, "#[unstable] can only be specified once"));
196 }
197 is_stable = false;
198 },
199 "prefixed_alias" => {
200 alias_to = Some(item.sig.ident.to_string());
201 item.sig.ident = syn::Ident::new(
202 &format!("seal_{}", &item.sig.ident.to_string()),
203 item.sig.ident.span(),
204 );
205 },
206 "deprecated" => {
207 if !not_deprecated {
208 return Err(err(span, "#[deprecated] can only be specified once"));
209 }
210 not_deprecated = false;
211 },
212 "mutating" => {
213 if mutating {
214 return Err(err(span, "#[mutating] can only be specified once"));
215 }
216 mutating = true;
217 },
218 "cfg" => {
219 if cfg.is_some() {
220 return Err(err(span, "#[cfg] can only be specified once"));
221 }
222 cfg = Some(attr);
223 },
224 id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))),
225 }
226 }
227
228 if mutating {
229 let stmt = syn::parse_quote! {
230 if ctx.ext().is_read_only() {
231 return Err(Error::<E::T>::StateChangeDenied.into());
232 }
233 };
234 item.block.stmts.insert(0, stmt);
235 }
236
237 let name = item.sig.ident.to_string();
238
239 if !(is_stable || not_deprecated) {
240 return Err(err(span, "#[deprecated] is mutually exclusive with #[unstable]"));
241 }
242
243 let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _";
246 let special_args = item
247 .sig
248 .inputs
249 .iter()
250 .take(2)
251 .enumerate()
252 .map(|(i, arg)| is_valid_special_arg(i, arg))
253 .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc });
254
255 if special_args != 2 {
256 return Err(err(span, msg));
257 }
258
259 let msg = r#"Should return one of the following:
261 - Result<(), TrapReason>,
262 - Result<ReturnErrorCode, TrapReason>,
263 - Result<u64, TrapReason>,
264 - Result<u32, TrapReason>"#;
265 let ret_ty = match item.clone().sig.output {
266 syn::ReturnType::Type(_, ty) => Ok(ty.clone()),
267 _ => Err(err(span, &msg)),
268 }?;
269 match *ret_ty {
270 syn::Type::Path(tp) => {
271 let result = &tp.path.segments.last().ok_or(err(span, &msg))?;
272 let (id, span) = (result.ident.to_string(), result.ident.span());
273 id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?;
274
275 match &result.arguments {
276 syn::PathArguments::AngleBracketed(group) => {
277 if group.args.len() != 2 {
278 return Err(err(span, &msg));
279 };
280
281 let arg2 = group.args.last().ok_or(err(span, &msg))?;
282
283 let err_ty = match arg2 {
284 syn::GenericArgument::Type(ty) => Ok(ty.clone()),
285 _ => Err(err(arg2.span(), &msg)),
286 }?;
287
288 match err_ty {
289 syn::Type::Path(tp) => Ok(tp
290 .path
291 .segments
292 .first()
293 .ok_or(err(arg2.span(), &msg))?
294 .ident
295 .to_string()),
296 _ => Err(err(tp.span(), &msg)),
297 }?
298 .eq("TrapReason")
299 .then_some(())
300 .ok_or(err(span, &msg))?;
301
302 let arg1 = group.args.first().ok_or(err(span, &msg))?;
303 let ok_ty = match arg1 {
304 syn::GenericArgument::Type(ty) => Ok(ty.clone()),
305 _ => Err(err(arg1.span(), &msg)),
306 }?;
307 let ok_ty_str = match ok_ty {
308 syn::Type::Path(tp) => Ok(tp
309 .path
310 .segments
311 .first()
312 .ok_or(err(arg1.span(), &msg))?
313 .ident
314 .to_string()),
315 syn::Type::Tuple(tt) => {
316 if !tt.elems.is_empty() {
317 return Err(err(arg1.span(), &msg));
318 };
319 Ok("()".to_string())
320 },
321 _ => Err(err(ok_ty.span(), &msg)),
322 }?;
323 let returns = match ok_ty_str.as_str() {
324 "()" => Ok(HostFnReturn::Unit),
325 "u32" => Ok(HostFnReturn::U32),
326 "u64" => Ok(HostFnReturn::U64),
327 "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode),
328 _ => Err(err(arg1.span(), &msg)),
329 }?;
330
331 Ok(Self {
332 item,
333 version: maybe_version.unwrap_or_default(),
334 name,
335 returns,
336 is_stable,
337 alias_to,
338 not_deprecated,
339 cfg,
340 })
341 },
342 _ => Err(err(span, &msg)),
343 }
344 },
345 _ => Err(err(span, &msg)),
346 }
347 }
348
349 fn module(&self) -> String {
350 format!("seal{}", self.version)
351 }
352}
353
354impl EnvDef {
355 pub fn try_from(item: syn::ItemMod) -> syn::Result<Self> {
356 let span = item.span();
357 let err = |msg| syn::Error::new(span, msg);
358 let items = &item
359 .content
360 .as_ref()
361 .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))?
362 .1;
363
364 let extract_fn = |i: &syn::Item| match i {
365 syn::Item::Fn(i_fn) => Some(i_fn.clone()),
366 _ => None,
367 };
368
369 let selector = |a: &syn::Attribute| a.path().is_ident("prefixed_alias");
370
371 let aliases = items
372 .iter()
373 .filter_map(extract_fn)
374 .filter(|i| i.attrs.iter().any(selector))
375 .map(|i| HostFn::try_from(i));
376
377 let host_funcs = items
378 .iter()
379 .filter_map(extract_fn)
380 .map(|mut i| {
381 i.attrs.retain(|i| !selector(i));
382 i
383 })
384 .map(|i| HostFn::try_from(i))
385 .chain(aliases)
386 .collect::<Result<Vec<_>, _>>()?;
387
388 Ok(Self { host_funcs })
389 }
390}
391
392fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool {
393 let FnArg::Typed(pat) = arg else { return false };
394 let ident =
395 if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false };
396 let name_ok = match idx {
397 0 => ident == "ctx" || ident == "_ctx",
398 1 => ident == "memory" || ident == "_memory",
399 _ => false,
400 };
401 if !name_ok {
402 return false;
403 }
404 matches!(*pat.ty, syn::Type::Infer(_))
405}
406
407fn expand_func_doc(func: &HostFn) -> TokenStream2 {
408 let func_decl = {
410 let mut sig = func.item.sig.clone();
411 sig.inputs = sig
412 .inputs
413 .iter()
414 .skip(2)
415 .map(|p| p.clone())
416 .collect::<Punctuated<FnArg, Comma>>();
417 sig.to_token_stream()
418 };
419 let func_doc = {
420 let func_docs = if let Some(origin_fn) = &func.alias_to {
421 let alias_doc = format!(
422 "This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.",
423 origin_fn,
424 );
425 quote! { #[doc = #alias_doc] }
426 } else {
427 let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| {
428 let docs = d.to_token_stream();
429 quote! { #docs }
430 });
431 quote! { #( #docs )* }
432 };
433 let deprecation_notice = if !func.not_deprecated {
434 let warning = "\n # Deprecated\n\n \
435 This function is deprecated and will be removed in future versions.\n \
436 No new code or contracts with this API can be deployed.";
437 quote! { #[doc = #warning] }
438 } else {
439 quote! {}
440 };
441 let import_notice = {
442 let info = format!(
443 "\n# Wasm Import Statement\n```wat\n(import \"seal{}\" \"{}\" (func ...))\n```",
444 func.version, func.name,
445 );
446 quote! { #[doc = #info] }
447 };
448 let unstable_notice = if !func.is_stable {
449 let warning = "\n # Unstable\n\n \
450 This function is unstable and it is a subject to change (or removal) in the future.\n \
451 Do not deploy a contract using it to a production chain.";
452 quote! { #[doc = #warning] }
453 } else {
454 quote! {}
455 };
456 quote! {
457 #deprecation_notice
458 #func_docs
459 #import_notice
460 #unstable_notice
461 }
462 };
463 quote! {
464 #func_doc
465 #func_decl;
466 }
467}
468
469fn expand_docs(def: &EnvDef) -> TokenStream2 {
471 let mut current_docs = std::collections::HashMap::new();
474 let mut funcs: Vec<_> = def.host_funcs.iter().filter(|f| f.alias_to.is_none()).collect();
475 funcs.sort_unstable_by_key(|func| Reverse(func.version));
476 for func in funcs {
477 if current_docs.contains_key(&func.name) {
478 continue;
479 }
480 current_docs.insert(func.name.clone(), expand_func_doc(&func));
481 }
482 let current_docs = current_docs.values();
483
484 let mut legacy_doc = std::collections::BTreeMap::<u8, Vec<TokenStream2>>::new();
487 for func in def.host_funcs.iter() {
488 legacy_doc.entry(func.version).or_default().push(expand_func_doc(&func));
489 }
490 let legacy_doc = legacy_doc.into_iter().map(|(version, funcs)| {
491 let doc = format!("All functions available in the **seal{}** module", version);
492 let version = Ident::new(&format!("Version{version}"), Span::call_site());
493 quote! {
494 #[doc = #doc]
495 pub trait #version {
496 #( #funcs )*
497 }
498 }
499 });
500
501 quote! {
502 pub trait Current {
514 #( #current_docs )*
515 }
516 #( #legacy_doc )*
517 }
518}
519
520fn expand_env(def: &EnvDef, docs: bool) -> TokenStream2 {
525 let impls = expand_impls(def);
526 let docs = docs.then(|| expand_docs(def)).unwrap_or(TokenStream2::new());
527 let stable_api_count = def.host_funcs.iter().filter(|f| f.is_stable).count();
528
529 quote! {
530 pub struct Env;
531
532 #[cfg(test)]
533 pub const STABLE_API_COUNT: usize = #stable_api_count;
534
535 #impls
536 #[cfg(doc)]
547 pub mod api_doc {
548 use super::{TrapReason, ReturnErrorCode};
549 #docs
550 }
551 }
552}
553
554fn expand_impls(def: &EnvDef) -> TokenStream2 {
558 let impls = expand_functions(def, ExpandMode::Impl);
559 let dummy_impls = expand_functions(def, ExpandMode::MockImpl);
560 let bench_impls = expand_functions(def, ExpandMode::BenchImpl);
561
562 quote! {
563 impl<'a, E: Ext> crate::wasm::Environment<crate::wasm::runtime::Runtime<'a, E>> for Env
564 {
565 fn define(
566 store: &mut ::wasmi::Store<crate::wasm::Runtime<E>>,
567 linker: &mut ::wasmi::Linker<crate::wasm::Runtime<E>>,
568 allow_unstable: AllowUnstableInterface,
569 allow_deprecated: AllowDeprecatedInterface,
570 ) -> Result<(),::wasmi::errors::LinkerError> {
571 #impls
572 Ok(())
573 }
574 }
575
576 #[cfg(feature = "runtime-benchmarks")]
577 pub struct BenchEnv<E>(::core::marker::PhantomData<E>);
578
579 #[cfg(feature = "runtime-benchmarks")]
580 impl<E: Ext> BenchEnv<E> {
581 #bench_impls
582 }
583
584 impl crate::wasm::Environment<()> for Env
585 {
586 fn define(
587 store: &mut ::wasmi::Store<()>,
588 linker: &mut ::wasmi::Linker<()>,
589 allow_unstable: AllowUnstableInterface,
590 allow_deprecated: AllowDeprecatedInterface,
591 ) -> Result<(), ::wasmi::errors::LinkerError> {
592 #dummy_impls
593 Ok(())
594 }
595 }
596 }
597}
598
599enum ExpandMode {
600 Impl,
601 BenchImpl,
602 MockImpl,
603}
604
605impl ExpandMode {
606 fn expand_blocks(&self) -> bool {
607 match *self {
608 ExpandMode::Impl | ExpandMode::BenchImpl => true,
609 ExpandMode::MockImpl => false,
610 }
611 }
612
613 fn host_state(&self) -> TokenStream2 {
614 match *self {
615 ExpandMode::Impl | ExpandMode::BenchImpl => quote! { crate::wasm::runtime::Runtime<E> },
616 ExpandMode::MockImpl => quote! { () },
617 }
618 }
619}
620
621fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 {
622 let impls = def.host_funcs.iter().map(|f| {
623 let params = f.item.sig.inputs.iter().skip(2);
625 let module = f.module();
626 let cfg = &f.cfg;
627 let name = &f.name;
628 let body = &f.item.block;
629 let wasm_output = f.returns.to_wasm_sig();
630 let output = &f.item.sig.output;
631 let is_stable = f.is_stable;
632 let not_deprecated = f.not_deprecated;
633
634 let wrapped_body_with_trace = {
637 let trace_fmt_args = params.clone().filter_map(|arg| match arg {
638 syn::FnArg::Receiver(_) => None,
639 syn::FnArg::Typed(p) => {
640 match *p.pat.clone() {
641 syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()),
642 _ => None,
643 }
644 },
645 });
646
647 let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::<Vec<_>>().join(", ");
648 let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);
649
650 quote! {
651 let result = #body;
652 if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
653 use core::fmt::Write;
654 let mut msg = alloc::string::String::default();
655 let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result);
656 ctx.ext().append_debug_buffer(&msg);
657 }
658 result
659 }
660 };
661
662 let expand_blocks = expand_mode.expand_blocks();
667 let inner = match expand_mode {
668 ExpandMode::Impl => {
669 quote! { || #output {
670 let (memory, ctx) = __caller__
671 .data()
672 .memory()
673 .expect("Memory must be set when setting up host data; qed")
674 .data_and_store_mut(&mut __caller__);
675 #wrapped_body_with_trace
676 } }
677 },
678 ExpandMode::BenchImpl => {
679 let body = &body.stmts;
680 quote!{
681 #(#body)*
682 }
683 },
684 ExpandMode::MockImpl => {
685 quote! { || -> #wasm_output {
686 ::core::unreachable!()
690 } }
691 },
692 };
693
694 let into_host = if expand_blocks {
695 quote! {
696 |reason| {
697 ::wasmi::Error::host(reason)
698 }
699 }
700 } else {
701 quote! {
702 |reason| { reason }
703 }
704 };
705 let allow_unused = if expand_blocks {
706 quote! { }
707 } else {
708 quote! { #[allow(unused_variables)] }
709 };
710 let sync_gas_before = if expand_blocks {
711 quote! {
712 let __gas_left_before__ = {
714 let fuel =
715 __caller__.get_fuel().expect("Fuel metering is enabled; qed");
716 __caller__
717 .data_mut()
718 .ext()
719 .gas_meter_mut()
720 .sync_from_executor(fuel)
721 .map_err(TrapReason::from)
722 .map_err(#into_host)?
723 };
724
725 __caller__.data_mut().charge_gas(crate::wasm::RuntimeCosts::HostFn)
727 .map_err(TrapReason::from)
728 .map_err(#into_host)?;
729 }
730 } else {
731 quote! { }
732 };
733 let sync_gas_after = if expand_blocks {
735 quote! {
736 let fuel = __caller__
737 .data_mut()
738 .ext()
739 .gas_meter_mut()
740 .sync_to_executor(__gas_left_before__)
741 .map_err(|err| {
742 let err = TrapReason::from(err);
743 wasmi::Error::host(err)
744 })?;
745 __caller__
746 .set_fuel(fuel.into())
747 .expect("Fuel metering is enabled; qed");
748 }
749 } else {
750 quote! { }
751 };
752
753 match expand_mode {
754 ExpandMode::BenchImpl => {
755 let name = Ident::new(&format!("{module}_{name}"), Span::call_site());
756 quote! {
757 pub fn #name(ctx: &mut crate::wasm::Runtime<E>, memory: &mut [u8], #(#params),*) #output {
758 #inner
759 }
760 }
761 },
762 _ => {
763 let host_state = expand_mode.host_state();
764 quote! {
765 #cfg
770 if ::core::cfg!(feature = "runtime-benchmarks") ||
771 ((#is_stable || __allow_unstable__) && (#not_deprecated || __allow_deprecated__))
772 {
773 #allow_unused
774 linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output {
775 #sync_gas_before
776 let mut func = #inner;
777 let result = func().map_err(#into_host).map(::core::convert::Into::into);
778 #sync_gas_after
779 result
780 }))?;
781 }
782 }
783 },
784 }
785 });
786
787 match expand_mode {
788 ExpandMode::BenchImpl => {
789 quote! {
790 #( #impls )*
791 }
792 },
793 _ => quote! {
794 let __allow_unstable__ = matches!(allow_unstable, AllowUnstableInterface::Yes);
795 let __allow_deprecated__ = matches!(allow_deprecated, AllowDeprecatedInterface::Yes);
796 #( #impls )*
797 },
798 }
799}
800
801#[proc_macro_attribute]
908pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream {
909 if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) {
910 let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attribute:
911 - `#[define_env]`
912 - `#[define_env(doc)]`"#;
913 let span = TokenStream2::from(attr).span();
914 return syn::Error::new(span, msg).to_compile_error().into();
915 }
916
917 let item = syn::parse_macro_input!(item as syn::ItemMod);
918
919 match EnvDef::try_from(item) {
920 Ok(mut def) => expand_env(&mut def, !attr.is_empty()).into(),
921 Err(e) => e.to_compile_error().into(),
922 }
923}