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;20pub mod benchmarking;21pub mod erc;22pub mod eth;2324#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]25pub struct CollectionHandle<T: Config> {26 pub id: CollectionId,27 collection: Collection<T>,28 pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,29}30impl<T: Config> CollectionHandle<T> {31 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {32 <CollectionById<T>>::get(id).map(|collection| Self {33 id,34 collection,35 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(36 eth::collection_id_to_address(id),37 gas_limit,38 ),39 })40 }41 pub fn new(id: CollectionId) -> Option<Self> {42 Self::new_with_gas_limit(id, u64::MAX)43 }44 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {45 Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)46 }47 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {48 self.recorder.log_sub(log)49 }50 pub fn log_infallible(&self, log: impl evm_coder::ToLog) {51 self.recorder.log_infallible(log)52 }53 #[allow(dead_code)]54 fn consume_gas(&self, gas: u64) -> DispatchResult {55 self.recorder.consume_gas_sub(gas)56 }57 pub fn consume_sload(&self) -> DispatchResult {58 self.recorder.consume_sload_sub()59 }60 pub fn consume_sstores(&self, amount: usize) -> DispatchResult {61 self.recorder.consume_sstores_sub(amount)62 }63 pub fn consume_sstore(&self) -> DispatchResult {64 self.recorder.consume_sstore_sub()65 }66 pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {67 self.recorder.consume_log_sub(topics, data)68 }69 pub fn submit_logs(self) -> DispatchResult {70 self.recorder.submit_logs()71 }72 pub fn save(self) -> DispatchResult {73 self.recorder.submit_logs()?;74 <CollectionById<T>>::insert(self.id, self.collection);75 Ok(())76 }77}78impl<T: Config> Deref for CollectionHandle<T> {79 type Target = Collection<T>;8081 fn deref(&self) -> &Self::Target {82 &self.collection83 }84}8586impl<T: Config> DerefMut for CollectionHandle<T> {87 fn deref_mut(&mut self) -> &mut Self::Target {88 &mut self.collection89 }90}9192impl<T: Config> CollectionHandle<T> {93 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {94 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);95 Ok(())96 }97 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {98 self.consume_sload()?;99100 Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject.as_sub())))101 }102 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {103 ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);104 Ok(())105 }106 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {107 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)108 }109 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {110 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)111 }112 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {113 self.consume_sload()?;114115 ensure!(116 <Allowlist<T>>::get((self.id, user.as_sub())),117 <Error<T>>::AddressNotInAllowlist118 );119 Ok(())120 }121122 pub fn check_can_update_meta(123 &self,124 subject: &T::CrossAccountId,125 item_owner: &T::CrossAccountId,126 ) -> DispatchResult {127 match self.meta_update_permission {128 MetaUpdatePermission::ItemOwner => {129 ensure!(subject == item_owner, <Error<T>>::NoPermission);130 Ok(())131 }132 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),133 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),134 }135 }136}137138#[frame_support::pallet]139pub mod pallet {140 use super::*;141 use frame_support::{pallet_prelude::*};142 use frame_support::{Blake2_128Concat, storage::Key};143 use account::{EvmBackwardsAddressMapping, CrossAccountId};144 use frame_support::traits::Currency;145 use nft_data_structs::TokenId;146147 #[pallet::config]148 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config {149 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;150151 type CrossAccountId: CrossAccountId<Self::AccountId>;152153 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;154 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;155156 type Currency: Currency<Self::AccountId>;157 type CollectionCreationPrice: Get<158 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,159 >;160 type TreasuryAccountId: Get<Self::AccountId>;161 }162163 #[pallet::pallet]164 #[pallet::generate_store(pub(super) trait Store)]165 pub struct Pallet<T>(_);166167 #[pallet::event]168 #[pallet::generate_deposit(pub fn deposit_event)]169 pub enum Event<T: Config> {170 /// New collection was created171 ///172 /// # Arguments173 ///174 /// * collection_id: Globally unique identifier of newly created collection.175 ///176 /// * mode: [CollectionMode] converted into u8.177 ///178 /// * account_id: Collection owner.179 CollectionCreated(CollectionId, u8, T::AccountId),180181 /// New item was created.182 ///183 /// # Arguments184 ///185 /// * collection_id: Id of the collection where item was created.186 ///187 /// * item_id: Id of an item. Unique within the collection.188 ///189 /// * recipient: Owner of newly created item190 ///191 /// * amount: Always 1 for NFT192 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),193194 /// Collection item was burned.195 ///196 /// # Arguments197 ///198 /// * collection_id.199 ///200 /// * item_id: Identifier of burned NFT.201 ///202 /// * owner: which user has destroyed its tokens203 ///204 /// * amount: Always 1 for NFT205 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),206207 /// Item was transferred208 ///209 /// * collection_id: Id of collection to which item is belong210 ///211 /// * item_id: Id of an item212 ///213 /// * sender: Original owner of item214 ///215 /// * recipient: New owner of item216 ///217 /// * amount: Always 1 for NFT218 Transfer(219 CollectionId,220 TokenId,221 T::CrossAccountId,222 T::CrossAccountId,223 u128,224 ),225226 /// * collection_id227 ///228 /// * item_id229 ///230 /// * sender231 ///232 /// * spender233 ///234 /// * amount235 Approved(236 CollectionId,237 TokenId,238 T::CrossAccountId,239 T::CrossAccountId,240 u128,241 ),242 }243244 #[pallet::error]245 pub enum Error<T> {246 /// This collection does not exist.247 CollectionNotFound,248 /// Sender parameter and item owner must be equal.249 MustBeTokenOwner,250 /// No permission to perform action251 NoPermission,252 /// Collection is not in mint mode.253 PublicMintingNotAllowed,254 /// Address is not in white list.255 AddressNotInAllowlist,256257 /// Collection name can not be longer than 63 char.258 CollectionNameLimitExceeded,259 /// Collection description can not be longer than 255 char.260 CollectionDescriptionLimitExceeded,261 /// Token prefix can not be longer than 15 char.262 CollectionTokenPrefixLimitExceeded,263 /// Total collections bound exceeded.264 TotalCollectionsLimitExceeded,265 /// variable_data exceeded data limit.266 TokenVariableDataLimitExceeded,267268 /// Collection settings not allowing items transferring269 TransferNotAllowed,270 /// Account token limit exceeded per collection271 AccountTokenLimitExceeded,272 /// Collection token limit exceeded273 CollectionTokenLimitExceeded,274 /// Metadata flag frozen275 MetadataFlagFrozen,276277 /// Item not exists.278 TokenNotFound,279 /// Item balance not enough.280 TokenValueTooLow,281 /// Requested value more than approved.282 TokenValueNotEnough,283 /// Tried to approve more than owned284 CantApproveMoreThanOwned,285286 /// Can't transfer tokens to ethereum zero address287 AddressIsZero,288 /// Target collection doesn't supports this operation289 UnsupportedOperation,290 }291292 #[pallet::storage]293 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;294 #[pallet::storage]295 pub type DestroyedCollectionCount<T> =296 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;297298 /// Collection info299 #[pallet::storage]300 pub type CollectionById<T> = StorageMap<301 Hasher = Blake2_128Concat,302 Key = CollectionId,303 Value = Collection<T>,304 QueryKind = OptionQuery,305 >;306307 /// List of collection admins308 #[pallet::storage]309 pub type IsAdmin<T: Config> = StorageNMap<310 Key = (311 Key<Blake2_128Concat, CollectionId>,312 Key<Blake2_128Concat, T::AccountId>,313 ),314 Value = bool,315 QueryKind = ValueQuery,316 >;317318 /// Allowlisted collection users319 #[pallet::storage]320 pub type Allowlist<T: Config> = StorageNMap<321 Key = (322 Key<Blake2_128Concat, CollectionId>,323 Key<Blake2_128Concat, T::AccountId>,324 ),325 Value = bool,326 QueryKind = ValueQuery,327 >;328}329330impl<T: Config> Pallet<T> {331 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens332 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {333 ensure!(334 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,335 <Error<T>>::AddressIsZero336 );337 Ok(())338 }339}340341impl<T: Config> Pallet<T> {342 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {343 {344 ensure!(345 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,346 Error::<T>::CollectionNameLimitExceeded347 );348 ensure!(349 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,350 Error::<T>::CollectionDescriptionLimitExceeded351 );352 ensure!(353 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,354 Error::<T>::CollectionTokenPrefixLimitExceeded355 );356 }357358 let created_count = <CreatedCollectionCount<T>>::get()359 .0360 .checked_add(1)361 .ok_or(ArithmeticError::Overflow)?;362 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;363 let id = CollectionId(created_count);364365 // bound Total number of collections366 ensure!(367 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,368 <Error<T>>::TotalCollectionsLimitExceeded369 );370371 // =========372373 // Take a (non-refundable) deposit of collection creation374 {375 let mut imbalance =376 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();377 imbalance.subsume(378 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(379 &T::TreasuryAccountId::get(),380 T::CollectionCreationPrice::get(),381 ),382 );383 <T as Config>::Currency::settle(384 &data.owner,385 imbalance,386 WithdrawReasons::TRANSFER,387 ExistenceRequirement::KeepAlive,388 )389 .map_err(|_| Error::<T>::NoPermission)?;390 }391392 <CreatedCollectionCount<T>>::put(created_count);393 <Pallet<T>>::deposit_event(Event::CollectionCreated(394 id,395 data.mode.id(),396 data.owner.clone(),397 ));398 <CollectionById<T>>::insert(id, data);399 Ok(id)400 }401402 pub fn destroy_collection(403 collection: CollectionHandle<T>,404 sender: &T::CrossAccountId,405 ) -> DispatchResult {406 if !collection.limits.owner_can_destroy {407 fail!(Error::<T>::NoPermission);408 }409 collection.check_is_owner(&sender)?;410411 let destroyed_collections = <DestroyedCollectionCount<T>>::get()412 .0413 .checked_add(1)414 .ok_or(ArithmeticError::Overflow)?;415416 // =========417418 <DestroyedCollectionCount<T>>::put(destroyed_collections);419 <CollectionById<T>>::remove(collection.id);420 <IsAdmin<T>>::remove_prefix((collection.id,), None);421 <Allowlist<T>>::remove_prefix((collection.id,), None);422 Ok(())423 }424425 pub fn toggle_allowlist(426 collection: &CollectionHandle<T>,427 sender: &T::CrossAccountId,428 user: &T::CrossAccountId,429 allowed: bool,430 ) -> DispatchResult {431 collection.check_is_owner_or_admin(&sender)?;432433 // =========434435 if allowed {436 <Allowlist<T>>::insert((collection.id, user.as_sub()), true);437 } else {438 <Allowlist<T>>::remove((collection.id, user.as_sub()));439 }440441 Ok(())442 }443}444445#[macro_export]446macro_rules! unsupported {447 () => {448 Err(<Error<T>>::UnsupportedOperation.into())449 };450}451452/// Worst cases453pub trait CommonWeightInfo {454 fn create_item() -> Weight;455 fn create_multiple_items(amount: u32) -> Weight;456 fn burn_item() -> Weight;457 fn transfer() -> Weight;458 fn approve() -> Weight;459 fn transfer_from() -> Weight;460 fn set_variable_metadata(bytes: u32) -> Weight;461}462463pub trait CommonCollectionOperations<T: Config> {464 fn create_item(465 &self,466 sender: T::CrossAccountId,467 to: T::CrossAccountId,468 data: CreateItemData,469 ) -> DispatchResultWithPostInfo;470 fn create_multiple_items(471 &self,472 sender: T::CrossAccountId,473 to: T::CrossAccountId,474 data: Vec<CreateItemData>,475 ) -> DispatchResultWithPostInfo;476 fn burn_item(477 &self,478 sender: T::CrossAccountId,479 token: TokenId,480 amount: u128,481 ) -> DispatchResultWithPostInfo;482483 fn transfer(484 &self,485 sender: T::CrossAccountId,486 to: T::CrossAccountId,487 token: TokenId,488 amount: u128,489 ) -> DispatchResultWithPostInfo;490 fn approve(491 &self,492 sender: T::CrossAccountId,493 spender: T::CrossAccountId,494 token: TokenId,495 amount: u128,496 ) -> DispatchResultWithPostInfo;497 fn transfer_from(498 &self,499 sender: T::CrossAccountId,500 from: T::CrossAccountId,501 to: T::CrossAccountId,502 token: TokenId,503 amount: u128,504 ) -> DispatchResultWithPostInfo;505506 fn set_variable_metadata(507 &self,508 sender: T::CrossAccountId,509 token: TokenId,510 data: Vec<u8>,511 ) -> DispatchResultWithPostInfo;512513 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;514 fn token_exists(&self, token: TokenId) -> bool;515 fn last_token_id(&self) -> TokenId;516517 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;518 fn const_metadata(&self, token: TokenId) -> Vec<u8>;519 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;520521 /// How many tokens collection contains (Applicable to nonfungible/refungible)522 fn collection_tokens(&self) -> u32;523 /// Amount of different tokens account has (Applicable to nonfungible/refungible)524 fn account_balance(&self, account: T::CrossAccountId) -> u32;525 /// Amount of specific token account have (Applicable to fungible/refungible)526 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;527 fn allowance(528 &self,529 sender: T::CrossAccountId,530 spender: T::CrossAccountId,531 token: TokenId,532 ) -> u128;533}534535// Flexible enough for implementing CommonCollectionOperations536pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {537 let post_info = PostDispatchInfo {538 actual_weight: Some(weight),539 pays_fee: Pays::Yes,540 };541 match res {542 Ok(()) => Ok(post_info),543 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),544 }545}1#![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',