1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::{vec::Vec, collections::btree_map::BTreeMap};22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_mirrored(log)80 }81 pub fn log_direct(&self, log: impl evm_coder::ToLog) {82 self.recorder.log_direct(log)83 }84 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {85 self.recorder86 .consume_gas(T::GasWeightMapping::weight_to_gas(87 <T as frame_system::Config>::DbWeight::get()88 .read89 .saturating_mul(reads),90 ))91 }92 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {93 self.recorder94 .consume_gas(T::GasWeightMapping::weight_to_gas(95 <T as frame_system::Config>::DbWeight::get()96 .write97 .saturating_mul(writes),98 ))99 }100 pub fn submit_logs(self) {101 self.recorder.submit_logs()102 }103 pub fn save(self) -> DispatchResult {104 self.recorder.submit_logs();105 <CollectionById<T>>::insert(self.id, self.collection);106 Ok(())107 }108}109impl<T: Config> Deref for CollectionHandle<T> {110 type Target = Collection<T::AccountId>;111112 fn deref(&self) -> &Self::Target {113 &self.collection114 }115}116117impl<T: Config> DerefMut for CollectionHandle<T> {118 fn deref_mut(&mut self) -> &mut Self::Target {119 &mut self.collection120 }121}122123impl<T: Config> CollectionHandle<T> {124 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {125 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);126 Ok(())127 }128 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {129 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))130 }131 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {132 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);133 Ok(())134 }135 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {139 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)140 }141 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {142 ensure!(143 <Allowlist<T>>::get((self.id, user)),144 <Error<T>>::AddressNotInAllowlist145 );146 Ok(())147 }148149 pub fn check_can_update_meta(150 &self,151 subject: &T::CrossAccountId,152 item_owner: &T::CrossAccountId,153 ) -> DispatchResult {154 match self.meta_update_permission {155 MetaUpdatePermission::ItemOwner => {156 ensure!(subject == item_owner, <Error<T>>::NoPermission);157 Ok(())158 }159 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),160 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),161 }162 }163}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use pallet_evm::account;169 use dispatch::CollectionDispatch;170 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};171 use frame_system::pallet_prelude::*;172 use frame_support::traits::Currency;173 use up_data_structs::{TokenId, mapping::TokenAddressMapping};174 use scale_info::TypeInfo;175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config179 {180 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;181182 type Currency: Currency<Self::AccountId>;183184 #[pallet::constant]185 type CollectionCreationPrice: Get<186 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,187 >;188 type CollectionDispatch: CollectionDispatch<Self>;189190 type TreasuryAccountId: Get<Self::AccountId>;191192 type EvmTokenAddressMapping: TokenAddressMapping<H160>;193 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;194 }195196 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);197198 #[pallet::pallet]199 #[pallet::storage_version(STORAGE_VERSION)]200 #[pallet::generate_store(pub(super) trait Store)]201 pub struct Pallet<T>(_);202203 #[pallet::extra_constants]204 impl<T: Config> Pallet<T> {205 pub fn collection_admins_limit() -> u32 {206 COLLECTION_ADMINS_LIMIT207 }208 }209210 #[pallet::event]211 #[pallet::generate_deposit(pub fn deposit_event)]212 pub enum Event<T: Config> {213 214 215 216 217 218 219 220 221 222 CollectionCreated(CollectionId, u8, T::AccountId),223224 225 226 227 228 229 CollectionDestroyed(CollectionId),230231 232 233 234 235 236 237 238 239 240 241 242 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 Transfer(269 CollectionId,270 TokenId,271 T::CrossAccountId,272 T::CrossAccountId,273 u128,274 ),275276 277 278 279 280 281 282 283 284 285 Approved(286 CollectionId,287 TokenId,288 T::CrossAccountId,289 T::CrossAccountId,290 u128,291 ),292293 CollectionPropertySet(CollectionId, Property),294295 TokenPropertySet(CollectionId, TokenId, Property),296 }297298 #[pallet::error]299 pub enum Error<T> {300 301 CollectionNotFound,302 303 MustBeTokenOwner,304 305 NoPermission,306 307 PublicMintingNotAllowed,308 309 AddressNotInAllowlist,310311 312 CollectionNameLimitExceeded,313 314 CollectionDescriptionLimitExceeded,315 316 CollectionTokenPrefixLimitExceeded,317 318 TotalCollectionsLimitExceeded,319 320 TokenVariableDataLimitExceeded,321 322 CollectionAdminCountExceeded,323 324 CollectionLimitBoundsExceeded,325 326 OwnerPermissionsCantBeReverted,327 328 TransferNotAllowed,329 330 AccountTokenLimitExceeded,331 332 CollectionTokenLimitExceeded,333 334 MetadataFlagFrozen,335336 337 TokenNotFound,338 339 TokenValueTooLow,340 341 ApprovedValueTooLow,342 343 CantApproveMoreThanOwned,344345 346 AddressIsZero,347 348 UnsupportedOperation,349350 351 NotSufficientFounds,352353 354 NestingIsDisabled,355 356 OnlyOwnerAllowedToNest,357 358 SourceCollectionIsNotAllowedToNest,359360 361 CollectionFieldSizeExceeded,362 }363364 #[pallet::storage]365 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;366 #[pallet::storage]367 pub type DestroyedCollectionCount<T> =368 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;369370 371 #[pallet::storage]372 pub type CollectionById<T> = StorageMap<373 Hasher = Blake2_128Concat,374 Key = CollectionId,375 Value = Collection<<T as frame_system::Config>::AccountId>,376 QueryKind = OptionQuery,377 >;378379 380 #[pallet::storage]381 pub type CollectionProperties<T> = StorageMap<382 Hasher = Blake2_128Concat,383 Key = CollectionId,384 Value = Properties,385 QueryKind = ValueQuery,386 OnEmpty = up_data_structs::CollectionProperties,387 >;388389 #[pallet::storage]390 #[pallet::getter(fn property_permission)]391 pub type CollectionPropertyPermissions<T> = StorageMap<392 Hasher = Blake2_128Concat,393 Key = CollectionId,394 Value = PropertiesPermissionMap,395 QueryKind = ValueQuery,396 >;397398 399 #[pallet::storage]400 pub type CollectionData<T> = StorageNMap<401 Key = (402 Key<Twox64Concat, CollectionId>,403 Key<Twox64Concat, CollectionField>,404 ),405 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,406 QueryKind = ValueQuery,407 >;408409 #[pallet::storage]410 pub type AdminAmount<T> = StorageMap<411 Hasher = Blake2_128Concat,412 Key = CollectionId,413 Value = u32,414 QueryKind = ValueQuery,415 >;416417 418 #[pallet::storage]419 pub type IsAdmin<T: Config> = StorageNMap<420 Key = (421 Key<Blake2_128Concat, CollectionId>,422 Key<Blake2_128Concat, T::CrossAccountId>,423 ),424 Value = bool,425 QueryKind = ValueQuery,426 >;427428 429 #[pallet::storage]430 pub type Allowlist<T: Config> = StorageNMap<431 Key = (432 Key<Blake2_128Concat, CollectionId>,433 Key<Blake2_128Concat, T::CrossAccountId>,434 ),435 Value = bool,436 QueryKind = ValueQuery,437 >;438439 440 #[pallet::storage]441 pub type DummyStorageValue<T: Config> = StorageValue<442 Value = (443 CollectionStats,444 CollectionId,445 TokenId,446 PhantomType<RpcCollection<T::AccountId>>,447 ),448 QueryKind = OptionQuery,449 >;450451 #[pallet::hooks]452 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {453 fn on_runtime_upgrade() -> Weight {454 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {455 use up_data_structs::{CollectionVersion1, CollectionVersion2};456 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {457 Self::set_field_raw(458 id,459 CollectionField::OffchainSchema,460 v.offchain_schema.clone().into_inner(),461 )462 .expect("data has lower bounds than field");463 Self::set_field_raw(464 id,465 CollectionField::VariableOnChainSchema,466 v.variable_on_chain_schema.clone().into_inner(),467 )468 .expect("data has lower bounds than field");469 Self::set_field_raw(470 id,471 CollectionField::ConstOnChainSchema,472 v.const_on_chain_schema.clone().into_inner(),473 )474 .expect("data has lower bounds than field");475476 Some(CollectionVersion2::from(v))477 });478 }479480 0481 }482 }483}484485impl<T: Config> Pallet<T> {486 487 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {488 ensure!(489 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,490 <Error<T>>::AddressIsZero491 );492 Ok(())493 }494 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {495 <IsAdmin<T>>::iter_prefix((collection,))496 .map(|(a, _)| a)497 .collect()498 }499 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {500 <Allowlist<T>>::iter_prefix((collection,))501 .map(|(a, _)| a)502 .collect()503 }504 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {505 <Allowlist<T>>::get((collection, user))506 }507 pub fn collection_stats() -> CollectionStats {508 let created = <CreatedCollectionCount<T>>::get();509 let destroyed = <DestroyedCollectionCount<T>>::get();510 CollectionStats {511 created: created.0,512 destroyed: destroyed.0,513 alive: created.0 - destroyed.0,514 }515 }516517 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {518 let collection = <CollectionById<T>>::get(collection);519 if collection.is_none() {520 return None;521 }522523 let collection = collection.unwrap();524 let limits = collection.limits;525 let effective_limits = CollectionLimits {526 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),527 sponsored_data_size: Some(limits.sponsored_data_size()),528 sponsored_data_rate_limit: Some(529 limits530 .sponsored_data_rate_limit531 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),532 ),533 token_limit: Some(limits.token_limit()),534 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(535 match collection.mode {536 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,537 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,538 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,539 },540 )),541 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),542 owner_can_transfer: Some(limits.owner_can_transfer()),543 owner_can_destroy: Some(limits.owner_can_destroy()),544 transfers_enabled: Some(limits.transfers_enabled()),545 nesting_rule: Some(limits.nesting_rule().clone()),546 };547548 Some(effective_limits)549 }550551 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {552 let Collection {553 name,554 description,555 owner,556 mode,557 access,558 token_prefix,559 mint_mode,560 schema_version,561 sponsorship,562 limits,563 meta_update_permission,564 ..565 } = <CollectionById<T>>::get(collection)?;566 Some(RpcCollection {567 name: name.into_inner(),568 description: description.into_inner(),569 owner,570 mode,571 access,572 token_prefix: token_prefix.into_inner(),573 mint_mode,574 schema_version,575 sponsorship,576 limits,577 meta_update_permission,578 offchain_schema: <CollectionData<T>>::get((579 collection,580 CollectionField::OffchainSchema,581 ))582 .into_inner(),583 const_on_chain_schema: <CollectionData<T>>::get((584 collection,585 CollectionField::ConstOnChainSchema,586 ))587 .into_inner(),588 variable_on_chain_schema: <CollectionData<T>>::get((589 collection,590 CollectionField::VariableOnChainSchema,591 ))592 .into_inner(),593 })594 }595}596597impl<T: Config> Pallet<T> {598 pub fn init_collection(599 owner: T::AccountId,600 data: CreateCollectionData<T::AccountId>,601 ) -> Result<CollectionId, DispatchError> {602 {603 ensure!(604 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,605 Error::<T>::CollectionTokenPrefixLimitExceeded606 );607 }608609 let created_count = <CreatedCollectionCount<T>>::get()610 .0611 .checked_add(1)612 .ok_or(ArithmeticError::Overflow)?;613 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;614 let id = CollectionId(created_count);615616 617 ensure!(618 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,619 <Error<T>>::TotalCollectionsLimitExceeded620 );621622 623624 let collection = Collection {625 owner: owner.clone(),626 name: data.name,627 mode: data.mode.clone(),628 mint_mode: false,629 access: data.access.unwrap_or_default(),630 description: data.description,631 token_prefix: data.token_prefix,632 schema_version: data.schema_version.unwrap_or_default(),633 sponsorship: data634 .pending_sponsor635 .map(SponsorshipState::Unconfirmed)636 .unwrap_or_default(),637 limits: data638 .limits639 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))640 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,641 meta_update_permission: data.meta_update_permission.unwrap_or_default(),642 643 644 };645646 CollectionProperties::<T>::insert(647 id,648 Properties::from_collection_props_vec(data.properties)?,649 );650651 let token_props_permissions: PropertiesPermissionMap = data652 .token_property_permissions653 .into_iter()654 .map(|property| (property.key, property.permission))655 .collect::<BTreeMap<_, _>>()656 .try_into()657 .map_err(|_| PropertiesError::PropertyLimitReached)?;658659 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);660661 662 {663 let mut imbalance =664 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();665 imbalance.subsume(666 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(667 &T::TreasuryAccountId::get(),668 T::CollectionCreationPrice::get(),669 ),670 );671 <T as Config>::Currency::settle(672 &owner,673 imbalance,674 WithdrawReasons::TRANSFER,675 ExistenceRequirement::KeepAlive,676 )677 .map_err(|_| Error::<T>::NotSufficientFounds)?;678 }679680 <CreatedCollectionCount<T>>::put(created_count);681 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));682 <CollectionById<T>>::insert(id, collection);683 Self::set_field_raw(684 id,685 CollectionField::OffchainSchema,686 data.offchain_schema.into_inner(),687 )688 .expect("data has lower bounds than field");689 Self::set_field_raw(690 id,691 CollectionField::VariableOnChainSchema,692 data.variable_on_chain_schema.into_inner(),693 )694 .expect("data has lower bounds than field");695 Self::set_field_raw(696 id,697 CollectionField::ConstOnChainSchema,698 data.const_on_chain_schema.into_inner(),699 )700 .expect("data has lower bounds than field");701 Ok(id)702 }703704 pub fn destroy_collection(705 collection: CollectionHandle<T>,706 sender: &T::CrossAccountId,707 ) -> DispatchResult {708 ensure!(709 collection.limits.owner_can_destroy(),710 <Error<T>>::NoPermission,711 );712 collection.check_is_owner(sender)?;713714 let destroyed_collections = <DestroyedCollectionCount<T>>::get()715 .0716 .checked_add(1)717 .ok_or(ArithmeticError::Overflow)?;718719 720721 <DestroyedCollectionCount<T>>::put(destroyed_collections);722 <CollectionById<T>>::remove(collection.id);723 <CollectionData<T>>::remove_prefix((collection.id,), None);724 <AdminAmount<T>>::remove(collection.id);725 <IsAdmin<T>>::remove_prefix((collection.id,), None);726 <Allowlist<T>>::remove_prefix((collection.id,), None);727728 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));729 Ok(())730 }731732 pub fn change_collection_property(733 collection: &CollectionHandle<T>,734 sender: &T::CrossAccountId,735 property: Property,736 ) -> DispatchResult {737 collection.check_is_owner_or_admin(sender)?;738739 CollectionProperties::<T>::try_mutate(740 collection.id,741 |properties| properties.try_change_property(property.clone())742 )?;743744 <Pallet<T>>::deposit_event(Event::CollectionPropertySet(collection.id, property));745746 Ok(())747 }748749 pub fn change_collection_properties(750 collection: &CollectionHandle<T>,751 sender: &T::CrossAccountId,752 properties: Vec<Property>,753 ) -> DispatchResult {754 for property in properties {755 Self::change_collection_property(collection, sender, property)?;756 }757758 Ok(())759 }760761 pub fn change_property_permission(762 collection: &CollectionHandle<T>,763 sender: &T::CrossAccountId,764 property_key: PropertyKey,765 permission: PropertyPermission,766 ) -> DispatchResult {767 collection.check_is_owner_or_admin(sender)?;768769 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {770 permissions.try_insert(property_key, permission)771 })772 .map_err(|_| PropertiesError::PropertyLimitReached)?;773774 Ok(())775 }776777 fn set_field_raw(778 collection_id: CollectionId,779 field: CollectionField,780 value: Vec<u8>,781 ) -> DispatchResult {782 if !value.is_empty() {783 <CollectionData<T>>::insert(784 (collection_id, field),785 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,786 )787 } else {788 <CollectionData<T>>::remove((collection_id, field));789 }790 Ok(())791 }792793 pub fn set_field(794 collection: &CollectionHandle<T>,795 sender: &T::CrossAccountId,796 field: CollectionField,797 value: Vec<u8>,798 ) -> DispatchResult {799 collection.check_is_owner_or_admin(sender)?;800801 802803 Self::set_field_raw(collection.id, field, value)804 }805806 pub fn toggle_allowlist(807 collection: &CollectionHandle<T>,808 sender: &T::CrossAccountId,809 user: &T::CrossAccountId,810 allowed: bool,811 ) -> DispatchResult {812 collection.check_is_owner_or_admin(sender)?;813814 815816 if allowed {817 <Allowlist<T>>::insert((collection.id, user), true);818 } else {819 <Allowlist<T>>::remove((collection.id, user));820 }821822 Ok(())823 }824825 pub fn toggle_admin(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 user: &T::CrossAccountId,829 admin: bool,830 ) -> DispatchResult {831 collection.check_is_owner_or_admin(sender)?;832833 let was_admin = <IsAdmin<T>>::get((collection.id, user));834 if was_admin == admin {835 return Ok(());836 }837 let amount = <AdminAmount<T>>::get(collection.id);838839 if admin {840 let amount = amount841 .checked_add(1)842 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;843 ensure!(844 amount <= Self::collection_admins_limit(),845 <Error<T>>::CollectionAdminCountExceeded,846 );847848 849850 <AdminAmount<T>>::insert(collection.id, amount);851 <IsAdmin<T>>::insert((collection.id, user), true);852 } else {853 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));854 <IsAdmin<T>>::remove((collection.id, user));855 }856857 Ok(())858 }859860 pub fn clamp_limits(861 mode: CollectionMode,862 old_limit: &CollectionLimits,863 mut new_limit: CollectionLimits,864 ) -> Result<CollectionLimits, DispatchError> {865 macro_rules! limit_default {866 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{867 $(868 if let Some($new) = $new.$field {869 let $old = $old.$field($($arg)?);870 let _ = $new;871 let _ = $old;872 $check873 } else {874 $new.$field = $old.$field875 }876 )*877 }};878 }879880 limit_default!(old_limit, new_limit,881 account_token_ownership_limit => ensure!(882 new_limit <= MAX_TOKEN_OWNERSHIP,883 <Error<T>>::CollectionLimitBoundsExceeded,884 ),885 sponsor_transfer_timeout(match mode {886 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,887 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,888 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,889 }) => ensure!(890 new_limit <= MAX_SPONSOR_TIMEOUT,891 <Error<T>>::CollectionLimitBoundsExceeded,892 ),893 sponsored_data_size => ensure!(894 new_limit <= CUSTOM_DATA_LIMIT,895 <Error<T>>::CollectionLimitBoundsExceeded,896 ),897 token_limit => ensure!(898 old_limit >= new_limit && new_limit > 0,899 <Error<T>>::CollectionTokenLimitExceeded900 ),901 owner_can_transfer => ensure!(902 old_limit || !new_limit,903 <Error<T>>::OwnerPermissionsCantBeReverted,904 ),905 owner_can_destroy => ensure!(906 old_limit || !new_limit,907 <Error<T>>::OwnerPermissionsCantBeReverted,908 ),909 sponsored_data_rate_limit => {},910 transfers_enabled => {},911 );912 Ok(new_limit)913 }914}915916#[macro_export]917macro_rules! unsupported {918 () => {919 Err(<Error<T>>::UnsupportedOperation.into())920 };921}922923924pub trait CommonWeightInfo<CrossAccountId> {925 fn create_item() -> Weight;926 fn create_multiple_items(amount: u32) -> Weight;927 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;928 fn burn_item() -> Weight;929 fn change_collection_properties(amount: u32) -> Weight;930 fn change_token_properties(amount: u32) -> Weight;931 fn transfer() -> Weight;932 fn approve() -> Weight;933 fn transfer_from() -> Weight;934 fn burn_from() -> Weight;935 fn set_variable_metadata(bytes: u32) -> Weight;936}937938pub trait CommonCollectionOperations<T: Config> {939 fn create_item(940 &self,941 sender: T::CrossAccountId,942 to: T::CrossAccountId,943 data: CreateItemData,944 nesting_budget: &dyn Budget,945 ) -> DispatchResultWithPostInfo;946 fn create_multiple_items(947 &self,948 sender: T::CrossAccountId,949 to: T::CrossAccountId,950 data: Vec<CreateItemData>,951 nesting_budget: &dyn Budget,952 ) -> DispatchResultWithPostInfo;953 fn create_multiple_items_ex(954 &self,955 sender: T::CrossAccountId,956 data: CreateItemExData<T::CrossAccountId>,957 nesting_budget: &dyn Budget,958 ) -> DispatchResultWithPostInfo;959 fn burn_item(960 &self,961 sender: T::CrossAccountId,962 token: TokenId,963 amount: u128,964 ) -> DispatchResultWithPostInfo;965966 fn change_collection_properties(967 &self,968 sender: T::CrossAccountId,969 properties: Vec<Property>,970 ) -> DispatchResultWithPostInfo;971972 fn change_token_properties(973 &self,974 sender: T::CrossAccountId,975 token_id: TokenId,976 property: Vec<Property>,977 ) -> DispatchResultWithPostInfo;978979 fn transfer(980 &self,981 sender: T::CrossAccountId,982 to: T::CrossAccountId,983 token: TokenId,984 amount: u128,985 nesting_budget: &dyn Budget,986 ) -> DispatchResultWithPostInfo;987 fn approve(988 &self,989 sender: T::CrossAccountId,990 spender: T::CrossAccountId,991 token: TokenId,992 amount: u128,993 ) -> DispatchResultWithPostInfo;994 fn transfer_from(995 &self,996 sender: T::CrossAccountId,997 from: T::CrossAccountId,998 to: T::CrossAccountId,999 token: TokenId,1000 amount: u128,1001 nesting_budget: &dyn Budget,1002 ) -> DispatchResultWithPostInfo;1003 fn burn_from(1004 &self,1005 sender: T::CrossAccountId,1006 from: T::CrossAccountId,1007 token: TokenId,1008 amount: u128,1009 nesting_budget: &dyn Budget,1010 ) -> DispatchResultWithPostInfo;10111012 fn set_variable_metadata(1013 &self,1014 sender: T::CrossAccountId,1015 token: TokenId,1016 data: BoundedVec<u8, CustomDataLimit>,1017 ) -> DispatchResultWithPostInfo;10181019 fn check_nesting(1020 &self,1021 sender: T::CrossAccountId,1022 from: (CollectionId, TokenId),1023 under: TokenId,1024 budget: &dyn Budget,1025 ) -> DispatchResult;10261027 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1028 fn collection_tokens(&self) -> Vec<TokenId>;1029 fn token_exists(&self, token: TokenId) -> bool;1030 fn last_token_id(&self) -> TokenId;10311032 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1033 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1034 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;10351036 1037 fn total_supply(&self) -> u32;1038 1039 fn account_balance(&self, account: T::CrossAccountId) -> u32;1040 1041 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1042 fn allowance(1043 &self,1044 sender: T::CrossAccountId,1045 spender: T::CrossAccountId,1046 token: TokenId,1047 ) -> u128;1048}104910501051pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1052 let post_info = PostDispatchInfo {1053 actual_weight: Some(weight),1054 pays_fee: Pays::Yes,1055 };1056 match res {1057 Ok(()) => Ok(post_info),1058 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1059 }1060}