diff --git a/Cargo.lock b/Cargo.lock index 3d5eccf3f72bf..9edd56a6e356d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5770,11 +5770,13 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", "frame-support", + "frame-system", "hex-literal", "pallet-assets", "pallet-balances", "pallet-bridge-messages", "pallet-message-queue", + "pallet-whitelist", "pallet-xcm", "pallet-xcm-bridge-hub", "parachains-common", @@ -7375,6 +7377,25 @@ dependencies = [ "testnet-parachains-constants", ] +[[package]] +name = "governance-westend-integration-tests" +version = "0.0.0" +dependencies = [ + "collectives-westend-runtime", + "emulated-integration-tests-common", + "frame-support", + "frame-system", + "pallet-utility", + "pallet-whitelist", + "pallet-xcm", + "parity-scale-codec", + "sp-core 36.1.0", + "sp-runtime 41.1.0", + "staging-xcm", + "westend-runtime", + "westend-system-emulated-network", +] + [[package]] name = "governor" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 5ab4876c8965f..b52a9897c3261 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ members = [ "cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend", "cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo", "cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend", + "cumulus/parachains/integration-tests/emulated/tests/governance/westend", "cumulus/parachains/integration-tests/emulated/tests/people/people-rococo", "cumulus/parachains/integration-tests/emulated/tests/people/people-westend", "cumulus/parachains/pallets/collective-content", diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 481816787c4fb..1041d0bf22830 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -27,6 +27,8 @@ cumulus-primitives-core.default-features = true cumulus-primitives-core.workspace = true frame-support.default-features = true frame-support.workspace = true +frame-system.default-features = true +frame-system.workspace = true hex-literal = { workspace = true } pallet-assets.default-features = true pallet-assets.workspace = true @@ -36,6 +38,8 @@ pallet-bridge-messages.default-features = true pallet-bridge-messages.workspace = true pallet-message-queue.default-features = true pallet-message-queue.workspace = true +pallet-whitelist.default-features = true +pallet-whitelist.workspace = true pallet-xcm-bridge-hub.default-features = true pallet-xcm-bridge-hub.workspace = true parachains-common.default-features = true diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index de969d665e8fa..8bf403d44291f 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -20,6 +20,7 @@ pub use frame_support::{pallet_prelude::Weight, weights::WeightToFee}; pub use pallet_assets; pub use pallet_balances; pub use pallet_message_queue; +pub use pallet_whitelist; pub use pallet_xcm; // Polkadot @@ -37,7 +38,7 @@ pub use xcm::{ pub use asset_test_utils; pub use cumulus_pallet_xcmp_queue; pub use parachains_common::AccountId; -pub use xcm_emulator::Chain; +pub use xcm_emulator::{assert_expected_events, Chain}; #[macro_export] macro_rules! test_parachain_is_trusted_teleporter { @@ -822,3 +823,18 @@ macro_rules! test_cross_chain_alias { } }; } + +#[macro_export] +macro_rules! assert_whitelisted { + ($chain:ident, $expected_call_hash:expr) => { + type RuntimeEvent = <$chain as $crate::macros::Chain>::RuntimeEvent; + $crate::macros::assert_expected_events!( + $chain, + vec![ + RuntimeEvent::Whitelist($crate::macros::pallet_whitelist::Event::CallWhitelisted { call_hash }) => { + call_hash: *call_hash == $expected_call_hash, + }, + ] + ); + }; +} diff --git a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs index 309a62ec1e12f..a990e70434b1e 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -21,6 +21,11 @@ use sp_core::H256; use xcm::{prelude::*, DoubleEncoded}; use xcm_emulator::Chain; +use crate::impls::{bx, Encode}; +use frame_support::dispatch::{DispatchResultWithPostInfo, PostDispatchInfo}; +use sp_runtime::traits::{Dispatchable, Hash}; +use xcm::{VersionedLocation, VersionedXcm}; + /// Helper method to build a XCM with a `Transact` instruction and paying for its execution pub fn xcm_transact_paid_execution( call: DoubleEncoded<()>, @@ -112,3 +117,68 @@ where { pallet_xcm::xcm_helpers::find_xcm_sent_message_id::<::Runtime>(C::events()) } + +/// Wraps a runtime call in a whitelist preimage call and dispatches it +pub fn dispatch_whitelisted_call_with_preimage( + call: T::RuntimeCall, + origin: T::RuntimeOrigin, +) -> DispatchResultWithPostInfo +where + T: Chain, + T::Runtime: pallet_whitelist::Config, + T::RuntimeCall: From> + + Into<::RuntimeCall> + + Dispatchable, +{ + T::execute_with(|| { + let whitelist_call: T::RuntimeCall = + pallet_whitelist::Call::::dispatch_whitelisted_call_with_preimage { + call: Box::new(call.into()), + } + .into(); + whitelist_call.dispatch(origin) + }) +} + +/// Builds a `pallet_xcm::send` call to authorize an upgrade at the provided location, +/// wrapped in an unpaid XCM `Transact` with `OriginKind::Superuser`. +pub fn build_xcm_send_authorize_upgrade_call( + location: Location, + code_hash: &H256, + fallback_max_weight: Option, +) -> T::RuntimeCall +where + T: Chain, + T::Runtime: pallet_xcm::Config, + T::RuntimeCall: Encode + From>, + D: Chain, + D::Runtime: frame_system::Config, + D::RuntimeCall: Encode + From>, +{ + let transact_call: D::RuntimeCall = + frame_system::Call::authorize_upgrade { code_hash: *code_hash }.into(); + + let call: T::RuntimeCall = pallet_xcm::Call::send { + dest: bx!(VersionedLocation::from(location)), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Superuser, + fallback_max_weight, + call: transact_call.encode().into(), + } + ]))), + } + .into(); + call +} + +/// Encodes a runtime call and returns its H256 hash +pub fn call_hash_of(call: &T::RuntimeCall) -> H256 +where + T: Chain, + T::Runtime: frame_system::Config, + T::RuntimeCall: Encode, +{ + ::Hashing::hash_of(&call) +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/governance/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/Cargo.toml new file mode 100644 index 0000000000000..fe57924de231c --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "governance-westend-integration-tests" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Westend governance integration tests with xcm-emulator" +publish = false + +[lints] +workspace = true + +[dependencies] +codec = { workspace = true, default-features = true } + +# Substrate +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +pallet-whitelist = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } + +pallet-xcm = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } + +emulated-integration-tests-common = { workspace = true } + +# Local +collectives-westend-runtime = { workspace = true } +westend-runtime = { workspace = true } +westend-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/common.rs b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/common.rs new file mode 100644 index 0000000000000..b0e01c03b189b --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/common.rs @@ -0,0 +1,54 @@ +// Copyright (C) Parity Technologies and the various Polkadot contributors, see Contributions.md +// for a list of specific contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; + +/// CollectivesWestend dispatches `pallet_xcm::send` with `OriginKind:Xcm` to the dest with encoded +/// whitelist call. +#[cfg(test)] +pub fn collectives_send_whitelist( + dest: Location, + encoded_whitelist_call: impl FnOnce() -> Vec, +) { + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + + let send_whitelist_call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(dest)), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: None, + call: encoded_whitelist_call().into(), + } + ]))), + }); + + use collectives_westend_runtime::fellowship::pallet_fellowship_origins::Origin::Fellows as FellowsOrigin; + let fellows_origin: RuntimeOrigin = FellowsOrigin.into(); + assert_ok!(send_whitelist_call.dispatch(fellows_origin)); + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/lib.rs new file mode 100644 index 0000000000000..1c4aa01baccab --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/lib.rs @@ -0,0 +1,33 @@ +// Copyright (C) Parity Technologies and the various Polkadot contributors, see Contributions.md +// for a list of specific contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(test)] +mod imports { + pub(crate) use emulated_integration_tests_common::{ + impls::{assert_expected_events, bx, TestExt}, + xcm_emulator::Chain, + }; + pub(crate) use frame_support::assert_ok; + pub(crate) use sp_runtime::traits::Dispatchable; + pub(crate) use westend_system_emulated_network::CollectivesWestendPara as CollectivesWestend; + pub(crate) use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; +} + +#[cfg(test)] +mod common; + +#[cfg(test)] +mod open_gov_on_relay; diff --git a/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/open_gov_on_relay.rs b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/open_gov_on_relay.rs new file mode 100644 index 0000000000000..c891578e6ee84 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/governance/westend/src/open_gov_on_relay.rs @@ -0,0 +1,247 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{common::*, imports::*}; +use codec::Encode; +use emulated_integration_tests_common::{ + assert_whitelisted, + impls::RelayChain, + xcm_emulator::{Chain, Parachain, TestExt}, + xcm_helpers::{ + build_xcm_send_authorize_upgrade_call, call_hash_of, + dispatch_whitelisted_call_with_preimage, + }, +}; +use frame_support::assert_err; +use sp_runtime::DispatchError; +use westend_runtime::governance::pallet_custom_origins::Origin; +use westend_system_emulated_network::{ + AssetHubWestendPara as AssetHubWestend, BridgeHubWestendPara as BridgeHubWestend, + CoretimeWestendPara as CoretimeWestend, PeopleWestendPara as PeopleWestend, + WestendRelay as Westend, +}; + +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; + +#[test] +fn relaychain_can_authorize_upgrade_for_itself() { + let code_hash = [1u8; 32].into(); + type WestendRuntime = ::Runtime; + type WestendRuntimeCall = ::RuntimeCall; + type WestendRuntimeOrigin = ::RuntimeOrigin; + + let authorize_upgrade = + WestendRuntimeCall::Utility(pallet_utility::Call::::force_batch { + calls: vec![ + // upgrade the relaychain + WestendRuntimeCall::System(frame_system::Call::authorize_upgrade { code_hash }), + ], + }); + + // bad origin + let invalid_origin: WestendRuntimeOrigin = Origin::StakingAdmin.into(); + // ok origin + let ok_origin: WestendRuntimeOrigin = Origin::WhitelistedCaller.into(); + + let call_hash = call_hash_of::(&authorize_upgrade); + + // Err - when dispatch non-whitelisted + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + ok_origin.clone() + ), + DispatchError::Module(sp_runtime::ModuleError { + index: 36, + error: [3, 0, 0, 0], + message: Some("CallIsNotWhitelisted") + }) + ); + + // whitelist + collectives_send_whitelist(Location::parent(), || { + WestendRuntimeCall::Whitelist(pallet_whitelist::Call::::whitelist_call { + call_hash, + }) + .encode() + }); + + // Err - when dispatch wrong origin + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + invalid_origin + ), + DispatchError::BadOrigin + ); + + // check before + Westend::execute_with(|| assert!(::System::authorized_upgrade().is_none())); + + // ok - authorized + assert_ok!(dispatch_whitelisted_call_with_preimage::(authorize_upgrade, ok_origin)); + + // check after - authorized + Westend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash + ) + }); +} + +#[test] +fn relaychain_can_authorize_upgrade_for_system_chains() { + type WestendRuntime = ::Runtime; + type WestendRuntimeCall = ::RuntimeCall; + type WestendRuntimeOrigin = ::RuntimeOrigin; + + Westend::execute_with(|| { + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + Dmp::make_parachain_reachable(BridgeHubWestend::para_id()); + Dmp::make_parachain_reachable(CollectivesWestend::para_id()); + Dmp::make_parachain_reachable(CoretimeWestend::para_id()); + Dmp::make_parachain_reachable(PeopleWestend::para_id()); + }); + + let code_hash_asset_hub = [1u8; 32].into(); + let code_hash_bridge_hub = [2u8; 32].into(); + let code_hash_collectives = [3u8; 32].into(); + let code_hash_coretime = [4u8; 32].into(); + let code_hash_people = [5u8; 32].into(); + + let authorize_upgrade = + WestendRuntimeCall::Utility(pallet_utility::Call::::force_batch { + calls: vec![ + build_xcm_send_authorize_upgrade_call::( + Westend::child_location_of(AssetHubWestend::para_id()), + &code_hash_asset_hub, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Westend::child_location_of(BridgeHubWestend::para_id()), + &code_hash_bridge_hub, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Westend::child_location_of(CollectivesWestend::para_id()), + &code_hash_collectives, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Westend::child_location_of(CoretimeWestend::para_id()), + &code_hash_coretime, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Westend::child_location_of(PeopleWestend::para_id()), + &code_hash_people, + None, + ), + ], + }); + + // bad origin + let invalid_origin: WestendRuntimeOrigin = Origin::StakingAdmin.into(); + // ok origin + let ok_origin: WestendRuntimeOrigin = Origin::WhitelistedCaller.into(); + + let call_hash = call_hash_of::(&authorize_upgrade); + + // Err - when dispatch non-whitelisted + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + ok_origin.clone() + ), + DispatchError::Module(sp_runtime::ModuleError { + index: 36, + error: [3, 0, 0, 0], + message: Some("CallIsNotWhitelisted") + }) + ); + + // whitelist + collectives_send_whitelist(Location::parent(), || { + WestendRuntimeCall::Whitelist(pallet_whitelist::Call::::whitelist_call { + call_hash, + }) + .encode() + }); + + Westend::execute_with(|| { + assert_whitelisted!(Westend, call_hash); + }); + + // Err - when dispatch wrong origin + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + invalid_origin + ), + DispatchError::BadOrigin + ); + + // check before + AssetHubWestend::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + BridgeHubWestend::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + CollectivesWestend::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + CoretimeWestend::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + PeopleWestend::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + + // ok - authorized + assert_ok!(dispatch_whitelisted_call_with_preimage::(authorize_upgrade, ok_origin)); + + AssetHubWestend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash_asset_hub + ) + }); + // check after - authorized + BridgeHubWestend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash_bridge_hub + ) + }); + CollectivesWestend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash_collectives + ) + }); + CoretimeWestend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash_coretime + ) + }); + PeopleWestend::execute_with(|| { + assert_eq!( + ::System::authorized_upgrade().unwrap().code_hash(), + &code_hash_people + ) + }); +} diff --git a/prdoc/pr_8787.prdoc b/prdoc/pr_8787.prdoc new file mode 100644 index 0000000000000..e0a30183a0114 --- /dev/null +++ b/prdoc/pr_8787.prdoc @@ -0,0 +1,13 @@ +title: Westend governance authorize_upgrade integration tests +doc: +- audience: Runtime Dev + description: |- + Integration tests covering `authorize_upgrade` with whitelisting via Collectives for Westend network + +crates: +- name: emulated-integration-tests-common + bump: minor +- name: frame-system + bump: minor +- name: frame-system-benchmarking + bump: patch \ No newline at end of file diff --git a/substrate/frame/system/benchmarking/src/inner.rs b/substrate/frame/system/benchmarking/src/inner.rs index 0fb592f3dbba7..72131c66caf0f 100644 --- a/substrate/frame/system/benchmarking/src/inner.rs +++ b/substrate/frame/system/benchmarking/src/inner.rs @@ -205,7 +205,7 @@ mod benchmarks { #[extrinsic_call] authorize_upgrade(RawOrigin::Root, hash); - assert!(System::::authorized_upgrade().is_some()); + assert_eq!(System::::authorized_upgrade().unwrap().code_hash(), &hash); Ok(()) } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index ce249ebde9dc5..7c5887411d932 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -265,6 +265,16 @@ where check_version: bool, } +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl CodeUpgradeAuthorization +where + T: Config, +{ + pub fn code_hash(&self) -> &T::Hash { + &self.code_hash + } +} + /// Information about the dispatch of a call, to be displayed in the /// [`ExtrinsicSuccess`](Event::ExtrinsicSuccess) and [`ExtrinsicFailed`](Event::ExtrinsicFailed) /// events. diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 2c1abfd85fc37..59137f4bf9d46 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -741,7 +741,7 @@ fn set_code_via_authorization_works() { System::assert_has_event( SysEvent::UpgradeAuthorized { code_hash: hash, check_version: true }.into(), ); - assert!(System::authorized_upgrade().is_some()); + assert_eq!(System::authorized_upgrade().unwrap().code_hash(), &hash); // Can't be sneaky let mut bad_runtime = substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec();