referrerpolicy=no-referrer-when-downgrade

pallet_scarcity/
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_scarcity`.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22extern crate alloc;
23
24use alloc::{vec, vec::Vec};
25use frame_benchmarking::v2::*;
26use frame_support::{
27	dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
28	traits::{Consideration, Get},
29};
30use frame_system::RawOrigin;
31use sp_runtime::{
32	traits::{Bounded, Dispatchable, TransactionExtension, TxBaseImplication},
33	transaction_validity::TransactionSource,
34	DispatchError,
35};
36
37use super::*;
38use crate::extension::{AsScarcity, AsScarcityInfo};
39
40fn metadata_key<T: Config>(seed: u32) -> MetadataKeyOf<T> {
41	let mut bytes = vec![0; T::MaxKeyLen::get() as usize];
42	for (destination, source) in bytes.iter_mut().zip(seed.to_le_bytes()) {
43		*destination = source;
44	}
45	bytes.try_into().ok().expect("key uses the configured maximum length")
46}
47
48fn metadata_value<T: Config>(byte: u8, len: u32) -> MetadataValueOf<T> {
49	vec![byte; len as usize]
50		.try_into()
51		.ok()
52		.expect("value length is benchmark-bounded")
53}
54
55fn fund<T: Config>(account: &T::AccountId) {
56	T::Consideration::ensure_successful(account, BalanceOf::<T>::max_value() / 4u32.into());
57}
58
59fn create_definition<T: Config>(
60	owner: &T::AccountId,
61) -> Result<(CollectionId, ItemIndex), BenchmarkError> {
62	fund::<T>(owner);
63	let collection = Pallet::<T>::do_create_collection(owner.clone())?;
64	let item = Pallet::<T>::do_define_item(owner.clone(), collection, Vec::new())?;
65	Ok((collection, item))
66}
67
68#[benchmarks(
69	where
70		T::RuntimeCall: From<Call<T>>
71			+ Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
72			+ GetDispatchInfo,
73)]
74mod benchmarks {
75	use super::*;
76
77	#[benchmark]
78	fn create_collection() {
79		let caller: T::AccountId = whitelisted_caller();
80		fund::<T>(&caller);
81
82		#[extrinsic_call]
83		_(RawOrigin::Signed(caller.clone()));
84
85		assert_eq!(NextCollectionId::<T>::get(), 1);
86		assert_eq!(Collections::<T>::get(0).map(|info| info.owner), Some(caller));
87	}
88
89	#[benchmark]
90	fn define_item(m: Linear<0, 100>) -> Result<(), BenchmarkError> {
91		let caller: T::AccountId = whitelisted_caller();
92		fund::<T>(&caller);
93		let collection = Pallet::<T>::do_create_collection(caller.clone())?;
94		let metadata = (0..m)
95			.map(|index| {
96				let key = metadata_key::<T>(index);
97				let value = metadata_value::<T>(0x42, T::MaxValueLen::get());
98				(key, value)
99			})
100			.collect::<Vec<_>>();
101
102		#[extrinsic_call]
103		_(RawOrigin::Signed(caller), collection, metadata);
104
105		assert!(ItemDefs::<T>::contains_key(collection, 0));
106		assert_eq!(ItemMetadata::<T>::iter_prefix((collection, 0)).count(), m as usize);
107		Ok(())
108	}
109
110	#[benchmark]
111	fn mint(m: Linear<0, { T::MaxInstanceMetadata::get() }>) -> Result<(), BenchmarkError> {
112		let caller: T::AccountId = whitelisted_caller();
113		let destination: T::AccountId = account("destination", 0, 0);
114		NftsByOwner::<T>::remove(&destination);
115		let (collection, item) = create_definition::<T>(&caller)?;
116		let metadata = (0..m)
117			.map(|index| {
118				let key = metadata_key::<T>(index);
119				let value = metadata_value::<T>(0x44, T::MaxValueLen::get());
120				(key, value)
121			})
122			.collect::<Vec<_>>();
123
124		#[extrinsic_call]
125		_(RawOrigin::Signed(caller), collection, item, destination.clone(), metadata);
126
127		let nft = NftsByOwner::<T>::get(&destination).expect("destination owns the minted NFT");
128		assert_eq!(Instances::<T>::get(nft.instance), Some(destination));
129		assert_eq!(InstanceMetadataCount::<T>::get(nft.instance), m);
130		Ok(())
131	}
132
133	#[benchmark]
134	fn transfer() -> Result<(), BenchmarkError> {
135		let owner: T::AccountId = whitelisted_caller();
136		let destination: T::AccountId = account("destination", 0, 0);
137		NftsByOwner::<T>::remove(&destination);
138		let (collection, item) = create_definition::<T>(&owner)?;
139		Pallet::<T>::do_mint(owner.clone(), collection, item, owner.clone(), Vec::new())?;
140		let nft = NftsByOwner::<T>::take(&owner).expect("owner has the minted NFT");
141		let instance = nft.instance;
142		let origin: T::RuntimeOrigin = Origin::<T>::Nft { owner, nft }.into();
143
144		#[extrinsic_call]
145		_(origin, destination.clone());
146
147		assert_eq!(NftsByOwner::<T>::get(&destination).map(|nft| nft.instance), Some(instance));
148		assert_eq!(Instances::<T>::get(instance), Some(destination));
149		Ok(())
150	}
151
152	#[benchmark]
153	fn burn(m: Linear<0, { T::MaxInstanceMetadata::get() }>) -> Result<(), BenchmarkError> {
154		let owner: T::AccountId = whitelisted_caller();
155		let (collection, item) = create_definition::<T>(&owner)?;
156		let metadata = (0..m)
157			.map(|index| {
158				let key = metadata_key::<T>(index);
159				let value = metadata_value::<T>(0x44, T::MaxValueLen::get());
160				(key, value)
161			})
162			.collect::<Vec<_>>();
163		Pallet::<T>::do_mint(owner.clone(), collection, item, owner.clone(), metadata)?;
164		let nft = NftsByOwner::<T>::take(&owner).expect("owner has the minted NFT");
165		let instance = nft.instance;
166		assert!(InstanceDeposits::<T>::contains_key(instance));
167		assert_eq!(InstanceMetadataCount::<T>::get(instance), m);
168		let origin: T::RuntimeOrigin = Origin::<T>::Nft { owner, nft }.into();
169
170		#[extrinsic_call]
171		_(origin);
172
173		assert!(!Instances::<T>::contains_key(instance));
174		assert!(!InstanceDeposits::<T>::contains_key(instance));
175		assert!(!InstanceMetadataCount::<T>::contains_key(instance));
176		assert_eq!(InstanceMetadata::<T>::iter_prefix(instance).count(), 0);
177		Ok(())
178	}
179
180	#[benchmark]
181	fn nominate_collection_owner() -> Result<(), BenchmarkError> {
182		let caller: T::AccountId = whitelisted_caller();
183		let nominee: T::AccountId = account("nominee", 0, 0);
184		fund::<T>(&caller);
185		let collection = Pallet::<T>::do_create_collection(caller.clone())?;
186
187		#[extrinsic_call]
188		_(RawOrigin::Signed(caller), collection, Some(nominee.clone()));
189
190		assert_eq!(
191			Collections::<T>::get(collection).and_then(|info| info.pending_owner),
192			Some(nominee),
193		);
194		Ok(())
195	}
196
197	#[benchmark]
198	fn set_collection_metadata() -> Result<(), BenchmarkError> {
199		let caller: T::AccountId = whitelisted_caller();
200		fund::<T>(&caller);
201		let collection = Pallet::<T>::do_create_collection(caller.clone())?;
202		let key = metadata_key::<T>(0);
203		let value = metadata_value::<T>(0x22, T::MaxValueLen::get());
204
205		#[extrinsic_call]
206		_(RawOrigin::Signed(caller), collection, key.clone(), Some(value.clone()));
207
208		assert_eq!(Pallet::<T>::collection_metadata_of(collection, &key), Some(value));
209		frame_system::Pallet::<T>::assert_last_event(
210			Event::<T>::CollectionMetadataSet { collection, key }.into(),
211		);
212		Ok(())
213	}
214
215	#[benchmark]
216	fn set_item_metadata() -> Result<(), BenchmarkError> {
217		let caller: T::AccountId = whitelisted_caller();
218		let (collection, item) = create_definition::<T>(&caller)?;
219		let key = metadata_key::<T>(0);
220		let value = metadata_value::<T>(0x22, T::MaxValueLen::get());
221
222		#[extrinsic_call]
223		_(RawOrigin::Signed(caller), collection, item, key.clone(), Some(value.clone()));
224
225		assert_eq!(Pallet::<T>::item_metadata_of(collection, item, &key), Some(value));
226		frame_system::Pallet::<T>::assert_last_event(
227			Event::<T>::ItemMetadataSet { collection, item, key }.into(),
228		);
229		Ok(())
230	}
231
232	#[benchmark]
233	fn set_instance_metadata() -> Result<(), BenchmarkError> {
234		let caller: T::AccountId = whitelisted_caller();
235		let destination: T::AccountId = account("destination", 0, 0);
236		let (collection, item) = create_definition::<T>(&caller)?;
237		let instance =
238			Pallet::<T>::do_mint(caller.clone(), collection, item, destination, Vec::new())?;
239		let key = metadata_key::<T>(0);
240		let value = metadata_value::<T>(0x66, T::MaxValueLen::get());
241
242		#[extrinsic_call]
243		_(RawOrigin::Signed(caller), instance, key.clone(), Some(value.clone()));
244
245		assert_eq!(Pallet::<T>::instance_metadata_of(instance, &key), Some(value));
246		frame_system::Pallet::<T>::assert_last_event(
247			Event::<T>::InstanceMetadataSet { instance, key }.into(),
248		);
249		Ok(())
250	}
251
252	#[benchmark]
253	fn force_burn(m: Linear<0, { T::MaxInstanceMetadata::get() }>) -> Result<(), BenchmarkError> {
254		let caller: T::AccountId = whitelisted_caller();
255		let destination: T::AccountId = account("destination", 0, 0);
256		let (collection, item) = create_definition::<T>(&caller)?;
257		let metadata = (0..m)
258			.map(|index| {
259				let key = metadata_key::<T>(index);
260				let value = metadata_value::<T>(0x55, T::MaxValueLen::get());
261				(key, value)
262			})
263			.collect::<Vec<_>>();
264		let instance =
265			Pallet::<T>::do_mint(caller.clone(), collection, item, destination.clone(), metadata)?;
266
267		#[extrinsic_call]
268		_(RawOrigin::Signed(caller), instance);
269
270		assert!(!NftsByOwner::<T>::contains_key(destination));
271		assert!(!Instances::<T>::contains_key(instance));
272		assert_eq!(InstanceMetadata::<T>::iter_prefix(instance).count(), 0);
273		Ok(())
274	}
275
276	#[benchmark]
277	fn force_transfer() -> Result<(), BenchmarkError> {
278		let caller: T::AccountId = whitelisted_caller();
279		let from: T::AccountId = account("from", 0, 0);
280		let to: T::AccountId = account("to", 0, 0);
281		NftsByOwner::<T>::remove(&to);
282		let (collection, item) = create_definition::<T>(&caller)?;
283		let instance =
284			Pallet::<T>::do_mint(caller.clone(), collection, item, from.clone(), Vec::new())?;
285		Locked::<T>::insert(&from, LockInfo { retries: u8::MAX, until: u64::MAX });
286
287		#[extrinsic_call]
288		_(RawOrigin::Signed(caller), instance, to.clone());
289
290		let nft = NftsByOwner::<T>::get(&to).expect("destination owns the transferred NFT");
291		assert_eq!(nft.instance, instance);
292		assert_eq!(nft.state_nonce, 1);
293		assert_eq!(Instances::<T>::get(instance), Some(to));
294		assert!(!NftsByOwner::<T>::contains_key(from.clone()));
295		assert!(!Locked::<T>::contains_key(from));
296		Ok(())
297	}
298
299	#[benchmark]
300	fn delete_item() -> Result<(), BenchmarkError> {
301		let caller: T::AccountId = whitelisted_caller();
302		let (collection, item) = create_definition::<T>(&caller)?;
303
304		#[extrinsic_call]
305		_(RawOrigin::Signed(caller), collection, item);
306
307		assert!(!ItemDefs::<T>::contains_key(collection, item));
308		assert_eq!(Collections::<T>::get(collection).map(|info| info.item_count), Some(0));
309		Ok(())
310	}
311
312	#[benchmark]
313	fn delete_collection() -> Result<(), BenchmarkError> {
314		let caller: T::AccountId = whitelisted_caller();
315		fund::<T>(&caller);
316		let collection = Pallet::<T>::do_create_collection(caller.clone())?;
317
318		#[extrinsic_call]
319		_(RawOrigin::Signed(caller), collection);
320
321		assert!(!Collections::<T>::contains_key(collection));
322		Ok(())
323	}
324
325	#[benchmark]
326	fn claim_collection_ownership() -> Result<(), BenchmarkError> {
327		let owner: T::AccountId = whitelisted_caller();
328		let new_owner: T::AccountId = account("new_owner", 0, 0);
329		fund::<T>(&owner);
330		fund::<T>(&new_owner);
331		let collection = Pallet::<T>::do_create_collection(owner.clone())?;
332		Pallet::<T>::nominate_collection_owner(
333			RawOrigin::Signed(owner.clone()).into(),
334			collection,
335			Some(new_owner.clone()),
336		)?;
337
338		#[extrinsic_call]
339		_(RawOrigin::Signed(new_owner.clone()), collection);
340
341		let info = Collections::<T>::get(collection).expect("collection remains after claim");
342		assert_eq!(info.owner, new_owner);
343		assert_eq!(info.pending_owner, None);
344		Ok(())
345	}
346
347	#[benchmark]
348	fn as_scarcity_pipeline() -> Result<(), BenchmarkError> {
349		let owner: T::AccountId = whitelisted_caller();
350		let destination: T::AccountId = account("destination", 0, 0);
351		NftsByOwner::<T>::remove(&destination);
352		let (collection, item) = create_definition::<T>(&owner)?;
353		Pallet::<T>::do_mint(owner.clone(), collection, item, owner.clone(), Vec::new())?;
354		Locked::<T>::insert(&owner, LockInfo { retries: u8::MAX, until: 0 });
355		let nft = NftsByOwner::<T>::get(&owner).expect("owner has the minted NFT");
356
357		let call: T::RuntimeCall = Call::<T>::transfer { to: destination }.into();
358		let info = call.get_dispatch_info();
359		let extension = AsScarcity::<T>::new(Some(AsScarcityInfo::AsNft {
360			instance: nft.instance,
361			state_nonce: nft.state_nonce,
362		}));
363		let origin: T::RuntimeOrigin = RawOrigin::Signed(owner.clone()).into();
364		let failed_dispatch = Err(DispatchError::Other("benchmark failure"));
365
366		#[block]
367		{
368			let (_, val, origin) = extension
369				.validate(
370					origin,
371					&call,
372					&info,
373					0,
374					(),
375					&TxBaseImplication(()),
376					TransactionSource::External,
377				)
378				.expect("worst-case scarcity transfer validates");
379			let pre = extension
380				.prepare(val, &origin, &call, &info, 0)
381				.expect("validated NFT is prepared");
382			AsScarcity::<T>::post_dispatch_details(
383				pre,
384				&info,
385				&Default::default(),
386				0,
387				&failed_dispatch,
388			)
389			.expect("failed dispatch is restored and locked");
390		}
391
392		assert!(NftsByOwner::<T>::contains_key(&owner));
393		assert_eq!(Locked::<T>::get(&owner).map(|lock| lock.retries), Some(u8::MAX));
394		Ok(())
395	}
396
397	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
398}