diff --git a/.github/workflows/integration-tests-matrix.json b/.github/workflows/integration-tests-matrix.json index 8184e2a1c9..bb5266dd5a 100644 --- a/.github/workflows/integration-tests-matrix.json +++ b/.github/workflows/integration-tests-matrix.json @@ -18,5 +18,9 @@ { "name": "people-kusama", "package": "people-kusama-integration-tests" + }, + { + "name": "collectives-polkadot", + "package": "collectives-polkadot-integration-tests" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f515bde7d..0a7655a7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Bump parachains runtime API to v10 in Polkadot to enable async-backing subsystems(still in backwards compatible mode) [polkadot-fellows/runtimes#222](https://github.com/polkadot-fellows/runtimes/pull/222) - Prepared system parachain runtimes for async backing enabling ([polkadot-fellows/runtimes#228](https://github.com/polkadot-fellows/runtimes/pull/228)) - Update runtime weights [polkadot-fellows/runtimes#223](https://github.com/polkadot-fellows/runtimes/pull/223) +- Treasury Spend detects relative locations of the native asset ([polkadot-fellows/runtimes#233](https://github.com/polkadot-fellows/runtimes/pull/233)) ### Removed diff --git a/Cargo.lock b/Cargo.lock index df448dcffb..ded0471ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,14 +576,18 @@ dependencies = [ "emulated-integration-tests-common", "frame-support", "integration-tests-helpers", + "kusama-runtime-constants", "kusama-system-emulated-network", "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-message-queue", + "pallet-treasury", + "pallet-utility", "pallet-xcm", "parachains-common", "parity-scale-codec", + "polkadot-runtime-common", "sp-runtime", "staging-kusama-runtime", "staging-xcm", @@ -2230,6 +2234,38 @@ dependencies = [ "sp-core", ] +[[package]] +name = "collectives-polkadot-integration-tests" +version = "1.0.0" +dependencies = [ + "assert_matches", + "asset-hub-polkadot-runtime", + "asset-test-utils", + "collectives-polkadot-runtime", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "emulated-integration-tests-common", + "frame-support", + "integration-tests-helpers", + "pallet-asset-rate", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-treasury", + "pallet-utility", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "polkadot-runtime", + "polkadot-runtime-common", + "polkadot-runtime-constants", + "polkadot-system-emulated-network", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", + "system-parachains-constants", +] + [[package]] name = "collectives-polkadot-runtime" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index caa3fee9cc..bdfc9b0e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "integration-tests/emulated/tests/assets/asset-hub-polkadot", "integration-tests/emulated/tests/bridges/bridge-hub-kusama", "integration-tests/emulated/tests/bridges/bridge-hub-polkadot", + "integration-tests/emulated/tests/collectives/collectives-polkadot", "integration-tests/emulated/tests/people/people-kusama", ] diff --git a/integration-tests/emulated/chains/relays/kusama/src/lib.rs b/integration-tests/emulated/chains/relays/kusama/src/lib.rs index 43a98d7f0c..2ac67cdc25 100644 --- a/integration-tests/emulated/chains/relays/kusama/src/lib.rs +++ b/integration-tests/emulated/chains/relays/kusama/src/lib.rs @@ -38,6 +38,8 @@ decl_test_relay_chains! { Hrmp: kusama_runtime::Hrmp, Identity: kusama_runtime::Identity, IdentityMigrator: kusama_runtime::IdentityMigrator, + Treasury: kusama_runtime::Treasury, + AssetRate: kusama_runtime::AssetRate, } }, } diff --git a/integration-tests/emulated/tests/assets/asset-hub-kusama/Cargo.toml b/integration-tests/emulated/tests/assets/asset-hub-kusama/Cargo.toml index 1cccf0b697..32719c27a1 100644 --- a/integration-tests/emulated/tests/assets/asset-hub-kusama/Cargo.toml +++ b/integration-tests/emulated/tests/assets/asset-hub-kusama/Cargo.toml @@ -17,12 +17,15 @@ frame-support = { version = "29.0.0" } pallet-assets = { version = "30.0.0" } pallet-balances = { version = "29.0.0" } pallet-asset-conversion = { version = "11.0.0" } +pallet-treasury = { version = "28.0.0" } pallet-message-queue = { version = "32.0.0" } +pallet-utility = { version = "29.0.0" } # Polkadot xcm = { package = "staging-xcm", version = "8.0.1" } xcm-executor = { package = "staging-xcm-executor", default-features = false, version = "8.0.1" } pallet-xcm = { version = "8.0.3" } +runtime-common = { package = "polkadot-runtime-common", default-features = false, version = "8.0.1" } # Cumulus parachains-common = { version = "8.0.0" } @@ -36,3 +39,4 @@ integration-tests-helpers = { path = "../../../helpers" } kusama-runtime = { package = "staging-kusama-runtime", path = "../../../../../relay/kusama" } kusama-system-emulated-network = { path = "../../../networks/kusama-system" } system-parachains-constants = { path = "../../../../../system-parachains/constants" } +kusama-runtime-constants = { path = "../../../../../relay/kusama/constants" } diff --git a/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/mod.rs b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/mod.rs index 3bded12dd1..7e60ad90b2 100644 --- a/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/mod.rs +++ b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/mod.rs @@ -19,6 +19,7 @@ mod send; mod set_xcm_versions; mod swap; mod teleport; +mod treasury; use crate::*; emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!( diff --git a/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/treasury.rs b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/treasury.rs new file mode 100644 index 0000000000..0bf533a613 --- /dev/null +++ b/integration-tests/emulated/tests/assets/asset-hub-kusama/src/tests/treasury.rs @@ -0,0 +1,271 @@ +// 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. + +//! Tests concerning the Kusama Treasury. + +use crate::*; +use emulated_integration_tests_common::accounts::{ALICE, BOB}; +use frame_support::{ + dispatch::RawOrigin, + sp_runtime::traits::Dispatchable, + traits::{ + fungible::Inspect, + fungibles::{Create, Inspect as FungiblesInspect, Mutate}, + }, +}; +use kusama_runtime::OriginCaller; +use kusama_runtime_constants::currency::GRAND; +use runtime_common::impls::VersionedLocatableAsset; +use xcm_executor::traits::ConvertLocation; + +// Fund Treasury account on Asset Hub from Treasury account on Relay Chain with KSMs. +#[test] +fn spend_ksm_on_asset_hub() { + // initial treasury balance on Asset Hub in KSMs. + let treasury_balance = 9_000 * GRAND; + // the balance spend on Asset Hub. + let treasury_spend_balance = 1_000 * GRAND; + + let init_alice_balance = AssetHubKusama::execute_with(|| { + <::Balances as Inspect<_>>::balance( + &AssetHubKusama::account_id_of(ALICE), + ) + }); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type Runtime = ::Runtime; + type Balances = ::Balances; + type Treasury = ::Treasury; + + // Fund Treasury account on Asset Hub with KSMs. + + let root = ::RuntimeOrigin::root(); + let treasury_account = Treasury::account_id(); + + // Mint assets to Treasury account on Relay Chain. + assert_ok!(Balances::force_set_balance( + root.clone(), + treasury_account.clone().into(), + treasury_balance * 2, + )); + + let native_asset = Location::here(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let treasury_location: Location = (Parent, PalletInstance(18)).into(); + + let teleport_call = RuntimeCall::Utility(pallet_utility::Call::::dispatch_as { + as_origin: bx!(OriginCaller::system(RawOrigin::Signed(treasury_account))), + call: bx!(RuntimeCall::XcmPallet(pallet_xcm::Call::::teleport_assets { + dest: bx!(VersionedLocation::V4(asset_hub_location.clone())), + beneficiary: bx!(VersionedLocation::V4(treasury_location)), + assets: bx!(VersionedAssets::V4( + Asset { id: native_asset.clone().into(), fun: treasury_balance.into() }.into() + )), + fee_asset_item: 0, + })), + }); + + // Dispatched from Root to `dispatch_as` `Signed(treasury_account)`. + assert_ok!(teleport_call.dispatch(root)); + + assert_expected_events!( + Kusama, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type Treasury = ::Treasury; + + // Fund Alice account from Kusama Treasury account on Asset Hub. + + let treasury_origin: RuntimeOrigin = + kusama_runtime::governance::pallet_custom_origins::Origin::Treasurer.into(); + + let alice_location: Location = + [Junction::AccountId32 { network: None, id: Kusama::account_id_of(ALICE).into() }] + .into(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let native_asset_on_asset_hub = Location::parent(); + + let treasury_spend_call = RuntimeCall::Treasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location.clone(), + asset_id: native_asset_on_asset_hub.into(), + }), + amount: treasury_spend_balance, + beneficiary: bx!(VersionedLocation::V4(alice_location)), + valid_from: None, + }); + + assert_ok!(treasury_spend_call.dispatch(treasury_origin)); + + // Claim the spend. + + let bob_signed = RuntimeOrigin::signed(Kusama::account_id_of(BOB)); + assert_ok!(Treasury::payout(bob_signed.clone(), 0)); + + assert_expected_events!( + Kusama, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to Alice account. + + let alice_account = AssetHubKusama::account_id_of(ALICE); + assert_eq!( + >::balance(&alice_account), + treasury_spend_balance + init_alice_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); +} + +#[test] +fn create_and_claim_treasury_spend_in_usdt() { + const ASSET_ID: u32 = 1984; + const SPEND_AMOUNT: u128 = 1_000_000; + // treasury location from a sibling parachain. + let treasury_location: Location = Location::new(1, PalletInstance(18)); + // treasury account on a sibling parachain. + let treasury_account = + asset_hub_kusama_runtime::xcm_config::LocationToAccountId::convert_location( + &treasury_location, + ) + .unwrap(); + let asset_hub_location = + v3::Location::new(0, v3::Junction::Parachain(AssetHubKusama::para_id().into())); + let root = ::RuntimeOrigin::root(); + // asset kind to be spend from the treasury. + let asset_kind = VersionedLocatableAsset::V3 { + location: asset_hub_location, + asset_id: v3::AssetId::Concrete( + (v3::Junction::PalletInstance(50), v3::Junction::GeneralIndex(ASSET_ID.into())).into(), + ), + }; + // treasury spend beneficiary. + let alice: AccountId = Kusama::account_id_of(ALICE); + let bob: AccountId = Kusama::account_id_of(BOB); + let bob_signed = ::RuntimeOrigin::signed(bob.clone()); + + AssetHubKusama::execute_with(|| { + type Assets = ::Assets; + + // create an asset class and mint some assets to the treasury account. + assert_ok!(>::create( + ASSET_ID, + treasury_account.clone(), + true, + SPEND_AMOUNT / 2 + )); + assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); + // beneficiary has zero balance. + assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); + }); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Treasury = ::Treasury; + type AssetRate = ::AssetRate; + + // create a conversion rate from `asset_kind` to the native currency. + assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + + // create and approve a treasury spend. + assert_ok!(Treasury::spend( + root, + Box::new(asset_kind), + SPEND_AMOUNT, + Box::new(Location::new(0, Into::<[u8; 32]>::into(alice.clone())).into()), + None, + )); + // claim the spend. + assert_ok!(Treasury::payout(bob_signed.clone(), 0)); + + assert_expected_events!( + Kusama, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Assets = ::Assets; + + // assert events triggered by xcm pay program + // 1. treasury asset transferred to spend beneficiary + // 2. response to Relay Chain treasury pallet instance sent back + // 3. XCM program completed + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => { + id: id == &ASSET_ID, + from: from == &treasury_account, + to: to == &alice, + amount: amount == &SPEND_AMOUNT, + }, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + // beneficiary received the assets from the treasury. + assert_eq!(>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,); + }); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Treasury = ::Treasury; + + // check the payment status to ensure the response from the AssetHub was received. + assert_ok!(Treasury::check_status(bob_signed, 0)); + assert_expected_events!( + Kusama, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::SpendProcessed { .. }) => {}, + ] + ); + }); +} diff --git a/integration-tests/emulated/tests/collectives/collectives-polkadot/Cargo.toml b/integration-tests/emulated/tests/collectives/collectives-polkadot/Cargo.toml new file mode 100644 index 0000000000..ee15b60abe --- /dev/null +++ b/integration-tests/emulated/tests/collectives/collectives-polkadot/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "collectives-polkadot-integration-tests" +version.workspace = true +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Collectives Polkadot runtime integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.9" } +assert_matches = "1.5.0" + +# Substrate +sp-runtime = { version = "32.0.0" } +frame-support = { version = "29.0.0" } +pallet-balances = { version = "29.0.0" } +pallet-asset-rate = { version = "8.0.0" } +pallet-assets = { version = "30.0.0" } +pallet-treasury = { version = "28.0.0" } +pallet-message-queue = { version = "32.0.0" } +pallet-utility = { version = "29.0.0" } + +# Polkadot +polkadot-runtime-common = { version = "8.0.1" } +xcm = { package = "staging-xcm", version = "8.0.1" } +pallet-xcm = { version = "8.0.2" } +xcm-executor = { package = "staging-xcm-executor", version = "8.0.1" } + +# Cumulus +asset-test-utils = { version = "8.0.1" } +emulated-integration-tests-common = { version = "4.0.0" } +parachains-common = { version = "8.0.0" } +cumulus-pallet-xcmp-queue = { version = "0.8.0" } +cumulus-pallet-parachain-system = { features = ["parameterized-consensus-hook"], version = "0.8.1" } + +# Local +asset-hub-polkadot-runtime = { path = "../../../../../system-parachains/asset-hubs/asset-hub-polkadot" } +collectives-polkadot-runtime = { path = "../../../../../system-parachains/collectives/collectives-polkadot" } +integration-tests-helpers = { path = "../../../helpers" } +polkadot-runtime = { path = "../../../../../relay/polkadot" } +polkadot-runtime-constants = { path = "../../../../../relay/polkadot/constants" } +polkadot-system-emulated-network = { path = "../../../networks/polkadot-system" } +system-parachains-constants = { path = "../../../../../system-parachains/constants" } diff --git a/integration-tests/emulated/tests/collectives/collectives-polkadot/src/lib.rs b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/lib.rs new file mode 100644 index 0000000000..605d817598 --- /dev/null +++ b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/lib.rs @@ -0,0 +1,32 @@ +// 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. + +// Polkadot +pub use xcm::{prelude::*, v3}; + +// Cumulus +pub use emulated_integration_tests_common::xcm_emulator::{ + assert_expected_events, bx, Chain, RelayChain as Relay, TestExt, +}; +pub use polkadot_system_emulated_network::{ + asset_hub_polkadot_emulated_chain::AssetHubPolkadotParaPallet as AssetHubPolkadotPallet, + collectives_polkadot_emulated_chain::CollectivesPolkadotParaPallet as CollectivesPolkadotPallet, + polkadot_emulated_chain::PolkadotRelayPallet as PolkadotPallet, + AssetHubPolkadotPara as AssetHubPolkadot, CollectivesPolkadotPara as CollectivesPolkadot, + PolkadotRelay as Polkadot, +}; + +#[cfg(test)] +mod tests; diff --git a/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/fellowship_treasury.rs b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/fellowship_treasury.rs new file mode 100644 index 0000000000..03adf3dbbc --- /dev/null +++ b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/fellowship_treasury.rs @@ -0,0 +1,234 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use asset_hub_polkadot_runtime::xcm_config::LocationToAccountId as AssetHubLocationToAccountId; +use emulated_integration_tests_common::accounts::ALICE; +use frame_support::{ + assert_ok, dispatch::RawOrigin, instances::Instance1, sp_runtime::traits::Dispatchable, + traits::fungible::Inspect, +}; +use polkadot_runtime::OriginCaller; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use polkadot_runtime_constants::currency::UNITS; +use xcm_executor::traits::ConvertLocation; + +// Fund Fellowship Treasury from Polkadot Treasury and spend from Fellowship Treasury. +#[test] +fn fellowship_treasury_spend() { + // initial treasury balance on Asset Hub in DOTs. + let treasury_balance = 20_000_000 * UNITS; + // target fellowship balance on Asset Hub in DOTs. + let fellowship_treasury_balance = 1_000_000 * UNITS; + // fellowship first spend balance in DOTs. + let fellowship_spend_balance = 10_000 * UNITS; + + let init_alice_balance = AssetHubPolkadot::execute_with(|| { + <::Balances as Inspect<_>>::balance( + &Polkadot::account_id_of(ALICE), + ) + }); + + Polkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type Runtime = ::Runtime; + type Balances = ::Balances; + type Treasury = ::Treasury; + + // Fund Treasury account on Asset Hub with DOTs. + + let root = ::RuntimeOrigin::root(); + let treasury_account = Treasury::account_id(); + + // Mint assets to Treasury account on Relay Chain. + assert_ok!(Balances::force_set_balance( + root.clone(), + treasury_account.clone().into(), + treasury_balance * 2, + )); + + let native_asset = Location::here(); + let asset_hub_location: Location = [Parachain(1000)].into(); + let treasury_location: Location = (Parent, PalletInstance(19)).into(); + + let teleport_call = RuntimeCall::Utility(pallet_utility::Call::::dispatch_as { + as_origin: bx!(OriginCaller::system(RawOrigin::Signed(treasury_account))), + call: bx!(RuntimeCall::XcmPallet(pallet_xcm::Call::::teleport_assets { + dest: bx!(VersionedLocation::V4(asset_hub_location.clone())), + beneficiary: bx!(VersionedLocation::V4(treasury_location)), + assets: bx!(VersionedAssets::V4( + Asset { id: native_asset.clone().into(), fun: treasury_balance.into() }.into() + )), + fee_asset_item: 0, + })), + }); + + // Dispatched from Root to `despatch_as` `Signed(treasury_account)`. + assert_ok!(teleport_call.dispatch(root)); + + assert_expected_events!( + Polkadot, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + Polkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type Treasury = ::Treasury; + + // Fund Fellowship Treasury from Polkadot Treasury. + + let treasury_origin: RuntimeOrigin = + polkadot_runtime::governance::pallet_custom_origins::Origin::Treasurer.into(); + let fellowship_treasury_location: Location = + Location::new(1, [Parachain(1001), PalletInstance(65)]); + let asset_hub_location: Location = [Parachain(1000)].into(); + let native_asset_on_asset_hub = Location::parent(); + + let treasury_spend_call = RuntimeCall::Treasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location.clone(), + asset_id: native_asset_on_asset_hub.into(), + }), + amount: fellowship_treasury_balance, + beneficiary: bx!(VersionedLocation::V4(fellowship_treasury_location)), + valid_from: None, + }); + + assert_ok!(treasury_spend_call.dispatch(treasury_origin)); + + // Claim the spend. + + let alice_signed = RuntimeOrigin::signed(Polkadot::account_id_of(ALICE)); + assert_ok!(Treasury::payout(alice_signed.clone(), 0)); + + assert_expected_events!( + Polkadot, + vec![ + RuntimeEvent::Treasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to the Fellowship Treasury account. + + let fellowship_treasury_location: Location = + Location::new(1, [Parachain(1001), PalletInstance(65)]); + let fellowship_treasury_account = + AssetHubLocationToAccountId::convert_location(&fellowship_treasury_location).unwrap(); + + assert_eq!( + >::balance(&fellowship_treasury_account), + fellowship_treasury_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); + + CollectivesPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type FellowshipTreasury = + ::FellowshipTreasury; + + // Fund Alice account from Fellowship Treasury. + + let fellows_origin: RuntimeOrigin = + collectives_polkadot_runtime::fellowship::pallet_fellowship_origins::Origin::Fellows + .into(); + let asset_hub_location: Location = (Parent, Parachain(1000)).into(); + let native_asset_on_asset_hub = Location::parent(); + + let alice_location: Location = + [Junction::AccountId32 { network: None, id: Polkadot::account_id_of(ALICE).into() }] + .into(); + + let fellowship_treasury_spend_call = + RuntimeCall::FellowshipTreasury(pallet_treasury::Call::::spend { + asset_kind: bx!(VersionedLocatableAsset::V4 { + location: asset_hub_location, + asset_id: native_asset_on_asset_hub.into(), + }), + amount: fellowship_spend_balance, + beneficiary: bx!(VersionedLocation::V4(alice_location)), + valid_from: None, + }); + + assert_ok!(fellowship_treasury_spend_call.dispatch(fellows_origin)); + + // Claim the spend. + + let alice_signed = RuntimeOrigin::signed(Polkadot::account_id_of(ALICE)); + assert_ok!(FellowshipTreasury::payout(alice_signed.clone(), 0)); + + assert_expected_events!( + CollectivesPolkadot, + vec![ + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::AssetSpendApproved { .. }) => {}, + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + // Ensure that the funds deposited to Alice account. + + let alice_account = Polkadot::account_id_of(ALICE); + assert_eq!( + >::balance(&alice_account), + fellowship_spend_balance + init_alice_balance + ); + + // Assert events triggered by xcm pay program: + // 1. treasury asset transferred to spend beneficiary; + // 2. response to Relay Chain Treasury pallet instance sent back; + // 3. XCM program completed; + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::Balances(pallet_balances::Event::Transfer { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); +} diff --git a/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/mod.rs b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/mod.rs new file mode 100644 index 0000000000..a9f65df34b --- /dev/null +++ b/integration-tests/emulated/tests/collectives/collectives-polkadot/src/tests/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod fellowship_treasury; diff --git a/relay/kusama/src/impls.rs b/relay/kusama/src/impls.rs index bbe5e9e525..23e63b637c 100644 --- a/relay/kusama/src/impls.rs +++ b/relay/kusama/src/impls.rs @@ -14,15 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use super::*; use crate::xcm_config; -use frame_support::{defensive, pallet_prelude::DispatchResult}; +use core::marker::PhantomData; +use frame_support::{ + defensive, + pallet_prelude::DispatchResult, + traits::{tokens::ConversionFromAssetBalance, Contains}, +}; use frame_system::RawOrigin; -use kusama_runtime_constants::{currency::*, system_parachain::PEOPLE_ID}; +use kusama_runtime_constants::system_parachain::PEOPLE_ID; use parity_scale_codec::{Decode, Encode}; -use primitives::Balance; +use primitives::{Balance, Id as ParaId}; use runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; -use sp_std::{marker::PhantomData, prelude::*}; use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_builder::IsChildSystemParachain; use xcm_executor::traits::TransactAsset; /// A type containing the encoding of the People Chain pallets in its runtime. Used to construct any @@ -175,3 +181,40 @@ where Ok(()) } } + +// TODO: replace by types from polkadot-sdk https://github.com/paritytech/polkadot-sdk/pull/3659 +/// Determines if the given `asset_kind` is a native asset. If it is, returns the balance without +/// conversion; otherwise, delegates to the implementation specified by `I`. +/// +/// Example where the `asset_kind` represents the native asset: +/// - location: (1, Parachain(1000)), // location of a Sibling Parachain; +/// - asset_id: (1, Here), // the asset id in the context of `asset_kind.location`; +pub struct NativeOnSystemParachain(PhantomData); +impl ConversionFromAssetBalance + for NativeOnSystemParachain +where + I: ConversionFromAssetBalance, +{ + type Error = (); + fn from_asset_balance( + balance: Balance, + asset_kind: VersionedLocatableAsset, + ) -> Result { + use VersionedLocatableAsset::*; + let (location, asset_id) = match asset_kind.clone() { + V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?), + V4 { location, asset_id } => (location, asset_id), + }; + if asset_id.0.contains_parents_only(1) && + IsChildSystemParachain::::contains(&location) + { + Ok(balance) + } else { + I::from_asset_balance(balance, asset_kind).map_err(|_| ()) + } + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(asset_kind: VersionedLocatableAsset) { + I::ensure_successful(asset_kind) + } +} diff --git a/relay/kusama/src/lib.rs b/relay/kusama/src/lib.rs index 41a8d3e083..c799066bd9 100644 --- a/relay/kusama/src/lib.rs +++ b/relay/kusama/src/lib.rs @@ -142,13 +142,12 @@ use governance::{ Treasurer, TreasurySpender, }; +// Implemented types. +pub mod impls; + #[cfg(test)] mod tests; -// Implemented types. -mod impls; -use impls::ToParachainIdentityReaper; - impl_runtime_weights!(kusama_runtime_constants); // Make the WASM binary available. @@ -823,7 +822,7 @@ impl pallet_treasury::Config for Runtime { LocatableAssetConverter, VersionedLocationConverter, >; - type BalanceConverter = AssetRate; + type BalanceConverter = impls::NativeOnSystemParachain; type PayoutPeriod = PayoutSpendPeriod; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments; @@ -1003,7 +1002,7 @@ impl identity_migrator::Config for Runtime { type RuntimeEvent = RuntimeEvent; // To be updated to `EnsureSigned` once the parachain is producing blocks. type Reaper = EnsureRoot; - type ReapIdentityHandler = ToParachainIdentityReaper; + type ReapIdentityHandler = impls::ToParachainIdentityReaper; type WeightInfo = weights::runtime_common_identity_migrator::WeightInfo; } diff --git a/relay/polkadot/src/impls.rs b/relay/polkadot/src/impls.rs new file mode 100644 index 0000000000..3e13683348 --- /dev/null +++ b/relay/polkadot/src/impls.rs @@ -0,0 +1,58 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +use super::*; +use core::marker::PhantomData; +use frame_support::traits::{tokens::ConversionFromAssetBalance, Contains}; +use primitives::Id as ParaId; +use xcm_builder::IsChildSystemParachain; + +// TODO: replace by types from polkadot-sdk https://github.com/paritytech/polkadot-sdk/pull/3659 +/// Determines if the given `asset_kind` is a native asset. If it is, returns the balance without +/// conversion; otherwise, delegates to the implementation specified by `I`. +/// +/// Example where the `asset_kind` represents the native asset: +/// - location: (1, Parachain(1000)), // location of a Sibling Parachain; +/// - asset_id: (1, Here), // the asset id in the context of `asset_kind.location`; +pub struct NativeOnSystemParachain(PhantomData); +impl ConversionFromAssetBalance + for NativeOnSystemParachain +where + I: ConversionFromAssetBalance, +{ + type Error = (); + fn from_asset_balance( + balance: Balance, + asset_kind: VersionedLocatableAsset, + ) -> Result { + use VersionedLocatableAsset::*; + let (location, asset_id) = match asset_kind.clone() { + V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?), + V4 { location, asset_id } => (location, asset_id), + }; + if asset_id.0.contains_parents_only(1) && + IsChildSystemParachain::::contains(&location) + { + Ok(balance) + } else { + I::from_asset_balance(balance, asset_kind).map_err(|_| ()) + } + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(asset_kind: VersionedLocatableAsset) { + I::ensure_successful(asset_kind) + } +} diff --git a/relay/polkadot/src/lib.rs b/relay/polkadot/src/lib.rs index 0d5c2f94e4..8617fc3be0 100644 --- a/relay/polkadot/src/lib.rs +++ b/relay/polkadot/src/lib.rs @@ -129,7 +129,7 @@ use governance::{ pallet_custom_origins, AuctionAdmin, FellowshipAdmin, GeneralAdmin, LeaseAdmin, StakingAdmin, Treasurer, TreasurySpender, }; - +pub mod impls; pub mod xcm_config; pub const LOG_TARGET: &'static str = "runtime::polkadot"; @@ -895,7 +895,7 @@ impl pallet_treasury::Config for Runtime { LocatableAssetConverter, VersionedLocationConverter, >; - type BalanceConverter = AssetRate; + type BalanceConverter = impls::NativeOnSystemParachain; type PayoutPeriod = PayoutSpendPeriod; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments; diff --git a/system-parachains/collectives/collectives-polkadot/src/fellowship/mod.rs b/system-parachains/collectives/collectives-polkadot/src/fellowship/mod.rs index 3505587d19..0f1c5fd815 100644 --- a/system-parachains/collectives/collectives-polkadot/src/fellowship/mod.rs +++ b/system-parachains/collectives/collectives-polkadot/src/fellowship/mod.rs @@ -23,8 +23,8 @@ use crate::{ weights, xcm_config::{LocationToAccountId, TreasurerBodyId}, AccountId, AssetRate, Balance, Balances, FellowshipReferenda, GovernanceLocation, - PolkadotTreasuryAccount, Preimage, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - Scheduler, DAYS, FELLOWSHIP_TREASURY_PALLET_ID, + ParachainInfo, PolkadotTreasuryAccount, Preimage, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, Scheduler, DAYS, FELLOWSHIP_TREASURY_PALLET_ID, }; use cumulus_primitives_core::Junction::GeneralIndex; use frame_support::{ @@ -352,7 +352,7 @@ impl pallet_treasury::Config for Runtime { type Paymaster = FellowshipTreasuryPaymaster; #[cfg(feature = "runtime-benchmarks")] type Paymaster = PayWithEnsure>>; - type BalanceConverter = AssetRate; + type BalanceConverter = crate::impls::NativeOnSiblingParachain; type PayoutPeriod = ConstU32<{ 30 * DAYS }>; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments< diff --git a/system-parachains/collectives/collectives-polkadot/src/impls.rs b/system-parachains/collectives/collectives-polkadot/src/impls.rs index d4f1fd8ecc..02086c0adb 100644 --- a/system-parachains/collectives/collectives-polkadot/src/impls.rs +++ b/system-parachains/collectives/collectives-polkadot/src/impls.rs @@ -13,15 +13,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::*; use crate::OriginCaller; use frame_support::{ dispatch::DispatchResultWithPostInfo, - traits::{Currency, Get, Imbalance, OnUnbalanced, OriginTrait, PrivilegeCmp}, + traits::{ + tokens::ConversionFromAssetBalance, Contains, Currency, Get, Imbalance, OnUnbalanced, + OriginTrait, PrivilegeCmp, + }, weights::Weight, }; use log; use pallet_alliance::{ProposalIndex, ProposalProvider}; use parachains_common::impls::NegativeImbalance; +use polkadot_parachain_primitives::primitives::{Id as ParaId, IsSystem}; use sp_runtime::DispatchError; use sp_std::{cmp::Ordering, marker::PhantomData, prelude::*}; use xcm::latest::{Fungibility, Junction, Junctions::Here, Location, Parent, WeightLimit}; @@ -159,6 +164,55 @@ impl PrivilegeCmp for EqualOrGreatestRootCmp { } } +// TODO: replace by types from polkadot-sdk https://github.com/paritytech/polkadot-sdk/pull/3659 +/// Contains a system-level sibling parachain. +pub struct IsSiblingSystemParachain(PhantomData<(ParaId, SelfParaId)>); +impl + Eq, SelfParaId: Get> Contains + for IsSiblingSystemParachain +{ + fn contains(l: &Location) -> bool { + matches!( + l.unpack(), + (1, [Parachain(id)]) if ParaId::from(*id).is_system() && SelfParaId::get() != ParaId::from(*id), + ) + } +} + +// TODO: replace by types from polkadot-sdk https://github.com/paritytech/polkadot-sdk/pull/3659 +/// Determines if the given `asset_kind` is a native asset. If it is, returns the balance without +/// conversion; otherwise, delegates to the implementation specified by `I`. +pub struct NativeOnSiblingParachain(PhantomData<(I, SelfParaId)>); +impl ConversionFromAssetBalance + for NativeOnSiblingParachain +where + I: ConversionFromAssetBalance, + SelfParaId: Get, +{ + type Error = (); + fn from_asset_balance( + balance: Balance, + asset_kind: VersionedLocatableAsset, + ) -> Result { + use VersionedLocatableAsset::*; + let (location, asset_id) = match asset_kind.clone() { + V3 { location, asset_id } => (location.try_into()?, asset_id.try_into()?), + V4 { location, asset_id } => (location, asset_id), + }; + + if asset_id.0.contains_parents_only(1) && + IsSiblingSystemParachain::::contains(&location) + { + Ok(balance) + } else { + I::from_asset_balance(balance, asset_kind).map_err(|_| ()) + } + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(asset_kind: VersionedLocatableAsset) { + I::ensure_successful(asset_kind) + } +} + #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks { use super::*;