diff --git a/Cargo.lock b/Cargo.lock index f049182c04286..30668271f101a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7464,6 +7464,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", + "pallet-assets", "pallet-balances", "pallet-utility", "parity-scale-codec", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6dc9841f6b44f..fea52fee6f587 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -35,7 +35,7 @@ use frame_support::{ parameter_types, traits::{ fungible::ItemOf, - tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount}, + tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount, PayFungibles}, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, WithdrawReasons, @@ -1106,6 +1106,10 @@ parameter_types! { impl pallet_treasury::Config for Runtime { type PalletId = TreasuryPalletId; + type AssetId = u32; + type AssetKind = pallet_treasury::SimpleAsset; + type Paymaster = PayFungibles; + type BalanceConverter = (); type Currency = Balances; type ApproveOrigin = EitherOfDiverse< EnsureRoot, @@ -1127,6 +1131,9 @@ impl pallet_treasury::Config for Runtime { type WeightInfo = pallet_treasury::weights::SubstrateWeight; type MaxApprovals = MaxApprovals; type SpendOrigin = EnsureWithSuccess, AccountId, MaxBalance>; + type MaxPaymentRetries = ConstU32<3>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } impl pallet_asset_rate::Config for Runtime { diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 9fe34b16029cb..dc2920a6d6561 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -26,7 +26,7 @@ use frame_support::{ assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize}, + traits::{tokens::PayFromAccount, ConstU32, ConstU64, OnInitialize}, PalletId, }; @@ -59,6 +59,8 @@ frame_support::construct_runtime!( parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub TreasuryAccount: u128 = Treasury::account_id(); + pub TreasuryAccount1: u128 = Treasury1::account_id(); } type Balance = u64; @@ -114,8 +116,19 @@ parameter_types! { pub static SpendLimit1: Balance = u64::MAX; } +pub struct NilBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl> pallet_treasury::BenchmarkHelper for NilBenchmarkHelper { + fn create_asset_kind(_id: u32) -> AssetKind { + ().into() + } +} + impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; + type AssetKind = (); + type Paymaster = PayFromAccount; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -131,10 +144,15 @@ impl pallet_treasury::Config for Test { type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_system::EnsureRootWithSuccess; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = NilBenchmarkHelper; } impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId2; + type AssetKind = (); + type Paymaster = PayFromAccount; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -150,6 +168,8 @@ impl pallet_treasury::Config for Test { type SpendFunds = Bounties1; type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_system::EnsureRootWithSuccess; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = NilBenchmarkHelper; } parameter_types! { diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index a8f0e16ea2136..3a1e965e198f0 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -26,11 +26,10 @@ use frame_support::{ assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize}, + traits::{tokens::PayFromAccount, ConstU32, ConstU64, OnInitialize}, weights::Weight, PalletId, }; - use sp_core::H256; use sp_runtime::{ testing::Header, @@ -113,10 +112,15 @@ parameter_types! { pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const SpendLimit: Balance = u64::MAX; + pub TreasuryAccount: u128 = Treasury::account_id(); } impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; + type AssetId = (); + type AssetKind = u64; + type Paymaster = PayFromAccount; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -132,6 +136,9 @@ impl pallet_treasury::Config for Test { type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_system::EnsureRootWithSuccess; + type MaxPaymentRetries = ConstU32<3>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = pallet_treasury::NilBenchmarkHelper; } parameter_types! { // This will be 50% of the bounty fee. diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 6961e69ba5750..90f7888b3c919 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -163,7 +163,9 @@ pub trait EnsureOriginWithArg { /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin(a: &Argument) -> Result; + fn try_successful_origin(a: &Argument) -> Result{ + Err(()) + } } pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 253b49c6671f8..2534be0615940 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -33,4 +33,4 @@ pub use misc::{ ConvertRank, DepositConsequence, ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, Preservation, Provenance, Restriction, WithdrawConsequence, WithdrawReasons, }; -pub use pay::{Pay, PayFromAccount, PaymentStatus}; +pub use pay::{Pay, PayFromAccount, PayFungibles, PaymentStatus}; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index baf3fd5f35464..0fda4166bda03 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -262,6 +262,18 @@ pub trait ConversionFromAssetBalance { ) -> Result; } +impl> + ConversionFromAssetBalance for () +{ + type Error = (); + fn from_asset_balance( + balance: InBalance, + _asset_id: AssetId, + ) -> Result { + Ok(balance.into()) + } +} + /// Trait to handle NFT locking mechanism to ensure interactions with the asset can be implemented /// downstream to extend logic of Uniques/Nfts current functionality. pub trait Locker { diff --git a/frame/support/src/traits/tokens/pay.rs b/frame/support/src/traits/tokens/pay.rs index 78f8e7b873480..b2d6b7130b4e4 100644 --- a/frame/support/src/traits/tokens/pay.rs +++ b/frame/support/src/traits/tokens/pay.rs @@ -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,29 @@ impl> Pay for PayFromAccount { #[cfg(feature = "runtime-benchmarks")] fn ensure_concluded(_: Self::Id) {} } + +pub struct PayFungibles(sp_std::marker::PhantomData<(F, A)>); +impl + fungibles::Inspect> Pay + for PayFungibles +{ + type Balance = F::Balance; + type Beneficiary = A::Type; + type AssetKind = F::AssetId; + type Id = (); + type Error = DispatchError; + fn pay( + who: &Self::Beneficiary, + asset_id: Self::AssetKind, + amount: Self::Balance, + ) -> Result { + >::transfer(asset_id, &A::get(), who, amount, Expendable)?; + Ok(()) + } + fn check_payment(_: ()) -> PaymentStatus { + PaymentStatus::Success + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::Beneficiary, _: Self::Balance) {} + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(_: Self::Id) {} +} diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index 25043b413db07..53ac127c2d1d6 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -32,7 +32,7 @@ use frame_support::{ pallet_prelude::GenesisBuild, parameter_types, storage::StoragePrefixedMap, - traits::{ConstU32, ConstU64, SortedMembers, StorageVersion}, + traits::{tokens::PayFromAccount, ConstU32, ConstU64, SortedMembers, StorageVersion}, PalletId, }; @@ -59,6 +59,8 @@ frame_support::construct_runtime!( parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub TreasuryAccount: u128 = Treasury::account_id(); + pub TreasuryAccount1: u128 = Treasury1::account_id(); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; @@ -132,8 +134,13 @@ parameter_types! { pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); } + impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; + type AssetId = (); + type AssetKind = u64; + type Paymaster = PayFromAccount; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -149,10 +156,17 @@ impl pallet_treasury::Config for Test { type SpendFunds = (); type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type MaxPaymentRetries = ConstU32<3>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = pallet_treasury::NilBenchmarkHelper; } impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId2; + type AssetId = (); + type AssetKind = u64; + type Paymaster = PayFromAccount; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -168,6 +182,9 @@ impl pallet_treasury::Config for Test { type SpendFunds = (); type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_support::traits::NeverEnsureOrigin; + type MaxPaymentRetries = ConstU32<3>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = pallet_treasury::NilBenchmarkHelper; } parameter_types! { diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 5d8617f76d849..a7193f7fdedd8 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -24,6 +24,7 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } +pallet-assets = { version = "4.0.0-dev", default-features = false, path = "../assets" } sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } @@ -40,6 +41,7 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", + "pallet-assets/std", "scale-info/std", "serde", "sp-runtime/std", diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index a3761083e4faa..bc60d94c7fd61 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -31,6 +31,10 @@ use frame_system::RawOrigin; const SEED: u32 = 0; +fn default_asset_kind, I: 'static>() -> T::AssetKind { + T::BenchmarkHelper::create_asset_kind(0) +} + // Create the pre-requisite information needed to create a treasury `propose_spend`. fn setup_proposal, I: 'static>( u: u32, @@ -43,6 +47,19 @@ fn setup_proposal, I: 'static>( (caller, value, beneficiary_lookup) } +// Create the pre-requisite information needed to create a treasury `spend` call. +fn setup_spend_call, I: 'static>( + u: u32, +) -> (T::AssetKind, T::AccountId, PayBalanceOf, AccountIdLookupOf) { + let caller = account("caller", u, SEED); + + let value: PayBalanceOf = SEED.into(); + let beneficiary = account("beneficiary", u, SEED); + let beneficiary_lookup = T::Lookup::unlookup(beneficiary); + let asset_kind = default_asset_kind::(); + (asset_kind, caller, value, beneficiary_lookup) +} + // Create proposals that are approved for use in `on_initialize`. fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'static str> { for i in 0..n { @@ -55,6 +72,16 @@ fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'s Ok(()) } +// Create pending payments that are approved for use in `on_initialize`. +fn create_pending_payments, I: 'static>(n: u32) -> Result<(), &'static str> { + for i in 0..n { + let (asset_kind, _, value, lookup) = setup_spend_call::(i); + Treasury::::spend(RawOrigin::Root.into(), asset_kind, value, lookup)?; + } + ensure!(>::count() == n, "Enpty pending payments storage"); + Ok(()) +} + fn setup_pot_account, I: 'static>() { let pot_account = Treasury::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); @@ -68,11 +95,11 @@ fn assert_last_event, I: 'static>(generic_event: >:: benchmarks_instance_pallet! { // This benchmark is short-circuited if `SpendOrigin` cannot provide // a successful origin, in which case `spend` is un-callable and can use weight=0. - spend { + spend_local { let (_, value, beneficiary_lookup) = setup_proposal::(SEED); let origin = T::SpendOrigin::try_successful_origin(); let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); - let call = Call::::spend { amount: value, beneficiary: beneficiary_lookup }; + let call = Call::::spend_local { amount: value, beneficiary: beneficiary_lookup }; }: { if let Ok(origin) = origin.clone() { call.dispatch_bypass_filter(origin)?; @@ -84,6 +111,24 @@ benchmarks_instance_pallet! { } } + // This benchmark is short-circuited if `SpendOrigin` cannot provide + // a successful origin, in which case `spend` is un-callable and can use weight=0. + spend { + let (asset_kind, _, value, beneficiary_lookup) = setup_spend_call::(SEED); + let origin = T::SpendOrigin::try_successful_origin(); + let beneficiary: T::AccountId = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); + let call = Call::::spend { asset_kind, amount: value, beneficiary: beneficiary_lookup }; + }: { + if let Ok(origin) = origin.clone() { + call.dispatch_bypass_filter(origin)?; + } + } + verify { + if origin.is_ok() { + assert_last_event::(Event::PaymentQueued{ pending_payment_index: 0, asset_kind, amount: value, beneficiary }.into()) + } + } + propose_spend { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); // Whitelist caller account from further DB operations. @@ -138,5 +183,12 @@ benchmarks_instance_pallet! { Treasury::::on_initialize(T::BlockNumber::zero()); } + on_initialize_pending_payments { + let p in 0 .. T::MaxApprovals::get(); + create_pending_payments::(p)?; + }: { + Treasury::::on_initialize(T::BlockNumber::zero()); + } + impl_benchmark_test_suite!(Treasury, crate::tests::new_test_ext(), crate::tests::Test); } diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 847ff96a7a78b..a6b575ac2ba1a 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -73,10 +73,12 @@ use sp_runtime::{ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use frame_support::{ - print, + log, print, traits::{ - Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced, - ReservableCurrency, WithdrawReasons, + tokens::{AssetId, ConversionFromAssetBalance, Pay, PaymentStatus}, + Currency, + ExistenceRequirement::KeepAlive, + Get, Imbalance, OnUnbalanced, ReservableCurrency, WithdrawReasons, }, weights::Weight, PalletId, @@ -85,6 +87,9 @@ use frame_support::{ pub use pallet::*; pub use weights::WeightInfo; +const LOG_TARGET: &str = "runtime::treasury"; + +pub type PayBalanceOf = <>::Paymaster as Pay>::Balance; pub type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; pub type PositiveImbalanceOf = <>::Currency as Currency< @@ -116,9 +121,45 @@ pub trait SpendFunds, I: 'static = ()> { ); } +pub trait Asset { + fn asset_kind(&self) -> AssetId; + fn amount(&self) -> Fungibility; +} + +/// An asset implementation for the unit. Useful in cases where the asset_id is not needed +impl Asset<(), Self> for u64 { + fn asset_kind(&self) -> () { + ().into() + } + fn amount(&self) -> Self { + *self + } +} + +/// SimpleAsset is an asset adapter where we can specify the AssetId and fungibility. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub struct SimpleAsset(AssetId, Fungibility); + +impl Asset for SimpleAsset { + fn asset_kind(&self) -> A { + let SimpleAsset(asset_kind, _val) = self; + *asset_kind + } + fn amount(&self) -> F { + let SimpleAsset(_asset_kind, val) = self; + (*val).into() + } +} + /// An index of a proposal. Just a `u32`. pub type ProposalIndex = u32; +/// An index of a pending payment. Just a `u32`. +pub type PendingPaymentIndex = u32; + +/// An index for the number tor times a payment is retried. Just a u32 +pub type RetryIndex = u32; + /// A spending proposal. #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] @@ -133,6 +174,24 @@ pub struct Proposal { bond: Balance, } +/// PendingPayment represents treasury spend payment which has not yet succeeded. +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub struct PendingPayment { + /// The account to whom the payment should be made if the proposal is accepted. + beneficiary: AccountId, + /// The asset_kind of the amount to be paid + asset_kind: AssetKind, + /// The (total) amount that should be paid. + // value: AssetBalance, + /// The amount to be paid, but normalized to the native asset class + normalized_value: Balance, + /// The identifier for tracking the status of a payment which is in flight. + payment_id: Option, + /// The number of times this payment has been attempted + tries: RetryIndex, +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -142,6 +201,25 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn create_asset_kind(id: u32) -> AssetKind; + } + #[cfg(feature = "runtime-benchmarks")] + impl> BenchmarkHelper for () { + fn create_asset_kind(_id: u32) -> AssetKind { + 0u32.into() + } + } + #[cfg(feature = "runtime-benchmarks")] + pub struct NilBenchmarkHelper; + #[cfg(feature = "runtime-benchmarks")] + impl> BenchmarkHelper for NilBenchmarkHelper { + fn create_asset_kind(_id: u32) -> AssetKind { + ().into() + } + } + #[pallet::config] pub trait Config: frame_system::Config { /// The staking balance. @@ -153,6 +231,25 @@ pub mod pallet { /// Origin from which rejections must come. type RejectOrigin: EnsureOrigin; + /// The identifier for what asset should be spent. + type AssetId: AssetId; + + // TODO: replace with individual types + type AssetKind: Asset> + AssetId; + + /// The means by which we can make payments to beneficiaries. + /// This can be implmented over fungibles or some other means. + type Paymaster: Pay; + + type MaxPaymentRetries: Get; + + // The means of knowing what is the equivalent native Balance of a given asset id Balance. + type BalanceConverter: ConversionFromAssetBalance< + PayBalanceOf, + Self::AssetKind, + BalanceOf, + >; + /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -204,6 +301,10 @@ pub mod pallet { /// process. The `Success` value is the maximum amount that this origin is allowed to /// spend at a time. type SpendOrigin: EnsureOrigin>; + + /// Helper trait for benchmarks. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; } /// Number of proposals that have been made. @@ -222,6 +323,29 @@ pub mod pallet { OptionQuery, >; + /// PendingPaymentsInbox that have not yet processed or are not yet successful. + /// When a `PendingPayment` is processed or paid out, it is moved to the PendingPayments + /// storage where it is monitored until it is successful. + #[pallet::storage] + pub type PendingPaymentsInbox, I: 'static = ()> = CountedStorageMap< + _, + Twox64Concat, + PendingPaymentIndex, + PendingPayment, T::AssetKind, ::Id>, + OptionQuery, + >; + + /// PendingPayments that have are not yet successful. When a `PendingPayment` is verified to be + /// successful, it is deleted from storage. + #[pallet::storage] + pub type PendingPayments, I: 'static = ()> = CountedStorageMap< + _, + Twox64Concat, + PendingPaymentIndex, + PendingPayment, T::AssetKind, ::Id>, + OptionQuery, + >; + /// The amount which has been reported as inactive to Currency. #[pallet::storage] pub type Deactivated, I: 'static = ()> = @@ -280,6 +404,14 @@ pub mod pallet { Rollover { rollover_balance: BalanceOf }, /// Some funds have been deposited. Deposit { value: BalanceOf }, + /// We have ended a spend period and will now allocate funds. + ProcessingProposals { waiting_proposals: ProposalIndex }, + /// Spending has finished; this is the number of proposals rolled over till next + /// T::SpendPeriod. + RolloverPayments { + rollover_payments: ProposalIndex, + allocated_payments: PendingPaymentIndex, + }, /// A new spend proposal has been approved. SpendApproved { proposal_index: ProposalIndex, @@ -288,6 +420,33 @@ pub mod pallet { }, /// The inactive funds of the pallet have been updated. UpdatedInactive { reactivated: BalanceOf, deactivated: BalanceOf }, + /// The payment has been queued to be paid out at the next Spend Period + PaymentQueued { + pending_payment_index: PendingPaymentIndex, + asset_kind: T::AssetKind, + beneficiary: T::AccountId, + }, + /// The payment has been processed but awaiting payment status. + PaymentTriggered { + pending_payment_index: PendingPaymentIndex, + asset_kind: T::AssetKind, + payment_id: ::Id, + tries: RetryIndex, + }, + /// The proposal was paid successfully + PaymentSuccess { + pending_payment_index: PendingPaymentIndex, + asset_kind: T::AssetKind, + payment_id: ::Id, + tries: RetryIndex, + }, + /// The proposal payment failed. Payment will be retried in next spend period. + PaymentFailure { + pending_payment_index: PendingPaymentIndex, + asset_kind: T::AssetKind, + payment_id: Option<::Id>, + tries: RetryIndex, + }, } /// Error for the treasury pallet. @@ -304,6 +463,10 @@ pub mod pallet { InsufficientPermission, /// Proposal has not been approved. ProposalNotApproved, + /// Unable to convert asset to native balance + BalanceConversionFailed, + /// Invalid Spend Request + InvalidSpendRequest, } #[pallet::hooks] @@ -322,10 +485,9 @@ pub mod pallet { deactivated: pot, }); } - // Check to see if we should spend some funds! if (n % T::SpendPeriod::get()).is_zero() { - Self::spend_funds() + Self::spend_funds().saturating_add(Self::spend_funds_local()) } else { Weight::zero() } @@ -415,7 +577,8 @@ pub mod pallet { Ok(()) } - /// Propose and approve a spend of treasury funds. + /// Propose and approve a spend of treasury funds. This is a legacy extrinsic which might be + /// removed in the future. /// /// - `origin`: Must be `SpendOrigin` with the `Success` value being at least `amount`. /// - `amount`: The amount to be transferred from the treasury to the `beneficiary`. @@ -424,30 +587,26 @@ pub mod pallet { /// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the /// beneficiary. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::spend())] - pub fn spend( + #[pallet::weight(T::WeightInfo::spend_local())] + pub fn spend_local( origin: OriginFor, #[pallet::compact] amount: BalanceOf, beneficiary: AccountIdLookupOf, ) -> DispatchResult { let max_amount = T::SpendOrigin::ensure_origin(origin)?; ensure!(amount <= max_amount, Error::::InsufficientPermission); - with_context::>, _>(|v| { let context = v.or_default(); - // We group based on `max_amount`, to dinstinguish between different kind of // origins. (assumes that all origins have different `max_amount`) // // Worst case is that we reject some "valid" request. let spend = context.spend_in_context.entry(max_amount).or_default(); - // Ensure that we don't overflow nor use more than `max_amount` if spend.checked_add(&amount).map(|s| s > max_amount).unwrap_or(true) { Err(Error::::InsufficientPermission) } else { *spend = spend.saturating_add(amount); - Ok(()) } }) @@ -464,12 +623,75 @@ pub mod pallet { bond: Default::default(), }; Proposals::::insert(proposal_index, proposal); - ProposalCount::::put(proposal_index + 1); + ProposalCount::::put(proposal_index.saturating_add(1)); Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary }); Ok(()) } + /// Propose and approve a spend of treasury funds. + /// + /// - `origin`: Must be `T::SpendOrigin` with the `Success` value being at least `amount`. + /// - `asset_kind`: An indicator of the specific asset class which should be spent + /// - `amount`: The amount to be transferred from the treasury to the `beneficiary`. + /// - `beneficiary`: The destination account for the transfer. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::spend())] + pub fn spend( + origin: OriginFor, + assets: Vec, + beneficiary: AccountIdLookupOf, + ) -> DispatchResult { + let max_amount = T::SpendOrigin::ensure_origin(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + for asset in assets { + let normalized_amount = + T::BalanceConverter::from_asset_balance(asset.amount(), asset.clone()) + .map_err(|_| Error::::BalanceConversionFailed)?; + ensure!(normalized_amount <= max_amount, Error::::InsufficientPermission); + + with_context::>, _>(|v| { + let context = v.or_default(); + + // We group based on `max_amount`, to dinstinguish between different kind of + // origins. (assumes that all origins have different `max_amount`) + // + // Worst case is that we reject some "valid" request. + let spend = context.spend_in_context.entry(max_amount).or_default(); + + // Ensure that we don't overflow nor use more than `max_amount` + if spend.checked_add(&normalized_amount).map(|s| s > max_amount).unwrap_or(true) + { + Err(Error::::InsufficientPermission) + } else { + *spend = spend.saturating_add(normalized_amount); + + Ok(()) + } + }) + .unwrap_or(Ok(()))?; + + let pending_payment = PendingPayment { + asset_kind: asset.clone(), + beneficiary: beneficiary.clone(), + normalized_value: normalized_amount, + payment_id: None, + tries: 0, + }; + + let next_index = PendingPaymentsInbox::::count(); + PendingPaymentsInbox::::insert(next_index, pending_payment); + + Self::deposit_event(Event::PaymentQueued { + pending_payment_index: next_index, + asset_kind: asset, + beneficiary: beneficiary.clone(), + }); + } + Ok(()) + } + /// Force a previously approved proposal to be removed from the approval queue. /// The original deposit will no longer be returned. /// @@ -525,9 +747,159 @@ impl, I: 'static> Pallet { r } - /// Spend some money! returns number of approvals before spend. + /// Spend_funds is triggered periodically and uses the `T::Paymaster` to payout all spend + /// requests in the `PendingPayments` storage map. + pub fn check_and_retry_payments() -> Weight { + let mut total_weight = Weight::zero(); + let pending_payments_len = PendingPayments::::count(); + + Self::deposit_event(Event::ProcessingProposals { waiting_proposals: pending_payments_len }); + + for key in PendingPayments::::iter_keys() { + if let Some(mut p) = PendingPayments::::get(key) { + if p.tries > T::MaxPaymentRetries::get() { + // Skip further processing of payments which have hit the maximum number of + // retures + continue + } + match p.payment_id { + None => match T::Paymaster::pay( + &p.beneficiary, + p.asset_kind.asset_kind(), + p.asset_kind.amount(), + ) { + Ok(id) => { + p.tries = p.tries.saturating_add(1); + p.payment_id = Some(id); + Self::deposit_event(Event::PaymentTriggered { + pending_payment_index: key, + asset_kind: p.asset_kind.clone(), + payment_id: id, + tries: p.tries, + }); + PendingPayments::::set(key, Some(p)); + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Paymaster::pay failed for PendingPayment with index: {:?} and error: {:?}", key, err); + Self::deposit_event(Event::PaymentFailure { + pending_payment_index: key, + asset_kind: p.asset_kind.clone(), + payment_id: None, + tries: p.tries, + }); + p.tries = p.tries.saturating_add(1); + PendingPayments::::set(key, Some(p)); + }, + }, + Some(payment_id) => match T::Paymaster::check_payment(payment_id) { + PaymentStatus::Failure | PaymentStatus::Unknown => { + log::debug!( + target: LOG_TARGET, + "Paymaster::pay failed for PendingPayment with index: {:?}", + key + ); + + p.tries = p.tries.saturating_add(1); + if p.tries < T::MaxPaymentRetries::get() { + // Force the payment to none, so a fresh payment is sent during the + // next T::CheckAndRetryPeriod. + p.payment_id = None; + } + + Self::deposit_event(Event::PaymentFailure { + pending_payment_index: key, + asset_kind: p.asset_kind.clone(), + payment_id: Some(payment_id), + tries: p.tries, + }); + PendingPayments::::set(key, Some(p)); + }, + PaymentStatus::Success => { + PendingPayments::::remove(key); + Self::deposit_event(Event::PaymentSuccess { + pending_payment_index: key, + asset_kind: p.asset_kind, + payment_id, + tries: p.tries, + }); + }, + // PaymentStatus::InProgress and PaymentStatus::Unknown indicate that the + // proposal status is inconclusive, and might still be successful or failed + // in the future. + PaymentStatus::InProgress => {}, + }, + } + } + } + + total_weight = total_weight + .saturating_add(T::WeightInfo::on_initialize_pending_payments(pending_payments_len)); + total_weight + } + + /// Spend_funds is triggered periodically and uses the `T::Paymaster` to payout all spend + /// requests in the `PendingPayments` storage map. pub fn spend_funds() -> Weight { let mut total_weight = Weight::zero(); + let mut total_spent = BalanceOf::::zero(); + let mut missed_payments: u32 = 0; + let pending_payments_len = PendingPayments::::count(); + + Self::deposit_event(Event::ProcessingProposals { waiting_proposals: pending_payments_len }); + + for key in PendingPaymentsInbox::::iter_keys() { + if let Some(mut p) = PendingPaymentsInbox::::get(key) { + match p.payment_id { + None => match T::Paymaster::pay( + &p.beneficiary, + p.asset_kind.asset_kind(), + p.asset_kind.amount(), + ) { + Ok(id) => { + total_spent = total_spent.saturating_add(p.normalized_value); + Self::deposit_event(Event::PaymentTriggered { + pending_payment_index: key, + asset_kind: p.asset_kind.clone(), + payment_id: id, + tries: p.tries.saturating_add(1), + }); + PendingPayments::::insert(key, p); + PendingPaymentsInbox::::remove(key); + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Paymaster::pay failed for PendingPayment with index: {:?} and error: {:?}", key, err); + missed_payments = missed_payments.saturating_add(1); + Self::deposit_event(Event::PaymentFailure { + pending_payment_index: key, + asset_kind: p.asset_kind.clone(), + payment_id: None, + tries: p.tries, + }); + p.tries = p.tries.saturating_add(1); + // Insert it into `T::PendingPayments` to be retried. + PendingPayments::::insert(key, p); + PendingPaymentsInbox::::remove(key); + }, + }, + Some(_payment_id) => unreachable!(), + } + } + } + + total_weight = total_weight + .saturating_add(T::WeightInfo::on_initialize_pending_payments(pending_payments_len)); + + Self::deposit_event(Event::RolloverPayments { + rollover_payments: missed_payments, + allocated_payments: pending_payments_len.saturating_sub(missed_payments), + }); + + total_weight + } + + /// Spend some money! returns number of approvals before spend. + pub fn spend_funds_local() -> Weight { + let mut total_weight = Weight::zero(); let mut budget_remaining = Self::pot(); Self::deposit_event(Event::Spending { budget_remaining }); @@ -568,7 +940,8 @@ impl, I: 'static> Pallet { proposals_approvals_len }); - total_weight += T::WeightInfo::on_initialize_proposals(proposals_len); + total_weight = + total_weight.saturating_add(T::WeightInfo::on_initialize_proposals(proposals_len)); // Call Runtime hooks to external pallet using treasury to compute spend funds. T::SpendFunds::spend_funds( diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 0659c2f5941b1..f16d50aa8dbe4 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -19,19 +19,24 @@ #![cfg(test)] -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup}, -}; - use frame_support::{ assert_err_ignore_postinfo, assert_noop, assert_ok, pallet_prelude::GenesisBuild, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize}, + traits::{ + fungibles::*, + tokens::{pay::PayFungibles, Fortitude::Polite, Preservation::Expendable}, + AsEnsureOriginWithArg, ConstU32, ConstU64, OnInitialize, + }, PalletId, }; +use frame_system::EnsureRoot; +use pallet_assets; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup}, +}; use super::*; use crate as treasury; @@ -51,6 +56,7 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Treasury: treasury::{Pallet, Call, Storage, Config, Event}, Utility: pallet_utility, + Assets: pallet_assets::{Pallet, Call, Config, Storage, Event}, } ); @@ -80,6 +86,7 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = ConstU32<16>; } + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); @@ -103,10 +110,38 @@ impl pallet_utility::Config for Test { type WeightInfo = (); } +type AssetId = u32; +type BalanceU64 = u64; + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = BalanceU64; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = AssetId; + type AssetIdParameter = AssetId; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ConstU64<2>; + type AssetAccountDeposit = ConstU64<2>; + type MetadataDepositBase = ConstU64<0>; + type MetadataDepositPerByte = ConstU64<0>; + type ApprovalDeposit = ConstU64<0>; + type StringLimit = ConstU32<20>; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub TreasuryAccount: u128 = Treasury::account_id(); } pub struct TestSpendOrigin; impl frame_support::traits::EnsureOrigin for TestSpendOrigin { @@ -127,8 +162,40 @@ impl frame_support::traits::EnsureOrigin for TestSpendOrigin { } } +// #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +// pub struct NilAssetKind(u32); + +// impl, I: 'static> Asset for NilAssetKind { +// fn asset_kind(&self) -> T::AssetId { +// ().into() +// } +// fn amount(&self) -> PayBalanceOf { +// let NilAssetKind(val) = self; +// (*val).into() +// } +// } + +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub struct SimpleAsset(AssetId, Fungibility); + +impl Asset for SimpleAsset { + fn asset_kind(&self) -> A { + let SimpleAsset(asset_kind, _val) = self; + *asset_kind + } + fn amount(&self) -> F { + let SimpleAsset(_asset_kind, val) = self; + (*val).into() + } +} + impl Config for Test { type PalletId = TreasuryPalletId; + type AssetId = AssetId; + // TODO: can that AssetId be a type argument of SimpleAsset instead? + type AssetKind = SimpleAsset>; + type Paymaster = PayFungibles; + type BalanceConverter = (); type Currency = pallet_balances::Pallet; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; @@ -144,6 +211,9 @@ impl Config for Test { type SpendFunds = (); type MaxApprovals = ConstU32<100>; type SpendOrigin = TestSpendOrigin; + type MaxPaymentRetries = ConstU32<3>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -166,41 +236,67 @@ fn genesis_config_works() { }); } +#[test] +fn spend_local_origin_permissioning_works() { + new_test_ext().execute_with(|| { + assert_noop!(Treasury::spend_local(RuntimeOrigin::signed(1), 1, 1), BadOrigin); + assert_noop!( + Treasury::spend_local(RuntimeOrigin::signed(10), 6, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend_local(RuntimeOrigin::signed(11), 11, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend_local(RuntimeOrigin::signed(12), 21, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend_local(RuntimeOrigin::signed(13), 51, 1), + Error::::InsufficientPermission + ); + }); +} + #[test] fn spend_origin_permissioning_works() { new_test_ext().execute_with(|| { - assert_noop!(Treasury::spend(RuntimeOrigin::signed(1), 1, 1), BadOrigin); assert_noop!( - Treasury::spend(RuntimeOrigin::signed(10), 6, 1), + Treasury::spend(RuntimeOrigin::signed(1), vec![SimpleAsset(0, 1)], 1), + BadOrigin + ); + assert_noop!( + Treasury::spend(RuntimeOrigin::signed(10), vec![SimpleAsset(0, 6)], 1), Error::::InsufficientPermission ); assert_noop!( - Treasury::spend(RuntimeOrigin::signed(11), 11, 1), + Treasury::spend(RuntimeOrigin::signed(11), vec![SimpleAsset(0, 11)], 1), Error::::InsufficientPermission ); assert_noop!( - Treasury::spend(RuntimeOrigin::signed(12), 21, 1), + Treasury::spend(RuntimeOrigin::signed(12), vec![SimpleAsset(0, 21)], 1), Error::::InsufficientPermission ); assert_noop!( - Treasury::spend(RuntimeOrigin::signed(13), 51, 1), + Treasury::spend(RuntimeOrigin::signed(13), vec![SimpleAsset(0, 51)], 1), Error::::InsufficientPermission ); }); } #[test] -fn spend_origin_works() { +fn spend_local_origin_works() { new_test_ext().execute_with(|| { // Check that accumulate works when we have Some value in Dummy already. Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), 10, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), 20, 6)); - assert_ok!(Treasury::spend(RuntimeOrigin::signed(13), 50, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(11), 10, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6)); + assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6)); >::on_initialize(1); assert_eq!(Balances::free_balance(6), 0); @@ -211,6 +307,32 @@ fn spend_origin_works() { }); } +#[test] +fn spend_origin_works() { + new_test_ext().execute_with(|| { + // Check that accumulate works when we have Some value in Dummy already. + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, Treasury::account_id(), true, 1)); + assert_ok!(Assets::mint_into(0, &Treasury::account_id(), 100)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), vec![SimpleAsset(0, 5)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), vec![SimpleAsset(0, 5)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), vec![SimpleAsset(0, 5)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), vec![SimpleAsset(0, 5)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), vec![SimpleAsset(0, 10)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), vec![SimpleAsset(0, 20)], 6)); + assert_ok!(Treasury::spend(RuntimeOrigin::signed(13), vec![SimpleAsset(0, 50)], 6)); + + // Treasury account should still have funds until next T::SpendPeriod + assert_eq!(Assets::reducible_balance(0, &Treasury::account_id(), Expendable, Polite), 100); + assert_eq!(Assets::reducible_balance(0, &6, Expendable, Polite), 0); + >::on_initialize(1); + assert_eq!(Assets::reducible_balance(0, &Treasury::account_id(), Expendable, Polite), 100); + + >::on_initialize(2); + assert_eq!(Assets::reducible_balance(0, &Treasury::account_id(), Expendable, Polite), 0); + assert_eq!(Assets::reducible_balance(0, &6, Expendable, Polite), 100); + }); +} + #[test] fn minting_works() { new_test_ext().execute_with(|| { @@ -454,7 +576,7 @@ fn max_approvals_limited() { Balances::make_free_balance_be(&Treasury::account_id(), u64::MAX); Balances::make_free_balance_be(&0, u64::MAX); - for _ in 0..::MaxApprovals::get() { + for _ in 0..<::MaxApprovals as sp_core::TypedGet>::get() { assert_ok!(Treasury::propose_spend(RuntimeOrigin::signed(0), 100, 3)); assert_ok!(Treasury::approve_proposal(RuntimeOrigin::root(), 0)); } @@ -486,14 +608,45 @@ fn remove_already_removed_approval_fails() { }); } +#[test] +fn spending_local_in_batch_respects_max_total() { + new_test_ext().execute_with(|| { + // Respect the `max_total` for the given origin. + assert_ok!(RuntimeCall::from(UtilityCall::batch_all { + calls: vec![ + RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }), + RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 101 }) + ] + }) + .dispatch(RuntimeOrigin::signed(10))); + + assert_err_ignore_postinfo!( + RuntimeCall::from(UtilityCall::batch_all { + calls: vec![ + RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }), + RuntimeCall::from(TreasuryCall::spend_local { amount: 4, beneficiary: 101 }) + ] + }) + .dispatch(RuntimeOrigin::signed(10)), + Error::::InsufficientPermission + ); + }) +} + #[test] fn spending_in_batch_respects_max_total() { new_test_ext().execute_with(|| { // Respect the `max_total` for the given origin. assert_ok!(RuntimeCall::from(UtilityCall::batch_all { calls: vec![ - RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }), - RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 101 }) + RuntimeCall::from(TreasuryCall::spend { + assets: vec![SimpleAsset(0, 2)], + beneficiary: 100 + }), + RuntimeCall::from(TreasuryCall::spend { + assets: vec![SimpleAsset(0, 2)], + beneficiary: 101 + }) ] }) .dispatch(RuntimeOrigin::signed(10))); @@ -501,8 +654,14 @@ fn spending_in_batch_respects_max_total() { assert_err_ignore_postinfo!( RuntimeCall::from(UtilityCall::batch_all { calls: vec![ - RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }), - RuntimeCall::from(TreasuryCall::spend { amount: 4, beneficiary: 101 }) + RuntimeCall::from(TreasuryCall::spend { + assets: vec![SimpleAsset(0, 2)], + beneficiary: 100 + }), + RuntimeCall::from(TreasuryCall::spend { + assets: vec![SimpleAsset(0, 4)], + beneficiary: 101 + }) ] }) .dispatch(RuntimeOrigin::signed(10)), diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index edf1a674f73ff..0b8403e75043d 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -48,17 +48,30 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_treasury. pub trait WeightInfo { + fn spend_local() -> Weight; fn spend() -> Weight; fn propose_spend() -> Weight; fn reject_proposal() -> Weight; fn approve_proposal(p: u32, ) -> Weight; fn remove_approval() -> Weight; fn on_initialize_proposals(p: u32, ) -> Weight; + fn on_initialize_pending_payments(p: u32, ) -> Weight; } /// Weights for pallet_treasury using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// Storage: Treasury PendingPayments (r:0 w:1) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + fn spend_local() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3376` + // Minimum execution time: 17_010_000 picoseconds. + Weight::from_parts(17_247_000, 3376) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } /// Storage: Treasury ProposalCount (r:1 w:1) /// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// Storage: Treasury Approvals (r:1 w:1) @@ -152,10 +165,46 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into())) } + /// Storage: Treasury Deactivated (r:1 w:1) + /// Proof: Treasury Deactivated (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Treasury Approvals (r:1 w:1) + /// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) + /// Storage: Treasury Proposals (r:100 w:100) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) + /// The range of component `p` is `[0, 100]`. + fn on_initialize_pending_payments(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `387 + p * (251 ±0)` + // Estimated: `7255 + p * (7789 ±0)` + // Minimum execution time: 43_781_000 picoseconds. + Weight::from_parts(68_521_487, 7255) + // Standard Error: 58_804 + .saturating_add(Weight::from_parts(33_271_211, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 7789).saturating_mul(p.into())) + } } // For backwards compatibility and tests impl WeightInfo for () { + /// Storage: Treasury Proposals (r:0 w:1) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + fn spend_local() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3376` + // Minimum execution time: 17_010_000 picoseconds. + Weight::from_parts(17_247_000, 3376) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } /// Storage: Treasury ProposalCount (r:1 w:1) /// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// Storage: Treasury Approvals (r:1 w:1) @@ -249,4 +298,24 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into())) } + /// Storage: Treasury Deactivated (r:1 w:1) + /// Proof: Treasury Deactivated (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Treasury PendingPayments (r:100 w:100) + /// Proof: Treasury PendingPayments (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `p` is `[0, 100]`. + fn on_initialize_pending_payments(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `387 + p * (251 ±0)` + // Estimated: `7255 + p * (7789 ±0)` + // Minimum execution time: 43_781_000 picoseconds. + Weight::from_parts(68_521_487, 7255) + // Standard Error: 58_804 + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(p.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 7789).saturating_mul(p.into())) + } }