Treasury spends various asset kinds (#1333)

### 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 <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: command-bot <>
This commit is contained in:
Muharem
2023-10-07 19:32:35 +02:00
committed by GitHub
parent 5a6912606a
commit cb944dc548
32 changed files with 2213 additions and 286 deletions
+2 -1
View File
@@ -31,6 +31,7 @@ pub mod pay;
pub use misc::{
AssetId, Balance, BalanceStatus, ConversionFromAssetBalance, ConversionToAssetBalance,
ConvertRank, DepositConsequence, ExistenceRequirement, Fortitude, GetSalary, Locker, Precision,
Preservation, Provenance, Restriction, WithdrawConsequence, WithdrawReasons,
Preservation, Provenance, Restriction, UnityAssetBalanceConversion, WithdrawConsequence,
WithdrawReasons,
};
pub use pay::{Pay, PayFromAccount, PaymentStatus};
@@ -277,6 +277,26 @@ pub trait ConversionFromAssetBalance<AssetBalance, AssetId, OutBalance> {
balance: AssetBalance,
asset_id: AssetId,
) -> Result<OutBalance, Self::Error>;
/// Ensures that a conversion for the `asset_id` will be successful if done immediately after
/// this call.
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(asset_id: AssetId);
}
/// Implements [`ConversionFromAssetBalance`], enabling a 1:1 conversion of the asset balance
/// value to the balance.
pub struct UnityAssetBalanceConversion;
impl<AssetBalance, AssetId, OutBalance>
ConversionFromAssetBalance<AssetBalance, AssetId, OutBalance> for UnityAssetBalanceConversion
where
AssetBalance: Into<OutBalance>,
{
type Error = ();
fn from_asset_balance(balance: AssetBalance, _: AssetId) -> Result<OutBalance, Self::Error> {
Ok(balance.into())
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(_: AssetId) {}
}
/// Trait to handle NFT locking mechanism to ensure interactions with the asset can be implemented
@@ -23,7 +23,7 @@ use sp_core::{RuntimeDebug, TypedGet};
use sp_runtime::DispatchError;
use sp_std::fmt::Debug;
use super::{fungible, Balance, Preservation::Expendable};
use super::{fungible, fungibles, Balance, Preservation::Expendable};
/// Can be implemented by `PayFromAccount` using a `fungible` impl, but can also be implemented with
/// XCM/MultiAsset and made generic over assets.
@@ -107,3 +107,36 @@ impl<A: TypedGet, F: fungible::Mutate<A::Type>> Pay for PayFromAccount<F, A> {
#[cfg(feature = "runtime-benchmarks")]
fn ensure_concluded(_: Self::Id) {}
}
/// Simple implementation of `Pay` for assets which makes a payment from a "pot" - i.e. a single
/// account.
pub struct PayAssetFromAccount<F, A>(sp_std::marker::PhantomData<(F, A)>);
impl<A, F> frame_support::traits::tokens::Pay for PayAssetFromAccount<F, A>
where
A: TypedGet,
F: fungibles::Mutate<A::Type> + fungibles::Create<A::Type>,
{
type Balance = F::Balance;
type Beneficiary = A::Type;
type AssetKind = F::AssetId;
type Id = ();
type Error = DispatchError;
fn pay(
who: &Self::Beneficiary,
asset: Self::AssetKind,
amount: Self::Balance,
) -> Result<Self::Id, Self::Error> {
<F as fungibles::Mutate<_>>::transfer(asset, &A::get(), who, amount, Expendable)?;
Ok(())
}
fn check_payment(_: ()) -> PaymentStatus {
PaymentStatus::Success
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(_: &Self::Beneficiary, asset: Self::AssetKind, amount: Self::Balance) {
<F as fungibles::Create<_>>::create(asset.clone(), A::get(), true, amount).unwrap();
<F as fungibles::Mutate<_>>::mint_into(asset, &A::get(), amount).unwrap();
}
#[cfg(feature = "runtime-benchmarks")]
fn ensure_concluded(_: Self::Id) {}
}