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