difftreelog
feat(common) benchmarking
in: master
3 files changed
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -1 +1,91 @@
-#![cfg(feature = "runtime-benchmarking")]
+use sp_std::vec::Vec;
+use crate::{Config, CollectionHandle};
+use nft_data_structs::{
+ CollectionMode, Collection, CollectionId, MAX_COLLECTION_NAME_LENGTH,
+ MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, OFFCHAIN_SCHEMA_LIMIT,
+ VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+};
+use frame_support::traits::{Currency, Get};
+use core::convert::TryInto;
+use sp_runtime::DispatchError;
+
+pub fn create_data(size: usize) -> Vec<u8> {
+ (0..size).map(|v| (v & 0xff) as u8).collect()
+}
+pub fn create_u16_data(size: usize) -> Vec<u16> {
+ (0..size).map(|v| (v & 0xffff) as u16).collect()
+}
+
+pub fn create_collection_raw<T: Config, R>(
+ owner: T::AccountId,
+ mode: CollectionMode,
+ handler: impl FnOnce(Collection<T>) -> Result<CollectionId, DispatchError>,
+ cast: impl FnOnce(CollectionHandle<T>) -> R,
+) -> Result<R, DispatchError> {
+ T::Currency::deposit_creating(&owner, T::CollectionCreationPrice::get());
+ let name = create_u16_data(MAX_COLLECTION_NAME_LENGTH)
+ .try_into()
+ .unwrap();
+ let description = create_u16_data(MAX_COLLECTION_DESCRIPTION_LENGTH)
+ .try_into()
+ .unwrap();
+ let token_prefix = create_data(MAX_TOKEN_PREFIX_LENGTH).try_into().unwrap();
+ let offchain_schema = create_data(OFFCHAIN_SCHEMA_LIMIT as usize)
+ .try_into()
+ .unwrap();
+ let variable_on_chain_schema = create_data(VARIABLE_ON_CHAIN_SCHEMA_LIMIT as usize)
+ .try_into()
+ .unwrap();
+ let const_on_chain_schema = create_data(CONST_ON_CHAIN_SCHEMA_LIMIT as usize)
+ .try_into()
+ .unwrap();
+ handler(Collection {
+ owner,
+ mode,
+ access: Default::default(),
+ name,
+ description,
+ token_prefix,
+ mint_mode: true,
+ offchain_schema,
+ schema_version: Default::default(),
+ sponsorship: Default::default(),
+ limits: Default::default(),
+ variable_on_chain_schema,
+ const_on_chain_schema,
+ meta_update_permission: Default::default(),
+ transfers_enabled: true,
+ })
+ .and_then(CollectionHandle::try_get)
+ .map(cast)
+}
+
+#[macro_export]
+macro_rules! bench_init {
+ ($name:ident: sub $(($id:expr))?; $($rest:tt)*) => {
+ let $name: T::AccountId = account(stringify!($name), 0 $(+ $id)?, SEED);
+ bench_init!($($rest)*);
+ };
+ ($name:ident: collection($owner:ident); $($rest:tt)*) => {
+ let $name = create_collection::<T>($owner.clone())?;
+ bench_init!($($rest)*);
+ };
+ ($name:ident: cross; $($rest:tt)*) => {
+ let $name = T::CrossAccountId::from_sub($name);
+ bench_init!($($rest)*);
+ };
+ ($name:ident: cross_sub $(($id:expr))?; $($rest:tt)*) => {
+ let account: T::AccountId = account(stringify!($name), 0 $(+ $id)?, SEED);
+ let $name = T::CrossAccountId::from_sub(account);
+ bench_init!($($rest)*);
+ };
+ ($name:ident: cross_from_sub; $($rest:tt)*) => {
+ let $name = T::CrossAccountId::from_sub($name);
+ bench_init!($($rest)*);
+ };
+ ($name:ident: cross_from_sub($from:ident); $($rest:tt)*) => {
+ let $name = T::CrossAccountId::from_sub($from);
+ bench_init!($($rest)*);
+ };
+ () => {}
+}
pallets/common/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use sp_std::vec::Vec;5use account::CrossAccountId;6use frame_support::{7 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},8 ensure, fail,9 traits::{Imbalance, Get, Currency},10};11use nft_data_structs::{12 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,13 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,14 MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,15};16pub use pallet::*;17use sp_core::H160;18use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};19pub mod account;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod erc;23pub mod eth;2425#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]26pub struct CollectionHandle<T: Config> {27 pub id: CollectionId,28 collection: Collection<T>,29 pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,30}31impl<T: Config> CollectionHandle<T> {32 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {33 <CollectionById<T>>::get(id).map(|collection| Self {34 id,35 collection,36 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(37 eth::collection_id_to_address(id),38 gas_limit,39 ),40 })41 }42 pub fn new(id: CollectionId) -> Option<Self> {43 Self::new_with_gas_limit(id, u64::MAX)44 }45 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {46 Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)47 }48 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {49 self.recorder.log_sub(log)50 }51 pub fn log_infallible(&self, log: impl evm_coder::ToLog) {52 self.recorder.log_infallible(log)53 }54 #[allow(dead_code)]55 fn consume_gas(&self, gas: u64) -> DispatchResult {56 self.recorder.consume_gas_sub(gas)57 }58 pub fn consume_sload(&self) -> DispatchResult {59 self.recorder.consume_sload_sub()60 }61 pub fn consume_sstores(&self, amount: usize) -> DispatchResult {62 self.recorder.consume_sstores_sub(amount)63 }64 pub fn consume_sstore(&self) -> DispatchResult {65 self.recorder.consume_sstore_sub()66 }67 pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {68 self.recorder.consume_log_sub(topics, data)69 }70 pub fn submit_logs(self) -> DispatchResult {71 self.recorder.submit_logs()72 }73 pub fn save(self) -> DispatchResult {74 self.recorder.submit_logs()?;75 <CollectionById<T>>::insert(self.id, self.collection);76 Ok(())77 }78}79impl<T: Config> Deref for CollectionHandle<T> {80 type Target = Collection<T>;8182 fn deref(&self) -> &Self::Target {83 &self.collection84 }85}8687impl<T: Config> DerefMut for CollectionHandle<T> {88 fn deref_mut(&mut self) -> &mut Self::Target {89 &mut self.collection90 }91}9293impl<T: Config> CollectionHandle<T> {94 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {95 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);96 Ok(())97 }98 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {99 self.consume_sload()?;100101 Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject.as_sub())))102 }103 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {104 ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);105 Ok(())106 }107 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {108 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)109 }110 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {111 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)112 }113 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {114 self.consume_sload()?;115116 ensure!(117 <Allowlist<T>>::get((self.id, user.as_sub())),118 <Error<T>>::AddressNotInAllowlist119 );120 Ok(())121 }122123 pub fn check_can_update_meta(124 &self,125 subject: &T::CrossAccountId,126 item_owner: &T::CrossAccountId,127 ) -> DispatchResult {128 match self.meta_update_permission {129 MetaUpdatePermission::ItemOwner => {130 ensure!(subject == item_owner, <Error<T>>::NoPermission);131 Ok(())132 }133 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),134 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),135 }136 }137}138139#[frame_support::pallet]140pub mod pallet {141 use super::*;142 use frame_support::{pallet_prelude::*};143 use frame_support::{Blake2_128Concat, storage::Key};144 use account::{EvmBackwardsAddressMapping, CrossAccountId};145 use frame_support::traits::Currency;146 use nft_data_structs::TokenId;147148 #[pallet::config]149 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config {150 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;151152 type CrossAccountId: CrossAccountId<Self::AccountId>;153154 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;155 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;156157 type Currency: Currency<Self::AccountId>;158 type CollectionCreationPrice: Get<159 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,160 >;161 type TreasuryAccountId: Get<Self::AccountId>;162 }163164 #[pallet::pallet]165 #[pallet::generate_store(pub(super) trait Store)]166 pub struct Pallet<T>(_);167168 #[pallet::event]169 #[pallet::generate_deposit(pub fn deposit_event)]170 pub enum Event<T: Config> {171 /// New collection was created172 ///173 /// # Arguments174 ///175 /// * collection_id: Globally unique identifier of newly created collection.176 ///177 /// * mode: [CollectionMode] converted into u8.178 ///179 /// * account_id: Collection owner.180 CollectionCreated(CollectionId, u8, T::AccountId),181182 /// New item was created.183 ///184 /// # Arguments185 ///186 /// * collection_id: Id of the collection where item was created.187 ///188 /// * item_id: Id of an item. Unique within the collection.189 ///190 /// * recipient: Owner of newly created item191 ///192 /// * amount: Always 1 for NFT193 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),194195 /// Collection item was burned.196 ///197 /// # Arguments198 ///199 /// * collection_id.200 ///201 /// * item_id: Identifier of burned NFT.202 ///203 /// * owner: which user has destroyed its tokens204 ///205 /// * amount: Always 1 for NFT206 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),207208 /// Item was transferred209 ///210 /// * collection_id: Id of collection to which item is belong211 ///212 /// * item_id: Id of an item213 ///214 /// * sender: Original owner of item215 ///216 /// * recipient: New owner of item217 ///218 /// * amount: Always 1 for NFT219 Transfer(220 CollectionId,221 TokenId,222 T::CrossAccountId,223 T::CrossAccountId,224 u128,225 ),226227 /// * collection_id228 ///229 /// * item_id230 ///231 /// * sender232 ///233 /// * spender234 ///235 /// * amount236 Approved(237 CollectionId,238 TokenId,239 T::CrossAccountId,240 T::CrossAccountId,241 u128,242 ),243 }244245 #[pallet::error]246 pub enum Error<T> {247 /// This collection does not exist.248 CollectionNotFound,249 /// Sender parameter and item owner must be equal.250 MustBeTokenOwner,251 /// No permission to perform action252 NoPermission,253 /// Collection is not in mint mode.254 PublicMintingNotAllowed,255 /// Address is not in white list.256 AddressNotInAllowlist,257258 /// Collection name can not be longer than 63 char.259 CollectionNameLimitExceeded,260 /// Collection description can not be longer than 255 char.261 CollectionDescriptionLimitExceeded,262 /// Token prefix can not be longer than 15 char.263 CollectionTokenPrefixLimitExceeded,264 /// Total collections bound exceeded.265 TotalCollectionsLimitExceeded,266 /// variable_data exceeded data limit.267 TokenVariableDataLimitExceeded,268269 /// Collection settings not allowing items transferring270 TransferNotAllowed,271 /// Account token limit exceeded per collection272 AccountTokenLimitExceeded,273 /// Collection token limit exceeded274 CollectionTokenLimitExceeded,275 /// Metadata flag frozen276 MetadataFlagFrozen,277278 /// Item not exists.279 TokenNotFound,280 /// Item balance not enough.281 TokenValueTooLow,282 /// Requested value more than approved.283 TokenValueNotEnough,284 /// Tried to approve more than owned285 CantApproveMoreThanOwned,286287 /// Can't transfer tokens to ethereum zero address288 AddressIsZero,289 /// Target collection doesn't supports this operation290 UnsupportedOperation,291 }292293 #[pallet::storage]294 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;295 #[pallet::storage]296 pub type DestroyedCollectionCount<T> =297 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;298299 /// Collection info300 #[pallet::storage]301 pub type CollectionById<T> = StorageMap<302 Hasher = Blake2_128Concat,303 Key = CollectionId,304 Value = Collection<T>,305 QueryKind = OptionQuery,306 >;307308 /// List of collection admins309 #[pallet::storage]310 pub type IsAdmin<T: Config> = StorageNMap<311 Key = (312 Key<Blake2_128Concat, CollectionId>,313 Key<Blake2_128Concat, T::AccountId>,314 ),315 Value = bool,316 QueryKind = ValueQuery,317 >;318319 /// Allowlisted collection users320 #[pallet::storage]321 pub type Allowlist<T: Config> = StorageNMap<322 Key = (323 Key<Blake2_128Concat, CollectionId>,324 Key<Blake2_128Concat, T::AccountId>,325 ),326 Value = bool,327 QueryKind = ValueQuery,328 >;329}330331impl<T: Config> Pallet<T> {332 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens333 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {334 ensure!(335 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,336 <Error<T>>::AddressIsZero337 );338 Ok(())339 }340}341342impl<T: Config> Pallet<T> {343 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {344 {345 ensure!(346 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,347 Error::<T>::CollectionNameLimitExceeded348 );349 ensure!(350 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,351 Error::<T>::CollectionDescriptionLimitExceeded352 );353 ensure!(354 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,355 Error::<T>::CollectionTokenPrefixLimitExceeded356 );357 }358359 let created_count = <CreatedCollectionCount<T>>::get()360 .0361 .checked_add(1)362 .ok_or(ArithmeticError::Overflow)?;363 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;364 let id = CollectionId(created_count);365366 // bound Total number of collections367 ensure!(368 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,369 <Error<T>>::TotalCollectionsLimitExceeded370 );371372 // =========373374 // Take a (non-refundable) deposit of collection creation375 {376 let mut imbalance =377 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();378 imbalance.subsume(379 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(380 &T::TreasuryAccountId::get(),381 T::CollectionCreationPrice::get(),382 ),383 );384 <T as Config>::Currency::settle(385 &data.owner,386 imbalance,387 WithdrawReasons::TRANSFER,388 ExistenceRequirement::KeepAlive,389 )390 .map_err(|_| Error::<T>::NoPermission)?;391 }392393 <CreatedCollectionCount<T>>::put(created_count);394 <Pallet<T>>::deposit_event(Event::CollectionCreated(395 id,396 data.mode.id(),397 data.owner.clone(),398 ));399 <CollectionById<T>>::insert(id, data);400 Ok(id)401 }402403 pub fn destroy_collection(404 collection: CollectionHandle<T>,405 sender: &T::CrossAccountId,406 ) -> DispatchResult {407 if !collection.limits.owner_can_destroy {408 fail!(Error::<T>::NoPermission);409 }410 collection.check_is_owner(&sender)?;411412 let destroyed_collections = <DestroyedCollectionCount<T>>::get()413 .0414 .checked_add(1)415 .ok_or(ArithmeticError::Overflow)?;416417 // =========418419 <DestroyedCollectionCount<T>>::put(destroyed_collections);420 <CollectionById<T>>::remove(collection.id);421 <IsAdmin<T>>::remove_prefix((collection.id,), None);422 <Allowlist<T>>::remove_prefix((collection.id,), None);423 Ok(())424 }425426 pub fn toggle_allowlist(427 collection: &CollectionHandle<T>,428 sender: &T::CrossAccountId,429 user: &T::CrossAccountId,430 allowed: bool,431 ) -> DispatchResult {432 collection.check_is_owner_or_admin(&sender)?;433434 // =========435436 if allowed {437 <Allowlist<T>>::insert((collection.id, user.as_sub()), true);438 } else {439 <Allowlist<T>>::remove((collection.id, user.as_sub()));440 }441442 Ok(())443 }444}445446#[macro_export]447macro_rules! unsupported {448 () => {449 Err(<Error<T>>::UnsupportedOperation.into())450 };451}452453/// Worst cases454pub trait CommonWeightInfo {455 fn create_item() -> Weight;456 fn create_multiple_items(amount: u32) -> Weight;457 fn burn_item() -> Weight;458 fn transfer() -> Weight;459 fn approve() -> Weight;460 fn transfer_from() -> Weight;461 fn set_variable_metadata(bytes: u32) -> Weight;462}463464pub trait CommonCollectionOperations<T: Config> {465 fn create_item(466 &self,467 sender: T::CrossAccountId,468 to: T::CrossAccountId,469 data: CreateItemData,470 ) -> DispatchResultWithPostInfo;471 fn create_multiple_items(472 &self,473 sender: T::CrossAccountId,474 to: T::CrossAccountId,475 data: Vec<CreateItemData>,476 ) -> DispatchResultWithPostInfo;477 fn burn_item(478 &self,479 sender: T::CrossAccountId,480 token: TokenId,481 amount: u128,482 ) -> DispatchResultWithPostInfo;483484 fn transfer(485 &self,486 sender: T::CrossAccountId,487 to: T::CrossAccountId,488 token: TokenId,489 amount: u128,490 ) -> DispatchResultWithPostInfo;491 fn approve(492 &self,493 sender: T::CrossAccountId,494 spender: T::CrossAccountId,495 token: TokenId,496 amount: u128,497 ) -> DispatchResultWithPostInfo;498 fn transfer_from(499 &self,500 sender: T::CrossAccountId,501 from: T::CrossAccountId,502 to: T::CrossAccountId,503 token: TokenId,504 amount: u128,505 ) -> DispatchResultWithPostInfo;506507 fn set_variable_metadata(508 &self,509 sender: T::CrossAccountId,510 token: TokenId,511 data: Vec<u8>,512 ) -> DispatchResultWithPostInfo;513514 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;515 fn token_exists(&self, token: TokenId) -> bool;516 fn last_token_id(&self) -> TokenId;517518 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;519 fn const_metadata(&self, token: TokenId) -> Vec<u8>;520 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;521522 /// How many tokens collection contains (Applicable to nonfungible/refungible)523 fn collection_tokens(&self) -> u32;524 /// Amount of different tokens account has (Applicable to nonfungible/refungible)525 fn account_balance(&self, account: T::CrossAccountId) -> u32;526 /// Amount of specific token account have (Applicable to fungible/refungible)527 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;528 fn allowance(529 &self,530 sender: T::CrossAccountId,531 spender: T::CrossAccountId,532 token: TokenId,533 ) -> u128;534}535536// Flexible enough for implementing CommonCollectionOperations537pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {538 let post_info = PostDispatchInfo {539 actual_weight: Some(weight),540 pays_fee: Pays::Yes,541 };542 match res {543 Ok(()) => Ok(post_info),544 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),545 }546}pallets/nft/Cargo.tomldiffbeforeafterboth--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -16,7 +16,10 @@
[features]
default = ['std']
-runtime-benchmarks = ['frame-benchmarking']
+runtime-benchmarks = [
+ 'frame-benchmarking',
+ 'pallet-common/runtime-benchmarks',
+]
std = [
'codec/std',
'serde/std',