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,55 @@
[package]
name = "pezpallet-origin-restriction"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Pallet to give some execution allowance for some origins"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
pezframe-benchmarking = { workspace = true, optional = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
log = { workspace = true }
pezpallet-transaction-payment = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
pezsp-arithmetic = { workspace = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
pezsp-runtime = { workspace = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-transaction-payment/std",
"scale-info/std",
"pezsp-arithmetic/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-runtime/std",
]
runtime-benchmarks = [
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-transaction-payment/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-transaction-payment/try-runtime",
"pezsp-runtime/try-runtime",
]
@@ -0,0 +1,71 @@
// 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.
//! Benchmarks for pallet origin restriction.
use super::*;
use pezframe_benchmarking::{v2::*, BenchmarkError};
use pezsp_runtime::traits::DispatchTransaction;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
}
#[benchmarks]
mod benches {
use super::*;
#[benchmark]
fn clean_usage() -> Result<(), BenchmarkError> {
let origin = T::RestrictedEntity::benchmarked_restricted_origin();
let entity = T::RestrictedEntity::restricted_entity(&origin)
.expect("The origin from `benchmarked_restricted_origin` must be restricted");
Usages::<T>::insert(&entity, Usage { used: 1u32.into(), at_block: 0u32.into() });
pezframe_system::Pallet::<T>::set_block_number(1_000u32.into());
#[extrinsic_call]
_(pezframe_system::RawOrigin::Root, entity.clone());
assert_last_event::<T>(Event::UsageCleaned { entity }.into());
Ok(())
}
// This benchmark may miss the cost for `OperationAllowedOneTimeExcess::contains`.
#[benchmark]
fn restrict_origin_tx_ext() -> Result<(), BenchmarkError> {
let tx_ext = RestrictOrigin::<T>::new(true);
let origin = T::RestrictedEntity::benchmarked_restricted_origin();
let call = pezframe_system::Call::remark { remark: alloc::vec![] }.into();
#[block]
{
tx_ext
.test_run(origin.into(), &call, &Default::default(), 0, 0, |_| {
Ok(Default::default())
})
.expect("Failed to allow the cheapest call, benchmark needs to be improved")
.expect("inner call successful");
}
Ok(())
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,370 @@
// 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.
//! # Origin restriction pallet and transaction extension
//!
//! This pallet tracks certain origin and limits how much total "fee usage" they can accumulate.
//! Usage gradually recovers as blocks pass.
//!
//! First the entity is extracted from the restricted origin, the entity represents the granularity
//! of usage tracking.
//!
//! For example, an origin like `DaoOrigin { name: [u8; 8], tally: Percent }`
//! can have its usage tracked and restricted at the DAO level, so the tracked entity would be
//! `DaoEntity { name: [u8; 8] }`. This ensures that usage restrictions apply to the DAO as a whole,
//! independent of any particular voter percentage.
//!
//! Then when dispatching a transaction, if the entitys new usage would exceed its max allowance,
//! the transaction is invalid, except if the call is in the set of calls permitted to exceed that
//! limit (see `OperationAllowedOneTimeExcess`). In that case, as long as the entity's usage prior
//! to dispatch was zero, the transaction is valid (with respect to usage). If the entity's
//! usage is already above the limit, the transaction is always invalid. After dispatch, any call
//! flagged as `Pays::No` fully restores the consumed usage.
//!
//! To expand on `OperationAllowedOneTimeExcess`, user have to wait for the usage to completely
//! recover to zero before being able to do an operation that exceed max allowance.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
extern crate alloc;
pub use weights::WeightInfo;
use codec::{Decode, DecodeWithMemTracking, Encode};
use pezframe_support::{
dispatch::{DispatchInfo, PostDispatchInfo},
pezpallet_prelude::{Pays, Zero},
traits::{ContainsPair, OriginTrait},
weights::WeightToFee,
Parameter, RuntimeDebugNoBound,
};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
use pezpallet_transaction_payment::OnChargeTransaction;
use scale_info::TypeInfo;
use pezsp_runtime::{
traits::{
AsTransactionAuthorizedOrigin, DispatchInfoOf, DispatchOriginOf, Dispatchable, Implication,
PostDispatchInfoOf, TransactionExtension, ValidateResult,
},
transaction_validity::{
InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction,
},
DispatchError::BadOrigin,
DispatchResult, RuntimeDebug, SaturatedConversion, Saturating, Weight,
};
/// The allowance for an entity, defining its usage limit and recovery rate.
#[derive(Clone, Debug)]
pub struct Allowance<Balance> {
/// The maximum usage allowed before transactions are restricted.
pub max: Balance,
/// The amount of usage recovered per block.
pub recovery_per_block: Balance,
}
/// The restriction of an entity.
pub trait RestrictedEntity<OriginCaller, Balance>: Sized {
/// The allowance given for the entity.
fn allowance(&self) -> Allowance<Balance>;
/// Whether the origin is restricted, and what entity it belongs to.
fn restricted_entity(caller: &OriginCaller) -> Option<Self>;
#[cfg(feature = "runtime-benchmarks")]
fn benchmarked_restricted_origin() -> OriginCaller;
}
pub use pallet::*;
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use pezframe_support::{pezpallet_prelude::*, traits::ContainsPair};
use pezframe_system::pezpallet_prelude::*;
/// The usage of an entity.
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct Usage<Balance, BlockNumber> {
/// The amount of usage consumed at block `at_block`.
pub used: Balance,
/// The block number at which the usage was last updated.
pub at_block: BlockNumber,
}
pub(crate) type OriginCallerFor<T> =
<<T as pezframe_system::Config>::RuntimeOrigin as OriginTrait>::PalletsOrigin;
pub(crate) type BalanceOf<T> =
<<T as pezpallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<
T,
>>::Balance;
#[pallet::pallet]
pub struct Pallet<T>(_);
/// The current usage for each entity.
#[pallet::storage]
pub type Usages<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::RestrictedEntity,
Usage<BalanceOf<T>, BlockNumberFor<T>>,
>;
#[pallet::config]
pub trait Config:
pezframe_system::Config<
RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
RuntimeOrigin: AsTransactionAuthorizedOrigin,
> + pezpallet_transaction_payment::Config
+ Send
+ Sync
{
/// The weight information for this pallet.
type WeightInfo: WeightInfo;
/// The type that represent the entities tracked, its allowance and the conversion from
/// origin is bounded in [`RestrictedEntity`].
///
/// This is the canonical origin from the point of view of usage tracking.
/// Each entity is tracked separately.
///
/// This is different from origin as a multiple origin can represent a single entity.
/// For example, imagine a DAO origin with a percentage of voters, we want to track the DAO
/// entity regardless of the voter percentage.
type RestrictedEntity: RestrictedEntity<OriginCallerFor<Self>, BalanceOf<Self>>
+ Parameter
+ MaxEncodedLen;
/// For some entities, the calls that are allowed to go beyond the max allowance.
///
/// This must be only for call which have a reasonable maximum weight and length.
type OperationAllowedOneTimeExcess: ContainsPair<Self::RestrictedEntity, Self::RuntimeCall>;
/// The runtime event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
}
#[pallet::error]
pub enum Error<T> {
/// The origin has no usage tracked.
NoUsage,
/// The usage is not zero.
NotZero,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
/// Usage for an entity is cleaned.
UsageCleaned { entity: T::RestrictedEntity },
}
#[pallet::call(weight = <T as Config>::WeightInfo)]
impl<T: Config> Pallet<T> {
/// Allow to clean usage associated with an entity when it is zero or when there is no
/// longer any allowance for the origin.
// This could be an unsigned call
#[pallet::call_index(1)]
pub fn clean_usage(
origin: OriginFor<T>,
entity: T::RestrictedEntity,
) -> DispatchResultWithPostInfo {
// `None` origin is better to reject in general, due to being used for inherents and
// validate unsigned.
if ensure_none(origin.clone()).is_ok() {
return Err(BadOrigin.into());
}
let Some(mut usage) = Usages::<T>::take(&entity) else {
return Err(Error::<T>::NoUsage.into());
};
let now = pezframe_system::Pallet::<T>::block_number();
let elapsed = now.saturating_sub(usage.at_block).saturated_into::<u32>();
let allowance = entity.allowance();
let receive_back = allowance.recovery_per_block.saturating_mul(elapsed.into());
usage.used = usage.used.saturating_sub(receive_back);
ensure!(usage.used.is_zero(), Error::<T>::NotZero);
Self::deposit_event(Event::UsageCleaned { entity });
Ok(Pays::No.into())
}
}
}
fn extrinsic_fee<T: Config>(weight: Weight, length: usize) -> BalanceOf<T> {
let weight_fee = T::WeightToFee::weight_to_fee(&weight);
let length_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0));
weight_fee.saturating_add(length_fee)
}
/// This transaction extension restricts some origins and prevents them from dispatching calls,
/// based on their usage and allowance.
///
/// The extension can be enabled or disabled with the inner boolean. When enabled, the restriction
/// process executes. When disabled, only the `RestrictedOrigins` check is executed.
/// You can always enable it, the only advantage of disabling it is have better pre-dispatch weight.
#[derive(
Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound, DecodeWithMemTracking,
)]
#[scale_info(skip_type_params(T))]
pub struct RestrictOrigin<T>(bool, core::marker::PhantomData<T>);
impl<T> RestrictOrigin<T> {
/// Instantiates a new `RestrictOrigins` extension.
pub fn new(enable: bool) -> Self {
Self(enable, core::marker::PhantomData)
}
}
/// The info passed between the validate and prepare steps for the `RestrictOrigins` extension.
#[derive(RuntimeDebugNoBound)]
pub enum Val<T: Config> {
Charge { fee: BalanceOf<T>, entity: T::RestrictedEntity },
NoCharge,
}
/// The info passed between the prepare and post-dispatch steps for the `RestrictOrigins`
/// extension.
pub enum Pre<T: Config> {
Charge {
fee: BalanceOf<T>,
entity: T::RestrictedEntity,
},
NoCharge {
// weight initially estimated by the extension, to be refunded
refund: Weight,
},
}
impl<T: Config> TransactionExtension<T::RuntimeCall> for RestrictOrigin<T> {
const IDENTIFIER: &'static str = "RestrictOrigins";
type Implicit = ();
type Val = Val<T>;
type Pre = Pre<T>;
fn weight(&self, _call: &T::RuntimeCall) -> pezframe_support::weights::Weight {
if !self.0 {
return Weight::zero();
}
<T as Config>::WeightInfo::restrict_origin_tx_ext()
}
fn validate(
&self,
origin: DispatchOriginOf<T::RuntimeCall>,
call: &T::RuntimeCall,
info: &DispatchInfoOf<T::RuntimeCall>,
len: usize,
_self_implicit: (),
_inherited_implication: &impl Implication,
_source: TransactionSource,
) -> ValidateResult<Self::Val, T::RuntimeCall> {
let origin_caller = origin.caller();
let Some(entity) = T::RestrictedEntity::restricted_entity(origin_caller) else {
return Ok((ValidTransaction::default(), Val::NoCharge, origin));
};
let allowance = T::RestrictedEntity::allowance(&entity);
if !self.0 {
// Extension is disabled, but the restriction must happen, the extension should have
// been enabled.
return Err(InvalidTransaction::Call.into());
}
let now = pezframe_system::Pallet::<T>::block_number();
let mut usage = match Usages::<T>::get(&entity) {
Some(mut usage) => {
let elapsed = now.saturating_sub(usage.at_block).saturated_into::<u32>();
let receive_back = allowance.recovery_per_block.saturating_mul(elapsed.into());
usage.used = usage.used.saturating_sub(receive_back);
usage.at_block = now;
usage
},
None => Usage { used: 0u32.into(), at_block: now },
};
// The usage before taking into account this extrinsic.
let usage_without_new_xt = usage.used;
let fee = extrinsic_fee::<T>(info.total_weight(), len);
usage.used = usage.used.saturating_add(fee);
Usages::<T>::insert(&entity, &usage);
let allowed_one_time_excess = || {
usage_without_new_xt == 0u32.into() &&
T::OperationAllowedOneTimeExcess::contains(&entity, call)
};
if usage.used <= allowance.max || allowed_one_time_excess() {
Ok((ValidTransaction::default(), Val::Charge { fee, entity }, origin))
} else {
Err(InvalidTransaction::Payment.into())
}
}
fn prepare(
self,
val: Self::Val,
_origin: &DispatchOriginOf<T::RuntimeCall>,
call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
match val {
Val::Charge { fee, entity } => Ok(Pre::Charge { fee, entity }),
Val::NoCharge => Ok(Pre::NoCharge { refund: self.weight(call) }),
}
}
fn post_dispatch_details(
pre: Self::Pre,
_info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
_len: usize,
_result: &DispatchResult,
) -> Result<Weight, TransactionValidityError> {
match pre {
Pre::Charge { fee, entity } =>
if post_info.pays_fee == Pays::No {
Usages::<T>::mutate_exists(entity, |maybe_usage| {
if let Some(usage) = maybe_usage {
usage.used = usage.used.saturating_sub(fee);
if usage.used.is_zero() {
*maybe_usage = None;
}
}
});
Ok(Weight::zero())
} else {
Ok(Weight::zero())
},
Pre::NoCharge { refund } => Ok(refund),
}
}
}
@@ -0,0 +1,343 @@
// 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::*;
use codec::MaxEncodedLen;
use pezframe_support::{
derive_impl,
dispatch::{DispatchErrorWithPostInfo, GetDispatchInfo},
pezpallet_prelude::TransactionValidityError,
storage::with_transaction,
traits::ContainsPair,
weights::IdentityFee,
};
use pezpallet_transaction_payment::ConstFeeMultiplier;
use pezsp_core::{ConstU64, H256};
use pezsp_runtime::{
testing::UintAuthorityId,
traits::{Applyable, BlakeTwo256, Checkable, ConstUint, IdentityLookup},
transaction_validity::{InvalidTransaction, TransactionSource},
BuildStorage, DispatchError, FixedU128, TransactionOutcome,
};
pub type AccountId = <Test as pezframe_system::Config>::AccountId;
pub type BlockNumber = u64;
pub type TransactionExtension = (RestrictOrigin<Test>,);
pub type Header = pezsp_runtime::generic::Header<BlockNumber, BlakeTwo256>;
pub type Block = pezsp_runtime::generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = pezsp_runtime::generic::UncheckedExtrinsic<
AccountId,
RuntimeCall,
pezsp_runtime::testing::UintAuthorityId,
TransactionExtension,
>;
pub const CALL_WEIGHT: u64 = 15;
pub const CALL_WEIGHT_EXCESS: u64 = 150;
/// A small mock pallet to test calls from within the runtime.
#[pezframe_support::pallet(dev_mode)]
pub mod mock_pallet {
use super::{CALL_WEIGHT, CALL_WEIGHT_EXCESS};
use pezframe_support::pezpallet_prelude::*;
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> {
#[pallet::call_index(0)]
#[pallet::weight(Weight::from_parts(CALL_WEIGHT, 0))]
pub fn do_something(_origin: OriginFor<T>) -> DispatchResult {
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(Weight::from_parts(CALL_WEIGHT, 0))]
pub fn do_something_refunded(_origin: OriginFor<T>) -> DispatchResultWithPostInfo {
Ok(Pays::No.into())
}
#[pallet::call_index(2)]
#[pallet::weight(Weight::from_parts(CALL_WEIGHT_EXCESS, 0))]
pub fn do_something_allowed_excess(_origin: OriginFor<T>) -> DispatchResult {
Ok(())
}
}
}
pezframe_support::construct_runtime!(
pub enum Test {
System: pezframe_system,
MockPallet: mock_pallet,
OriginsRestriction: crate,
TransactionPayment: pezpallet_transaction_payment,
}
);
/// Convenience aliases for the mock pallet calls.
pub type MockPalletCall = mock_pallet::Call<Test>;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type PalletInfo = PalletInfo;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type BlockHashCount = ConstU64<250>;
type AccountData = ();
}
pub const RESTRICTED_ORIGIN_1: u64 = 1;
pub const RESTRICTED_ORIGIN_2: u64 = 2;
pub const NON_RESTRICTED_ORIGIN: u64 = 3;
#[derive(
Encode,
Decode,
Clone,
PartialEq,
Eq,
Debug,
MaxEncodedLen,
scale_info::TypeInfo,
DecodeWithMemTracking,
)]
pub enum RuntimeRestrictedEntity {
A,
B,
}
impl RestrictedEntity<OriginCaller, u64> for RuntimeRestrictedEntity {
fn allowance(&self) -> Allowance<u64> {
Allowance { max: MAX_ALLOWANCE, recovery_per_block: ALLOWANCE_RECOVERY_PER_BLOCK }
}
fn restricted_entity(caller: &OriginCaller) -> Option<RuntimeRestrictedEntity> {
match caller {
OriginCaller::system(pezframe_system::Origin::<Test>::Signed(RESTRICTED_ORIGIN_1)) =>
Some(RuntimeRestrictedEntity::A),
OriginCaller::system(pezframe_system::Origin::<Test>::Signed(RESTRICTED_ORIGIN_2)) =>
Some(RuntimeRestrictedEntity::B),
_ => None,
}
}
#[cfg(feature = "runtime-benchmarks")]
fn benchmarked_restricted_origin() -> OriginCaller {
OriginCaller::system(pezframe_system::Origin::<Test>::Signed(RESTRICTED_ORIGIN_1))
}
}
pub struct TestOperationAllowedOneTimeExcess;
impl ContainsPair<RuntimeRestrictedEntity, RuntimeCall> for TestOperationAllowedOneTimeExcess {
fn contains(entity: &RuntimeRestrictedEntity, call: &RuntimeCall) -> bool {
matches!(
(entity, call),
(
RuntimeRestrictedEntity::A,
RuntimeCall::MockPallet(mock_pallet::Call::do_something_allowed_excess { .. })
)
)
}
}
pub const MAX_ALLOWANCE: u64 = 100;
pub const ALLOWANCE_RECOVERY_PER_BLOCK: u64 = 5;
impl crate::Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type RestrictedEntity = RuntimeRestrictedEntity;
type OperationAllowedOneTimeExcess = TestOperationAllowedOneTimeExcess;
}
pezframe_support::parameter_types! {
pub ConstFeeMultiplierInner: FixedU128 = FixedU128::from_u32(1);
}
pub struct OnChargeTransaction;
impl pezpallet_transaction_payment::OnChargeTransaction<Test> for OnChargeTransaction {
type Balance = u64;
type LiquidityInfo = ();
fn withdraw_fee(
_who: &AccountId,
_call: &RuntimeCall,
_dispatch_info: &DispatchInfoOf<RuntimeCall>,
_fee: Self::Balance,
_tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
unimplemented!()
}
#[cfg(feature = "runtime-benchmarks")]
fn endow_account(_who: &AccountId, _amount: Self::Balance) {
unimplemented!()
}
#[cfg(feature = "runtime-benchmarks")]
fn minimum_balance() -> Self::Balance {
unimplemented!()
}
fn can_withdraw_fee(
_who: &AccountId,
_call: &RuntimeCall,
_dispatch_info: &DispatchInfoOf<RuntimeCall>,
_fee: Self::Balance,
_tip: Self::Balance,
) -> Result<(), TransactionValidityError> {
unimplemented!()
}
fn correct_and_deposit_fee(
_who: &AccountId,
_dispatch_info: &DispatchInfoOf<RuntimeCall>,
_post_info: &PostDispatchInfoOf<RuntimeCall>,
_corrected_fee: Self::Balance,
_tip: Self::Balance,
_already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
unimplemented!()
}
}
impl pezpallet_transaction_payment::TxCreditHold<Test> for OnChargeTransaction {
type Credit = ();
}
impl pezpallet_transaction_payment::Config for Test {
type WeightInfo = ();
type RuntimeEvent = RuntimeEvent;
type WeightToFee = IdentityFee<u64>;
type LengthToFee = IdentityFee<u64>;
type OperationalFeeMultiplier = ConstUint<1>;
type FeeMultiplierUpdate = ConstFeeMultiplier<ConstFeeMultiplierInner>;
type OnChargeTransaction = OnChargeTransaction;
}
impl mock_pallet::Config for Test {}
/// Advance the chain to a certain block number.
#[allow(dead_code)]
pub fn advance_to(b: BlockNumber) {
while System::block_number() < b {
System::set_block_number(System::block_number() + 1);
}
}
/// Advance the chain by a certain number of blocks.
pub fn advance_by(b: BlockNumber) {
let initial_block = System::block_number();
while System::block_number() < b + initial_block {
System::set_block_number(System::block_number() + 1);
}
}
/// Builds a new `TestExternalities`.
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let storage = RuntimeGenesisConfig {
system: Default::default(),
transaction_payment: Default::default(),
}
.build_storage()
.unwrap();
pezsp_io::TestExternalities::from(storage)
}
/// We gather both error into a single type in order to do `assert_ok` and `assert_err` safely.
/// Otherwise, we can easily miss the inner error in a `Resut<Resut<_, _>, _>`.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum TransactionExecutionError {
Validity(TransactionValidityError),
// This ignores the post info.
Dispatch(DispatchErrorWithPostInfo),
}
impl From<DispatchErrorWithPostInfo> for TransactionExecutionError {
fn from(e: DispatchErrorWithPostInfo) -> Self {
Self::Dispatch(e)
}
}
impl From<TransactionValidityError> for TransactionExecutionError {
fn from(e: TransactionValidityError) -> Self {
Self::Validity(e)
}
}
impl From<DispatchError> for TransactionExecutionError {
fn from(e: DispatchError) -> Self {
Self::Dispatch(e.into())
}
}
impl From<InvalidTransaction> for TransactionExecutionError {
fn from(e: InvalidTransaction) -> Self {
Self::Validity(e.into())
}
}
/// Execute a transaction with the given origin, call and transaction extension.
pub fn exec_signed_tx(
who: u64,
call: impl Into<RuntimeCall>,
) -> Result<(), TransactionExecutionError> {
let tx_ext = (RestrictOrigin::<Test>::new(true),);
let tx = UncheckedExtrinsic::new_signed(call.into(), who, UintAuthorityId(who), tx_ext);
exec_tx(tx)
}
/// Execute a transaction with the given origin, call and transaction extension. but with the
/// `RestrictOrigin` disabled.
pub fn exec_signed_tx_disabled(
who: u64,
call: impl Into<RuntimeCall>,
) -> Result<(), TransactionExecutionError> {
// Construct the extension with `false` for the enabling boolean.
let tx_ext = (RestrictOrigin::<Test>(false, Default::default()),);
let tx = UncheckedExtrinsic::new_signed(call.into(), who, UintAuthorityId(who), tx_ext);
exec_tx(tx)
}
/// Execute a transaction with the given origin, call and transaction extension.
pub fn exec_tx(tx: UncheckedExtrinsic) -> Result<(), TransactionExecutionError> {
let info = tx.get_dispatch_info();
let len = tx.encoded_size();
let checked = Checkable::check(tx, &pezframe_system::ChainContext::<Test>::default())?;
with_transaction(|| {
let validity = checked.validate::<Test>(TransactionSource::External, &info, len);
TransactionOutcome::Rollback(Result::<_, DispatchError>::Ok(validity))
})??;
checked.apply::<Test>(&info, len)??;
Ok(())
}
@@ -0,0 +1,253 @@
// 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::{mock::*, *};
use pezframe_support::{assert_noop, assert_ok};
use pezsp_runtime::{testing::UintAuthorityId, transaction_validity::InvalidTransaction};
/// Test that a non-restricted origin (`NON_RESTRICTED_ORIGIN`) is never tracked, i.e., no usage.
#[test]
fn non_restricted_origin_is_not_charged() {
new_test_ext().execute_with(|| {
advance_by(1);
assert_ok!(exec_signed_tx(NON_RESTRICTED_ORIGIN, MockPalletCall::do_something {}));
assert!(
Usages::<Test>::iter().next().is_none(),
"Non-restricted origin should have no tracked usage."
);
});
}
/// Test that restricted origins (`RESTRICTED_ORIGIN_1`, `RESTRICTED_ORIGIN_2`) have their usage
/// tracked, refunded on Pays::No, and also can exceed the limit one time if the call is
/// whitelisted.
#[test]
fn restricted_origin_works() {
new_test_ext().execute_with(|| {
// length of the extrinsic.
let len = {
let tx_ext = (RestrictOrigin::<Test>::new(true),);
let tx = UncheckedExtrinsic::new_signed(
MockPalletCall::do_something {}.into(),
RESTRICTED_ORIGIN_1,
UintAuthorityId(RESTRICTED_ORIGIN_1),
tx_ext,
);
tx.encoded_size() as u64
};
let mut previous_used = 0;
assert_eq!(ALLOWANCE_RECOVERY_PER_BLOCK, 5);
assert_eq!(CALL_WEIGHT, 15);
assert_eq!(MAX_ALLOWANCE, 100);
// Move beyond block 0 for events
advance_by(1);
// Normal call => usage increases
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}));
let usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert_eq!(usage.used, previous_used + CALL_WEIGHT + len);
assert_eq!(usage.at_block, 1);
// A call with `Pays::No` => usage is refunded
previous_used = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap().used;
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something_refunded {}));
let usage_after = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert_eq!(usage_after.used, previous_used);
// Again a normal call => usage increases
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}));
let usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert_eq!(usage.used, previous_used + CALL_WEIGHT + len);
// Now we have reached the limit
// Normal calls that push usage above the max should fail.
assert_noop!(
exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}),
InvalidTransaction::Payment
);
// Advance a few blocks to partially recover usage
advance_by(1);
// Still not enough to do another normal call if we haven't recovered enough.
assert_noop!(
exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}),
InvalidTransaction::Payment
);
// Advance one more block => total 5 blocks.
advance_by(1);
previous_used = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap().used;
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}));
let current_usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
let recovered_amount = 2 * ALLOWANCE_RECOVERY_PER_BLOCK;
// Usage = (previous_used - recovered_amount) + (CALL_WEIGHT + len).
assert_eq!(current_usage.used, previous_used + CALL_WEIGHT + len - recovered_amount);
assert_eq!(current_usage.at_block, 3);
});
}
#[test]
fn one_time_excess_works_and_works_only_one_time() {
new_test_ext().execute_with(|| {
advance_by(1);
// Given usage is 0, RESTRICTED_ORIGIN_1 can exceed once.
assert_ok!(exec_signed_tx(
RESTRICTED_ORIGIN_1,
MockPalletCall::do_something_allowed_excess {}
));
let current_usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert!(current_usage.used > MAX_ALLOWANCE);
// Now that usage has exceeded the max, even the "allowed excess" call should fail.
assert_noop!(
exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something_allowed_excess {}),
InvalidTransaction::Payment
);
});
}
#[test]
fn one_time_excess_is_origin_specific() {
new_test_ext().execute_with(|| {
advance_by(1);
// try the "allowed_excess" call from RESTRICTED_ORIGIN_2:
// It should *fail*, because RESTRICTED_ORIGIN_2 is not in `OperationAllowedOneTimeExcess`.
assert_noop!(
exec_signed_tx(RESTRICTED_ORIGIN_2, MockPalletCall::do_something_allowed_excess {}),
InvalidTransaction::Payment
);
// Demonstrate that RESTRICTED_ORIGIN_1 *can* exceed (for completeness).
assert_ok!(exec_signed_tx(
RESTRICTED_ORIGIN_1,
MockPalletCall::do_something_allowed_excess {}
));
let current_usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert!(current_usage.used > MAX_ALLOWANCE);
});
}
#[test]
fn one_time_excess_requires_usage_zero() {
new_test_ext().execute_with(|| {
advance_by(1);
// We use a bit of the allowance.
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}));
let usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).unwrap();
assert!(usage.used < MAX_ALLOWANCE);
assert!(usage.used > 0);
// Now that usage is non-zero, we can call exceeding operations.
assert_noop!(
exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something_allowed_excess {}),
InvalidTransaction::Payment
);
});
}
#[test]
fn clean_usage_works() {
new_test_ext().execute_with(|| {
// Move beyond block 0 for clarity in block numbering.
advance_by(1);
// 1) Attempt to clean usage with no recorded usage => should fail with NoUsage.
assert_noop!(
OriginsRestriction::clean_usage(
pezframe_system::RawOrigin::Root.into(),
RuntimeRestrictedEntity::A
),
Error::<Test>::NoUsage
);
// Create some usage for RESTRICTED_ORIGIN_1 (which maps to RuntimeRestrictedEntity::A).
assert_ok!(exec_signed_tx(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}));
let usage = Usages::<Test>::get(RuntimeRestrictedEntity::A).expect("Usage must be present");
assert!(usage.used > 0, "Usage should have increased after the call");
// 2) Try cleaning while usage is non-zero => should fail with NotZero.
assert_noop!(
OriginsRestriction::clean_usage(
pezframe_system::RawOrigin::Root.into(),
RuntimeRestrictedEntity::A
),
Error::<Test>::NotZero
);
// Figure out how many blocks to advance so that usage recovers fully back to zero.
// The usage recovers ALLOWANCE_RECOVERY_PER_BLOCK every block, so compute the needed
// blocks.
let used_amount = usage.used;
let blocks_needed = used_amount.div_ceil(ALLOWANCE_RECOVERY_PER_BLOCK); // Ceiling division
advance_by(blocks_needed);
// 3) Now that enough blocks have passed, usage should be zero => clean_usage should
// succeed.
assert_ok!(OriginsRestriction::clean_usage(
pezframe_system::RawOrigin::Root.into(),
RuntimeRestrictedEntity::A
));
// We expect the storage to be removed and the UsageCleaned event to be emitted.
assert!(Usages::<Test>::get(RuntimeRestrictedEntity::A).is_none());
System::assert_last_event(RuntimeEvent::OriginsRestriction(Event::UsageCleaned {
entity: RuntimeRestrictedEntity::A,
}));
// 4) Calling again when there is no usage => fail with NoUsage.
assert_noop!(
OriginsRestriction::clean_usage(
pezframe_system::RawOrigin::Root.into(),
RuntimeRestrictedEntity::A
),
Error::<Test>::NoUsage
);
});
}
#[test]
fn restrict_origin_extension_disabled_behavior() {
new_test_ext().execute_with(|| {
// Move to a non-zero block number for clarity.
advance_by(1);
// 1) Attempt from restricted origin => Expect InvalidTransaction::Call
// because the pallet explicitly forbids restricted origins if the extension is off.
assert_noop!(
exec_signed_tx_disabled(RESTRICTED_ORIGIN_1, MockPalletCall::do_something {}),
pezsp_runtime::transaction_validity::InvalidTransaction::Call
);
// 2) Attempt from non-restricted origin => Should succeed and also
// should not track any usage since usage is only tracked for restricted origins.
assert_ok!(exec_signed_tx_disabled(NON_RESTRICTED_ORIGIN, MockPalletCall::do_something {}));
assert!(
Usages::<Test>::iter().next().is_none(),
"Extension is disabled, so no usage should be tracked for non-restricted origins."
);
});
}
@@ -0,0 +1,35 @@
// 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.
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use pezframe_support::weights::Weight;
/// Weight functions needed for pallet origins restriction.
pub trait WeightInfo {
fn clean_usage() -> Weight;
fn restrict_origin_tx_ext() -> Weight;
}
// For tests
impl WeightInfo for () {
fn clean_usage() -> Weight { Weight::zero() }
fn restrict_origin_tx_ext() -> Weight { Weight::zero() }
}