diff --git a/Cargo.lock b/Cargo.lock index 58297393c834d..e1be23a0b71a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5005,6 +5005,7 @@ dependencies = [ "pallet-uniques", "pallet-utility", "pallet-vesting", + "pallet-whitelist", "parity-scale-codec", "scale-info", "sp-api", @@ -6599,6 +6600,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-whitelist" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-preimage", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "parity-db" version = "0.3.5" @@ -11312,7 +11331,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.2", "rand 0.8.4", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index d919fc4805631..1f2bbb8e9607d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,6 +138,7 @@ members = [ "frame/uniques", "frame/utility", "frame/vesting", + "frame/whitelist", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 0055230295a1e..39c4609c4425f 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -100,6 +100,7 @@ pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, pat pallet-transaction-storage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-storage" } pallet-uniques = { version = "4.0.0-dev", default-features = false, path = "../../../frame/uniques" } pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/vesting" } +pallet-whitelist = { version = "4.0.0-dev", default-features = false, path = "../../../frame/whitelist" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } @@ -220,6 +221,7 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", + "pallet-whitelist/runtime-benchmarks", "frame-system-benchmarking", "hex-literal", ] @@ -267,6 +269,7 @@ try-runtime = [ "pallet-uniques/try-runtime", "pallet-utility/try-runtime", "pallet-vesting/try-runtime", + "pallet-whitelist/try-runtime", ] # Make contract callable functions marked as __unstable__ available. Do not enable # on live chains as those are subject to change. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0aff3d8046eef..9ff19760c775f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1357,6 +1357,15 @@ impl pallet_transaction_storage::Config for Runtime { type WeightInfo = pallet_transaction_storage::weights::SubstrateWeight; } +impl pallet_whitelist::Config for Runtime { + type Event = Event; + type Call = Call; + type WhitelistOrigin = EnsureRoot; + type DispatchWhitelistedOrigin = EnsureRoot; + type PreimageProvider = Preimage; + type WeightInfo = pallet_whitelist::weights::SubstrateWeight; +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -1411,6 +1420,7 @@ construct_runtime!( ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, ConvictionVoting: pallet_conviction_voting, + Whitelist: pallet_whitelist, } ); @@ -1509,6 +1519,7 @@ mod benches { [pallet_uniques, Uniques] [pallet_utility, Utility] [pallet_vesting, Vesting] + [pallet_whitelist, Whitelist] ); } diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 039fdb07caabc..7e99302574354 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-conviction-voting" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index a3067956db2b0..56598af91393e 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-referenda" version = "4.0.0-dev" authors = ["Parity Technologies "] -edition = "2018" +edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml new file mode 100644 index 0000000000000..057e7fd71f307 --- /dev/null +++ b/frame/whitelist/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-whitelist" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for whitelisting call, and dispatch from specific origin" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "5.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "5.0.0", default-features = false, path = "../../primitives/runtime" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +sp-core = { version = "5.0.0", path = "../../primitives/core" } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage/" } +pallet-balances = { version = "4.0.0-dev", path = "../balances/" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-std/std", + "sp-io/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs new file mode 100644 index 0000000000000..c51ea0a0a3246 --- /dev/null +++ b/frame/whitelist/src/benchmarking.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Whitelist pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use core::convert::TryInto; +use frame_benchmarking::benchmarks; +use frame_support::{ensure, traits::PreimageRecipient}; +use sp_runtime::traits::Hash; + +#[cfg(test)] +use crate::Pallet as Whitelist; + +benchmarks! { + whitelist_call { + let origin = T::WhitelistOrigin::successful_origin(); + let call_hash = Default::default(); + }: _(origin, call_hash) + verify { + ensure!( + WhitelistedCall::::contains_key(call_hash), + "call not whitelisted" + ); + ensure!( + T::PreimageProvider::preimage_requested(&call_hash), + "preimage not requested" + ); + } + + remove_whitelisted_call { + let origin = T::WhitelistOrigin::successful_origin(); + let call_hash = Default::default(); + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + }: _(origin, call_hash) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::PreimageProvider::preimage_requested(&call_hash), + "preimage still requested" + ); + } + + // We benchmark with the maximum possible size for a call. + // If the resulting weight is too big, maybe it worth having a weight which depends + // on the size of the call, with a new witness in parameter. + dispatch_whitelisted_call { + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + // NOTE: we remove `10` because we need some bytes to encode the variants and vec length + let remark_len = >::MaxSize::get() - 10; + let remark = sp_std::vec![1_8; remark_len as usize]; + + let call: ::Call = frame_system::Call::remark { remark }.into(); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_hash = T::Hashing::hash(&encoded_call[..]); + + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + + let encoded_call = encoded_call.try_into().expect("encoded_call must be small enough"); + T::PreimageProvider::note_preimage(encoded_call); + + }: _(origin, call_hash, call_weight) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::PreimageProvider::preimage_requested(&call_hash), + "preimage still requested" + ); + } + + dispatch_whitelisted_call_with_preimage { + let n in 1 .. 10_000; + + let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let remark = sp_std::vec![1u8; n as usize]; + + let call: ::Call = frame_system::Call::remark { remark }.into(); + let call_hash = T::Hashing::hash_of(&call); + + Pallet::::whitelist_call(origin.clone(), call_hash) + .expect("whitelisting call must be successful"); + }: _(origin, Box::new(call)) + verify { + ensure!( + !WhitelistedCall::::contains_key(call_hash), + "whitelist not removed" + ); + ensure!( + !T::PreimageProvider::preimage_requested(&call_hash), + "preimage still requested" + ); + } + + impl_benchmark_test_suite!(Whitelist, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs new file mode 100644 index 0000000000000..9f20386da85f5 --- /dev/null +++ b/frame/whitelist/src/lib.rs @@ -0,0 +1,244 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Whitelist Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! Allow some configurable origin: [`Config::WhitelistOrigin`] to whitelist some hash of a call, +//! and allow another configurable origin: [`Config::DispatchWhitelistedOrigin`] to dispatch them +//! with the root origin. +//! +//! In the meantime the call corresponding to the hash must have been submitted to the to the +//! pre-image handler [`PreimageProvider`]. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +use sp_runtime::traits::Dispatchable; +use sp_std::prelude::*; + +use codec::{Decode, DecodeLimit, Encode, FullCodec, MaxEncodedLen}; +use frame_support::{ + ensure, + traits::{PreimageProvider, PreimageRecipient}, + weights::{GetDispatchInfo, PostDispatchInfo}, +}; +use scale_info::TypeInfo; +use sp_api::HashT; +use weights::WeightInfo; + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub use pallet::*; + +#[derive(Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct Preimage { + preimage: BoundedVec, + deposit: Option<(AccountId, Balance)>, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The overarching call type. + type Call: IsType<::Call> + + Dispatchable + + GetDispatchInfo + + FullCodec + + TypeInfo + + From> + + Parameter; + + /// Required origin for whitelisting a call. + type WhitelistOrigin: EnsureOrigin; + + /// Required origin for dispatching whitelisted call with root origin. + type DispatchWhitelistedOrigin: EnsureOrigin; + + /// The handler of pre-images. + // NOTE: recipient is only needed for benchmarks. + type PreimageProvider: PreimageProvider + PreimageRecipient; + + /// The weight information for this pallet. + type WeightInfo: weights::WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + CallWhitelisted { call_hash: T::Hash }, + WhitelistedCallRemoved { call_hash: T::Hash }, + WhitelistedCallDispatched { call_hash: T::Hash, result: DispatchResultWithPostInfo }, + } + + #[pallet::error] + pub enum Error { + /// The preimage of the call hash could not be loaded. + UnavailablePreImage, + /// The call could not be decoded. + UndecodableCall, + /// The weight of the decoded call was higher than the witness. + InvalidCallWeightWitness, + /// The call was not whitelisted. + CallIsNotWhitelisted, + /// The call was already whitelisted; No-Op. + CallAlreadyWhitelisted, + } + + #[pallet::storage] + pub type WhitelistedCall = StorageMap<_, Twox64Concat, T::Hash, (), OptionQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::weight(T::WeightInfo::whitelist_call())] + pub fn whitelist_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + T::WhitelistOrigin::ensure_origin(origin)?; + + ensure!( + !WhitelistedCall::::contains_key(call_hash), + Error::::CallAlreadyWhitelisted, + ); + + WhitelistedCall::::insert(call_hash, ()); + T::PreimageProvider::request_preimage(&call_hash); + + Self::deposit_event(Event::::CallWhitelisted { call_hash }); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::remove_whitelisted_call())] + pub fn remove_whitelisted_call(origin: OriginFor, call_hash: T::Hash) -> DispatchResult { + T::WhitelistOrigin::ensure_origin(origin)?; + + WhitelistedCall::::take(call_hash).ok_or(Error::::CallIsNotWhitelisted)?; + + T::PreimageProvider::unrequest_preimage(&call_hash); + + Self::deposit_event(Event::::WhitelistedCallRemoved { call_hash }); + + Ok(()) + } + + #[pallet::weight( + T::WeightInfo::dispatch_whitelisted_call().saturating_add(*call_weight_witness) + )] + pub fn dispatch_whitelisted_call( + origin: OriginFor, + call_hash: T::Hash, + call_weight_witness: Weight, + ) -> DispatchResultWithPostInfo { + T::DispatchWhitelistedOrigin::ensure_origin(origin)?; + + ensure!( + WhitelistedCall::::contains_key(call_hash), + Error::::CallIsNotWhitelisted, + ); + + let call = T::PreimageProvider::get_preimage(&call_hash) + .ok_or(Error::::UnavailablePreImage)?; + + let call = ::Call::decode_all_with_depth_limit( + sp_api::MAX_EXTRINSIC_DEPTH, + &mut &call[..], + ) + .map_err(|_| Error::::UndecodableCall)?; + + ensure!( + call.get_dispatch_info().weight <= call_weight_witness, + Error::::InvalidCallWeightWitness + ); + + let actual_weight = Self::clean_and_dispatch(call_hash, call) + .map(|w| w.saturating_add(T::WeightInfo::dispatch_whitelisted_call())); + + Ok(actual_weight.into()) + } + + #[pallet::weight({ + let call_weight = call.get_dispatch_info().weight; + let call_len = call.encoded_size() as u32; + + T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len) + .saturating_add(call_weight) + })] + pub fn dispatch_whitelisted_call_with_preimage( + origin: OriginFor, + call: Box<::Call>, + ) -> DispatchResultWithPostInfo { + T::DispatchWhitelistedOrigin::ensure_origin(origin)?; + + let call_hash = ::Hashing::hash_of(&call); + + ensure!( + WhitelistedCall::::contains_key(call_hash), + Error::::CallIsNotWhitelisted, + ); + + let call_len = call.encoded_size() as u32; + let actual_weight = Self::clean_and_dispatch(call_hash, *call).map(|w| { + w.saturating_add(T::WeightInfo::dispatch_whitelisted_call_with_preimage(call_len)) + }); + + Ok(actual_weight.into()) + } + } +} + +impl Pallet { + /// Clean whitelisting/preimage and dispatch call. + /// + /// Return the call actual weight of the dispatched call if there is some. + fn clean_and_dispatch(call_hash: T::Hash, call: ::Call) -> Option { + WhitelistedCall::::remove(call_hash); + + T::PreimageProvider::unrequest_preimage(&call_hash); + + let result = call.dispatch(frame_system::Origin::::Root.into()); + + let call_actual_weight = match result { + Ok(call_post_info) => call_post_info.actual_weight, + Err(call_err) => call_err.post_info.actual_weight, + }; + + Self::deposit_event(Event::::WhitelistedCallDispatched { call_hash, result }); + + call_actual_weight + } +} diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs new file mode 100644 index 0000000000000..3009a6f6b5d58 --- /dev/null +++ b/frame/whitelist/src/mock.rs @@ -0,0 +1,124 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Mock for Whitelist Pallet + +#![cfg(test)] + +use crate as pallet_whitelist; + +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, Nothing}, +}; +use frame_system::EnsureRoot; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Whitelist: pallet_whitelist, + Preimage: pallet_preimage, + } +); + +frame_support::parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); +} +impl frame_system::Config for Test { + type BaseCallFilter = Nothing; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = Call; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); +} + +parameter_types! { + // Taken from Polkadot as reference. + pub const PreimageMaxSize: u32 = 4096 * 1024; +} + +impl pallet_preimage::Config for Test { + type Event = Event; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type MaxSize = PreimageMaxSize; + type BaseDeposit = ConstU64<1>; + type ByteDeposit = ConstU64<1>; + type WeightInfo = (); +} + +impl pallet_whitelist::Config for Test { + type Event = Event; + type Call = Call; + type WhitelistOrigin = EnsureRoot; + type DispatchWhitelistedOrigin = EnsureRoot; + type PreimageProvider = Preimage; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = 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/frame/whitelist/src/tests.rs b/frame/whitelist/src/tests.rs new file mode 100644 index 0000000000000..67bccaeaeebe1 --- /dev/null +++ b/frame/whitelist/src/tests.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for Whitelist Pallet + +use crate::mock::*; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, dispatch::GetDispatchInfo, traits::PreimageProvider}; +use sp_runtime::{traits::Hash, DispatchError}; + +#[test] +fn test_whitelist_call_and_remove() { + new_test_ext().execute_with(|| { + let call = Call::System(frame_system::Call::remark { remark: vec![] }); + let encoded_call = call.encode(); + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_noop!( + Whitelist::remove_whitelisted_call(Origin::root(), call_hash), + crate::Error::::CallIsNotWhitelisted, + ); + + assert_noop!( + Whitelist::whitelist_call(Origin::signed(1), call_hash), + DispatchError::BadOrigin, + ); + + assert_ok!(Whitelist::whitelist_call(Origin::root(), call_hash)); + + assert!(Preimage::preimage_requested(&call_hash)); + + assert_noop!( + Whitelist::whitelist_call(Origin::root(), call_hash), + crate::Error::::CallAlreadyWhitelisted, + ); + + assert_noop!( + Whitelist::remove_whitelisted_call(Origin::signed(1), call_hash), + DispatchError::BadOrigin, + ); + + assert_ok!(Whitelist::remove_whitelisted_call(Origin::root(), call_hash)); + + assert!(!Preimage::preimage_requested(&call_hash)); + + assert_noop!( + Whitelist::remove_whitelisted_call(Origin::root(), call_hash), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute() { + new_test_ext().execute_with(|| { + let call = Call::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight), + crate::Error::::CallIsNotWhitelisted, + ); + + assert_ok!(Whitelist::whitelist_call(Origin::root(), call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::signed(1), call_hash, call_weight), + DispatchError::BadOrigin, + ); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight), + crate::Error::::UnavailablePreImage, + ); + + assert_ok!(Preimage::note_preimage(Origin::root(), encoded_call)); + + assert!(Preimage::preimage_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight - 1), + crate::Error::::InvalidCallWeightWitness, + ); + + assert_ok!(Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight)); + + assert!(!Preimage::preimage_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute_failing_call() { + new_test_ext().execute_with(|| { + let call = Call::Whitelist(crate::Call::dispatch_whitelisted_call { + call_hash: Default::default(), + call_weight_witness: 0, + }); + let call_weight = call.get_dispatch_info().weight; + let encoded_call = call.encode(); + let call_hash = ::Hashing::hash(&encoded_call[..]); + + assert_ok!(Whitelist::whitelist_call(Origin::root(), call_hash)); + assert_ok!(Preimage::note_preimage(Origin::root(), encoded_call)); + assert!(Preimage::preimage_requested(&call_hash)); + assert_ok!(Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight)); + assert!(!Preimage::preimage_requested(&call_hash)); + }); +} + +#[test] +fn test_whitelist_call_and_execute_without_note_preimage() { + new_test_ext().execute_with(|| { + let call = + Box::new(Call::System(frame_system::Call::remark_with_event { remark: vec![1] })); + let call_hash = ::Hashing::hash_of(&call); + + assert_ok!(Whitelist::whitelist_call(Origin::root(), call_hash)); + assert!(Preimage::preimage_requested(&call_hash)); + + assert_ok!(Whitelist::dispatch_whitelisted_call_with_preimage( + Origin::root(), + call.clone() + )); + + assert!(!Preimage::preimage_requested(&call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call_with_preimage(Origin::root(), call), + crate::Error::::CallIsNotWhitelisted, + ); + }); +} + +#[test] +fn test_whitelist_call_and_execute_decode_consumes_all() { + new_test_ext().execute_with(|| { + let call = Call::System(frame_system::Call::remark_with_event { remark: vec![1] }); + let call_weight = call.get_dispatch_info().weight; + let mut call = call.encode(); + // Appending something does not make the encoded call invalid. + // This tests that the decode function consumes all data. + call.extend(call.clone()); + + let call_hash = ::Hashing::hash(&call[..]); + + assert_ok!(Preimage::note_preimage(Origin::root(), call)); + assert_ok!(Whitelist::whitelist_call(Origin::root(), call_hash)); + + assert_noop!( + Whitelist::dispatch_whitelisted_call(Origin::root(), call_hash, call_weight), + crate::Error::::UndecodableCall, + ); + }); +} diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs new file mode 100644 index 0000000000000..fdba734db64ba --- /dev/null +++ b/frame/whitelist/src/weights.rs @@ -0,0 +1,126 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_whitelist +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-02-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_whitelist +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/whitelist/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_whitelist. +pub trait WeightInfo { + fn whitelist_call() -> Weight; + fn remove_whitelisted_call() -> Weight; + fn dispatch_whitelisted_call() -> Weight; + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight; +} + +/// Weights for pallet_whitelist using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn whitelist_call() -> Weight { + (16_254_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn remove_whitelisted_call() -> Weight { + (18_348_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn dispatch_whitelisted_call() -> Weight { + (6_618_241_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { + (20_619_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn whitelist_call() -> Weight { + (16_254_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn remove_whitelisted_call() -> Weight { + (18_348_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage PreimageFor (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + fn dispatch_whitelisted_call() -> Weight { + (6_618_241_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Whitelist WhitelistedCall (r:1 w:1) + // Storage: Preimage StatusFor (r:1 w:1) + // Storage: Preimage PreimageFor (r:0 w:1) + fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { + (20_619_000 as Weight) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 6d32d2322c765..a428da59f6a0d 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -511,7 +511,7 @@ pub enum DispatchError { /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about /// the `Dispatchable` that is only known post dispatch. -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug)] +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct DispatchErrorWithPostInfo where Info: Eq + PartialEq + Clone + Copy + Encode + Decode + traits::Printable,