1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151152 pub fn save(self) -> DispatchResult {153 <CollectionById<T>>::insert(self.id, self.collection);154 Ok(())155 }156157 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {158 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);159 Ok(())160 }161162 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {163 if self.collection.sponsorship.pending_sponsor() != Some(sender) {164 return Ok(false);165 }166167 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());168 Ok(true)169 }170171 172 173 pub fn check_is_internal(&self) -> DispatchResult {174 if self.external_collection {175 return Err(<Error<T>>::CollectionIsExternal)?;176 }177178 Ok(())179 }180181 182 183 pub fn check_is_external(&self) -> DispatchResult {184 if !self.external_collection {185 return Err(<Error<T>>::CollectionIsInternal)?;186 }187188 Ok(())189 }190}191192impl<T: Config> Deref for CollectionHandle<T> {193 type Target = Collection<T::AccountId>;194195 fn deref(&self) -> &Self::Target {196 &self.collection197 }198}199200impl<T: Config> DerefMut for CollectionHandle<T> {201 fn deref_mut(&mut self) -> &mut Self::Target {202 &mut self.collection203 }204}205206impl<T: Config> CollectionHandle<T> {207 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {208 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);209 Ok(())210 }211 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {212 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))213 }214 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {215 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);216 Ok(())217 }218 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {219 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)220 }221 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {222 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)223 }224 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {225 ensure!(226 <Allowlist<T>>::get((self.id, user)),227 <Error<T>>::AddressNotInAllowlist228 );229 Ok(())230 }231}232233#[frame_support::pallet]234pub mod pallet {235 use super::*;236 use pallet_evm::account;237 use dispatch::CollectionDispatch;238 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};239 use frame_system::pallet_prelude::*;240 use frame_support::traits::Currency;241 use up_data_structs::{TokenId, mapping::TokenAddressMapping};242 use scale_info::TypeInfo;243 use weights::WeightInfo;244245 #[pallet::config]246 pub trait Config:247 frame_system::Config248 + pallet_evm_coder_substrate::Config249 + pallet_evm::Config250 + TypeInfo251 + account::Config252 {253 type WeightInfo: WeightInfo;254 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;255256 type Currency: Currency<Self::AccountId>;257258 #[pallet::constant]259 type CollectionCreationPrice: Get<260 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,261 >;262 type CollectionDispatch: CollectionDispatch<Self>;263264 type TreasuryAccountId: Get<Self::AccountId>;265 type ContractAddress: Get<H160>;266267 type EvmTokenAddressMapping: TokenAddressMapping<H160>;268 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;269 }270271 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);272273 #[pallet::pallet]274 #[pallet::storage_version(STORAGE_VERSION)]275 #[pallet::generate_store(pub(super) trait Store)]276 pub struct Pallet<T>(_);277278 #[pallet::extra_constants]279 impl<T: Config> Pallet<T> {280 pub fn collection_admins_limit() -> u32 {281 COLLECTION_ADMINS_LIMIT282 }283 }284285 #[pallet::event]286 #[pallet::generate_deposit(pub fn deposit_event)]287 pub enum Event<T: Config> {288 289 290 291 292 293 294 295 296 297 CollectionCreated(CollectionId, u8, T::AccountId),298299 300 301 302 303 304 CollectionDestroyed(CollectionId),305306 307 308 309 310 311 312 313 314 315 316 317 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),318319 320 321 322 323 324 325 326 327 328 329 330 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),331332 333 334 335 336 337 338 339 340 341 342 343 Transfer(344 CollectionId,345 TokenId,346 T::CrossAccountId,347 T::CrossAccountId,348 u128,349 ),350351 352 353 354 355 356 357 358 359 360 Approved(361 CollectionId,362 TokenId,363 T::CrossAccountId,364 T::CrossAccountId,365 u128,366 ),367368 CollectionPropertySet(CollectionId, PropertyKey),369370 CollectionPropertyDeleted(CollectionId, PropertyKey),371372 TokenPropertySet(CollectionId, TokenId, PropertyKey),373374 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),375376 PropertyPermissionSet(CollectionId, PropertyKey),377 }378379 #[pallet::error]380 pub enum Error<T> {381 382 CollectionNotFound,383 384 MustBeTokenOwner,385 386 NoPermission,387 388 CantDestroyNotEmptyCollection,389 390 PublicMintingNotAllowed,391 392 AddressNotInAllowlist,393394 395 CollectionNameLimitExceeded,396 397 CollectionDescriptionLimitExceeded,398 399 CollectionTokenPrefixLimitExceeded,400 401 TotalCollectionsLimitExceeded,402 403 CollectionAdminCountExceeded,404 405 CollectionLimitBoundsExceeded,406 407 OwnerPermissionsCantBeReverted,408 409 TransferNotAllowed,410 411 AccountTokenLimitExceeded,412 413 CollectionTokenLimitExceeded,414 415 MetadataFlagFrozen,416417 418 TokenNotFound,419 420 TokenValueTooLow,421 422 ApprovedValueTooLow,423 424 CantApproveMoreThanOwned,425426 427 AddressIsZero,428 429 UnsupportedOperation,430431 432 NotSufficientFounds,433434 435 NestingIsDisabled,436 437 OnlyOwnerAllowedToNest,438 439 SourceCollectionIsNotAllowedToNest,440441 442 CollectionFieldSizeExceeded,443444 445 NoSpaceForProperty,446447 448 PropertyLimitReached,449450 451 PropertyKeyIsTooLong,452453 454 InvalidCharacterInPropertyKey,455456 457 EmptyPropertyKey,458459 460 CollectionIsExternal,461462 463 CollectionIsInternal,464 }465466 #[pallet::storage]467 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;468 #[pallet::storage]469 pub type DestroyedCollectionCount<T> =470 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;471472 473 #[pallet::storage]474 pub type CollectionById<T> = StorageMap<475 Hasher = Blake2_128Concat,476 Key = CollectionId,477 Value = Collection<<T as frame_system::Config>::AccountId>,478 QueryKind = OptionQuery,479 >;480481 482 #[pallet::storage]483 #[pallet::getter(fn collection_properties)]484 pub type CollectionProperties<T> = StorageMap<485 Hasher = Blake2_128Concat,486 Key = CollectionId,487 Value = Properties,488 QueryKind = ValueQuery,489 OnEmpty = up_data_structs::CollectionProperties,490 >;491492 #[pallet::storage]493 #[pallet::getter(fn property_permissions)]494 pub type CollectionPropertyPermissions<T> = StorageMap<495 Hasher = Blake2_128Concat,496 Key = CollectionId,497 Value = PropertiesPermissionMap,498 QueryKind = ValueQuery,499 >;500501 #[pallet::storage]502 pub type AdminAmount<T> = StorageMap<503 Hasher = Blake2_128Concat,504 Key = CollectionId,505 Value = u32,506 QueryKind = ValueQuery,507 >;508509 510 #[pallet::storage]511 pub type IsAdmin<T: Config> = StorageNMap<512 Key = (513 Key<Blake2_128Concat, CollectionId>,514 Key<Blake2_128Concat, T::CrossAccountId>,515 ),516 Value = bool,517 QueryKind = ValueQuery,518 >;519520 521 #[pallet::storage]522 pub type Allowlist<T: Config> = StorageNMap<523 Key = (524 Key<Blake2_128Concat, CollectionId>,525 Key<Blake2_128Concat, T::CrossAccountId>,526 ),527 Value = bool,528 QueryKind = ValueQuery,529 >;530531 532 #[pallet::storage]533 pub type DummyStorageValue<T: Config> = StorageValue<534 Value = (535 CollectionStats,536 CollectionId,537 TokenId,538 TokenChild,539 PhantomType<(540 TokenData<T::CrossAccountId>,541 RpcCollection<T::AccountId>,542 543 RmrkCollectionInfo<T::AccountId>,544 RmrkInstanceInfo<T::AccountId>,545 RmrkResourceInfo,546 RmrkPropertyInfo,547 RmrkBaseInfo<T::AccountId>,548 RmrkPartType,549 RmrkTheme,550 RmrkNftChild,551 )>,552 ),553 QueryKind = OptionQuery,554 >;555556 #[pallet::hooks]557 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {558 fn on_runtime_upgrade() -> Weight {559 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {560 use up_data_structs::{CollectionVersion1, CollectionVersion2};561 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {562 let mut props = Vec::new();563 if !v.offchain_schema.is_empty() {564 props.push(Property {565 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),566 value: v567 .offchain_schema568 .clone()569 .into_inner()570 .try_into()571 .expect("offchain schema too big"),572 });573 }574 if !v.variable_on_chain_schema.is_empty() {575 props.push(Property {576 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),577 value: v578 .variable_on_chain_schema579 .clone()580 .into_inner()581 .try_into()582 .expect("offchain schema too big"),583 });584 }585 if !v.const_on_chain_schema.is_empty() {586 props.push(Property {587 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),588 value: v589 .const_on_chain_schema590 .clone()591 .into_inner()592 .try_into()593 .expect("offchain schema too big"),594 });595 }596 props.push(Property {597 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),598 value: match v.schema_version {599 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),600 SchemaVersion::Unique => b"Unique".as_slice(),601 }602 .to_vec()603 .try_into()604 .unwrap(),605 });606 Self::set_scoped_collection_properties(607 id,608 PropertyScope::None,609 props.into_iter(),610 )611 .expect("existing data larger than properties");612 let mut new = CollectionVersion2::from(v.clone());613 new.permissions.access = Some(v.access);614 new.permissions.mint_mode = Some(v.mint_mode);615 Some(new)616 });617 }618619 0620 }621 }622}623624impl<T: Config> Pallet<T> {625 626 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {627 ensure!(628 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,629 <Error<T>>::AddressIsZero630 );631 Ok(())632 }633 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {634 <IsAdmin<T>>::iter_prefix((collection,))635 .map(|(a, _)| a)636 .collect()637 }638 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {639 <Allowlist<T>>::iter_prefix((collection,))640 .map(|(a, _)| a)641 .collect()642 }643 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {644 <Allowlist<T>>::get((collection, user))645 }646 pub fn collection_stats() -> CollectionStats {647 let created = <CreatedCollectionCount<T>>::get();648 let destroyed = <DestroyedCollectionCount<T>>::get();649 CollectionStats {650 created: created.0,651 destroyed: destroyed.0,652 alive: created.0 - destroyed.0,653 }654 }655656 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {657 let collection = <CollectionById<T>>::get(collection);658 if collection.is_none() {659 return None;660 }661662 let collection = collection.unwrap();663 let limits = collection.limits;664 let effective_limits = CollectionLimits {665 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),666 sponsored_data_size: Some(limits.sponsored_data_size()),667 sponsored_data_rate_limit: Some(668 limits669 .sponsored_data_rate_limit670 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),671 ),672 token_limit: Some(limits.token_limit()),673 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(674 match collection.mode {675 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,676 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,677 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,678 },679 )),680 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),681 owner_can_transfer: Some(limits.owner_can_transfer()),682 owner_can_destroy: Some(limits.owner_can_destroy()),683 transfers_enabled: Some(limits.transfers_enabled()),684 };685686 Some(effective_limits)687 }688689 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {690 let Collection {691 name,692 description,693 owner,694 mode,695 token_prefix,696 sponsorship,697 limits,698 permissions,699 external_collection,700 } = <CollectionById<T>>::get(collection)?;701702 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)703 .into_iter()704 .map(|(key, permission)| PropertyKeyPermission { key, permission })705 .collect();706707 let properties = <CollectionProperties<T>>::get(collection)708 .into_iter()709 .map(|(key, value)| Property { key, value })710 .collect();711712 let permissions = CollectionPermissions {713 access: Some(permissions.access()),714 mint_mode: Some(permissions.mint_mode()),715 nesting: Some(permissions.nesting().clone()),716 };717718 Some(RpcCollection {719 name: name.into_inner(),720 description: description.into_inner(),721 owner,722 mode,723 token_prefix: token_prefix.into_inner(),724 sponsorship,725 limits,726 permissions,727 token_property_permissions,728 properties,729 read_only: external_collection,730 })731 }732}733734macro_rules! limit_default {735 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{736 $(737 if let Some($new) = $new.$field {738 let $old = $old.$field($($arg)?);739 let _ = $new;740 let _ = $old;741 $check742 } else {743 $new.$field = $old.$field744 }745 )*746 }};747}748macro_rules! limit_default_clone {749 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{750 $(751 if let Some($new) = $new.$field.clone() {752 let $old = $old.$field($($arg)?);753 let _ = $new;754 let _ = $old;755 $check756 } else {757 $new.$field = $old.$field.clone()758 }759 )*760 }};761}762763impl<T: Config> Pallet<T> {764 pub fn init_collection(765 owner: T::CrossAccountId,766 data: CreateCollectionData<T::AccountId>,767 is_external: bool,768 ) -> Result<CollectionId, DispatchError> {769 {770 ensure!(771 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,772 Error::<T>::CollectionTokenPrefixLimitExceeded773 );774 }775776 let created_count = <CreatedCollectionCount<T>>::get()777 .0778 .checked_add(1)779 .ok_or(ArithmeticError::Overflow)?;780 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;781 let id = CollectionId(created_count);782783 784 ensure!(785 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,786 <Error<T>>::TotalCollectionsLimitExceeded787 );788789 790791 let collection = Collection {792 owner: owner.as_sub().clone(),793 name: data.name,794 mode: data.mode.clone(),795 description: data.description,796 token_prefix: data.token_prefix,797 sponsorship: data798 .pending_sponsor799 .map(SponsorshipState::Unconfirmed)800 .unwrap_or_default(),801 limits: data802 .limits803 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))804 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,805 permissions: data806 .permissions807 .map(|permissions| {808 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)809 })810 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,811 external_collection: is_external,812 };813814 let mut collection_properties = up_data_structs::CollectionProperties::get();815 collection_properties816 .try_set_from_iter(data.properties.into_iter())817 .map_err(<Error<T>>::from)?;818819 CollectionProperties::<T>::insert(id, collection_properties);820821 let mut token_props_permissions = PropertiesPermissionMap::new();822 token_props_permissions823 .try_set_from_iter(data.token_property_permissions.into_iter())824 .map_err(<Error<T>>::from)?;825826 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);827828 829 {830 let mut imbalance =831 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();832 imbalance.subsume(833 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(834 &T::TreasuryAccountId::get(),835 T::CollectionCreationPrice::get(),836 ),837 );838 <T as Config>::Currency::settle(839 &owner.as_sub(),840 imbalance,841 WithdrawReasons::TRANSFER,842 ExistenceRequirement::KeepAlive,843 )844 .map_err(|_| Error::<T>::NotSufficientFounds)?;845 }846847 <CreatedCollectionCount<T>>::put(created_count);848 <Pallet<T>>::deposit_event(Event::CollectionCreated(849 id,850 data.mode.id(),851 owner.as_sub().clone(),852 ));853 <PalletEvm<T>>::deposit_log(854 erc::CollectionHelpersEvents::CollectionCreated {855 owner: *owner.as_eth(),856 collection_id: eth::collection_id_to_address(id),857 }858 .to_log(T::ContractAddress::get()),859 );860 <CollectionById<T>>::insert(id, collection);861 Ok(id)862 }863864 pub fn destroy_collection(865 collection: CollectionHandle<T>,866 sender: &T::CrossAccountId,867 ) -> DispatchResult {868 ensure!(869 collection.limits.owner_can_destroy(),870 <Error<T>>::NoPermission,871 );872 collection.check_is_owner(sender)?;873874 let destroyed_collections = <DestroyedCollectionCount<T>>::get()875 .0876 .checked_add(1)877 .ok_or(ArithmeticError::Overflow)?;878879 880881 <DestroyedCollectionCount<T>>::put(destroyed_collections);882 <CollectionById<T>>::remove(collection.id);883 <AdminAmount<T>>::remove(collection.id);884 <IsAdmin<T>>::remove_prefix((collection.id,), None);885 <Allowlist<T>>::remove_prefix((collection.id,), None);886 <CollectionProperties<T>>::remove(collection.id);887888 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));889 Ok(())890 }891892 pub fn set_collection_property(893 collection: &CollectionHandle<T>,894 sender: &T::CrossAccountId,895 property: Property,896 ) -> DispatchResult {897 collection.check_is_owner_or_admin(sender)?;898899 CollectionProperties::<T>::try_mutate(collection.id, |properties| {900 let property = property.clone();901 properties.try_set(property.key, property.value)902 })903 .map_err(<Error<T>>::from)?;904905 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));906907 Ok(())908 }909910 pub fn set_scoped_collection_property(911 collection_id: CollectionId,912 scope: PropertyScope,913 property: Property,914 ) -> DispatchResult {915 CollectionProperties::<T>::try_mutate(collection_id, |properties| {916 properties.try_scoped_set(scope, property.key, property.value)917 })918 .map_err(<Error<T>>::from)?;919920 Ok(())921 }922923 pub fn set_scoped_collection_properties(924 collection_id: CollectionId,925 scope: PropertyScope,926 properties: impl Iterator<Item = Property>,927 ) -> DispatchResult {928 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {929 stored_properties.try_scoped_set_from_iter(scope, properties)930 })931 .map_err(<Error<T>>::from)?;932933 Ok(())934 }935936 #[transactional]937 pub fn set_collection_properties(938 collection: &CollectionHandle<T>,939 sender: &T::CrossAccountId,940 properties: Vec<Property>,941 ) -> DispatchResult {942 for property in properties {943 Self::set_collection_property(collection, sender, property)?;944 }945946 Ok(())947 }948949 pub fn delete_collection_property(950 collection: &CollectionHandle<T>,951 sender: &T::CrossAccountId,952 property_key: PropertyKey,953 ) -> DispatchResult {954 collection.check_is_owner_or_admin(sender)?;955956 CollectionProperties::<T>::try_mutate(collection.id, |properties| {957 properties.remove(&property_key)958 })959 .map_err(<Error<T>>::from)?;960961 Self::deposit_event(Event::CollectionPropertyDeleted(962 collection.id,963 property_key,964 ));965966 Ok(())967 }968969 #[transactional]970 pub fn delete_collection_properties(971 collection: &CollectionHandle<T>,972 sender: &T::CrossAccountId,973 property_keys: Vec<PropertyKey>,974 ) -> DispatchResult {975 for key in property_keys {976 Self::delete_collection_property(collection, sender, key)?;977 }978979 Ok(())980 }981982 983 pub fn set_property_permission_unchecked(984 collection: CollectionId,985 property_permission: PropertyKeyPermission,986 ) -> DispatchResult {987 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {988 permissions.try_set(property_permission.key, property_permission.permission)989 })990 .map_err(<Error<T>>::from)?;991 Ok(())992 }993994 pub fn set_property_permission(995 collection: &CollectionHandle<T>,996 sender: &T::CrossAccountId,997 property_permission: PropertyKeyPermission,998 ) -> DispatchResult {999 collection.check_is_owner_or_admin(sender)?;10001001 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1002 let current_permission = all_permissions.get(&property_permission.key);1003 if matches![1004 current_permission,1005 Some(PropertyPermission { mutable: false, .. })1006 ] {1007 return Err(<Error<T>>::NoPermission.into());1008 }10091010 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1011 let property_permission = property_permission.clone();1012 permissions.try_set(property_permission.key, property_permission.permission)1013 })1014 .map_err(<Error<T>>::from)?;10151016 Self::deposit_event(Event::PropertyPermissionSet(1017 collection.id,1018 property_permission.key,1019 ));10201021 Ok(())1022 }10231024 #[transactional]1025 pub fn set_property_permissions(1026 collection: &CollectionHandle<T>,1027 sender: &T::CrossAccountId,1028 property_permissions: Vec<PropertyKeyPermission>,1029 ) -> DispatchResult {1030 for prop_pemission in property_permissions {1031 Self::set_property_permission(collection, sender, prop_pemission)?;1032 }10331034 Ok(())1035 }10361037 pub fn get_collection_property(1038 collection_id: CollectionId,1039 key: &PropertyKey,1040 ) -> Option<PropertyValue> {1041 Self::collection_properties(collection_id).get(key).cloned()1042 }10431044 pub fn bytes_keys_to_property_keys(1045 keys: Vec<Vec<u8>>,1046 ) -> Result<Vec<PropertyKey>, DispatchError> {1047 keys.into_iter()1048 .map(|key| -> Result<PropertyKey, DispatchError> {1049 key.try_into()1050 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1051 })1052 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1053 }10541055 pub fn filter_collection_properties(1056 collection_id: CollectionId,1057 keys: Option<Vec<PropertyKey>>,1058 ) -> Result<Vec<Property>, DispatchError> {1059 let properties = Self::collection_properties(collection_id);10601061 let properties = keys1062 .map(|keys| {1063 keys.into_iter()1064 .filter_map(|key| {1065 properties.get(&key).map(|value| Property {1066 key,1067 value: value.clone(),1068 })1069 })1070 .collect()1071 })1072 .unwrap_or_else(|| {1073 properties1074 .into_iter()1075 .map(|(key, value)| Property { key, value })1076 .collect()1077 });10781079 Ok(properties)1080 }10811082 pub fn filter_property_permissions(1083 collection_id: CollectionId,1084 keys: Option<Vec<PropertyKey>>,1085 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1086 let permissions = Self::property_permissions(collection_id);10871088 let key_permissions = keys1089 .map(|keys| {1090 keys.into_iter()1091 .filter_map(|key| {1092 permissions1093 .get(&key)1094 .map(|permission| PropertyKeyPermission {1095 key,1096 permission: permission.clone(),1097 })1098 })1099 .collect()1100 })1101 .unwrap_or_else(|| {1102 permissions1103 .into_iter()1104 .map(|(key, permission)| PropertyKeyPermission { key, permission })1105 .collect()1106 });11071108 Ok(key_permissions)1109 }11101111 pub fn toggle_allowlist(1112 collection: &CollectionHandle<T>,1113 sender: &T::CrossAccountId,1114 user: &T::CrossAccountId,1115 allowed: bool,1116 ) -> DispatchResult {1117 collection.check_is_owner_or_admin(sender)?;11181119 11201121 if allowed {1122 <Allowlist<T>>::insert((collection.id, user), true);1123 } else {1124 <Allowlist<T>>::remove((collection.id, user));1125 }11261127 Ok(())1128 }11291130 pub fn toggle_admin(1131 collection: &CollectionHandle<T>,1132 sender: &T::CrossAccountId,1133 user: &T::CrossAccountId,1134 admin: bool,1135 ) -> DispatchResult {1136 collection.check_is_mutable()?;1137 collection.check_is_owner(sender)?;11381139 let was_admin = <IsAdmin<T>>::get((collection.id, user));1140 if was_admin == admin {1141 return Ok(());1142 }1143 let amount = <AdminAmount<T>>::get(collection.id);11441145 if admin {1146 let amount = amount1147 .checked_add(1)1148 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1149 ensure!(1150 amount <= Self::collection_admins_limit(),1151 <Error<T>>::CollectionAdminCountExceeded,1152 );11531154 11551156 <AdminAmount<T>>::insert(collection.id, amount);1157 <IsAdmin<T>>::insert((collection.id, user), true);1158 } else {1159 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1160 <IsAdmin<T>>::remove((collection.id, user));1161 }11621163 Ok(())1164 }11651166 pub fn clamp_limits(1167 mode: CollectionMode,1168 old_limit: &CollectionLimits,1169 mut new_limit: CollectionLimits,1170 ) -> Result<CollectionLimits, DispatchError> {1171 limit_default!(old_limit, new_limit,1172 account_token_ownership_limit => ensure!(1173 new_limit <= MAX_TOKEN_OWNERSHIP,1174 <Error<T>>::CollectionLimitBoundsExceeded,1175 ),1176 sponsored_data_size => ensure!(1177 new_limit <= CUSTOM_DATA_LIMIT,1178 <Error<T>>::CollectionLimitBoundsExceeded,1179 ),11801181 sponsored_data_rate_limit => {},1182 token_limit => ensure!(1183 old_limit >= new_limit && new_limit > 0,1184 <Error<T>>::CollectionTokenLimitExceeded1185 ),11861187 sponsor_transfer_timeout(match mode {1188 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1189 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1190 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1191 }) => ensure!(1192 new_limit <= MAX_SPONSOR_TIMEOUT,1193 <Error<T>>::CollectionLimitBoundsExceeded,1194 ),1195 sponsor_approve_timeout => {},1196 owner_can_transfer => ensure!(1197 old_limit || !new_limit,1198 <Error<T>>::OwnerPermissionsCantBeReverted,1199 ),1200 owner_can_destroy => ensure!(1201 old_limit || !new_limit,1202 <Error<T>>::OwnerPermissionsCantBeReverted,1203 ),1204 transfers_enabled => {},1205 );1206 Ok(new_limit)1207 }12081209 pub fn clamp_permissions(1210 _mode: CollectionMode,1211 old_limit: &CollectionPermissions,1212 mut new_limit: CollectionPermissions,1213 ) -> Result<CollectionPermissions, DispatchError> {1214 limit_default_clone!(old_limit, new_limit,1215 access => {},1216 mint_mode => {},1217 nesting => {},1218 );1219 Ok(new_limit)1220 }1221}12221223#[macro_export]1224macro_rules! unsupported {1225 () => {1226 Err(<Error<T>>::UnsupportedOperation.into())1227 };1228}122912301231pub trait CommonWeightInfo<CrossAccountId> {1232 fn create_item() -> Weight;1233 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1234 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1235 fn burn_item() -> Weight;1236 fn set_collection_properties(amount: u32) -> Weight;1237 fn delete_collection_properties(amount: u32) -> Weight;1238 fn set_token_properties(amount: u32) -> Weight;1239 fn delete_token_properties(amount: u32) -> Weight;1240 fn set_property_permissions(amount: u32) -> Weight;1241 fn transfer() -> Weight;1242 fn approve() -> Weight;1243 fn transfer_from() -> Weight;1244 fn burn_from() -> Weight;12451246 1247 1248 1249 1250 fn burn_recursively_self_raw() -> Weight;1251 1252 1253 1254 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12551256 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1257 Self::burn_recursively_self_raw()1258 .saturating_mul(max_selfs.max(1) as u64)1259 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1260 }1261}12621263pub trait CommonCollectionOperations<T: Config> {1264 fn create_item(1265 &self,1266 sender: T::CrossAccountId,1267 to: T::CrossAccountId,1268 data: CreateItemData,1269 nesting_budget: &dyn Budget,1270 ) -> DispatchResultWithPostInfo;1271 fn create_multiple_items(1272 &self,1273 sender: T::CrossAccountId,1274 to: T::CrossAccountId,1275 data: Vec<CreateItemData>,1276 nesting_budget: &dyn Budget,1277 ) -> DispatchResultWithPostInfo;1278 fn create_multiple_items_ex(1279 &self,1280 sender: T::CrossAccountId,1281 data: CreateItemExData<T::CrossAccountId>,1282 nesting_budget: &dyn Budget,1283 ) -> DispatchResultWithPostInfo;1284 fn burn_item(1285 &self,1286 sender: T::CrossAccountId,1287 token: TokenId,1288 amount: u128,1289 ) -> DispatchResultWithPostInfo;1290 fn burn_item_recursively(1291 &self,1292 sender: T::CrossAccountId,1293 token: TokenId,1294 self_budget: &dyn Budget,1295 breadth_budget: &dyn Budget,1296 ) -> DispatchResultWithPostInfo;1297 fn set_collection_properties(1298 &self,1299 sender: T::CrossAccountId,1300 properties: Vec<Property>,1301 ) -> DispatchResultWithPostInfo;1302 fn delete_collection_properties(1303 &self,1304 sender: &T::CrossAccountId,1305 property_keys: Vec<PropertyKey>,1306 ) -> DispatchResultWithPostInfo;1307 fn set_token_properties(1308 &self,1309 sender: T::CrossAccountId,1310 token_id: TokenId,1311 property: Vec<Property>,1312 ) -> DispatchResultWithPostInfo;1313 fn delete_token_properties(1314 &self,1315 sender: T::CrossAccountId,1316 token_id: TokenId,1317 property_keys: Vec<PropertyKey>,1318 ) -> DispatchResultWithPostInfo;1319 fn set_property_permissions(1320 &self,1321 sender: &T::CrossAccountId,1322 property_permissions: Vec<PropertyKeyPermission>,1323 ) -> DispatchResultWithPostInfo;1324 fn transfer(1325 &self,1326 sender: T::CrossAccountId,1327 to: T::CrossAccountId,1328 token: TokenId,1329 amount: u128,1330 nesting_budget: &dyn Budget,1331 ) -> DispatchResultWithPostInfo;1332 fn approve(1333 &self,1334 sender: T::CrossAccountId,1335 spender: T::CrossAccountId,1336 token: TokenId,1337 amount: u128,1338 ) -> DispatchResultWithPostInfo;1339 fn transfer_from(1340 &self,1341 sender: T::CrossAccountId,1342 from: T::CrossAccountId,1343 to: T::CrossAccountId,1344 token: TokenId,1345 amount: u128,1346 nesting_budget: &dyn Budget,1347 ) -> DispatchResultWithPostInfo;1348 fn burn_from(1349 &self,1350 sender: T::CrossAccountId,1351 from: T::CrossAccountId,1352 token: TokenId,1353 amount: u128,1354 nesting_budget: &dyn Budget,1355 ) -> DispatchResultWithPostInfo;13561357 fn check_nesting(1358 &self,1359 sender: T::CrossAccountId,1360 from: (CollectionId, TokenId),1361 under: TokenId,1362 budget: &dyn Budget,1363 ) -> DispatchResult;13641365 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13681369 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1370 fn collection_tokens(&self) -> Vec<TokenId>;1371 fn token_exists(&self, token: TokenId) -> bool;1372 fn last_token_id(&self) -> TokenId;13731374 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1375 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1376 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1377 1378 fn total_supply(&self) -> u32;1379 1380 fn account_balance(&self, account: T::CrossAccountId) -> u32;1381 1382 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1383 fn allowance(1384 &self,1385 sender: T::CrossAccountId,1386 spender: T::CrossAccountId,1387 token: TokenId,1388 ) -> u128;1389}139013911392pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1393 let post_info = PostDispatchInfo {1394 actual_weight: Some(weight),1395 pays_fee: Pays::Yes,1396 };1397 match res {1398 Ok(()) => Ok(post_info),1399 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1400 }1401}14021403impl<T: Config> From<PropertiesError> for Error<T> {1404 fn from(error: PropertiesError) -> Self {1405 match error {1406 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1407 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1408 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1409 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1410 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1411 }1412 }1413}