// This file is part of Substrate. // 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. //! Treasury pallet benchmarking. #![cfg(feature = "runtime-benchmarks")] use super::{Pallet as Treasury, *}; use frame_benchmarking::{ v1::{account, BenchmarkError}, v2::*, }; use frame_support::{ ensure, traits::{ tokens::{ConversionFromAssetBalance, PaymentStatus}, EnsureOrigin, OnInitialize, }, }; use frame_system::RawOrigin; use sp_core::crypto::FromEntropy; /// Trait describing factory functions for dispatchables' parameters. pub trait ArgumentsFactory { /// Factory function for an asset kind. fn create_asset_kind(seed: u32) -> AssetKind; /// Factory function for a beneficiary. fn create_beneficiary(seed: [u8; 32]) -> Beneficiary; } /// Implementation that expects the parameters implement the [`FromEntropy`] trait. impl ArgumentsFactory for () where AssetKind: FromEntropy, Beneficiary: FromEntropy, { fn create_asset_kind(seed: u32) -> AssetKind { AssetKind::from_entropy(&mut seed.encode().as_slice()).unwrap() } fn create_beneficiary(seed: [u8; 32]) -> Beneficiary { Beneficiary::from_entropy(&mut seed.as_slice()).unwrap() } } const SEED: u32 = 0; // Create the pre-requisite information needed to create a treasury `propose_spend`. fn setup_proposal, I: 'static>( u: u32, ) -> (T::AccountId, BalanceOf, AccountIdLookupOf) { let caller = account("caller", u, SEED); let value: BalanceOf = T::ProposalBondMinimum::get().saturating_mul(100u32.into()); let _ = T::Currency::make_free_balance_be(&caller, value); let beneficiary = account("beneficiary", u, SEED); let beneficiary_lookup = T::Lookup::unlookup(beneficiary); (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 { let (caller, value, lookup) = setup_proposal::(i); #[allow(deprecated)] Treasury::::propose_spend(RawOrigin::Signed(caller).into(), value, lookup)?; let proposal_id = >::get() - 1; #[allow(deprecated)] Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; } ensure!(>::get().len() == n as usize, "Not all approved"); 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()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } // Create the arguments for the `spend` dispatchable. fn create_spend_arguments, I: 'static>( seed: u32, ) -> (T::AssetKind, AssetBalanceOf, T::Beneficiary, BeneficiaryLookupOf) { let asset_kind = T::BenchmarkHelper::create_asset_kind(seed); let beneficiary = T::BenchmarkHelper::create_beneficiary([seed.try_into().unwrap(); 32]); let beneficiary_lookup = T::BeneficiaryLookup::unlookup(beneficiary.clone()); (asset_kind, 100u32.into(), beneficiary, beneficiary_lookup) } #[instance_benchmarks] mod benchmarks { use super::*; // This benchmark is short-circuited if `SpendOrigin` cannot provide // a successful origin, in which case `spend` is un-callable and can use weight=0. #[benchmark] fn spend_local() -> Result<(), BenchmarkError> { let (_, value, beneficiary_lookup) = setup_proposal::(SEED); let origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); #[extrinsic_call] _(origin as T::RuntimeOrigin, value, beneficiary_lookup); assert_last_event::( Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into(), ); Ok(()) } #[benchmark] fn propose_spend() -> Result<(), BenchmarkError> { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); #[extrinsic_call] _(RawOrigin::Signed(caller), value, beneficiary_lookup); Ok(()) } #[benchmark] fn reject_proposal() -> Result<(), BenchmarkError> { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); #[allow(deprecated)] Treasury::::propose_spend( RawOrigin::Signed(caller).into(), value, beneficiary_lookup, )?; let proposal_id = Treasury::::proposal_count() - 1; let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(reject_origin as T::RuntimeOrigin, proposal_id); Ok(()) } #[benchmark] fn approve_proposal( p: Linear<0, { T::MaxApprovals::get() - 1 }>, ) -> Result<(), BenchmarkError> { create_approved_proposals::(p)?; let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); #[allow(deprecated)] Treasury::::propose_spend( RawOrigin::Signed(caller).into(), value, beneficiary_lookup, )?; let proposal_id = Treasury::::proposal_count() - 1; let approve_origin = T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(approve_origin as T::RuntimeOrigin, proposal_id); Ok(()) } #[benchmark] fn remove_approval() -> Result<(), BenchmarkError> { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); #[allow(deprecated)] Treasury::::propose_spend( RawOrigin::Signed(caller).into(), value, beneficiary_lookup, )?; let proposal_id = Treasury::::proposal_count() - 1; #[allow(deprecated)] Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(reject_origin as T::RuntimeOrigin, proposal_id); Ok(()) } #[benchmark] fn on_initialize_proposals( p: Linear<0, { T::MaxApprovals::get() - 1 }>, ) -> Result<(), BenchmarkError> { setup_pot_account::(); create_approved_proposals::(p)?; #[block] { Treasury::::on_initialize(0u32.into()); } Ok(()) } #[benchmark] fn spend() -> Result<(), BenchmarkError> { let origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let (asset_kind, amount, beneficiary, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); #[extrinsic_call] _( origin as T::RuntimeOrigin, Box::new(asset_kind.clone()), amount, Box::new(beneficiary_lookup), None, ); let valid_from = frame_system::Pallet::::block_number(); let expire_at = valid_from.saturating_add(T::PayoutPeriod::get()); assert_last_event::( Event::AssetSpendApproved { index: 0, asset_kind, amount, beneficiary, valid_from, expire_at, } .into(), ); Ok(()) } #[benchmark] fn payout() -> Result<(), BenchmarkError> { let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, beneficiary, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); Treasury::::spend( origin, Box::new(asset_kind.clone()), amount, Box::new(beneficiary_lookup), None, )?; T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount); let caller: T::AccountId = account("caller", 0, SEED); #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), 0u32); let id = match Spends::::get(0).unwrap().status { PaymentState::Attempted { id, .. } => { assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); id }, _ => panic!("No payout attempt made"), }; assert_last_event::(Event::Paid { index: 0, payment_id: id }.into()); assert!(Treasury::::payout(RawOrigin::Signed(caller).into(), 0u32).is_err()); Ok(()) } #[benchmark] fn check_status() -> Result<(), BenchmarkError> { let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, beneficiary, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); Treasury::::spend( origin, Box::new(asset_kind.clone()), amount, Box::new(beneficiary_lookup), None, )?; T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount); let caller: T::AccountId = account("caller", 0, SEED); Treasury::::payout(RawOrigin::Signed(caller.clone()).into(), 0u32)?; match Spends::::get(0).unwrap().status { PaymentState::Attempted { id, .. } => { T::Paymaster::ensure_concluded(id); }, _ => panic!("No payout attempt made"), }; #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), 0u32); if let Some(s) = Spends::::get(0) { assert!(!matches!(s.status, PaymentState::Attempted { .. })); } Ok(()) } #[benchmark] fn void_spend() -> Result<(), BenchmarkError> { let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?; let (asset_kind, amount, _, beneficiary_lookup) = create_spend_arguments::(SEED); T::BalanceConverter::ensure_successful(asset_kind.clone()); Treasury::::spend( origin, Box::new(asset_kind.clone()), amount, Box::new(beneficiary_lookup), None, )?; assert!(Spends::::get(0).is_some()); let origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, 0u32); assert!(Spends::::get(0).is_none()); Ok(()) } impl_benchmark_test_suite!( Treasury, crate::tests::ExtBuilder::default().build(), crate::tests::Test ); }