Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Runtime: Allow unpaid execution for Treasury Pallet on AssetHubs #2902

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/subst
sp-weights = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-assets = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-treasury = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-asset-rate = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }

# Polkadot
polkadot-core-primitives = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
Expand All @@ -24,12 +26,16 @@ polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot",
polkadot-runtime = { git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm-executor = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm-builder = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
pallet-xcm = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }

# Cumulus
parachains-common = { path = "../../../../common" }
penpal-runtime = { path = "../../../../runtimes/testing/penpal" }
asset-hub-kusama-runtime = { path = "../../../../runtimes/assets/asset-hub-kusama" }
cumulus-pallet-xcmp-queue = { default-features = false, path = "../../../../../pallets/xcmp-queue" }
cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../pallets/dmp-queue" }
cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../pallets/parachain-system" }

# Local
xcm-emulator = { default-features = false, path = "../../../../../xcm/xcm-emulator" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
mod reserve_transfer;
mod teleport;
mod transact;
mod treasury;
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright Parity Technologies (UK) Ltd.
// This file is part of Cumulus.

// Cumulus 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.

// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.

use crate::*;
use frame_support::traits::fungibles::{Create, Inspect, Mutate};
use xcm_builder::LocatableAssetId;
use xcm_executor::traits::ConvertLocation;

#[test]
fn create_and_claim_treasury_spend() {
const ASSET_ID: u32 = 1984;
const SPEND_AMOUNT: u128 = 1_000_000;
// treasury location from a sibling parachain.
let treasury_location: MultiLocation = MultiLocation::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 = MultiLocation::new(0, Parachain(AssetHubKusama::para_id().into()));
let root = <Kusama as Relay>::RuntimeOrigin::root();
// asset kind to be spend from the treasury.
let asset_kind = LocatableAssetId::new(
AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()),
asset_hub_location,
);
// treasury spend beneficiary.
let alice: AccountId = Kusama::account_id_of(ALICE);
let bob: AccountId = Kusama::account_id_of(BOB);
let bob_signed = <Kusama as Relay>::RuntimeOrigin::signed(bob.clone());

AssetHubKusama::execute_with(|| {
type Assets = <AssetHubKusama as AssetHubKusamaPallet>::Assets;

// create an asset class and mint some assets to the treasury account.
assert_ok!(<Assets as Create<_>>::create(
ASSET_ID,
treasury_account.clone(),
true,
SPEND_AMOUNT / 2
));
assert_ok!(<Assets as Mutate<_>>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4));
// beneficiary has zero balance.
assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), 0u128,);
});

Kusama::execute_with(|| {
type RuntimeEvent = <Kusama as Relay>::RuntimeEvent;
type Treasury = <Kusama as KusamaPallet>::Treasury;
type AssetRate = <Kusama as KusamaPallet>::AssetRate;

// create a conversion rate from `asset_kind` to the native currency.
assert_ok!(AssetRate::create(root.clone(), asset_kind.clone(), 2.into()));

// create and approve a treasury spend.
assert_ok!(Treasury::spend(
root,
asset_kind,
SPEND_AMOUNT,
MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())),
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 = <AssetHubKusama as Para>::RuntimeEvent;
type Assets = <AssetHubKusama as AssetHubKusamaPallet>::Assets;

// assets transferred, response sent back via UMP.
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::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Complete(..) ,.. }) => {},
]
);
// beneficiary received the assets from the treasury.
assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,);
});

Kusama::execute_with(|| {
type RuntimeEvent = <Kusama as Relay>::RuntimeEvent;
type Treasury = <Kusama as KusamaPallet>::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 { .. }) => {},
]
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/subst
sp-weights = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-assets = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-treasury = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-asset-rate = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "master" }

# Polkadot
polkadot-core-primitives = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
Expand All @@ -24,12 +26,16 @@ polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot",
polkadot-runtime = { git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm-executor = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm-builder = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }
pallet-xcm = { default-features = false, git = "https://github.com/paritytech/polkadot", branch = "master" }

# Cumulus
parachains-common = { path = "../../../../common" }
penpal-runtime = { path = "../../../../runtimes/testing/penpal" }
asset-hub-polkadot-runtime = { path = "../../../../runtimes/assets/asset-hub-polkadot" }
cumulus-pallet-xcmp-queue = { default-features = false, path = "../../../../../pallets/xcmp-queue" }
cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../pallets/dmp-queue" }
cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../pallets/parachain-system" }

# Local
xcm-emulator = { default-features = false, path = "../../../../../xcm/xcm-emulator" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
mod reserve_transfer;
mod teleport;
mod transact;
mod treasury;
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright Parity Technologies (UK) Ltd.
// This file is part of Cumulus.

// Cumulus 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.

// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.

use crate::*;
use frame_support::traits::fungibles::{Create, Inspect, Mutate};
use xcm_builder::LocatableAssetId;
use xcm_executor::traits::ConvertLocation;

#[test]
fn create_and_claim_treasury_spend() {
const ASSET_ID: u32 = 1984;
const SPEND_AMOUNT: u128 = 1_000_000;
// treasury location from a sibling parachain.
let treasury_location: MultiLocation = MultiLocation::new(1, PalletInstance(19));
// treasury account on a sibling parachain.
let treasury_account =
asset_hub_polkadot_runtime::xcm_config::LocationToAccountId::convert_location(
&treasury_location,
)
.unwrap();
let asset_hub_location = MultiLocation::new(0, Parachain(AssetHubPolkadot::para_id().into()));
let root = <Polkadot as Relay>::RuntimeOrigin::root();
// asset kind to be spent from the treasury.
let asset_kind = LocatableAssetId::new(
AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()),
asset_hub_location,
);
// treasury spend beneficiary.
let alice: AccountId = Polkadot::account_id_of(ALICE);
let bob: AccountId = Polkadot::account_id_of(BOB);
let bob_signed = <Polkadot as Relay>::RuntimeOrigin::signed(bob.clone());

AssetHubPolkadot::execute_with(|| {
type Assets = <AssetHubPolkadot as AssetHubPolkadotPallet>::Assets;

// create an asset class and mint some assets to the treasury account.
assert_ok!(<Assets as Create<_>>::create(
ASSET_ID,
treasury_account.clone(),
true,
SPEND_AMOUNT / 2
));
assert_ok!(<Assets as Mutate<_>>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4));
// beneficiary has zero balance.
assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), 0u128,);
});

Polkadot::execute_with(|| {
type RuntimeEvent = <Polkadot as Relay>::RuntimeEvent;
type Treasury = <Polkadot as PolkadotPallet>::Treasury;
type AssetRate = <Polkadot as PolkadotPallet>::AssetRate;

// create a conversion rate from `asset_kind` to the native currency.
assert_ok!(AssetRate::create(root.clone(), asset_kind.clone(), 2.into()));

// create and approve a treasury spend.
assert_ok!(Treasury::spend(
root,
asset_kind,
SPEND_AMOUNT,
MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())),
None,
));
// claim the spend.
assert_ok!(Treasury::payout(bob_signed.clone(), 0));

assert_expected_events!(
Polkadot,
vec![
RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {},
]
);
});

AssetHubPolkadot::execute_with(|| {
type RuntimeEvent = <AssetHubPolkadot as Para>::RuntimeEvent;
type Assets = <AssetHubPolkadot as AssetHubPolkadotPallet>::Assets;

// assets transferred, response sent back via UMP.
assert_expected_events!(
AssetHubPolkadot,
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::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Complete(..) ,.. }) => {},
]
);
// beneficiary received the assets from the treasury.
assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,);
});

Polkadot::execute_with(|| {
type RuntimeEvent = <Polkadot as Relay>::RuntimeEvent;
type Treasury = <Polkadot as PolkadotPallet>::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!(
Polkadot,
vec![
RuntimeEvent::Treasury(pallet_treasury::Event::SpendProcessed { .. }) => {},
]
);
});
}
4 changes: 4 additions & 0 deletions parachains/integration-tests/emulated/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ decl_test_relay_chains! {
},
pallets_extra = {
XcmPallet: polkadot_runtime::XcmPallet,
Treasury: polkadot_runtime::Treasury,
AssetRate: polkadot_runtime::AssetRate,
}
},
#[api_version(5)]
Expand All @@ -56,6 +58,8 @@ decl_test_relay_chains! {
},
pallets_extra = {
XcmPallet: kusama_runtime::XcmPallet,
Treasury: kusama_runtime::Treasury,
AssetRate: kusama_runtime::AssetRate,
}
},
#[api_version(5)]
Expand Down
8 changes: 6 additions & 2 deletions parachains/runtimes/assets/asset-hub-kusama/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ match_types! {
MultiLocation { parents: 1, interior: Here } |
MultiLocation { parents: 1, interior: X1(_) }
};
pub type TreasuryPallet: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: X1(PalletInstance(18)) }
};
}

/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
Expand Down Expand Up @@ -357,8 +360,9 @@ pub type Barrier = TrailingSetTopicAsId<
(
// If the message is one that immediately attemps to pay for execution, then allow it.
AllowTopLevelPaidExecutionFrom<Everything>,
// Parent and its pluralities (i.e. governance bodies) get free execution.
AllowExplicitUnpaidExecutionFrom<ParentOrParentsPlurality>,
// Parent, parent's pluralities (i.e. governance bodies) and parent's treasury
// pallet get free execution.
AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, TreasuryPallet)>,
// Subscriptions for version tracking are OK.
AllowSubscriptionsFrom<ParentOrSiblings>,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ match_types! {
pub type FellowshipSalaryPallet: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: X2(Parachain(1001), PalletInstance(64)) }
};
pub type TreasuryPallet: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: X1(PalletInstance(19)) }
};
}

/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
Expand Down Expand Up @@ -361,11 +364,13 @@ pub type Barrier = DenyThenTry<
(
// If the message is one that immediately attemps to pay for execution, then allow it.
AllowTopLevelPaidExecutionFrom<Everything>,
// Parent, its pluralities (i.e. governance bodies), and the Fellows plurality get free execution.
// Parent, parent's pluralities (i.e. governance bodies), parent's treasury pallet
// and the Fellows plurality get free execution.
AllowExplicitUnpaidExecutionFrom<(
ParentOrParentsPlurality,
FellowsPlurality,
FellowshipSalaryPallet,
TreasuryPallet,
)>,
// Subscriptions for version tracking are OK.
AllowSubscriptionsFrom<ParentOrSiblings>,
Expand Down