Files
pezkuwi-sdk/bizinikiwi/pezframe/contracts/src/wasm/prepare.rs
T
pezkuwichain 1c0e57d984 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.
2025-12-14 00:04:10 +03:00

877 lines
21 KiB
Rust

// 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.
//! This module takes care of loading, checking and preprocessing of a
//! wasm module before execution. It also extracts some essential information
//! from a module.
use crate::{
chain_extension::ChainExtension,
storage::meter::Diff,
wasm::{
runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob,
BYTES_PER_PAGE,
},
AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET,
};
#[cfg(any(test, feature = "runtime-benchmarks"))]
use alloc::vec::Vec;
use codec::MaxEncodedLen;
use pezsp_runtime::{traits::Hash, DispatchError};
use wasmi::{
core::ValType as WasmiValueType, CompilationMode, Config as WasmiConfig, Engine, ExternType,
Module, StackLimits,
};
/// Imported memory must be located inside this module. The reason for hardcoding is that current
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
pub const IMPORT_MODULE_MEMORY: &str = "env";
/// The inner deserialized module is valid and contains only allowed WebAssembly features.
/// This is checked by loading it into wasmi interpreter `engine`.
pub struct LoadedModule {
pub module: Module,
pub engine: Engine,
}
#[derive(PartialEq, Debug, Clone)]
pub enum LoadingMode {
Checked,
Unchecked,
}
#[cfg(test)]
pub mod tracker {
use core::cell::RefCell;
thread_local! {
pub static LOADED_MODULE: RefCell<Vec<super::LoadingMode>> = RefCell::new(Vec::new());
}
}
impl LoadedModule {
/// Creates a new instance of `LoadedModule`.
///
/// The inner Wasm module is checked not to have restricted WebAssembly proposals.
/// Returns `Err` if the `code` cannot be deserialized or if it contains an invalid module.
pub fn new<T>(
code: &[u8],
determinism: Determinism,
stack_limits: Option<StackLimits>,
loading_mode: LoadingMode,
compilation_mode: CompilationMode,
) -> Result<Self, &'static str> {
// NOTE: wasmi does not support unstable WebAssembly features. The module is implicitly
// checked for not having those ones when creating `wasmi::Module` below.
let mut config = WasmiConfig::default();
config
.wasm_multi_value(false)
.wasm_mutable_global(false)
.wasm_sign_extension(true)
.wasm_bulk_memory(false)
.wasm_reference_types(false)
.wasm_tail_call(false)
.wasm_extended_const(false)
.wasm_saturating_float_to_int(false)
.floats(matches!(determinism, Determinism::Relaxed))
.compilation_mode(compilation_mode)
.consume_fuel(true);
if let Some(stack_limits) = stack_limits {
config.set_stack_limits(stack_limits);
}
let engine = Engine::new(&config);
let module = match loading_mode {
LoadingMode::Checked => Module::new(&engine, code),
// Safety: The code has been validated, Therefore we know that it's a valid binary.
LoadingMode::Unchecked => unsafe { Module::new_unchecked(&engine, code) },
}
.map_err(|err| {
log::debug!(target: LOG_TARGET, "Module creation failed: {:?}", err);
"Can't load the module into wasmi!"
})?;
#[cfg(test)]
tracker::LOADED_MODULE.with(|t| t.borrow_mut().push(loading_mode));
// Return a `LoadedModule` instance with
// __valid__ module.
Ok(LoadedModule { module, engine })
}
/// Check that the module has required exported functions. For now
/// these are just entrypoints:
///
/// - 'call'
/// - 'deploy'
///
/// Any other exports are not allowed.
fn scan_exports(&self) -> Result<(), &'static str> {
let mut deploy_found = false;
let mut call_found = false;
let module = &self.module;
let exports = module.exports();
for export in exports {
match export.ty() {
ExternType::Func(ft) => {
match export.name() {
"call" => call_found = true,
"deploy" => deploy_found = true,
_ =>
return Err(
"unknown function export: expecting only deploy and call functions",
),
}
// Check the signature.
// Both "call" and "deploy" have the () -> () function type.
// We still support () -> (i32) for backwards compatibility.
if !(ft.params().is_empty() &&
(ft.results().is_empty() || ft.results() == [WasmiValueType::I32]))
{
return Err("entry point has wrong signature");
}
},
ExternType::Memory(_) => return Err("memory export is forbidden"),
ExternType::Global(_) => return Err("global export is forbidden"),
ExternType::Table(_) => return Err("table export is forbidden"),
}
}
if !deploy_found {
return Err("deploy function isn't exported");
}
if !call_found {
return Err("call function isn't exported");
}
Ok(())
}
/// Scan an import section if any.
///
/// This makes sure that:
/// - The import section looks as we expect it from a contract.
/// - The limits of the memory type declared by the contract comply with the Schedule.
///
/// Returns the checked memory limits back to caller.
///
/// This method fails if:
///
/// - Memory import not found in the module.
/// - Tables or globals found among imports.
/// - `call_chain_extension` host function is imported, while chain extensions are disabled.
///
/// NOTE that only single memory instance is allowed for contract modules, which is enforced by
/// this check combined with multi_memory proposal gets disabled in the engine.
pub fn scan_imports<T: Config>(
&self,
schedule: &Schedule<T>,
) -> Result<(u32, u32), &'static str> {
let module = &self.module;
let imports = module.imports();
let mut memory_limits = None;
for import in imports {
match *import.ty() {
ExternType::Table(_) => return Err("Cannot import tables"),
ExternType::Global(_) => return Err("Cannot import globals"),
ExternType::Func(_) => {
import.ty().func().ok_or("expected a function")?;
if !<T as Config>::ChainExtension::enabled() &&
(import.name().as_bytes() == b"seal_call_chain_extension" ||
import.name().as_bytes() == b"call_chain_extension")
{
return Err(
"Module uses chain extensions but chain extensions are disabled",
);
}
},
ExternType::Memory(mt) => {
if import.module().as_bytes() != IMPORT_MODULE_MEMORY.as_bytes() {
return Err("Invalid module for imported memory");
}
if import.name().as_bytes() != b"memory" {
return Err("Memory import must have the field name 'memory'");
}
if memory_limits.is_some() {
return Err("Multiple memory imports defined");
}
// Parse memory limits defaulting it to (0,0).
// Any access to it will then lead to out of bounds trap.
let (initial, maximum) = (
mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE)
as u32,
mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| {
p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32
}),
);
if initial > maximum {
return Err(
"Requested initial number of memory pages should not exceed the requested maximum",
);
}
if maximum > schedule.limits.memory_pages {
return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule");
}
memory_limits = Some((initial, maximum));
continue;
},
}
}
memory_limits.ok_or("No memory import found in the module")
}
}
/// Check that given `code` satisfies constraints required for the contract Wasm module.
/// This includes two groups of checks:
///
/// 1. General engine-side validation makes sure the module is consistent and does not contain
/// forbidden WebAssembly features.
/// 2. Additional checks which are specific to smart contracts eligible for this pallet.
fn validate<E, T>(
code: &[u8],
schedule: &Schedule<T>,
determinism: &mut Determinism,
) -> Result<(), (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
{
let module = (|| {
// We don't actually ever execute this instance so we can get away with a minimal stack
// which reduces the amount of memory that needs to be zeroed.
let stack_limits = Some(StackLimits::new(1, 1, 0).expect("initial <= max; qed"));
// We check that the module is generally valid,
// and does not have restricted WebAssembly features, here.
let contract_module = match *determinism {
Determinism::Relaxed => {
if let Ok(module) = LoadedModule::new::<T>(
code,
Determinism::Enforced,
stack_limits,
LoadingMode::Checked,
CompilationMode::Eager,
) {
*determinism = Determinism::Enforced;
module
} else {
LoadedModule::new::<T>(
code,
Determinism::Relaxed,
None,
LoadingMode::Checked,
CompilationMode::Eager,
)?
}
},
Determinism::Enforced => LoadedModule::new::<T>(
code,
Determinism::Enforced,
stack_limits,
LoadingMode::Checked,
CompilationMode::Eager,
)?,
};
// The we check that module satisfies constraints the pallet puts on contracts.
contract_module.scan_exports()?;
contract_module.scan_imports::<T>(schedule)?;
Ok(contract_module)
})()
.map_err(|msg: &str| {
log::debug!(target: LOG_TARGET, "New code rejected on validation: {}", msg);
(Error::<T>::CodeRejected.into(), msg)
})?;
// This will make sure that the module can be actually run within wasmi:
//
// - It doesn't use any unknown imports.
// - It doesn't explode the wasmi bytecode generation.
WasmBlob::<T>::instantiate::<E, _>(module, (), schedule, AllowDeprecatedInterface::No)
.map_err(|err| {
log::debug!(target: LOG_TARGET, "{err}");
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
})?;
Ok(())
}
/// Validates the given binary `code` is a valid Wasm module satisfying following constraints:
///
/// - The module doesn't export any memory.
/// - The module does imports memory, which limits lay within the limits permitted by the
/// `schedule`.
/// - All imported functions from the external environment match defined by `env` module.
///
/// Also constructs contract `code_info` by calculating the storage deposit.
pub fn prepare<E, T>(
code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
mut determinism: Determinism,
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
{
validate::<E, T>(code.as_ref(), schedule, &mut determinism)?;
// Calculate deposit for storing contract code and `code_info` in two different storage items.
let code_len = code.len() as u32;
let bytes_added = code_len.saturating_add(<CodeInfo<T>>::max_encoded_len() as u32);
let deposit = Diff { bytes_added, items_added: 2, ..Default::default() }
.update_contract::<T>(None)
.charge_or_zero();
let code_info = CodeInfo { owner, deposit, determinism, refcount: 0, code_len };
let code_hash = T::Hashing::hash(&code);
Ok(WasmBlob { code, code_info, code_hash })
}
/// Alternate (possibly unsafe) preparation functions used only for benchmarking and testing.
///
/// For benchmarking we need to construct special contracts that might not pass our
/// sanity checks. We hide functions allowing this behind a feature that is only set during
/// benchmarking or testing to prevent usage in production code.
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub mod benchmarking {
use super::*;
/// Prepare function that does not perform export section checks on the passed in code.
pub fn prepare<T: Config>(
code: Vec<u8>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
) -> Result<WasmBlob<T>, DispatchError> {
let determinism = Determinism::Enforced;
let contract_module = LoadedModule::new::<T>(
&code,
determinism,
None,
LoadingMode::Checked,
CompilationMode::Eager,
)?;
contract_module.scan_imports::<T>(schedule)?;
let code: CodeVec<T> = code.try_into().map_err(|_| <Error<T>>::CodeTooLarge)?;
let code_info = CodeInfo {
owner,
// this is a helper function for benchmarking which skips deposit collection
deposit: Default::default(),
refcount: 0,
code_len: code.len() as u32,
determinism,
};
let code_hash = T::Hashing::hash(&code);
Ok(WasmBlob { code, code_info, code_hash })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
exec::Ext,
schedule::Limits,
tests::{Test, ALICE},
};
use pezpallet_contracts_proc_macro::define_env;
use std::fmt;
impl fmt::Debug for WasmBlob<Test> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ContractCode {{ .. }}")
}
}
/// Using unreachable statements triggers unreachable warnings in the generated code
#[allow(unreachable_code)]
mod env {
use super::*;
use crate::wasm::runtime::{AllowDeprecatedInterface, AllowUnstableInterface, TrapReason};
// Define test environment for tests. We need ImportSatisfyCheck
// implementation from it. So actual implementations doesn't matter.
#[define_env]
pub mod test_env {
fn panic(_ctx: _, _memory: _) -> Result<(), TrapReason> {
Ok(())
}
// gas is an implementation defined function and a contract can't import it.
fn gas(_ctx: _, _memory: _, _amount: u64) -> Result<(), TrapReason> {
Ok(())
}
fn nop(_ctx: _, _memory: _, _unused: u64) -> Result<(), TrapReason> {
Ok(())
}
// new version of nop with other data type for argument
#[version(1)]
fn nop(_ctx: _, _memory: _, _unused: i32) -> Result<(), TrapReason> {
Ok(())
}
}
}
macro_rules! prepare_test {
($name:ident, $wat:expr, $($expected:tt)*) => {
#[test]
fn $name() {
let wasm = wat::parse_str($wat).unwrap().try_into().unwrap();
let schedule = Schedule {
limits: Limits {
memory_pages: 16,
.. Default::default()
},
.. Default::default()
};
let r = prepare::<env::Env, Test>(
wasm,
&schedule,
ALICE,
Determinism::Enforced,
);
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
}
};
}
prepare_test!(
no_floats,
r#"
(module
(func (export "call")
(drop
(f32.add
(f32.const 0)
(f32.const 1)
)
)
)
(func (export "deploy"))
)"#,
Err("Can't load the module into wasmi!")
);
mod memories {
use super::*;
prepare_test!(
memory_with_one_page,
r#"
(module
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
internal_memory_declaration,
r#"
(module
(memory 1 1)
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("No memory import found in the module")
);
prepare_test!(
no_memory_import,
r#"
(module
;; no memory imported
(func (export "call"))
(func (export "deploy"))
)"#,
Err("No memory import found in the module")
);
prepare_test!(
initial_exceeds_maximum,
r#"
(module
(import "env" "memory" (memory 16 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
prepare_test!(
requested_maximum_valid,
r#"
(module
(import "env" "memory" (memory 1 16))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
requested_maximum_exceeds_configured_maximum,
r#"
(module
(import "env" "memory" (memory 1 17))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule")
);
prepare_test!(
field_name_not_memory,
r#"
(module
(import "env" "forgetit" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Memory import must have the field name 'memory'")
);
prepare_test!(
multiple_memory_imports,
r#"
(module
(import "env" "memory" (memory 1 1))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
prepare_test!(
table_import,
r#"
(module
(import "seal0" "table" (table 1 anyfunc))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Cannot import tables")
);
prepare_test!(
global_import,
r#"
(module
(global $g (import "seal0" "global") i32)
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Cannot import globals")
);
}
mod imports {
use super::*;
prepare_test!(
can_import_legit_function,
r#"
(module
(import "seal0" "nop" (func (param i64)))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
// memory is in "env" and not in "seal0"
prepare_test!(
memory_not_in_seal0,
r#"
(module
(import "seal0" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Invalid module for imported memory")
);
// Memory is in "env" and not in some arbitrary module
prepare_test!(
memory_not_in_arbitrary_module,
r#"
(module
(import "any_module" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Invalid module for imported memory")
);
prepare_test!(
function_in_other_module_works,
r#"
(module
(import "seal1" "nop" (func (param i32)))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
wrong_signature,
r#"
(module
(import "seal0" "input" (func (param i64)))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("New code rejected on wasmi instantiation!")
);
prepare_test!(
unknown_func_name,
r#"
(module
(import "seal0" "unknown_func" (func))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("No memory import found in the module")
);
// Try to import function from not a "seal*" module.
prepare_test!(
try_import_from_wrong_module,
r#"
(module
(import "env" "panic" (func))
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("New code rejected on wasmi instantiation!")
);
}
mod entrypoints {
use super::*;
prepare_test!(
it_works,
r#"
(module
(import "env" "memory" (memory 1 1))
(func (export "call"))
(func (export "deploy"))
)
"#,
Ok(_)
);
prepare_test!(
signed_extension_works,
r#"
(module
(import "env" "memory" (memory 1 1))
(func (export "deploy"))
(func (export "call"))
(func (param i32) (result i32)
local.get 0
i32.extend8_s
)
)
"#,
Ok(_)
);
prepare_test!(
omit_memory,
r#"
(module
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("No memory import found in the module")
);
prepare_test!(
omit_deploy,
r#"
(module
(func (export "call"))
)
"#,
Err("deploy function isn't exported")
);
prepare_test!(
omit_call,
r#"
(module
(func (export "deploy"))
)
"#,
Err("call function isn't exported")
);
// Try to use imported function as an entry point.
// This is allowed.
prepare_test!(
try_sneak_export_as_entrypoint,
r#"
(module
(import "seal0" "panic" (func))
(import "env" "memory" (memory 1 1))
(func (export "deploy"))
(export "call" (func 0))
)
"#,
Ok(_)
);
// Try to use global as an entry point.
prepare_test!(
try_sneak_export_as_global,
r#"
(module
(func (export "deploy"))
(global (export "call") i32 (i32.const 0))
)
"#,
Err("global export is forbidden")
);
prepare_test!(
wrong_signature,
r#"
(module
(func (export "deploy"))
(func (export "call") (param i32))
)
"#,
Err("entry point has wrong signature")
);
prepare_test!(
unknown_exports,
r#"
(module
(func (export "call"))
(func (export "deploy"))
(func (export "whatevs"))
)
"#,
Err("unknown function export: expecting only deploy and call functions")
);
prepare_test!(
global_float,
r#"
(module
(global $x f32 (f32.const 0))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
prepare_test!(
local_float,
r#"
(module
(func $foo (local f32))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
prepare_test!(
param_float,
r#"
(module
(func $foo (param f32))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
prepare_test!(
result_float,
r#"
(module
(func $foo (result f32) (f32.const 0))
(func (export "call"))
(func (export "deploy"))
)
"#,
Err("Can't load the module into wasmi!")
);
}
}