referrerpolicy=no-referrer-when-downgrade

pallet_contracts_proc_macro/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Procedural macros used in the contracts module.
19//!
20//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides
21//! boilerplate of defining external environment for a wasm module.
22
23use 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/// This derives `Debug` for a struct where each field must be of some numeric type.
33/// It interprets each field as its represents some weight and formats it as times so that
34/// it is readable by humans.
35#[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
120/// Parsed environment definition.
121struct EnvDef {
122	host_funcs: Vec<HostFn>,
123}
124
125/// Parsed host function definition.
126struct HostFn {
127	item: syn::ItemFn,
128	version: u8,
129	name: String,
130	returns: HostFnReturn,
131	is_stable: bool,
132	alias_to: Option<String>,
133	/// Formulating the predicate inverted makes the expression using it simpler.
134	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		// process attributes
172		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		// process arguments: The first and second args are treated differently (ctx, memory)
244		// they must exist and be `ctx: _` and `memory: _`.
245		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		// process return type
260		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 = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false };
395	let name_ok = match idx {
396		0 => ident == "ctx" || ident == "_ctx",
397		1 => ident == "memory" || ident == "_memory",
398		_ => false,
399	};
400	if !name_ok {
401		return false
402	}
403	matches!(*pat.ty, syn::Type::Infer(_))
404}
405
406fn expand_func_doc(func: &HostFn) -> TokenStream2 {
407	// Remove auxiliary args: `ctx: _` and `memory: _`
408	let func_decl = {
409		let mut sig = func.item.sig.clone();
410		sig.inputs = sig
411			.inputs
412			.iter()
413			.skip(2)
414			.map(|p| p.clone())
415			.collect::<Punctuated<FnArg, Comma>>();
416		sig.to_token_stream()
417	};
418	let func_doc = {
419		let func_docs = if let Some(origin_fn) = &func.alias_to {
420			let alias_doc = format!(
421				"This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.",
422				origin_fn,
423			);
424			quote! { #[doc = #alias_doc] }
425		} else {
426			let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| {
427				let docs = d.to_token_stream();
428				quote! { #docs }
429			});
430			quote! { #( #docs )* }
431		};
432		let deprecation_notice = if !func.not_deprecated {
433			let warning = "\n # Deprecated\n\n \
434								This function is deprecated and will be removed in future versions.\n \
435								No new code or contracts with this API can be deployed.";
436			quote! { #[doc = #warning] }
437		} else {
438			quote! {}
439		};
440		let import_notice = {
441			let info = format!(
442				"\n# Wasm Import Statement\n```wat\n(import \"seal{}\" \"{}\" (func ...))\n```",
443				func.version, func.name,
444			);
445			quote! { #[doc = #info] }
446		};
447		let unstable_notice = if !func.is_stable {
448			let warning = "\n # Unstable\n\n \
449								This function is unstable and it is a subject to change (or removal) in the future.\n \
450								Do not deploy a contract using it to a production chain.";
451			quote! { #[doc = #warning] }
452		} else {
453			quote! {}
454		};
455		quote! {
456			#deprecation_notice
457			#func_docs
458			#import_notice
459			#unstable_notice
460		}
461	};
462	quote! {
463		#func_doc
464		#func_decl;
465	}
466}
467
468/// Expands documentation for host functions.
469fn expand_docs(def: &EnvDef) -> TokenStream2 {
470	// Create the `Current` trait with only the newest versions
471	// we sort so that only the newest versions make it into `docs`
472	let mut current_docs = std::collections::HashMap::new();
473	let mut funcs: Vec<_> = def.host_funcs.iter().filter(|f| f.alias_to.is_none()).collect();
474	funcs.sort_unstable_by_key(|func| Reverse(func.version));
475	for func in funcs {
476		if current_docs.contains_key(&func.name) {
477			continue
478		}
479		current_docs.insert(func.name.clone(), expand_func_doc(&func));
480	}
481	let current_docs = current_docs.values();
482
483	// Create the `legacy` module with all functions
484	// Maps from version to list of functions that have this version
485	let mut legacy_doc = std::collections::BTreeMap::<u8, Vec<TokenStream2>>::new();
486	for func in def.host_funcs.iter() {
487		legacy_doc.entry(func.version).or_default().push(expand_func_doc(&func));
488	}
489	let legacy_doc = legacy_doc.into_iter().map(|(version, funcs)| {
490		let doc = format!("All functions available in the **seal{}** module", version);
491		let version = Ident::new(&format!("Version{version}"), Span::call_site());
492		quote! {
493			#[doc = #doc]
494			pub trait #version {
495				#( #funcs )*
496			}
497		}
498	});
499
500	quote! {
501		/// Contains only the latest version of each function.
502		///
503		/// In reality there are more functions available but they are all obsolete: When a function
504		/// is updated a new **version** is added and the old versions stays available as-is.
505		/// We only list the newest version here. Some functions are available under additional
506		/// names (aliases) for historic reasons which are omitted here.
507		///
508		/// If you want an overview of all the functions available to a contact all you need
509		/// to look at is this trait. It contains only the latest version of each
510		/// function and no aliases. If you are writing a contract(language) from scratch
511		/// this is where you should look at.
512		pub trait Current {
513			#( #current_docs )*
514		}
515		#( #legacy_doc )*
516	}
517}
518
519/// Expands environment definition.
520/// Should generate source code for:
521///  - implementations of the host functions to be added to the wasm runtime environment (see
522///    `expand_impls()`).
523fn expand_env(def: &EnvDef, docs: bool) -> TokenStream2 {
524	let impls = expand_impls(def);
525	let docs = docs.then(|| expand_docs(def)).unwrap_or(TokenStream2::new());
526	let stable_api_count = def.host_funcs.iter().filter(|f| f.is_stable).count();
527
528	quote! {
529		pub struct Env;
530
531		#[cfg(test)]
532		pub const STABLE_API_COUNT: usize = #stable_api_count;
533
534		#impls
535		/// Documentation of the API (host functions) available to contracts.
536		///
537		/// The `Current` trait might be the most useful doc to look at. The versioned
538		/// traits only exist for reference: If trying to find out if a specific version of
539		/// `pallet-contracts` contains a certain function.
540		///
541		/// # Note
542		///
543		/// This module is not meant to be used by any code. Rather, it is meant to be
544		/// consumed by humans through rustdoc.
545		#[cfg(doc)]
546		pub mod api_doc {
547			use super::{TrapReason, ReturnErrorCode};
548			#docs
549		}
550	}
551}
552
553/// Generates for every host function:
554///   - real implementation, to register it in the contract execution environment;
555///   - dummy implementation, to be used as mocks for contract validation step.
556fn expand_impls(def: &EnvDef) -> TokenStream2 {
557	let impls = expand_functions(def, ExpandMode::Impl);
558	let dummy_impls = expand_functions(def, ExpandMode::MockImpl);
559	let bench_impls = expand_functions(def, ExpandMode::BenchImpl);
560
561	quote! {
562		impl<'a, E: Ext> crate::wasm::Environment<crate::wasm::runtime::Runtime<'a, E>> for Env
563		{
564			fn define(
565				store: &mut ::wasmi::Store<crate::wasm::Runtime<E>>,
566				linker: &mut ::wasmi::Linker<crate::wasm::Runtime<E>>,
567				allow_unstable: AllowUnstableInterface,
568				allow_deprecated: AllowDeprecatedInterface,
569			) -> Result<(),::wasmi::errors::LinkerError> {
570				#impls
571				Ok(())
572			}
573		}
574
575		#[cfg(feature = "runtime-benchmarks")]
576		pub struct BenchEnv<E>(::core::marker::PhantomData<E>);
577
578		#[cfg(feature = "runtime-benchmarks")]
579		impl<E: Ext> BenchEnv<E> {
580			#bench_impls
581		}
582
583		impl crate::wasm::Environment<()> for Env
584		{
585			fn define(
586				store: &mut ::wasmi::Store<()>,
587				linker: &mut ::wasmi::Linker<()>,
588				allow_unstable: AllowUnstableInterface,
589				allow_deprecated: AllowDeprecatedInterface,
590			) -> Result<(), ::wasmi::errors::LinkerError> {
591				#dummy_impls
592				Ok(())
593			}
594		}
595	}
596}
597
598enum ExpandMode {
599	Impl,
600	BenchImpl,
601	MockImpl,
602}
603
604impl ExpandMode {
605	fn expand_blocks(&self) -> bool {
606		match *self {
607			ExpandMode::Impl | ExpandMode::BenchImpl => true,
608			ExpandMode::MockImpl => false,
609		}
610	}
611
612	fn host_state(&self) -> TokenStream2 {
613		match *self {
614			ExpandMode::Impl | ExpandMode::BenchImpl => quote! { crate::wasm::runtime::Runtime<E> },
615			ExpandMode::MockImpl => quote! { () },
616		}
617	}
618}
619
620fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 {
621	let impls = def.host_funcs.iter().map(|f| {
622		// skip the context and memory argument
623		let params = f.item.sig.inputs.iter().skip(2);
624		let module = f.module();
625		let cfg = &f.cfg;
626		let name = &f.name;
627		let body = &f.item.block;
628		let wasm_output = f.returns.to_wasm_sig();
629		let output = &f.item.sig.output;
630		let is_stable = f.is_stable;
631		let not_deprecated = f.not_deprecated;
632
633		// wrapped host function body call with host function traces
634		// see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/contracts#host-function-tracing
635		let wrapped_body_with_trace = {
636			let trace_fmt_args = params.clone().filter_map(|arg| match arg {
637				syn::FnArg::Receiver(_) => None,
638				syn::FnArg::Typed(p) => {
639					match *p.pat.clone() {
640						syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()),
641						_ => None,
642					}
643				},
644			});
645
646			let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::<Vec<_>>().join(", ");
647			let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str);
648
649			quote! {
650				let result = #body;
651				if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) {
652						use core::fmt::Write;
653						let mut msg = alloc::string::String::default();
654						let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result);
655						ctx.ext().append_debug_buffer(&msg);
656				}
657				result
658			}
659		};
660
661		// If we don't expand blocks (implementing for `()`) we change a few things:
662		// - We replace any code by unreachable!
663		// - Allow unused variables as the code that uses is not expanded
664		// - We don't need to map the error as we simply panic if they code would ever be executed
665		let expand_blocks = expand_mode.expand_blocks();
666		let inner = match expand_mode {
667			ExpandMode::Impl => {
668				quote! { || #output {
669					let (memory, ctx) = __caller__
670						.data()
671						.memory()
672						.expect("Memory must be set when setting up host data; qed")
673						.data_and_store_mut(&mut __caller__);
674					#wrapped_body_with_trace
675				} }
676			},
677			ExpandMode::BenchImpl => {
678				let body = &body.stmts;
679				quote!{
680					#(#body)*
681				}
682			},
683			ExpandMode::MockImpl => {
684				quote! { || -> #wasm_output {
685					// This is part of the implementation for `Environment<()>` which is not
686					// meant to be actually executed. It is only for validation which will
687					// never call host functions.
688					::core::unreachable!()
689				} }
690			},
691		};
692
693		let into_host = if expand_blocks {
694			quote! {
695				|reason| {
696					::wasmi::Error::host(reason)
697				}
698			}
699		} else {
700			quote! {
701				|reason| { reason }
702			}
703		};
704		let allow_unused =  if expand_blocks {
705			quote! { }
706		} else {
707			quote! { #[allow(unused_variables)] }
708		};
709		let sync_gas_before = if expand_blocks {
710			quote! {
711				// Write gas from wasmi into pallet-contracts before entering the host function.
712				let __gas_left_before__ = {
713					let fuel =
714						__caller__.get_fuel().expect("Fuel metering is enabled; qed");
715					__caller__
716						.data_mut()
717						.ext()
718						.gas_meter_mut()
719						.sync_from_executor(fuel)
720						.map_err(TrapReason::from)
721						.map_err(#into_host)?
722				};
723
724				// Charge gas for host function execution.
725				__caller__.data_mut().charge_gas(crate::wasm::RuntimeCosts::HostFn)
726						.map_err(TrapReason::from)
727						.map_err(#into_host)?;
728			}
729		} else {
730			quote! { }
731		};
732		// Write gas from pallet-contracts into wasmi after leaving the host function.
733		let sync_gas_after = if expand_blocks {
734			quote! {
735				let fuel = __caller__
736					.data_mut()
737					.ext()
738					.gas_meter_mut()
739					.sync_to_executor(__gas_left_before__)
740					.map_err(|err| {
741						let err = TrapReason::from(err);
742						wasmi::Error::host(err)
743					})?;
744				 __caller__
745					 .set_fuel(fuel.into())
746					 .expect("Fuel metering is enabled; qed");
747			}
748		} else {
749			quote! { }
750		};
751
752		match expand_mode {
753			ExpandMode::BenchImpl => {
754				let name = Ident::new(&format!("{module}_{name}"), Span::call_site());
755				quote! {
756					pub fn #name(ctx: &mut crate::wasm::Runtime<E>, memory: &mut [u8], #(#params),*) #output {
757						#inner
758					}
759				}
760			},
761			_ => {
762				let host_state = expand_mode.host_state();
763				quote! {
764					// We need to allow all interfaces when runtime benchmarks are performed because
765					// we generate the weights even when those interfaces are not enabled. This
766					// is necessary as the decision whether we allow unstable or deprecated functions
767					// is a decision made at runtime. Generation of the weights happens statically.
768					#cfg
769					if ::core::cfg!(feature = "runtime-benchmarks") ||
770						((#is_stable || __allow_unstable__) && (#not_deprecated || __allow_deprecated__))
771					{
772						#allow_unused
773						linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output {
774							#sync_gas_before
775							let mut func = #inner;
776							let result = func().map_err(#into_host).map(::core::convert::Into::into);
777							#sync_gas_after
778							result
779						}))?;
780					}
781				}
782			},
783		}
784	});
785
786	match expand_mode {
787		ExpandMode::BenchImpl => {
788			quote! {
789			 #( #impls )*
790			}
791		},
792		_ => quote! {
793			let __allow_unstable__ = matches!(allow_unstable, AllowUnstableInterface::Yes);
794			let __allow_deprecated__ = matches!(allow_deprecated, AllowDeprecatedInterface::Yes);
795			#( #impls )*
796		},
797	}
798}
799
800/// Defines a host functions set that can be imported by contract wasm code.
801///
802/// **NB**: Be advised that all functions defined by this macro
803/// will panic if called with unexpected arguments.
804///
805/// It's up to you as the user of this macro to check signatures of wasm code to be executed
806/// and reject the code if any imported function has a mismatched signature.
807///
808/// ## Example
809///
810/// ```nocompile
811/// #[define_env]
812/// pub mod some_env {
813/// 	fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> {
814/// 		ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
815/// 	}
816/// }
817/// ```
818/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is
819/// because the module `seal0` is the default when no module is specified.
820///
821/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the
822/// appropriate attribute as follows:
823///
824/// ## Example
825///
826/// ```nocompile
827/// #[define_env]
828/// pub mod some_env {
829/// 	#[version(2)]
830/// 	fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnErrorCode, TrapReason> {
831/// 		ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
832/// 	}
833///
834/// 	#[version(3)]
835/// 	#[unstable]
836/// 	fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
837/// 		ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
838/// 	}
839/// }
840/// ```
841/// The function `bar` is additionally annotated with `unstable` which removes it from the stable
842/// interface. Check out the README to learn about unstable functions.
843///
844/// In legacy versions of pallet_contracts, it was a naming convention that all host functions had
845/// to be named with the `seal_` prefix. For the sake of backwards compatibility, each host function
846/// now can get a such prefix-named alias function generated by marking it by the
847/// `#[prefixed_alias]` attribute:
848///
849/// ## Example
850///
851/// ```nocompile
852/// #[define_env]
853/// pub mod some_env {
854/// 	#[version(1)]
855/// 	#[prefixed_alias]
856/// 	fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<ReturnErrorCode, TrapReason> {
857/// 		ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
858/// 	}
859///
860/// 	#[version(42)]
861/// 	fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<u32, TrapReason> {
862/// 		ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ())
863/// 	}
864/// }
865/// ```
866///
867/// In this example, the following host functions will be generated by the macro:
868/// - `foo()` in module `seal1`,
869/// - `seal_foo()` in module `seal1`,
870/// - `bar()` in module `seal42`.
871///
872/// Only following return types are allowed for the host functions defined with the macro:
873/// - `Result<(), TrapReason>`,
874/// - `Result<ReturnErrorCode, TrapReason>`,
875/// - `Result<u32, TrapReason>`.
876///
877/// The macro expands to `pub struct Env` declaration, with the following traits implementations:
878/// - `pallet_contracts::wasm::Environment<Runtime<E>> where E: Ext`
879/// - `pallet_contracts::wasm::Environment<()>`
880///
881/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful
882/// when only checking whether a code can be instantiated without actually executing any code.
883///
884/// # Generating Documentation
885///
886/// Passing `doc` attribute to the macro (like `#[define_env(doc)]`) will make it also expand
887/// additional `pallet_contracts::api_doc::seal0`, `pallet_contracts::api_doc::seal1`,
888/// `...` modules each having its `Api` trait containing functions holding documentation for every
889/// host function defined by the macro.
890///
891/// # Deprecated Interfaces
892///
893/// An interface can be annotated with `#[deprecated]`. It is mutually exclusive with `#[unstable]`.
894/// Deprecated interfaces have the following properties:
895/// 	- New contract codes utilizing those interfaces cannot be uploaded.
896/// 	- New contracts from existing codes utilizing those interfaces cannot be instantiated.
897/// - Existing contracts containing those interfaces still work.
898///
899/// Those interfaces will eventually be removed.
900///
901/// To build up these docs, run:
902///
903/// ```nocompile
904/// cargo doc
905/// ```
906#[proc_macro_attribute]
907pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream {
908	if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) {
909		let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attribute:
910					 - `#[define_env]`
911					 - `#[define_env(doc)]`"#;
912		let span = TokenStream2::from(attr).span();
913		return syn::Error::new(span, msg).to_compile_error().into()
914	}
915
916	let item = syn::parse_macro_input!(item as syn::ItemMod);
917
918	match EnvDef::try_from(item) {
919		Ok(mut def) => expand_env(&mut def, !attr.is_empty()).into(),
920		Err(e) => e.to_compile_error().into(),
921	}
922}