// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Procedural macros used in the contracts module. //! //! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides //! boilerplate of defining external environment for a wasm module. use core::cmp::Reverse; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, quote_spanned, ToTokens}; use syn::{ parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DeriveInput, Fields, FnArg, Ident, }; /// This derives `Debug` for a struct where each field must be of some numeric type. /// It interprets each field as its represents some weight and formats it as times so that /// it is readable by humans. #[proc_macro_derive(WeightDebug)] pub fn derive_weight_debug(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let data = if let Data::Struct(data) = &input.data { data } else { return quote_spanned! { name.span() => compile_error!("WeightDebug is only supported for structs."); } .into(); }; let fields = match &data.fields { Fields::Named(fields) => { let recurse = fields.named.iter().filter_map(|f| { let name = f.ident.as_ref()?; if name.to_string().starts_with('_') { return None; } let ret = quote_spanned! { f.span() => formatter.field(stringify!(#name), &HumanWeight(self.#name)); }; Some(ret) }); quote! { #( #recurse )* } }, Fields::Unnamed(fields) => quote_spanned! { fields.span() => compile_error!("Unnamed fields are not supported") }, Fields::Unit => quote!(), }; let tokens = quote! { impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause { fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> core::fmt::Result { use ::pezsp_runtime::{FixedPointNumber, FixedU128 as Fixed}; use ::core::{fmt, write}; struct HumanWeight(Weight); impl fmt::Debug for HumanWeight { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { if self.0.ref_time() > 1_000_000_000 { write!( formatter, "{} ms, {} bytes", Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000_000).into_inner() / Fixed::accuracy(), self.0.proof_size() ) } else if self.0.ref_time() > 1_000_000 { write!( formatter, "{} µs, {} bytes", Fixed::saturating_from_rational(self.0.ref_time(), 1_000_000).into_inner() / Fixed::accuracy(), self.0.proof_size() ) } else if self.0.ref_time() > 1_000 { write!( formatter, "{} ns, {} bytes", Fixed::saturating_from_rational(self.0.ref_time(), 1_000).into_inner() / Fixed::accuracy(), self.0.proof_size() ) } else { write!(formatter, "{} ps, {} bytes", self.0.ref_time(), self.0.proof_size()) } } } let mut formatter = formatter.debug_struct(stringify!(#name)); #fields formatter.finish() } } }; tokens.into() } /// Parsed environment definition. struct EnvDef { host_funcs: Vec, } /// Parsed host function definition. struct HostFn { item: syn::ItemFn, version: u8, name: String, returns: HostFnReturn, is_stable: bool, alias_to: Option, /// Formulating the predicate inverted makes the expression using it simpler. not_deprecated: bool, cfg: Option, } enum HostFnReturn { Unit, U32, U64, ReturnCode, } impl HostFnReturn { fn to_wasm_sig(&self) -> TokenStream2 { let ok = match self { Self::Unit => quote! { () }, Self::U32 | Self::ReturnCode => quote! { ::core::primitive::u32 }, Self::U64 => quote! { ::core::primitive::u64 }, }; quote! { ::core::result::Result<#ok, ::wasmi::Error> } } } impl ToTokens for HostFn { fn to_tokens(&self, tokens: &mut TokenStream2) { self.item.to_tokens(tokens); } } impl HostFn { pub fn try_from(mut item: syn::ItemFn) -> syn::Result { let err = |span, msg| { let msg = format!("Invalid host function definition.\n{}", msg); syn::Error::new(span, msg) }; // process attributes let msg = "Only #[version()], #[unstable], #[prefixed_alias], #[cfg], #[mutating] and #[deprecated] attributes are allowed."; let span = item.span(); let mut attrs = item.attrs.clone(); attrs.retain(|a| !a.path().is_ident("doc")); let mut maybe_version = None; let mut is_stable = true; let mut alias_to = None; let mut not_deprecated = true; let mut mutating = false; let mut cfg = None; while let Some(attr) = attrs.pop() { let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); match ident.as_str() { "version" => { if maybe_version.is_some() { return Err(err(span, "#[version] can only be specified once")); } maybe_version = Some(attr.parse_args::().and_then(|lit| lit.base10_parse())?); }, "unstable" => { if !is_stable { return Err(err(span, "#[unstable] can only be specified once")); } is_stable = false; }, "prefixed_alias" => { alias_to = Some(item.sig.ident.to_string()); item.sig.ident = syn::Ident::new( &format!("seal_{}", &item.sig.ident.to_string()), item.sig.ident.span(), ); }, "deprecated" => { if !not_deprecated { return Err(err(span, "#[deprecated] can only be specified once")); } not_deprecated = false; }, "mutating" => { if mutating { return Err(err(span, "#[mutating] can only be specified once")); } mutating = true; }, "cfg" => { if cfg.is_some() { return Err(err(span, "#[cfg] can only be specified once")); } cfg = Some(attr); }, id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), } } if mutating { let stmt = syn::parse_quote! { if ctx.ext().is_read_only() { return Err(Error::::StateChangeDenied.into()); } }; item.block.stmts.insert(0, stmt); } let name = item.sig.ident.to_string(); if !(is_stable || not_deprecated) { return Err(err(span, "#[deprecated] is mutually exclusive with #[unstable]")); } // process arguments: The first and second args are treated differently (ctx, memory) // they must exist and be `ctx: _` and `memory: _`. let msg = "Every function must start with two inferred parameters: ctx: _ and memory: _"; let special_args = item .sig .inputs .iter() .take(2) .enumerate() .map(|(i, arg)| is_valid_special_arg(i, arg)) .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); if special_args != 2 { return Err(err(span, msg)); } // process return type let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result, - Result, - Result"#; let ret_ty = match item.clone().sig.output { syn::ReturnType::Type(_, ty) => Ok(ty.clone()), _ => Err(err(span, &msg)), }?; match *ret_ty { syn::Type::Path(tp) => { let result = &tp.path.segments.last().ok_or(err(span, &msg))?; let (id, span) = (result.ident.to_string(), result.ident.span()); id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; match &result.arguments { syn::PathArguments::AngleBracketed(group) => { if group.args.len() != 2 { return Err(err(span, &msg)); }; let arg2 = group.args.last().ok_or(err(span, &msg))?; let err_ty = match arg2 { syn::GenericArgument::Type(ty) => Ok(ty.clone()), _ => Err(err(arg2.span(), &msg)), }?; match err_ty { syn::Type::Path(tp) => Ok(tp .path .segments .first() .ok_or(err(arg2.span(), &msg))? .ident .to_string()), _ => Err(err(tp.span(), &msg)), }? .eq("TrapReason") .then_some(()) .ok_or(err(span, &msg))?; let arg1 = group.args.first().ok_or(err(span, &msg))?; let ok_ty = match arg1 { syn::GenericArgument::Type(ty) => Ok(ty.clone()), _ => Err(err(arg1.span(), &msg)), }?; let ok_ty_str = match ok_ty { syn::Type::Path(tp) => Ok(tp .path .segments .first() .ok_or(err(arg1.span(), &msg))? .ident .to_string()), syn::Type::Tuple(tt) => { if !tt.elems.is_empty() { return Err(err(arg1.span(), &msg)); }; Ok("()".to_string()) }, _ => Err(err(ok_ty.span(), &msg)), }?; let returns = match ok_ty_str.as_str() { "()" => Ok(HostFnReturn::Unit), "u32" => Ok(HostFnReturn::U32), "u64" => Ok(HostFnReturn::U64), "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), _ => Err(err(arg1.span(), &msg)), }?; Ok(Self { item, version: maybe_version.unwrap_or_default(), name, returns, is_stable, alias_to, not_deprecated, cfg, }) }, _ => Err(err(span, &msg)), } }, _ => Err(err(span, &msg)), } } fn module(&self) -> String { format!("seal{}", self.version) } } impl EnvDef { pub fn try_from(item: syn::ItemMod) -> syn::Result { let span = item.span(); let err = |msg| syn::Error::new(span, msg); let items = &item .content .as_ref() .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? .1; let extract_fn = |i: &syn::Item| match i { syn::Item::Fn(i_fn) => Some(i_fn.clone()), _ => None, }; let selector = |a: &syn::Attribute| a.path().is_ident("prefixed_alias"); let aliases = items .iter() .filter_map(extract_fn) .filter(|i| i.attrs.iter().any(selector)) .map(|i| HostFn::try_from(i)); let host_funcs = items .iter() .filter_map(extract_fn) .map(|mut i| { i.attrs.retain(|i| !selector(i)); i }) .map(|i| HostFn::try_from(i)) .chain(aliases) .collect::, _>>()?; Ok(Self { host_funcs }) } } fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { let FnArg::Typed(pat) = arg else { return false }; let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; let name_ok = match idx { 0 => ident == "ctx" || ident == "_ctx", 1 => ident == "memory" || ident == "_memory", _ => false, }; if !name_ok { return false; } matches!(*pat.ty, syn::Type::Infer(_)) } fn expand_func_doc(func: &HostFn) -> TokenStream2 { // Remove auxiliary args: `ctx: _` and `memory: _` let func_decl = { let mut sig = func.item.sig.clone(); sig.inputs = sig .inputs .iter() .skip(2) .map(|p| p.clone()) .collect::>(); sig.to_token_stream() }; let func_doc = { let func_docs = if let Some(origin_fn) = &func.alias_to { let alias_doc = format!( "This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.", origin_fn, ); quote! { #[doc = #alias_doc] } } else { let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { let docs = d.to_token_stream(); quote! { #docs } }); quote! { #( #docs )* } }; let deprecation_notice = if !func.not_deprecated { let warning = "\n # Deprecated\n\n \ This function is deprecated and will be removed in future versions.\n \ No new code or contracts with this API can be deployed."; quote! { #[doc = #warning] } } else { quote! {} }; let import_notice = { let info = format!( "\n# Wasm Import Statement\n```wat\n(import \"seal{}\" \"{}\" (func ...))\n```", func.version, func.name, ); quote! { #[doc = #info] } }; let unstable_notice = if !func.is_stable { let warning = "\n # Unstable\n\n \ This function is unstable and it is a subject to change (or removal) in the future.\n \ Do not deploy a contract using it to a production chain."; quote! { #[doc = #warning] } } else { quote! {} }; quote! { #deprecation_notice #func_docs #import_notice #unstable_notice } }; quote! { #func_doc #func_decl; } } /// Expands documentation for host functions. fn expand_docs(def: &EnvDef) -> TokenStream2 { // Create the `Current` trait with only the newest versions // we sort so that only the newest versions make it into `docs` let mut current_docs = std::collections::HashMap::new(); let mut funcs: Vec<_> = def.host_funcs.iter().filter(|f| f.alias_to.is_none()).collect(); funcs.sort_unstable_by_key(|func| Reverse(func.version)); for func in funcs { if current_docs.contains_key(&func.name) { continue; } current_docs.insert(func.name.clone(), expand_func_doc(&func)); } let current_docs = current_docs.values(); // Create the `legacy` module with all functions // Maps from version to list of functions that have this version let mut legacy_doc = std::collections::BTreeMap::>::new(); for func in def.host_funcs.iter() { legacy_doc.entry(func.version).or_default().push(expand_func_doc(&func)); } let legacy_doc = legacy_doc.into_iter().map(|(version, funcs)| { let doc = format!("All functions available in the **seal{}** module", version); let version = Ident::new(&format!("Version{version}"), Span::call_site()); quote! { #[doc = #doc] pub trait #version { #( #funcs )* } } }); quote! { /// Contains only the latest version of each function. /// /// In reality there are more functions available but they are all obsolete: When a function /// is updated a new **version** is added and the old versions stays available as-is. /// We only list the newest version here. Some functions are available under additional /// names (aliases) for historic reasons which are omitted here. /// /// If you want an overview of all the functions available to a contact all you need /// to look at is this trait. It contains only the latest version of each /// function and no aliases. If you are writing a contract(language) from scratch /// this is where you should look at. pub trait Current { #( #current_docs )* } #( #legacy_doc )* } } /// Expands environment definition. /// Should generate source code for: /// - implementations of the host functions to be added to the wasm runtime environment (see /// `expand_impls()`). fn expand_env(def: &EnvDef, docs: bool) -> TokenStream2 { let impls = expand_impls(def); let docs = docs.then(|| expand_docs(def)).unwrap_or(TokenStream2::new()); let stable_api_count = def.host_funcs.iter().filter(|f| f.is_stable).count(); quote! { pub struct Env; #[cfg(test)] pub const STABLE_API_COUNT: usize = #stable_api_count; #impls /// Documentation of the API (host functions) available to contracts. /// /// The `Current` trait might be the most useful doc to look at. The versioned /// traits only exist for reference: If trying to find out if a specific version of /// `pezpallet-contracts` contains a certain function. /// /// # Note /// /// This module is not meant to be used by any code. Rather, it is meant to be /// consumed by humans through rustdoc. #[cfg(doc)] pub mod api_doc { use super::{TrapReason, ReturnErrorCode}; #docs } } } /// Generates for every host function: /// - real implementation, to register it in the contract execution environment; /// - dummy implementation, to be used as mocks for contract validation step. fn expand_impls(def: &EnvDef) -> TokenStream2 { let impls = expand_functions(def, ExpandMode::Impl); let dummy_impls = expand_functions(def, ExpandMode::MockImpl); let bench_impls = expand_functions(def, ExpandMode::BenchImpl); quote! { impl<'a, E: Ext> crate::wasm::Environment> for Env { fn define( store: &mut ::wasmi::Store>, linker: &mut ::wasmi::Linker>, allow_unstable: AllowUnstableInterface, allow_deprecated: AllowDeprecatedInterface, ) -> Result<(),::wasmi::errors::LinkerError> { #impls Ok(()) } } #[cfg(feature = "runtime-benchmarks")] pub struct BenchEnv(::core::marker::PhantomData); #[cfg(feature = "runtime-benchmarks")] impl BenchEnv { #bench_impls } impl crate::wasm::Environment<()> for Env { fn define( store: &mut ::wasmi::Store<()>, linker: &mut ::wasmi::Linker<()>, allow_unstable: AllowUnstableInterface, allow_deprecated: AllowDeprecatedInterface, ) -> Result<(), ::wasmi::errors::LinkerError> { #dummy_impls Ok(()) } } } } enum ExpandMode { Impl, BenchImpl, MockImpl, } impl ExpandMode { fn expand_blocks(&self) -> bool { match *self { ExpandMode::Impl | ExpandMode::BenchImpl => true, ExpandMode::MockImpl => false, } } fn host_state(&self) -> TokenStream2 { match *self { ExpandMode::Impl | ExpandMode::BenchImpl => quote! { crate::wasm::runtime::Runtime }, ExpandMode::MockImpl => quote! { () }, } } } fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 { let impls = def.host_funcs.iter().map(|f| { // skip the context and memory argument let params = f.item.sig.inputs.iter().skip(2); let module = f.module(); let cfg = &f.cfg; let name = &f.name; let body = &f.item.block; let wasm_output = f.returns.to_wasm_sig(); let output = &f.item.sig.output; let is_stable = f.is_stable; let not_deprecated = f.not_deprecated; // wrapped host function body call with host function traces // see https://github.com/pezkuwichain/pezkuwi-sdk/tree/main/bizinikiwi/pezframe/contracts#host-function-tracing let wrapped_body_with_trace = { let trace_fmt_args = params.clone().filter_map(|arg| match arg { syn::FnArg::Receiver(_) => None, syn::FnArg::Typed(p) => { match *p.pat.clone() { syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()), _ => None, } }, }); let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::>().join(", "); let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str); quote! { let result = #body; if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) { use core::fmt::Write; let mut msg = alloc::string::String::default(); let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); ctx.ext().append_debug_buffer(&msg); } result } }; // If we don't expand blocks (implementing for `()`) we change a few things: // - We replace any code by unreachable! // - Allow unused variables as the code that uses is not expanded // - We don't need to map the error as we simply panic if they code would ever be executed let expand_blocks = expand_mode.expand_blocks(); let inner = match expand_mode { ExpandMode::Impl => { quote! { || #output { let (memory, ctx) = __caller__ .data() .memory() .expect("Memory must be set when setting up host data; qed") .data_and_store_mut(&mut __caller__); #wrapped_body_with_trace } } }, ExpandMode::BenchImpl => { let body = &body.stmts; quote!{ #(#body)* } }, ExpandMode::MockImpl => { quote! { || -> #wasm_output { // This is part of the implementation for `Environment<()>` which is not // meant to be actually executed. It is only for validation which will // never call host functions. ::core::unreachable!() } } }, }; let into_host = if expand_blocks { quote! { |reason| { ::wasmi::Error::host(reason) } } } else { quote! { |reason| { reason } } }; let allow_unused = if expand_blocks { quote! { } } else { quote! { #[allow(unused_variables)] } }; let sync_gas_before = if expand_blocks { quote! { // Write gas from wasmi into pezpallet-contracts before entering the host function. let __gas_left_before__ = { let fuel = __caller__.get_fuel().expect("Fuel metering is enabled; qed"); __caller__ .data_mut() .ext() .gas_meter_mut() .sync_from_executor(fuel) .map_err(TrapReason::from) .map_err(#into_host)? }; // Charge gas for host function execution. __caller__.data_mut().charge_gas(crate::wasm::RuntimeCosts::HostFn) .map_err(TrapReason::from) .map_err(#into_host)?; } } else { quote! { } }; // Write gas from pezpallet-contracts into wasmi after leaving the host function. let sync_gas_after = if expand_blocks { quote! { let fuel = __caller__ .data_mut() .ext() .gas_meter_mut() .sync_to_executor(__gas_left_before__) .map_err(|err| { let err = TrapReason::from(err); wasmi::Error::host(err) })?; __caller__ .set_fuel(fuel.into()) .expect("Fuel metering is enabled; qed"); } } else { quote! { } }; match expand_mode { ExpandMode::BenchImpl => { let name = Ident::new(&format!("{module}_{name}"), Span::call_site()); quote! { pub fn #name(ctx: &mut crate::wasm::Runtime, memory: &mut [u8], #(#params),*) #output { #inner } } }, _ => { let host_state = expand_mode.host_state(); quote! { // We need to allow all interfaces when runtime benchmarks are performed because // we generate the weights even when those interfaces are not enabled. This // is necessary as the decision whether we allow unstable or deprecated functions // is a decision made at runtime. Generation of the weights happens statically. #cfg if ::core::cfg!(feature = "runtime-benchmarks") || ((#is_stable || __allow_unstable__) && (#not_deprecated || __allow_deprecated__)) { #allow_unused linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output { #sync_gas_before let mut func = #inner; let result = func().map_err(#into_host).map(::core::convert::Into::into); #sync_gas_after result }))?; } } }, } }); match expand_mode { ExpandMode::BenchImpl => { quote! { #( #impls )* } }, _ => quote! { let __allow_unstable__ = matches!(allow_unstable, AllowUnstableInterface::Yes); let __allow_deprecated__ = matches!(allow_deprecated, AllowDeprecatedInterface::Yes); #( #impls )* }, } } /// Defines a host functions set that can be imported by contract wasm code. /// /// **NB**: Be advised that all functions defined by this macro /// will panic if called with unexpected arguments. /// /// It's up to you as the user of this macro to check signatures of wasm code to be executed /// and reject the code if any imported function has a mismatched signature. /// /// ## Example /// /// ```nocompile /// #[define_env] /// pub mod some_env { /// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` /// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is /// because the module `seal0` is the default when no module is specified. /// /// To define a host function in `seal2` and `seal3` modules, it should be annotated with the /// appropriate attribute as follows: /// /// ## Example /// /// ```nocompile /// #[define_env] /// pub mod some_env { /// #[version(2)] /// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// /// #[version(3)] /// #[unstable] /// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` /// The function `bar` is additionally annotated with `unstable` which removes it from the stable /// interface. Check out the README to learn about unstable functions. /// /// In legacy versions of pezpallet_contracts, it was a naming convention that all host functions /// had to be named with the `seal_` prefix. For the sake of backwards compatibility, each host /// function now can get a such prefix-named alias function generated by marking it by the /// `#[prefixed_alias]` attribute: /// /// ## Example /// /// ```nocompile /// #[define_env] /// pub mod some_env { /// #[version(1)] /// #[prefixed_alias] /// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// /// #[version(42)] /// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { /// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) /// } /// } /// ``` /// /// In this example, the following host functions will be generated by the macro: /// - `foo()` in module `seal1`, /// - `seal_foo()` in module `seal1`, /// - `bar()` in module `seal42`. /// /// Only following return types are allowed for the host functions defined with the macro: /// - `Result<(), TrapReason>`, /// - `Result`, /// - `Result`. /// /// The macro expands to `pub struct Env` declaration, with the following traits implementations: /// - `pezpallet_contracts::wasm::Environment> where E: Ext` /// - `pezpallet_contracts::wasm::Environment<()>` /// /// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful /// when only checking whether a code can be instantiated without actually executing any code. /// /// # Generating Documentation /// /// Passing `doc` attribute to the macro (like `#[define_env(doc)]`) will make it also expand /// additional `pezpallet_contracts::api_doc::seal0`, `pezpallet_contracts::api_doc::seal1`, /// `...` modules each having its `Api` trait containing functions holding documentation for every /// host function defined by the macro. /// /// # Deprecated Interfaces /// /// An interface can be annotated with `#[deprecated]`. It is mutually exclusive with `#[unstable]`. /// Deprecated interfaces have the following properties: /// - New contract codes utilizing those interfaces cannot be uploaded. /// - New contracts from existing codes utilizing those interfaces cannot be instantiated. /// - Existing contracts containing those interfaces still work. /// /// Those interfaces will eventually be removed. /// /// To build up these docs, run: /// /// ```nocompile /// cargo doc /// ``` #[proc_macro_attribute] pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) { let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attribute: - `#[define_env]` - `#[define_env(doc)]`"#; let span = TokenStream2::from(attr).span(); return syn::Error::new(span, msg).to_compile_error().into(); } let item = syn::parse_macro_input!(item as syn::ItemMod); match EnvDef::try_from(item) { Ok(mut def) => expand_env(&mut def, !attr.is_empty()).into(), Err(e) => e.to_compile_error().into(), } }