referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_common/claims/
benchmarking.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Benchmarking for claims pallet
18
19#[cfg(feature = "runtime-benchmarks")]
20use super::*;
21use crate::claims::Call;
22use frame_benchmarking::v2::*;
23use frame_support::{
24	dispatch::{DispatchInfo, GetDispatchInfo},
25	traits::{Currency, UnfilteredDispatchable},
26};
27use frame_system::RawOrigin;
28use k256::ecdsa::SigningKey;
29use secp_utils::*;
30use sp_runtime::{traits::DispatchTransaction, DispatchResult};
31
32const SEED: u32 = 0;
33
34const MAX_CLAIMS: u32 = 10_000;
35const MIN_VALUE: u32 = 1_000_000;
36
37/// Claim amount used by the benchmarks. Floored to the runtime's existential deposit, since these
38/// claims carry a vesting schedule: [`Pallet::process_claim`] rejects a vesting claim whose
39/// resulting balance would be below the ED ([`Error::ClaimBelowExistentialDeposit`]), because the
40/// dest account must stay alive to hold the vesting lock. The benchmark deposits into fresh
41/// accounts, so without the floor this trips on chains where ED exceeds [`MIN_VALUE`] (1M).
42fn bench_claim_value<T: Config>() -> BalanceOf<T> {
43	CurrencyOf::<T>::minimum_balance().max(MIN_VALUE.into())
44}
45
46fn create_claim<T: Config>(input: u32) -> DispatchResult {
47	let secret_key =
48		SigningKey::from_slice(&keccak_256(&input.encode())).expect("32 bytes, within curve order");
49	let eth_address = eth(&secret_key);
50	let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
51	super::Pallet::<T>::mint_claim(
52		RawOrigin::Root.into(),
53		eth_address,
54		bench_claim_value::<T>(),
55		vesting,
56		None,
57	)?;
58	Ok(())
59}
60
61fn create_claim_attest<T: Config>(input: u32) -> DispatchResult {
62	let secret_key =
63		SigningKey::from_slice(&keccak_256(&input.encode())).expect("32 bytes, within curve order");
64	let eth_address = eth(&secret_key);
65	let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
66	super::Pallet::<T>::mint_claim(
67		RawOrigin::Root.into(),
68		eth_address,
69		bench_claim_value::<T>(),
70		vesting,
71		Some(Default::default()),
72	)?;
73	Ok(())
74}
75
76#[benchmarks(
77		where
78			<T as frame_system::Config>::RuntimeCall: IsSubType<Call<T>> + From<Call<T>>,
79			<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo> + GetDispatchInfo,
80			<<T as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + AsTransactionAuthorizedOrigin + Clone,
81			<<T as frame_system::Config>::RuntimeCall as Dispatchable>::PostInfo: Default,
82	)]
83mod benchmarks {
84	use super::*;
85
86	#[allow(deprecated)]
87	use sp_runtime::traits::ValidateUnsigned;
88
89	// Benchmark `claim` including `validate_unsigned` logic.
90	#[benchmark]
91	fn claim() -> Result<(), BenchmarkError> {
92		let c = MAX_CLAIMS;
93		for _ in 0..c / 2 {
94			create_claim::<T>(c)?;
95			create_claim_attest::<T>(u32::MAX - c)?;
96		}
97		let secret_key =
98			SigningKey::from_slice(&keccak_256(&c.encode())).expect("32 bytes, within curve order");
99		let eth_address = eth(&secret_key);
100		let account: T::AccountId = account("user", c, SEED);
101		let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
102		let signature = sig::<T>(&secret_key, &account.encode(), &[][..]);
103		super::Pallet::<T>::mint_claim(
104			RawOrigin::Root.into(),
105			eth_address,
106			bench_claim_value::<T>(),
107			vesting,
108			None,
109		)?;
110		assert_eq!(Claims::<T>::get(eth_address), Some(bench_claim_value::<T>()));
111		let source = sp_runtime::transaction_validity::TransactionSource::External;
112		let call_enc =
113			Call::<T>::claim { dest: account.clone(), ethereum_signature: signature.clone() }
114				.encode();
115
116		#[block]
117		{
118			let call = <Call<T> as Decode>::decode(&mut &*call_enc)
119				.expect("call is encoded above, encoding must be correct");
120			#[allow(deprecated)]
121			super::Pallet::<T>::validate_unsigned(source, &call)
122				.map_err(|e| -> &'static str { e.into() })?;
123			call.dispatch_bypass_filter(RawOrigin::None.into())?;
124		}
125
126		assert_eq!(Claims::<T>::get(eth_address), None);
127		Ok(())
128	}
129
130	// Benchmark `mint_claim` when there already exists `c` claims in storage.
131	#[benchmark]
132	fn mint_claim() -> Result<(), BenchmarkError> {
133		let c = MAX_CLAIMS;
134		for _ in 0..c / 2 {
135			create_claim::<T>(c)?;
136			create_claim_attest::<T>(u32::MAX - c)?;
137		}
138		let eth_address = account("eth_address", 0, SEED);
139		let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
140		let statement = StatementKind::Regular;
141
142		#[extrinsic_call]
143		_(RawOrigin::Root, eth_address, bench_claim_value::<T>(), vesting, Some(statement));
144
145		assert_eq!(Claims::<T>::get(eth_address), Some(bench_claim_value::<T>()));
146		Ok(())
147	}
148
149	// Benchmark `claim_attest` including `validate_unsigned` logic.
150	#[benchmark]
151	fn claim_attest() -> Result<(), BenchmarkError> {
152		let c = MAX_CLAIMS;
153		for _ in 0..c / 2 {
154			create_claim::<T>(c)?;
155			create_claim_attest::<T>(u32::MAX - c)?;
156		}
157		// Crate signature
158		let attest_c = u32::MAX - c;
159		let secret_key = SigningKey::from_slice(&keccak_256(&attest_c.encode()))
160			.expect("32 bytes, within curve order");
161		let eth_address = eth(&secret_key);
162		let account: T::AccountId = account("user", c, SEED);
163		let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
164		let statement = StatementKind::Regular;
165		let signature = sig::<T>(&secret_key, &account.encode(), statement.to_text());
166		super::Pallet::<T>::mint_claim(
167			RawOrigin::Root.into(),
168			eth_address,
169			bench_claim_value::<T>(),
170			vesting,
171			Some(statement),
172		)?;
173		assert_eq!(Claims::<T>::get(eth_address), Some(bench_claim_value::<T>()));
174		let call_enc = Call::<T>::claim_attest {
175			dest: account.clone(),
176			ethereum_signature: signature.clone(),
177			statement: StatementKind::Regular.to_text().to_vec(),
178		}
179		.encode();
180		let source = sp_runtime::transaction_validity::TransactionSource::External;
181
182		#[block]
183		{
184			let call = <Call<T> as Decode>::decode(&mut &*call_enc)
185				.expect("call is encoded above, encoding must be correct");
186			#[allow(deprecated)]
187			super::Pallet::<T>::validate_unsigned(source, &call)
188				.map_err(|e| -> &'static str { e.into() })?;
189			call.dispatch_bypass_filter(RawOrigin::None.into())?;
190		}
191
192		assert_eq!(Claims::<T>::get(eth_address), None);
193		Ok(())
194	}
195
196	// Benchmark `attest` including prevalidate logic.
197	#[benchmark]
198	fn attest() -> Result<(), BenchmarkError> {
199		let c = MAX_CLAIMS;
200		for _ in 0..c / 2 {
201			create_claim::<T>(c)?;
202			create_claim_attest::<T>(u32::MAX - c)?;
203		}
204		let attest_c = u32::MAX - c;
205		let secret_key = SigningKey::from_slice(&keccak_256(&attest_c.encode()))
206			.expect("32 bytes, within curve order");
207		let eth_address = eth(&secret_key);
208		let account: T::AccountId = account("user", c, SEED);
209		let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
210		let statement = StatementKind::Regular;
211		super::Pallet::<T>::mint_claim(
212			RawOrigin::Root.into(),
213			eth_address,
214			bench_claim_value::<T>(),
215			vesting,
216			Some(statement),
217		)?;
218		Preclaims::<T>::insert(&account, eth_address);
219		assert_eq!(Claims::<T>::get(eth_address), Some(bench_claim_value::<T>()));
220
221		let stmt = StatementKind::Regular.to_text().to_vec();
222
223		#[extrinsic_call]
224		_(RawOrigin::Signed(account), stmt);
225
226		assert_eq!(Claims::<T>::get(eth_address), None);
227		Ok(())
228	}
229
230	#[benchmark]
231	fn move_claim() -> Result<(), BenchmarkError> {
232		let c = MAX_CLAIMS;
233		for _ in 0..c / 2 {
234			create_claim::<T>(c)?;
235			create_claim_attest::<T>(u32::MAX - c)?;
236		}
237		let attest_c = u32::MAX - c;
238		let secret_key = SigningKey::from_slice(&keccak_256(&attest_c.encode()))
239			.expect("32 bytes, within curve order");
240		let eth_address = eth(&secret_key);
241
242		let new_secret_key = SigningKey::from_slice(&keccak_256(&(u32::MAX / 2).encode()))
243			.expect("32 bytes, within curve order");
244		let new_eth_address = eth(&new_secret_key);
245
246		let account: T::AccountId = account("user", c, SEED);
247		Preclaims::<T>::insert(&account, eth_address);
248
249		assert!(Claims::<T>::contains_key(eth_address));
250		assert!(!Claims::<T>::contains_key(new_eth_address));
251
252		#[extrinsic_call]
253		_(RawOrigin::Root, eth_address, new_eth_address, Some(account));
254
255		assert!(!Claims::<T>::contains_key(eth_address));
256		assert!(Claims::<T>::contains_key(new_eth_address));
257		Ok(())
258	}
259
260	// Benchmark the time it takes to do `repeat` number of keccak256 hashes
261	#[benchmark(extra)]
262	fn keccak256(i: Linear<0, 10_000>) {
263		let bytes = (i).encode();
264
265		#[block]
266		{
267			for _ in 0..i {
268				let _hash = keccak_256(&bytes);
269			}
270		}
271	}
272
273	// Benchmark the time it takes to do `repeat` number of `eth_recover`
274	#[benchmark(extra)]
275	fn eth_recover(i: Linear<0, 1_000>) {
276		// Crate signature
277		let secret_key =
278			SigningKey::from_slice(&keccak_256(&i.encode())).expect("32 bytes, within curve order");
279		let account: T::AccountId = account("user", i, SEED);
280		let signature = sig::<T>(&secret_key, &account.encode(), &[][..]);
281		let data = account.using_encoded(to_ascii_hex);
282		let extra = StatementKind::default().to_text();
283
284		#[block]
285		{
286			for _ in 0..i {
287				assert!(super::Pallet::<T>::eth_recover(&signature, &data, extra).is_some());
288			}
289		}
290	}
291
292	#[benchmark]
293	fn prevalidate_attests() -> Result<(), BenchmarkError> {
294		let c = MAX_CLAIMS;
295		for _ in 0..c / 2 {
296			create_claim::<T>(c)?;
297			create_claim_attest::<T>(u32::MAX - c)?;
298		}
299		let ext = PrevalidateAttests::<T>::new();
300		let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() };
301		let call: <T as frame_system::Config>::RuntimeCall = call.into();
302		let info = call.get_dispatch_info();
303		let attest_c = u32::MAX - c;
304		let secret_key = SigningKey::from_slice(&keccak_256(&attest_c.encode()))
305			.expect("32 bytes, within curve order");
306		let eth_address = eth(&secret_key);
307		let account: T::AccountId = account("user", c, SEED);
308		let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into()));
309		let statement = StatementKind::Regular;
310		super::Pallet::<T>::mint_claim(
311			RawOrigin::Root.into(),
312			eth_address,
313			bench_claim_value::<T>(),
314			vesting,
315			Some(statement),
316		)?;
317		Preclaims::<T>::insert(&account, eth_address);
318		assert_eq!(Claims::<T>::get(eth_address), Some(bench_claim_value::<T>()));
319
320		#[block]
321		{
322			assert!(ext
323				.test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| {
324					Ok(Default::default())
325				})
326				.unwrap()
327				.is_ok());
328		}
329
330		Ok(())
331	}
332
333	impl_benchmark_test_suite!(
334		Pallet,
335		crate::claims::mock::new_test_ext(),
336		crate::claims::mock::Test,
337	);
338}