referrerpolicy=no-referrer-when-downgrade

pallet_pgas_allowance/
benchmarking.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Benchmarks for the `ChargePGAS` transaction extension.
17//!
18//! The inner extension is pinned to `()` so the benchmarks measure only the wrapper's own
19//! cost; runtimes add the inner extension's weight via its own `weight()` fn.
20
21extern crate alloc;
22
23use super::*;
24use crate::{BenchmarkHelperTrait, Pallet};
25use frame_benchmarking::v2::*;
26use frame_support::{
27	dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo},
28	pallet_prelude::Weight,
29	traits::tokens::fungibles,
30};
31use frame_system::RawOrigin;
32use sp_runtime::traits::{
33	AsSystemOriginSigner, AsTransactionAuthorizedOrigin, DispatchTransaction, Dispatchable,
34};
35
36#[benchmarks(where
37	T: Send + Sync,
38	T::RuntimeOrigin: AsTransactionAuthorizedOrigin,
39	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
40		+ From<frame_system::Call<T>>,
41	BalanceOf<T>: Send + Sync + From<u64>,
42	AssetIdOf<T>: Send + Sync,
43	<T::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
44)]
45mod benchmarks {
46	use super::*;
47
48	/// PGAS path: caller holds enough PGAS and the call matches the filter, so the fee is
49	/// withdrawn into a credit and the unused portion is resolved back to the caller.
50	#[benchmark]
51	fn charge_pgas() {
52		let caller: T::AccountId = account("caller", 0, 0);
53		let initial: BalanceOf<T> = u64::MAX.into();
54		<T as Config>::BenchmarkHelper::mint_pgas(&caller, T::PGASAssetId::get(), initial);
55
56		let ext: ChargePGAS<T, ()> = ChargePGAS::<T, ()>::default();
57		let call: T::RuntimeCall = frame_system::Call::<T>::remark { remark: alloc::vec![] }.into();
58		let info = DispatchInfo {
59			call_weight: Weight::from_parts(100, 0),
60			class: DispatchClass::Normal,
61			..Default::default()
62		};
63		let post_info = PostDispatchInfo {
64			actual_weight: Some(Weight::from_parts(10, 0)),
65			pays_fee: Default::default(),
66		};
67
68		let result;
69		#[block]
70		{
71			result =
72				ext.test_run(RawOrigin::Signed(caller.clone()).into(), &call, &info, 0, 0, |_| {
73					Ok(post_info)
74				});
75		}
76		assert!(result.unwrap().is_ok());
77		let remaining = <T::Assets as fungibles::Inspect<T::AccountId>>::balance(
78			T::PGASAssetId::get(),
79			&caller,
80		);
81		assert!(remaining < initial, "PGAS should be charged on the PGAS path");
82	}
83
84	/// Skip path: caller holds some PGAS but not enough to cover the fee, so the extension falls
85	/// through to the inner extension. Measures the overhead of the PGAS preamble (origin, filter,
86	/// balance read) when the path is ultimately skipped.
87	#[benchmark]
88	fn charge_pgas_skip() {
89		let caller: T::AccountId = account("caller", 0, 0);
90		// Mint the asset's minimum balance so the caller has an `Assets::Account` entry.
91		// Without one, `reducible_balance` returns early before reading the freezer storage,
92		// so the worst-case storage path on the skip branch wouldn't be captured.
93		let min_balance =
94			<T::Assets as fungibles::Inspect<T::AccountId>>::minimum_balance(T::PGASAssetId::get());
95		<T as Config>::BenchmarkHelper::mint_pgas(&caller, T::PGASAssetId::get(), min_balance);
96
97		let ext: ChargePGAS<T, ()> = ChargePGAS::<T, ()>::default();
98		let call: T::RuntimeCall = frame_system::Call::<T>::remark { remark: alloc::vec![] }.into();
99		let info = DispatchInfo {
100			call_weight: Weight::from_parts(10, 0),
101			class: DispatchClass::Normal,
102			..Default::default()
103		};
104		let post_info = PostDispatchInfo {
105			actual_weight: Some(Weight::from_parts(10, 0)),
106			pays_fee: Default::default(),
107		};
108
109		let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(0, &info, Zero::zero());
110		assert!(!fee.is_zero(), "skip path requires fee > 0 to exercise `pgas < fee`");
111		let before = <T::Assets as fungibles::Inspect<T::AccountId>>::balance(
112			T::PGASAssetId::get(),
113			&caller,
114		);
115		assert!(before < fee, "caller must not hold enough PGAS to take the PGAS branch");
116		let result;
117		#[block]
118		{
119			result =
120				ext.test_run(RawOrigin::Signed(caller.clone()).into(), &call, &info, 0, 0, |_| {
121					Ok(post_info)
122				});
123		}
124		assert!(result.unwrap().is_ok());
125		let after = <T::Assets as fungibles::Inspect<T::AccountId>>::balance(
126			T::PGASAssetId::get(),
127			&caller,
128		);
129		assert_eq!(before, after, "PGAS must not be charged on the skip path");
130	}
131
132	impl_benchmark_test_suite!(
133		Pallet,
134		crate::mock::ExtBuilder::default().build(),
135		crate::mock::Runtime
136	);
137}