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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,68 @@
// 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.
//! Functions that deal with address derivation.
use crate::{CodeHash, Config};
use codec::{Decode, Encode};
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
/// Provides the contract address generation method.
///
/// See [`DefaultAddressGenerator`] for the default implementation.
///
/// # Note for implementors
///
/// 1. Make sure that there are no collisions, different inputs never lead to the same output.
/// 2. Make sure that the same inputs lead to the same output.
pub trait AddressGenerator<T: Config> {
/// The address of a contract based on the given instantiate parameters.
///
/// Changing the formular for an already deployed chain is fine as long as no collisions
/// with the old formular. Changes only affect existing contracts.
fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
input_data: &[u8],
salt: &[u8],
) -> T::AccountId;
}
/// Default address generator.
///
/// This is the default address generator used by contract instantiation. Its result
/// is only dependent on its inputs. It can therefore be used to reliably predict the
/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There
/// is no CREATE equivalent because CREATE2 is strictly more powerful.
/// Formula:
/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
pub struct DefaultAddressGenerator;
impl<T: Config> AddressGenerator<T> for DefaultAddressGenerator {
/// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)`
fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
input_data: &[u8],
salt: &[u8],
) -> T::AccountId {
let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt)
.using_encoded(T::Hashing::hash);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed")
}
}
@@ -0,0 +1,236 @@
// 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.
use crate::{
benchmarking::{Contract, WasmModule},
exec::{Ext, Key, Stack},
storage::meter::Meter,
transient_storage::MeterEntry,
wasm::Runtime,
BalanceOf, Config, DebugBufferVec, Determinism, Error, ExecReturnValue, GasMeter, Origin,
Schedule, TypeInfo, WasmBlob, Weight,
};
use alloc::{vec, vec::Vec};
use codec::{Encode, HasCompact};
use core::fmt::Debug;
use pezframe_benchmarking::benchmarking;
use pezsp_core::Get;
type StackExt<'a, T> = Stack<'a, T, WasmBlob<T>>;
/// A prepared contract call ready to be executed.
pub struct PreparedCall<'a, T: Config> {
func: wasmi::Func,
store: wasmi::Store<Runtime<'a, StackExt<'a, T>>>,
}
impl<'a, T: Config> PreparedCall<'a, T> {
pub fn call(mut self) -> ExecReturnValue {
let result = self.func.call(&mut self.store, &[], &mut []);
WasmBlob::<T>::process_result(self.store, result).unwrap()
}
}
/// A builder used to prepare a contract call.
pub struct CallSetup<T: Config> {
contract: Contract<T>,
dest: T::AccountId,
origin: Origin<T>,
gas_meter: GasMeter<T>,
storage_meter: Meter<T>,
schedule: Schedule<T>,
value: BalanceOf<T>,
debug_message: Option<DebugBufferVec<T>>,
determinism: Determinism,
data: Vec<u8>,
transient_storage_size: u32,
}
impl<T> Default for CallSetup<T>
where
T: Config + pezpallet_balances::Config,
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
{
fn default() -> Self {
Self::new(WasmModule::dummy())
}
}
impl<T> CallSetup<T>
where
T: Config + pezpallet_balances::Config,
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
{
/// Setup a new call for the given module.
pub fn new(module: WasmModule<T>) -> Self {
let contract = Contract::<T>::new(module.clone(), vec![]).unwrap();
let dest = contract.account_id.clone();
let origin = Origin::from_account_id(contract.caller.clone());
let storage_meter = Meter::new(&origin, None, 0u32.into()).unwrap();
// Whitelist contract account, as it is already accounted for in the call benchmark
benchmarking::add_to_whitelist(
pezframe_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
);
// Whitelist the contract's contractInfo as it is already accounted for in the call
// benchmark
benchmarking::add_to_whitelist(
crate::ContractInfoOf::<T>::hashed_key_for(&contract.account_id).into(),
);
Self {
contract,
dest,
origin,
gas_meter: GasMeter::new(Weight::MAX),
storage_meter,
schedule: T::Schedule::get(),
value: 0u32.into(),
debug_message: None,
determinism: Determinism::Enforced,
data: vec![],
transient_storage_size: 0,
}
}
/// Set the meter's storage deposit limit.
pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf<T>) {
self.storage_meter = Meter::new(&self.origin, Some(balance), 0u32.into()).unwrap();
}
/// Set the call's origin.
pub fn set_origin(&mut self, origin: Origin<T>) {
self.origin = origin;
}
/// Set the contract's balance.
pub fn set_balance(&mut self, value: BalanceOf<T>) {
self.contract.set_balance(value);
}
/// Set the call's input data.
pub fn set_data(&mut self, value: Vec<u8>) {
self.data = value;
}
/// Set the transient storage size.
pub fn set_transient_storage_size(&mut self, size: u32) {
self.transient_storage_size = size;
}
/// Set the debug message.
pub fn enable_debug_message(&mut self) {
self.debug_message = Some(Default::default());
}
/// Get the debug message.
pub fn debug_message(&self) -> Option<DebugBufferVec<T>> {
self.debug_message.clone()
}
/// Get the call's input data.
pub fn data(&self) -> Vec<u8> {
self.data.clone()
}
/// Get the call's contract.
pub fn contract(&self) -> Contract<T> {
self.contract.clone()
}
/// Build the call stack.
pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob<T>) {
let mut ext = StackExt::bench_new_call(
self.dest.clone(),
self.origin.clone(),
&mut self.gas_meter,
&mut self.storage_meter,
&self.schedule,
self.value,
self.debug_message.as_mut(),
self.determinism,
);
if self.transient_storage_size > 0 {
Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
}
ext
}
/// Prepare a call to the module.
pub fn prepare_call<'a>(
ext: &'a mut StackExt<'a, T>,
module: WasmBlob<T>,
input: Vec<u8>,
) -> PreparedCall<'a, T> {
let (func, store) = module.bench_prepare_call(ext, input);
PreparedCall { func, store }
}
/// Add transient_storage
fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
ext.transient_storage().meter().current_mut().limit = size;
for i in 1u32.. {
let mut key_data = i.to_le_bytes().to_vec();
while key_data.last() == Some(&0) {
key_data.pop();
}
let key = Key::<T>::try_from_var(key_data).unwrap();
if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
// Restore previous settings.
ext.transient_storage().meter().current_mut().limit = limit;
ext.transient_storage().meter().current_mut().amount = amount;
if e == Error::<T>::OutOfTransientStorage.into() {
break;
} else {
return Err("Initialization of the transient storage failed");
}
}
}
Ok(())
}
}
#[macro_export]
macro_rules! memory(
($($bytes:expr,)*) => {
vec![]
.into_iter()
$(.chain($bytes))*
.collect::<Vec<_>>()
};
);
#[macro_export]
macro_rules! build_runtime(
($runtime:ident, $memory:ident: [$($segment:expr,)*]) => {
$crate::build_runtime!($runtime, _contract, $memory: [$($segment,)*]);
};
($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => {
$crate::build_runtime!($runtime, $contract);
let mut $memory = $crate::memory!($($bytes,)*);
};
($runtime:ident, $contract:ident) => {
let mut setup = CallSetup::<T>::default();
let $contract = setup.contract();
let input = setup.data();
let (mut ext, _) = setup.ext();
let mut $runtime = crate::wasm::Runtime::new(&mut ext, input);
};
);
@@ -0,0 +1,363 @@
// 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.
//! Functions to procedurally construct contract code used for benchmarking.
//!
//! In order to be able to benchmark events that are triggered by contract execution
//! (API calls into seal, individual instructions), we need to generate contracts that
//! perform those events. Because those contracts can get very big we cannot simply define
//! them as text (.wat) as this will be too slow and consume too much memory. Therefore
//! we define this simple definition of a contract that can be passed to `create_code` that
//! compiles it down into a `WasmModule` that can be used as a contract's code.
use crate::Config;
use alloc::{borrow::ToOwned, vec, vec::Vec};
use pezframe_support::traits::Get;
use pezsp_runtime::{traits::Hash, Saturating};
use wasm_instrument::parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section,
ValueType,
},
};
/// The location where to put the generated code.
pub enum Location {
/// Generate all code into the `call` exported function.
Call,
/// Generate all code into the `deploy` exported function.
Deploy,
}
/// Pass to `create_code` in order to create a compiled `WasmModule`.
///
/// This exists to have a more declarative way to describe a wasm module than to use
/// parity-wasm directly. It is tailored to fit the structure of contracts that are
/// needed for benchmarking.
#[derive(Default)]
pub struct ModuleDefinition {
/// Imported memory attached to the module. No memory is imported if `None`.
pub memory: Option<ImportedMemory>,
/// Initializers for the imported memory.
pub data_segments: Vec<DataSegment>,
/// Creates the supplied amount of i64 mutable globals initialized with random values.
pub num_globals: u32,
/// List of functions that the module should import. They start with index 0.
pub imported_functions: Vec<ImportedFunction>,
/// Function body of the exported `deploy` function. Body is empty if `None`.
/// Its index is `imported_functions.len()`.
pub deploy_body: Option<FuncBody>,
/// Function body of the exported `call` function. Body is empty if `None`.
/// Its index is `imported_functions.len() + 1`.
pub call_body: Option<FuncBody>,
/// Function body of a non-exported function with index `imported_functions.len() + 2`.
pub aux_body: Option<FuncBody>,
/// The amount of I64 arguments the aux function should have.
pub aux_arg_num: u32,
/// Create a table containing function pointers.
pub table: Option<TableSegment>,
/// Create a section named "dummy" of the specified size. This is useful in order to
/// benchmark the overhead of loading and storing codes of specified sizes. The dummy
/// section only contributes to the size of the contract but does not affect execution.
pub dummy_section: u32,
}
pub struct TableSegment {
/// How many elements should be created inside the table.
pub num_elements: u32,
/// The function index with which all table elements should be initialized.
pub function_index: u32,
}
pub struct DataSegment {
pub offset: u32,
pub value: Vec<u8>,
}
#[derive(Clone)]
pub struct ImportedMemory {
pub min_pages: u32,
pub max_pages: u32,
}
impl ImportedMemory {
pub fn max<T: Config>() -> Self {
let pages = max_pages::<T>();
Self { min_pages: pages, max_pages: pages }
}
}
pub struct ImportedFunction {
pub module: &'static str,
pub name: &'static str,
pub params: Vec<ValueType>,
pub return_type: Option<ValueType>,
}
/// A wasm module ready to be put on chain.
#[derive(Clone)]
pub struct WasmModule<T: Config> {
pub code: Vec<u8>,
pub hash: <T::Hashing as Hash>::Output,
}
impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
fn from(def: ModuleDefinition) -> Self {
// internal functions start at that offset.
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
// Every contract must export "deploy" and "call" functions.
let mut contract = builder::module()
// deploy function (first internal function)
.function()
.signature()
.build()
.with_body(
def.deploy_body
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
)
.build()
// call function (second internal function)
.function()
.signature()
.build()
.with_body(
def.call_body
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
)
.build()
.export()
.field("deploy")
.internal()
.func(func_offset)
.build()
.export()
.field("call")
.internal()
.func(func_offset + 1)
.build();
// If specified we add an additional internal function
if let Some(body) = def.aux_body {
let mut signature = contract.function().signature();
for _ in 0..def.aux_arg_num {
signature = signature.with_param(ValueType::I64);
}
contract = signature.build().with_body(body).build();
}
// Grant access to linear memory.
// Every contract module is required to have an imported memory.
// If no memory is specified in the passed ModuleDefinition, then
// default to (1, 1).
let (init, max) = if let Some(memory) = &def.memory {
(memory.min_pages, Some(memory.max_pages))
} else {
(1, Some(1))
};
contract = contract.import().path("env", "memory").external().memory(init, max).build();
// Import supervisor functions. They start with idx 0.
for func in def.imported_functions {
let sig = builder::signature()
.with_params(func.params)
.with_results(func.return_type)
.build_sig();
let sig = contract.push_signature(sig);
contract = contract
.import()
.module(func.module)
.field(func.name)
.with_external(elements::External::Function(sig))
.build();
}
// Initialize memory
for data in def.data_segments {
contract = contract
.data()
.offset(Instruction::I32Const(data.offset as i32))
.value(data.value)
.build()
}
// Add global variables
if def.num_globals > 0 {
use rand::{distributions::Standard, prelude::*};
let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
contract = contract
.global()
.value_type()
.i64()
.mutable()
.init_expr(Instruction::I64Const(val))
.build()
}
}
// Add function pointer table
if let Some(table) = def.table {
contract = contract
.table()
.with_min(table.num_elements)
.with_max(Some(table.num_elements))
.with_element(0, vec![table.function_index; table.num_elements as usize])
.build();
}
// Add the dummy section
if def.dummy_section > 0 {
contract = contract.with_section(Section::Custom(CustomSection::new(
"dummy".to_owned(),
vec![42; def.dummy_section as usize],
)));
}
let code = contract.build().into_bytes().unwrap();
let hash = T::Hashing::hash(&code);
Self { code: code.into(), hash }
}
}
impl<T: Config> WasmModule<T> {
/// Creates a wasm module with an empty `call` and `deploy` function and nothing else.
pub fn dummy() -> Self {
ModuleDefinition::default().into()
}
/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
// We want the module to have the size `dummy_bytes`.
// This is not completely correct as the overhead grows when the contract grows
// because of variable length integer encoding. However, it is good enough to be that
// close for benchmarking purposes.
let module_overhead = 65;
ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
dummy_section: dummy_bytes.saturating_sub(module_overhead),
..Default::default()
}
.into()
}
/// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of
/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
/// `code_location`: Whether to place the code into `deploy` or `call`.
pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
use self::elements::Instruction::{End, GetLocal, If, Return};
// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
// We do one expansion less to account for the code section and function body
// size fields inside the binary wasm module representation which are leb128 encoded
// and therefore grow in size when the contract grows. We are not allowed to overshoot
// because of the maximum code size that is enforced by `instantiate_with_code`.
let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
let mut locals = vec![Local::new(1, ValueType::I32)];
if use_float {
locals.push(Local::new(1, ValueType::F32));
locals.push(Local::new(2, ValueType::F32));
locals.push(Local::new(3, ValueType::F32));
expansions.saturating_dec();
}
let mut module =
ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
match code_location {
Location::Call => module.call_body = body,
Location::Deploy => module.deploy_body = body,
}
module.into()
}
/// Creates a wasm module that calls the imported function `noop` `repeat` times.
pub fn noop(repeat: u32) -> Self {
let pages = max_pages::<T>();
ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "noop",
params: vec![],
return_type: None,
}],
// Write the output buffer size. The output size will be overwritten by the
// supervisor with the real size when calling the getter. Since this size does not
// change between calls it suffices to start with an initial value and then just
// leave as whatever value was written there.
data_segments: vec![DataSegment {
offset: 0,
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
}],
call_body: Some(body::repeated(
repeat,
&[
Instruction::Call(0), // call the imported function
],
)),
..Default::default()
}
.into()
}
}
/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
pub mod body {
use super::*;
pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {
repeated_with_locals(&[], repetitions, instructions)
}
pub fn repeated_with_locals(
locals: &[Local],
repetitions: u32,
instructions: &[Instruction],
) -> FuncBody {
let instructions = Instructions::new(
instructions
.iter()
.cycle()
.take(instructions.len() * usize::try_from(repetitions).unwrap())
.cloned()
.chain(core::iter::once(Instruction::End))
.collect(),
);
FuncBody::new(locals.to_vec(), instructions)
}
pub fn repeated_with_locals_using<const N: usize>(
locals: &[Local],
repetitions: u32,
mut f: impl FnMut() -> [Instruction; N],
) -> FuncBody {
let mut instructions = Vec::new();
for _ in 0..repetitions {
instructions.extend(f());
}
instructions.push(Instruction::End);
FuncBody::new(locals.to_vec(), Instructions::new(instructions))
}
}
/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
pub fn max_pages<T: Config>() -> u32 {
T::Schedule::get().limits.memory_pages
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,91 @@
// 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.
/// ! For instruction benchmarking we do not instantiate a full contract but merely the
/// ! sandbox to execute the Wasm code. This is because we do not need the full
/// ! environment that provides the seal interface as imported functions.
use super::{code::WasmModule, Config};
use crate::wasm::{
AllowDeprecatedInterface, AllowUnstableInterface, Determinism, Environment, LoadedModule,
LoadingMode, WasmBlob,
};
use pezsp_core::Get;
use wasmi::{errors::LinkerError, CompilationMode, Func, Linker, StackLimits, Store};
/// Minimal execution environment without any imported functions.
pub struct Sandbox {
entry_point: Func,
store: Store<()>,
}
impl Sandbox {
/// Invoke the `call` function of a contract code and panic on any execution error.
pub fn invoke(&mut self) {
self.entry_point.call(&mut self.store, &[], &mut []).unwrap();
}
}
impl<T: Config> From<&WasmModule<T>> for Sandbox {
/// Creates an instance from the supplied module.
/// Sets the execution engine fuel level to `u64::MAX`.
fn from(module: &WasmModule<T>) -> Self {
let contract = LoadedModule::new::<T>(
&module.code,
Determinism::Relaxed,
Some(StackLimits::default()),
LoadingMode::Checked,
CompilationMode::Eager,
)
.expect("Failed to load Wasm module");
let (mut store, _memory, instance) = WasmBlob::<T>::instantiate::<EmptyEnv, _>(
contract,
(),
&<T>::Schedule::get(),
// We are testing with an empty environment anyways
AllowDeprecatedInterface::No,
)
.expect("Failed to create benchmarking Sandbox instance");
// Set fuel for wasmi execution.
store
.set_fuel(u64::MAX)
.expect("We've set up engine to fuel consuming mode; qed");
let entry_point = instance
.start(&mut store)
.unwrap()
.get_export(&store, "call")
.unwrap()
.into_func()
.unwrap();
Self { entry_point, store }
}
}
struct EmptyEnv;
impl Environment<()> for EmptyEnv {
fn define(
_: &mut Store<()>,
_: &mut Linker<()>,
_: AllowUnstableInterface,
_: AllowDeprecatedInterface,
) -> Result<(), LinkerError> {
Ok(())
}
}
@@ -0,0 +1,495 @@
// 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.
//! A mechanism for runtime authors to augment the functionality of contracts.
//!
//! The runtime is able to call into any contract and retrieve the result using
//! [`bare_call`](crate::Pallet::bare_call). This already allows customization of runtime
//! behaviour by user generated code (contracts). However, often it is more straightforward
//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter
//! one a "chain extension" because it allows the chain to extend the set of functions that are
//! callable by a contract.
//!
//! In order to create a chain extension the runtime author implements the [`ChainExtension`]
//! trait and declares it in this pallet's [configuration Trait](crate::Config). All types
//! required for this endeavour are defined or re-exported in this module. There is an
//! implementation on `()` which can be used to signal that no chain extension is available.
//!
//! # Using multiple chain extensions
//!
//! Often there is a need for having multiple chain extensions. This is often the case when
//! some generally useful off-the-shelf extensions should be included. To have multiple chain
//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like
//! this `type Extensions = (ExtensionA, ExtensionB)`.
//!
//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple.
//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions
//! should be used when the contract calls a chain extensions. Extensions which are generally
//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry)
//! so that no collisions with other vendors will occur.
//!
//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with
//! the registry.**
//!
//! # Security
//!
//! The chain author alone is responsible for the security of the chain extension.
//! This includes avoiding the exposure of exploitable functions and charging the
//! appropriate amount of weight. In order to do so benchmarks must be written and the
//! [`charge_weight`](Environment::charge_weight) function must be called **before**
//! carrying out any action that causes the consumption of the chargeable weight.
//! It cannot be overstated how delicate of a process the creation of a chain extension
//! is. Check whether using [`bare_call`](crate::Pallet::bare_call) suffices for the
//! use case at hand.
//!
//! # Benchmarking
//!
//! The builtin contract callable functions that pezpallet-contracts provides all have
//! benchmarks that determine the correct weight that an invocation of these functions
//! induces. In order to be able to charge the correct weight for the functions defined
//! by a chain extension benchmarks must be written, too. In the near future this crate
//! will provide the means for easier creation of those specialized benchmarks.
//!
//! # Example
//!
//! The ink-examples repository maintains an
//! [end-to-end example](https://github.com/use-ink/ink-examples/tree/v5.x.x/rand-extension)
//! on how to use a chain extension in order to provide new features to ink! contracts.
use crate::{
wasm::{Runtime, RuntimeCosts},
Error,
};
use alloc::vec::Vec;
use codec::{Decode, MaxEncodedLen};
use core::marker::PhantomData;
use pezframe_support::weights::Weight;
use pezsp_runtime::DispatchError;
pub use crate::{exec::Ext, gas::ChargedAmount, storage::meter::Diff, Config};
pub use pezframe_system::Config as SysConfig;
pub use pezpallet_contracts_uapi::ReturnFlags;
/// Result that returns a [`DispatchError`] on error.
pub type Result<T> = core::result::Result<T, DispatchError>;
/// A trait used to extend the set of contract callable functions.
///
/// In order to create a custom chain extension this trait must be implemented and supplied
/// to the pallet contracts configuration trait as the associated type of the same name.
/// Consult the [module documentation](self) for a general explanation of chain extensions.
///
/// # Lifetime
///
/// The extension will be [`Default`] initialized at the beginning of each call
/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension
/// can be used as a per-call scratch buffer.
pub trait ChainExtension<C: Config> {
/// Call the chain extension logic.
///
/// This is the only function that needs to be implemented in order to write a
/// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension`
/// imported wasm function.
///
/// # Parameters
/// - `env`: Access to the remaining arguments and the execution environment.
///
/// # Return
///
/// In case of `Err` the contract execution is immediately suspended and the passed error
/// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit
/// behaviour.
///
/// # Note
///
/// The [`Self::call`] can be invoked within a read-only context, where any state-changing calls
/// are disallowed. This information can be obtained using `env.ext().is_read_only()`. It is
/// crucial for the implementer to handle this scenario appropriately.
fn call<E: Ext<T = C>>(&mut self, env: Environment<E, InitState>) -> Result<RetVal>;
/// Determines whether chain extensions are enabled for this chain.
///
/// The default implementation returns `true`. Therefore it is not necessary to overwrite
/// this function when implementing a chain extension. In case of `false` the deployment of
/// a contract that references `seal_call_chain_extension` will be denied and calling this
/// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling
/// into [`call`](Self::call).
fn enabled() -> bool {
true
}
}
/// A [`ChainExtension`] that can be composed with other extensions using a tuple.
///
/// An extension that implements this trait can be put in a tuple in order to have multiple
/// extensions available. The tuple implementation routes requests based on the first two
/// most significant bytes of the `id` passed to `call`.
///
/// If this extensions is to be used by multiple runtimes consider
/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there
/// are no collisions with other vendors.
///
/// # Note
///
/// Currently, we support tuples of up to ten registered chain extensions. If more chain extensions
/// are needed consider opening an issue.
pub trait RegisteredChainExtension<C: Config>: ChainExtension<C> {
/// The extensions globally unique identifier.
const ID: u16;
}
#[impl_trait_for_tuples::impl_for_tuples(10)]
#[tuple_types_custom_trait_bound(RegisteredChainExtension<C>)]
impl<C: Config> ChainExtension<C> for Tuple {
fn call<E: Ext<T = C>>(&mut self, mut env: Environment<E, InitState>) -> Result<RetVal> {
for_tuples!(
#(
if (Tuple::ID == env.ext_id()) && Tuple::enabled() {
return Tuple.call(env);
}
)*
);
Err(Error::<E::T>::NoChainExtension.into())
}
fn enabled() -> bool {
for_tuples!(
#(
if Tuple::enabled() {
return true;
}
)*
);
false
}
}
/// Determines the exit behaviour and return value of a chain extension.
pub enum RetVal {
/// The chain extensions returns the supplied value to its calling contract.
Converging(u32),
/// The control does **not** return to the calling contract.
///
/// Use this to stop the execution of the contract when the chain extension returns.
/// The semantic is the same as for calling `seal_return`: The control returns to
/// the caller of the currently executing contract yielding the supplied buffer and
/// flags.
Diverging { flags: ReturnFlags, data: Vec<u8> },
}
/// Grants the chain extension access to its parameters and execution environment.
///
/// It uses [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html)
/// to enforce the correct usage of the parameters passed to the chain extension.
pub struct Environment<'a, 'b, E: Ext, S: State> {
/// The actual data of this type.
inner: Inner<'a, 'b, E>,
/// `S` is only used in the type system but never as value.
phantom: PhantomData<S>,
}
/// Functions that are available in every state of this type.
impl<'a, 'b, E: Ext, S: State> Environment<'a, 'b, E, S> {
/// The function id within the `id` passed by a contract.
///
/// It returns the two least significant bytes of the `id` passed by a contract as the other
/// two bytes represent the chain extension itself (the code which is calling this function).
pub fn func_id(&self) -> u16 {
(self.inner.id & 0x0000FFFF) as u16
}
/// The chain extension id within the `id` passed by a contract.
///
/// It returns the two most significant bytes of the `id` passed by a contract which represent
/// the chain extension itself (the code which is calling this function).
pub fn ext_id(&self) -> u16 {
(self.inner.id >> 16) as u16
}
/// Charge the passed `amount` of weight from the overall limit.
///
/// It returns `Ok` when there the remaining weight budget is larger than the passed
/// `weight`. It returns `Err` otherwise. In this case the chain extension should
/// abort the execution and pass through the error.
///
/// The returned value can be used to with [`Self::adjust_weight`]. Other than that
/// it has no purpose.
///
/// # Note
///
/// Weight is synonymous with gas in bizinikiwi.
pub fn charge_weight(&mut self, amount: Weight) -> Result<ChargedAmount> {
self.inner.runtime.charge_gas(RuntimeCosts::ChainExtension(amount))
}
/// Adjust a previously charged amount down to its actual amount.
///
/// This is when a maximum a priori amount was charged and then should be partially
/// refunded to match the actual amount.
pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) {
self.inner
.runtime
.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight))
}
/// Grants access to the execution environment of the current contract call.
///
/// Consult the functions on the returned type before re-implementing those functions.
pub fn ext(&mut self) -> &mut E {
self.inner.runtime.ext()
}
}
/// Functions that are only available in the initial state of this type.
///
/// Those are the functions that determine how the arguments to the chain extensions
/// should be consumed.
impl<'a, 'b, E: Ext> Environment<'a, 'b, E, InitState> {
/// Creates a new environment for consumption by a chain extension.
///
/// It is only available to this crate because only the wasm runtime module needs to
/// ever create this type. Chain extensions merely consume it.
pub(crate) fn new(
runtime: &'a mut Runtime<'b, E>,
memory: &'a mut [u8],
id: u32,
input_ptr: u32,
input_len: u32,
output_ptr: u32,
output_len_ptr: u32,
) -> Self {
Environment {
inner: Inner { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr },
phantom: PhantomData,
}
}
/// Use all arguments as integer values.
pub fn only_in(self) -> Environment<'a, 'b, E, OnlyInState> {
Environment { inner: self.inner, phantom: PhantomData }
}
/// Use input arguments as integer and output arguments as pointer to a buffer.
pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, PrimInBufOutState> {
Environment { inner: self.inner, phantom: PhantomData }
}
/// Use input and output arguments as pointers to a buffer.
pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, BufInBufOutState> {
Environment { inner: self.inner, phantom: PhantomData }
}
}
/// Functions to use the input arguments as integers.
impl<'a, 'b, E: Ext, S: PrimIn> Environment<'a, 'b, E, S> {
/// The `input_ptr` argument.
pub fn val0(&self) -> u32 {
self.inner.input_ptr
}
/// The `input_len` argument.
pub fn val1(&self) -> u32 {
self.inner.input_len
}
}
/// Functions to use the output arguments as integers.
impl<'a, 'b, E: Ext, S: PrimOut> Environment<'a, 'b, E, S> {
/// The `output_ptr` argument.
pub fn val2(&self) -> u32 {
self.inner.output_ptr
}
/// The `output_len_ptr` argument.
pub fn val3(&self) -> u32 {
self.inner.output_len_ptr
}
}
/// Functions to use the input arguments as pointer to a buffer.
impl<'a, 'b, E: Ext, S: BufIn> Environment<'a, 'b, E, S> {
/// Reads `min(max_len, in_len)` from contract memory.
///
/// This does **not** charge any weight. The caller must make sure that the an
/// appropriate amount of weight is charged **before** reading from contract memory.
/// The reason for that is that usually the costs for reading data and processing
/// said data cannot be separated in a benchmark. Therefore a chain extension would
/// charge the overall costs either using `max_len` (worst case approximation) or using
/// [`in_len()`](Self::in_len).
pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
self.inner.runtime.read_sandbox_memory(
self.inner.memory,
self.inner.input_ptr,
self.inner.input_len.min(max_len),
)
}
/// Reads `min(buffer.len(), in_len) from contract memory.
///
/// This takes a mutable pointer to a buffer fills it with data and shrinks it to
/// the size of the actual data. Apart from supporting pre-allocated buffers it is
/// equivalent to to [`read()`](Self::read).
pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> {
let len = buffer.len();
let sliced = {
let buffer = core::mem::take(buffer);
&mut buffer[..len.min(self.inner.input_len as usize)]
};
self.inner.runtime.read_sandbox_memory_into_buf(
self.inner.memory,
self.inner.input_ptr,
sliced,
)?;
*buffer = sliced;
Ok(())
}
/// Reads and decodes a type with a size fixed at compile time from contract memory.
///
/// This function is secure and recommended for all input types of fixed size
/// as long as the cost of reading the memory is included in the overall already charged
/// weight of the chain extension. This should usually be the case when fixed input types
/// are used.
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
self.inner
.runtime
.read_sandbox_memory_as(self.inner.memory, self.inner.input_ptr)
}
/// Reads and decodes a type with a dynamic size from contract memory.
///
/// Make sure to include `len` in your weight calculations.
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as_unbounded(
self.inner.memory,
self.inner.input_ptr,
len,
)
}
/// The length of the input as passed in as `input_len`.
///
/// A chain extension would use this value to calculate the dynamic part of its
/// weight. For example a chain extension that calculates the hash of some passed in
/// bytes would use `in_len` to charge the costs of hashing that amount of bytes.
/// This also subsumes the act of copying those bytes as a benchmarks measures both.
pub fn in_len(&self) -> u32 {
self.inner.input_len
}
}
/// Functions to use the output arguments as pointer to a buffer.
impl<'a, 'b, E: Ext, S: BufOut> Environment<'a, 'b, E, S> {
/// Write the supplied buffer to contract memory.
///
/// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned.
/// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer
/// by supplying the guard value of `pezpallet-contracts::SENTINEL` as `out_ptr`. The
/// `weight_per_byte` is only charged when the write actually happens and is not skipped or
/// failed due to a too small output buffer.
pub fn write(
&mut self,
buffer: &[u8],
allow_skip: bool,
weight_per_byte: Option<Weight>,
) -> Result<()> {
self.inner.runtime.write_sandbox_output(
self.inner.memory,
self.inner.output_ptr,
self.inner.output_len_ptr,
buffer,
allow_skip,
|len| {
weight_per_byte.map(|w| RuntimeCosts::ChainExtension(w.saturating_mul(len.into())))
},
)
}
}
/// The actual data of an `Environment`.
///
/// All data is put into this struct to easily pass it around as part of the typestate
/// pattern. Also it creates the opportunity to box this struct in the future in case it
/// gets too large.
struct Inner<'a, 'b, E: Ext> {
/// The runtime contains all necessary functions to interact with the running contract.
runtime: &'a mut Runtime<'b, E>,
/// Reference to the contracts memory.
memory: &'a mut [u8],
/// Verbatim argument passed to `seal_call_chain_extension`.
id: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
input_ptr: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
input_len: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
output_ptr: u32,
/// Verbatim argument passed to `seal_call_chain_extension`.
output_len_ptr: u32,
}
/// Any state of an [`Environment`] implements this trait.
/// See [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html).
pub trait State: sealed::Sealed {}
/// A state that uses primitive inputs.
pub trait PrimIn: State {}
/// A state that uses primitive outputs.
pub trait PrimOut: State {}
/// A state that uses a buffer as input.
pub trait BufIn: State {}
/// A state that uses a buffer as output.
pub trait BufOut: State {}
/// The initial state of an [`Environment`].
pub enum InitState {}
/// A state that uses all arguments as primitive inputs.
pub enum OnlyInState {}
/// A state that uses two arguments as primitive inputs and the other two as buffer output.
pub enum PrimInBufOutState {}
/// Uses a buffer for input and a buffer for output.
pub enum BufInBufOutState {}
mod sealed {
use super::*;
/// Trait to prevent users from implementing `State` for anything else.
pub trait Sealed {}
impl Sealed for InitState {}
impl Sealed for OnlyInState {}
impl Sealed for PrimInBufOutState {}
impl Sealed for BufInBufOutState {}
impl State for InitState {}
impl State for OnlyInState {}
impl State for PrimInBufOutState {}
impl State for BufInBufOutState {}
impl PrimIn for OnlyInState {}
impl PrimOut for OnlyInState {}
impl PrimIn for PrimInBufOutState {}
impl BufOut for PrimInBufOutState {}
impl BufIn for BufInBufOutState {}
impl BufOut for BufInBufOutState {}
}
+112
View File
@@ -0,0 +1,112 @@
// 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.
pub use crate::{
exec::{ExecResult, ExportedFunction},
primitives::ExecReturnValue,
};
use crate::{Config, LOG_TARGET};
/// Umbrella trait for all interfaces that serves for debugging.
pub trait Debugger<T: Config>: Tracing<T> + CallInterceptor<T> {}
impl<T: Config, V> Debugger<T> for V where V: Tracing<T> + CallInterceptor<T> {}
/// Defines methods to capture contract calls, enabling external observers to
/// measure, trace, and react to contract interactions.
pub trait Tracing<T: Config> {
/// The type of [`CallSpan`] that is created by this trait.
type CallSpan: CallSpan;
/// Creates a new call span to encompass the upcoming contract execution.
///
/// This method should be invoked just before the execution of a contract and
/// marks the beginning of a traceable span of execution.
///
/// # Arguments
///
/// * `contract_address` - The address of the contract that is about to be executed.
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
/// * `input_data` - The raw input data of the call.
fn new_call_span(
contract_address: &T::AccountId,
entry_point: ExportedFunction,
input_data: &[u8],
) -> Self::CallSpan;
}
/// Defines a span of execution for a contract call.
pub trait CallSpan {
/// Called just after the execution of a contract.
///
/// # Arguments
///
/// * `output` - The raw output of the call.
fn after_call(self, output: &ExecReturnValue);
}
impl<T: Config> Tracing<T> for () {
type CallSpan = ();
fn new_call_span(
contract_address: &T::AccountId,
entry_point: ExportedFunction,
input_data: &[u8],
) {
log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}")
}
}
impl CallSpan for () {
fn after_call(self, output: &ExecReturnValue) {
log::trace!(target: LOG_TARGET, "call result {output:?}")
}
}
/// Provides an interface for intercepting contract calls.
pub trait CallInterceptor<T: Config> {
/// Allows to intercept contract calls and decide whether they should be executed or not.
/// If the call is intercepted, the mocked result of the call is returned.
///
/// # Arguments
///
/// * `contract_address` - The address of the contract that is about to be executed.
/// * `entry_point` - Describes whether the call is the constructor or a regular call.
/// * `input_data` - The raw input data of the call.
///
/// # Expected behavior
///
/// This method should return:
/// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call
/// is returned.
/// * `None` - otherwise, i.e. the call should be executed normally.
fn intercept_call(
contract_address: &T::AccountId,
entry_point: &ExportedFunction,
input_data: &[u8],
) -> Option<ExecResult>;
}
impl<T: Config> CallInterceptor<T> for () {
fn intercept_call(
_contract_address: &T::AccountId,
_entry_point: &ExportedFunction,
_input_data: &[u8],
) -> Option<ExecResult> {
None
}
}
File diff suppressed because it is too large Load Diff
+399
View File
@@ -0,0 +1,399 @@
// 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.
use crate::{exec::ExecError, Config, Error};
use core::marker::PhantomData;
use pezframe_support::{
dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo},
weights::Weight,
DefaultNoBound,
};
use pezsp_core::Get;
use pezsp_runtime::{traits::Zero, DispatchError};
#[cfg(test)]
use std::{any::Any, fmt::Debug};
#[derive(Debug, PartialEq, Eq)]
pub struct ChargedAmount(Weight);
impl ChargedAmount {
pub fn amount(&self) -> Weight {
self.0
}
}
/// Meter for syncing the gas between the executor and the gas meter.
#[derive(DefaultNoBound)]
struct EngineMeter<T: Config> {
fuel: u64,
_phantom: PhantomData<T>,
}
impl<T: Config> EngineMeter<T> {
/// Create a meter with the given fuel limit.
fn new(limit: Weight) -> Self {
Self {
fuel: limit.ref_time().saturating_div(T::Schedule::get().ref_time_by_fuel()),
_phantom: PhantomData,
}
}
/// Set the fuel left to the given value.
/// Returns the amount of Weight consumed since the last update.
fn set_fuel(&mut self, fuel: u64) -> Weight {
let consumed = self
.fuel
.saturating_sub(fuel)
.saturating_mul(T::Schedule::get().ref_time_by_fuel());
self.fuel = fuel;
Weight::from_parts(consumed, 0)
}
/// Charge the given amount of gas.
/// Returns the amount of fuel left.
fn charge_ref_time(&mut self, ref_time: u64) -> Result<Syncable, DispatchError> {
let amount = ref_time
.checked_div(T::Schedule::get().ref_time_by_fuel())
.ok_or(Error::<T>::InvalidSchedule)?;
self.fuel.checked_sub(amount).ok_or_else(|| Error::<T>::OutOfGas)?;
Ok(Syncable(self.fuel))
}
}
/// Used to capture the gas left before entering a host function.
///
/// Has to be consumed in order to sync back the gas after leaving the host function.
#[must_use]
pub struct RefTimeLeft(u64);
/// Resource that needs to be synced to the executor.
///
/// Wrapped to make sure that the resource will be synced back the the executor.
#[must_use]
pub struct Syncable(u64);
impl From<Syncable> for u64 {
fn from(from: Syncable) -> u64 {
from.0
}
}
#[cfg(not(test))]
pub trait TestAuxiliaries {}
#[cfg(not(test))]
impl<T> TestAuxiliaries for T {}
#[cfg(test)]
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
#[cfg(test)]
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
/// This trait represents a token that can be used for charging `GasMeter`.
/// There is no other way of charging it.
///
/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
/// for consistency). If inlined there should be no observable difference compared
/// to a hand-written code.
pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
/// Return the amount of gas that should be taken by this token.
///
/// This function should be really lightweight and must not fail. It is not
/// expected that implementors will query the storage or do any kinds of heavy operations.
///
/// That said, implementors of this function still can run into overflows
/// while calculating the amount. In this case it is ok to use saturating operations
/// since on overflow they will return `max_value` which should consume all gas.
fn weight(&self) -> Weight;
/// Returns true if this token is expected to influence the lowest gas limit.
fn influence_lowest_gas_limit(&self) -> bool {
true
}
}
/// A wrapper around a type-erased trait object of what used to be a `Token`.
#[cfg(test)]
pub struct ErasedToken {
pub description: String,
pub token: Box<dyn Any>,
}
#[derive(DefaultNoBound)]
pub struct GasMeter<T: Config> {
gas_limit: Weight,
/// Amount of gas left from initial gas limit. Can reach zero.
gas_left: Weight,
/// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value.
gas_left_lowest: Weight,
/// The amount of resources that was consumed by the execution engine.
/// We have to track it separately in order to avoid the loss of precision that happens when
/// converting from ref_time to the execution engine unit.
engine_meter: EngineMeter<T>,
_phantom: PhantomData<T>,
#[cfg(test)]
tokens: Vec<ErasedToken>,
}
impl<T: Config> GasMeter<T> {
pub fn new(gas_limit: Weight) -> Self {
GasMeter {
gas_limit,
gas_left: gas_limit,
gas_left_lowest: gas_limit,
engine_meter: EngineMeter::new(gas_limit),
_phantom: PhantomData,
#[cfg(test)]
tokens: Vec::new(),
}
}
/// Create a new gas meter by removing gas from the current meter.
///
/// # Note
///
/// Passing `0` as amount is interpreted as "all remaining gas".
pub fn nested(&mut self, amount: Weight) -> Self {
let amount = Weight::from_parts(
if amount.ref_time().is_zero() {
self.gas_left().ref_time()
} else {
amount.ref_time()
},
if amount.proof_size().is_zero() {
self.gas_left().proof_size()
} else {
amount.proof_size()
},
)
.min(self.gas_left);
self.gas_left -= amount;
GasMeter::new(amount)
}
/// Absorb the remaining gas of a nested meter after we are done using it.
pub fn absorb_nested(&mut self, nested: Self) {
self.gas_left_lowest = (self.gas_left + nested.gas_limit)
.saturating_sub(nested.gas_required())
.min(self.gas_left_lowest);
self.gas_left += nested.gas_left;
}
/// Account for used gas.
///
/// Amount is calculated by the given `token`.
///
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
/// amount of gas has lead to overflow.
///
/// NOTE that amount isn't consumed if there is not enough gas. This is considered
/// safe because we always charge gas before performing any resource-spending action.
#[inline]
pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
#[cfg(test)]
{
// Unconditionally add the token to the storage.
let erased_tok =
ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
self.tokens.push(erased_tok);
}
let amount = token.weight();
// It is OK to not charge anything on failure because we always charge _before_ we perform
// any action
self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::<T>::OutOfGas)?;
Ok(ChargedAmount(amount))
}
/// Adjust a previously charged amount down to its actual amount.
///
/// This is when a maximum a priori amount was charged and then should be partially
/// refunded to match the actual amount.
pub fn adjust_gas<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
if token.influence_lowest_gas_limit() {
self.gas_left_lowest = self.gas_left_lowest();
}
let adjustment = charged_amount.0.saturating_sub(token.weight());
self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
}
/// Hand over the gas metering responsibility from the executor to this meter.
///
/// Needs to be called when entering a host function to update this meter with the
/// gas that was tracked by the executor. It tracks the latest seen total value
/// in order to compute the delta that needs to be charged.
pub fn sync_from_executor(&mut self, engine_fuel: u64) -> Result<RefTimeLeft, DispatchError> {
let weight_consumed = self.engine_meter.set_fuel(engine_fuel);
self.gas_left
.checked_reduce(weight_consumed)
.ok_or_else(|| Error::<T>::OutOfGas)?;
Ok(RefTimeLeft(self.gas_left.ref_time()))
}
/// Hand over the gas metering responsibility from this meter to the executor.
///
/// Needs to be called when leaving a host function in order to calculate how much
/// gas needs to be charged from the **executor**. It updates the last seen executor
/// total value so that it is correct when `sync_from_executor` is called the next time.
///
/// It is important that this does **not** actually sync with the executor. That has
/// to be done by the caller.
pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result<Syncable, DispatchError> {
let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time());
self.engine_meter.charge_ref_time(ref_time_consumed)
}
/// Returns the amount of gas that is required to run the same call.
///
/// This can be different from `gas_spent` because due to `adjust_gas` the amount of
/// spent gas can temporarily drop and be refunded later.
pub fn gas_required(&self) -> Weight {
self.gas_limit.saturating_sub(self.gas_left_lowest())
}
/// Returns how much gas was spent
pub fn gas_consumed(&self) -> Weight {
self.gas_limit.saturating_sub(self.gas_left)
}
/// Returns how much gas left from the initial budget.
pub fn gas_left(&self) -> Weight {
self.gas_left
}
/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
pub fn into_dispatch_result<R, E>(
self,
result: Result<R, E>,
base_weight: Weight,
) -> DispatchResultWithPostInfo
where
E: Into<ExecError>,
{
let post_info = PostDispatchInfo {
actual_weight: Some(self.gas_consumed().saturating_add(base_weight)),
pays_fee: Default::default(),
};
result
.map(|_| post_info)
.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
}
fn gas_left_lowest(&self) -> Weight {
self.gas_left_lowest.min(self.gas_left)
}
#[cfg(test)]
pub fn tokens(&self) -> &[ErasedToken] {
&self.tokens
}
}
#[cfg(test)]
mod tests {
use super::{GasMeter, Token, Weight};
use crate::tests::Test;
/// A simple utility macro that helps to match against a
/// list of tokens.
macro_rules! match_tokens {
($tokens_iter:ident,) => {
};
($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
{
let next = ($tokens_iter).next().unwrap();
let pattern = $x;
// Note that we don't specify the type name directly in this macro,
// we only have some expression $x of some type. At the same time, we
// have an iterator of Box<dyn Any> and to downcast we need to specify
// the type which we want downcast to.
//
// So what we do is we assign `_pattern_typed_next_ref` to a variable which has
// the required type.
//
// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
let mut _pattern_typed_next_ref = &pattern;
_pattern_typed_next_ref = match next.token.downcast_ref() {
Some(p) => {
assert_eq!(p, &pattern);
p
}
None => {
panic!("expected type {} got {}", stringify!($x), next.description);
}
};
}
match_tokens!($tokens_iter, $($rest)*);
};
}
/// A trivial token that charges the specified number of gas units.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SimpleToken(u64);
impl Token<Test> for SimpleToken {
fn weight(&self) -> Weight {
Weight::from_parts(self.0, 0)
}
}
#[test]
fn it_works() {
let gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0));
}
#[test]
fn tracing() {
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
assert!(!gas_meter.charge(SimpleToken(1)).is_err());
let mut tokens = gas_meter.tokens().iter();
match_tokens!(tokens, SimpleToken(1),);
}
// This test makes sure that nothing can be executed if there is no gas.
#[test]
fn refuse_to_execute_anything_if_zero() {
let mut gas_meter = GasMeter::<Test>::new(Weight::zero());
assert!(gas_meter.charge(SimpleToken(1)).is_err());
}
// Make sure that the gas meter does not charge in case of overcharge
#[test]
fn overcharge_does_not_charge() {
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(200, 0));
// The first charge is should lead to OOG.
assert!(gas_meter.charge(SimpleToken(300)).is_err());
// The gas meter should still contain the full 200.
assert!(gas_meter.charge(SimpleToken(200)).is_ok());
}
// Charging the exact amount that the user paid for should be
// possible.
#[test]
fn charge_exact_amount() {
let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(25, 0));
assert!(!gas_meter.charge(SimpleToken(25)).is_err());
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,658 @@
// 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.
//! Multi-block Migration framework for pezpallet-contracts.
//!
//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be
//! executed across multiple blocks.
//!
//! # Usage
//!
//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number.
//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`.
//!
//! ## Example:
//!
//! To configure a migration to `v11` for a runtime using `v10` of pezpallet-contracts on the chain,
//! you would set the `Migrations` type as follows:
//!
//! ```
//! use pezpallet_contracts::migration::{v10, v11};
//! # pub enum Runtime {};
//! # struct Currency;
//! type Migrations = (v10::Migration<Runtime, Currency>, v11::Migration<Runtime>);
//! ```
//!
//! ## Notes:
//!
//! - Migrations should always be tested with `try-runtime` before being deployed.
//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work
//! and that you have included the required steps.
//!
//! ## Low Level / Implementation Details
//!
//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of
//! performing the actual migration, we set a custom storage item [`MigrationInProgress`].
//! This storage item defines a [`Cursor`] for the current migration.
//!
//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its
//! value holds a cursor for the current migration step. These migration steps are executed during
//! [`Hooks<BlockNumber>::on_idle`] or when the [`Pallet::migrate`] dispatchable is
//! called.
//!
//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns
//! a `MigrationInProgress` error.
pub mod v09;
pub mod v10;
pub mod v11;
pub mod v12;
pub mod v13;
pub mod v14;
pub mod v15;
pub mod v16;
include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs"));
use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET};
use codec::{Codec, Decode};
use core::marker::PhantomData;
use pezframe_support::{
pezpallet_prelude::*,
traits::{ConstU32, OnRuntimeUpgrade},
weights::WeightMeter,
};
use pezsp_runtime::Saturating;
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed";
const PROOF_DECODE: &str =
"We encode to the same type in this trait only. No other code touches this item; qed";
fn invalid_version(version: StorageVersion) -> ! {
panic!("Required migration {version:?} not supported by this runtime. This is a bug.");
}
/// The cursor used to encode the position (usually the last iterated key) of the current migration
/// step.
pub type Cursor = BoundedVec<u8, ConstU32<1024>>;
/// IsFinished describes whether a migration is finished or not.
pub enum IsFinished {
Yes,
No,
}
/// A trait that allows to migrate storage from one version to another.
///
/// The migration is done in steps. The migration is finished when
/// `step()` returns `IsFinished::Yes`.
pub trait MigrationStep: Codec + MaxEncodedLen + Default {
/// Returns the version of the migration.
const VERSION: u16;
/// Returns the maximum weight that can be consumed in a single step.
fn max_step_weight() -> Weight;
/// Process one step of the migration.
///
/// Returns whether the migration is finished.
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished;
/// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater
/// than `max_block_weight`.
fn integrity_test(max_block_weight: Weight) {
if Self::max_step_weight().any_gt(max_block_weight) {
panic!(
"Invalid max_step_weight for Migration {}. Value should be lower than {}",
Self::VERSION,
max_block_weight
);
}
let len = <Self as MaxEncodedLen>::max_encoded_len();
let max = Cursor::bound();
if len > max {
panic!(
"Migration {} has size {} which is bigger than the maximum of {}",
Self::VERSION,
len,
max,
);
}
}
/// Execute some pre-checks prior to running the first step of this migration.
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
Ok(Vec::new())
}
/// Execute some post-checks after running the last step of this migration.
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
Ok(())
}
}
/// A noop migration that can be used when there is no migration to be done for a given version.
#[doc(hidden)]
#[derive(pezframe_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)]
pub struct NoopMigration<const N: u16>;
impl<const N: u16> MigrationStep for NoopMigration<N> {
const VERSION: u16 = N;
fn max_step_weight() -> Weight {
Weight::zero()
}
fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished {
log::debug!(target: LOG_TARGET, "Noop migration for version {}", N);
IsFinished::Yes
}
}
mod private {
use crate::migration::MigrationStep;
pub trait Sealed {}
#[impl_trait_for_tuples::impl_for_tuples(10)]
#[tuple_types_custom_trait_bound(MigrationStep)]
impl Sealed for Tuple {}
}
/// Defines a sequence of migrations.
///
/// The sequence must be defined by a tuple of migrations, each of which must implement the
/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps.
pub trait MigrateSequence: private::Sealed {
/// Returns the range of versions that this migrations sequence can handle.
/// Migrations must be ordered by their versions with no gaps.
///
/// The following code will fail to compile:
///
/// ```compile_fail
/// # use pezpallet_contracts::{NoopMigration, MigrateSequence};
/// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE;
/// ```
/// The following code will compile:
/// ```
/// # use pezpallet_contracts::{NoopMigration, MigrateSequence};
/// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE;
/// ```
const VERSION_RANGE: (u16, u16);
/// Returns the default cursor for the given version.
fn new(version: StorageVersion) -> Cursor;
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step(_version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
Ok(Vec::new())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(_version: StorageVersion, _state: Vec<u8>) -> Result<(), TryRuntimeError> {
Ok(())
}
/// Execute the migration step until the available weight is consumed.
fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult;
/// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater
/// than `max_block_weight`.
fn integrity_test(max_block_weight: Weight);
/// Returns whether migrating from `in_storage` to `target` is supported.
///
/// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target).
fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool {
let (low, high) = Self::VERSION_RANGE;
target == high && in_storage + 1 == low
}
}
/// Performs all necessary migrations based on `StorageVersion`.
///
/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations
/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step
/// by step migration works.
pub struct Migration<T: Config, const TEST_ALL_STEPS: bool = true>(PhantomData<T>);
#[cfg(feature = "try-runtime")]
impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
fn run_all_steps() -> Result<(), TryRuntimeError> {
let mut meter = &mut WeightMeter::new();
let name = <Pallet<T>>::name();
loop {
let in_progress_version = <Pallet<T>>::on_chain_storage_version() + 1;
let state = T::Migrations::pre_upgrade_step(in_progress_version)?;
let before = meter.consumed();
let status = Self::migrate(&mut meter);
log::info!(
target: LOG_TARGET,
"{name}: Migration step {:?} weight = {}",
in_progress_version,
meter.consumed() - before
);
T::Migrations::post_upgrade_step(in_progress_version, state)?;
if matches!(status, MigrateResult::Completed) {
break;
}
}
let name = <Pallet<T>>::name();
log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed());
Ok(())
}
}
impl<T: Config, const TEST_ALL_STEPS: bool> OnRuntimeUpgrade for Migration<T, TEST_ALL_STEPS> {
fn on_runtime_upgrade() -> Weight {
let name = <Pallet<T>>::name();
let in_code_version = <Pallet<T>>::in_code_storage_version();
let on_chain_version = <Pallet<T>>::on_chain_storage_version();
if on_chain_version == in_code_version {
log::warn!(
target: LOG_TARGET,
"{name}: No Migration performed storage_version = latest_version = {:?}",
&on_chain_version
);
return T::WeightInfo::on_runtime_upgrade_noop();
}
// In case a migration is already in progress we create the next migration
// (if any) right when the current one finishes.
if Self::in_progress() {
log::warn!(
target: LOG_TARGET,
"{name}: Migration already in progress {:?}",
&on_chain_version
);
return T::WeightInfo::on_runtime_upgrade_in_progress();
}
log::info!(
target: LOG_TARGET,
"{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.",
);
let cursor = T::Migrations::new(on_chain_version + 1);
MigrationInProgress::<T>::set(Some(cursor));
#[cfg(feature = "try-runtime")]
if TEST_ALL_STEPS {
Self::run_all_steps().unwrap();
}
T::WeightInfo::on_runtime_upgrade()
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
// We can't really do much here as our migrations do not happen during the runtime upgrade.
// Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate
// over our migrations.
let on_chain_version = <Pallet<T>>::on_chain_storage_version();
let in_code_version = <Pallet<T>>::in_code_storage_version();
if on_chain_version == in_code_version {
return Ok(Default::default());
}
log::debug!(
target: LOG_TARGET,
"Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)",
<Pallet<T>>::name(), on_chain_version, in_code_version
);
ensure!(
T::Migrations::is_upgrade_supported(on_chain_version, in_code_version),
"Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)"
);
Ok(Default::default())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
if !TEST_ALL_STEPS {
return Ok(());
}
log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ===");
// Ensure that the hashing algorithm is correct for each storage map.
if let Some(hash) = crate::CodeInfoOf::<T>::iter_keys().next() {
crate::CodeInfoOf::<T>::get(hash).expect("CodeInfo exists for hash; qed");
}
if let Some(hash) = crate::PristineCode::<T>::iter_keys().next() {
crate::PristineCode::<T>::get(hash).expect("PristineCode exists for hash; qed");
}
if let Some(account_id) = crate::ContractInfoOf::<T>::iter_keys().next() {
crate::ContractInfoOf::<T>::get(account_id)
.expect("ContractInfo exists for account_id; qed");
}
if let Some(nonce) = crate::DeletionQueue::<T>::iter_keys().next() {
crate::DeletionQueue::<T>::get(nonce).expect("DeletionQueue exists for nonce; qed");
}
Ok(())
}
}
/// The result of running the migration.
#[derive(Debug, PartialEq)]
pub enum MigrateResult {
/// No migration was performed
NoMigrationPerformed,
/// No migration currently in progress
NoMigrationInProgress,
/// A migration is in progress
InProgress { steps_done: u32 },
/// All migrations are completed
Completed,
}
/// The result of running a migration step.
#[derive(Debug, PartialEq)]
pub enum StepResult {
InProgress { cursor: Cursor, steps_done: u32 },
Completed { steps_done: u32 },
}
impl<T: Config, const TEST_ALL_STEPS: bool> Migration<T, TEST_ALL_STEPS> {
/// Verify that each migration's step of the [`Config::Migrations`] sequence fits into
/// `Cursor`.
pub(crate) fn integrity_test() {
let max_weight = <T as pezframe_system::Config>::BlockWeights::get().max_block;
T::Migrations::integrity_test(max_weight)
}
/// Execute the multi-step migration.
/// Returns whether or not a migration is in progress
pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult {
let name = <Pallet<T>>::name();
if meter.try_consume(T::WeightInfo::migrate()).is_err() {
return MigrateResult::NoMigrationPerformed;
}
MigrationInProgress::<T>::mutate_exists(|progress| {
let Some(cursor_before) = progress.as_mut() else {
meter.consume(T::WeightInfo::migration_noop());
return MigrateResult::NoMigrationInProgress;
};
// if a migration is running it is always upgrading to the next version
let storage_version = <Pallet<T>>::on_chain_storage_version();
let in_progress_version = storage_version + 1;
log::info!(
target: LOG_TARGET,
"{name}: Migrating from {:?} to {:?},",
storage_version,
in_progress_version,
);
let result =
match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter)
{
StepResult::InProgress { cursor, steps_done } => {
*progress = Some(cursor);
MigrateResult::InProgress { steps_done }
},
StepResult::Completed { steps_done } => {
in_progress_version.put::<Pallet<T>>();
if <Pallet<T>>::in_code_storage_version() != in_progress_version {
log::info!(
target: LOG_TARGET,
"{name}: Next migration is {:?},",
in_progress_version + 1
);
*progress = Some(T::Migrations::new(in_progress_version + 1));
MigrateResult::InProgress { steps_done }
} else {
log::info!(
target: LOG_TARGET,
"{name}: All migrations done. At version {:?},",
in_progress_version
);
*progress = None;
MigrateResult::Completed
}
},
};
result
})
}
pub(crate) fn ensure_migrated() -> DispatchResult {
if Self::in_progress() {
Err(Error::<T>::MigrationInProgress.into())
} else {
Ok(())
}
}
pub(crate) fn in_progress() -> bool {
MigrationInProgress::<T>::exists()
}
}
#[impl_trait_for_tuples::impl_for_tuples(10)]
#[tuple_types_custom_trait_bound(MigrationStep)]
impl MigrateSequence for Tuple {
const VERSION_RANGE: (u16, u16) = {
let mut versions: (u16, u16) = (0, 0);
for_tuples!(
#(
match versions {
(0, 0) => {
versions = (Tuple::VERSION, Tuple::VERSION);
},
(min_version, last_version) if Tuple::VERSION == last_version + 1 => {
versions = (min_version, Tuple::VERSION);
},
_ => panic!("Migrations must be ordered by their versions with no gaps.")
}
)*
);
versions
};
fn new(version: StorageVersion) -> Cursor {
for_tuples!(
#(
if version == Tuple::VERSION {
return Tuple::default().encode().try_into().expect(PROOF_ENCODE)
}
)*
);
invalid_version(version)
}
#[cfg(feature = "try-runtime")]
/// Execute the pre-checks of the step associated with this version.
fn pre_upgrade_step(version: StorageVersion) -> Result<Vec<u8>, TryRuntimeError> {
for_tuples!(
#(
if version == Tuple::VERSION {
return Tuple::pre_upgrade_step()
}
)*
);
invalid_version(version)
}
#[cfg(feature = "try-runtime")]
/// Execute the post-checks of the step associated with this version.
fn post_upgrade_step(version: StorageVersion, state: Vec<u8>) -> Result<(), TryRuntimeError> {
for_tuples!(
#(
if version == Tuple::VERSION {
return Tuple::post_upgrade_step(state)
}
)*
);
invalid_version(version)
}
fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult {
for_tuples!(
#(
if version == Tuple::VERSION {
let mut migration = <Tuple as Decode>::decode(&mut cursor)
.expect(PROOF_DECODE);
let max_weight = Tuple::max_step_weight();
let mut steps_done = 0;
while meter.can_consume(max_weight) {
steps_done.saturating_accrue(1);
if matches!(migration.step(meter), IsFinished::Yes) {
return StepResult::Completed{ steps_done }
}
}
return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done }
}
)*
);
invalid_version(version)
}
fn integrity_test(max_block_weight: Weight) {
for_tuples!(
#(
Tuple::integrity_test(max_block_weight);
)*
);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
migration::codegen::LATEST_MIGRATION_VERSION,
tests::{ExtBuilder, Test},
};
#[derive(Default, Encode, Decode, MaxEncodedLen)]
struct MockMigration<const N: u16> {
// MockMigration<N> needs `N` steps to finish
count: u16,
}
impl<const N: u16> MigrationStep for MockMigration<N> {
const VERSION: u16 = N;
fn max_step_weight() -> Weight {
Weight::from_all(1)
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
assert!(self.count != N);
self.count += 1;
meter.consume(Weight::from_all(1));
if self.count == N {
IsFinished::Yes
} else {
IsFinished::No
}
}
}
#[test]
fn test_storage_version_matches_last_migration_file() {
assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION);
}
#[test]
fn version_range_works() {
let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE;
assert_eq!(range, (1, 2));
}
#[test]
fn is_upgrade_supported_works() {
type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>);
assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11)));
assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11)));
assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12)));
}
#[test]
fn steps_works() {
type Migrations = (MockMigration<2>, MockMigration<3>);
let version = StorageVersion::new(2);
let mut cursor = Migrations::new(version);
let mut meter = WeightMeter::with_limit(Weight::from_all(1));
let result = Migrations::steps(version, &cursor, &mut meter);
cursor = alloc::vec![1u8, 0].try_into().unwrap();
assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 });
assert_eq!(meter.consumed(), Weight::from_all(1));
let mut meter = WeightMeter::with_limit(Weight::from_all(1));
assert_eq!(
Migrations::steps(version, &cursor, &mut meter),
StepResult::Completed { steps_done: 1 }
);
}
#[test]
fn no_migration_in_progress_works() {
type TestMigration = Migration<Test>;
ExtBuilder::default().build().execute_with(|| {
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
assert_eq!(
TestMigration::migrate(&mut WeightMeter::new()),
MigrateResult::NoMigrationInProgress
)
});
}
#[test]
fn migration_works() {
type TestMigration = Migration<Test, false>;
ExtBuilder::default()
.set_storage_version(LATEST_MIGRATION_VERSION - 2)
.build()
.execute_with(|| {
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION - 2);
TestMigration::on_runtime_upgrade();
for (version, status) in [
(LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }),
(LATEST_MIGRATION_VERSION, MigrateResult::Completed),
] {
assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status);
assert_eq!(
<Pallet<Test>>::on_chain_storage_version(),
StorageVersion::new(version)
);
}
assert_eq!(
TestMigration::migrate(&mut WeightMeter::new()),
MigrateResult::NoMigrationInProgress
);
assert_eq!(StorageVersion::get::<Pallet<Test>>(), LATEST_MIGRATION_VERSION);
});
}
}
@@ -0,0 +1,148 @@
// 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.
//! Update `CodeStorage` with the new `determinism` field.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use pezframe_support::{
pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound, Identity,
};
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
mod v8 {
use super::*;
#[derive(Encode, Decode)]
pub struct PrefabWasmModule {
#[codec(compact)]
pub instruction_weights_version: u32,
#[codec(compact)]
pub initial: u32,
#[codec(compact)]
pub maximum: u32,
pub code: Vec<u8>,
}
#[storage_alias]
pub type CodeStorage<T: Config> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
}
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_dummy_code<T: Config>(len: usize) {
use pezsp_runtime::traits::Hash;
let module = v8::PrefabWasmModule {
instruction_weights_version: 0,
initial: 0,
maximum: 0,
code: alloc::vec![42u8; len],
};
let hash = T::Hashing::hash(&module.code);
v8::CodeStorage::<T>::insert(hash, module);
}
#[derive(Encode, Decode)]
struct PrefabWasmModule {
#[codec(compact)]
pub instruction_weights_version: u32,
#[codec(compact)]
pub initial: u32,
#[codec(compact)]
pub maximum: u32,
pub code: Vec<u8>,
pub determinism: Determinism,
}
#[storage_alias]
type CodeStorage<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
last_code_hash: Option<CodeHash<T>>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 9;
fn max_step_weight() -> Weight {
T::WeightInfo::v9_migration_step(T::MaxCodeLen::get())
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
v8::CodeStorage::<T>::iter_from(v8::CodeStorage::<T>::hashed_key_for(last_key))
} else {
v8::CodeStorage::<T>::iter()
};
if let Some((key, old)) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating contract code {:?}", key);
let len = old.code.len() as u32;
let module = PrefabWasmModule {
instruction_weights_version: old.instruction_weights_version,
initial: old.initial,
maximum: old.maximum,
code: old.code,
determinism: Determinism::Enforced,
};
CodeStorage::<T>::insert(key, module);
self.last_code_hash = Some(key);
meter.consume(T::WeightInfo::v9_migration_step(len));
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "No more contracts code to migrate");
meter.consume(T::WeightInfo::v9_migration_step(0));
IsFinished::Yes
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let sample: Vec<_> = v8::CodeStorage::<T>::iter().take(100).collect();
log::debug!(target: LOG_TARGET, "Taking sample of {} contract codes", sample.len());
Ok(sample.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let sample = <Vec<(CodeHash<T>, v8::PrefabWasmModule)> as Decode>::decode(&mut &state[..])
.expect("pre_upgrade_step provides a valid state; qed");
log::debug!(target: LOG_TARGET, "Validating sample of {} contract codes", sample.len());
for (code_hash, old) in sample {
let module = CodeStorage::<T>::get(&code_hash).unwrap();
ensure!(
module.instruction_weights_version == old.instruction_weights_version,
"invalid instruction weights version"
);
ensure!(module.determinism == Determinism::Enforced, "invalid determinism");
ensure!(module.initial == old.initial, "invalid initial");
ensure!(module.maximum == old.maximum, "invalid maximum");
ensure!(module.code == old.code, "invalid code");
}
Ok(())
}
}
@@ -0,0 +1,322 @@
// 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.
//! Don't rely on reserved balances keeping an account alive
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/44>.
use crate::{
exec::AccountIdOf,
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
};
use codec::{Decode, Encode};
use core::{
cmp::{max, min},
ops::Deref,
};
use pezframe_support::{
pezpallet_prelude::*,
storage_alias,
traits::{
tokens::{fungible::Inspect, Fortitude::Polite, Preservation::Preserve},
ExistenceRequirement, ReservableCurrency,
},
weights::WeightMeter,
DefaultNoBound,
};
use pezsp_core::hexdisplay::HexDisplay;
use pezsp_runtime::{
traits::{Hash, TrailingZeroInput, Zero},
Perbill, Saturating,
};
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
mod v9 {
use super::*;
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
<T as pezframe_system::Config>::AccountId,
>>::Balance;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T, OldCurrency))]
pub struct ContractInfo<T: Config, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
pub trie_id: TrieId,
pub code_hash: CodeHash<T>,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: BalanceOf<T, OldCurrency>,
pub storage_item_deposit: BalanceOf<T, OldCurrency>,
pub storage_base_deposit: BalanceOf<T, OldCurrency>,
}
#[storage_alias]
pub type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
Pallet<T>,
Twox64Concat,
<T as pezframe_system::Config>::AccountId,
ContractInfo<T, OldCurrency>,
>;
}
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_contract_info<T: Config, OldCurrency>(
account: T::AccountId,
info: crate::ContractInfo<T>,
) where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
{
let info = v9::ContractInfo {
trie_id: info.trie_id,
code_hash: info.code_hash,
storage_bytes: Default::default(),
storage_items: Default::default(),
storage_byte_deposit: Default::default(),
storage_item_deposit: Default::default(),
storage_base_deposit: Default::default(),
};
v9::ContractInfoOf::<T, OldCurrency>::insert(account, info);
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct DepositAccount<T: Config>(AccountIdOf<T>);
impl<T: Config> Deref for DepositAccount<T> {
type Target = AccountIdOf<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T, OldCurrency))]
pub struct ContractInfo<T: Config, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
pub trie_id: TrieId,
deposit_account: DepositAccount<T>,
pub code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
pub storage_byte_deposit: v9::BalanceOf<T, OldCurrency>,
storage_item_deposit: v9::BalanceOf<T, OldCurrency>,
storage_base_deposit: v9::BalanceOf<T, OldCurrency>,
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config, OldCurrency = ()> {
last_account: Option<T::AccountId>,
_phantom: PhantomData<(T, OldCurrency)>,
}
#[storage_alias]
type ContractInfoOf<T: Config, OldCurrency> = StorageMap<
Pallet<T>,
Twox64Concat,
<T as pezframe_system::Config>::AccountId,
ContractInfo<T, OldCurrency>,
>;
/// Formula: `hash("contract_depo_v1" ++ contract_addr)`
fn deposit_address<T: Config>(
contract_addr: &<T as pezframe_system::Config>::AccountId,
) -> <T as pezframe_system::Config>::AccountId {
let entropy = (b"contract_depo_v1", contract_addr)
.using_encoded(<T as pezframe_system::Config>::Hashing::hash);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed")
}
impl<T: Config, OldCurrency: 'static> MigrationStep for Migration<T, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>
+ Inspect<<T as pezframe_system::Config>::AccountId, Balance = v9::BalanceOf<T, OldCurrency>>,
{
const VERSION: u16 = 10;
fn max_step_weight() -> Weight {
T::WeightInfo::v10_migration_step()
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_account) = self.last_account.take() {
v9::ContractInfoOf::<T, OldCurrency>::iter_from(
v9::ContractInfoOf::<T, OldCurrency>::hashed_key_for(last_account),
)
} else {
v9::ContractInfoOf::<T, OldCurrency>::iter()
};
if let Some((account, contract)) = iter.next() {
let min_balance = <OldCurrency as pezframe_support::traits::Currency<
<T as pezframe_system::Config>::AccountId,
>>::minimum_balance();
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
// Get the new deposit account address
let deposit_account: DepositAccount<T> = DepositAccount(deposit_address::<T>(&account));
// Calculate the existing deposit, that should be reserved on the contract account
let old_deposit = contract
.storage_base_deposit
.saturating_add(contract.storage_item_deposit)
.saturating_add(contract.storage_byte_deposit);
// Unreserve the existing deposit
// Note we can't use repatriate_reserve, because it only works with existing accounts
let remaining = OldCurrency::unreserve(&account, old_deposit);
if !remaining.is_zero() {
log::warn!(
target: LOG_TARGET,
"Partially unreserved. Remaining {:?} out of {:?} asked",
remaining,
old_deposit
);
}
// Attempt to transfer the old deposit to the deposit account.
let amount = old_deposit
.saturating_sub(min_balance)
.min(OldCurrency::reducible_balance(&account, Preserve, Polite));
let new_deposit = OldCurrency::transfer(
&account,
&deposit_account,
amount,
ExistenceRequirement::KeepAlive,
)
.map(|_| {
log::debug!(
target: LOG_TARGET,
"Transferred deposit ({:?}) to deposit account",
amount
);
amount
})
// If it fails we fallback to minting the ED.
.unwrap_or_else(|err| {
log::error!(
target: LOG_TARGET,
"Failed to transfer the base deposit, reason: {:?}",
err
);
let _ = OldCurrency::deposit_creating(&deposit_account, min_balance);
min_balance
});
// Calculate the new base_deposit to store in the contract:
// Ideally, it should be the same as the old one
// Ideally, it should be at least 2xED (for the contract and deposit accounts).
// It can't be more than the `new_deposit`.
let new_base_deposit = min(
max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)),
new_deposit,
);
// Calculate the ratio to adjust storage_byte and storage_item deposits.
let new_deposit_without_base = new_deposit.saturating_sub(new_base_deposit);
let old_deposit_without_base =
old_deposit.saturating_sub(contract.storage_base_deposit);
let ratio = Perbill::from_rational(new_deposit_without_base, old_deposit_without_base);
// Calculate the new storage deposits based on the ratio
let storage_byte_deposit = ratio.mul_ceil(contract.storage_byte_deposit);
let storage_item_deposit = ratio.mul_ceil(contract.storage_item_deposit);
// Recalculate the new base deposit, instead of using new_base_deposit to avoid rounding
// errors
let storage_base_deposit = new_deposit
.saturating_sub(storage_byte_deposit)
.saturating_sub(storage_item_deposit);
let new_contract_info = ContractInfo {
trie_id: contract.trie_id,
deposit_account,
code_hash: contract.code_hash,
storage_bytes: contract.storage_bytes,
storage_items: contract.storage_items,
storage_byte_deposit,
storage_item_deposit,
storage_base_deposit,
};
ContractInfoOf::<T, OldCurrency>::insert(&account, new_contract_info);
// Store last key for next migration step
self.last_account = Some(account);
meter.consume(T::WeightInfo::v10_migration_step());
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "Done Migrating contract info");
meter.consume(T::WeightInfo::v10_migration_step());
IsFinished::Yes
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let sample: Vec<_> = v9::ContractInfoOf::<T, OldCurrency>::iter().take(10).collect();
log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
Ok(sample.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let sample = <Vec<(T::AccountId, v9::ContractInfo<T, OldCurrency>)> as Decode>::decode(
&mut &state[..],
)
.expect("pre_upgrade_step provides a valid state; qed");
log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
for (account, old_contract) in sample {
log::debug!(target: LOG_TARGET, "===");
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
let contract = ContractInfoOf::<T, OldCurrency>::get(&account).unwrap();
ensure!(old_contract.trie_id == contract.trie_id, "invalid trie_id");
ensure!(old_contract.code_hash == contract.code_hash, "invalid code_hash");
ensure!(old_contract.storage_bytes == contract.storage_bytes, "invalid storage_bytes");
ensure!(old_contract.storage_items == contract.storage_items, "invalid storage_items");
let deposit = <OldCurrency as pezframe_support::traits::Currency<_>>::total_balance(
&contract.deposit_account,
);
ensure!(
deposit ==
contract
.storage_base_deposit
.saturating_add(contract.storage_item_deposit)
.saturating_add(contract.storage_byte_deposit),
"deposit mismatch"
);
}
Ok(())
}
}
@@ -0,0 +1,136 @@
// 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.
//! Overflowing bounded DeletionQueue.
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/47>.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
Config, Pallet, TrieId, Weight, LOG_TARGET,
};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use core::marker::PhantomData;
use pezframe_support::{pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound};
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
mod v10 {
use super::*;
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct DeletedContract {
pub(crate) trie_id: TrieId,
}
#[storage_alias]
pub type DeletionQueue<T: Config> = StorageValue<Pallet<T>, Vec<DeletedContract>>;
}
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct DeletionQueueManager<T: Config> {
insert_counter: u32,
delete_counter: u32,
_phantom: PhantomData<T>,
}
#[cfg(any(feature = "runtime-benchmarks", feature = "try-runtime"))]
pub fn fill_old_queue<T: Config>(len: usize) {
let queue: Vec<v10::DeletedContract> =
core::iter::repeat_with(|| v10::DeletedContract { trie_id: Default::default() })
.take(len)
.collect();
v10::DeletionQueue::<T>::set(Some(queue));
}
#[storage_alias]
type DeletionQueue<T: Config> = StorageMap<Pallet<T>, Twox64Concat, u32, TrieId>;
#[storage_alias]
type DeletionQueueCounter<T: Config> = StorageValue<Pallet<T>, DeletionQueueManager<T>, ValueQuery>;
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
_phantom: PhantomData<T>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 11;
// It would be more correct to make our use the now removed [DeletionQueueDepth](https://github.com/pezkuwichain/kurdistan-sdk/issues/47/files#diff-70e9723e9db62816e35f6f885b6770a8449c75a6c2733e9fa7a245fe52c4656c)
// but in practice the queue is always empty, so 128 is a good enough approximation for not
// underestimating the weight of our migration.
fn max_step_weight() -> Weight {
T::WeightInfo::v11_migration_step(128)
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let Some(old_queue) = v10::DeletionQueue::<T>::take() else {
meter.consume(T::WeightInfo::v11_migration_step(0));
return IsFinished::Yes;
};
let len = old_queue.len();
log::debug!(
target: LOG_TARGET,
"Migrating deletion queue with {} deleted contracts",
old_queue.len()
);
if !old_queue.is_empty() {
let mut queue = DeletionQueueManager::<T>::default();
for contract in old_queue {
<DeletionQueue<T>>::insert(queue.insert_counter, contract.trie_id);
queue.insert_counter += 1;
}
<DeletionQueueCounter<T>>::set(queue);
}
meter.consume(T::WeightInfo::v11_migration_step(len as u32));
IsFinished::Yes
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let old_queue = v10::DeletionQueue::<T>::take().unwrap_or_default();
if old_queue.is_empty() {
let len = 10u32;
log::debug!(
target: LOG_TARGET,
"Injecting {len} entries to deletion queue to test migration"
);
fill_old_queue::<T>(len as usize);
return Ok(len.encode());
}
Ok((old_queue.len() as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let len = <u32 as Decode>::decode(&mut &state[..])
.expect("pre_upgrade_step provides a valid state; qed");
let counter = <DeletionQueueCounter<T>>::get();
ensure!(counter.insert_counter == len, "invalid insert counter");
ensure!(counter.delete_counter == 0, "invalid delete counter");
Ok(())
}
}
@@ -0,0 +1,351 @@
// 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.
//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and
//! repay deposits.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET,
};
use alloc::vec::Vec;
use codec::{Decode, Encode};
use pezframe_support::{
pezpallet_prelude::*, storage_alias, traits::ReservableCurrency, weights::WeightMeter,
DefaultNoBound, Identity,
};
use scale_info::prelude::format;
use pezsp_core::hexdisplay::HexDisplay;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
use pezsp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating};
mod v11 {
use super::*;
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
<T as pezframe_system::Config>::AccountId,
>>::Balance;
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T, OldCurrency))]
pub struct OwnerInfo<T: Config, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
pub owner: AccountIdOf<T>,
#[codec(compact)]
pub deposit: BalanceOf<T, OldCurrency>,
#[codec(compact)]
pub refcount: u64,
}
#[derive(Encode, Decode, scale_info::TypeInfo)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T))]
pub struct PrefabWasmModule {
#[codec(compact)]
pub instruction_weights_version: u32,
#[codec(compact)]
pub initial: u32,
#[codec(compact)]
pub maximum: u32,
pub code: Vec<u8>,
pub determinism: Determinism,
}
#[storage_alias]
pub type OwnerInfoOf<T: Config, OldCurrency> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, OwnerInfo<T, OldCurrency>>;
#[storage_alias]
pub type CodeStorage<T: Config> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, PrefabWasmModule>;
}
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T, OldCurrency))]
pub struct CodeInfo<T: Config, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
owner: AccountIdOf<T>,
#[codec(compact)]
deposit: v11::BalanceOf<T, OldCurrency>,
#[codec(compact)]
refcount: u64,
determinism: Determinism,
code_len: u32,
}
#[storage_alias]
pub type CodeInfoOf<T: Config, OldCurrency> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
#[storage_alias]
pub type PristineCode<T: Config> = StorageMap<Pallet<T>, Identity, CodeHash<T>, Vec<u8>>;
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_dummy_code<T: Config, OldCurrency>(len: usize, account: T::AccountId)
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
{
use pezsp_runtime::traits::Hash;
let code = alloc::vec![42u8; len];
let hash = T::Hashing::hash(&code);
PristineCode::<T>::insert(hash, code.clone());
let module = v11::PrefabWasmModule {
instruction_weights_version: Default::default(),
initial: Default::default(),
maximum: Default::default(),
code,
determinism: Determinism::Enforced,
};
v11::CodeStorage::<T>::insert(hash, module);
let info = v11::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX };
v11::OwnerInfoOf::<T, OldCurrency>::insert(hash, info);
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
OldCurrency::Balance: From<BalanceOf<T>>,
{
last_code_hash: Option<CodeHash<T>>,
_phantom: PhantomData<OldCurrency>,
}
impl<T: Config, OldCurrency> MigrationStep for Migration<T, OldCurrency>
where
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
OldCurrency::Balance: From<BalanceOf<T>>,
{
const VERSION: u16 = 12;
fn max_step_weight() -> Weight {
T::WeightInfo::v12_migration_step(T::MaxCodeLen::get())
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_key) = self.last_code_hash.take() {
v11::OwnerInfoOf::<T, OldCurrency>::iter_from(
v11::OwnerInfoOf::<T, OldCurrency>::hashed_key_for(last_key),
)
} else {
v11::OwnerInfoOf::<T, OldCurrency>::iter()
};
if let Some((hash, old_info)) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash);
let module = v11::CodeStorage::<T>::take(hash)
.expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str());
let code_len = module.code.len();
// We print this to measure the impact of the migration.
// Storage removed: deleted PrefabWasmModule's encoded len.
// Storage added: determinism field encoded len (as all other CodeInfo fields are the
// same as in the deleted OwnerInfo).
log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,);
// Storage usage prices could change over time, and accounts who uploaded their
// contracts code before the storage deposits where introduced, had not been ever
// charged with any deposit for that (see migration v6).
//
// This is why deposit to be refunded here is calculated as follows:
//
// 1. Calculate the deposit amount for storage before the migration, given current
// prices.
// 2. Given current reserved deposit amount, calculate the correction factor.
// 3. Calculate the deposit amount for storage after the migration, given current
// prices.
// 4. Calculate real deposit amount to be reserved after the migration.
let price_per_byte = T::DepositPerByte::get();
let price_per_item = T::DepositPerItem::get();
let bytes_before = module
.encoded_size()
.saturating_add(code_len)
.saturating_add(v11::OwnerInfo::<T, OldCurrency>::max_encoded_len())
as u32;
let items_before = 3u32;
let deposit_expected_before = price_per_byte
.saturating_mul(bytes_before.into())
.saturating_add(price_per_item.saturating_mul(items_before.into()));
let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
let bytes_after =
code_len.saturating_add(CodeInfo::<T, OldCurrency>::max_encoded_len()) as u32;
let items_after = 2u32;
let deposit_expected_after = price_per_byte
.saturating_mul(bytes_after.into())
.saturating_add(price_per_item.saturating_mul(items_after.into()));
let deposit = ratio.saturating_mul_int(deposit_expected_after);
let info = CodeInfo::<T, OldCurrency> {
determinism: module.determinism,
owner: old_info.owner,
deposit: deposit.into(),
refcount: old_info.refcount,
code_len: code_len as u32,
};
let amount = old_info.deposit.saturating_sub(info.deposit);
if !amount.is_zero() {
OldCurrency::unreserve(&info.owner, amount);
log::debug!(
target: LOG_TARGET,
"Deposit refunded: {:?} Balance, to: {:?}",
&amount,
HexDisplay::from(&info.owner.encode())
);
} else {
log::warn!(
target: LOG_TARGET,
"new deposit: {:?} >= old deposit: {:?}",
&info.deposit,
&old_info.deposit
);
}
CodeInfoOf::<T, OldCurrency>::insert(hash, info);
self.last_code_hash = Some(hash);
meter.consume(T::WeightInfo::v12_migration_step(code_len as u32));
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate");
meter.consume(T::WeightInfo::v12_migration_step(0));
IsFinished::Yes
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let len = 100;
log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len);
let sample: Vec<_> = v11::OwnerInfoOf::<T, OldCurrency>::iter()
.take(len)
.map(|(k, v)| {
let module = v11::CodeStorage::<T>::get(k)
.expect("No PrefabWasmModule found for code_hash: {:?}");
let info: CodeInfo<T, OldCurrency> = CodeInfo {
determinism: module.determinism,
deposit: v.deposit,
refcount: v.refcount,
owner: v.owner,
code_len: module.code.len() as u32,
};
(k, info)
})
.collect();
let storage: u32 =
v11::CodeStorage::<T>::iter().map(|(_k, v)| v.encoded_size() as u32).sum();
let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
v11::OwnerInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| deposit += v.deposit);
Ok((sample, deposit, storage).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let state = <(
Vec<(CodeHash<T>, CodeInfo<T, OldCurrency>)>,
v11::BalanceOf<T, OldCurrency>,
u32,
) as Decode>::decode(&mut &state[..])
.unwrap();
log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len());
for (hash, old) in state.0 {
let info = CodeInfoOf::<T, OldCurrency>::get(&hash)
.expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str());
ensure!(info.determinism == old.determinism, "invalid determinism");
ensure!(info.owner == old.owner, "invalid owner");
ensure!(info.refcount == old.refcount, "invalid refcount");
}
if let Some((k, _)) = v11::CodeStorage::<T>::iter().next() {
log::warn!(
target: LOG_TARGET,
"CodeStorage is still NOT empty, found code_hash: {:?}",
k
);
} else {
log::debug!(target: LOG_TARGET, "CodeStorage is empty.");
}
if let Some((k, _)) = v11::OwnerInfoOf::<T, OldCurrency>::iter().next() {
log::warn!(
target: LOG_TARGET,
"OwnerInfoOf is still NOT empty, found code_hash: {:?}",
k
);
} else {
log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty.");
}
let mut deposit: v11::BalanceOf<T, OldCurrency> = Default::default();
let mut items = 0u32;
let mut storage_info = 0u32;
CodeInfoOf::<T, OldCurrency>::iter().for_each(|(_k, v)| {
deposit += v.deposit;
items += 1;
storage_info += v.encoded_size() as u32;
});
let mut storage_code = 0u32;
PristineCode::<T>::iter().for_each(|(_k, v)| {
storage_code += v.len() as u32;
});
let (_, old_deposit, storage_module) = state;
// CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1
// I.e. code info adds up 1 byte per record.
let info_bytes_added = items;
// We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code.
let storage_removed = storage_module.saturating_sub(info_bytes_added);
// module+code+info - bytes
let storage_was = storage_module
.saturating_add(storage_code)
.saturating_add(storage_info)
.saturating_sub(info_bytes_added);
// We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3
// items per code).
let items_removed = items;
log::info!(
target: LOG_TARGET,
"Storage freed, bytes: {} (of {}), items: {} (of {})",
storage_removed,
storage_was,
items_removed,
items_removed * 3,
);
log::info!(
target: LOG_TARGET,
"Deposits returned, total: {:?} Balance (of {:?} Balance)",
old_deposit.saturating_sub(deposit),
old_deposit,
);
Ok(())
}
}
@@ -0,0 +1,136 @@
// 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.
//! Add `delegate_dependencies` to `ContractInfo`.
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/49>.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
AccountIdOf, BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET,
};
use codec::{Decode, Encode};
use pezframe_support::{pezpallet_prelude::*, storage_alias, weights::WeightMeter, DefaultNoBound};
use pezsp_runtime::BoundedBTreeMap;
mod v12 {
use super::*;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
pub trie_id: TrieId,
pub deposit_account: AccountIdOf<T>,
pub code_hash: CodeHash<T>,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
pub storage_item_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
}
#[storage_alias]
pub type ContractInfoOf<T: Config> = StorageMap<
Pallet<T>,
Twox64Concat,
<T as pezframe_system::Config>::AccountId,
ContractInfo<T>,
>;
}
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
let entropy = (b"contract_depo_v1", account.clone()).using_encoded(T::Hashing::hash);
let deposit_account = Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed");
let info = v12::ContractInfo {
trie_id: info.trie_id.clone(),
deposit_account,
code_hash: info.code_hash,
storage_bytes: Default::default(),
storage_items: Default::default(),
storage_byte_deposit: Default::default(),
storage_item_deposit: Default::default(),
storage_base_deposit: Default::default(),
};
v12::ContractInfoOf::<T>::insert(account, info);
}
#[storage_alias]
pub type ContractInfoOf<T: Config> =
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
trie_id: TrieId,
deposit_account: AccountIdOf<T>,
code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
storage_byte_deposit: BalanceOf<T>,
storage_item_deposit: BalanceOf<T>,
storage_base_deposit: BalanceOf<T>,
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
last_account: Option<T::AccountId>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 13;
fn max_step_weight() -> Weight {
T::WeightInfo::v13_migration_step()
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_account) = self.last_account.take() {
v12::ContractInfoOf::<T>::iter_from(v12::ContractInfoOf::<T>::hashed_key_for(
last_account,
))
} else {
v12::ContractInfoOf::<T>::iter()
};
if let Some((key, old)) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating contract {:?}", key);
let info = ContractInfo {
trie_id: old.trie_id,
deposit_account: old.deposit_account,
code_hash: old.code_hash,
storage_bytes: old.storage_bytes,
storage_items: old.storage_items,
storage_byte_deposit: old.storage_byte_deposit,
storage_item_deposit: old.storage_item_deposit,
storage_base_deposit: old.storage_base_deposit,
delegate_dependencies: Default::default(),
};
ContractInfoOf::<T>::insert(key.clone(), info);
self.last_account = Some(key);
meter.consume(T::WeightInfo::v13_migration_step());
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "No more contracts to migrate");
meter.consume(T::WeightInfo::v13_migration_step());
IsFinished::Yes
}
}
}
@@ -0,0 +1,274 @@
// 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.
//! Update the code owner balance, make the code upload deposit balance to be held instead of
//! reserved. Since [`Currency`](pezframe_support::traits::Currency) has been
//! [deprecated](https://github.com/pezkuwichain/kurdistan-sdk/issues/40), we need the deposits to be
//! handled by the [`pezframe_support::traits::fungible`] traits.
use crate::{
exec::AccountIdOf,
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
BalanceOf, CodeHash, Config, Determinism, HoldReason, Pallet, Weight, LOG_TARGET,
};
#[cfg(feature = "try-runtime")]
use alloc::collections::btree_map::BTreeMap;
use codec::{Decode, Encode};
#[cfg(feature = "try-runtime")]
use environmental::Vec;
#[cfg(feature = "try-runtime")]
use pezframe_support::traits::fungible::{Inspect, InspectHold};
use pezframe_support::{
pezpallet_prelude::*,
storage_alias,
traits::{fungible::MutateHold, ReservableCurrency},
weights::WeightMeter,
DefaultNoBound,
};
use pezsp_core::hexdisplay::HexDisplay;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
use pezsp_runtime::{traits::Zero, Saturating};
mod v13 {
use super::*;
pub type BalanceOf<T, OldCurrency> = <OldCurrency as pezframe_support::traits::Currency<
<T as pezframe_system::Config>::AccountId,
>>::Balance;
#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(T, OldCurrency))]
pub struct CodeInfo<T, OldCurrency>
where
T: Config,
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
pub owner: AccountIdOf<T>,
#[codec(compact)]
pub deposit: v13::BalanceOf<T, OldCurrency>,
#[codec(compact)]
pub refcount: u64,
pub determinism: Determinism,
pub code_len: u32,
}
#[storage_alias]
pub type CodeInfoOf<T: Config, OldCurrency> =
StorageMap<Pallet<T>, Identity, CodeHash<T>, CodeInfo<T, OldCurrency>>;
}
#[cfg(feature = "runtime-benchmarks")]
pub fn store_dummy_code<T: Config, OldCurrency>(account: T::AccountId)
where
T: Config,
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId> + 'static,
{
use alloc::vec;
use pezsp_runtime::traits::Hash;
let len = T::MaxCodeLen::get();
let code = vec![42u8; len as usize];
let hash = T::Hashing::hash(&code);
let info = v13::CodeInfo {
owner: account,
deposit: 10_000u32.into(),
refcount: u64::MAX,
determinism: Determinism::Enforced,
code_len: len,
};
v13::CodeInfoOf::<T, OldCurrency>::insert(hash, info);
}
#[cfg(feature = "try-runtime")]
#[derive(Encode, Decode)]
/// Accounts for the balance allocation of a code owner.
struct BalanceAllocation<T, OldCurrency>
where
T: Config,
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
/// Total reserved balance as code upload deposit for the owner.
reserved: v13::BalanceOf<T, OldCurrency>,
/// Total balance of the owner.
total: v13::BalanceOf<T, OldCurrency>,
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T, OldCurrency>
where
T: Config,
OldCurrency: ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
{
last_code_hash: Option<CodeHash<T>>,
_phantom: PhantomData<(T, OldCurrency)>,
}
impl<T, OldCurrency> MigrationStep for Migration<T, OldCurrency>
where
T: Config,
OldCurrency: 'static + ReservableCurrency<<T as pezframe_system::Config>::AccountId>,
BalanceOf<T>: From<OldCurrency::Balance>,
{
const VERSION: u16 = 14;
fn max_step_weight() -> Weight {
T::WeightInfo::v14_migration_step()
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_hash) = self.last_code_hash.take() {
v13::CodeInfoOf::<T, OldCurrency>::iter_from(
v13::CodeInfoOf::<T, OldCurrency>::hashed_key_for(last_hash),
)
} else {
v13::CodeInfoOf::<T, OldCurrency>::iter()
};
if let Some((hash, code_info)) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating code upload deposit for 0x{:?}", HexDisplay::from(&code_info.owner.encode()));
let remaining = OldCurrency::unreserve(&code_info.owner, code_info.deposit);
if remaining > Zero::zero() {
log::warn!(
target: LOG_TARGET,
"Code owner's account 0x{:?} for code {:?} has some non-unreservable deposit {:?} from a total of {:?} that will remain in reserved.",
HexDisplay::from(&code_info.owner.encode()),
hash,
remaining,
code_info.deposit
);
}
let unreserved = code_info.deposit.saturating_sub(remaining);
let amount = BalanceOf::<T>::from(unreserved);
log::debug!(
target: LOG_TARGET,
"Holding {:?} on the code owner's account 0x{:?} for code {:?}.",
amount,
HexDisplay::from(&code_info.owner.encode()),
hash,
);
T::Currency::hold(
&HoldReason::CodeUploadDepositReserve.into(),
&code_info.owner,
amount,
)
.unwrap_or_else(|err| {
log::error!(
target: LOG_TARGET,
"Failed to hold {:?} from the code owner's account 0x{:?} for code {:?}, reason: {:?}.",
amount,
HexDisplay::from(&code_info.owner.encode()),
hash,
err
);
});
self.last_code_hash = Some(hash);
meter.consume(T::WeightInfo::v14_migration_step());
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "No more code upload deposit to migrate");
meter.consume(T::WeightInfo::v14_migration_step());
IsFinished::Yes
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let info: Vec<_> = v13::CodeInfoOf::<T, OldCurrency>::iter().collect();
let mut owner_balance_allocation =
BTreeMap::<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>>::new();
// Calculates the balance allocation by accumulating the code upload deposits of all codes
// owned by an owner.
for (_, code_info) in info {
owner_balance_allocation
.entry(code_info.owner.clone())
.and_modify(|alloc| {
alloc.reserved = alloc.reserved.saturating_add(code_info.deposit);
})
.or_insert(BalanceAllocation {
reserved: code_info.deposit,
total: OldCurrency::total_balance(&code_info.owner),
});
}
Ok(owner_balance_allocation.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let owner_balance_allocation =
<BTreeMap<AccountIdOf<T>, BalanceAllocation<T, OldCurrency>> as Decode>::decode(
&mut &state[..],
)
.expect("pre_upgrade_step provides a valid state; qed");
let mut total_held: BalanceOf<T> = Zero::zero();
let count = owner_balance_allocation.len();
for (owner, old_balance_allocation) in owner_balance_allocation {
let held =
T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &owner);
log::debug!(
target: LOG_TARGET,
"Validating code upload deposit for owner 0x{:?}, reserved: {:?}, held: {:?}",
HexDisplay::from(&owner.encode()),
old_balance_allocation.reserved,
held
);
ensure!(held == old_balance_allocation.reserved.into(), "Held amount mismatch");
log::debug!(
target: LOG_TARGET,
"Validating total balance for owner 0x{:?}, new: {:?}, old: {:?}",
HexDisplay::from(&owner.encode()),
T::Currency::total_balance(&owner),
old_balance_allocation.total
);
ensure!(
T::Currency::total_balance(&owner) ==
BalanceOf::<T>::decode(&mut &old_balance_allocation.total.encode()[..])
.unwrap(),
"Balance mismatch "
);
total_held += held;
}
log::info!(
target: LOG_TARGET,
"Code owners processed: {:?}.",
count
);
log::info!(
target: LOG_TARGET,
"Total held amount for code upload deposit: {:?}",
total_held
);
Ok(())
}
}
@@ -0,0 +1,332 @@
// 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.
//! Move contracts' _reserved_ balance from the `deposit_account` to be _held_ in the contract's
//! account instead. Since [`Currency`](pezframe_support::traits::Currency) has been
//! [deprecated](https://github.com/pezkuwichain/kurdistan-sdk/issues/40), we need the deposits to be
//! handled by the [`pezframe_support::traits::fungible`] traits instead. For this transfer the
//! balance from the deposit account to the contract's account and hold it in there.
//! Then the deposit account is not needed anymore and we can get rid of it.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
AccountIdOf, BalanceOf, CodeHash, Config, HoldReason, Pallet, TrieId, Weight, LOG_TARGET,
};
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
#[cfg(feature = "try-runtime")]
use pezframe_support::traits::fungible::InspectHold;
use pezframe_support::{
pezpallet_prelude::*,
storage_alias,
traits::{
fungible::{Mutate, MutateHold},
tokens::{fungible::Inspect, Fortitude, Preservation},
},
weights::WeightMeter,
BoundedBTreeMap, DefaultNoBound,
};
use pezframe_system::Pallet as System;
use pezsp_core::hexdisplay::HexDisplay;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
use pezsp_runtime::{traits::Zero, Saturating};
mod v14 {
use super::*;
#[derive(
Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
pub trie_id: TrieId,
pub deposit_account: AccountIdOf<T>,
pub code_hash: CodeHash<T>,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
pub storage_item_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
pub delegate_dependencies:
BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
#[storage_alias]
pub type ContractInfoOf<T: Config> = StorageMap<
Pallet<T>,
Twox64Concat,
<T as pezframe_system::Config>::AccountId,
ContractInfo<T>,
>;
}
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_contract_info<T: Config>(account: T::AccountId, info: crate::ContractInfo<T>) {
use pezsp_runtime::traits::{Hash, TrailingZeroInput};
let entropy = (b"contract_depo_v1", account.clone()).using_encoded(T::Hashing::hash);
let deposit_account = Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed");
let info = v14::ContractInfo {
trie_id: info.trie_id.clone(),
deposit_account,
code_hash: info.code_hash,
storage_bytes: Default::default(),
storage_items: Default::default(),
storage_byte_deposit: info.storage_byte_deposit,
storage_item_deposit: Default::default(),
storage_base_deposit: info.storage_base_deposit(),
delegate_dependencies: info.delegate_dependencies().clone(),
};
v14::ContractInfoOf::<T>::insert(account, info);
}
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
struct ContractInfo<T: Config> {
pub trie_id: TrieId,
pub code_hash: CodeHash<T>,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: BalanceOf<T>,
pub storage_item_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
pub delegate_dependencies:
BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
#[storage_alias]
type ContractInfoOf<T: Config> =
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
last_account: Option<T::AccountId>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 15;
fn max_step_weight() -> Weight {
T::WeightInfo::v15_migration_step()
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_account) = self.last_account.take() {
v14::ContractInfoOf::<T>::iter_from(v14::ContractInfoOf::<T>::hashed_key_for(
last_account,
))
} else {
v14::ContractInfoOf::<T>::iter()
};
if let Some((account, old_contract)) = iter.next() {
let deposit_account = &old_contract.deposit_account;
System::<T>::dec_consumers(deposit_account);
// Get the deposit balance to transfer.
let total_deposit_balance = T::Currency::total_balance(deposit_account);
let reducible_deposit_balance = T::Currency::reducible_balance(
deposit_account,
Preservation::Expendable,
Fortitude::Force,
);
if total_deposit_balance > reducible_deposit_balance {
// This should never happen, as by design all balance in the deposit account is
// storage deposit and therefore reducible after decrementing the consumer
// reference.
log::warn!(
target: LOG_TARGET,
"Deposit account 0x{:?} for contract 0x{:?} has some non-reducible balance {:?} from a total of {:?} that will remain in there.",
HexDisplay::from(&deposit_account.encode()),
HexDisplay::from(&account.encode()),
total_deposit_balance.saturating_sub(reducible_deposit_balance),
total_deposit_balance
);
}
// Move balance reserved from the deposit account back to the contract account.
// Let the deposit account die.
log::debug!(
target: LOG_TARGET,
"Transferring {:?} from the deposit account 0x{:?} to the contract 0x{:?}.",
reducible_deposit_balance,
HexDisplay::from(&deposit_account.encode()),
HexDisplay::from(&account.encode())
);
let transferred_deposit_balance = T::Currency::transfer(
deposit_account,
&account,
reducible_deposit_balance,
Preservation::Expendable,
)
.unwrap_or_else(|err| {
log::error!(
target: LOG_TARGET,
"Failed to transfer {:?} from the deposit account 0x{:?} to the contract 0x{:?}, reason: {:?}.",
reducible_deposit_balance,
HexDisplay::from(&deposit_account.encode()),
HexDisplay::from(&account.encode()),
err
);
Zero::zero()
});
// Hold the reserved balance.
if transferred_deposit_balance == Zero::zero() {
log::warn!(
target: LOG_TARGET,
"No balance to hold as storage deposit on the contract 0x{:?}.",
HexDisplay::from(&account.encode())
);
} else {
log::debug!(
target: LOG_TARGET,
"Holding {:?} as storage deposit on the contract 0x{:?}.",
transferred_deposit_balance,
HexDisplay::from(&account.encode())
);
T::Currency::hold(
&HoldReason::StorageDepositReserve.into(),
&account,
transferred_deposit_balance,
)
.unwrap_or_else(|err| {
log::error!(
target: LOG_TARGET,
"Failed to hold {:?} as storage deposit on the contract 0x{:?}, reason: {:?}.",
transferred_deposit_balance,
HexDisplay::from(&account.encode()),
err
);
});
}
log::debug!(target: LOG_TARGET, "===");
let info = ContractInfo {
trie_id: old_contract.trie_id,
code_hash: old_contract.code_hash,
storage_bytes: old_contract.storage_bytes,
storage_items: old_contract.storage_items,
storage_byte_deposit: old_contract.storage_byte_deposit,
storage_item_deposit: old_contract.storage_item_deposit,
storage_base_deposit: old_contract.storage_base_deposit,
delegate_dependencies: old_contract.delegate_dependencies,
};
ContractInfoOf::<T>::insert(account.clone(), info);
// Store last key for next migration step
self.last_account = Some(account);
meter.consume(T::WeightInfo::v15_migration_step());
IsFinished::No
} else {
log::info!(target: LOG_TARGET, "Done Migrating Storage Deposits.");
meter.consume(T::WeightInfo::v15_migration_step());
IsFinished::Yes
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade_step() -> Result<Vec<u8>, TryRuntimeError> {
let sample: Vec<_> = v14::ContractInfoOf::<T>::iter().take(100).collect();
log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len());
let state: Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> = sample
.iter()
.map(|(account, contract)| {
(
account.clone(),
contract.clone(),
T::Currency::total_balance(&account),
T::Currency::total_balance(&contract.deposit_account),
)
})
.collect();
Ok(state.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade_step(state: Vec<u8>) -> Result<(), TryRuntimeError> {
let sample =
<Vec<(T::AccountId, v14::ContractInfo<T>, BalanceOf<T>, BalanceOf<T>)> as Decode>::decode(
&mut &state[..],
)
.expect("pre_upgrade_step provides a valid state; qed");
log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len());
for (account, old_contract, old_account_balance, old_deposit_balance) in sample {
log::debug!(target: LOG_TARGET, "===");
log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode()));
let on_hold =
T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account);
let account_balance = T::Currency::total_balance(&account);
log::debug!(
target: LOG_TARGET,
"Validating balances match. Old deposit account's balance: {:?}. Contract's on hold: {:?}. Old contract's total balance: {:?}, Contract's total balance: {:?}.",
old_deposit_balance,
on_hold,
old_account_balance,
account_balance
);
ensure!(
old_account_balance.saturating_add(old_deposit_balance) == account_balance,
"total balance mismatch"
);
ensure!(old_deposit_balance == on_hold, "deposit mismatch");
ensure!(
!System::<T>::account_exists(&old_contract.deposit_account),
"deposit account still exists"
);
let migration_contract_info = ContractInfoOf::<T>::try_get(&account).unwrap();
let crate_contract_info = crate::ContractInfoOf::<T>::try_get(&account).unwrap();
ensure!(
migration_contract_info.trie_id == crate_contract_info.trie_id,
"trie_id mismatch"
);
ensure!(
migration_contract_info.code_hash == crate_contract_info.code_hash,
"code_hash mismatch"
);
ensure!(
migration_contract_info.storage_byte_deposit ==
crate_contract_info.storage_byte_deposit,
"storage_byte_deposit mismatch"
);
ensure!(
migration_contract_info.storage_base_deposit ==
crate_contract_info.storage_base_deposit(),
"storage_base_deposit mismatch"
);
ensure!(
&migration_contract_info.delegate_dependencies ==
crate_contract_info.delegate_dependencies(),
"delegate_dependencies mismatch"
);
}
Ok(())
}
}
@@ -0,0 +1,106 @@
// 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.
//! Remove ED from storage base deposit.
//! See <https://github.com/pezkuwichain/kurdistan-sdk/issues/116>.
use crate::{
migration::{IsFinished, MigrationStep},
weights::WeightInfo,
BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, WeightMeter, LOG_TARGET,
};
use codec::{Decode, Encode};
use pezframe_support::{pezpallet_prelude::*, storage_alias, DefaultNoBound};
use pezsp_runtime::{BoundedBTreeMap, Saturating};
#[cfg(feature = "runtime-benchmarks")]
pub fn store_old_contract_info<T: Config>(
account: T::AccountId,
info: &crate::ContractInfo<T>,
) -> BalanceOf<T> {
let storage_base_deposit = Pallet::<T>::min_balance() + 1u32.into();
ContractInfoOf::<T>::insert(
account,
ContractInfo {
trie_id: info.trie_id.clone(),
code_hash: info.code_hash,
storage_bytes: Default::default(),
storage_items: Default::default(),
storage_byte_deposit: Default::default(),
storage_item_deposit: Default::default(),
storage_base_deposit,
delegate_dependencies: Default::default(),
},
);
storage_base_deposit
}
#[storage_alias]
pub type ContractInfoOf<T: Config> =
StorageMap<Pallet<T>, Twox64Concat, <T as pezframe_system::Config>::AccountId, ContractInfo<T>>;
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
trie_id: TrieId,
code_hash: CodeHash<T>,
storage_bytes: u32,
storage_items: u32,
storage_byte_deposit: BalanceOf<T>,
storage_item_deposit: BalanceOf<T>,
pub storage_base_deposit: BalanceOf<T>,
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)]
pub struct Migration<T: Config> {
last_account: Option<T::AccountId>,
}
impl<T: Config> MigrationStep for Migration<T> {
const VERSION: u16 = 16;
fn max_step_weight() -> Weight {
T::WeightInfo::v16_migration_step()
}
fn step(&mut self, meter: &mut WeightMeter) -> IsFinished {
let mut iter = if let Some(last_account) = self.last_account.take() {
ContractInfoOf::<T>::iter_keys_from(ContractInfoOf::<T>::hashed_key_for(last_account))
} else {
ContractInfoOf::<T>::iter_keys()
};
if let Some(key) = iter.next() {
log::debug!(target: LOG_TARGET, "Migrating contract {:?}", key);
ContractInfoOf::<T>::mutate(key.clone(), |info| {
let ed = Pallet::<T>::min_balance();
let mut updated_info = info.take().expect("Item exists; qed");
updated_info.storage_base_deposit.saturating_reduce(ed);
*info = Some(updated_info);
});
self.last_account = Some(key);
meter.consume(T::WeightInfo::v16_migration_step());
IsFinished::No
} else {
log::debug!(target: LOG_TARGET, "No more contracts to migrate");
meter.consume(T::WeightInfo::v16_migration_step());
IsFinished::Yes
}
}
}
@@ -0,0 +1,252 @@
// 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.
//! A crate that hosts a common definitions that are relevant for the pezpallet-contracts.
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use pezframe_support::weights::Weight;
use pezpallet_contracts_uapi::ReturnFlags;
use scale_info::TypeInfo;
use pezsp_runtime::{
traits::{Saturating, Zero},
DispatchError, RuntimeDebug,
};
/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and
/// `ContractsApi::instantiate`.
///
/// It contains the execution result together with some auxiliary information.
///
/// #Note
///
/// It has been extended to include `events` at the end of the struct while not bumping the
/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data
/// should be ignored to avoid any potential compatibility issues.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct ContractResult<R, Balance, EventRecord> {
/// How much weight was consumed during execution.
pub gas_consumed: Weight,
/// How much weight is required as gas limit in order to execute this call.
///
/// This value should be used to determine the weight limit for on-chain execution.
///
/// # Note
///
/// This can only different from [`Self::gas_consumed`] when weight pre charging
/// is used. Currently, only `seal_call_runtime` makes use of pre charging.
/// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging
/// when a non-zero `gas_limit` argument is supplied.
pub gas_required: Weight,
/// How much balance was paid by the origin into the contract's deposit account in order to
/// pay for storage.
///
/// The storage deposit is never actually charged from the origin in case of [`Self::result`]
/// is `Err`. This is because on error all storage changes are rolled back including the
/// payment of the deposit.
pub storage_deposit: StorageDeposit<Balance>,
/// An optional debug message. This message is only filled when explicitly requested
/// by the code that calls into the contract. Otherwise it is empty.
///
/// The contained bytes are valid UTF-8. This is not declared as `String` because
/// this type is not allowed within the runtime.
///
/// Clients should not make any assumptions about the format of the buffer.
/// They should just display it as-is. It is **not** only a collection of log lines
/// provided by a contract but a formatted buffer with different sections.
///
/// # Note
///
/// The debug message is never generated during on-chain execution. It is reserved for
/// RPC calls.
pub debug_message: Vec<u8>,
/// The execution result of the wasm code.
pub result: R,
/// The events that were emitted during execution. It is an option as event collection is
/// optional.
pub events: Option<Vec<EventRecord>>,
}
/// Result type of a `bare_call` call as well as `ContractsApi::call`.
pub type ContractExecResult<Balance, EventRecord> =
ContractResult<Result<ExecReturnValue, DispatchError>, Balance, EventRecord>;
/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`.
pub type ContractInstantiateResult<AccountId, Balance, EventRecord> =
ContractResult<Result<InstantiateReturnValue<AccountId>, DispatchError>, Balance, EventRecord>;
/// Result type of a `bare_code_upload` call.
pub type CodeUploadResult<CodeHash, Balance> =
Result<CodeUploadReturnValue<CodeHash, Balance>, DispatchError>;
/// Result type of a `get_storage` call.
pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
/// The possible errors that can happen querying the storage of a contract.
#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub enum ContractAccessError {
/// The given address doesn't point to a contract.
DoesntExist,
/// Storage key cannot be decoded from the provided input data.
KeyDecodingFailed,
/// Storage is migrating. Try again later.
MigrationInProgress,
}
/// Output of a contract call or instantiation which ran to completion.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct ExecReturnValue {
/// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
pub flags: ReturnFlags,
/// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
pub data: Vec<u8>,
}
impl ExecReturnValue {
/// The contract did revert all storage changes.
pub fn did_revert(&self) -> bool {
self.flags.contains(ReturnFlags::REVERT)
}
}
/// The result of a successful contract instantiation.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct InstantiateReturnValue<AccountId> {
/// The output of the called constructor.
pub result: ExecReturnValue,
/// The account id of the new contract.
pub account_id: AccountId,
}
/// The result of successfully uploading a contract.
#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct CodeUploadReturnValue<CodeHash, Balance> {
/// The key under which the new code is stored.
pub code_hash: CodeHash,
/// The deposit that was reserved at the caller. Is zero when the code already existed.
pub deposit: Balance,
}
/// Reference to an existing code hash or a new wasm module.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum Code<Hash> {
/// A wasm module as raw bytes.
Upload(Vec<u8>),
/// The code hash of an on-chain wasm blob.
Existing(Hash),
}
/// The amount of balance that was either charged or refunded in order to pay for storage.
#[derive(
Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo,
)]
pub enum StorageDeposit<Balance> {
/// The transaction reduced storage consumption.
///
/// This means that the specified amount of balance was transferred from the involved
/// deposit accounts to the origin.
Refund(Balance),
/// The transaction increased storage consumption.
///
/// This means that the specified amount of balance was transferred from the origin
/// to the involved deposit accounts.
Charge(Balance),
}
impl<Balance: Zero> Default for StorageDeposit<Balance> {
fn default() -> Self {
Self::Charge(Zero::zero())
}
}
impl<Balance: Zero + Copy> StorageDeposit<Balance> {
/// Returns how much balance is charged or `0` in case of a refund.
pub fn charge_or_zero(&self) -> Balance {
match self {
Self::Charge(amount) => *amount,
Self::Refund(_) => Zero::zero(),
}
}
pub fn is_zero(&self) -> bool {
match self {
Self::Charge(amount) => amount.is_zero(),
Self::Refund(amount) => amount.is_zero(),
}
}
}
impl<Balance> StorageDeposit<Balance>
where
Balance: Saturating + Ord + Copy,
{
/// This is essentially a saturating signed add.
pub fn saturating_add(&self, rhs: &Self) -> Self {
use StorageDeposit::*;
match (self, rhs) {
(Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)),
(Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)),
(Charge(lhs), Refund(rhs)) =>
if lhs >= rhs {
Charge(lhs.saturating_sub(*rhs))
} else {
Refund(rhs.saturating_sub(*lhs))
},
(Refund(lhs), Charge(rhs)) =>
if lhs > rhs {
Refund(lhs.saturating_sub(*rhs))
} else {
Charge(rhs.saturating_sub(*lhs))
},
}
}
/// This is essentially a saturating signed sub.
pub fn saturating_sub(&self, rhs: &Self) -> Self {
use StorageDeposit::*;
match (self, rhs) {
(Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)),
(Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)),
(Charge(lhs), Charge(rhs)) =>
if lhs >= rhs {
Charge(lhs.saturating_sub(*rhs))
} else {
Refund(rhs.saturating_sub(*lhs))
},
(Refund(lhs), Refund(rhs)) =>
if lhs > rhs {
Refund(lhs.saturating_sub(*rhs))
} else {
Charge(rhs.saturating_sub(*lhs))
},
}
}
/// If the amount of deposit (this type) is constrained by a `limit` this calculates how
/// much balance (if any) is still available from this limit.
///
/// # Note
///
/// In case of a refund the return value can be larger than `limit`.
pub fn available(&self, limit: &Balance) -> Balance {
use StorageDeposit::*;
match self {
Charge(amount) => limit.saturating_sub(*amount),
Refund(amount) => limit.saturating_add(*amount),
}
}
}
@@ -0,0 +1,149 @@
// 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 contains the cost schedule and supporting code that constructs a
//! sane default schedule from a `WeightInfo` implementation.
use crate::{weights::WeightInfo, Config};
use codec::{Decode, Encode};
use core::marker::PhantomData;
use pezframe_support::DefaultNoBound;
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
/// Definition of the cost schedule and other parameterizations for the wasm vm.
///
/// Its [`Default`] implementation is the designated way to initialize this type. It uses
/// the benchmarked information supplied by [`Config::WeightInfo`]. All of its fields are
/// public and can therefore be modified. For example in order to change some of the limits
/// and set a custom instruction weight version the following code could be used:
/// ```rust
/// use pezpallet_contracts::{Schedule, Limits, InstructionWeights, Config};
///
/// fn create_schedule<T: Config>() -> Schedule<T> {
/// Schedule {
/// limits: Limits {
/// memory_pages: 16,
/// .. Default::default()
/// },
/// instruction_weights: InstructionWeights {
/// .. Default::default()
/// },
/// .. Default::default()
/// }
/// }
/// ```
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
#[cfg_attr(feature = "runtime-benchmarks", derive(pezframe_support::DebugNoBound))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, DefaultNoBound, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Schedule<T: Config> {
/// Describes the upper limits on various metrics.
pub limits: Limits,
/// The weights for individual wasm instructions.
pub instruction_weights: InstructionWeights<T>,
}
impl<T: Config> Schedule<T> {
/// Returns the reference time per engine fuel.
pub fn ref_time_by_fuel(&self) -> u64 {
self.instruction_weights.base as u64
}
}
/// Describes the upper limits on various metrics.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "runtime-benchmarks", derive(Debug))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
pub struct Limits {
/// The maximum number of topics supported by an event.
pub event_topics: u32,
/// Maximum number of memory pages allowed for a contract.
pub memory_pages: u32,
/// The maximum length of a subject in bytes used for PRNG generation.
pub subject_len: u32,
/// The maximum size of a storage value and event payload in bytes.
pub payload_len: u32,
/// The maximum node runtime memory. This is for integrity checks only and does not affect the
/// real setting.
pub runtime_memory: u32,
/// The maximum validator node runtime memory. This is for integrity checks only and does not
/// affect the real setting.
pub validator_runtime_memory: u32,
/// The additional ref_time added to the `deposit_event` host function call per event data
/// byte.
pub event_ref_time: u64,
}
impl Limits {
/// The maximum memory size in bytes that a contract can occupy.
pub fn max_memory_size(&self) -> u32 {
self.memory_pages * 64 * 1024
}
}
/// Gas metering of Wasm executed instructions is being done on the engine side.
/// This struct holds a reference value used to gas units scaling between host and engine.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "runtime-benchmarks", derive(pezframe_support::DebugNoBound))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct InstructionWeights<T: Config> {
/// Base instruction `ref_time` Weight.
/// Should match to wasmi's `1` fuel (see <https://github.com/wasmi-labs/wasmi/issues/701>).
pub base: u32,
/// The type parameter is used in the default implementation.
#[codec(skip)]
pub _phantom: PhantomData<T>,
}
impl Default for Limits {
fn default() -> Self {
Self {
event_topics: 4,
memory_pages: 16,
subject_len: 32,
payload_len: 16 * 1024,
runtime_memory: 1024 * 1024 * 128,
validator_runtime_memory: 1024 * 1024 * 512,
event_ref_time: 60_000,
}
}
}
impl<T: Config> Default for InstructionWeights<T> {
/// We execute 6 different instructions therefore we have to divide the actual
/// computed gas costs by 6 to have a rough estimate as to how expensive each
/// single executed instruction is going to be.
fn default() -> Self {
let instr_cost = T::WeightInfo::instr_i64_load_store(1)
.saturating_sub(T::WeightInfo::instr_i64_load_store(0))
.ref_time() as u32;
let base = instr_cost / 6;
Self { base, _phantom: PhantomData }
}
}
@@ -0,0 +1,480 @@
// 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 contains routines for accessing and altering a contract related state.
pub mod meter;
use crate::{
exec::{AccountIdOf, Key},
weights::WeightInfo,
BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter,
Error, TrieId, SENTINEL,
};
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use pezframe_support::{
storage::child::{self, ChildInfo},
weights::{Weight, WeightMeter},
CloneNoBound, DefaultNoBound,
};
use scale_info::TypeInfo;
use pezsp_core::Get;
use pezsp_io::KillStorageResult;
use pezsp_runtime::{
traits::{Hash, Saturating, Zero},
BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug,
};
use self::meter::Diff;
/// Information for managing an account and its sub trie abstraction.
/// This is the required info to cache for an account.
#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
pub struct ContractInfo<T: Config> {
/// Unique ID for the subtree encoded as a bytes vector.
pub trie_id: TrieId,
/// The code associated with a given account.
pub code_hash: CodeHash<T>,
/// How many bytes of storage are accumulated in this contract's child trie.
storage_bytes: u32,
/// How many items of storage are accumulated in this contract's child trie.
storage_items: u32,
/// This records to how much deposit the accumulated `storage_bytes` amount to.
pub storage_byte_deposit: BalanceOf<T>,
/// This records to how much deposit the accumulated `storage_items` amount to.
storage_item_deposit: BalanceOf<T>,
/// This records how much deposit is put down in order to pay for the contract itself.
///
/// We need to store this information separately so it is not used when calculating any refunds
/// since the base deposit can only ever be refunded on contract termination.
storage_base_deposit: BalanceOf<T>,
/// Map of code hashes and deposit balances.
///
/// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added
/// to the map can not be removed from the chain state and can be safely used for delegate
/// calls.
delegate_dependencies: BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies>,
}
impl<T: Config> ContractInfo<T> {
/// Constructs a new contract info **without** writing it to storage.
///
/// This returns an `Err` if an contract with the supplied `account` already exists
/// in storage.
pub fn new(
account: &AccountIdOf<T>,
nonce: u64,
code_hash: CodeHash<T>,
) -> Result<Self, DispatchError> {
if <ContractInfoOf<T>>::contains_key(account) {
return Err(Error::<T>::DuplicateContract.into());
}
let trie_id = {
let buf = (account, nonce).using_encoded(T::Hashing::hash);
buf.as_ref()
.to_vec()
.try_into()
.expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
};
let contract = Self {
trie_id,
code_hash,
storage_bytes: 0,
storage_items: 0,
storage_byte_deposit: Zero::zero(),
storage_item_deposit: Zero::zero(),
storage_base_deposit: Zero::zero(),
delegate_dependencies: Default::default(),
};
Ok(contract)
}
/// Returns the number of locked delegate dependencies.
pub fn delegate_dependencies_count(&self) -> usize {
self.delegate_dependencies.len()
}
/// Associated child trie unique id is built from the hash part of the trie id.
pub fn child_trie_info(&self) -> ChildInfo {
ChildInfo::new_default(self.trie_id.as_ref())
}
/// The deposit paying for the accumulated storage generated within the contract's child trie.
pub fn extra_deposit(&self) -> BalanceOf<T> {
self.storage_byte_deposit.saturating_add(self.storage_item_deposit)
}
/// Same as [`Self::extra_deposit`] but including the base deposit.
pub fn total_deposit(&self) -> BalanceOf<T> {
self.extra_deposit().saturating_add(self.storage_base_deposit)
}
/// Returns the storage base deposit of the contract.
pub fn storage_base_deposit(&self) -> BalanceOf<T> {
self.storage_base_deposit
}
/// Reads a storage kv pair of a contract.
///
/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
/// contract doesn't store under the given `key` `None` is returned.
pub fn read(&self, key: &Key<T>) -> Option<Vec<u8>> {
child::get_raw(&self.child_trie_info(), key.hash().as_slice())
}
/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
///
/// Returns `None` if the `key` wasn't previously set by `set_storage` or
/// was deleted.
pub fn size(&self, key: &Key<T>) -> Option<u32> {
child::len(&self.child_trie_info(), key.hash().as_slice())
}
/// Update a storage entry into a contract's kv storage.
///
/// If the `new_value` is `None` then the kv pair is removed. If `take` is true
/// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`].
///
/// This function also records how much storage was created or removed if a `storage_meter`
/// is supplied. It should only be absent for testing or benchmarking code.
pub fn write(
&self,
key: &Key<T>,
new_value: Option<Vec<u8>>,
storage_meter: Option<&mut meter::NestedMeter<T>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
let hashed_key = key.hash();
self.write_raw(&hashed_key, new_value, storage_meter, take)
}
/// Update a storage entry into a contract's kv storage.
/// Function used in benchmarks, which can simulate prefix collision in keys.
#[cfg(feature = "runtime-benchmarks")]
pub fn bench_write_raw(
&self,
key: &[u8],
new_value: Option<Vec<u8>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
self.write_raw(key, new_value, None, take)
}
fn write_raw(
&self,
key: &[u8],
new_value: Option<Vec<u8>>,
storage_meter: Option<&mut meter::NestedMeter<T>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
let child_trie_info = &self.child_trie_info();
let (old_len, old_value) = if take {
let val = child::get_raw(child_trie_info, key);
(val.as_ref().map(|v| v.len() as u32), val)
} else {
(child::len(child_trie_info, key), None)
};
if let Some(storage_meter) = storage_meter {
let mut diff = meter::Diff::default();
match (old_len, new_value.as_ref().map(|v| v.len() as u32)) {
(Some(old_len), Some(new_len)) =>
if new_len > old_len {
diff.bytes_added = new_len - old_len;
} else {
diff.bytes_removed = old_len - new_len;
},
(None, Some(new_len)) => {
diff.bytes_added = new_len;
diff.items_added = 1;
},
(Some(old_len), None) => {
diff.bytes_removed = old_len;
diff.items_removed = 1;
},
(None, None) => (),
}
storage_meter.charge(&diff);
}
match &new_value {
Some(new_value) => child::put_raw(child_trie_info, key, new_value),
None => child::kill(child_trie_info, key),
}
Ok(match (old_len, old_value) {
(None, _) => WriteOutcome::New,
(Some(old_len), None) => WriteOutcome::Overwritten(old_len),
(Some(_), Some(old_value)) => WriteOutcome::Taken(old_value),
})
}
/// Sets and returns the contract base deposit.
///
/// The base deposit is updated when the `code_hash` of the contract changes, as it depends on
/// the deposit paid to upload the contract's code.
pub fn update_base_deposit(&mut self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
let info_deposit =
Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() }
.update_contract::<T>(None)
.charge_or_zero();
// Instantiating the contract prevents its code to be deleted, therefore the base deposit
// includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit
// to prevent abuse.
let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit());
let deposit = info_deposit.saturating_add(upload_deposit);
self.storage_base_deposit = deposit;
deposit
}
/// Adds a new delegate dependency to the contract.
/// The `amount` is the amount of funds that will be reserved for the dependency.
///
/// Returns an error if the maximum number of delegate_dependencies is reached or if
/// the delegate dependency already exists.
pub fn lock_delegate_dependency(
&mut self,
code_hash: CodeHash<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
self.delegate_dependencies
.try_insert(code_hash, amount)
.map_err(|_| Error::<T>::MaxDelegateDependenciesReached)?
.map_or(Ok(()), |_| Err(Error::<T>::DelegateDependencyAlreadyExists))
.map_err(Into::into)
}
/// Removes the delegate dependency from the contract and returns the deposit held for this
/// dependency.
///
/// Returns an error if the entry doesn't exist.
pub fn unlock_delegate_dependency(
&mut self,
code_hash: &CodeHash<T>,
) -> Result<BalanceOf<T>, DispatchError> {
self.delegate_dependencies
.remove(code_hash)
.ok_or(Error::<T>::DelegateDependencyNotFound.into())
}
/// Returns the delegate_dependencies of the contract.
pub fn delegate_dependencies(
&self,
) -> &BoundedBTreeMap<CodeHash<T>, BalanceOf<T>, T::MaxDelegateDependencies> {
&self.delegate_dependencies
}
/// Push a contract's trie to the deletion queue for lazy removal.
///
/// You must make sure that the contract is also removed when queuing the trie for deletion.
pub fn queue_trie_for_deletion(&self) {
DeletionQueueManager::<T>::load().insert(self.trie_id.clone());
}
/// Calculates the weight that is necessary to remove one key from the trie and how many
/// of those keys can be deleted from the deletion queue given the supplied weight limit.
pub fn deletion_budget(meter: &WeightMeter) -> (Weight, u32) {
let base_weight = T::WeightInfo::on_process_deletion_queue_batch();
let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) -
T::WeightInfo::on_initialize_per_trie_key(0);
// `weight_per_key` being zero makes no sense and would constitute a failure to
// benchmark properly. We opt for not removing any keys at all in this case.
let key_budget = meter
.limit()
.saturating_sub(base_weight)
.checked_div_per_component(&weight_per_key)
.unwrap_or(0) as u32;
(weight_per_key, key_budget)
}
/// Delete as many items from the deletion queue possible within the supplied weight limit.
pub fn process_deletion_queue_batch(meter: &mut WeightMeter) {
if meter.try_consume(T::WeightInfo::on_process_deletion_queue_batch()).is_err() {
return;
};
let mut queue = <DeletionQueueManager<T>>::load();
if queue.is_empty() {
return;
}
let (weight_per_key, budget) = Self::deletion_budget(&meter);
let mut remaining_key_budget = budget;
while remaining_key_budget > 0 {
let Some(entry) = queue.next() else { break };
#[allow(deprecated)]
let outcome = child::kill_storage(
&ChildInfo::new_default(&entry.trie_id),
Some(remaining_key_budget),
);
match outcome {
// This happens when our budget wasn't large enough to remove all keys.
KillStorageResult::SomeRemaining(keys_removed) => {
remaining_key_budget.saturating_reduce(keys_removed);
break;
},
KillStorageResult::AllRemoved(keys_removed) => {
entry.remove();
// charge at least one key even if none were removed.
remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1));
},
};
}
meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget)))
}
/// Returns the code hash of the contract specified by `account` ID.
pub fn load_code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
<ContractInfoOf<T>>::get(account).map(|i| i.code_hash)
}
}
/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`].
#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))]
pub enum WriteOutcome {
/// No value existed at the specified key.
New,
/// A value of the returned length was overwritten.
Overwritten(u32),
/// The returned value was taken out of storage before being overwritten.
///
/// This is only returned when specifically requested because it causes additional work
/// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`]
/// is returned instead.
Taken(Vec<u8>),
}
impl WriteOutcome {
/// Extracts the size of the overwritten value or `0` if there
/// was no value in storage.
pub fn old_len(&self) -> u32 {
match self {
Self::New => 0,
Self::Overwritten(len) => *len,
Self::Taken(value) => value.len() as u32,
}
}
/// Extracts the size of the overwritten value or `SENTINEL` if there
/// was no value in storage.
///
/// # Note
///
/// We cannot use `0` as sentinel value because there could be a zero sized
/// storage entry which is different from a non existing one.
pub fn old_len_with_sentinel(&self) -> u32 {
match self {
Self::New => SENTINEL,
Self::Overwritten(len) => *len,
Self::Taken(value) => value.len() as u32,
}
}
}
/// Manage the removal of contracts storage that are marked for deletion.
///
/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible
/// immediately, but the deletion of the storage items it has accumulated is performed
/// later by pulling the contract from the queue in the `on_idle` hook.
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
#[scale_info(skip_type_params(T))]
pub struct DeletionQueueManager<T: Config> {
/// Counter used as a key for inserting a new deleted contract in the queue.
/// The counter is incremented after each insertion.
insert_counter: u32,
/// The index used to read the next element to be deleted in the queue.
/// The counter is incremented after each deletion.
delete_counter: u32,
_phantom: PhantomData<T>,
}
/// View on a contract that is marked for deletion.
struct DeletionQueueEntry<'a, T: Config> {
/// the trie id of the contract to delete.
trie_id: TrieId,
/// A mutable reference on the queue so that the contract can be removed, and none can be added
/// or read in the meantime.
queue: &'a mut DeletionQueueManager<T>,
}
impl<'a, T: Config> DeletionQueueEntry<'a, T> {
/// Remove the contract from the deletion queue.
fn remove(self) {
<DeletionQueue<T>>::remove(self.queue.delete_counter);
self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1);
<DeletionQueueCounter<T>>::set(self.queue.clone());
}
}
impl<T: Config> DeletionQueueManager<T> {
/// Load the `DeletionQueueCounter`, so we can perform read or write operations on the
/// DeletionQueue storage.
fn load() -> Self {
<DeletionQueueCounter<T>>::get()
}
/// Returns `true` if the queue contains no elements.
fn is_empty(&self) -> bool {
self.insert_counter.wrapping_sub(self.delete_counter) == 0
}
/// Insert a contract in the deletion queue.
fn insert(&mut self, trie_id: TrieId) {
<DeletionQueue<T>>::insert(self.insert_counter, trie_id);
self.insert_counter = self.insert_counter.wrapping_add(1);
<DeletionQueueCounter<T>>::set(self.clone());
}
/// Fetch the next contract to be deleted.
///
/// Note:
/// we use the delete counter to get the next value to read from the queue and thus don't pay
/// the cost of an extra call to `pezsp_io::storage::next_key` to lookup the next entry in the map
fn next(&mut self) -> Option<DeletionQueueEntry<'_, T>> {
if self.is_empty() {
return None;
}
let entry = <DeletionQueue<T>>::get(self.delete_counter);
entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self })
}
}
#[cfg(test)]
impl<T: Config> DeletionQueueManager<T> {
pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self {
Self { insert_counter, delete_counter, _phantom: Default::default() }
}
pub fn as_test_tuple(&self) -> (u32, u32) {
(self.insert_counter, self.delete_counter)
}
}
@@ -0,0 +1,908 @@
// 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 contains functions to meter the storage deposit.
use crate::{
storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason,
Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET,
};
use alloc::vec::Vec;
use core::{fmt::Debug, marker::PhantomData};
use pezframe_support::{
ensure,
traits::{
fungible::{Mutate, MutateHold},
tokens::{
Fortitude, Fortitude::Polite, Precision, Preservation, Restriction, WithdrawConsequence,
},
Get,
},
DefaultNoBound, RuntimeDebugNoBound,
};
use pezsp_runtime::{
traits::{Saturating, Zero},
DispatchError, FixedPointNumber, FixedU128,
};
/// Deposit that uses the native fungible's balance type.
pub type DepositOf<T> = Deposit<BalanceOf<T>>;
/// A production root storage meter that actually charges from its origin.
pub type Meter<T> = RawMeter<T, ReservingExt, Root>;
/// A production nested storage meter that actually charges from its origin.
pub type NestedMeter<T> = RawMeter<T, ReservingExt, Nested>;
/// A production storage meter that actually charges from its origin.
///
/// This can be used where we want to be generic over the state (Root vs. Nested).
pub type GenericMeter<T, S> = RawMeter<T, ReservingExt, S>;
/// A trait that allows to decouple the metering from the charging of balance.
///
/// This mostly exists for testing so that the charging can be mocked.
pub trait Ext<T: Config> {
/// This checks whether `origin` is able to afford the storage deposit limit.
///
/// It is necessary to do this check beforehand so that the charge won't fail later on.
///
/// `origin`: The origin of the call stack from which is responsible for putting down a deposit.
/// `limit`: The limit with which the meter was constructed.
/// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should
/// be left inside the `origin` account.
///
/// Returns the limit that should be used by the meter. If origin can't afford the `limit`
/// it returns `Err`.
fn check_limit(
origin: &T::AccountId,
limit: Option<BalanceOf<T>>,
min_leftover: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError>;
/// This is called to inform the implementer that some balance should be charged due to
/// some interaction of the `origin` with a `contract`.
///
/// The balance transfer can either flow from `origin` to `contract` or the other way
/// around depending on whether `amount` constitutes a `Charge` or a `Refund`.
/// It should be used in combination with `check_limit` to check that no more balance than this
/// limit is ever charged.
fn charge(
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
state: &ContractState<T>,
) -> Result<(), DispatchError>;
}
/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged.
///
/// It uses [`pezframe_support::traits::fungible::Mutate`] in order to do accomplish the reserves.
pub enum ReservingExt {}
/// Used to implement a type state pattern for the meter.
///
/// It is sealed and cannot be implemented outside of this module.
pub trait State: private::Sealed {}
/// State parameter that constitutes a meter that is in its root state.
#[derive(Default, Debug)]
pub struct Root;
/// State parameter that constitutes a meter that is in its nested state.
/// Its value indicates whether the nested meter has its own limit.
#[derive(DefaultNoBound, RuntimeDebugNoBound)]
pub enum Nested {
#[default]
DerivedLimit,
OwnLimit,
}
impl State for Root {}
impl State for Nested {}
/// A type that allows the metering of consumed or freed storage of a single contract call stack.
#[derive(DefaultNoBound, RuntimeDebugNoBound)]
pub struct RawMeter<T: Config, E, S: State + Default + Debug> {
/// The limit of how much balance this meter is allowed to consume.
limit: BalanceOf<T>,
/// The amount of balance that was used in this meter and all of its already absorbed children.
total_deposit: DepositOf<T>,
/// The amount of storage changes that were recorded in this meter alone.
own_contribution: Contribution<T>,
/// List of charges that should be applied at the end of a contract stack execution.
///
/// We only have one charge per contract hence the size of this vector is
/// limited by the maximum call depth.
charges: Vec<Charge<T>>,
/// We store the nested state to determine if it has a special limit for sub-call.
nested: S,
/// Type parameter only used in impls.
_phantom: PhantomData<E>,
}
/// This type is used to describe a storage change when charging from the meter.
#[derive(Default, RuntimeDebugNoBound)]
pub struct Diff {
/// How many bytes were added to storage.
pub bytes_added: u32,
/// How many bytes were removed from storage.
pub bytes_removed: u32,
/// How many storage items were added to storage.
pub items_added: u32,
/// How many storage items were removed from storage.
pub items_removed: u32,
}
impl Diff {
/// Calculate how much of a charge or refund results from applying the diff and store it
/// in the passed `info` if any.
///
/// # Note
///
/// In case `None` is passed for `info` only charges are calculated. This is because refunds
/// are calculated pro rata of the existing storage within a contract and hence need extract
/// this information from the passed `info`.
pub fn update_contract<T: Config>(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
let per_byte = T::DepositPerByte::get();
let per_item = T::DepositPerItem::get();
let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed);
let items_added = self.items_added.saturating_sub(self.items_removed);
let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into()));
let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into()));
// Without any contract info we can only calculate diffs which add storage
let info = if let Some(info) = info {
info
} else {
debug_assert_eq!(self.bytes_removed, 0);
debug_assert_eq!(self.items_removed, 0);
return bytes_deposit.saturating_add(&items_deposit);
};
// Refunds are calculated pro rata based on the accumulated storage within the contract
let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added);
let items_removed = self.items_removed.saturating_sub(self.items_added);
let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
bytes_deposit = bytes_deposit
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit)));
let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items)
.unwrap_or_default()
.min(FixedU128::from_u32(1));
items_deposit = items_deposit
.saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit)));
// We need to update the contract info structure with the new deposits
info.storage_bytes =
info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed);
info.storage_items =
info.storage_items.saturating_add(items_added).saturating_sub(items_removed);
match &bytes_deposit {
Deposit::Charge(amount) =>
info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount),
Deposit::Refund(amount) =>
info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount),
}
match &items_deposit {
Deposit::Charge(amount) =>
info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount),
Deposit::Refund(amount) =>
info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount),
}
bytes_deposit.saturating_add(&items_deposit)
}
}
impl Diff {
fn saturating_add(&self, rhs: &Self) -> Self {
Self {
bytes_added: self.bytes_added.saturating_add(rhs.bytes_added),
bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed),
items_added: self.items_added.saturating_add(rhs.items_added),
items_removed: self.items_removed.saturating_add(rhs.items_removed),
}
}
}
/// The state of a contract.
///
/// In case of termination the beneficiary is indicated.
#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
pub enum ContractState<T: Config> {
Alive,
Terminated { beneficiary: AccountIdOf<T> },
}
/// Records information to charge or refund a plain account.
///
/// All the charges are deferred to the end of a whole call stack. Reason is that by doing
/// this we can do all the refunds before doing any charge. This way a plain account can use
/// more deposit than it has balance as along as it is covered by a refund. This
/// essentially makes the order of storage changes irrelevant with regard to the deposit system.
/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract
/// call. In that case the limit is enforced once the call is returned, rolling it back if
/// exhausted.
#[derive(RuntimeDebugNoBound, Clone)]
struct Charge<T: Config> {
contract: T::AccountId,
amount: DepositOf<T>,
state: ContractState<T>,
}
/// Records the storage changes of a storage meter.
#[derive(RuntimeDebugNoBound)]
enum Contribution<T: Config> {
/// The contract the meter belongs to is alive and accumulates changes using a [`Diff`].
Alive(Diff),
/// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of
/// its execution. In this process the [`Diff`] was converted into a [`Deposit`].
Checked(DepositOf<T>),
/// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`]
/// in order to calculate the refund. Upon termination the `reducible_balance` in the
/// contract's account is transferred to the [`beneficiary`].
Terminated { deposit: DepositOf<T>, beneficiary: AccountIdOf<T> },
}
impl<T: Config> Contribution<T> {
/// See [`Diff::update_contract`].
fn update_contract(&self, info: Option<&mut ContractInfo<T>>) -> DepositOf<T> {
match self {
Self::Alive(diff) => diff.update_contract::<T>(info),
Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) =>
deposit.clone(),
}
}
}
impl<T: Config> Default for Contribution<T> {
fn default() -> Self {
Self::Alive(Default::default())
}
}
/// Functions that apply to all states.
impl<T, E, S> RawMeter<T, E, S>
where
T: Config,
E: Ext<T>,
S: State + Default + Debug,
{
/// Create a new child that has its `limit`.
/// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent.
///
/// This is called whenever a new subcall is initiated in order to track the storage
/// usage for this sub call separately. This is necessary because we want to exchange balance
/// with the current contract we are interacting with.
pub fn nested(&self, limit: BalanceOf<T>) -> RawMeter<T, E, Nested> {
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
// If a special limit is specified higher than it is available,
// we want to enforce the lesser limit to the nested meter, to fail in the sub-call.
let limit = self.available().min(limit);
if limit.is_zero() {
RawMeter { limit: self.available(), ..Default::default() }
} else {
RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() }
}
}
/// Absorb a child that was spawned to handle a sub call.
///
/// This should be called whenever a sub call comes to its end and it is **not** reverted.
/// This does the actual balance transfer from/to `origin` and `contract` based on the
/// overall storage consumption of the call. It also updates the supplied contract info.
///
/// In case a contract reverted the child meter should just be dropped in order to revert
/// any changes it recorded.
///
/// # Parameters
///
/// - `absorbed`: The child storage meter that should be absorbed.
/// - `origin`: The origin that spawned the original root meter.
/// - `contract`: The contract's account that this sub call belongs to.
/// - `info`: The info of the contract in question. `None` if the contract was terminated.
pub fn absorb(
&mut self,
absorbed: RawMeter<T, E, Nested>,
contract: &T::AccountId,
info: Option<&mut ContractInfo<T>>,
) {
let own_deposit = absorbed.own_contribution.update_contract(info);
self.total_deposit = self
.total_deposit
.saturating_add(&absorbed.total_deposit)
.saturating_add(&own_deposit);
self.charges.extend_from_slice(&absorbed.charges);
if !own_deposit.is_zero() {
self.charges.push(Charge {
contract: contract.clone(),
amount: own_deposit,
state: absorbed.contract_state(),
});
}
}
/// The amount of balance that is still available from the original `limit`.
fn available(&self) -> BalanceOf<T> {
self.total_deposit.available(&self.limit)
}
/// Returns the state of the currently executed contract.
fn contract_state(&self) -> ContractState<T> {
match &self.own_contribution {
Contribution::Terminated { deposit: _, beneficiary } =>
ContractState::Terminated { beneficiary: beneficiary.clone() },
_ => ContractState::Alive,
}
}
}
/// Functions that only apply to the root state.
impl<T, E> RawMeter<T, E, Root>
where
T: Config,
E: Ext<T>,
{
/// Create new storage meter for the specified `origin` and `limit`.
///
/// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible.
pub fn new(
origin: &Origin<T>,
limit: Option<BalanceOf<T>>,
min_leftover: BalanceOf<T>,
) -> Result<Self, DispatchError> {
// Check the limit only if the origin is not root.
return match origin {
Origin::Root => Ok(Self {
limit: limit.unwrap_or(T::DefaultDepositLimit::get()),
..Default::default()
}),
Origin::Signed(o) => {
let limit = E::check_limit(o, limit, min_leftover)?;
Ok(Self { limit, ..Default::default() })
},
};
}
/// The total amount of deposit that should change hands as result of the execution
/// that this meter was passed into. This will also perform all the charges accumulated
/// in the whole contract stack.
///
/// This drops the root meter in order to make sure it is only called when the whole
/// execution did finish.
pub fn try_into_deposit(self, origin: &Origin<T>) -> Result<DepositOf<T>, DispatchError> {
// Only refund or charge deposit if the origin is not root.
let origin = match origin {
Origin::Root => return Ok(Deposit::Charge(Zero::zero())),
Origin::Signed(o) => o,
};
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) {
E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
}
for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) {
E::charge(origin, &charge.contract, &charge.amount, &charge.state)?;
}
Ok(self.total_deposit)
}
}
/// Functions that only apply to the nested state.
impl<T, E> RawMeter<T, E, Nested>
where
T: Config,
E: Ext<T>,
{
/// Charges `diff` from the meter.
pub fn charge(&mut self, diff: &Diff) {
match &mut self.own_contribution {
Contribution::Alive(own) => *own = own.saturating_add(diff),
_ => panic!("Charge is never called after termination; qed"),
};
}
/// Adds a deposit charge.
///
/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
/// change. This is the case when a `delegate_dependency` is added or removed, or when the
/// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the
/// deposit charge separately from the storage charge.
pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
self.total_deposit = self.total_deposit.saturating_add(&amount);
self.charges.push(Charge { contract, amount, state: ContractState::Alive });
}
/// Charges from `origin` a storage deposit for contract instantiation.
///
/// This immediately transfers the balance in order to create the account.
pub fn charge_instantiate(
&mut self,
origin: &T::AccountId,
contract: &T::AccountId,
contract_info: &mut ContractInfo<T>,
code_info: &CodeInfo<T>,
) -> Result<(), DispatchError> {
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
// We need to make sure that the contract's account exists.
let ed = Pallet::<T>::min_balance();
self.total_deposit = Deposit::Charge(ed);
T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?;
// A consumer is added at account creation and removed it on termination, otherwise the
// runtime could remove the account. As long as a contract exists its account must exist.
// With the consumer, a correct runtime cannot remove the account.
System::<T>::inc_consumers(contract)?;
let deposit = contract_info.update_base_deposit(&code_info);
let deposit = Deposit::Charge(deposit);
self.charge_deposit(contract.clone(), deposit);
Ok(())
}
/// Call to tell the meter that the currently executing contract was terminated.
///
/// This will manipulate the meter so that all storage deposit accumulated in
/// `contract_info` will be refunded to the `origin` of the meter. And the free
/// (`reducible_balance`) will be sent to the `beneficiary`.
pub fn terminate(&mut self, info: &ContractInfo<T>, beneficiary: T::AccountId) {
debug_assert!(matches!(self.contract_state(), ContractState::Alive));
self.own_contribution = Contribution::Terminated {
deposit: Deposit::Refund(info.total_deposit()),
beneficiary,
};
}
/// [`Self::charge`] does not enforce the storage limit since we want to do this check as late
/// as possible to allow later refunds to offset earlier charges.
///
/// # Note
///
/// We normally need to call this **once** for every call stack and not for every cross contract
/// call. However, if a dedicated limit is specified for a sub-call, this needs to be called
/// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is
/// used.
pub fn enforce_limit(
&mut self,
info: Option<&mut ContractInfo<T>>,
) -> Result<(), DispatchError> {
let deposit = self.own_contribution.update_contract(info);
let total_deposit = self.total_deposit.saturating_add(&deposit);
// We don't want to override a `Terminated` with a `Checked`.
if matches!(self.contract_state(), ContractState::Alive) {
self.own_contribution = Contribution::Checked(deposit);
}
if let Deposit::Charge(amount) = total_deposit {
if amount > self.limit {
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
}
}
Ok(())
}
/// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to
/// enforce its special limit if needed.
pub fn enforce_subcall_limit(
&mut self,
info: Option<&mut ContractInfo<T>>,
) -> Result<(), DispatchError> {
match self.nested {
Nested::OwnLimit => self.enforce_limit(info),
Nested::DerivedLimit => Ok(()),
}
}
}
impl<T: Config> Ext<T> for ReservingExt {
fn check_limit(
origin: &T::AccountId,
limit: Option<BalanceOf<T>>,
min_leftover: BalanceOf<T>,
) -> Result<BalanceOf<T>, DispatchError> {
// We are sending the `min_leftover` and the `min_balance` from the origin
// account as part of a contract call. Hence origin needs to have those left over
// as free balance after accounting for all deposits.
let max = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite)
.saturating_sub(min_leftover)
.saturating_sub(Pallet::<T>::min_balance());
let default = max.min(T::DefaultDepositLimit::get());
let limit = limit.unwrap_or(default);
ensure!(
limit <= max &&
matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success),
<Error<T>>::StorageDepositNotEnoughFunds,
);
Ok(limit)
}
fn charge(
origin: &T::AccountId,
contract: &T::AccountId,
amount: &DepositOf<T>,
state: &ContractState<T>,
) -> Result<(), DispatchError> {
match amount {
Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()),
Deposit::Charge(amount) => {
// This could fail if the `origin` does not have enough liquidity. Ideally, though,
// this should have been checked before with `check_limit`.
T::Currency::transfer_and_hold(
&HoldReason::StorageDepositReserve.into(),
origin,
contract,
*amount,
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)?;
Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndHeld {
from: origin.clone(),
to: contract.clone(),
amount: *amount,
});
},
Deposit::Refund(amount) => {
let transferred = T::Currency::transfer_on_hold(
&HoldReason::StorageDepositReserve.into(),
contract,
origin,
*amount,
Precision::BestEffort,
Restriction::Free,
Fortitude::Polite,
)?;
Pallet::<T>::deposit_event(Event::StorageDepositTransferredAndReleased {
from: contract.clone(),
to: origin.clone(),
amount: transferred,
});
if transferred < *amount {
// This should never happen, if it does it means that there is a bug in the
// runtime logic. In the rare case this happens we try to refund as much as we
// can, thus the `Precision::BestEffort`.
log::error!(
target: LOG_TARGET,
"Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.",
amount, contract, origin, transferred,
);
}
},
}
if let ContractState::<T>::Terminated { beneficiary } = state {
System::<T>::dec_consumers(&contract);
// Whatever is left in the contract is sent to the termination beneficiary.
T::Currency::transfer(
&contract,
&beneficiary,
T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite),
Preservation::Expendable,
)?;
}
Ok(())
}
}
mod private {
pub trait Sealed {}
impl Sealed for super::Root {}
impl Sealed for super::Nested {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
exec::AccountIdOf,
tests::{Test, ALICE, BOB, CHARLIE},
};
use pezframe_support::parameter_types;
use pretty_assertions::assert_eq;
type TestMeter = RawMeter<Test, TestExt, Root>;
parameter_types! {
static TestExtTestValue: TestExt = Default::default();
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct LimitCheck {
origin: AccountIdOf<Test>,
limit: BalanceOf<Test>,
min_leftover: BalanceOf<Test>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct Charge {
origin: AccountIdOf<Test>,
contract: AccountIdOf<Test>,
amount: DepositOf<Test>,
state: ContractState<Test>,
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct TestExt {
limit_checks: Vec<LimitCheck>,
charges: Vec<Charge>,
}
impl TestExt {
fn clear(&mut self) {
self.limit_checks.clear();
self.charges.clear();
}
}
impl Ext<Test> for TestExt {
fn check_limit(
origin: &AccountIdOf<Test>,
limit: Option<BalanceOf<Test>>,
min_leftover: BalanceOf<Test>,
) -> Result<BalanceOf<Test>, DispatchError> {
let limit = limit.unwrap_or(42);
TestExtTestValue::mutate(|ext| {
ext.limit_checks
.push(LimitCheck { origin: origin.clone(), limit, min_leftover })
});
Ok(limit)
}
fn charge(
origin: &AccountIdOf<Test>,
contract: &AccountIdOf<Test>,
amount: &DepositOf<Test>,
state: &ContractState<Test>,
) -> Result<(), DispatchError> {
TestExtTestValue::mutate(|ext| {
ext.charges.push(Charge {
origin: origin.clone(),
contract: contract.clone(),
amount: amount.clone(),
state: state.clone(),
})
});
Ok(())
}
}
fn clear_ext() {
TestExtTestValue::mutate(|ext| ext.clear())
}
struct ChargingTestCase {
origin: Origin<Test>,
deposit: DepositOf<Test>,
expected: TestExt,
}
#[derive(Default)]
struct StorageInfo {
bytes: u32,
items: u32,
bytes_deposit: BalanceOf<Test>,
items_deposit: BalanceOf<Test>,
}
fn new_info(info: StorageInfo) -> ContractInfo<Test> {
ContractInfo::<Test> {
trie_id: Default::default(),
code_hash: Default::default(),
storage_bytes: info.bytes,
storage_items: info.items,
storage_byte_deposit: info.bytes_deposit,
storage_item_deposit: info.items_deposit,
storage_base_deposit: Default::default(),
delegate_dependencies: Default::default(),
}
}
#[test]
fn new_reserves_balance_works() {
clear_ext();
TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
assert_eq!(
TestExtTestValue::get(),
TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
..Default::default()
}
)
}
#[test]
fn empty_charge_works() {
clear_ext();
let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap();
assert_eq!(meter.available(), 1_000);
// an empty charge does not create a `Charge` entry
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Default::default());
meter.absorb(nested0, &BOB, None);
assert_eq!(
TestExtTestValue::get(),
TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
..Default::default()
}
)
}
#[test]
fn charging_works() {
let test_cases = vec![
ChargingTestCase {
origin: Origin::<Test>::from_account_id(ALICE),
deposit: Deposit::Refund(28),
expected: TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(10),
state: ContractState::Alive,
},
Charge {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(20),
state: ContractState::Alive,
},
Charge {
origin: ALICE,
contract: BOB,
amount: Deposit::Charge(2),
state: ContractState::Alive,
},
],
},
},
ChargingTestCase {
origin: Origin::<Test>::Root,
deposit: Deposit::Charge(0),
expected: TestExt { limit_checks: vec![], charges: vec![] },
},
];
for test_case in test_cases {
clear_ext();
let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap();
assert_eq!(meter.available(), 100);
let mut nested0_info = new_info(StorageInfo {
bytes: 100,
items: 5,
bytes_deposit: 100,
items_deposit: 10,
});
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 108,
bytes_removed: 5,
items_added: 1,
items_removed: 2,
});
nested0.charge(&Diff { bytes_removed: 99, ..Default::default() });
let mut nested1_info = new_info(StorageInfo {
bytes: 100,
items: 10,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info));
let mut nested2_info = new_info(StorageInfo {
bytes: 100,
items: 7,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested2 = nested0.nested(BalanceOf::<Test>::zero());
nested2.charge(&Diff { items_removed: 7, ..Default::default() });
nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info));
nested0.enforce_limit(Some(&mut nested0_info)).unwrap();
meter.absorb(nested0, &BOB, Some(&mut nested0_info));
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
assert_eq!(nested0_info.extra_deposit(), 112);
assert_eq!(nested1_info.extra_deposit(), 110);
assert_eq!(nested2_info.extra_deposit(), 100);
assert_eq!(TestExtTestValue::get(), test_case.expected)
}
}
#[test]
fn termination_works() {
let test_cases = vec![
ChargingTestCase {
origin: Origin::<Test>::from_account_id(ALICE),
deposit: Deposit::Refund(108),
expected: TestExt {
limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }],
charges: vec![
Charge {
origin: ALICE,
contract: CHARLIE,
amount: Deposit::Refund(120),
state: ContractState::Terminated { beneficiary: CHARLIE },
},
Charge {
origin: ALICE,
contract: BOB,
amount: Deposit::Charge(12),
state: ContractState::Alive,
},
],
},
},
ChargingTestCase {
origin: Origin::<Test>::Root,
deposit: Deposit::Charge(0),
expected: TestExt { limit_checks: vec![], charges: vec![] },
},
];
for test_case in test_cases {
clear_ext();
let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap();
assert_eq!(meter.available(), 1_000);
let mut nested0 = meter.nested(BalanceOf::<Test>::zero());
nested0.charge(&Diff {
bytes_added: 5,
bytes_removed: 1,
items_added: 3,
items_removed: 1,
});
nested0.charge(&Diff { items_added: 2, ..Default::default() });
let mut nested1_info = new_info(StorageInfo {
bytes: 100,
items: 10,
bytes_deposit: 100,
items_deposit: 20,
});
let mut nested1 = nested0.nested(BalanceOf::<Test>::zero());
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
nested1.terminate(&nested1_info, CHARLIE);
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
nested0.absorb(nested1, &CHARLIE, None);
meter.absorb(nested0, &BOB, None);
assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit);
assert_eq!(TestExtTestValue::get(), test_case.expected)
}
}
}
@@ -0,0 +1,30 @@
// 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.
//! Shared utilities for testing contracts.
//! This is not part of the tests module because it is made public for other crates to use.
#![cfg(feature = "std")]
use pezframe_support::weights::Weight;
pub use pezsp_runtime::AccountId32;
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
pub const BOB: AccountId32 = AccountId32::new([2u8; 32]);
pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]);
pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]);
pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024);
pub mod builder;
@@ -0,0 +1,220 @@
// 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.
use super::GAS_LIMIT;
use crate::{
AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, Config,
ContractExecResult, ContractInstantiateResult, DebugInfo, Determinism, EventRecordOf,
ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight,
};
use codec::{Encode, HasCompact};
use core::fmt::Debug;
use pezframe_support::pezpallet_prelude::DispatchResultWithPostInfo;
use paste::paste;
use scale_info::TypeInfo;
/// Helper macro to generate a builder for contract API calls.
macro_rules! builder {
// Entry point to generate a builder for the given method.
(
$method:ident($($field:ident: $type:ty,)*) -> $result:ty;
$($extra:item)*
) => {
paste!{
builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result; $($extra)*);
}
};
// Generate the builder struct and its methods.
(
$name:ident,
$method:ident($($field:ident: $type:ty,)*) -> $result:ty;
$($extra:item)*
) => {
#[doc = concat!("A builder to construct a ", stringify!($method), " call")]
pub struct $name<T: Config> {
$($field: $type,)*
}
#[allow(dead_code)]
impl<T: Config> $name<T>
where
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
{
$(
#[doc = concat!("Set the ", stringify!($field))]
pub fn $field(mut self, value: $type) -> Self {
self.$field = value;
self
}
)*
#[doc = concat!("Build the ", stringify!($method), " call")]
pub fn build(self) -> $result {
Pallet::<T>::$method(
$(self.$field,)*
)
}
$($extra)*
}
}
}
builder!(
instantiate_with_code(
origin: OriginFor<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo;
/// Create an [`InstantiateWithCodeBuilder`] with default values.
pub fn instantiate_with_code(origin: OriginFor<T>, code: Vec<u8>) -> Self {
Self {
origin: origin,
value: 0u32.into(),
gas_limit: GAS_LIMIT,
storage_deposit_limit: None,
code,
data: vec![],
salt: vec![],
}
}
);
builder!(
instantiate(
origin: OriginFor<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code_hash: CodeHash<T>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo;
/// Create an [`InstantiateBuilder`] with default values.
pub fn instantiate(origin: OriginFor<T>, code_hash: CodeHash<T>) -> Self {
Self {
origin,
value: 0u32.into(),
gas_limit: GAS_LIMIT,
storage_deposit_limit: None,
code_hash,
data: vec![],
salt: vec![],
}
}
);
builder!(
bare_instantiate(
origin: AccountIdOf<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<BalanceOf<T>>,
code: Code<CodeHash<T>>,
data: Vec<u8>,
salt: Vec<u8>,
debug: DebugInfo,
collect_events: CollectEvents,
) -> ContractInstantiateResult<AccountIdOf<T>, BalanceOf<T>, EventRecordOf<T>>;
/// Build the instantiate call and unwrap the result.
pub fn build_and_unwrap_result(self) -> InstantiateReturnValue<AccountIdOf<T>> {
self.build().result.unwrap()
}
/// Build the instantiate call and unwrap the account id.
pub fn build_and_unwrap_account_id(self) -> AccountIdOf<T> {
self.build().result.unwrap().account_id
}
pub fn bare_instantiate(origin: AccountIdOf<T>, code: Code<CodeHash<T>>) -> Self {
Self {
origin,
value: 0u32.into(),
gas_limit: GAS_LIMIT,
storage_deposit_limit: None,
code,
data: vec![],
salt: vec![],
debug: DebugInfo::Skip,
collect_events: CollectEvents::Skip,
}
}
);
builder!(
call(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo;
/// Create a [`CallBuilder`] with default values.
pub fn call(origin: OriginFor<T>, dest: AccountIdLookupOf<T>) -> Self {
CallBuilder {
origin,
dest,
value: 0u32.into(),
gas_limit: GAS_LIMIT,
storage_deposit_limit: None,
data: vec![],
}
}
);
builder!(
bare_call(
origin: AccountIdOf<T>,
dest: AccountIdOf<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<BalanceOf<T>>,
data: Vec<u8>,
debug: DebugInfo,
collect_events: CollectEvents,
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>>;
/// Build the call and unwrap the result.
pub fn build_and_unwrap_result(self) -> ExecReturnValue {
self.build().result.unwrap()
}
/// Create a [`BareCallBuilder`] with default values.
pub fn bare_call(origin: AccountIdOf<T>, dest: AccountIdOf<T>) -> Self {
Self {
origin,
dest,
value: 0u32.into(),
gas_limit: GAS_LIMIT,
storage_deposit_limit: None,
data: vec![],
debug: DebugInfo::Skip,
collect_events: CollectEvents::Skip,
determinism: Determinism::Enforced,
}
}
);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
// 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.
pub use pallet::*;
#[pezframe_support::pallet(dev_mode)]
pub mod pallet {
use pezframe_support::{
dispatch::{Pays, PostDispatchInfo},
ensure,
pezpallet_prelude::DispatchResultWithPostInfo,
weights::Weight,
};
use pezframe_system::pezpallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: pezframe_system::Config {}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Dummy function that overcharges the predispatch weight, allowing us to test the correct
/// values of [`ContractResult::gas_consumed`] and [`ContractResult::gas_required`] in
/// tests.
#[pallet::call_index(1)]
#[pallet::weight(*pre_charge)]
pub fn overestimate_pre_charge(
origin: OriginFor<T>,
pre_charge: Weight,
actual_weight: Weight,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
ensure!(pre_charge.any_gt(actual_weight), "pre_charge must be > actual_weight");
Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
}
}
}
@@ -0,0 +1,229 @@
// 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.
use super::*;
use crate::{
debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing},
primitives::ExecReturnValue,
AccountIdOf,
};
use pezframe_support::traits::Currency;
use pretty_assertions::assert_eq;
use std::cell::RefCell;
#[derive(Clone, PartialEq, Eq, Debug)]
struct DebugFrame {
contract_account: AccountId32,
call: ExportedFunction,
input: Vec<u8>,
result: Option<Vec<u8>>,
}
thread_local! {
static DEBUG_EXECUTION_TRACE: RefCell<Vec<DebugFrame>> = RefCell::new(Vec::new());
static INTERCEPTED_ADDRESS: RefCell<Option<AccountId32>> = RefCell::new(None);
}
pub struct TestDebug;
pub struct TestCallSpan {
contract_account: AccountId32,
call: ExportedFunction,
input: Vec<u8>,
}
impl Tracing<Test> for TestDebug {
type CallSpan = TestCallSpan;
fn new_call_span(
contract_account: &AccountIdOf<Test>,
entry_point: ExportedFunction,
input_data: &[u8],
) -> TestCallSpan {
DEBUG_EXECUTION_TRACE.with(|d| {
d.borrow_mut().push(DebugFrame {
contract_account: contract_account.clone(),
call: entry_point,
input: input_data.to_vec(),
result: None,
})
});
TestCallSpan {
contract_account: contract_account.clone(),
call: entry_point,
input: input_data.to_vec(),
}
}
}
impl CallInterceptor<Test> for TestDebug {
fn intercept_call(
contract_address: &<Test as pezframe_system::Config>::AccountId,
_entry_point: &ExportedFunction,
_input_data: &[u8],
) -> Option<ExecResult> {
INTERCEPTED_ADDRESS.with(|i| {
if i.borrow().as_ref() == Some(contract_address) {
Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] }))
} else {
None
}
})
}
}
impl CallSpan for TestCallSpan {
fn after_call(self, output: &ExecReturnValue) {
DEBUG_EXECUTION_TRACE.with(|d| {
d.borrow_mut().push(DebugFrame {
contract_account: self.contract_account,
call: self.call,
input: self.input,
result: Some(output.data.clone()),
})
});
}
}
#[test]
fn debugging_works() {
let (wasm_caller, _) = compile_module::<Test>("call").unwrap();
let (wasm_callee, _) = compile_module::<Test>("store_call").unwrap();
fn current_stack() -> Vec<DebugFrame> {
DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone())
}
fn deploy(wasm: Vec<u8>) -> AccountId32 {
Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id
}
fn constructor_frame(contract_account: &AccountId32, after: bool) -> DebugFrame {
DebugFrame {
contract_account: contract_account.clone(),
call: ExportedFunction::Constructor,
input: vec![],
result: if after { Some(vec![]) } else { None },
}
}
fn call_frame(contract_account: &AccountId32, args: Vec<u8>, after: bool) -> DebugFrame {
DebugFrame {
contract_account: contract_account.clone(),
call: ExportedFunction::Call,
input: args,
result: if after { Some(vec![]) } else { None },
}
}
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_eq!(current_stack(), vec![]);
let addr_caller = deploy(wasm_caller);
let addr_callee = deploy(wasm_callee);
assert_eq!(
current_stack(),
vec![
constructor_frame(&addr_caller, false),
constructor_frame(&addr_caller, true),
constructor_frame(&addr_callee, false),
constructor_frame(&addr_callee, true),
]
);
let main_args = (100u32, &addr_callee.clone()).encode();
let inner_args = (100u32).encode();
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
None,
main_args.clone()
));
let stack_top = current_stack()[4..].to_vec();
assert_eq!(
stack_top,
vec![
call_frame(&addr_caller, main_args.clone(), false),
call_frame(&addr_callee, inner_args.clone(), false),
call_frame(&addr_callee, inner_args, true),
call_frame(&addr_caller, main_args, true),
]
);
});
}
#[test]
fn call_interception_works() {
let (wasm, _) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let account_id = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
// some salt to ensure that the address of this contract is unique among all tests
vec![0x41, 0x41, 0x41, 0x41],
DebugInfo::Skip,
CollectEvents::Skip,
)
.result
.unwrap()
.account_id;
// no interception yet
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
account_id.clone(),
0,
GAS_LIMIT,
None,
vec![],
));
// intercept calls to this contract
INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id.clone()));
assert_err_ignore_postinfo!(
Contracts::call(RuntimeOrigin::signed(ALICE), account_id, 0, GAS_LIMIT, None, vec![],),
<Error<Test>>::ContractReverted,
);
});
}
@@ -0,0 +1,698 @@
// 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 contains routines for accessing and altering a contract transient storage.
use crate::{
exec::{AccountIdOf, Key},
storage::WriteOutcome,
Config, Error,
};
use alloc::{collections::BTreeMap, vec::Vec};
use codec::Encode;
use core::{marker::PhantomData, mem};
use pezframe_support::DefaultNoBound;
use pezsp_runtime::{DispatchError, DispatchResult, Saturating};
/// Meter entry tracks transaction allocations.
#[derive(Default, Debug)]
pub struct MeterEntry {
/// Allocations made in the current transaction.
pub amount: u32,
/// Allocations limit in the current transaction.
pub limit: u32,
}
impl MeterEntry {
/// Create a new entry.
fn new(limit: u32) -> Self {
Self { limit, amount: Default::default() }
}
/// Check if the allocated amount exceeds the limit.
fn exceeds_limit(&self, amount: u32) -> bool {
self.amount.saturating_add(amount) > self.limit
}
/// Absorb the allocation amount of the nested entry into the current entry.
fn absorb(&mut self, rhs: Self) {
self.amount.saturating_accrue(rhs.amount)
}
}
// The storage meter enforces a limit for each transaction,
// which is calculated as free_storage * (1 - 1/16) for each subsequent frame.
#[derive(DefaultNoBound)]
pub struct StorageMeter<T: Config> {
nested_meters: Vec<MeterEntry>,
root_meter: MeterEntry,
_phantom: PhantomData<T>,
}
impl<T: Config> StorageMeter<T> {
const STORAGE_FRACTION_DENOMINATOR: u32 = 16;
/// Create a new storage allocation meter.
fn new(memory_limit: u32) -> Self {
Self { root_meter: MeterEntry::new(memory_limit), ..Default::default() }
}
/// Charge the allocated amount of transaction storage from the meter.
fn charge(&mut self, amount: u32) -> DispatchResult {
let meter = self.current_mut();
if meter.exceeds_limit(amount) {
return Err(Error::<T>::OutOfTransientStorage.into());
}
meter.amount.saturating_accrue(amount);
Ok(())
}
/// Revert a transaction meter.
fn revert(&mut self) {
self.nested_meters.pop().expect(
"A call to revert a meter must be preceded by a corresponding call to start a meter;
the code within this crate makes sure that this is always the case; qed",
);
}
/// Start a transaction meter.
fn start(&mut self) {
let meter = self.current();
let mut transaction_limit = meter.limit.saturating_sub(meter.amount);
if !self.nested_meters.is_empty() {
// Allow use of (1 - 1/STORAGE_FRACTION_DENOMINATOR) of free storage for subsequent
// calls.
transaction_limit.saturating_reduce(
transaction_limit.saturating_div(Self::STORAGE_FRACTION_DENOMINATOR),
);
}
self.nested_meters.push(MeterEntry::new(transaction_limit));
}
/// Commit a transaction meter.
fn commit(&mut self) {
let transaction_meter = self.nested_meters.pop().expect(
"A call to commit a meter must be preceded by a corresponding call to start a meter;
the code within this crate makes sure that this is always the case; qed",
);
self.current_mut().absorb(transaction_meter)
}
/// The total allocated amount of memory.
#[cfg(test)]
fn total_amount(&self) -> u32 {
self.nested_meters
.iter()
.fold(self.root_meter.amount, |acc, e| acc.saturating_add(e.amount))
}
/// A mutable reference to the current meter entry.
pub fn current_mut(&mut self) -> &mut MeterEntry {
self.nested_meters.last_mut().unwrap_or(&mut self.root_meter)
}
/// A reference to the current meter entry.
pub fn current(&self) -> &MeterEntry {
self.nested_meters.last().unwrap_or(&self.root_meter)
}
}
/// An entry representing a journal change.
struct JournalEntry {
key: Vec<u8>,
prev_value: Option<Vec<u8>>,
}
impl JournalEntry {
/// Create a new change.
fn new(key: Vec<u8>, prev_value: Option<Vec<u8>>) -> Self {
Self { key, prev_value }
}
/// Revert the change.
fn revert(self, storage: &mut Storage) {
storage.write(&self.key, self.prev_value);
}
}
/// A journal containing transient storage modifications.
struct Journal(Vec<JournalEntry>);
impl Journal {
/// Create a new journal.
fn new() -> Self {
Self(Default::default())
}
/// Add a change to the journal.
fn push(&mut self, entry: JournalEntry) {
self.0.push(entry);
}
/// Length of the journal.
fn len(&self) -> usize {
self.0.len()
}
/// Roll back all journal changes until the chackpoint
fn rollback(&mut self, storage: &mut Storage, checkpoint: usize) {
self.0.drain(checkpoint..).rev().for_each(|entry| entry.revert(storage));
}
}
/// Storage for maintaining the current transaction state.
#[derive(Default)]
struct Storage(BTreeMap<Vec<u8>, Vec<u8>>);
impl Storage {
/// Read the storage entry.
fn read(&self, key: &Vec<u8>) -> Option<Vec<u8>> {
self.0.get(key).cloned()
}
/// Write the storage entry.
fn write(&mut self, key: &Vec<u8>, value: Option<Vec<u8>>) -> Option<Vec<u8>> {
if let Some(value) = value {
// Insert storage entry.
self.0.insert(key.clone(), value)
} else {
// Remove storage entry.
self.0.remove(key)
}
}
}
/// Transient storage behaves almost identically to regular storage but is discarded after each
/// transaction. It consists of a `BTreeMap` for the current state, a journal of all changes, and a
/// list of checkpoints. On entry to the `start_transaction` function, a marker (checkpoint) is
/// added to the list. New values are written to the current state, and the previous value is
/// recorded in the journal (`write`). When the `commit_transaction` function is called, the marker
/// to the journal index (checkpoint) of when that call was entered is discarded.
/// On `rollback_transaction`, all entries are reverted up to the last checkpoint.
pub struct TransientStorage<T: Config> {
// The storage and journal size is limited by the storage meter.
storage: Storage,
journal: Journal,
// The size of the StorageMeter is limited by the stack depth.
meter: StorageMeter<T>,
// The size of the checkpoints is limited by the stack depth.
checkpoints: Vec<usize>,
}
impl<T: Config> TransientStorage<T> {
/// Create new transient storage with the supplied memory limit.
pub fn new(memory_limit: u32) -> Self {
TransientStorage {
storage: Default::default(),
journal: Journal::new(),
checkpoints: Default::default(),
meter: StorageMeter::new(memory_limit),
}
}
/// Read the storage value. If the entry does not exist, `None` is returned.
pub fn read(&self, account: &AccountIdOf<T>, key: &Key<T>) -> Option<Vec<u8>> {
self.storage.read(&Self::storage_key(&account.encode(), &key.hash()))
}
/// Write a value to storage.
///
/// If the `value` is `None`, then the entry is removed. If `take` is true,
/// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`].
/// If the entry did not exist, [`WriteOutcome::New`] is returned.
pub fn write(
&mut self,
account: &AccountIdOf<T>,
key: &Key<T>,
value: Option<Vec<u8>>,
take: bool,
) -> Result<WriteOutcome, DispatchError> {
let key = Self::storage_key(&account.encode(), &key.hash());
let prev_value = self.storage.read(&key);
// Skip if the same value is being set.
if prev_value != value {
// Calculate the allocation size.
if let Some(value) = &value {
// Charge the key, value and journal entry.
// If a new value is written, a new journal entry is created. The previous value is
// moved to the journal along with its key, and the new value is written to
// storage.
let key_len = key.capacity();
let mut amount = value
.capacity()
.saturating_add(key_len)
.saturating_add(mem::size_of::<JournalEntry>());
if prev_value.is_none() {
// Charge a new storage entry.
// If there was no previous value, a new entry is added to storage (BTreeMap)
// containing a Vec for the key and a Vec for the value. The value was already
// included in the amount.
amount.saturating_accrue(key_len.saturating_add(mem::size_of::<Vec<u8>>()));
}
self.meter.charge(amount as _)?;
}
self.storage.write(&key, value);
// Update the journal.
self.journal.push(JournalEntry::new(key, prev_value.clone()));
}
Ok(match (take, prev_value) {
(_, None) => WriteOutcome::New,
(false, Some(prev_value)) => WriteOutcome::Overwritten(prev_value.len() as _),
(true, Some(prev_value)) => WriteOutcome::Taken(prev_value),
})
}
/// Start a new nested transaction.
///
/// This allows to either commit or roll back all changes that are made after this call.
/// For every transaction there must be a matching call to either `rollback_transaction`
/// or `commit_transaction`.
pub fn start_transaction(&mut self) {
self.meter.start();
self.checkpoints.push(self.journal.len());
}
/// Rollback the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are discarded.
///
/// # Panics
///
/// Will panic if there is no open transaction.
pub fn rollback_transaction(&mut self) {
let checkpoint = self
.checkpoints
.pop()
.expect(
"A call to rollback_transaction must be preceded by a corresponding call to start_transaction;
the code within this crate makes sure that this is always the case; qed"
);
self.meter.revert();
self.journal.rollback(&mut self.storage, checkpoint);
}
/// Commit the last transaction started by `start_transaction`.
///
/// Any changes made during that transaction are committed.
///
/// # Panics
///
/// Will panic if there is no open transaction.
pub fn commit_transaction(&mut self) {
self.checkpoints
.pop()
.expect(
"A call to commit_transaction must be preceded by a corresponding call to start_transaction;
the code within this crate makes sure that this is always the case; qed"
);
self.meter.commit();
}
/// The storage allocation meter used for transaction metering.
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn meter(&mut self) -> &mut StorageMeter<T> {
return &mut self.meter;
}
fn storage_key(account: &[u8], key: &[u8]) -> Vec<u8> {
let mut storage_key = Vec::with_capacity(account.len() + key.len());
storage_key.extend_from_slice(&account);
storage_key.extend_from_slice(&key);
storage_key
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
tests::{Test, ALICE, BOB, CHARLIE},
Error,
};
use core::u32::MAX;
// Calculate the allocation size for the given entry.
fn allocation_size(
account: &AccountIdOf<Test>,
key: &Key<Test>,
value: Option<Vec<u8>>,
) -> u32 {
let mut storage: TransientStorage<Test> = TransientStorage::<Test>::new(MAX);
storage
.write(account, key, value, false)
.expect("Could not write to transient storage.");
storage.meter().current().amount
}
#[test]
fn read_write_works() {
let mut storage: TransientStorage<Test> = TransientStorage::<Test>::new(2048);
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![2]), true),
Ok(WriteOutcome::New)
);
assert_eq!(
storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![3]), false),
Ok(WriteOutcome::New)
);
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1]));
assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![2]));
assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![3]));
// Overwrite values.
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![4, 5]), false),
Ok(WriteOutcome::Overwritten(1))
);
assert_eq!(
storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![6, 7]), true),
Ok(WriteOutcome::Taken(vec![3]))
);
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1]));
assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![4, 5]));
assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![6, 7]));
// Check for an empty value.
assert_eq!(
storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![]), true),
Ok(WriteOutcome::Taken(vec![6, 7]))
);
assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![]));
assert_eq!(
storage.write(&BOB, &Key::Fix([3; 32]), None, true),
Ok(WriteOutcome::Taken(vec![]))
);
assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), None);
}
#[test]
fn read_write_with_var_sized_keys_works() {
let mut storage = TransientStorage::<Test>::new(2048);
assert_eq!(
storage.write(
&ALICE,
&Key::<Test>::try_from_var([1; 64].to_vec()).unwrap(),
Some(vec![1]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
storage.write(
&BOB,
&Key::<Test>::try_from_var([2; 64].to_vec()).unwrap(),
Some(vec![2, 3]),
false
),
Ok(WriteOutcome::New)
);
assert_eq!(
storage.read(&ALICE, &Key::<Test>::try_from_var([1; 64].to_vec()).unwrap()),
Some(vec![1])
);
assert_eq!(
storage.read(&BOB, &Key::<Test>::try_from_var([2; 64].to_vec()).unwrap()),
Some(vec![2, 3])
);
// Overwrite values.
assert_eq!(
storage.write(
&ALICE,
&Key::<Test>::try_from_var([1; 64].to_vec()).unwrap(),
Some(vec![4, 5]),
false
),
Ok(WriteOutcome::Overwritten(1))
);
assert_eq!(
storage.read(&ALICE, &Key::<Test>::try_from_var([1; 64].to_vec()).unwrap()),
Some(vec![4, 5])
);
}
#[test]
fn rollback_transaction_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.rollback_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None)
}
#[test]
fn commit_transaction_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.commit_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1]))
}
#[test]
fn overwrite_and_commmit_transaction_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1, 2]), false),
Ok(WriteOutcome::Overwritten(1))
);
storage.commit_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1, 2]))
}
#[test]
fn rollback_in_nested_transaction_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.rollback_transaction();
storage.commit_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1]));
assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None)
}
#[test]
fn commit_in_nested_transaction_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false),
Ok(WriteOutcome::New)
);
storage.commit_transaction();
storage.commit_transaction();
storage.commit_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1]));
assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), Some(vec![2]));
assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), Some(vec![3]));
}
#[test]
fn rollback_all_transactions_works() {
let mut storage = TransientStorage::<Test>::new(1024);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false),
Ok(WriteOutcome::New)
);
storage.commit_transaction();
storage.commit_transaction();
storage.rollback_transaction();
assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None);
assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None);
assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), None);
}
#[test]
fn metering_transactions_works() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size * 2);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
let limit = storage.meter().current().limit;
storage.commit_transaction();
storage.start_transaction();
assert_eq!(storage.meter().current().limit, limit - size);
assert_eq!(storage.meter().current().limit - storage.meter().current().amount, size);
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
assert_eq!(storage.meter().current().amount, size);
storage.commit_transaction();
assert_eq!(storage.meter().total_amount(), size * 2);
}
#[test]
fn metering_nested_transactions_works() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size * 3);
storage.start_transaction();
let limit = storage.meter().current().limit;
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(storage.meter().total_amount(), size);
assert!(storage.meter().current().limit < limit - size);
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.commit_transaction();
assert_eq!(storage.meter().current().limit, limit);
assert_eq!(storage.meter().total_amount(), storage.meter().current().amount);
storage.commit_transaction();
}
#[test]
fn metering_transaction_fails() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size - 1);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Err(Error::<Test>::OutOfTransientStorage.into())
);
assert_eq!(storage.meter.current().amount, 0);
storage.commit_transaction();
assert_eq!(storage.meter.total_amount(), 0);
}
#[test]
fn metering_nested_transactions_fails() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size * 2);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false),
Err(Error::<Test>::OutOfTransientStorage.into())
);
storage.commit_transaction();
storage.commit_transaction();
assert_eq!(storage.meter.total_amount(), size);
}
#[test]
fn metering_nested_transaction_with_rollback_works() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size * 2);
storage.start_transaction();
let limit = storage.meter.current().limit;
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.rollback_transaction();
assert_eq!(storage.meter.total_amount(), 0);
assert_eq!(storage.meter.current().limit, limit);
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
let amount = storage.meter().current().amount;
assert_eq!(storage.meter().total_amount(), amount);
storage.commit_transaction();
}
#[test]
fn metering_with_rollback_works() {
let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]));
let mut storage = TransientStorage::<Test>::new(size * 5);
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
let amount = storage.meter.total_amount();
storage.start_transaction();
assert_eq!(
storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.start_transaction();
assert_eq!(
storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false),
Ok(WriteOutcome::New)
);
storage.commit_transaction();
storage.rollback_transaction();
assert_eq!(storage.meter.total_amount(), amount);
storage.commit_transaction();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,876 @@
// 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!")
);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff