referrerpolicy=no-referrer-when-downgrade

pallet_assets_precompiles/
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//! Benchmarks for `pallet_assets_precompiles`.
19//!
20//! All benchmarks are registered under `foreign_assets::Pallet` so that a single
21//! `frame-omni-bencher --pallet=pallet_assets_precompiles` run generates one
22//! `weights.rs` containing every weight function.
23
24#![cfg(feature = "runtime-benchmarks")]
25
26use crate::{
27	foreign_assets::pallet::{Config, Pallet},
28	migration::MigrateForeignAssetPrecompileMappings,
29};
30use frame_benchmarking::v2::*;
31use frame_support::{migrations::SteppedMigration, weights::WeightMeter};
32use pallet_revive::precompiles::H160;
33use sp_core::U256;
34use sp_runtime::traits::StaticLookup;
35
36/// Test owner address (Hardhat account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)
37const TEST_OWNER: [u8; 20] = [
38	0xf3, 0x9f, 0xd6, 0xe5, 0x1a, 0xad, 0x88, 0xf6, 0xf4, 0xce, 0x6a, 0xb8, 0x82, 0x72, 0x79, 0xcf,
39	0xff, 0xb9, 0x22, 0x66,
40];
41
42fn test_verifying_contract() -> H160 {
43	H160::from_low_u64_be(0x1234_5678)
44}
45
46fn test_owner() -> H160 {
47	H160::from_slice(&TEST_OWNER)
48}
49
50/// Test token name for EIP-712 domain separator.
51const TEST_TOKEN_NAME: &[u8] = b"Asset Permit";
52
53#[benchmarks(
54	where
55		// Migration bounds
56		T: pallet_assets::Config<T::AssetsInstance, AssetId = <T as Config>::ForeignAssetId>,
57		// Permit bounds
58		T: crate::permit::Config,
59		<T as pallet_assets::Config<T::AssetsInstance>>::Balance: From<u32>,
60		<T as pallet_assets::Config<T::AssetsInstance>>::AssetIdParameter: From<<T as pallet_assets::Config<T::AssetsInstance>>::AssetId>,
61)]
62mod benchmarks {
63	use super::*;
64	use pallet_assets::BenchmarkHelper;
65
66	// ==================== Migration benchmarks ====================
67
68	/// Benchmark one complete `step()` invocation of the
69	/// [`MigrateForeignAssetPrecompileMappings`] stepped migration.
70	#[benchmark]
71	fn migrate_foreign_asset_step() {
72		// Clear any pre-existing assets from genesis so that only our
73		// benchmark asset is present during the migration step.
74		let _ = pallet_assets::Asset::<T, T::AssetsInstance>::clear(u32::MAX, None);
75
76		// Create one asset in pallet_assets storage.
77		let caller: T::AccountId = whitelisted_caller();
78		let caller_lookup = <T as frame_system::Config>::Lookup::unlookup(caller);
79		let asset_id_param = <T as pallet_assets::Config<T::AssetsInstance>>::BenchmarkHelper::create_asset_id_parameter(42);
80		let asset_id: <T as pallet_assets::Config<T::AssetsInstance>>::AssetId =
81			asset_id_param.clone().into();
82
83		pallet_assets::Pallet::<T, T::AssetsInstance>::force_create(
84			frame_system::RawOrigin::Root.into(),
85			asset_id_param,
86			caller_lookup,
87			true,
88			1u32.into(),
89		)
90		.unwrap();
91
92		// Remove the mapping that was auto-created by the AssetsCallback hook
93		Pallet::<T>::remove_asset_mapping(&asset_id);
94
95		// Verify no precompile mapping exists yet.
96		assert!(Pallet::<T>::asset_index_of(&asset_id).is_none());
97
98		let mut meter = WeightMeter::new();
99
100		#[block]
101		{
102			MigrateForeignAssetPrecompileMappings::<T, T::AssetsInstance, ()>::step(
103				None, &mut meter,
104			)
105			.unwrap();
106		}
107
108		// Verify the asset was migrated.
109		assert!(Pallet::<T>::asset_index_of(&asset_id).is_some());
110		// The step consumes the weight twice: once for migrating the asset and once for
111		// discovering that there are no more assets to migrate.
112		assert_eq!(
113			meter.consumed(),
114			<() as crate::weights::WeightInfo>::migrate_foreign_asset_step() * 2
115		);
116	}
117
118	// ==================== Permit benchmarks ====================
119
120	#[benchmark]
121	fn nonces() {
122		let verifying_contract = test_verifying_contract();
123		let owner = test_owner();
124		crate::permit::Nonces::<T>::insert(&verifying_contract, &owner, U256::from(42));
125
126		let result;
127		#[block]
128		{
129			result = crate::permit::Pallet::<T>::nonce(&verifying_contract, &owner);
130		}
131		assert_eq!(result, U256::from(42));
132	}
133
134	#[benchmark]
135	fn domain_separator() {
136		let verifying_contract = test_verifying_contract();
137		let name = TEST_TOKEN_NAME;
138
139		let result;
140		#[block]
141		{
142			result =
143				crate::permit::Pallet::<T>::compute_domain_separator(&verifying_contract, name);
144		}
145		assert_ne!(result, sp_core::H256::zero());
146	}
147
148	/// Benchmark for `use_permit` — the EIP-2612 signature verification and nonce
149	/// increment that is called inside the `permit` precompile.
150	///
151	/// Measures: domain separator computation, struct hash, ECDSA recovery, nonce
152	/// read + write. This is the weight charged for the cryptographic portion of
153	/// a permit call (the asset approval weights are tracked separately).
154	#[benchmark]
155	fn use_permit() {
156		let owner = test_owner();
157		let spender = H160::from_low_u64_be(0x9876_5432);
158		let verifying_contract = test_verifying_contract();
159		let value: [u8; 32] = {
160			let mut buf = [0u8; 32];
161			buf[31] = 100; // value = 100
162			buf
163		};
164		let deadline: [u8; 32] = {
165			let mut buf = [0u8; 32];
166			// Set deadline far in the future (max u64)
167			buf[24..32].copy_from_slice(&u64::MAX.to_be_bytes());
168			buf
169		};
170
171		// Set timestamp so deadline check passes.
172		// Write directly to storage instead of calling `set_timestamp()`, which
173		// triggers `OnTimestampSet` callbacks (e.g. pallet_babe slot validation)
174		// that require additional setup not relevant to this benchmark.
175		let timestamp: <T as pallet_timestamp::Config>::Moment =
176			1_704_067_200_000u64.try_into().unwrap_or_default();
177		pallet_timestamp::Now::<T>::put(timestamp);
178
179		// Compute EIP-712 digest using runtime's chain_id.
180		let nonce = U256::zero();
181		let digest = crate::permit::Pallet::<T>::permit_digest(
182			&verifying_contract,
183			TEST_TOKEN_NAME,
184			&owner,
185			&spender,
186			&value,
187			&nonce,
188			&deadline,
189		);
190
191		// Sign with Hardhat account #0 private key via sp_io host function.
192		// This works in both native and WASM benchmark environments, unlike
193		// using k256 directly which may not work in the WASM sandbox.
194		let key_type = sp_core::crypto::KeyTypeId(*b"prmt");
195		let pub_key = sp_io::crypto::ecdsa_generate(
196			key_type,
197			Some(b"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_vec()),
198		);
199		let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &digest)
200			.expect("signing with Hardhat #0 key must succeed; qed");
201		let sig_bytes: &[u8; 65] = sig.as_ref();
202		let r: [u8; 32] = sig_bytes[0..32].try_into().expect("r is 32 bytes");
203		let s: [u8; 32] = sig_bytes[32..64].try_into().expect("s is 32 bytes");
204		let v: u8 = sig_bytes[64] + 27;
205
206		#[block]
207		{
208			crate::permit::Pallet::<T>::use_permit(
209				&verifying_contract,
210				TEST_TOKEN_NAME,
211				&owner,
212				&spender,
213				&value,
214				&deadline,
215				v,
216				&r,
217				&s,
218			)
219			.expect("use_permit should succeed");
220		}
221
222		// Verify nonce was incremented, confirming the full flow ran.
223		assert_eq!(crate::permit::Pallet::<T>::nonce(&verifying_contract, &owner), U256::one());
224	}
225
226	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
227}