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, PropertyKeyPermission,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 CollectionPropertyDeleted(CollectionId, PropertyKey),296297 TokenPropertySet(CollectionId, TokenId, Property),298299 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),300301 PropertyPermissionSet(CollectionId, PropertyKeyPermission),302 }303304 #[pallet::error]305 pub enum Error<T> {306 307 CollectionNotFound,308 309 MustBeTokenOwner,310 311 NoPermission,312 313 PublicMintingNotAllowed,314 315 AddressNotInAllowlist,316317 318 CollectionNameLimitExceeded,319 320 CollectionDescriptionLimitExceeded,321 322 CollectionTokenPrefixLimitExceeded,323 324 TotalCollectionsLimitExceeded,325 326 TokenVariableDataLimitExceeded,327 328 CollectionAdminCountExceeded,329 330 CollectionLimitBoundsExceeded,331 332 OwnerPermissionsCantBeReverted,333 334 TransferNotAllowed,335 336 AccountTokenLimitExceeded,337 338 CollectionTokenLimitExceeded,339 340 MetadataFlagFrozen,341342 343 TokenNotFound,344 345 TokenValueTooLow,346 347 ApprovedValueTooLow,348 349 CantApproveMoreThanOwned,350351 352 AddressIsZero,353 354 UnsupportedOperation,355356 357 NotSufficientFounds,358359 360 NestingIsDisabled,361 362 OnlyOwnerAllowedToNest,363 364 SourceCollectionIsNotAllowedToNest,365366 367 CollectionFieldSizeExceeded,368 }369370 #[pallet::storage]371 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;372 #[pallet::storage]373 pub type DestroyedCollectionCount<T> =374 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;375376 377 #[pallet::storage]378 pub type CollectionById<T> = StorageMap<379 Hasher = Blake2_128Concat,380 Key = CollectionId,381 Value = Collection<<T as frame_system::Config>::AccountId>,382 QueryKind = OptionQuery,383 >;384385 386 #[pallet::storage]387 pub type CollectionProperties<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = Properties,391 QueryKind = ValueQuery,392 OnEmpty = up_data_structs::CollectionProperties,393 >;394395 #[pallet::storage]396 #[pallet::getter(fn property_permission)]397 pub type CollectionPropertyPermissions<T> = StorageMap<398 Hasher = Blake2_128Concat,399 Key = CollectionId,400 Value = PropertiesPermissionMap,401 QueryKind = ValueQuery,402 >;403404 405 #[pallet::storage]406 pub type CollectionData<T> = StorageNMap<407 Key = (408 Key<Twox64Concat, CollectionId>,409 Key<Twox64Concat, CollectionField>,410 ),411 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,412 QueryKind = ValueQuery,413 >;414415 #[pallet::storage]416 pub type AdminAmount<T> = StorageMap<417 Hasher = Blake2_128Concat,418 Key = CollectionId,419 Value = u32,420 QueryKind = ValueQuery,421 >;422423 424 #[pallet::storage]425 pub type IsAdmin<T: Config> = StorageNMap<426 Key = (427 Key<Blake2_128Concat, CollectionId>,428 Key<Blake2_128Concat, T::CrossAccountId>,429 ),430 Value = bool,431 QueryKind = ValueQuery,432 >;433434 435 #[pallet::storage]436 pub type Allowlist<T: Config> = StorageNMap<437 Key = (438 Key<Blake2_128Concat, CollectionId>,439 Key<Blake2_128Concat, T::CrossAccountId>,440 ),441 Value = bool,442 QueryKind = ValueQuery,443 >;444445 446 #[pallet::storage]447 pub type DummyStorageValue<T: Config> = StorageValue<448 Value = (449 CollectionStats,450 CollectionId,451 TokenId,452 PhantomType<RpcCollection<T::AccountId>>,453 ),454 QueryKind = OptionQuery,455 >;456457 #[pallet::hooks]458 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {459 fn on_runtime_upgrade() -> Weight {460 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {461 use up_data_structs::{CollectionVersion1, CollectionVersion2};462 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {463 Self::set_field_raw(464 id,465 CollectionField::OffchainSchema,466 v.offchain_schema.clone().into_inner(),467 )468 .expect("data has lower bounds than field");469 Self::set_field_raw(470 id,471 CollectionField::VariableOnChainSchema,472 v.variable_on_chain_schema.clone().into_inner(),473 )474 .expect("data has lower bounds than field");475 Self::set_field_raw(476 id,477 CollectionField::ConstOnChainSchema,478 v.const_on_chain_schema.clone().into_inner(),479 )480 .expect("data has lower bounds than field");481482 Some(CollectionVersion2::from(v))483 });484 }485486 0487 }488 }489}490491impl<T: Config> Pallet<T> {492 493 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {494 ensure!(495 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,496 <Error<T>>::AddressIsZero497 );498 Ok(())499 }500 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {501 <IsAdmin<T>>::iter_prefix((collection,))502 .map(|(a, _)| a)503 .collect()504 }505 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {506 <Allowlist<T>>::iter_prefix((collection,))507 .map(|(a, _)| a)508 .collect()509 }510 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {511 <Allowlist<T>>::get((collection, user))512 }513 pub fn collection_stats() -> CollectionStats {514 let created = <CreatedCollectionCount<T>>::get();515 let destroyed = <DestroyedCollectionCount<T>>::get();516 CollectionStats {517 created: created.0,518 destroyed: destroyed.0,519 alive: created.0 - destroyed.0,520 }521 }522523 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {524 let collection = <CollectionById<T>>::get(collection);525 if collection.is_none() {526 return None;527 }528529 let collection = collection.unwrap();530 let limits = collection.limits;531 let effective_limits = CollectionLimits {532 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),533 sponsored_data_size: Some(limits.sponsored_data_size()),534 sponsored_data_rate_limit: Some(535 limits536 .sponsored_data_rate_limit537 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),538 ),539 token_limit: Some(limits.token_limit()),540 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(541 match collection.mode {542 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,543 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,544 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,545 },546 )),547 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),548 owner_can_transfer: Some(limits.owner_can_transfer()),549 owner_can_destroy: Some(limits.owner_can_destroy()),550 transfers_enabled: Some(limits.transfers_enabled()),551 nesting_rule: Some(limits.nesting_rule().clone()),552 };553554 Some(effective_limits)555 }556557 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {558 let Collection {559 name,560 description,561 owner,562 mode,563 access,564 token_prefix,565 mint_mode,566 schema_version,567 sponsorship,568 limits,569 meta_update_permission,570 ..571 } = <CollectionById<T>>::get(collection)?;572 Some(RpcCollection {573 name: name.into_inner(),574 description: description.into_inner(),575 owner,576 mode,577 access,578 token_prefix: token_prefix.into_inner(),579 mint_mode,580 schema_version,581 sponsorship,582 limits,583 meta_update_permission,584 offchain_schema: <CollectionData<T>>::get((585 collection,586 CollectionField::OffchainSchema,587 ))588 .into_inner(),589 const_on_chain_schema: <CollectionData<T>>::get((590 collection,591 CollectionField::ConstOnChainSchema,592 ))593 .into_inner(),594 variable_on_chain_schema: <CollectionData<T>>::get((595 collection,596 CollectionField::VariableOnChainSchema,597 ))598 .into_inner(),599 })600 }601}602603impl<T: Config> Pallet<T> {604 pub fn init_collection(605 owner: T::AccountId,606 data: CreateCollectionData<T::AccountId>,607 ) -> Result<CollectionId, DispatchError> {608 {609 ensure!(610 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,611 Error::<T>::CollectionTokenPrefixLimitExceeded612 );613 }614615 let created_count = <CreatedCollectionCount<T>>::get()616 .0617 .checked_add(1)618 .ok_or(ArithmeticError::Overflow)?;619 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;620 let id = CollectionId(created_count);621622 623 ensure!(624 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,625 <Error<T>>::TotalCollectionsLimitExceeded626 );627628 629630 let collection = Collection {631 owner: owner.clone(),632 name: data.name,633 mode: data.mode.clone(),634 mint_mode: false,635 access: data.access.unwrap_or_default(),636 description: data.description,637 token_prefix: data.token_prefix,638 schema_version: data.schema_version.unwrap_or_default(),639 sponsorship: data640 .pending_sponsor641 .map(SponsorshipState::Unconfirmed)642 .unwrap_or_default(),643 limits: data644 .limits645 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))646 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,647 meta_update_permission: data.meta_update_permission.unwrap_or_default(),648 649 650 };651652 CollectionProperties::<T>::insert(653 id,654 Properties::from_collection_props_vec(data.properties)?,655 );656657 let token_props_permissions: PropertiesPermissionMap = data658 .token_property_permissions659 .into_iter()660 .map(|property| (property.key, property.permission))661 .collect::<BTreeMap<_, _>>()662 .try_into()663 .map_err(|_| PropertiesError::PropertyLimitReached)?;664665 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);666667 668 {669 let mut imbalance =670 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();671 imbalance.subsume(672 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(673 &T::TreasuryAccountId::get(),674 T::CollectionCreationPrice::get(),675 ),676 );677 <T as Config>::Currency::settle(678 &owner,679 imbalance,680 WithdrawReasons::TRANSFER,681 ExistenceRequirement::KeepAlive,682 )683 .map_err(|_| Error::<T>::NotSufficientFounds)?;684 }685686 <CreatedCollectionCount<T>>::put(created_count);687 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));688 <CollectionById<T>>::insert(id, collection);689 Self::set_field_raw(690 id,691 CollectionField::OffchainSchema,692 data.offchain_schema.into_inner(),693 )694 .expect("data has lower bounds than field");695 Self::set_field_raw(696 id,697 CollectionField::VariableOnChainSchema,698 data.variable_on_chain_schema.into_inner(),699 )700 .expect("data has lower bounds than field");701 Self::set_field_raw(702 id,703 CollectionField::ConstOnChainSchema,704 data.const_on_chain_schema.into_inner(),705 )706 .expect("data has lower bounds than field");707 Ok(id)708 }709710 pub fn destroy_collection(711 collection: CollectionHandle<T>,712 sender: &T::CrossAccountId,713 ) -> DispatchResult {714 ensure!(715 collection.limits.owner_can_destroy(),716 <Error<T>>::NoPermission,717 );718 collection.check_is_owner(sender)?;719720 let destroyed_collections = <DestroyedCollectionCount<T>>::get()721 .0722 .checked_add(1)723 .ok_or(ArithmeticError::Overflow)?;724725 726727 <DestroyedCollectionCount<T>>::put(destroyed_collections);728 <CollectionById<T>>::remove(collection.id);729 <CollectionData<T>>::remove_prefix((collection.id,), None);730 <AdminAmount<T>>::remove(collection.id);731 <IsAdmin<T>>::remove_prefix((collection.id,), None);732 <Allowlist<T>>::remove_prefix((collection.id,), None);733734 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));735 Ok(())736 }737738 pub fn set_collection_property(739 collection: &CollectionHandle<T>,740 sender: &T::CrossAccountId,741 property: Property,742 ) -> DispatchResult {743 collection.check_is_owner_or_admin(sender)?;744745 CollectionProperties::<T>::try_mutate(collection.id, |properties| {746 properties.try_set_property(property.clone())747 })?;748749 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));750751 Ok(())752 }753754 pub fn set_collection_properties(755 collection: &CollectionHandle<T>,756 sender: &T::CrossAccountId,757 properties: Vec<Property>,758 ) -> DispatchResult {759 for property in properties {760 Self::set_collection_property(collection, sender, property)?;761 }762763 Ok(())764 }765766 pub fn delete_collection_property(767 collection: &CollectionHandle<T>,768 sender: &T::CrossAccountId,769 property_key: PropertyKey,770 ) -> DispatchResult {771 collection.check_is_owner_or_admin(sender)?;772773 CollectionProperties::<T>::mutate(collection.id, |properties| {774 properties.remove_property(&property_key);775 });776777 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, property_key));778779 Ok(())780 }781782 pub fn delete_collection_properties(783 collection: &CollectionHandle<T>,784 sender: &T::CrossAccountId,785 property_keys: Vec<PropertyKey>,786 ) -> DispatchResult {787 for key in property_keys {788 Self::delete_collection_property(collection, sender, key)?;789 }790791 Ok(())792 }793794 pub fn set_property_permission(795 collection: &CollectionHandle<T>,796 sender: &T::CrossAccountId,797 property_permission: PropertyKeyPermission,798 ) -> DispatchResult {799 collection.check_is_owner_or_admin(sender)?;800801 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);802 let current_permission = all_permissions.get(&property_permission.key);803 if matches![804 current_permission,805 Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)806 ] {807 return Err(<Error<T>>::NoPermission.into());808 }809810 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {811 let property_permission = property_permission.clone();812 permissions.try_insert(property_permission.key, property_permission.permission)813 })814 .map_err(|_| PropertiesError::PropertyLimitReached)?;815816 Self::deposit_event(Event::PropertyPermissionSet(817 collection.id,818 property_permission,819 ));820821 Ok(())822 }823824 pub fn set_property_permissions(825 collection: &CollectionHandle<T>,826 sender: &T::CrossAccountId,827 property_permissions: Vec<PropertyKeyPermission>,828 ) -> DispatchResult {829 for prop_pemission in property_permissions {830 Self::set_property_permission(collection, sender, prop_pemission)?;831 }832833 Ok(())834 }835836 fn set_field_raw(837 collection_id: CollectionId,838 field: CollectionField,839 value: Vec<u8>,840 ) -> DispatchResult {841 if !value.is_empty() {842 <CollectionData<T>>::insert(843 (collection_id, field),844 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,845 )846 } else {847 <CollectionData<T>>::remove((collection_id, field));848 }849 Ok(())850 }851852 pub fn set_field(853 collection: &CollectionHandle<T>,854 sender: &T::CrossAccountId,855 field: CollectionField,856 value: Vec<u8>,857 ) -> DispatchResult {858 collection.check_is_owner_or_admin(sender)?;859860 861862 Self::set_field_raw(collection.id, field, value)863 }864865 pub fn toggle_allowlist(866 collection: &CollectionHandle<T>,867 sender: &T::CrossAccountId,868 user: &T::CrossAccountId,869 allowed: bool,870 ) -> DispatchResult {871 collection.check_is_owner_or_admin(sender)?;872873 874875 if allowed {876 <Allowlist<T>>::insert((collection.id, user), true);877 } else {878 <Allowlist<T>>::remove((collection.id, user));879 }880881 Ok(())882 }883884 pub fn toggle_admin(885 collection: &CollectionHandle<T>,886 sender: &T::CrossAccountId,887 user: &T::CrossAccountId,888 admin: bool,889 ) -> DispatchResult {890 collection.check_is_owner_or_admin(sender)?;891892 let was_admin = <IsAdmin<T>>::get((collection.id, user));893 if was_admin == admin {894 return Ok(());895 }896 let amount = <AdminAmount<T>>::get(collection.id);897898 if admin {899 let amount = amount900 .checked_add(1)901 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;902 ensure!(903 amount <= Self::collection_admins_limit(),904 <Error<T>>::CollectionAdminCountExceeded,905 );906907 908909 <AdminAmount<T>>::insert(collection.id, amount);910 <IsAdmin<T>>::insert((collection.id, user), true);911 } else {912 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));913 <IsAdmin<T>>::remove((collection.id, user));914 }915916 Ok(())917 }918919 pub fn clamp_limits(920 mode: CollectionMode,921 old_limit: &CollectionLimits,922 mut new_limit: CollectionLimits,923 ) -> Result<CollectionLimits, DispatchError> {924 macro_rules! limit_default {925 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{926 $(927 if let Some($new) = $new.$field {928 let $old = $old.$field($($arg)?);929 let _ = $new;930 let _ = $old;931 $check932 } else {933 $new.$field = $old.$field934 }935 )*936 }};937 }938939 limit_default!(old_limit, new_limit,940 account_token_ownership_limit => ensure!(941 new_limit <= MAX_TOKEN_OWNERSHIP,942 <Error<T>>::CollectionLimitBoundsExceeded,943 ),944 sponsor_transfer_timeout(match mode {945 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,946 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,947 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,948 }) => ensure!(949 new_limit <= MAX_SPONSOR_TIMEOUT,950 <Error<T>>::CollectionLimitBoundsExceeded,951 ),952 sponsored_data_size => ensure!(953 new_limit <= CUSTOM_DATA_LIMIT,954 <Error<T>>::CollectionLimitBoundsExceeded,955 ),956 token_limit => ensure!(957 old_limit >= new_limit && new_limit > 0,958 <Error<T>>::CollectionTokenLimitExceeded959 ),960 owner_can_transfer => ensure!(961 old_limit || !new_limit,962 <Error<T>>::OwnerPermissionsCantBeReverted,963 ),964 owner_can_destroy => ensure!(965 old_limit || !new_limit,966 <Error<T>>::OwnerPermissionsCantBeReverted,967 ),968 sponsored_data_rate_limit => {},969 transfers_enabled => {},970 );971 Ok(new_limit)972 }973}974975#[macro_export]976macro_rules! unsupported {977 () => {978 Err(<Error<T>>::UnsupportedOperation.into())979 };980}981982983pub trait CommonWeightInfo<CrossAccountId> {984 fn create_item() -> Weight;985 fn create_multiple_items(amount: u32) -> Weight;986 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;987 fn burn_item() -> Weight;988 fn set_collection_properties(amount: u32) -> Weight;989 fn delete_collection_properties(amount: u32) -> Weight;990 fn set_token_properties(amount: u32) -> Weight;991 fn delete_token_properties(amount: u32) -> Weight;992 fn set_property_permissions(amount: u32) -> Weight;993 fn transfer() -> Weight;994 fn approve() -> Weight;995 fn transfer_from() -> Weight;996 fn burn_from() -> Weight;997 fn set_variable_metadata(bytes: u32) -> Weight;998}9991000pub trait CommonCollectionOperations<T: Config> {1001 fn create_item(1002 &self,1003 sender: T::CrossAccountId,1004 to: T::CrossAccountId,1005 data: CreateItemData,1006 nesting_budget: &dyn Budget,1007 ) -> DispatchResultWithPostInfo;1008 fn create_multiple_items(1009 &self,1010 sender: T::CrossAccountId,1011 to: T::CrossAccountId,1012 data: Vec<CreateItemData>,1013 nesting_budget: &dyn Budget,1014 ) -> DispatchResultWithPostInfo;1015 fn create_multiple_items_ex(1016 &self,1017 sender: T::CrossAccountId,1018 data: CreateItemExData<T::CrossAccountId>,1019 nesting_budget: &dyn Budget,1020 ) -> DispatchResultWithPostInfo;1021 fn burn_item(1022 &self,1023 sender: T::CrossAccountId,1024 token: TokenId,1025 amount: u128,1026 ) -> DispatchResultWithPostInfo;1027 fn set_collection_properties(1028 &self,1029 sender: T::CrossAccountId,1030 properties: Vec<Property>,1031 ) -> DispatchResultWithPostInfo;1032 fn delete_collection_properties(1033 &self,1034 sender: &T::CrossAccountId,1035 property_keys: Vec<PropertyKey>,1036 ) -> DispatchResultWithPostInfo;1037 fn set_token_properties(1038 &self,1039 sender: T::CrossAccountId,1040 token_id: TokenId,1041 property: Vec<Property>,1042 ) -> DispatchResultWithPostInfo;1043 fn delete_token_properties(1044 &self,1045 sender: T::CrossAccountId,1046 token_id: TokenId,1047 property_keys: Vec<PropertyKey>,1048 ) -> DispatchResultWithPostInfo;1049 fn set_property_permissions(1050 &self,1051 sender: &T::CrossAccountId,1052 property_permissions: Vec<PropertyKeyPermission>,1053 ) -> DispatchResultWithPostInfo;1054 fn transfer(1055 &self,1056 sender: T::CrossAccountId,1057 to: T::CrossAccountId,1058 token: TokenId,1059 amount: u128,1060 nesting_budget: &dyn Budget,1061 ) -> DispatchResultWithPostInfo;1062 fn approve(1063 &self,1064 sender: T::CrossAccountId,1065 spender: T::CrossAccountId,1066 token: TokenId,1067 amount: u128,1068 ) -> DispatchResultWithPostInfo;1069 fn transfer_from(1070 &self,1071 sender: T::CrossAccountId,1072 from: T::CrossAccountId,1073 to: T::CrossAccountId,1074 token: TokenId,1075 amount: u128,1076 nesting_budget: &dyn Budget,1077 ) -> DispatchResultWithPostInfo;1078 fn burn_from(1079 &self,1080 sender: T::CrossAccountId,1081 from: T::CrossAccountId,1082 token: TokenId,1083 amount: u128,1084 nesting_budget: &dyn Budget,1085 ) -> DispatchResultWithPostInfo;10861087 fn set_variable_metadata(1088 &self,1089 sender: T::CrossAccountId,1090 token: TokenId,1091 data: BoundedVec<u8, CustomDataLimit>,1092 ) -> DispatchResultWithPostInfo;10931094 fn check_nesting(1095 &self,1096 sender: T::CrossAccountId,1097 from: (CollectionId, TokenId),1098 under: TokenId,1099 budget: &dyn Budget,1100 ) -> DispatchResult;11011102 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1103 fn collection_tokens(&self) -> Vec<TokenId>;1104 fn token_exists(&self, token: TokenId) -> bool;1105 fn last_token_id(&self) -> TokenId;11061107 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1108 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1109 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;11101111 1112 fn total_supply(&self) -> u32;1113 1114 fn account_balance(&self, account: T::CrossAccountId) -> u32;1115 1116 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1117 fn allowance(1118 &self,1119 sender: T::CrossAccountId,1120 spender: T::CrossAccountId,1121 token: TokenId,1122 ) -> u128;1123}112411251126pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1127 let post_info = PostDispatchInfo {1128 actual_weight: Some(weight),1129 pays_fee: Pays::Yes,1130 };1131 match res {1132 Ok(()) => Ok(post_info),1133 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1134 }1135}