feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "pezpallet-contracts-proc-macro"
|
||||
version = "18.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Procedural macros used in pallet_contracts"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { features = ["full"], workspace = true }
|
||||
@@ -0,0 +1,922 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// 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<HostFn>,
|
||||
}
|
||||
|
||||
/// Parsed host function definition.
|
||||
struct HostFn {
|
||||
item: syn::ItemFn,
|
||||
version: u8,
|
||||
name: String,
|
||||
returns: HostFnReturn,
|
||||
is_stable: bool,
|
||||
alias_to: Option<String>,
|
||||
/// Formulating the predicate inverted makes the expression using it simpler.
|
||||
not_deprecated: bool,
|
||||
cfg: Option<syn::Attribute>,
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
let err = |span, msg| {
|
||||
let msg = format!("Invalid host function definition.\n{}", msg);
|
||||
syn::Error::new(span, msg)
|
||||
};
|
||||
|
||||
// process attributes
|
||||
let msg =
|
||||
"Only #[version(<u8>)], #[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::<syn::LitInt>().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::<E::T>::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<ReturnErrorCode, TrapReason>,
|
||||
- Result<u64, TrapReason>,
|
||||
- Result<u32, TrapReason>"#;
|
||||
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<Self> {
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
|
||||
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::<Punctuated<FnArg, Comma>>();
|
||||
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::<u8, Vec<TokenStream2>>::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<crate::wasm::runtime::Runtime<'a, E>> for Env
|
||||
{
|
||||
fn define(
|
||||
store: &mut ::wasmi::Store<crate::wasm::Runtime<E>>,
|
||||
linker: &mut ::wasmi::Linker<crate::wasm::Runtime<E>>,
|
||||
allow_unstable: AllowUnstableInterface,
|
||||
allow_deprecated: AllowDeprecatedInterface,
|
||||
) -> Result<(),::wasmi::errors::LinkerError> {
|
||||
#impls
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct BenchEnv<E>(::core::marker::PhantomData<E>);
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<E: Ext> BenchEnv<E> {
|
||||
#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<E> },
|
||||
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/master/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::<Vec<_>>().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<E>, 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<ReturnErrorCode, TrapReason> {
|
||||
/// 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<u32, TrapReason> {
|
||||
/// 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<ReturnErrorCode, TrapReason> {
|
||||
/// 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<u32, TrapReason> {
|
||||
/// 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<ReturnErrorCode, TrapReason>`,
|
||||
/// - `Result<u32, TrapReason>`.
|
||||
///
|
||||
/// The macro expands to `pub struct Env` declaration, with the following traits implementations:
|
||||
/// - `pezpallet_contracts::wasm::Environment<Runtime<E>> 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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user