From 8065645bc023ee3ed92af06e7016ad69fdac78a3 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 25 Nov 2022 16:34:08 +1300 Subject: [PATCH 01/10] poc --- xtokens/src/lib.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 89ad16bff..6d5bc7c68 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -60,6 +60,57 @@ enum TransferKind { } use TransferKind::*; +enum RateLimiterError { + NotDefined, + ExceedLimit, +} + +pub trait RateLimiter { + type RateLimiterId; + fn is_allowed(id: Self::RateLimiterId, key: impl Encode, value: u128) -> Result<(), RateLimiterError>; + fn record(id: Self::RateLimiterId, key: impl Encode, value: u128); +} + +// defined in runtime +enum RateLimiterId { + XtokensTransfer, +} + +// RateLimiter pallet + +mod rate_limiter { + + enum RateLimit { + PerBlock { + blocks: u32, + limit: u32, + }, + PerSeconds { + seconds: u32, + limit: u32, + }, + TokenBucket { + blocks: u32, // add `increment` limits per `blocks` + max: u32, // max limits + increment: u32, + }, + Unlimited, + NotAllowed, + } + + enum KeyFilter { + Match(Vec), + StartsWith(Vec), + EndsWith(Vec), + } + + // calls + fn set_limit(id: RateLimiterId, limit: RateLimit) {} + fn add_whitelist(id: RateLimiterId, whitelist: KeyFilter) {} + fn remove_whitelist(id: RateLimiterId, whitelist: KeyFilter) {} + fn reset_whitelist(id: RateLimiterId, whitelists: Vec) {} +} + #[frame_support::pallet] pub mod module { use super::*; @@ -119,6 +170,10 @@ pub mod module { /// The way to retreave the reserve of a MultiAsset. This can be /// configured to accept absolute or relative paths for self tokens type ReserveProvider: Reserve; + + type RateLimiter: RateLimiter; + type RateLimiterIdAsset: Get<::RateLimiterId>; + type RateLimiterIdUser: Get<::RateLimiterId>; } #[pallet::event] @@ -177,6 +232,8 @@ pub mod module { NotSupportedMultiLocation, /// MinXcmFee not registered for certain reserve location MinXcmFeeNotDefined, + + RateLimited, } #[pallet::hooks] @@ -527,6 +584,24 @@ pub mod module { Error::::DistinctReserveForAssetAndFee ); } + + // per asset check + let amount = match asset.fun { + Fungibility::Fungible(amount) => amount, + Fungibility::NonFungible(_) => 1, + }; + ensure!( + T::RateLimiter::is_allowed(T::RateLimiterIdAsset::get(), asset.id, amount), + Error::::RateLimited + ); + T::RateLimiter::record(T::RateLimiterIdAsset::get(), asset.id, amount); + + // per sender check + ensure!( + T::RateLimiter::is_allowed(T::RateLimiterIdUser::get(), who, amount), + Error::::RateLimited + ); + T::RateLimiter::record(T::RateLimiterIdUser::get(), asset.id, amount); } let fee_reserve = T::ReserveProvider::reserve(&fee); From 868ee67c6237923d72db0d46fbe5a73f7ec5c2a3 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Sun, 27 Nov 2022 18:36:22 +0800 Subject: [PATCH 02/10] add RateLimiter for xtokens --- asset-registry/src/mock/para.rs | 2 + traits/src/lib.rs | 4 +- traits/src/rate_limit.rs | 40 +++++++++ xtokens/src/lib.rs | 83 ++++-------------- xtokens/src/mock/mod.rs | 1 + xtokens/src/mock/para.rs | 54 +++++++++++- xtokens/src/mock/para_relative_view.rs | 2 + xtokens/src/mock/para_teleport.rs | 2 + xtokens/src/tests.rs | 111 +++++++++++++++++++++++++ 9 files changed, 229 insertions(+), 70 deletions(-) create mode 100644 traits/src/rate_limit.rs diff --git a/asset-registry/src/mock/para.rs b/asset-registry/src/mock/para.rs index e8900a634..c0fa9c1fc 100644 --- a/asset-registry/src/mock/para.rs +++ b/asset-registry/src/mock/para.rs @@ -336,6 +336,8 @@ impl orml_xtokens::Config for Runtime { type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; type ReserveProvider = RelativeReserveProvider; + type RateLimiter = (); + type RateLimiterId = (); } impl orml_xcm::Config for Runtime { diff --git a/traits/src/lib.rs b/traits/src/lib.rs index a5894d218..43eec34bd 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -2,6 +2,7 @@ use codec::{Decode, Encode}; use impl_trait_for_tuples::impl_for_tuples; +use scale_info::TypeInfo; use sp_runtime::{DispatchResult, RuntimeDebug}; use sp_std::{ cmp::{Eq, PartialEq}, @@ -23,8 +24,8 @@ pub use get_by_key::GetByKey; pub use multi_asset::ConcreteFungibleAsset; pub use nft::InspectExtended; pub use price::{DefaultPriceProvider, PriceProvider}; +pub use rate_limit::{RateLimiter, RateLimiterError}; pub use rewards::RewardHandler; -use scale_info::TypeInfo; pub use xcm_transfer::XcmTransfer; pub mod arithmetic; @@ -37,6 +38,7 @@ pub mod location; pub mod multi_asset; pub mod nft; pub mod price; +pub mod rate_limit; pub mod rewards; pub mod xcm_transfer; diff --git a/traits/src/rate_limit.rs b/traits/src/rate_limit.rs new file mode 100644 index 000000000..c63c30c19 --- /dev/null +++ b/traits/src/rate_limit.rs @@ -0,0 +1,40 @@ +use codec::Encode; +use frame_support::{Parameter, RuntimeDebug}; +use sp_runtime::traits::{MaybeSerializeDeserialize, Member}; + +#[derive(PartialEq, Eq, RuntimeDebug)] +pub enum RateLimiterError { + NotDefined, + ExceedLimit, +} + +/// Rate Limiter +pub trait RateLimiter { + /// The type for the rate limiter. + type RateLimiterId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord; + + /// Check whether the rate limiter of can be bypassed according to the + /// `key`. + fn bypass_limit(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool; + + /// Check whether the `value` can be passed the limit of `limit_key`. + fn is_allowed(limiter_id: Self::RateLimiterId, limit_key: impl Encode, value: u128) + -> Result<(), RateLimiterError>; + + /// The handler function after allowed. + fn record(limiter_id: Self::RateLimiterId, limit_key: impl Encode, value: u128); +} + +impl RateLimiter for () { + type RateLimiterId = (); + + fn bypass_limit(_: Self::RateLimiterId, _: impl Encode) -> bool { + true + } + + fn is_allowed(_: Self::RateLimiterId, _: impl Encode, _: u128) -> Result<(), RateLimiterError> { + Ok(()) + } + + fn record(_: Self::RateLimiterId, _: impl Encode, _: u128) {} +} diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 6d5bc7c68..69f7a9372 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -44,7 +44,7 @@ use xcm_executor::traits::{InvertLocation, WeightBounds}; pub use module::*; use orml_traits::{ location::{Parse, Reserve}, - GetByKey, XcmTransfer, + GetByKey, RateLimiter, XcmTransfer, }; mod mock; @@ -60,57 +60,6 @@ enum TransferKind { } use TransferKind::*; -enum RateLimiterError { - NotDefined, - ExceedLimit, -} - -pub trait RateLimiter { - type RateLimiterId; - fn is_allowed(id: Self::RateLimiterId, key: impl Encode, value: u128) -> Result<(), RateLimiterError>; - fn record(id: Self::RateLimiterId, key: impl Encode, value: u128); -} - -// defined in runtime -enum RateLimiterId { - XtokensTransfer, -} - -// RateLimiter pallet - -mod rate_limiter { - - enum RateLimit { - PerBlock { - blocks: u32, - limit: u32, - }, - PerSeconds { - seconds: u32, - limit: u32, - }, - TokenBucket { - blocks: u32, // add `increment` limits per `blocks` - max: u32, // max limits - increment: u32, - }, - Unlimited, - NotAllowed, - } - - enum KeyFilter { - Match(Vec), - StartsWith(Vec), - EndsWith(Vec), - } - - // calls - fn set_limit(id: RateLimiterId, limit: RateLimit) {} - fn add_whitelist(id: RateLimiterId, whitelist: KeyFilter) {} - fn remove_whitelist(id: RateLimiterId, whitelist: KeyFilter) {} - fn reset_whitelist(id: RateLimiterId, whitelists: Vec) {} -} - #[frame_support::pallet] pub mod module { use super::*; @@ -171,9 +120,12 @@ pub mod module { /// configured to accept absolute or relative paths for self tokens type ReserveProvider: Reserve; + /// The rate limiter used to limit the cross-chain transfer asset. type RateLimiter: RateLimiter; - type RateLimiterIdAsset: Get<::RateLimiterId>; - type RateLimiterIdUser: Get<::RateLimiterId>; + + /// The id of the RateLimiter. + #[pallet::constant] + type RateLimiterId: Get<::RateLimiterId>; } #[pallet::event] @@ -232,7 +184,7 @@ pub mod module { NotSupportedMultiLocation, /// MinXcmFee not registered for certain reserve location MinXcmFeeNotDefined, - + /// Asset transfer is limited by RateLimiter. RateLimited, } @@ -590,18 +542,17 @@ pub mod module { Fungibility::Fungible(amount) => amount, Fungibility::NonFungible(_) => 1, }; - ensure!( - T::RateLimiter::is_allowed(T::RateLimiterIdAsset::get(), asset.id, amount), - Error::::RateLimited - ); - T::RateLimiter::record(T::RateLimiterIdAsset::get(), asset.id, amount); - // per sender check - ensure!( - T::RateLimiter::is_allowed(T::RateLimiterIdUser::get(), who, amount), - Error::::RateLimited - ); - T::RateLimiter::record(T::RateLimiterIdUser::get(), asset.id, amount); + let rate_limiter_id = T::RateLimiterId::get(); + + // check if the asset transfer from `who` can bypass the rate limiter. + if !T::RateLimiter::bypass_limit(rate_limiter_id, who.clone()) { + // ensure the asset transfer be allowed by the rate limiter. + T::RateLimiter::is_allowed(rate_limiter_id, asset.id.clone(), amount) + .map_err(|_| Error::::RateLimited)?; + + T::RateLimiter::record(rate_limiter_id, asset.id.clone(), amount); + } } let fee_reserve = T::ReserveProvider::reserve(&fee); diff --git a/xtokens/src/mock/mod.rs b/xtokens/src/mock/mod.rs index 4a128ff11..b3275518a 100644 --- a/xtokens/src/mock/mod.rs +++ b/xtokens/src/mock/mod.rs @@ -20,6 +20,7 @@ pub mod teleport_currency_adapter; pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); +pub const CHARLIE: AccountId32 = AccountId32::new([2u8; 32]); #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord, codec::MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 6a91b6185..f4e461d4a 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -1,8 +1,9 @@ -use super::{Amount, Balance, CurrencyId, CurrencyIdConvert, ParachainXcmRouter}; +use super::{Amount, Balance, CurrencyId, CurrencyIdConvert, ParachainXcmRouter, RateLimiter, CHARLIE}; use crate as orml_xtokens; +use codec::Encode; use frame_support::{ - construct_runtime, match_types, parameter_types, + construct_runtime, ensure, match_types, parameter_types, traits::{ConstU128, ConstU32, ConstU64, Everything, Get, Nothing}, weights::constants::WEIGHT_PER_SECOND, }; @@ -13,6 +14,7 @@ use sp_runtime::{ traits::{Convert, IdentityLookup}, AccountId32, }; +use sp_std::cell::RefCell; use cumulus_primitives_core::{ChannelStatus, GetChannelInfo, ParaId}; use pallet_xcm::XcmPassthrough; @@ -26,7 +28,7 @@ use xcm_builder::{ use xcm_executor::{Config, XcmExecutor}; use crate::mock::AllTokensAreCreatedEqualToWeight; -use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key}; +use orml_traits::{location::AbsoluteReserveProvider, parameter_type_with_key, RateLimiterError}; use orml_xcm_support::{IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset}; pub type AccountId = AccountId32; @@ -239,6 +241,50 @@ parameter_type_with_key! { }; } +thread_local! { + pub static R_ACCUMULATION: RefCell = RefCell::new(0); +} + +pub struct MockRateLimiter; +impl RateLimiter for MockRateLimiter { + type RateLimiterId = u8; + + fn bypass_limit(_: Self::RateLimiterId, key: impl Encode) -> bool { + let encoded_charlie = CHARLIE.encode(); + let encoded_key: Vec = key.encode(); + encoded_key != encoded_charlie + } + + fn is_allowed(_: Self::RateLimiterId, limit_key: impl Encode, value: u128) -> Result<(), RateLimiterError> { + let encoded_limit_key = limit_key.encode(); + let r_multi_location: MultiLocation = CurrencyIdConvert::convert(CurrencyId::R).unwrap(); + let r_asset_id = AssetId::Concrete(r_multi_location); + let encoded_r_asset_id = r_asset_id.encode(); + + if encoded_limit_key == encoded_r_asset_id { + let accumulation = R_ACCUMULATION.with(|v| *v.borrow()); + ensure!((accumulation + value) <= 2000, RateLimiterError::ExceedLimit); + } + + Ok(()) + } + + fn record(_: Self::RateLimiterId, limit_key: impl Encode, value: u128) { + let encoded_limit_key = limit_key.encode(); + let r_multi_location: MultiLocation = CurrencyIdConvert::convert(CurrencyId::R).unwrap(); + let r_asset_id = AssetId::Concrete(r_multi_location); + let encoded_r_asset_id = r_asset_id.encode(); + + if encoded_limit_key == encoded_r_asset_id { + R_ACCUMULATION.with(|v| *v.borrow_mut() += value); + } + } +} + +parameter_types! { + pub const XtokensRateLimiterId: u8 = 0; +} + impl orml_xtokens::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -254,6 +300,8 @@ impl orml_xtokens::Config for Runtime { type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; type ReserveProvider = AbsoluteReserveProvider; + type RateLimiter = MockRateLimiter; + type RateLimiterId = XtokensRateLimiterId; } impl orml_xcm::Config for Runtime { diff --git a/xtokens/src/mock/para_relative_view.rs b/xtokens/src/mock/para_relative_view.rs index 357e5cd07..bd94cacc2 100644 --- a/xtokens/src/mock/para_relative_view.rs +++ b/xtokens/src/mock/para_relative_view.rs @@ -326,6 +326,8 @@ impl orml_xtokens::Config for Runtime { type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; type ReserveProvider = RelativeReserveProvider; + type RateLimiter = (); + type RateLimiterId = (); } impl orml_xcm::Config for Runtime { diff --git a/xtokens/src/mock/para_teleport.rs b/xtokens/src/mock/para_teleport.rs index 01bc013e5..d3197341c 100644 --- a/xtokens/src/mock/para_teleport.rs +++ b/xtokens/src/mock/para_teleport.rs @@ -245,6 +245,8 @@ impl orml_xtokens::Config for Runtime { type LocationInverter = LocationInverter; type MaxAssetsForTransfer = MaxAssetsForTransfer; type ReserveProvider = AbsoluteReserveProvider; + type RateLimiter = (); + type RateLimiterId = (); } impl orml_xcm::Config for Runtime { diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 79ef3c0b7..6959b8054 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -1723,3 +1723,114 @@ fn send_with_insufficient_weight_limit() { assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 0); }); } + +#[test] +fn send_relay_chain_asset_to_relay_chain_at_rate_limit() { + TestNet::reset(); + + Relay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 4000); + assert_eq!(RelayBalances::free_balance(¶_a_account()), 4000); + assert_eq!(RelayBalances::free_balance(&ALICE), 1000); + assert_eq!(RelayBalances::free_balance(&BOB), 0); + }); + + ParaA::execute_with(|| { + use crate::tests::para::R_ACCUMULATION; + + assert_ok!(ParaTokens::deposit(CurrencyId::R, &CHARLIE, 3000)); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 1000); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &CHARLIE), 3000); + assert_eq!(R_ACCUMULATION.with(|v| *v.borrow()), 0); + + // Rate limiter allowed CHARLIE's transfer, and reward accumulation + assert_ok!(ParaXTokens::transfer( + Some(CHARLIE).into(), + CurrencyId::R, + 1800, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: CHARLIE.into(), + }) + ) + .into() + ), + WeightLimit::Unlimited + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &CHARLIE), 1200); + assert_eq!(R_ACCUMULATION.with(|v| *v.borrow()), 1800); + + // Rate limiter refused CHARLIE's transfer, because the amount will exceed the + // limit + assert_noop!( + ParaXTokens::transfer( + Some(CHARLIE).into(), + CurrencyId::R, + 201, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: CHARLIE.into(), + }) + ) + .into() + ), + WeightLimit::Unlimited + ), + Error::::RateLimited + ); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &CHARLIE), 1200); + assert_eq!(R_ACCUMULATION.with(|v| *v.borrow()), 1800); + + // ALICE will bypass the rate limiter, and won't reward accumulation + assert_ok!(ParaXTokens::transfer( + Some(ALICE).into(), + CurrencyId::R, + 201, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: ALICE.into(), + }) + ) + .into() + ), + WeightLimit::Unlimited + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 799); + assert_eq!(R_ACCUMULATION.with(|v| *v.borrow()), 1800); + + // Rate limiter allowed CHARLIE's transfer, and reward accumulation + assert_ok!(ParaXTokens::transfer( + Some(CHARLIE).into(), + CurrencyId::R, + 200, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: CHARLIE.into(), + }) + ) + .into() + ), + WeightLimit::Unlimited + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &CHARLIE), 1000); + assert_eq!(R_ACCUMULATION.with(|v| *v.borrow()), 2000); + }); + + Relay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 1799); + assert_eq!(RelayBalances::free_balance(&ALICE), 1161); + assert_eq!(RelayBalances::free_balance(&CHARLIE), 1920); + }); +} From 6a93b1fb6fa21c9d1c5734300b33ada202c793ae Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Mon, 28 Nov 2022 17:38:33 +0800 Subject: [PATCH 03/10] impl new pallet orml-rate-limit --- rate-limit/Cargo.toml | 48 ++++++ rate-limit/src/lib.rs | 392 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 rate-limit/Cargo.toml create mode 100644 rate-limit/src/lib.rs diff --git a/rate-limit/Cargo.toml b/rate-limit/Cargo.toml new file mode 100644 index 000000000..d847bdf8e --- /dev/null +++ b/rate-limit/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "orml-rate-limit" +description = "Provides way to config rate limiter for limit some operation." +repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/rate-limit" +license = "Apache-2.0" +version = "0.4.1-dev" +authors = ["Laminar Developers "] +edition = "2021" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } + +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } + +orml-traits = { path = "../traits", version = "0.4.1-dev", default-features = false } + + +[features] +default = ["std"] +std = [ + "serde", + + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "orml-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs new file mode 100644 index 000000000..fbdc88cea --- /dev/null +++ b/rate-limit/src/lib.rs @@ -0,0 +1,392 @@ +//! # Oracle +//! A module to allow oracle operators to feed external data. +//! +//! - [`Config`](./trait.Config.html) +//! - [`Call`](./enum.Call.html) +//! - [`Module`](./struct.Module.html) +//! +//! ## Overview +//! +//! This module exposes capabilities for oracle operators to feed external +//! offchain data. The raw values can be combined to provide an aggregated +//! value. +//! +//! The data is valid only if feeded by an authorized operator. +//! `pallet_membership` in FRAME can be used to as source of `T::Members`. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +use frame_support::{pallet_prelude::*, traits::UnixTime, transactional, BoundedVec}; +use frame_system::pallet_prelude::*; +use orml_traits::{RateLimiter, RateLimiterError}; +use scale_info::TypeInfo; +use sp_runtime::traits::{MaybeSerializeDeserialize, SaturatedConversion, Zero}; +use sp_std::{prelude::*, vec::Vec}; + +//pub use module::*; +// pub use weights::WeightInfo; + +// mod mock; +// mod tests; +// pub mod weights; + +#[frame_support::pallet] +pub mod module { + use super::*; + + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum RateLimit { + PerBlocks { + blocks_count: u64, + quota: u128, + }, + PerSeconds { + secs_count: u64, + quota: u128, + }, + TokenBucket { + blocks_count: u64, + quota_increment: u128, + max_quota: u128, + }, + Unlimited, + NotAllowed, + } + + impl Default for RateLimit { + fn default() -> Self { + RateLimit::Unlimited + } + } + + #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum KeyFilter { + Match(Vec), + StartsWith(Vec), + EndsWith(Vec), + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Origin represented Governance + type GovernanceOrigin: EnsureOrigin; + + type RateLimiterId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + TypeInfo; + + #[pallet::constant] + type MaxWhitelistFilterCount: Get; + + type UnixTime: UnixTime; + + // /// Weight information for the extrinsics in this module. + // type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + FilterExisted, + FilterNotExisted, + MaxFilterExceeded, + DecodeKeyFailed, + InvalidRateLimit, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + LimiRateUpdated { + rate_limiter_id: T::RateLimiterId, + key: Vec, + update: Option, + }, + WhitelistFilterAdded { + rate_limiter_id: T::RateLimiterId, + }, + WhitelistFilterRemoved { + rate_limiter_id: T::RateLimiterId, + }, + WhitelistFilterReset { + rate_limiter_id: T::RateLimiterId, + }, + } + + #[pallet::storage] + #[pallet::getter(fn rate_limits)] + pub type RateLimits = + StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, RateLimit, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn rate_limit_quota)] + pub type RateLimitQuota = + StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, (u64, u128), ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn bypass_limit_whitelist)] + pub type BypassLimitWhitelist = + StorageMap<_, Twox64Concat, T::RateLimiterId, BoundedVec, ValueQuery>; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(10000)] + #[transactional] + pub fn update_rate_limit( + origin: OriginFor, + rate_limiter_id: T::RateLimiterId, + key: Vec, + update: Option, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + RateLimits::::try_mutate_exists(&rate_limiter_id, key.clone(), |maybe_limit| -> DispatchResult { + *maybe_limit = update.clone(); + + if let Some(rate_limit) = maybe_limit { + match rate_limit { + RateLimit::PerBlocks { blocks_count, quota } => { + ensure!( + !blocks_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimit + ); + } + RateLimit::PerSeconds { secs_count, quota } => { + ensure!(!secs_count.is_zero() && !quota.is_zero(), Error::::InvalidRateLimit); + } + RateLimit::TokenBucket { + blocks_count, + quota_increment, + max_quota, + } => { + ensure!( + !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), + Error::::InvalidRateLimit + ); + } + _ => {} + } + } + + // always reset RateLimitQuota. + RateLimitQuota::::remove(&rate_limiter_id, &key); + + Self::deposit_event(Event::LimiRateUpdated { + rate_limiter_id, + key, + update, + }); + + Ok(()) + }) + } + + #[pallet::weight(10000)] + #[transactional] + pub fn set_rate_limit_quota( + origin: OriginFor, + rate_limiter_id: T::RateLimiterId, + key: Vec, + last_update: u64, + amount: u128, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + RateLimitQuota::::insert(rate_limiter_id, key, (last_update, amount)); + + Ok(()) + } + + #[pallet::weight(10000)] + #[transactional] + pub fn add_whitelist( + origin: OriginFor, + rate_limiter_id: T::RateLimiterId, + key_filter: KeyFilter, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + BypassLimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { + let location = whitelist + .binary_search(&key_filter) + .err() + .ok_or(Error::::FilterExisted)?; + whitelist + .try_insert(location, key_filter) + .map_err(|_| Error::::MaxFilterExceeded)?; + + Self::deposit_event(Event::WhitelistFilterAdded { rate_limiter_id }); + Ok(()) + }) + } + + #[pallet::weight(10000)] + #[transactional] + pub fn remove_whitelist( + origin: OriginFor, + rate_limiter_id: T::RateLimiterId, + key_filter: KeyFilter, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + BypassLimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { + let location = whitelist + .binary_search(&key_filter) + .ok() + .ok_or(Error::::FilterExisted)?; + whitelist.remove(location); + + Self::deposit_event(Event::WhitelistFilterRemoved { rate_limiter_id }); + Ok(()) + }) + } + + #[pallet::weight(10000)] + #[transactional] + pub fn reset_whitelist( + origin: OriginFor, + rate_limiter_id: T::RateLimiterId, + new_list: Vec, + ) -> DispatchResult { + T::GovernanceOrigin::ensure_origin(origin)?; + + let mut whitelist: BoundedVec = + BoundedVec::try_from(new_list).map_err(|_| Error::::MaxFilterExceeded)?; + whitelist.sort(); + BypassLimitWhitelist::::insert(rate_limiter_id, whitelist); + + Self::deposit_event(Event::WhitelistFilterReset { rate_limiter_id }); + Ok(()) + } + } + + impl Pallet { + pub fn get_remainer_quota_after_update( + rate_limit: RateLimit, + limiter_id: &T::RateLimiterId, + encoded_key: &Vec, + ) -> u128 { + RateLimitQuota::::mutate(limiter_id, encoded_key, |(last_updated, remainer_quota)| -> u128 { + match rate_limit { + RateLimit::PerBlocks { blocks_count, quota } => { + let now: u64 = frame_system::Pallet::::block_number().saturated_into(); + let interval: u64 = now.saturating_sub(*last_updated); + if interval >= blocks_count { + *last_updated = now; + *remainer_quota = quota; + } + } + + RateLimit::PerSeconds { secs_count, quota } => { + let now: u64 = T::UnixTime::now().as_secs(); + let interval: u64 = now.saturating_sub(*last_updated); + if interval >= secs_count { + *last_updated = now; + *remainer_quota = quota; + } + } + + RateLimit::TokenBucket { + blocks_count, + quota_increment, + max_quota, + } => { + let now: u64 = frame_system::Pallet::::block_number().saturated_into(); + let interval: u64 = now.saturating_sub(*last_updated); + if !blocks_count.is_zero() && interval >= blocks_count { + let inc_times: u128 = interval + .checked_div(blocks_count) + .expect("already ensure blocks_count is not zero; qed") + .saturated_into(); + + *last_updated = now; + *remainer_quota = quota_increment + .saturating_mul(inc_times) + .saturating_add(*remainer_quota) + .min(max_quota); + } + } + + _ => {} + } + + *remainer_quota + }) + } + } + + impl RateLimiter for Pallet { + type RateLimiterId = T::RateLimiterId; + + fn bypass_limit(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool { + let encode_key: Vec = key.encode(); + + for key_filter in BypassLimitWhitelist::::get(limiter_id) { + match key_filter { + KeyFilter::Match(vec) => { + if encode_key == vec { + return true; + } + } + KeyFilter::StartsWith(prefix) => { + if encode_key.starts_with(&prefix) { + return true; + } + } + KeyFilter::EndsWith(postfix) => { + if encode_key.ends_with(&postfix) { + return true; + } + } + } + } + + false + } + + fn is_allowed(limiter_id: Self::RateLimiterId, key: impl Encode, value: u128) -> Result<(), RateLimiterError> { + let encoded_key: Vec = key.encode(); + + let allowed = match RateLimits::::get(&limiter_id, &encoded_key) { + Some(rate_limit @ RateLimit::PerBlocks { .. }) + | Some(rate_limit @ RateLimit::PerSeconds { .. }) + | Some(rate_limit @ RateLimit::TokenBucket { .. }) => { + let remainer_quota = Self::get_remainer_quota_after_update(rate_limit, &limiter_id, &encoded_key); + value <= remainer_quota + } + Some(RateLimit::Unlimited) => false, + Some(RateLimit::NotAllowed) => true, + None => { + // if not defined limit for key, allow it. + true + } + }; + + ensure!(allowed, RateLimiterError::ExceedLimit); + Ok(()) + } + + fn record(limiter_id: Self::RateLimiterId, key: impl Encode, value: u128) { + let encoded_key: Vec = key.encode(); + + match RateLimits::::get(&limiter_id, &encoded_key) { + Some(RateLimit::PerBlocks { .. }) + | Some(RateLimit::PerSeconds { .. }) + | Some(RateLimit::TokenBucket { .. }) => { + // consume remainer quota only in these situation. + RateLimitQuota::::mutate(&limiter_id, &encoded_key, |(_, remainer_quota)| { + *remainer_quota = (*remainer_quota).saturating_sub(value); + }); + } + _ => {} + }; + } + } +} From 782fb5e27c63410a199b0b47858231e262fa9d8e Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Tue, 29 Nov 2022 00:51:40 +0800 Subject: [PATCH 04/10] add unit tests --- rate-limit/Cargo.toml | 2 + rate-limit/src/lib.rs | 105 +++--- rate-limit/src/mock.rs | 105 ++++++ rate-limit/src/tests.rs | 725 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 875 insertions(+), 62 deletions(-) create mode 100644 rate-limit/src/mock.rs create mode 100644 rate-limit/src/tests.rs diff --git a/rate-limit/Cargo.toml b/rate-limit/Cargo.toml index d847bdf8e..d6e5e9401 100644 --- a/rate-limit/Cargo.toml +++ b/rate-limit/Cargo.toml @@ -21,6 +21,8 @@ sp-std = { git = "https://github.com/paritytech/substrate", default-features = f orml-traits = { path = "../traits", version = "0.4.1-dev", default-features = false } +[dev-dependencies] +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.32" } [features] default = ["std"] diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs index fbdc88cea..6ef00c991 100644 --- a/rate-limit/src/lib.rs +++ b/rate-limit/src/lib.rs @@ -24,11 +24,11 @@ use scale_info::TypeInfo; use sp_runtime::traits::{MaybeSerializeDeserialize, SaturatedConversion, Zero}; use sp_std::{prelude::*, vec::Vec}; -//pub use module::*; +pub use module::*; // pub use weights::WeightInfo; -// mod mock; -// mod tests; +mod mock; +mod tests; // pub mod weights; #[frame_support::pallet] @@ -36,7 +36,7 @@ pub mod module { use super::*; #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum RateLimit { + pub enum RateLimitRule { PerBlocks { blocks_count: u64, quota: u128, @@ -54,12 +54,6 @@ pub mod module { NotAllowed, } - impl Default for RateLimit { - fn default() -> Self { - RateLimit::Unlimited - } - } - #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum KeyFilter { Match(Vec), @@ -87,20 +81,19 @@ pub mod module { #[pallet::error] pub enum Error { + InvalidRateLimitRule, FilterExisted, FilterNotExisted, MaxFilterExceeded, - DecodeKeyFailed, - InvalidRateLimit, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - LimiRateUpdated { + LimiRateRuleUpdated { rate_limiter_id: T::RateLimiterId, key: Vec, - update: Option, + update: Option, }, WhitelistFilterAdded { rate_limiter_id: T::RateLimiterId, @@ -114,9 +107,9 @@ pub mod module { } #[pallet::storage] - #[pallet::getter(fn rate_limits)] - pub type RateLimits = - StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, RateLimit, OptionQuery>; + #[pallet::getter(fn rate_limit_rules)] + pub type RateLimitRules = + StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, RateLimitRule, OptionQuery>; #[pallet::storage] #[pallet::getter(fn rate_limit_quota)] @@ -139,36 +132,39 @@ pub mod module { impl Pallet { #[pallet::weight(10000)] #[transactional] - pub fn update_rate_limit( + pub fn update_rate_limit_rule( origin: OriginFor, rate_limiter_id: T::RateLimiterId, key: Vec, - update: Option, + update: Option, ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - RateLimits::::try_mutate_exists(&rate_limiter_id, key.clone(), |maybe_limit| -> DispatchResult { + RateLimitRules::::try_mutate_exists(&rate_limiter_id, key.clone(), |maybe_limit| -> DispatchResult { *maybe_limit = update.clone(); - if let Some(rate_limit) = maybe_limit { - match rate_limit { - RateLimit::PerBlocks { blocks_count, quota } => { + if let Some(rule) = maybe_limit { + match rule { + RateLimitRule::PerBlocks { blocks_count, quota } => { ensure!( !blocks_count.is_zero() && !quota.is_zero(), - Error::::InvalidRateLimit + Error::::InvalidRateLimitRule ); } - RateLimit::PerSeconds { secs_count, quota } => { - ensure!(!secs_count.is_zero() && !quota.is_zero(), Error::::InvalidRateLimit); + RateLimitRule::PerSeconds { secs_count, quota } => { + ensure!( + !secs_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimitRule + ); } - RateLimit::TokenBucket { + RateLimitRule::TokenBucket { blocks_count, quota_increment, max_quota, } => { ensure!( !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), - Error::::InvalidRateLimit + Error::::InvalidRateLimitRule ); } _ => {} @@ -178,7 +174,7 @@ pub mod module { // always reset RateLimitQuota. RateLimitQuota::::remove(&rate_limiter_id, &key); - Self::deposit_event(Event::LimiRateUpdated { + Self::deposit_event(Event::LimiRateRuleUpdated { rate_limiter_id, key, update, @@ -188,22 +184,6 @@ pub mod module { }) } - #[pallet::weight(10000)] - #[transactional] - pub fn set_rate_limit_quota( - origin: OriginFor, - rate_limiter_id: T::RateLimiterId, - key: Vec, - last_update: u64, - amount: u128, - ) -> DispatchResult { - T::GovernanceOrigin::ensure_origin(origin)?; - - RateLimitQuota::::insert(rate_limiter_id, key, (last_update, amount)); - - Ok(()) - } - #[pallet::weight(10000)] #[transactional] pub fn add_whitelist( @@ -268,14 +248,14 @@ pub mod module { } impl Pallet { - pub fn get_remainer_quota_after_update( - rate_limit: RateLimit, + pub fn access_remainer_quota_after_update( + rate_limit_rule: RateLimitRule, limiter_id: &T::RateLimiterId, encoded_key: &Vec, ) -> u128 { RateLimitQuota::::mutate(limiter_id, encoded_key, |(last_updated, remainer_quota)| -> u128 { - match rate_limit { - RateLimit::PerBlocks { blocks_count, quota } => { + match rate_limit_rule { + RateLimitRule::PerBlocks { blocks_count, quota } => { let now: u64 = frame_system::Pallet::::block_number().saturated_into(); let interval: u64 = now.saturating_sub(*last_updated); if interval >= blocks_count { @@ -284,7 +264,7 @@ pub mod module { } } - RateLimit::PerSeconds { secs_count, quota } => { + RateLimitRule::PerSeconds { secs_count, quota } => { let now: u64 = T::UnixTime::now().as_secs(); let interval: u64 = now.saturating_sub(*last_updated); if interval >= secs_count { @@ -293,7 +273,7 @@ pub mod module { } } - RateLimit::TokenBucket { + RateLimitRule::TokenBucket { blocks_count, quota_increment, max_quota, @@ -354,15 +334,16 @@ pub mod module { fn is_allowed(limiter_id: Self::RateLimiterId, key: impl Encode, value: u128) -> Result<(), RateLimiterError> { let encoded_key: Vec = key.encode(); - let allowed = match RateLimits::::get(&limiter_id, &encoded_key) { - Some(rate_limit @ RateLimit::PerBlocks { .. }) - | Some(rate_limit @ RateLimit::PerSeconds { .. }) - | Some(rate_limit @ RateLimit::TokenBucket { .. }) => { - let remainer_quota = Self::get_remainer_quota_after_update(rate_limit, &limiter_id, &encoded_key); + let allowed = match RateLimitRules::::get(&limiter_id, &encoded_key) { + Some(rate_limit_rule @ RateLimitRule::PerBlocks { .. }) + | Some(rate_limit_rule @ RateLimitRule::PerSeconds { .. }) + | Some(rate_limit_rule @ RateLimitRule::TokenBucket { .. }) => { + let remainer_quota = + Self::access_remainer_quota_after_update(rate_limit_rule, &limiter_id, &encoded_key); value <= remainer_quota } - Some(RateLimit::Unlimited) => false, - Some(RateLimit::NotAllowed) => true, + Some(RateLimitRule::Unlimited) => true, + Some(RateLimitRule::NotAllowed) => false, None => { // if not defined limit for key, allow it. true @@ -376,10 +357,10 @@ pub mod module { fn record(limiter_id: Self::RateLimiterId, key: impl Encode, value: u128) { let encoded_key: Vec = key.encode(); - match RateLimits::::get(&limiter_id, &encoded_key) { - Some(RateLimit::PerBlocks { .. }) - | Some(RateLimit::PerSeconds { .. }) - | Some(RateLimit::TokenBucket { .. }) => { + match RateLimitRules::::get(&limiter_id, &encoded_key) { + Some(RateLimitRule::PerBlocks { .. }) + | Some(RateLimitRule::PerSeconds { .. }) + | Some(RateLimitRule::TokenBucket { .. }) => { // consume remainer quota only in these situation. RateLimitQuota::::mutate(&limiter_id, &encoded_key, |(_, remainer_quota)| { *remainer_quota = (*remainer_quota).saturating_sub(value); diff --git a/rate-limit/src/mock.rs b/rate-limit/src/mock.rs new file mode 100644 index 000000000..e908348d0 --- /dev/null +++ b/rate-limit/src/mock.rs @@ -0,0 +1,105 @@ +//! Mocks for the rate limit. + +#![cfg(test)] + +use super::*; +use frame_support::traits::{ConstU32, ConstU64, Everything}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +pub type AccountId = AccountId32; +pub type BlockNumber = u64; +pub type CurrencyId = u32; +pub type Moment = u64; +pub type RateLimiterId = u8; + +pub const DOT: CurrencyId = 1; +pub const BTC: CurrencyId = 2; +pub const ETH: CurrencyId = 3; +pub const ALICE: AccountId = AccountId32::new([0u8; 32]); +pub const BOB: AccountId = AccountId32::new([1u8; 32]); +pub const CHARLIE: AccountId = AccountId32::new([2u8; 32]); +pub const DAVE: AccountId = AccountId32::new([3u8; 32]); +pub const TREASURY_ACCOUNT: AccountId = AccountId32::new([ + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, +]); + +use crate as rate_limit; + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1_000>; + type WeightInfo = (); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type GovernanceOrigin = EnsureRoot; + type RateLimiterId = RateLimiterId; + type MaxWhitelistFilterCount = ConstU32<3>; + type UnixTime = Timestamp; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + RateLimit: rate_limit::{Pallet, Storage, Call, Event}, + } +); + +pub struct ExtBuilder; + +impl Default for ExtBuilder { + fn default() -> Self { + ExtBuilder + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/rate-limit/src/tests.rs b/rate-limit/src/tests.rs new file mode 100644 index 000000000..058dcb62e --- /dev/null +++ b/rate-limit/src/tests.rs @@ -0,0 +1,725 @@ +//! Unit tests for the rate limit. +#![cfg(test)] + +use super::*; +use frame_support::{assert_noop, assert_ok}; +use mock::*; +use sp_runtime::traits::BadOrigin; + +#[test] +fn update_rate_limit_rule_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + RateLimit::update_rate_limit_rule( + RuntimeOrigin::signed(ALICE), + 0, + DOT.encode(), + Some(RateLimitRule::NotAllowed), + ), + BadOrigin + ); + + assert_eq!(RateLimit::rate_limit_rules(0, DOT.encode()), None); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::NotAllowed), + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::LimiRateRuleUpdated { + rate_limiter_id: 0, + key: DOT.encode(), + update: Some(RateLimitRule::NotAllowed), + })); + assert_eq!( + RateLimit::rate_limit_rules(0, DOT.encode()), + Some(RateLimitRule::NotAllowed) + ); + + assert_noop!( + RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::PerBlocks { + blocks_count: 0, + quota: 1, + }), + ), + Error::::InvalidRateLimitRule + ); + assert_noop!( + RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::PerSeconds { + secs_count: 1, + quota: 0, + }), + ), + Error::::InvalidRateLimitRule + ); + assert_noop!( + RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::TokenBucket { + blocks_count: 100, + quota_increment: 1000, + max_quota: 0, + }), + ), + Error::::InvalidRateLimitRule + ); + + // update will reset RateLimitQuota + RateLimitQuota::::insert(0, DOT.encode(), (10, 100)); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (10, 100)); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::TokenBucket { + blocks_count: 100, + quota_increment: 1000, + max_quota: 10000, + }), + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::LimiRateRuleUpdated { + rate_limiter_id: 0, + key: DOT.encode(), + update: Some(RateLimitRule::TokenBucket { + blocks_count: 100, + quota_increment: 1000, + max_quota: 10000, + }), + })); + assert_eq!( + RateLimit::rate_limit_rules(0, DOT.encode()), + Some(RateLimitRule::TokenBucket { + blocks_count: 100, + quota_increment: 1000, + max_quota: 10000, + }) + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + }); +} + +#[test] +fn add_whitelist_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + RateLimit::add_whitelist(RuntimeOrigin::signed(ALICE), 0, KeyFilter::Match(ALICE.encode())), + BadOrigin + ); + + assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + assert_ok!(RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(ALICE.encode()) + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterAdded { + rate_limiter_id: 0, + })); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![KeyFilter::Match(ALICE.encode())] + ); + + // add already existed. + assert_noop!( + RateLimit::add_whitelist(RuntimeOrigin::root(), 0, KeyFilter::Match(ALICE.encode())), + Error::::FilterExisted + ); + + assert_ok!(RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(BOB.encode()) + )); + assert_ok!(RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(CHARLIE.encode()) + )); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![ + KeyFilter::Match(ALICE.encode()), + KeyFilter::Match(BOB.encode()), + KeyFilter::Match(CHARLIE.encode()) + ] + ); + + // exceed filters limit + assert_noop!( + RateLimit::add_whitelist(RuntimeOrigin::root(), 0, KeyFilter::Match(DAVE.encode())), + Error::::MaxFilterExceeded + ); + }); +} + +#[test] +fn remove_whitelist_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(ALICE.encode()) + )); + assert_ok!(RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(BOB.encode()) + )); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + ); + + assert_noop!( + RateLimit::remove_whitelist(RuntimeOrigin::signed(ALICE), 0, KeyFilter::StartsWith(ALICE.encode())), + BadOrigin + ); + + assert_noop!( + RateLimit::remove_whitelist(RuntimeOrigin::root(), 0, KeyFilter::StartsWith(ALICE.encode())), + Error::::FilterExisted + ); + + assert_ok!(RateLimit::remove_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(ALICE.encode()) + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterRemoved { + rate_limiter_id: 0, + })); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![KeyFilter::Match(BOB.encode())] + ); + }); +} + +#[test] +fn reset_whitelist_work() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + RateLimit::reset_whitelist( + RuntimeOrigin::signed(ALICE), + 0, + vec![KeyFilter::StartsWith(ALICE.encode())], + ), + BadOrigin + ); + + // exceed filters limit + assert_noop!( + RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![ + KeyFilter::StartsWith(ALICE.encode()), + KeyFilter::StartsWith(DAVE.encode()), + KeyFilter::StartsWith(CHARLIE.encode()), + KeyFilter::StartsWith(DAVE.encode()) + ], + ), + Error::::MaxFilterExceeded + ); + + assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterReset { + rate_limiter_id: 0, + })); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + ); + + // will sort KeyFilter list before insert. + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![ + KeyFilter::Match(CHARLIE.encode()), + KeyFilter::Match(BOB.encode()), + KeyFilter::Match(ALICE.encode()) + ] + )); + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterReset { + rate_limiter_id: 0, + })); + assert_eq!( + RateLimit::bypass_limit_whitelist(0), + vec![ + KeyFilter::Match(ALICE.encode()), + KeyFilter::Match(BOB.encode()), + KeyFilter::Match(CHARLIE.encode()) + ] + ); + + // clear + assert_ok!(RateLimit::reset_whitelist(RuntimeOrigin::root(), 0, vec![])); + assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + }); +} + +#[test] +fn bypass_limit_work() { + ExtBuilder::default().build().execute_with(|| { + assert_eq!(RateLimit::bypass_limit(0, BOB), false); + assert_eq!(RateLimit::bypass_limit(1, BOB), false); + assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); + + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![KeyFilter::Match(BOB.encode())] + )); + assert_eq!(RateLimit::bypass_limit(0, BOB), true); + assert_eq!(RateLimit::bypass_limit(1, BOB), false); + assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); + + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![KeyFilter::StartsWith(vec![1, 1, 1, 1])] + )); + assert_eq!(RateLimit::bypass_limit(0, BOB), true); + assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), true); + + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![KeyFilter::StartsWith(vec![1, 1, 1, 1, 1])] + )); + assert_eq!(RateLimit::bypass_limit(0, BOB), true); + assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); + assert_eq!(RateLimit::bypass_limit(0, CHARLIE), false); + + assert_ok!(RateLimit::reset_whitelist( + RuntimeOrigin::root(), + 0, + vec![ + KeyFilter::StartsWith(vec![1, 1, 1, 1, 1]), + KeyFilter::EndsWith(vec![2, 2, 2, 2]) + ] + )); + assert_eq!(RateLimit::bypass_limit(0, BOB), true); + assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), true); + assert_eq!(RateLimit::bypass_limit(0, CHARLIE), true); + }); +} + +#[test] +fn access_remainer_quota_after_update_per_blocks() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(100); + assert_eq!(System::block_number(), 100); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + // current - last_updated >= blocks_count, will update RateLimitQuota firstly + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerBlocks { + blocks_count: 30, + quota: 500, + }, + &0, + &DOT.encode(), + ), + 500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 500)); + + // mock consume remainer_quota + RateLimitQuota::::mutate(0, DOT.encode(), |(_, remainer_quota)| *remainer_quota = 400); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + // current - last_updated < blocks_count, will not update RateLimitQuota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerBlocks { + blocks_count: 30, + quota: 5000, + }, + &0, + &DOT.encode(), + ), + 400 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + System::set_block_number(119); + assert_eq!(System::block_number(), 119); + + // current - last_updated < blocks_count, will not update RateLimitQuota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerBlocks { + blocks_count: 20, + quota: 100, + }, + &0, + &DOT.encode(), + ), + 400 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + System::set_block_number(120); + assert_eq!(System::block_number(), 120); + + // current - last_updated > blocks_count, will reset remainer_quota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerBlocks { + blocks_count: 20, + quota: 100, + }, + &0, + &DOT.encode(), + ), + 100 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (120, 100)); + }); +} + +#[test] +fn access_remainer_quota_after_update_per_seconds() { + ExtBuilder::default().build().execute_with(|| { + Timestamp::set_timestamp(100_000); + assert_eq!(::now().as_secs(), 100); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + // current - last_updated >= secs_count, will update RateLimitQuota firstly + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerSeconds { + secs_count: 30, + quota: 500, + }, + &0, + &DOT.encode(), + ), + 500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 500)); + + // mock consume remainer_quota + RateLimitQuota::::mutate(0, DOT.encode(), |(_, remainer_quota)| *remainer_quota = 400); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + // current - last_updated < secs_count, will not update RateLimitQuota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerSeconds { + secs_count: 30, + quota: 5000, + }, + &0, + &DOT.encode(), + ), + 400 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + Timestamp::set_timestamp(119_000); + assert_eq!(::now().as_secs(), 119); + + // current - last_updated < secs_count, will not update RateLimitQuota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerSeconds { + secs_count: 20, + quota: 100, + }, + &0, + &DOT.encode(), + ), + 400 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 400)); + + Timestamp::set_timestamp(120_000); + assert_eq!(::now().as_secs(), 120); + + // current - last_updated > secs_count, will reset remainer_quota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::PerSeconds { + secs_count: 20, + quota: 100, + }, + &0, + &DOT.encode(), + ), + 100 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (120, 100)); + }); +} + +#[test] +fn access_remainer_quota_after_update_token_bucket() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(100); + assert_eq!(System::block_number(), 100); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + // (current - last_updated) / blocks_count = 3, will inc 3 * quota_increment + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::TokenBucket { + blocks_count: 30, + quota_increment: 500, + max_quota: 1500, + }, + &0, + &DOT.encode(), + ), + 1500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 1500)); + + // mock consume remainer_quota + RateLimitQuota::::mutate(0, DOT.encode(), |(_, remainer_quota)| *remainer_quota = 1400); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 1400)); + + System::set_block_number(119); + assert_eq!(System::block_number(), 119); + + // (current - last_updated) / blocks_count = 0, will not update RateLimitQuota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::TokenBucket { + blocks_count: 30, + quota_increment: 500, + max_quota: 1500, + }, + &0, + &DOT.encode(), + ), + 1400 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 1400)); + + System::set_block_number(130); + assert_eq!(System::block_number(), 130); + + // (current - last_updated) / blocks_count = 1, will inc quota_increment, but + // remainer_quota always <= max_quota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::TokenBucket { + blocks_count: 30, + quota_increment: 500, + max_quota: 1500, + }, + &0, + &DOT.encode(), + ), + 1500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (130, 1500)); + + System::set_block_number(160); + assert_eq!(System::block_number(), 160); + + // (current - last_updated) / blocks_count = 1, will inc quota_increment, but + // remainer_quota always <= max_quota + assert_eq!( + RateLimit::access_remainer_quota_after_update( + RateLimitRule::TokenBucket { + blocks_count: 30, + quota_increment: 500, + max_quota: 200, + }, + &0, + &DOT.encode(), + ), + 200 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (160, 200)); + }); +} + +#[test] +fn access_remainer_quota_after_update_when_not_allowed_or_unlimited() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(100); + assert_eq!(System::block_number(), 100); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + assert_eq!( + RateLimit::access_remainer_quota_after_update(RateLimitRule::NotAllowed, &0, &DOT.encode(),), + 0 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + assert_eq!( + RateLimit::access_remainer_quota_after_update(RateLimitRule::Unlimited, &0, &DOT.encode(),), + 0 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + RateLimitQuota::::mutate(0, DOT.encode(), |(_, remainer_quota)| *remainer_quota = 500); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 500)); + + assert_eq!( + RateLimit::access_remainer_quota_after_update(RateLimitRule::NotAllowed, &0, &DOT.encode(),), + 500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 500)); + assert_eq!( + RateLimit::access_remainer_quota_after_update(RateLimitRule::Unlimited, &0, &DOT.encode(),), + 500 + ); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 500)); + }); +} + +#[test] +fn record_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::PerBlocks { + blocks_count: 30, + quota: 500, + }), + )); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + BTC.encode(), + Some(RateLimitRule::Unlimited), + )); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 1, + ETH.encode(), + Some(RateLimitRule::PerBlocks { + blocks_count: 30, + quota: 500, + }), + )); + + RateLimitQuota::::mutate(0, DOT.encode(), |(_, remainer_quota)| *remainer_quota = 10000); + RateLimitQuota::::mutate(0, BTC.encode(), |(_, remainer_quota)| *remainer_quota = 100); + RateLimitQuota::::mutate(0, ETH.encode(), |(_, remainer_quota)| *remainer_quota = 1000); + RateLimitQuota::::mutate(1, ETH.encode(), |(_, remainer_quota)| *remainer_quota = 1000); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 10000)); + assert_eq!(RateLimit::rate_limit_quota(0, BTC.encode()), (0, 100)); + assert_eq!(RateLimit::rate_limit_quota(0, ETH.encode()), (0, 1000)); + assert_eq!(RateLimit::rate_limit_quota(1, ETH.encode()), (0, 1000)); + + // will consume + RateLimit::record(0, DOT, 1000); + RateLimit::record(1, ETH, 500); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 9000)); + assert_eq!(RateLimit::rate_limit_quota(1, ETH.encode()), (0, 500)); + + // will not consume + RateLimit::record(0, BTC, 100); + RateLimit::record(0, ETH, 500); + assert_eq!(RateLimit::rate_limit_quota(0, BTC.encode()), (0, 100)); + assert_eq!(RateLimit::rate_limit_quota(0, ETH.encode()), (0, 1000)); + + // consume when vaule > remainer_quota + RateLimit::record(1, ETH, 1000); + assert_eq!(RateLimit::rate_limit_quota(1, ETH.encode()), (0, 0)); + }); +} + +#[test] +fn is_allowed_work() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + DOT.encode(), + Some(RateLimitRule::PerBlocks { + blocks_count: 30, + quota: 500, + }), + )); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 1, + DOT.encode(), + Some(RateLimitRule::TokenBucket { + blocks_count: 30, + quota_increment: 500, + max_quota: 1000, + }), + )); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 0, + BTC.encode(), + Some(RateLimitRule::NotAllowed), + )); + assert_ok!(RateLimit::update_rate_limit_rule( + RuntimeOrigin::root(), + 1, + BTC.encode(), + Some(RateLimitRule::Unlimited), + )); + + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + assert_ok!(RateLimit::is_allowed(0, DOT, 0)); + assert_eq!(RateLimit::is_allowed(0, DOT, 500), Err(RateLimiterError::ExceedLimit),); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + + assert_eq!(RateLimit::rate_limit_quota(1, DOT.encode()), (0, 0)); + assert_ok!(RateLimit::is_allowed(1, DOT, 0)); + assert_eq!(RateLimit::is_allowed(1, DOT, 501), Err(RateLimiterError::ExceedLimit),); + assert_eq!(RateLimit::rate_limit_quota(1, DOT.encode()), (0, 0)); + + System::set_block_number(100); + assert_eq!(System::block_number(), 100); + + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (0, 0)); + assert_ok!(RateLimit::is_allowed(0, DOT, 500)); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 500)); + assert_eq!(RateLimit::is_allowed(0, DOT, 501), Err(RateLimiterError::ExceedLimit),); + assert_eq!(RateLimit::rate_limit_quota(0, DOT.encode()), (100, 500)); + + assert_eq!(RateLimit::rate_limit_quota(1, DOT.encode()), (0, 0)); + assert_ok!(RateLimit::is_allowed(1, DOT, 501)); + assert_eq!(RateLimit::rate_limit_quota(1, DOT.encode()), (100, 1000)); + assert_eq!(RateLimit::is_allowed(1, DOT, 1001), Err(RateLimiterError::ExceedLimit),); + assert_eq!(RateLimit::rate_limit_quota(1, DOT.encode()), (100, 1000)); + + // NotAllowed always return error, even if value is 0 + RateLimitQuota::::mutate(0, BTC.encode(), |(_, remainer_quota)| *remainer_quota = 10000); + assert_eq!(RateLimit::rate_limit_quota(0, BTC.encode()), (0, 10000)); + assert_eq!(RateLimit::is_allowed(0, BTC, 0), Err(RateLimiterError::ExceedLimit),); + assert_eq!(RateLimit::is_allowed(0, BTC, 100), Err(RateLimiterError::ExceedLimit),); + + // Unlimited always return true + assert_eq!(RateLimit::rate_limit_quota(1, BTC.encode()), (0, 0)); + assert_ok!(RateLimit::is_allowed(1, BTC, 0)); + assert_ok!(RateLimit::is_allowed(1, BTC, 10000)); + assert_ok!(RateLimit::is_allowed(1, BTC, u128::MAX)); + + // if dosen't config rule, always return true + assert_eq!(RateLimitRules::::contains_key(0, ETH.encode()), false); + assert_ok!(RateLimit::is_allowed(0, ETH, 10000)); + assert_ok!(RateLimit::is_allowed(0, ETH, u128::MAX)); + }); +} From ae2d3aa5b093ba95e8f3851a6cb7a9dec6be797d Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Tue, 29 Nov 2022 13:19:28 +0800 Subject: [PATCH 05/10] add comment --- Cargo.dev.toml | 3 +- rate-limit/src/lib.rs | 202 ++++++++++++++++++++++++++-------------- rate-limit/src/tests.rs | 8 +- 3 files changed, 139 insertions(+), 74 deletions(-) diff --git a/Cargo.dev.toml b/Cargo.dev.toml index 1b99160a3..376b4d76f 100644 --- a/Cargo.dev.toml +++ b/Cargo.dev.toml @@ -23,7 +23,8 @@ members = [ "build-script-utils", "weight-gen", "weight-meter", - "payments" + "payments", + "rate-limit", ] resolver = "2" diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs index 6ef00c991..f39363895 100644 --- a/rate-limit/src/lib.rs +++ b/rate-limit/src/lib.rs @@ -1,5 +1,5 @@ -//! # Oracle -//! A module to allow oracle operators to feed external data. +//! # Rate Limit +//! A module to provide rate limit for arbitrary type Key and integer type Value //! //! - [`Config`](./trait.Config.html) //! - [`Call`](./enum.Call.html) @@ -7,12 +7,10 @@ //! //! ## Overview //! -//! This module exposes capabilities for oracle operators to feed external -//! offchain data. The raw values can be combined to provide an aggregated -//! value. -//! -//! The data is valid only if feeded by an authorized operator. -//! `pallet_membership` in FRAME can be used to as source of `T::Members`. +//! This module is a utility to provide rate limiter for arbitrary type Key and +//! integer type Value, which can config limit rule to produce quota and consume +//! quota, and expose quota consuming checking and whitelist that can bypass +//! checks. #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] @@ -35,29 +33,39 @@ mod tests; pub mod module { use super::*; + /// Limit rules type. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum RateLimitRule { - PerBlocks { - blocks_count: u64, - quota: u128, - }, - PerSeconds { - secs_count: u64, - quota: u128, - }, + /// Each `blocks_count` blocks to reset remainer quota to `quota` + /// amount. is_allowed check return true when the remainer quota gte the + /// consume amount. + PerBlocks { blocks_count: u64, quota: u128 }, + /// Each `secs_count` seconds to reset remainer quota to `quota` amount. + /// is_allowed check return true when the remainer quota gte the consume + /// amount. + PerSeconds { secs_count: u64, quota: u128 }, + /// Each `blocks_count` blocks to increase `quota_increment` amount to + /// remainer quota and keep remainer quota lte `max_quota`. is_allowed + /// check return true when the remainer quota gte the consume amount. TokenBucket { blocks_count: u64, quota_increment: u128, max_quota: u128, }, + /// is_allowed check return true always. Unlimited, + /// is_allowed check return false always. NotAllowed, } + /// Match rules to fitler key is in bypass whitelist. #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum KeyFilter { + /// If the encoded key is equal to the vec, the key is in whitelist. Match(Vec), + /// If the encoded key starts with the vec, the key is in whitelist. StartsWith(Vec), + /// If the encoded key ends with the vec, the key is in whitelist. EndsWith(Vec), } @@ -65,14 +73,16 @@ pub mod module { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Origin represented Governance + /// Origin represented Governance. type GovernanceOrigin: EnsureOrigin; type RateLimiterId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + TypeInfo; + /// The maximum number of KeyFilter configured to a RateLimiterId. #[pallet::constant] type MaxWhitelistFilterCount: Get; + /// Time used for calculate quota. type UnixTime: UnixTime; // /// Weight information for the extrinsics in this module. @@ -81,41 +91,54 @@ pub mod module { #[pallet::error] pub enum Error { + /// Invalid rate limit rule. InvalidRateLimitRule, + /// The KeyFilter has been existed already. FilterExisted, + /// The KeyFilter doesn't exist. FilterNotExisted, + /// Exceed the allowed maximum number of KeyFilter configured to a + /// RateLimiterId. MaxFilterExceeded, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - LimiRateRuleUpdated { + /// The rate limit rule has updated. + RateLimitRuleUpdated { rate_limiter_id: T::RateLimiterId, - key: Vec, + encoded_key: Vec, update: Option, }, - WhitelistFilterAdded { - rate_limiter_id: T::RateLimiterId, - }, - WhitelistFilterRemoved { - rate_limiter_id: T::RateLimiterId, - }, - WhitelistFilterReset { - rate_limiter_id: T::RateLimiterId, - }, + /// The whitelist of bypass rate limit has been added new KeyFilter. + WhitelistFilterAdded { rate_limiter_id: T::RateLimiterId }, + /// The whitelist of bypass rate limit has been removed a KeyFilter. + WhitelistFilterRemoved { rate_limiter_id: T::RateLimiterId }, + /// The whitelist of bypass rate limit has been reset. + WhitelistFilterReset { rate_limiter_id: T::RateLimiterId }, } + /// The rate limit rule for specific RateLimiterId and encoded key. + /// + /// RateLimitRules: double_map RateLimiterId, EncodedKey => RateLimitRule #[pallet::storage] #[pallet::getter(fn rate_limit_rules)] pub type RateLimitRules = StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, RateLimitRule, OptionQuery>; + /// The quota for specific RateLimiterId and encoded key. + /// + /// RateLimitQuota: double_map RateLimiterId, EncodedKey => + /// (LastUpdatedBlockOrTime, RemainerQuota) #[pallet::storage] #[pallet::getter(fn rate_limit_quota)] pub type RateLimitQuota = StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, (u64, u128), ValueQuery>; + /// The rules to filter if key is in whitelist for specific RateLimiterId. + /// + /// BypassLimitWhitelist: map RateLimiterId => Vec #[pallet::storage] #[pallet::getter(fn bypass_limit_whitelist)] pub type BypassLimitWhitelist = @@ -130,60 +153,80 @@ pub mod module { #[pallet::call] impl Pallet { + /// Config the rate limit rule. + /// + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `rate_limiter_id`: rate limiter id. + /// - `encoded key`: the encoded key to limit. + /// - `update`: the RateLimitRule to config, None will remove current + /// config. #[pallet::weight(10000)] #[transactional] pub fn update_rate_limit_rule( origin: OriginFor, rate_limiter_id: T::RateLimiterId, - key: Vec, + encoded_key: Vec, update: Option, ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - RateLimitRules::::try_mutate_exists(&rate_limiter_id, key.clone(), |maybe_limit| -> DispatchResult { - *maybe_limit = update.clone(); - - if let Some(rule) = maybe_limit { - match rule { - RateLimitRule::PerBlocks { blocks_count, quota } => { - ensure!( - !blocks_count.is_zero() && !quota.is_zero(), - Error::::InvalidRateLimitRule - ); - } - RateLimitRule::PerSeconds { secs_count, quota } => { - ensure!( - !secs_count.is_zero() && !quota.is_zero(), - Error::::InvalidRateLimitRule - ); - } - RateLimitRule::TokenBucket { - blocks_count, - quota_increment, - max_quota, - } => { - ensure!( - !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), - Error::::InvalidRateLimitRule - ); + RateLimitRules::::try_mutate_exists( + &rate_limiter_id, + encoded_key.clone(), + |maybe_limit| -> DispatchResult { + *maybe_limit = update.clone(); + + if let Some(rule) = maybe_limit { + match rule { + RateLimitRule::PerBlocks { blocks_count, quota } => { + ensure!( + !blocks_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimitRule + ); + } + RateLimitRule::PerSeconds { secs_count, quota } => { + ensure!( + !secs_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimitRule + ); + } + RateLimitRule::TokenBucket { + blocks_count, + quota_increment, + max_quota, + } => { + ensure!( + !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), + Error::::InvalidRateLimitRule + ); + } + _ => {} } - _ => {} } - } - // always reset RateLimitQuota. - RateLimitQuota::::remove(&rate_limiter_id, &key); + // always reset RateLimitQuota. + RateLimitQuota::::remove(&rate_limiter_id, &encoded_key); - Self::deposit_event(Event::LimiRateRuleUpdated { - rate_limiter_id, - key, - update, - }); + Self::deposit_event(Event::RateLimitRuleUpdated { + rate_limiter_id, + encoded_key, + update, + }); - Ok(()) - }) + Ok(()) + }, + ) } + /// Add whitelist filter rule. + /// + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `rate_limiter_id`: rate limiter id. + /// - `key_filter`: filter rule to add. #[pallet::weight(10000)] #[transactional] pub fn add_whitelist( @@ -207,6 +250,13 @@ pub mod module { }) } + /// Remove whitelist filter rule. + /// + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `rate_limiter_id`: rate limiter id. + /// - `key_filter`: filter rule to remove. #[pallet::weight(10000)] #[transactional] pub fn remove_whitelist( @@ -228,6 +278,13 @@ pub mod module { }) } + /// Resett whitelist filter rule. + /// + /// Requires `GovernanceOrigin` + /// + /// Parameters: + /// - `rate_limiter_id`: rate limiter id. + /// - `new_list`: the filter rule list to reset. #[pallet::weight(10000)] #[transactional] pub fn reset_whitelist( @@ -248,6 +305,8 @@ pub mod module { } impl Pallet { + /// Access the RateLimitQuota, if RateLimitRule will produce new quota, + /// update RateLimitQuota and then return remainer_quota pub fn access_remainer_quota_after_update( rate_limit_rule: RateLimitRule, limiter_id: &T::RateLimiterId, @@ -340,17 +399,22 @@ pub mod module { | Some(rate_limit_rule @ RateLimitRule::TokenBucket { .. }) => { let remainer_quota = Self::access_remainer_quota_after_update(rate_limit_rule, &limiter_id, &encoded_key); + value <= remainer_quota } Some(RateLimitRule::Unlimited) => true, - Some(RateLimitRule::NotAllowed) => false, + Some(RateLimitRule::NotAllowed) => { + // always return false, even if the value is zero. + false + } None => { - // if not defined limit for key, allow it. + // if doesn't rate limit rule, always return true. true } }; ensure!(allowed, RateLimiterError::ExceedLimit); + Ok(()) } @@ -361,7 +425,7 @@ pub mod module { Some(RateLimitRule::PerBlocks { .. }) | Some(RateLimitRule::PerSeconds { .. }) | Some(RateLimitRule::TokenBucket { .. }) => { - // consume remainer quota only in these situation. + // consume remainer quota in these situation. RateLimitQuota::::mutate(&limiter_id, &encoded_key, |(_, remainer_quota)| { *remainer_quota = (*remainer_quota).saturating_sub(value); }); diff --git a/rate-limit/src/tests.rs b/rate-limit/src/tests.rs index 058dcb62e..25a052cdc 100644 --- a/rate-limit/src/tests.rs +++ b/rate-limit/src/tests.rs @@ -26,9 +26,9 @@ fn update_rate_limit_rule_work() { DOT.encode(), Some(RateLimitRule::NotAllowed), )); - System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::LimiRateRuleUpdated { + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::RateLimitRuleUpdated { rate_limiter_id: 0, - key: DOT.encode(), + encoded_key: DOT.encode(), update: Some(RateLimitRule::NotAllowed), })); assert_eq!( @@ -87,9 +87,9 @@ fn update_rate_limit_rule_work() { max_quota: 10000, }), )); - System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::LimiRateRuleUpdated { + System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::RateLimitRuleUpdated { rate_limiter_id: 0, - key: DOT.encode(), + encoded_key: DOT.encode(), update: Some(RateLimitRule::TokenBucket { blocks_count: 100, quota_increment: 1000, From ca7f82bbd87bb05c6cb5cb62909298072f37d364 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Tue, 29 Nov 2022 18:18:06 +0800 Subject: [PATCH 06/10] add weight --- rate-limit/src/lib.rs | 20 ++++++------ rate-limit/src/mock.rs | 1 + rate-limit/src/weights.rs | 67 +++++++++++++++++++++++++++++++++++++++ traits/src/rate_limit.rs | 4 +-- 4 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 rate-limit/src/weights.rs diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs index f39363895..bd0fee6c0 100644 --- a/rate-limit/src/lib.rs +++ b/rate-limit/src/lib.rs @@ -19,15 +19,15 @@ use frame_support::{pallet_prelude::*, traits::UnixTime, transactional, BoundedV use frame_system::pallet_prelude::*; use orml_traits::{RateLimiter, RateLimiterError}; use scale_info::TypeInfo; -use sp_runtime::traits::{MaybeSerializeDeserialize, SaturatedConversion, Zero}; +use sp_runtime::traits::{SaturatedConversion, Zero}; use sp_std::{prelude::*, vec::Vec}; pub use module::*; -// pub use weights::WeightInfo; +pub use weights::WeightInfo; mod mock; mod tests; -// pub mod weights; +pub mod weights; #[frame_support::pallet] pub mod module { @@ -76,7 +76,7 @@ pub mod module { /// Origin represented Governance. type GovernanceOrigin: EnsureOrigin; - type RateLimiterId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + TypeInfo; + type RateLimiterId: Parameter + Member + Copy + TypeInfo; /// The maximum number of KeyFilter configured to a RateLimiterId. #[pallet::constant] @@ -85,8 +85,8 @@ pub mod module { /// Time used for calculate quota. type UnixTime: UnixTime; - // /// Weight information for the extrinsics in this module. - // type WeightInfo: WeightInfo; + /// Weight information for the extrinsics in this module. + type WeightInfo: WeightInfo; } #[pallet::error] @@ -162,7 +162,7 @@ pub mod module { /// - `encoded key`: the encoded key to limit. /// - `update`: the RateLimitRule to config, None will remove current /// config. - #[pallet::weight(10000)] + #[pallet::weight(T::WeightInfo::update_rate_limit_rule())] #[transactional] pub fn update_rate_limit_rule( origin: OriginFor, @@ -227,7 +227,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `key_filter`: filter rule to add. - #[pallet::weight(10000)] + #[pallet::weight(T::WeightInfo::add_whitelist())] #[transactional] pub fn add_whitelist( origin: OriginFor, @@ -257,7 +257,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `key_filter`: filter rule to remove. - #[pallet::weight(10000)] + #[pallet::weight(T::WeightInfo::remove_whitelist())] #[transactional] pub fn remove_whitelist( origin: OriginFor, @@ -285,7 +285,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `new_list`: the filter rule list to reset. - #[pallet::weight(10000)] + #[pallet::weight(T::WeightInfo::reset_whitelist())] #[transactional] pub fn reset_whitelist( origin: OriginFor, diff --git a/rate-limit/src/mock.rs b/rate-limit/src/mock.rs index e908348d0..6e538a79a 100644 --- a/rate-limit/src/mock.rs +++ b/rate-limit/src/mock.rs @@ -67,6 +67,7 @@ impl Config for Runtime { type RateLimiterId = RateLimiterId; type MaxWhitelistFilterCount = ConstU32<3>; type UnixTime = Timestamp; + type WeightInfo = (); } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/rate-limit/src/weights.rs b/rate-limit/src/weights.rs new file mode 100644 index 000000000..f7670259e --- /dev/null +++ b/rate-limit/src/weights.rs @@ -0,0 +1,67 @@ +//! Autogenerated weights for orml_rate_limit +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /Users/ermal/Acala/target/release/acala +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=orml_rate_limit +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=../templates/orml-weight-template.hbs +// --output=./tokens/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for orml_rate_limit. +pub trait WeightInfo { + fn update_rate_limit_rule() -> Weight; + fn add_whitelist() -> Weight; + fn remove_whitelist() -> Weight; + fn reset_whitelist() -> Weight; +} + +/// Default weights. +impl WeightInfo for () { + // Storage: RateLimit RateLimitRules (r:1 w:1) + // Storage: RateLimit RateLimitQuota (r:0 w:1) + fn update_rate_limit_rule() -> Weight { + // Minimum execution time: 31_000 nanoseconds. + Weight::from_ref_time(32_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: RateLimit BypassLimitWhitelist (r:1 w:1) + fn add_whitelist() -> Weight { + // Minimum execution time: 25_000 nanoseconds. + Weight::from_ref_time(27_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: RateLimit BypassLimitWhitelist (r:1 w:1) + fn remove_whitelist() -> Weight { + // Minimum execution time: 26_000 nanoseconds. + Weight::from_ref_time(27_000_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + // Storage: RateLimit BypassLimitWhitelist (r:0 w:1) + fn reset_whitelist() -> Weight { + // Minimum execution time: 22_000 nanoseconds. + Weight::from_ref_time(22_000_000) + .saturating_add(RocksDbWeight::get().writes(1)) + } +} diff --git a/traits/src/rate_limit.rs b/traits/src/rate_limit.rs index c63c30c19..e61f9dc40 100644 --- a/traits/src/rate_limit.rs +++ b/traits/src/rate_limit.rs @@ -1,6 +1,6 @@ use codec::Encode; use frame_support::{Parameter, RuntimeDebug}; -use sp_runtime::traits::{MaybeSerializeDeserialize, Member}; +use sp_runtime::traits::Member; #[derive(PartialEq, Eq, RuntimeDebug)] pub enum RateLimiterError { @@ -11,7 +11,7 @@ pub enum RateLimiterError { /// Rate Limiter pub trait RateLimiter { /// The type for the rate limiter. - type RateLimiterId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord; + type RateLimiterId: Parameter + Member + Copy; /// Check whether the rate limiter of can be bypassed according to the /// `key`. From 6839a5480946fa5c22f2cf31f881933fe80eaf1f Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Wed, 30 Nov 2022 17:16:10 +0800 Subject: [PATCH 07/10] update --- rate-limit/Cargo.toml | 2 + rate-limit/src/lib.rs | 139 +++++++++++++++++------------------ rate-limit/src/tests.rs | 150 ++++++++++++++++++++++---------------- rate-limit/src/weights.rs | 6 +- traits/src/rate_limit.rs | 4 +- xtokens/src/lib.rs | 11 ++- xtokens/src/mock/para.rs | 2 +- 7 files changed, 169 insertions(+), 145 deletions(-) diff --git a/rate-limit/Cargo.toml b/rate-limit/Cargo.toml index d6e5e9401..580ec17dc 100644 --- a/rate-limit/Cargo.toml +++ b/rate-limit/Cargo.toml @@ -20,6 +20,7 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", default-features sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.32" } orml-traits = { path = "../traits", version = "0.4.1-dev", default-features = false } +orml-utilities = { path = "../utilities", version = "0.4.1-dev", default-features = false } [dev-dependencies] pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.32" } @@ -38,6 +39,7 @@ std = [ "sp-runtime/std", "sp-std/std", "orml-traits/std", + "orml-utilities/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs index bd0fee6c0..6c3b51f62 100644 --- a/rate-limit/src/lib.rs +++ b/rate-limit/src/lib.rs @@ -15,9 +15,11 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::unused_unit)] +use codec::MaxEncodedLen; use frame_support::{pallet_prelude::*, traits::UnixTime, transactional, BoundedVec}; use frame_system::pallet_prelude::*; use orml_traits::{RateLimiter, RateLimiterError}; +use orml_utilities::OrderedSet; use scale_info::TypeInfo; use sp_runtime::traits::{SaturatedConversion, Zero}; use sp_std::{prelude::*, vec::Vec}; @@ -58,15 +60,18 @@ pub mod module { NotAllowed, } + /// The maximum length of KeyFilter inner key. + pub const MAX_FILTER_KEY_LENGTH: u32 = 256; + /// Match rules to fitler key is in bypass whitelist. - #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum KeyFilter { /// If the encoded key is equal to the vec, the key is in whitelist. - Match(Vec), + Match(BoundedVec>), /// If the encoded key starts with the vec, the key is in whitelist. - StartsWith(Vec), + StartsWith(BoundedVec>), /// If the encoded key ends with the vec, the key is in whitelist. - EndsWith(Vec), + EndsWith(BoundedVec>), } #[pallet::config] @@ -125,7 +130,7 @@ pub mod module { #[pallet::storage] #[pallet::getter(fn rate_limit_rules)] pub type RateLimitRules = - StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, RateLimitRule, OptionQuery>; + StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Blake2_128Concat, Vec, RateLimitRule, OptionQuery>; /// The quota for specific RateLimiterId and encoded key. /// @@ -134,15 +139,15 @@ pub mod module { #[pallet::storage] #[pallet::getter(fn rate_limit_quota)] pub type RateLimitQuota = - StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Twox64Concat, Vec, (u64, u128), ValueQuery>; + StorageDoubleMap<_, Twox64Concat, T::RateLimiterId, Blake2_128Concat, Vec, (u64, u128), ValueQuery>; /// The rules to filter if key is in whitelist for specific RateLimiterId. /// - /// BypassLimitWhitelist: map RateLimiterId => Vec + /// LimitWhitelist: map RateLimiterId => Vec #[pallet::storage] - #[pallet::getter(fn bypass_limit_whitelist)] - pub type BypassLimitWhitelist = - StorageMap<_, Twox64Concat, T::RateLimiterId, BoundedVec, ValueQuery>; + #[pallet::getter(fn limit_whitelist)] + pub type LimitWhitelist = + StorageMap<_, Twox64Concat, T::RateLimiterId, OrderedSet, ValueQuery>; #[pallet::pallet] #[pallet::without_storage_info] @@ -172,52 +177,49 @@ pub mod module { ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - RateLimitRules::::try_mutate_exists( - &rate_limiter_id, - encoded_key.clone(), - |maybe_limit| -> DispatchResult { - *maybe_limit = update.clone(); - - if let Some(rule) = maybe_limit { - match rule { - RateLimitRule::PerBlocks { blocks_count, quota } => { - ensure!( - !blocks_count.is_zero() && !quota.is_zero(), - Error::::InvalidRateLimitRule - ); - } - RateLimitRule::PerSeconds { secs_count, quota } => { - ensure!( - !secs_count.is_zero() && !quota.is_zero(), - Error::::InvalidRateLimitRule - ); - } - RateLimitRule::TokenBucket { - blocks_count, - quota_increment, - max_quota, - } => { - ensure!( - !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), - Error::::InvalidRateLimitRule - ); - } - _ => {} + RateLimitRules::::try_mutate_exists(&rate_limiter_id, &encoded_key, |maybe_limit| -> DispatchResult { + *maybe_limit = update.clone(); + + if let Some(rule) = maybe_limit { + match rule { + RateLimitRule::PerBlocks { blocks_count, quota } => { + ensure!( + !blocks_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimitRule + ); + } + RateLimitRule::PerSeconds { secs_count, quota } => { + ensure!( + !secs_count.is_zero() && !quota.is_zero(), + Error::::InvalidRateLimitRule + ); + } + RateLimitRule::TokenBucket { + blocks_count, + quota_increment, + max_quota, + } => { + ensure!( + !blocks_count.is_zero() && !quota_increment.is_zero() && !max_quota.is_zero(), + Error::::InvalidRateLimitRule + ); } + RateLimitRule::Unlimited => {} + RateLimitRule::NotAllowed => {} } + } - // always reset RateLimitQuota. - RateLimitQuota::::remove(&rate_limiter_id, &encoded_key); + // always reset RateLimitQuota. + RateLimitQuota::::remove(&rate_limiter_id, &encoded_key); - Self::deposit_event(Event::RateLimitRuleUpdated { - rate_limiter_id, - encoded_key, - update, - }); + Self::deposit_event(Event::RateLimitRuleUpdated { + rate_limiter_id, + encoded_key: encoded_key.clone(), + update, + }); - Ok(()) - }, - ) + Ok(()) + }) } /// Add whitelist filter rule. @@ -236,14 +238,10 @@ pub mod module { ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - BypassLimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { - let location = whitelist - .binary_search(&key_filter) - .err() - .ok_or(Error::::FilterExisted)?; - whitelist - .try_insert(location, key_filter) - .map_err(|_| Error::::MaxFilterExceeded)?; + LimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { + ensure!(!whitelist.contains(&key_filter), Error::::FilterExisted); + let inserted = whitelist.insert(key_filter); + ensure!(inserted, Error::::MaxFilterExceeded); Self::deposit_event(Event::WhitelistFilterAdded { rate_limiter_id }); Ok(()) @@ -266,12 +264,9 @@ pub mod module { ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - BypassLimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { - let location = whitelist - .binary_search(&key_filter) - .ok() - .ok_or(Error::::FilterExisted)?; - whitelist.remove(location); + LimitWhitelist::::try_mutate(rate_limiter_id, |whitelist| -> DispatchResult { + ensure!(whitelist.contains(&key_filter), Error::::FilterNotExisted); + whitelist.remove(&key_filter); Self::deposit_event(Event::WhitelistFilterRemoved { rate_limiter_id }); Ok(()) @@ -294,10 +289,10 @@ pub mod module { ) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; - let mut whitelist: BoundedVec = + let whitelist: BoundedVec = BoundedVec::try_from(new_list).map_err(|_| Error::::MaxFilterExceeded)?; - whitelist.sort(); - BypassLimitWhitelist::::insert(rate_limiter_id, whitelist); + let ordered_set: OrderedSet = whitelist.into(); + LimitWhitelist::::insert(rate_limiter_id, ordered_set); Self::deposit_event(Event::WhitelistFilterReset { rate_limiter_id }); Ok(()) @@ -364,13 +359,13 @@ pub mod module { impl RateLimiter for Pallet { type RateLimiterId = T::RateLimiterId; - fn bypass_limit(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool { + fn is_whitelist(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool { let encode_key: Vec = key.encode(); - for key_filter in BypassLimitWhitelist::::get(limiter_id) { + for key_filter in LimitWhitelist::::get(limiter_id).0 { match key_filter { - KeyFilter::Match(vec) => { - if encode_key == vec { + KeyFilter::Match(bounded_vec) => { + if encode_key == bounded_vec.into_inner() { return true; } } diff --git a/rate-limit/src/tests.rs b/rate-limit/src/tests.rs index 25a052cdc..7a398c154 100644 --- a/rate-limit/src/tests.rs +++ b/rate-limit/src/tests.rs @@ -112,52 +112,64 @@ fn update_rate_limit_rule_work() { fn add_whitelist_work() { ExtBuilder::default().build().execute_with(|| { assert_noop!( - RateLimit::add_whitelist(RuntimeOrigin::signed(ALICE), 0, KeyFilter::Match(ALICE.encode())), + RateLimit::add_whitelist( + RuntimeOrigin::signed(ALICE), + 0, + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + ), BadOrigin ); - assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + assert_eq!(RateLimit::limit_whitelist(0).0, vec![]); assert_ok!(RateLimit::add_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(ALICE.encode()) + KeyFilter::Match(ALICE.encode().try_into().unwrap()), )); System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterAdded { rate_limiter_id: 0, })); assert_eq!( - RateLimit::bypass_limit_whitelist(0), - vec![KeyFilter::Match(ALICE.encode())] + RateLimit::limit_whitelist(0).0, + vec![KeyFilter::Match(ALICE.encode().try_into().unwrap())] ); // add already existed. assert_noop!( - RateLimit::add_whitelist(RuntimeOrigin::root(), 0, KeyFilter::Match(ALICE.encode())), + RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(ALICE.encode().try_into().unwrap()) + ), Error::::FilterExisted ); assert_ok!(RateLimit::add_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(BOB.encode()) + KeyFilter::Match(BOB.encode().try_into().unwrap()) )); assert_ok!(RateLimit::add_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(CHARLIE.encode()) + KeyFilter::Match(CHARLIE.encode().try_into().unwrap()) )); assert_eq!( - RateLimit::bypass_limit_whitelist(0), + RateLimit::limit_whitelist(0).0, vec![ - KeyFilter::Match(ALICE.encode()), - KeyFilter::Match(BOB.encode()), - KeyFilter::Match(CHARLIE.encode()) + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(BOB.encode().try_into().unwrap()), + KeyFilter::Match(CHARLIE.encode().try_into().unwrap()) ] ); // exceed filters limit assert_noop!( - RateLimit::add_whitelist(RuntimeOrigin::root(), 0, KeyFilter::Match(DAVE.encode())), + RateLimit::add_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::Match(DAVE.encode().try_into().unwrap()) + ), Error::::MaxFilterExceeded ); }); @@ -169,39 +181,50 @@ fn remove_whitelist_work() { assert_ok!(RateLimit::add_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(ALICE.encode()) + KeyFilter::Match(ALICE.encode().try_into().unwrap()) )); assert_ok!(RateLimit::add_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(BOB.encode()) + KeyFilter::Match(BOB.encode().try_into().unwrap()) )); assert_eq!( - RateLimit::bypass_limit_whitelist(0), - vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + RateLimit::limit_whitelist(0).0, + vec![ + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(BOB.encode().try_into().unwrap()) + ] ); assert_noop!( - RateLimit::remove_whitelist(RuntimeOrigin::signed(ALICE), 0, KeyFilter::StartsWith(ALICE.encode())), + RateLimit::remove_whitelist( + RuntimeOrigin::signed(ALICE), + 0, + KeyFilter::StartsWith(ALICE.encode().try_into().unwrap()) + ), BadOrigin ); assert_noop!( - RateLimit::remove_whitelist(RuntimeOrigin::root(), 0, KeyFilter::StartsWith(ALICE.encode())), - Error::::FilterExisted + RateLimit::remove_whitelist( + RuntimeOrigin::root(), + 0, + KeyFilter::StartsWith(ALICE.encode().try_into().unwrap()) + ), + Error::::FilterNotExisted ); assert_ok!(RateLimit::remove_whitelist( RuntimeOrigin::root(), 0, - KeyFilter::Match(ALICE.encode()) + KeyFilter::Match(ALICE.encode().try_into().unwrap()) )); System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterRemoved { rate_limiter_id: 0, })); assert_eq!( - RateLimit::bypass_limit_whitelist(0), - vec![KeyFilter::Match(BOB.encode())] + RateLimit::limit_whitelist(0).0, + vec![KeyFilter::Match(BOB.encode().try_into().unwrap())] ); }); } @@ -213,7 +236,7 @@ fn reset_whitelist_work() { RateLimit::reset_whitelist( RuntimeOrigin::signed(ALICE), 0, - vec![KeyFilter::StartsWith(ALICE.encode())], + vec![KeyFilter::StartsWith(ALICE.encode().try_into().unwrap())], ), BadOrigin ); @@ -224,101 +247,106 @@ fn reset_whitelist_work() { RuntimeOrigin::root(), 0, vec![ - KeyFilter::StartsWith(ALICE.encode()), - KeyFilter::StartsWith(DAVE.encode()), - KeyFilter::StartsWith(CHARLIE.encode()), - KeyFilter::StartsWith(DAVE.encode()) + KeyFilter::StartsWith(ALICE.encode().try_into().unwrap()), + KeyFilter::StartsWith(DAVE.encode().try_into().unwrap()), + KeyFilter::StartsWith(CHARLIE.encode().try_into().unwrap()), + KeyFilter::StartsWith(DAVE.encode().try_into().unwrap()) ], ), Error::::MaxFilterExceeded ); - assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + assert_eq!(RateLimit::limit_whitelist(0).0, vec![]); assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, - vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + vec![ + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(BOB.encode().try_into().unwrap()) + ] )); System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterReset { rate_limiter_id: 0, })); assert_eq!( - RateLimit::bypass_limit_whitelist(0), - vec![KeyFilter::Match(ALICE.encode()), KeyFilter::Match(BOB.encode())] + RateLimit::limit_whitelist(0).0, + vec![ + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(BOB.encode().try_into().unwrap()) + ] ); - // will sort KeyFilter list before insert. + // will sort and dedup KeyFilter list before insert. assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, vec![ - KeyFilter::Match(CHARLIE.encode()), - KeyFilter::Match(BOB.encode()), - KeyFilter::Match(ALICE.encode()) + KeyFilter::Match(BOB.encode().try_into().unwrap()), + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(ALICE.encode().try_into().unwrap()), ] )); System::assert_last_event(RuntimeEvent::RateLimit(crate::Event::WhitelistFilterReset { rate_limiter_id: 0, })); assert_eq!( - RateLimit::bypass_limit_whitelist(0), + RateLimit::limit_whitelist(0).0, vec![ - KeyFilter::Match(ALICE.encode()), - KeyFilter::Match(BOB.encode()), - KeyFilter::Match(CHARLIE.encode()) + KeyFilter::Match(ALICE.encode().try_into().unwrap()), + KeyFilter::Match(BOB.encode().try_into().unwrap()), ] ); // clear assert_ok!(RateLimit::reset_whitelist(RuntimeOrigin::root(), 0, vec![])); - assert_eq!(RateLimit::bypass_limit_whitelist(0), vec![]); + assert_eq!(RateLimit::limit_whitelist(0).0, vec![]); }); } #[test] -fn bypass_limit_work() { +fn is_whitelist_work() { ExtBuilder::default().build().execute_with(|| { - assert_eq!(RateLimit::bypass_limit(0, BOB), false); - assert_eq!(RateLimit::bypass_limit(1, BOB), false); - assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); + assert_eq!(RateLimit::is_whitelist(0, BOB), false); + assert_eq!(RateLimit::is_whitelist(1, BOB), false); + assert_eq!(RateLimit::is_whitelist(0, TREASURY_ACCOUNT), false); assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, - vec![KeyFilter::Match(BOB.encode())] + vec![KeyFilter::Match(BOB.encode().try_into().unwrap())] )); - assert_eq!(RateLimit::bypass_limit(0, BOB), true); - assert_eq!(RateLimit::bypass_limit(1, BOB), false); - assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); + assert_eq!(RateLimit::is_whitelist(0, BOB), true); + assert_eq!(RateLimit::is_whitelist(1, BOB), false); + assert_eq!(RateLimit::is_whitelist(0, TREASURY_ACCOUNT), false); assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, - vec![KeyFilter::StartsWith(vec![1, 1, 1, 1])] + vec![KeyFilter::StartsWith(vec![1, 1, 1, 1].try_into().unwrap())] )); - assert_eq!(RateLimit::bypass_limit(0, BOB), true); - assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), true); + assert_eq!(RateLimit::is_whitelist(0, BOB), true); + assert_eq!(RateLimit::is_whitelist(0, TREASURY_ACCOUNT), true); assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, - vec![KeyFilter::StartsWith(vec![1, 1, 1, 1, 1])] + vec![KeyFilter::StartsWith(vec![1, 1, 1, 1, 1].try_into().unwrap())] )); - assert_eq!(RateLimit::bypass_limit(0, BOB), true); - assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), false); - assert_eq!(RateLimit::bypass_limit(0, CHARLIE), false); + assert_eq!(RateLimit::is_whitelist(0, BOB), true); + assert_eq!(RateLimit::is_whitelist(0, TREASURY_ACCOUNT), false); + assert_eq!(RateLimit::is_whitelist(0, CHARLIE), false); assert_ok!(RateLimit::reset_whitelist( RuntimeOrigin::root(), 0, vec![ - KeyFilter::StartsWith(vec![1, 1, 1, 1, 1]), - KeyFilter::EndsWith(vec![2, 2, 2, 2]) + KeyFilter::StartsWith(vec![1, 1, 1, 1, 1].try_into().unwrap()), + KeyFilter::EndsWith(vec![2, 2, 2, 2].try_into().unwrap()) ] )); - assert_eq!(RateLimit::bypass_limit(0, BOB), true); - assert_eq!(RateLimit::bypass_limit(0, TREASURY_ACCOUNT), true); - assert_eq!(RateLimit::bypass_limit(0, CHARLIE), true); + assert_eq!(RateLimit::is_whitelist(0, BOB), true); + assert_eq!(RateLimit::is_whitelist(0, TREASURY_ACCOUNT), true); + assert_eq!(RateLimit::is_whitelist(0, CHARLIE), true); }); } diff --git a/rate-limit/src/weights.rs b/rate-limit/src/weights.rs index f7670259e..b8343ea33 100644 --- a/rate-limit/src/weights.rs +++ b/rate-limit/src/weights.rs @@ -44,21 +44,21 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(2)) } - // Storage: RateLimit BypassLimitWhitelist (r:1 w:1) + // Storage: RateLimit LimitWhitelist (r:1 w:1) fn add_whitelist() -> Weight { // Minimum execution time: 25_000 nanoseconds. Weight::from_ref_time(27_000_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: RateLimit BypassLimitWhitelist (r:1 w:1) + // Storage: RateLimit LimitWhitelist (r:1 w:1) fn remove_whitelist() -> Weight { // Minimum execution time: 26_000 nanoseconds. Weight::from_ref_time(27_000_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } - // Storage: RateLimit BypassLimitWhitelist (r:0 w:1) + // Storage: RateLimit LimitWhitelist (r:0 w:1) fn reset_whitelist() -> Weight { // Minimum execution time: 22_000 nanoseconds. Weight::from_ref_time(22_000_000) diff --git a/traits/src/rate_limit.rs b/traits/src/rate_limit.rs index e61f9dc40..335a90cc4 100644 --- a/traits/src/rate_limit.rs +++ b/traits/src/rate_limit.rs @@ -15,7 +15,7 @@ pub trait RateLimiter { /// Check whether the rate limiter of can be bypassed according to the /// `key`. - fn bypass_limit(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool; + fn is_whitelist(limiter_id: Self::RateLimiterId, key: impl Encode) -> bool; /// Check whether the `value` can be passed the limit of `limit_key`. fn is_allowed(limiter_id: Self::RateLimiterId, limit_key: impl Encode, value: u128) @@ -28,7 +28,7 @@ pub trait RateLimiter { impl RateLimiter for () { type RateLimiterId = (); - fn bypass_limit(_: Self::RateLimiterId, _: impl Encode) -> bool { + fn is_whitelist(_: Self::RateLimiterId, _: impl Encode) -> bool { true } diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 69f7a9372..223697b98 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -32,20 +32,19 @@ use frame_support::{ Parameter, }; use frame_system::{ensure_signed, pallet_prelude::*}; +use orml_traits::{ + location::{Parse, Reserve}, + GetByKey, RateLimiter, XcmTransfer, +}; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member, Zero}, DispatchError, }; use sp_std::{prelude::*, result::Result}; - use xcm::{latest::Weight, prelude::*}; use xcm_executor::traits::{InvertLocation, WeightBounds}; pub use module::*; -use orml_traits::{ - location::{Parse, Reserve}, - GetByKey, RateLimiter, XcmTransfer, -}; mod mock; mod tests; @@ -546,7 +545,7 @@ pub mod module { let rate_limiter_id = T::RateLimiterId::get(); // check if the asset transfer from `who` can bypass the rate limiter. - if !T::RateLimiter::bypass_limit(rate_limiter_id, who.clone()) { + if !T::RateLimiter::is_whitelist(rate_limiter_id, &who) { // ensure the asset transfer be allowed by the rate limiter. T::RateLimiter::is_allowed(rate_limiter_id, asset.id.clone(), amount) .map_err(|_| Error::::RateLimited)?; diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index f4e461d4a..4dd31110d 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -249,7 +249,7 @@ pub struct MockRateLimiter; impl RateLimiter for MockRateLimiter { type RateLimiterId = u8; - fn bypass_limit(_: Self::RateLimiterId, key: impl Encode) -> bool { + fn is_whitelist(_: Self::RateLimiterId, key: impl Encode) -> bool { let encoded_charlie = CHARLIE.encode(); let encoded_key: Vec = key.encode(); encoded_key != encoded_charlie From 3a22b8fbe2b35db75d3fa18b7c8720cf4481bd3c Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Wed, 4 Jan 2023 10:36:31 +0800 Subject: [PATCH 08/10] use explicit call index --- rate-limit/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rate-limit/src/lib.rs b/rate-limit/src/lib.rs index 6c3b51f62..19cd1c818 100644 --- a/rate-limit/src/lib.rs +++ b/rate-limit/src/lib.rs @@ -167,6 +167,7 @@ pub mod module { /// - `encoded key`: the encoded key to limit. /// - `update`: the RateLimitRule to config, None will remove current /// config. + #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::update_rate_limit_rule())] #[transactional] pub fn update_rate_limit_rule( @@ -229,6 +230,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `key_filter`: filter rule to add. + #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::add_whitelist())] #[transactional] pub fn add_whitelist( @@ -255,6 +257,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `key_filter`: filter rule to remove. + #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::remove_whitelist())] #[transactional] pub fn remove_whitelist( @@ -280,6 +283,7 @@ pub mod module { /// Parameters: /// - `rate_limiter_id`: rate limiter id. /// - `new_list`: the filter rule list to reset. + #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::reset_whitelist())] #[transactional] pub fn reset_whitelist( From 175792bd5e06cf64e17762df037e15a4e6234d49 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Sat, 25 Nov 2023 19:45:17 +0800 Subject: [PATCH 09/10] fix features --- rate-limit/Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rate-limit/Cargo.toml b/rate-limit/Cargo.toml index d14a54846..c05931ea3 100644 --- a/rate-limit/Cargo.toml +++ b/rate-limit/Cargo.toml @@ -3,7 +3,7 @@ name = "orml-rate-limit" description = "Provides way to config rate limiter for limit some operation." repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/rate-limit" license = "Apache-2.0" -version = "0.6.1-dev" +version = "0.6.7" authors = ["Laminar Developers "] edition = "2021" @@ -26,20 +26,19 @@ orml-utilities = { path = "../utilities", version = "0.6.1-dev", default-feature pallet-timestamp = { workspace = true, features = ["std"] } [features] -default = ["std"] +default = [ "std" ] std = [ - "serde", - - "parity-scale-codec/std", "frame-support/std", "frame-system/std", + "orml-traits/std", + "orml-utilities/std", + "parity-scale-codec/std", "scale-info/std", + "serde", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "orml-traits/std", - "orml-utilities/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", @@ -49,4 +48,5 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", + "sp-runtime/try-runtime", ] From b1112723a0556d099a8c7e4914b73162e167658f Mon Sep 17 00:00:00 2001 From: zjb0807 Date: Wed, 13 Dec 2023 10:19:13 +0800 Subject: [PATCH 10/10] update Cargo.toml --- rate-limit/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rate-limit/Cargo.toml b/rate-limit/Cargo.toml index c05931ea3..e93e903b5 100644 --- a/rate-limit/Cargo.toml +++ b/rate-limit/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" [dependencies] parity-scale-codec = { workspace = true } -scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +scale-info = { workspace = true } serde = { workspace = true, optional = true } frame-support = { workspace = true } @@ -19,8 +19,8 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -orml-traits = { path = "../traits", version = "0.6.1-dev", default-features = false } -orml-utilities = { path = "../utilities", version = "0.6.1-dev", default-features = false } +orml-traits = { path = "../traits", version = "0.6.7", default-features = false } +orml-utilities = { path = "../utilities", version = "0.6.7", default-features = false } [dev-dependencies] pallet-timestamp = { workspace = true, features = ["std"] }