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>::get(collection.id).try_change_property(property)?;740741 Ok(())742 }743744 pub fn change_property_permission(745 collection: &CollectionHandle<T>,746 sender: &T::CrossAccountId,747 property_key: PropertyKey,748 permission: PropertyPermission,749 ) -> DispatchResult {750 collection.check_is_owner_or_admin(sender)?;751752 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {753 permissions.try_insert(property_key, permission)754 })755 .map_err(|_| PropertiesError::PropertyLimitReached)?;756757 Ok(())758 }759760 fn set_field_raw(761 collection_id: CollectionId,762 field: CollectionField,763 value: Vec<u8>,764 ) -> DispatchResult {765 if !value.is_empty() {766 <CollectionData<T>>::insert(767 (collection_id, field),768 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,769 )770 } else {771 <CollectionData<T>>::remove((collection_id, field));772 }773 Ok(())774 }775776 pub fn set_field(777 collection: &CollectionHandle<T>,778 sender: &T::CrossAccountId,779 field: CollectionField,780 value: Vec<u8>,781 ) -> DispatchResult {782 collection.check_is_owner_or_admin(sender)?;783784 785786 Self::set_field_raw(collection.id, field, value)787 }788789 pub fn toggle_allowlist(790 collection: &CollectionHandle<T>,791 sender: &T::CrossAccountId,792 user: &T::CrossAccountId,793 allowed: bool,794 ) -> DispatchResult {795 collection.check_is_owner_or_admin(sender)?;796797 798799 if allowed {800 <Allowlist<T>>::insert((collection.id, user), true);801 } else {802 <Allowlist<T>>::remove((collection.id, user));803 }804805 Ok(())806 }807808 pub fn toggle_admin(809 collection: &CollectionHandle<T>,810 sender: &T::CrossAccountId,811 user: &T::CrossAccountId,812 admin: bool,813 ) -> DispatchResult {814 collection.check_is_owner_or_admin(sender)?;815816 let was_admin = <IsAdmin<T>>::get((collection.id, user));817 if was_admin == admin {818 return Ok(());819 }820 let amount = <AdminAmount<T>>::get(collection.id);821822 if admin {823 let amount = amount824 .checked_add(1)825 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;826 ensure!(827 amount <= Self::collection_admins_limit(),828 <Error<T>>::CollectionAdminCountExceeded,829 );830831 832833 <AdminAmount<T>>::insert(collection.id, amount);834 <IsAdmin<T>>::insert((collection.id, user), true);835 } else {836 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));837 <IsAdmin<T>>::remove((collection.id, user));838 }839840 Ok(())841 }842843 pub fn clamp_limits(844 mode: CollectionMode,845 old_limit: &CollectionLimits,846 mut new_limit: CollectionLimits,847 ) -> Result<CollectionLimits, DispatchError> {848 macro_rules! limit_default {849 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{850 $(851 if let Some($new) = $new.$field {852 let $old = $old.$field($($arg)?);853 let _ = $new;854 let _ = $old;855 $check856 } else {857 $new.$field = $old.$field858 }859 )*860 }};861 }862863 limit_default!(old_limit, new_limit,864 account_token_ownership_limit => ensure!(865 new_limit <= MAX_TOKEN_OWNERSHIP,866 <Error<T>>::CollectionLimitBoundsExceeded,867 ),868 sponsor_transfer_timeout(match mode {869 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,870 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,871 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,872 }) => ensure!(873 new_limit <= MAX_SPONSOR_TIMEOUT,874 <Error<T>>::CollectionLimitBoundsExceeded,875 ),876 sponsored_data_size => ensure!(877 new_limit <= CUSTOM_DATA_LIMIT,878 <Error<T>>::CollectionLimitBoundsExceeded,879 ),880 token_limit => ensure!(881 old_limit >= new_limit && new_limit > 0,882 <Error<T>>::CollectionTokenLimitExceeded883 ),884 owner_can_transfer => ensure!(885 old_limit || !new_limit,886 <Error<T>>::OwnerPermissionsCantBeReverted,887 ),888 owner_can_destroy => ensure!(889 old_limit || !new_limit,890 <Error<T>>::OwnerPermissionsCantBeReverted,891 ),892 sponsored_data_rate_limit => {},893 transfers_enabled => {},894 );895 Ok(new_limit)896 }897}898899#[macro_export]900macro_rules! unsupported {901 () => {902 Err(<Error<T>>::UnsupportedOperation.into())903 };904}905906907pub trait CommonWeightInfo<CrossAccountId> {908 fn create_item() -> Weight;909 fn create_multiple_items(amount: u32) -> Weight;910 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;911 fn burn_item() -> Weight;912 fn set_property() -> Weight;913 fn transfer() -> Weight;914 fn approve() -> Weight;915 fn transfer_from() -> Weight;916 fn burn_from() -> Weight;917 fn set_variable_metadata(bytes: u32) -> Weight;918}919920pub trait CommonCollectionOperations<T: Config> {921 fn create_item(922 &self,923 sender: T::CrossAccountId,924 to: T::CrossAccountId,925 data: CreateItemData,926 nesting_budget: &dyn Budget,927 ) -> DispatchResultWithPostInfo;928 fn create_multiple_items(929 &self,930 sender: T::CrossAccountId,931 to: T::CrossAccountId,932 data: Vec<CreateItemData>,933 nesting_budget: &dyn Budget,934 ) -> DispatchResultWithPostInfo;935 fn create_multiple_items_ex(936 &self,937 sender: T::CrossAccountId,938 data: CreateItemExData<T::CrossAccountId>,939 nesting_budget: &dyn Budget,940 ) -> DispatchResultWithPostInfo;941 fn burn_item(942 &self,943 sender: T::CrossAccountId,944 token: TokenId,945 amount: u128,946 ) -> DispatchResultWithPostInfo;947948 fn change_collection_property(949 &self,950 sender: T::CrossAccountId,951 property: Property,952 ) -> DispatchResultWithPostInfo;953954 fn change_token_property(955 &self,956 sender: T::CrossAccountId,957 token_id: TokenId,958 property: Property,959 ) -> DispatchResultWithPostInfo;960961 fn transfer(962 &self,963 sender: T::CrossAccountId,964 to: T::CrossAccountId,965 token: TokenId,966 amount: u128,967 nesting_budget: &dyn Budget,968 ) -> DispatchResultWithPostInfo;969 fn approve(970 &self,971 sender: T::CrossAccountId,972 spender: T::CrossAccountId,973 token: TokenId,974 amount: u128,975 ) -> DispatchResultWithPostInfo;976 fn transfer_from(977 &self,978 sender: T::CrossAccountId,979 from: T::CrossAccountId,980 to: T::CrossAccountId,981 token: TokenId,982 amount: u128,983 nesting_budget: &dyn Budget,984 ) -> DispatchResultWithPostInfo;985 fn burn_from(986 &self,987 sender: T::CrossAccountId,988 from: T::CrossAccountId,989 token: TokenId,990 amount: u128,991 nesting_budget: &dyn Budget,992 ) -> DispatchResultWithPostInfo;993994 fn set_variable_metadata(995 &self,996 sender: T::CrossAccountId,997 token: TokenId,998 data: BoundedVec<u8, CustomDataLimit>,999 ) -> DispatchResultWithPostInfo;10001001 fn check_nesting(1002 &self,1003 sender: T::CrossAccountId,1004 from: (CollectionId, TokenId),1005 under: TokenId,1006 budget: &dyn Budget,1007 ) -> DispatchResult;10081009 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1010 fn collection_tokens(&self) -> Vec<TokenId>;1011 fn token_exists(&self, token: TokenId) -> bool;1012 fn last_token_id(&self) -> TokenId;10131014 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1015 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1016 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;10171018 1019 fn total_supply(&self) -> u32;1020 1021 fn account_balance(&self, account: T::CrossAccountId) -> u32;1022 1023 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1024 fn allowance(1025 &self,1026 sender: T::CrossAccountId,1027 spender: T::CrossAccountId,1028 token: TokenId,1029 ) -> u128;1030}103110321033pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1034 let post_info = PostDispatchInfo {1035 actual_weight: Some(weight),1036 pays_fee: Pays::Yes,1037 };1038 match res {1039 Ok(()) => Ok(post_info),1040 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1041 }1042}