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,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 entity’s 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() }
|
||||
}
|
||||
Reference in New Issue
Block a user