diff --git a/Cargo.lock b/Cargo.lock index 867694621f..456ec8b982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5289,6 +5289,24 @@ dependencies = [ "system-parachains-constants", ] +[[package]] +name = "governance-kusama-integration-tests" +version = "1.0.0" +dependencies = [ + "asset-hub-kusama-runtime", + "emulated-integration-tests-common", + "frame-support", + "frame-system", + "integration-tests-helpers", + "kusama-system-emulated-network", + "pallet-utility", + "pallet-whitelist", + "parity-scale-codec", + "people-kusama-runtime", + "sp-runtime", + "staging-kusama-runtime", +] + [[package]] name = "group" version = "0.13.0" @@ -6169,11 +6187,15 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", "frame-support", + "frame-system", "hex-literal", "pallet-balances", "pallet-message-queue", + "pallet-whitelist", "pallet-xcm", "paste", + "sp-core 36.1.0", + "sp-runtime", "staging-xcm", "xcm-emulator", ] diff --git a/Cargo.toml b/Cargo.toml index 139e7bc1b7..f7754ff40e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -283,6 +283,7 @@ members = [ "integration-tests/emulated/tests/collectives/collectives-polkadot", "integration-tests/emulated/tests/coretime/coretime-kusama", "integration-tests/emulated/tests/coretime/coretime-polkadot", + "integration-tests/emulated/tests/governance/kusama", "integration-tests/emulated/tests/people/people-kusama", "integration-tests/emulated/tests/people/people-polkadot", "integration-tests/zombienet", diff --git a/integration-tests/emulated/helpers/Cargo.toml b/integration-tests/emulated/helpers/Cargo.toml index d502a10342..93a6f4ae82 100644 --- a/integration-tests/emulated/helpers/Cargo.toml +++ b/integration-tests/emulated/helpers/Cargo.toml @@ -15,6 +15,10 @@ hex-literal = { workspace = true } frame-support = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-whitelist = { workspace = true, default-features = true } # Polkadot xcm = { workspace = true, default-features = true } diff --git a/integration-tests/emulated/helpers/src/lib.rs b/integration-tests/emulated/helpers/src/lib.rs index 7134981da2..7b0f43f914 100644 --- a/integration-tests/emulated/helpers/src/lib.rs +++ b/integration-tests/emulated/helpers/src/lib.rs @@ -31,6 +31,12 @@ pub use xcm_emulator::Chain; pub mod common; +use emulated_integration_tests_common::impls::{bx, Encode}; +use frame_support::dispatch::{DispatchResultWithPostInfo, PostDispatchInfo}; +use sp_core::H256; +use sp_runtime::traits::{Dispatchable, Hash}; +use xcm::{prelude::*, VersionedLocation, VersionedXcm}; + /// TODO: when bumping to polkadot-sdk v1.8.0, /// remove this crate altogether and get the macros from `emulated-integration-tests-common`. /// TODO: backport this macros to polkadot-sdk @@ -552,3 +558,71 @@ macro_rules! test_chain_can_claim_assets { } }; } + +// TODO: remove when stable2503 / stable2506 released +/// 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) + }) +} + +// TODO: remove when stable2503 / stable2506 released +/// 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 +} + +// TODO: remove when stable2503 / stable2506 released +/// 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/integration-tests/emulated/tests/governance/kusama/Cargo.toml b/integration-tests/emulated/tests/governance/kusama/Cargo.toml new file mode 100644 index 0000000000..664b31c4e8 --- /dev/null +++ b/integration-tests/emulated/tests/governance/kusama/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "governance-kusama-integration-tests" +version.workspace = true +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Kusama governance integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { workspace = true, default-features = true } + +# Substrate +sp-runtime = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-whitelist = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } + +# Cumulus +emulated-integration-tests-common = { workspace = true } + +# Local +asset-hub-kusama-runtime = { workspace = true } +integration-tests-helpers = { workspace = true } +people-kusama-runtime = { workspace = true } +kusama-runtime = { workspace = true } +kusama-system-emulated-network = { workspace = true } diff --git a/integration-tests/emulated/tests/governance/kusama/src/lib.rs b/integration-tests/emulated/tests/governance/kusama/src/lib.rs new file mode 100644 index 0000000000..21dbf6e412 --- /dev/null +++ b/integration-tests/emulated/tests/governance/kusama/src/lib.rs @@ -0,0 +1,18 @@ +// 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 open_gov_on_relay; diff --git a/integration-tests/emulated/tests/governance/kusama/src/open_gov_on_relay.rs b/integration-tests/emulated/tests/governance/kusama/src/open_gov_on_relay.rs new file mode 100644 index 0000000000..4ef1b942c1 --- /dev/null +++ b/integration-tests/emulated/tests/governance/kusama/src/open_gov_on_relay.rs @@ -0,0 +1,210 @@ +// 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 emulated_integration_tests_common::{ + impls::RelayChain, + xcm_emulator::{Chain, Parachain, TestExt}, +}; +use frame_support::{assert_err, assert_ok}; +use integration_tests_helpers::{ + build_xcm_send_authorize_upgrade_call, call_hash_of, dispatch_whitelisted_call_with_preimage, +}; +use kusama_runtime::{governance::pallet_custom_origins::Origin, Dmp}; +use kusama_system_emulated_network::{ + AssetHubKusamaPara as AssetHubKusama, BridgeHubKusamaPara as BridgeHubKusama, + CoretimeKusamaPara as CoretimeKusama, KusamaRelay as Kusama, PeopleKusamaPara as PeopleKusama, +}; +use sp_runtime::{traits::Dispatchable, DispatchError}; + +#[test] +fn relaychain_can_authorize_upgrade_for_itself() { + let code_hash = [1u8; 32].into(); + type KusamaRuntime = ::Runtime; + type KusamaRuntimeCall = ::RuntimeCall; + type KusamaRuntimeOrigin = ::RuntimeOrigin; + + let authorize_upgrade = + KusamaRuntimeCall::Utility(pallet_utility::Call::::force_batch { + calls: vec![ + // upgrade the relaychain + KusamaRuntimeCall::System(frame_system::Call::authorize_upgrade { code_hash }), + ], + }); + + // bad origin + let invalid_origin: KusamaRuntimeOrigin = Origin::StakingAdmin.into(); + // ok origin + let ok_origin: KusamaRuntimeOrigin = 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: 44, + error: [3, 0, 0, 0], + message: Some("CallIsNotWhitelisted") + }) + ); + + // whitelist + Kusama::execute_with(|| { + let whitelist_call = + KusamaRuntimeCall::Whitelist(pallet_whitelist::Call::::whitelist_call { + call_hash, + }); + use kusama_runtime::governance::pallet_custom_origins::Origin::Fellows as FellowsOrigin; + let fellows_origin: KusamaRuntimeOrigin = FellowsOrigin.into(); + assert_ok!(whitelist_call.dispatch(fellows_origin)); + }); + + // Err - when dispatch wrong origin + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + invalid_origin + ), + DispatchError::BadOrigin + ); + + // check before + Kusama::execute_with(|| assert!(::System::authorized_upgrade().is_none())); + + // ok - authorized + assert_ok!(dispatch_whitelisted_call_with_preimage::(authorize_upgrade, ok_origin)); + + // check after - authorized + Kusama::execute_with(|| assert!(::System::authorized_upgrade().is_some())); +} + +#[test] +fn relaychain_can_authorize_upgrade_for_system_chains() { + type KusamaRuntime = ::Runtime; + type KusamaRuntimeCall = ::RuntimeCall; + type KusamaRuntimeOrigin = ::RuntimeOrigin; + + Kusama::execute_with(|| { + Dmp::make_parachain_reachable(AssetHubKusama::para_id()); + Dmp::make_parachain_reachable(BridgeHubKusama::para_id()); + Dmp::make_parachain_reachable(CoretimeKusama::para_id()); + Dmp::make_parachain_reachable(PeopleKusama::para_id()); + }); + + let code_hash_asset_hub = [1u8; 32].into(); + let code_hash_bridge_hub = [2u8; 32].into(); + let code_hash_coretime = [4u8; 32].into(); + let code_hash_people = [5u8; 32].into(); + + let authorize_upgrade = + KusamaRuntimeCall::Utility(pallet_utility::Call::::force_batch { + calls: vec![ + build_xcm_send_authorize_upgrade_call::( + Kusama::child_location_of(AssetHubKusama::para_id()), + &code_hash_asset_hub, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Kusama::child_location_of(BridgeHubKusama::para_id()), + &code_hash_bridge_hub, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Kusama::child_location_of(CoretimeKusama::para_id()), + &code_hash_coretime, + None, + ), + build_xcm_send_authorize_upgrade_call::( + Kusama::child_location_of(PeopleKusama::para_id()), + &code_hash_people, + None, + ), + ], + }); + + // bad origin + let invalid_origin: KusamaRuntimeOrigin = Origin::StakingAdmin.into(); + // ok origin + let ok_origin: KusamaRuntimeOrigin = 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: 44, + error: [3, 0, 0, 0], + message: Some("CallIsNotWhitelisted") + }) + ); + + // whitelist + Kusama::execute_with(|| { + let whitelist_call = + KusamaRuntimeCall::Whitelist(pallet_whitelist::Call::::whitelist_call { + call_hash, + }); + use kusama_runtime::governance::pallet_custom_origins::Origin::Fellows as FellowsOrigin; + let fellows_origin: KusamaRuntimeOrigin = FellowsOrigin.into(); + assert_ok!(whitelist_call.dispatch(fellows_origin)); + }); + + // Err - when dispatch wrong origin + assert_err!( + dispatch_whitelisted_call_with_preimage::( + authorize_upgrade.clone(), + invalid_origin + ), + DispatchError::BadOrigin + ); + + // check before + AssetHubKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + BridgeHubKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + CoretimeKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + PeopleKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_none()) + }); + + // ok - authorized + assert_ok!(dispatch_whitelisted_call_with_preimage::(authorize_upgrade, ok_origin)); + + AssetHubKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_some()) + }); + // check after - authorized + BridgeHubKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_some()) + }); + CoretimeKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_some()) + }); + PeopleKusama::execute_with(|| { + assert!(::System::authorized_upgrade().is_some()) + }); +}