Skip to content

Commit

Permalink
Treasury spends various asset kinds (#1333)
Browse files Browse the repository at this point in the history
### Summary 

This PR introduces new dispatchables to the treasury pallet, allowing
spends of various asset types. The enhanced features of the treasury
pallet, in conjunction with the asset-rate pallet, are set up and
enabled for Westend and Rococo.

### Westend and Rococo runtimes.

Polkadot/Kusams/Rococo Treasury can accept proposals for `spends` of
various asset kinds by specifying the asset's location and ID.

#### Treasury Instance New Dispatchables:
- `spend(AssetKind, AssetBalance, Beneficiary, Option<ValidFrom>)` -
propose and approve a spend;
- `payout(SpendIndex)` - payout an approved spend or retry a failed
payout
- `check_payment(SpendIndex)` - check the status of a payout;
- `void_spend(SpendIndex)` - void previously approved spend;
> existing spend dispatchable renamed to spend_local

in this context, the `AssetKind` parameter contains the asset's location
and it's corresponding `asset_id`, for example:
`USDT` on `AssetHub`,
``` rust
location = MultiLocation(0, X1(Parachain(1000)))
asset_id = MultiLocation(0, X2(PalletInstance(50), GeneralIndex(1984)))
```

the `Beneficiary` parameter is a `MultiLocation` in the context of the
asset's location, for example
``` rust
// the Fellowship salary pallet's location / account
FellowshipSalaryPallet = MultiLocation(1, X2(Parachain(1001), PalletInstance(64)))
// or custom `AccountId`
Alice = MultiLocation(0, AccountId32(network: None, id: [1,...]))
```

the `AssetBalance` represents the amount of the `AssetKind` to be
transferred to the `Beneficiary`. For permission checks, the asset
amount is converted to the native amount and compared against the
maximum spendable amount determined by the commanding spend origin.

the `spend` dispatchable allows for batching spends with different
`ValidFrom` arguments, enabling milestone-based spending. If the
expectations tied to an approved spend are not met, it is possible to
void the spend later using the `void_spend` dispatchable.

Asset Rate Pallet provides the conversion rate from the `AssetKind` to
the native balance.

#### Asset Rate Instance Dispatchables:
- `create(AssetKind, Rate)` - initialize a conversion rate to the native
balance for the given asset
- `update(AssetKind, Rate)` - update the conversion rate to the native
balance for the given asset
- `remove(AssetKind)` - remove an existing conversion rate to the native
balance for the given asset

the pallet's dispatchables can be executed by the Root or Treasurer
origins.

### Treasury Pallet

Treasury Pallet can accept proposals for `spends` of various asset kinds
and pay them out through the implementation of the `Pay` trait.

New Dispatchables:
- `spend(Config::AssetKind, AssetBalance, Config::Beneficiary,
Option<ValidFrom>)` - propose and approve a spend;
- `payout(SpendIndex)` - payout an approved spend or retry a failed
payout;
- `check_payment(SpendIndex)` - check the status of a payout;
- `void_spend(SpendIndex)` - void previously approved spend;
> existing spend dispatchable renamed to spend_local

The parameters' types of the `spend` dispatchable exposed via the
pallet's `Config` and allows to propose and accept a spend of a certain
amount.

An approved spend can be claimed via the `payout` within the
`Config::SpendPeriod`. Clients provide an implementation of the `Pay`
trait which can pay an asset of the `AssetKind` to the `Beneficiary` in
`AssetBalance` units.

The implementation of the Pay trait might not have an immediate final
payment status, for example if implemented over `XCM` and the actual
transfer happens on a remote chain.

The `check_status` dispatchable can be executed to update the spend's
payment state and retry the `payout` if the payment has failed.

---------

Co-authored-by: joe petrowski <[email protected]>
Co-authored-by: command-bot <>
  • Loading branch information
muharem and joepetrowski authored Oct 7, 2023
1 parent 5a69126 commit cb944dc
Show file tree
Hide file tree
Showing 32 changed files with 2,212 additions and 285 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@ frame-system = { path = "../../../../../../substrate/frame/system", default-feat
pallet-balances = { path = "../../../../../../substrate/frame/balances", default-features = false}
pallet-assets = { path = "../../../../../../substrate/frame/assets", default-features = false}
pallet-asset-conversion = { path = "../../../../../../substrate/frame/asset-conversion", default-features = false}
pallet-treasury = { path = "../../../../../../substrate/frame/treasury", default-features = false}
pallet-asset-rate = { path = "../../../../../../substrate/frame/asset-rate", default-features = false}

# Polkadot
polkadot-core-primitives = { path = "../../../../../../polkadot/core-primitives", default-features = false}
polkadot-parachain-primitives = { path = "../../../../../../polkadot/parachain", default-features = false}
polkadot-runtime-common = { path = "../../../../../../polkadot/runtime/common" }
polkadot-runtime-parachains = { path = "../../../../../../polkadot/runtime/parachains" }
xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false}
xcm-builder = { package = "staging-xcm-builder", path = "../../../../../../polkadot/xcm/xcm-builder", default-features = false}
xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../polkadot/xcm/xcm-executor", default-features = false}
pallet-xcm = { path = "../../../../../../polkadot/xcm/pallet-xcm", default-features = false}

# Cumulus
parachains-common = { path = "../../../../common" }
asset-hub-westend-runtime = { path = "../../../../runtimes/assets/asset-hub-westend" }
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 = { path = "../../../../../xcm/xcm-emulator", default-features = false}
Expand All @@ -37,15 +44,21 @@ integration-tests-common = { path = "../../common", default-features = false}
[features]
runtime-benchmarks = [
"asset-hub-westend-runtime/runtime-benchmarks",
"cumulus-pallet-parachain-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"integration-tests-common/runtime-benchmarks",
"pallet-asset-conversion/runtime-benchmarks",
"pallet-asset-rate/runtime-benchmarks",
"pallet-assets/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-treasury/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
"parachains-common/runtime-benchmarks",
"polkadot-parachain-primitives/runtime-benchmarks",
"polkadot-runtime-common/runtime-benchmarks",
"polkadot-runtime-parachains/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
]
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ mod send;
mod set_xcm_versions;
mod swap;
mod teleport;
mod treasury;
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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 frame_support::traits::fungibles::{Create, Inspect, Mutate};
use integration_tests_common::constants::accounts::{ALICE, BOB};
use polkadot_runtime_common::impls::VersionedLocatableAsset;
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(37));
// treasury account on a sibling parachain.
let treasury_account =
asset_hub_westend_runtime::xcm_config::LocationToAccountId::convert_location(
&treasury_location,
)
.unwrap();
let asset_hub_location = MultiLocation::new(0, Parachain(AssetHubWestend::para_id().into()));
let root = <Westend as Chain>::RuntimeOrigin::root();
// asset kind to be spend from the treasury.
let asset_kind = VersionedLocatableAsset::V3 {
location: asset_hub_location,
asset_id: AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()),
};
// treasury spend beneficiary.
let alice: AccountId = Westend::account_id_of(ALICE);
let bob: AccountId = Westend::account_id_of(BOB);
let bob_signed = <Westend as Chain>::RuntimeOrigin::signed(bob.clone());

AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::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,);
});

Westend::execute_with(|| {
type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
type Treasury = <Westend as WestendPallet>::Treasury;
type AssetRate = <Westend as WestendPallet>::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(MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())).into()),
None,
));
// claim the spend.
assert_ok!(Treasury::payout(bob_signed.clone(), 0));

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

AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
type Assets = <AssetHubWestend as AssetHubWestendPallet>::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!(
AssetHubWestend,
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,);
});

Westend::execute_with(|| {
type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
type Treasury = <Westend as WestendPallet>::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!(
Westend,
vec![
RuntimeEvent::Treasury(pallet_treasury::Event::SpendProcessed { .. }) => {},
]
);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ decl_test_relay_chains! {
XcmPallet: westend_runtime::XcmPallet,
Sudo: westend_runtime::Sudo,
Balances: westend_runtime::Balances,
Treasury: westend_runtime::Treasury,
AssetRate: westend_runtime::AssetRate,
}
},
#[api_version(7)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ use xcm::latest::prelude::*;
use xcm_builder::{
AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses,
AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter,
DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FungiblesAdapter, IsConcrete,
LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId,
UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal,
EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NativeAsset,
NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents,
WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
};
use xcm_executor::{traits::WithOriginFilter, XcmExecutor};

Expand Down Expand Up @@ -75,6 +76,9 @@ pub type LocationToAccountId = (
SiblingParachainConvertsVia<Sibling, AccountId>,
// Straight up local `AccountId32` origins just alias directly to `AccountId`.
AccountId32Aliases<RelayNetwork, AccountId>,
// Foreign chain account alias into local accounts according to a hash of their standard
// description.
HashedDescription<AccountId, DescribeFamily<DescribePalletTerminal>>,
);

/// Means for transacting the native currency on this chain.
Expand Down Expand Up @@ -222,6 +226,9 @@ match_types! {
MultiLocation { parents: 1, interior: Here } |
MultiLocation { parents: 1, interior: X1(Plurality { .. }) }
};
pub type TreasuryPallet: impl Contains<MultiLocation> = {
MultiLocation { parents: 1, interior: X1(PalletInstance(37)) }
};
}

/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
Expand Down Expand Up @@ -449,8 +456,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, its pluralities (i.e. governance bodies) and treasury pallet get
// free execution.
AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, TreasuryPallet)>,
// Subscriptions for version tracking are OK.
AllowSubscriptionsFrom<Everything>,
),
Expand Down
7 changes: 7 additions & 0 deletions polkadot/runtime/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-featur
pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = false }
pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false }
pallet-treasury = { path = "../../../substrate/frame/treasury", default-features = false }
pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false }
pallet-election-provider-multi-phase = { path = "../../../substrate/frame/election-provider-multi-phase", default-features = false }
frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support", default-features = false }

Expand All @@ -50,6 +51,7 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac

slot-range-helper = { path = "slot_range_helper", default-features = false }
xcm = { package = "staging-xcm", path = "../../xcm", default-features = false }
xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false }

[dev-dependencies]
hex-literal = "0.4.1"
Expand All @@ -74,6 +76,7 @@ std = [
"inherents/std",
"libsecp256k1/std",
"log/std",
"pallet-asset-rate/std",
"pallet-authorship/std",
"pallet-balances/std",
"pallet-election-provider-multi-phase/std",
Expand All @@ -100,6 +103,7 @@ std = [
"sp-session/std",
"sp-staking/std",
"sp-std/std",
"xcm-builder/std",
"xcm/std",
]
runtime-benchmarks = [
Expand All @@ -109,6 +113,7 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"libsecp256k1/hmac",
"libsecp256k1/static-context",
"pallet-asset-rate/runtime-benchmarks",
"pallet-babe/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-election-provider-multi-phase/runtime-benchmarks",
Expand All @@ -121,12 +126,14 @@ runtime-benchmarks = [
"runtime-parachains/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
]
try-runtime = [
"frame-election-provider-support/try-runtime",
"frame-support-test/try-runtime",
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-asset-rate/try-runtime",
"pallet-authorship/try-runtime",
"pallet-babe?/try-runtime",
"pallet-balances/try-runtime",
Expand Down
Loading

0 comments on commit cb944dc

Please sign in to comment.