referrerpolicy=no-referrer-when-downgrade

pallet_on_demand_para/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg(feature = "runtime-benchmarks")]
19
20use super::*;
21
22use crate::{Pallet as OnDemand, DEFAULT_BASE_FEE, DEFAULT_PRICE_STEP};
23use frame_benchmarking::v2::*;
24use frame_support::{
25	pallet_prelude::*,
26	traits::{
27		fungible::{Inspect, Mutate},
28		Hooks,
29	},
30};
31use frame_system::{pallet_prelude::*, Pallet as System, RawOrigin};
32use sp_runtime::{Perbill, Saturating};
33
34#[benchmarks]
35mod benches {
36	use super::*;
37	#[cfg(not(feature = "std"))]
38	use num_traits::float::FloatCore;
39
40	#[benchmark]
41	fn configure() -> Result<(), BenchmarkError> {
42		let config = PriceParameters {
43			order_cap: 80,
44			drain_rate_per_block: 2,
45			price_step: Perbill::from_percent(2),
46			base_fee: BalanceOf::<T>::from(1_000_000u32),
47		};
48
49		let origin =
50			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
51
52		#[extrinsic_call]
53		_(origin as T::RuntimeOrigin, config.clone());
54
55		assert_eq!(PriceConfig::<T>::get(), config);
56
57		Ok(())
58	}
59
60	#[benchmark]
61	fn place_order() -> Result<(), BenchmarkError> {
62		let current_block = BlockNumberFor::<T>::from(1u32);
63		System::<T>::set_block_number(current_block);
64
65		let base_fee = BalanceOf::<T>::from(DEFAULT_BASE_FEE);
66
67		let caller: T::AccountId = whitelisted_caller();
68		T::Currency::set_balance(
69			&caller.clone(),
70			T::Currency::minimum_balance().saturating_add(base_fee),
71		);
72		// Store minimum balance in the pallet's account, so that small transfers to it don't fail
73		T::Currency::set_balance(&OnDemand::<T>::account_id(), T::Currency::minimum_balance());
74
75		let _ = OnDemand::<T>::on_initialize(current_block);
76
77		#[block]
78		{
79			OnDemand::<T>::place_order(
80				T::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())),
81				2000,
82				base_fee,
83			)
84			.map_err(|_| BenchmarkError::Stop("place_order failed"))?;
85		}
86
87		Ok(())
88	}
89
90	/// Benchmark the `on_finalize` hook scaling with number of orders.
91	///
92	/// This benchmark measures the marginal computational cost of adding orders
93	/// to a block during finalization.
94	///
95	/// We do not benchmark with zero orders, since that would skew the results by not generating an
96	/// outgoing message. We want the linear part to reflect the additional overhead of processing
97	/// more orders, and the fixed part to reflect sending a message - so we need all cases being
98	/// benchmarked to send exactly one message.
99	///
100	/// ## Parameters:
101	/// - `n`: Number of transactions in the block (1-100)
102	///
103	/// ## Test Setup:
104	/// - Initializes the account balance with enough funds to cover all orders.
105	/// - Places `n` orders.
106	#[benchmark(pov_mode = Measured)]
107	fn on_finalize_with_orders(n: Linear<1, 100>) -> Result<(), BenchmarkError> {
108		let current_block = BlockNumberFor::<T>::from(1u32);
109		System::<T>::set_block_number(current_block);
110
111		let base_fee = BalanceOf::<T>::from(DEFAULT_BASE_FEE);
112		let step = DEFAULT_PRICE_STEP as f32 / 100.0;
113
114		// k-th order's price will be base_fee * (1 + step)^k
115		// Thus, the cost of n orders will be the base_fee times 1 + x + x^2 + ... + x^(n-1),
116		// where x = 1 + step. Such a sum equals (x^n - 1) / (x - 1), which in our case gives
117		// ((1 + step)^n - 1) / step.
118		// We add n to account for possible rounding errors at each order.
119		let multiplier = ((1.0 + step).powi(n as i32) - 1.0) / step + n as f32;
120
121		let required_amount = base_fee
122			.checked_mul(&BalanceOf::<T>::from(multiplier.ceil() as u32))
123			.expect("the price of n orders should fit within the balance type");
124
125		let max_price = base_fee
126			.checked_mul(&BalanceOf::<T>::from((1.0 + step).powi(n as i32).ceil() as u32))
127			.expect("maximum price should fit within the balance type");
128
129		let caller: T::AccountId = whitelisted_caller();
130		T::Currency::set_balance(
131			&caller.clone(),
132			T::Currency::minimum_balance().saturating_add(required_amount),
133		);
134		// Store minimum balance in the pallet's account, so that small transfers to it don't fail
135		T::Currency::set_balance(&OnDemand::<T>::account_id(), T::Currency::minimum_balance());
136
137		// Pre-populate InflightTransactions with n transactions of fixed size
138		if n > 0 {
139			// Initialize block
140			let _ = OnDemand::<T>::on_initialize(current_block);
141
142			for _ in 0..n {
143				OnDemand::<T>::place_order(
144					T::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())),
145					From::from(2000 + n),
146					max_price,
147				)
148				.map_err(|_| BenchmarkError::Stop("place_order failed"))?;
149			}
150		}
151
152		#[block]
153		{
154			// Measure only the finalization cost with n transactions of fixed size
155			let _ = OnDemand::<T>::on_finalize(current_block);
156		}
157
158		Ok(())
159	}
160
161	// Implements a test for each benchmark. Execute with:
162	// `cargo test -p pallet-on-demand-para --features runtime-benchmarks`.
163	impl_benchmark_test_suite!(OnDemand, crate::mock::new_test_ext(), crate::mock::Test);
164}