feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,792 @@
|
||||
// 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, BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, HoldReason,
|
||||
Pallet, StorageDeposit as Deposit, LOG_TARGET,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use pezframe_support::{traits::Get, DefaultNoBound, RuntimeDebugNoBound};
|
||||
use pezsp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
DispatchError, DispatchResult, 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 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 will fail in case the `origin` has not enough balance to cover all storage deposits.
|
||||
fn charge(
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
amount: &DepositOf<T>,
|
||||
exec_config: &ExecConfig<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(Default, Debug)]
|
||||
pub struct Nested;
|
||||
|
||||
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>>,
|
||||
/// True if this is the root meter.
|
||||
///
|
||||
/// Sometimes we cannot know at compile time.
|
||||
is_root: bool,
|
||||
/// Type parameter only used in impls.
|
||||
_phantom: PhantomData<(E, S)>,
|
||||
}
|
||||
|
||||
/// 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::DepositPerChildTrieItem::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 {
|
||||
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.
|
||||
#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)]
|
||||
pub enum ContractState<T: Config> {
|
||||
Alive { amount: DepositOf<T> },
|
||||
Terminated,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
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>),
|
||||
}
|
||||
|
||||
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::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`.
|
||||
///
|
||||
/// 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> {
|
||||
RawMeter { limit: self.available().min(limit), ..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(),
|
||||
state: ContractState::Alive { amount: own_deposit },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a charge that has taken place externally.
|
||||
///
|
||||
/// This will not perform a charge. It just records it to reflect it in the
|
||||
/// total amount of storage required for a transaction.
|
||||
pub fn record_charge(&mut self, amount: &DepositOf<T>) -> DispatchResult {
|
||||
let total_deposit = self.total_deposit.saturating_add(&amount);
|
||||
|
||||
// Limits are enforced at the end of each frame. But plain balance transfers
|
||||
// do not sapwn a frame. This is specifically to enforce the limit for those.
|
||||
if self.is_root && total_deposit.charge_or_zero() > self.limit {
|
||||
log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit);
|
||||
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
|
||||
}
|
||||
|
||||
self.total_deposit = total_deposit;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The amount of balance that this meter has consumed.
|
||||
///
|
||||
/// This disregards any refunds pending in the current frame. This
|
||||
/// is because we can calculate refunds only at the end of each frame.
|
||||
pub fn consumed(&self) -> DepositOf<T> {
|
||||
self.total_deposit.saturating_add(&self.own_contribution.update_contract(None))
|
||||
}
|
||||
|
||||
/// The amount of balance still available from the current meter.
|
||||
///
|
||||
/// This includes charges from the current frame but no refunds.
|
||||
pub fn available(&self) -> BalanceOf<T> {
|
||||
self.consumed().available(&self.limit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that only apply to the root state.
|
||||
impl<T, E> RawMeter<T, E, Root>
|
||||
where
|
||||
T: Config,
|
||||
E: Ext<T>,
|
||||
{
|
||||
/// Create new storage limiting storage deposits to the passed `limit`.
|
||||
///
|
||||
/// If the limit is larger than what the origin can afford we will just fail
|
||||
/// when collecting the deposits in `try_into_deposit`.
|
||||
pub fn new(limit: BalanceOf<T>) -> Self {
|
||||
Self { limit, is_root: true, ..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(
|
||||
mut self,
|
||||
origin: &Origin<T>,
|
||||
exec_config: &ExecConfig<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,
|
||||
};
|
||||
|
||||
// Coalesce charges of the same contract
|
||||
self.charges.sort_by(|a, b| a.contract.cmp(&b.contract));
|
||||
self.charges = {
|
||||
let mut coalesced: Vec<Charge<T>> = Vec::with_capacity(self.charges.len());
|
||||
for mut ch in self.charges {
|
||||
if let Some(last) = coalesced.last_mut() {
|
||||
if last.contract == ch.contract {
|
||||
match (&mut last.state, &mut ch.state) {
|
||||
(
|
||||
ContractState::Alive { amount: last_amount },
|
||||
ContractState::Alive { amount: ch_amount },
|
||||
) => {
|
||||
*last_amount = last_amount.saturating_add(ch_amount);
|
||||
},
|
||||
(ContractState::Alive { amount }, ContractState::Terminated) |
|
||||
(ContractState::Terminated, ContractState::Alive { amount }) => {
|
||||
// undo all deposits made by a terminated contract
|
||||
self.total_deposit = self.total_deposit.saturating_sub(amount);
|
||||
last.state = ContractState::Terminated;
|
||||
},
|
||||
(ContractState::Terminated, ContractState::Terminated) => {
|
||||
debug_assert!(
|
||||
false,
|
||||
"We never emit two terminates for the same contract."
|
||||
)
|
||||
},
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
coalesced.push(ch);
|
||||
}
|
||||
coalesced
|
||||
};
|
||||
|
||||
// refunds first so origin is able to pay for the charges using the refunds
|
||||
for charge in self.charges.iter() {
|
||||
if let ContractState::Alive { amount: amount @ Deposit::Refund(_) } = &charge.state {
|
||||
E::charge(origin, &charge.contract, amount, exec_config)?;
|
||||
}
|
||||
}
|
||||
for charge in self.charges.iter() {
|
||||
if let ContractState::Alive { amount: amount @ Deposit::Charge(_) } = &charge.state {
|
||||
E::charge(origin, &charge.contract, amount, exec_config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.total_deposit)
|
||||
}
|
||||
|
||||
/// Flag a `contract` as terminated.
|
||||
///
|
||||
/// This will signal to the meter to discard all charged and refunds incured by this
|
||||
/// contract.
|
||||
pub fn terminate(&mut self, contract: T::AccountId, refunded: BalanceOf<T>) {
|
||||
self.total_deposit = self.total_deposit.saturating_add(&Deposit::Refund(refunded));
|
||||
self.charges.push(Charge { contract, state: ContractState::Terminated });
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that only apply to the nested state.
|
||||
impl<T: Config, E: Ext<T>> RawMeter<T, E, Nested> {
|
||||
/// 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 charge without recording it in the contract info.
|
||||
///
|
||||
/// Use this method instead of [`Self::charge`] when the charge is not the result of a storage
|
||||
/// change within the contract's child trie. This is the case when 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.
|
||||
///
|
||||
/// If this functions is used the amount of the charge has to be stored by the caller somewhere
|
||||
/// alese in order to be able to refund it.
|
||||
pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf<T>) {
|
||||
// will not fail in a nested meter
|
||||
self.record_charge(&amount).ok();
|
||||
self.charges.push(Charge { contract, state: ContractState::Alive { amount } });
|
||||
}
|
||||
|
||||
/// [`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.
|
||||
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);
|
||||
self.own_contribution = Contribution::Checked(deposit);
|
||||
if let Deposit::Charge(amount) = total_deposit {
|
||||
if amount > self.limit {
|
||||
log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit);
|
||||
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Ext<T> for ReservingExt {
|
||||
fn charge(
|
||||
origin: &T::AccountId,
|
||||
contract: &T::AccountId,
|
||||
amount: &DepositOf<T>,
|
||||
exec_config: &ExecConfig<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
match amount {
|
||||
Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => (),
|
||||
Deposit::Charge(amount) => {
|
||||
<Pallet<T>>::charge_deposit(
|
||||
Some(HoldReason::StorageDepositReserve),
|
||||
origin,
|
||||
contract,
|
||||
*amount,
|
||||
exec_config,
|
||||
)?;
|
||||
},
|
||||
Deposit::Refund(amount) => {
|
||||
<Pallet<T>>::refund_deposit(
|
||||
HoldReason::StorageDepositReserve,
|
||||
contract,
|
||||
origin,
|
||||
*amount,
|
||||
Some(exec_config),
|
||||
)?;
|
||||
},
|
||||
}
|
||||
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, test_utils::*, tests::Test};
|
||||
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 Charge {
|
||||
origin: AccountIdOf<Test>,
|
||||
contract: AccountIdOf<Test>,
|
||||
amount: DepositOf<Test>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TestExt {
|
||||
charges: Vec<Charge>,
|
||||
}
|
||||
|
||||
impl TestExt {
|
||||
fn clear(&mut self) {
|
||||
self.charges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Ext<Test> for TestExt {
|
||||
fn charge(
|
||||
origin: &AccountIdOf<Test>,
|
||||
contract: &AccountIdOf<Test>,
|
||||
amount: &DepositOf<Test>,
|
||||
_exec_config: &ExecConfig<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
TestExtTestValue::mutate(|ext| {
|
||||
ext.charges.push(Charge {
|
||||
origin: origin.clone(),
|
||||
contract: contract.clone(),
|
||||
amount: amount.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>,
|
||||
immutable_data_len: u32,
|
||||
}
|
||||
|
||||
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(),
|
||||
immutable_data_len: info.immutable_data_len,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_reserves_balance_works() {
|
||||
clear_ext();
|
||||
|
||||
TestMeter::new(1_000);
|
||||
|
||||
assert_eq!(TestExtTestValue::get(), TestExt { ..Default::default() })
|
||||
}
|
||||
|
||||
/// Previously, passing a limit of 0 meant unlimited storage for a nested call.
|
||||
///
|
||||
/// Now, a limit of 0 means the subcall will not be able to use any storage.
|
||||
#[test]
|
||||
fn nested_zero_limit_requested() {
|
||||
clear_ext();
|
||||
|
||||
let meter = TestMeter::new(1_000);
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
let nested0 = meter.nested(BalanceOf::<Test>::zero());
|
||||
assert_eq!(nested0.available(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_some_limit_requested() {
|
||||
clear_ext();
|
||||
|
||||
let meter = TestMeter::new(1_000);
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
let nested0 = meter.nested(500);
|
||||
assert_eq!(nested0.available(), 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_all_limit_requested() {
|
||||
clear_ext();
|
||||
|
||||
let meter = TestMeter::new(1_000);
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
let nested0 = meter.nested(1_000);
|
||||
assert_eq!(nested0.available(), 1_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_over_limit_requested() {
|
||||
clear_ext();
|
||||
|
||||
let meter = TestMeter::new(1_000);
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
let nested0 = meter.nested(2_000);
|
||||
assert_eq!(nested0.available(), 1_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_charge_works() {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(1_000);
|
||||
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 { ..Default::default() })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn charging_works() {
|
||||
let test_cases = vec![
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::from_account_id(ALICE),
|
||||
deposit: Deposit::Refund(28),
|
||||
expected: TestExt {
|
||||
charges: vec![
|
||||
Charge { origin: ALICE, contract: CHARLIE, amount: Deposit::Refund(30) },
|
||||
Charge { origin: ALICE, contract: BOB, amount: Deposit::Charge(2) },
|
||||
],
|
||||
},
|
||||
},
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::Root,
|
||||
deposit: Deposit::Charge(0),
|
||||
expected: TestExt { charges: vec![] },
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(100);
|
||||
assert_eq!(meter.available(), 100);
|
||||
|
||||
let mut nested0_info = new_info(StorageInfo {
|
||||
bytes: 100,
|
||||
items: 5,
|
||||
bytes_deposit: 100,
|
||||
items_deposit: 10,
|
||||
immutable_data_len: 0,
|
||||
});
|
||||
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,
|
||||
immutable_data_len: 0,
|
||||
});
|
||||
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,
|
||||
immutable_data_len: 0,
|
||||
});
|
||||
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, &ExecConfig::new_bizinikiwi_tx())
|
||||
.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 {
|
||||
charges: vec![Charge {
|
||||
origin: ALICE,
|
||||
contract: BOB,
|
||||
amount: Deposit::Charge(12),
|
||||
}],
|
||||
},
|
||||
},
|
||||
ChargingTestCase {
|
||||
origin: Origin::<Test>::Root,
|
||||
deposit: Deposit::Charge(0),
|
||||
expected: TestExt { charges: vec![] },
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
clear_ext();
|
||||
|
||||
let mut meter = TestMeter::new(1_000);
|
||||
assert_eq!(meter.available(), 1_000);
|
||||
|
||||
let mut nested0 = meter.nested(BalanceOf::<Test>::max_value());
|
||||
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,
|
||||
immutable_data_len: 0,
|
||||
});
|
||||
let mut nested1 = nested0.nested(BalanceOf::<Test>::max_value());
|
||||
let total_deposit = nested1_info.total_deposit();
|
||||
nested1.charge(&Diff { items_removed: 5, ..Default::default() });
|
||||
nested1.charge(&Diff { bytes_added: 20, ..Default::default() });
|
||||
nested0.enforce_limit(Some(&mut nested1_info)).unwrap();
|
||||
nested0.absorb(nested1, &CHARLIE, None);
|
||||
|
||||
meter.absorb(nested0, &BOB, None);
|
||||
|
||||
meter.terminate(CHARLIE, total_deposit);
|
||||
|
||||
assert_eq!(
|
||||
meter
|
||||
.try_into_deposit(&test_case.origin, &ExecConfig::new_bizinikiwi_tx())
|
||||
.unwrap(),
|
||||
test_case.deposit
|
||||
);
|
||||
assert_eq!(TestExtTestValue::get(), test_case.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user