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 RmrkBoundedTheme,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 }151 pub fn save(self) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 171 172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 181 182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 288 289 290 291 292 293 294 295 296 CollectionCreated(CollectionId, u8, T::AccountId),297298 299 300 301 302 303 CollectionDestroyed(CollectionId),304305 306 307 308 309 310 311 312 313 314 315 316 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318 319 320 321 322 323 324 325 326 327 328 329 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331 332 333 334 335 336 337 338 339 340 341 342 343 344 Transfer(345 CollectionId,346 TokenId,347 T::CrossAccountId,348 T::CrossAccountId,349 u128,350 ),351352 353 354 355 356 357 358 359 360 361 362 363 364 365 Approved(366 CollectionId,367 TokenId,368 T::CrossAccountId,369 T::CrossAccountId,370 u128,371 ),372373 374 375 376 377 378 379 380 CollectionPropertySet(CollectionId, PropertyKey),381382 383 384 385 386 387 388 389 CollectionPropertyDeleted(CollectionId, PropertyKey),390391 392 393 394 395 396 397 398 399 400 TokenPropertySet(CollectionId, TokenId, PropertyKey),401402 403 404 405 406 407 408 409 410 411 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413 414 415 416 417 418 419 420 PropertyPermissionSet(CollectionId, PropertyKey),421 }422423 #[pallet::error]424 pub enum Error<T> {425 426 CollectionNotFound,427 428 MustBeTokenOwner,429 430 NoPermission,431 432 CantDestroyNotEmptyCollection,433 434 PublicMintingNotAllowed,435 436 AddressNotInAllowlist,437438 439 CollectionNameLimitExceeded,440 441 CollectionDescriptionLimitExceeded,442 443 CollectionTokenPrefixLimitExceeded,444 445 TotalCollectionsLimitExceeded,446 447 CollectionAdminCountExceeded,448 449 CollectionLimitBoundsExceeded,450 451 OwnerPermissionsCantBeReverted,452 453 TransferNotAllowed,454 455 AccountTokenLimitExceeded,456 457 CollectionTokenLimitExceeded,458 459 MetadataFlagFrozen,460461 462 TokenNotFound,463 464 TokenValueTooLow,465 466 ApprovedValueTooLow,467 468 CantApproveMoreThanOwned,469470 471 AddressIsZero,472 473 UnsupportedOperation,474475 476 NotSufficientFounds,477478 479 UserIsNotAllowedToNest,480 481 SourceCollectionIsNotAllowedToNest,482483 484 CollectionFieldSizeExceeded,485486 487 NoSpaceForProperty,488489 490 PropertyLimitReached,491492 493 PropertyKeyIsTooLong,494495 496 InvalidCharacterInPropertyKey,497498 499 EmptyPropertyKey,500501 502 CollectionIsExternal,503504 505 CollectionIsInternal,506 }507508 509 #[pallet::storage]510 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512 513 #[pallet::storage]514 pub type DestroyedCollectionCount<T> =515 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517 518 #[pallet::storage]519 pub type CollectionById<T> = StorageMap<520 Hasher = Blake2_128Concat,521 Key = CollectionId,522 Value = Collection<<T as frame_system::Config>::AccountId>,523 QueryKind = OptionQuery,524 >;525526 527 #[pallet::storage]528 #[pallet::getter(fn collection_properties)]529 pub type CollectionProperties<T> = StorageMap<530 Hasher = Blake2_128Concat,531 Key = CollectionId,532 Value = Properties,533 QueryKind = ValueQuery,534 OnEmpty = up_data_structs::CollectionProperties,535 >;536537 538 #[pallet::storage]539 #[pallet::getter(fn property_permissions)]540 pub type CollectionPropertyPermissions<T> = StorageMap<541 Hasher = Blake2_128Concat,542 Key = CollectionId,543 Value = PropertiesPermissionMap,544 QueryKind = ValueQuery,545 >;546547 548 #[pallet::storage]549 pub type AdminAmount<T> = StorageMap<550 Hasher = Blake2_128Concat,551 Key = CollectionId,552 Value = u32,553 QueryKind = ValueQuery,554 >;555556 557 #[pallet::storage]558 pub type IsAdmin<T: Config> = StorageNMap<559 Key = (560 Key<Blake2_128Concat, CollectionId>,561 Key<Blake2_128Concat, T::CrossAccountId>,562 ),563 Value = bool,564 QueryKind = ValueQuery,565 >;566567 568 #[pallet::storage]569 pub type Allowlist<T: Config> = StorageNMap<570 Key = (571 Key<Blake2_128Concat, CollectionId>,572 Key<Blake2_128Concat, T::CrossAccountId>,573 ),574 Value = bool,575 QueryKind = ValueQuery,576 >;577578 579 #[pallet::storage]580 pub type DummyStorageValue<T: Config> = StorageValue<581 Value = (582 CollectionStats,583 CollectionId,584 TokenId,585 TokenChild,586 PhantomType<(587 TokenData<T::CrossAccountId>,588 RpcCollection<T::AccountId>,589 590 RmrkCollectionInfo<T::AccountId>,591 RmrkInstanceInfo<T::AccountId>,592 RmrkResourceInfo,593 RmrkPropertyInfo,594 RmrkBaseInfo<T::AccountId>,595 RmrkPartType,596 RmrkBoundedTheme,597 RmrkNftChild,598 )>,599 ),600 QueryKind = OptionQuery,601 >;602603 #[pallet::hooks]604 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605 fn on_runtime_upgrade() -> Weight {606 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607 use up_data_structs::{CollectionVersion1, CollectionVersion2};608 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609 let mut props = Vec::new();610 if !v.offchain_schema.is_empty() {611 props.push(Property {612 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613 value: v614 .offchain_schema615 .clone()616 .into_inner()617 .try_into()618 .expect("offchain schema too big"),619 });620 }621 if !v.variable_on_chain_schema.is_empty() {622 props.push(Property {623 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624 value: v625 .variable_on_chain_schema626 .clone()627 .into_inner()628 .try_into()629 .expect("offchain schema too big"),630 });631 }632 if !v.const_on_chain_schema.is_empty() {633 props.push(Property {634 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635 value: v636 .const_on_chain_schema637 .clone()638 .into_inner()639 .try_into()640 .expect("offchain schema too big"),641 });642 }643 props.push(Property {644 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645 value: match v.schema_version {646 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647 SchemaVersion::Unique => b"Unique".as_slice(),648 }649 .to_vec()650 .try_into()651 .unwrap(),652 });653 Self::set_scoped_collection_properties(654 id,655 PropertyScope::None,656 props.into_iter(),657 )658 .expect("existing data larger than properties");659 let mut new = CollectionVersion2::from(v.clone());660 new.permissions.access = Some(v.access);661 new.permissions.mint_mode = Some(v.mint_mode);662 Some(new)663 });664 }665666 0667 }668 }669}670671impl<T: Config> Pallet<T> {672 673 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674 ensure!(675 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676 <Error<T>>::AddressIsZero677 );678 Ok(())679 }680 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681 <IsAdmin<T>>::iter_prefix((collection,))682 .map(|(a, _)| a)683 .collect()684 }685 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686 <Allowlist<T>>::iter_prefix((collection,))687 .map(|(a, _)| a)688 .collect()689 }690 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691 <Allowlist<T>>::get((collection, user))692 }693 pub fn collection_stats() -> CollectionStats {694 let created = <CreatedCollectionCount<T>>::get();695 let destroyed = <DestroyedCollectionCount<T>>::get();696 CollectionStats {697 created: created.0,698 destroyed: destroyed.0,699 alive: created.0 - destroyed.0,700 }701 }702703 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704 let collection = <CollectionById<T>>::get(collection);705 if collection.is_none() {706 return None;707 }708709 let collection = collection.unwrap();710 let limits = collection.limits;711 let effective_limits = CollectionLimits {712 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713 sponsored_data_size: Some(limits.sponsored_data_size()),714 sponsored_data_rate_limit: Some(715 limits716 .sponsored_data_rate_limit717 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),718 ),719 token_limit: Some(limits.token_limit()),720 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721 match collection.mode {722 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725 },726 )),727 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728 owner_can_transfer: Some(limits.owner_can_transfer()),729 owner_can_destroy: Some(limits.owner_can_destroy()),730 transfers_enabled: Some(limits.transfers_enabled()),731 };732733 Some(effective_limits)734 }735736 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737 let Collection {738 name,739 description,740 owner,741 mode,742 token_prefix,743 sponsorship,744 limits,745 permissions,746 external_collection,747 } = <CollectionById<T>>::get(collection)?;748749 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750 .into_iter()751 .map(|(key, permission)| PropertyKeyPermission { key, permission })752 .collect();753754 let properties = <CollectionProperties<T>>::get(collection)755 .into_iter()756 .map(|(key, value)| Property { key, value })757 .collect();758759 let permissions = CollectionPermissions {760 access: Some(permissions.access()),761 mint_mode: Some(permissions.mint_mode()),762 nesting: Some(permissions.nesting().clone()),763 };764765 Some(RpcCollection {766 name: name.into_inner(),767 description: description.into_inner(),768 owner,769 mode,770 token_prefix: token_prefix.into_inner(),771 sponsorship,772 limits,773 permissions,774 token_property_permissions,775 properties,776 read_only: external_collection,777 })778 }779}780781macro_rules! limit_default {782 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783 $(784 if let Some($new) = $new.$field {785 let $old = $old.$field($($arg)?);786 let _ = $new;787 let _ = $old;788 $check789 } else {790 $new.$field = $old.$field791 }792 )*793 }};794}795macro_rules! limit_default_clone {796 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797 $(798 if let Some($new) = $new.$field.clone() {799 let $old = $old.$field($($arg)?);800 let _ = $new;801 let _ = $old;802 $check803 } else {804 $new.$field = $old.$field.clone()805 }806 )*807 }};808}809810impl<T: Config> Pallet<T> {811 pub fn init_collection(812 owner: T::CrossAccountId,813 data: CreateCollectionData<T::AccountId>,814 is_external: bool,815 ) -> Result<CollectionId, DispatchError> {816 {817 ensure!(818 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819 Error::<T>::CollectionTokenPrefixLimitExceeded820 );821 }822823 let created_count = <CreatedCollectionCount<T>>::get()824 .0825 .checked_add(1)826 .ok_or(ArithmeticError::Overflow)?;827 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828 let id = CollectionId(created_count);829830 831 ensure!(832 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833 <Error<T>>::TotalCollectionsLimitExceeded834 );835836 837838 let collection = Collection {839 owner: owner.as_sub().clone(),840 name: data.name,841 mode: data.mode.clone(),842 description: data.description,843 token_prefix: data.token_prefix,844 sponsorship: data845 .pending_sponsor846 .map(SponsorshipState::Unconfirmed)847 .unwrap_or_default(),848 limits: data849 .limits850 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,852 permissions: data853 .permissions854 .map(|permissions| {855 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856 })857 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858 external_collection: is_external,859 };860861 let mut collection_properties = up_data_structs::CollectionProperties::get();862 collection_properties863 .try_set_from_iter(data.properties.into_iter())864 .map_err(<Error<T>>::from)?;865866 CollectionProperties::<T>::insert(id, collection_properties);867868 let mut token_props_permissions = PropertiesPermissionMap::new();869 token_props_permissions870 .try_set_from_iter(data.token_property_permissions.into_iter())871 .map_err(<Error<T>>::from)?;872873 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875 876 {877 let mut imbalance =878 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879 imbalance.subsume(880 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881 &T::TreasuryAccountId::get(),882 T::CollectionCreationPrice::get(),883 ),884 );885 <T as Config>::Currency::settle(886 &owner.as_sub(),887 imbalance,888 WithdrawReasons::TRANSFER,889 ExistenceRequirement::KeepAlive,890 )891 .map_err(|_| Error::<T>::NotSufficientFounds)?;892 }893894 <CreatedCollectionCount<T>>::put(created_count);895 <Pallet<T>>::deposit_event(Event::CollectionCreated(896 id,897 data.mode.id(),898 owner.as_sub().clone(),899 ));900 <PalletEvm<T>>::deposit_log(901 erc::CollectionHelpersEvents::CollectionCreated {902 owner: *owner.as_eth(),903 collection_id: eth::collection_id_to_address(id),904 }905 .to_log(T::ContractAddress::get()),906 );907 <CollectionById<T>>::insert(id, collection);908 Ok(id)909 }910911 pub fn destroy_collection(912 collection: CollectionHandle<T>,913 sender: &T::CrossAccountId,914 ) -> DispatchResult {915 ensure!(916 collection.limits.owner_can_destroy(),917 <Error<T>>::NoPermission,918 );919 collection.check_is_owner(sender)?;920921 let destroyed_collections = <DestroyedCollectionCount<T>>::get()922 .0923 .checked_add(1)924 .ok_or(ArithmeticError::Overflow)?;925926 927928 <DestroyedCollectionCount<T>>::put(destroyed_collections);929 <CollectionById<T>>::remove(collection.id);930 <AdminAmount<T>>::remove(collection.id);931 <IsAdmin<T>>::remove_prefix((collection.id,), None);932 <Allowlist<T>>::remove_prefix((collection.id,), None);933 <CollectionProperties<T>>::remove(collection.id);934935 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936 Ok(())937 }938939 pub fn set_collection_property(940 collection: &CollectionHandle<T>,941 sender: &T::CrossAccountId,942 property: Property,943 ) -> DispatchResult {944 collection.check_is_owner_or_admin(sender)?;945946 CollectionProperties::<T>::try_mutate(collection.id, |properties| {947 let property = property.clone();948 properties.try_set(property.key, property.value)949 })950 .map_err(<Error<T>>::from)?;951952 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954 Ok(())955 }956957 pub fn set_scoped_collection_property(958 collection_id: CollectionId,959 scope: PropertyScope,960 property: Property,961 ) -> DispatchResult {962 CollectionProperties::<T>::try_mutate(collection_id, |properties| {963 properties.try_scoped_set(scope, property.key, property.value)964 })965 .map_err(<Error<T>>::from)?;966967 Ok(())968 }969970 pub fn set_scoped_collection_properties(971 collection_id: CollectionId,972 scope: PropertyScope,973 properties: impl Iterator<Item = Property>,974 ) -> DispatchResult {975 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976 stored_properties.try_scoped_set_from_iter(scope, properties)977 })978 .map_err(<Error<T>>::from)?;979980 Ok(())981 }982983 #[transactional]984 pub fn set_collection_properties(985 collection: &CollectionHandle<T>,986 sender: &T::CrossAccountId,987 properties: Vec<Property>,988 ) -> DispatchResult {989 for property in properties {990 Self::set_collection_property(collection, sender, property)?;991 }992993 Ok(())994 }995996 pub fn delete_collection_property(997 collection: &CollectionHandle<T>,998 sender: &T::CrossAccountId,999 property_key: PropertyKey,1000 ) -> DispatchResult {1001 collection.check_is_owner_or_admin(sender)?;10021003 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004 properties.remove(&property_key)1005 })1006 .map_err(<Error<T>>::from)?;10071008 Self::deposit_event(Event::CollectionPropertyDeleted(1009 collection.id,1010 property_key,1011 ));10121013 Ok(())1014 }10151016 #[transactional]1017 pub fn delete_collection_properties(1018 collection: &CollectionHandle<T>,1019 sender: &T::CrossAccountId,1020 property_keys: Vec<PropertyKey>,1021 ) -> DispatchResult {1022 for key in property_keys {1023 Self::delete_collection_property(collection, sender, key)?;1024 }10251026 Ok(())1027 }10281029 1030 pub fn set_property_permission_unchecked(1031 collection: CollectionId,1032 property_permission: PropertyKeyPermission,1033 ) -> DispatchResult {1034 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035 permissions.try_set(property_permission.key, property_permission.permission)1036 })1037 .map_err(<Error<T>>::from)?;1038 Ok(())1039 }10401041 pub fn set_property_permission(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 property_permission: PropertyKeyPermission,1045 ) -> DispatchResult {1046 collection.check_is_owner_or_admin(sender)?;10471048 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049 let current_permission = all_permissions.get(&property_permission.key);1050 if matches![1051 current_permission,1052 Some(PropertyPermission { mutable: false, .. })1053 ] {1054 return Err(<Error<T>>::NoPermission.into());1055 }10561057 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058 let property_permission = property_permission.clone();1059 permissions.try_set(property_permission.key, property_permission.permission)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Self::deposit_event(Event::PropertyPermissionSet(1064 collection.id,1065 property_permission.key,1066 ));10671068 Ok(())1069 }10701071 #[transactional]1072 pub fn set_token_property_permissions(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 property_permissions: Vec<PropertyKeyPermission>,1076 ) -> DispatchResult {1077 for prop_pemission in property_permissions {1078 Self::set_property_permission(collection, sender, prop_pemission)?;1079 }10801081 Ok(())1082 }10831084 pub fn get_collection_property(1085 collection_id: CollectionId,1086 key: &PropertyKey,1087 ) -> Option<PropertyValue> {1088 Self::collection_properties(collection_id).get(key).cloned()1089 }10901091 pub fn bytes_keys_to_property_keys(1092 keys: Vec<Vec<u8>>,1093 ) -> Result<Vec<PropertyKey>, DispatchError> {1094 keys.into_iter()1095 .map(|key| -> Result<PropertyKey, DispatchError> {1096 key.try_into()1097 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098 })1099 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1100 }11011102 pub fn filter_collection_properties(1103 collection_id: CollectionId,1104 keys: Option<Vec<PropertyKey>>,1105 ) -> Result<Vec<Property>, DispatchError> {1106 let properties = Self::collection_properties(collection_id);11071108 let properties = keys1109 .map(|keys| {1110 keys.into_iter()1111 .filter_map(|key| {1112 properties.get(&key).map(|value| Property {1113 key,1114 value: value.clone(),1115 })1116 })1117 .collect()1118 })1119 .unwrap_or_else(|| {1120 properties1121 .into_iter()1122 .map(|(key, value)| Property { key, value })1123 .collect()1124 });11251126 Ok(properties)1127 }11281129 pub fn filter_property_permissions(1130 collection_id: CollectionId,1131 keys: Option<Vec<PropertyKey>>,1132 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133 let permissions = Self::property_permissions(collection_id);11341135 let key_permissions = keys1136 .map(|keys| {1137 keys.into_iter()1138 .filter_map(|key| {1139 permissions1140 .get(&key)1141 .map(|permission| PropertyKeyPermission {1142 key,1143 permission: permission.clone(),1144 })1145 })1146 .collect()1147 })1148 .unwrap_or_else(|| {1149 permissions1150 .into_iter()1151 .map(|(key, permission)| PropertyKeyPermission { key, permission })1152 .collect()1153 });11541155 Ok(key_permissions)1156 }11571158 pub fn toggle_allowlist(1159 collection: &CollectionHandle<T>,1160 sender: &T::CrossAccountId,1161 user: &T::CrossAccountId,1162 allowed: bool,1163 ) -> DispatchResult {1164 collection.check_is_owner_or_admin(sender)?;11651166 11671168 if allowed {1169 <Allowlist<T>>::insert((collection.id, user), true);1170 } else {1171 <Allowlist<T>>::remove((collection.id, user));1172 }11731174 Ok(())1175 }11761177 pub fn toggle_admin(1178 collection: &CollectionHandle<T>,1179 sender: &T::CrossAccountId,1180 user: &T::CrossAccountId,1181 admin: bool,1182 ) -> DispatchResult {1183 collection.check_is_owner(sender)?;11841185 let was_admin = <IsAdmin<T>>::get((collection.id, user));1186 if was_admin == admin {1187 return Ok(());1188 }1189 let amount = <AdminAmount<T>>::get(collection.id);11901191 if admin {1192 let amount = amount1193 .checked_add(1)1194 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195 ensure!(1196 amount <= Self::collection_admins_limit(),1197 <Error<T>>::CollectionAdminCountExceeded,1198 );11991200 12011202 <AdminAmount<T>>::insert(collection.id, amount);1203 <IsAdmin<T>>::insert((collection.id, user), true);1204 } else {1205 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206 <IsAdmin<T>>::remove((collection.id, user));1207 }12081209 Ok(())1210 }12111212 pub fn clamp_limits(1213 mode: CollectionMode,1214 old_limit: &CollectionLimits,1215 mut new_limit: CollectionLimits,1216 ) -> Result<CollectionLimits, DispatchError> {1217 let limits = old_limit;1218 limit_default!(old_limit, new_limit,1219 account_token_ownership_limit => ensure!(1220 new_limit <= MAX_TOKEN_OWNERSHIP,1221 <Error<T>>::CollectionLimitBoundsExceeded,1222 ),1223 sponsored_data_size => ensure!(1224 new_limit <= CUSTOM_DATA_LIMIT,1225 <Error<T>>::CollectionLimitBoundsExceeded,1226 ),12271228 sponsored_data_rate_limit => {},1229 token_limit => ensure!(1230 old_limit >= new_limit && new_limit > 0,1231 <Error<T>>::CollectionTokenLimitExceeded1232 ),12331234 sponsor_transfer_timeout(match mode {1235 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238 }) => ensure!(1239 new_limit <= MAX_SPONSOR_TIMEOUT,1240 <Error<T>>::CollectionLimitBoundsExceeded,1241 ),1242 sponsor_approve_timeout => {},1243 owner_can_transfer => ensure!(1244 !limits.owner_can_transfer_instaled() ||1245 old_limit || !new_limit,1246 <Error<T>>::OwnerPermissionsCantBeReverted,1247 ),1248 owner_can_destroy => ensure!(1249 old_limit || !new_limit,1250 <Error<T>>::OwnerPermissionsCantBeReverted,1251 ),1252 transfers_enabled => {},1253 );1254 Ok(new_limit)1255 }12561257 pub fn clamp_permissions(1258 _mode: CollectionMode,1259 old_limit: &CollectionPermissions,1260 mut new_limit: CollectionPermissions,1261 ) -> Result<CollectionPermissions, DispatchError> {1262 limit_default_clone!(old_limit, new_limit,1263 access => {},1264 mint_mode => {},1265 nesting => { },1266 );1267 Ok(new_limit)1268 }1269}12701271#[macro_export]1272macro_rules! unsupported {1273 () => {1274 Err(<Error<T>>::UnsupportedOperation.into())1275 };1276}127712781279pub trait CommonWeightInfo<CrossAccountId> {1280 fn create_item() -> Weight;1281 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283 fn burn_item() -> Weight;1284 fn set_collection_properties(amount: u32) -> Weight;1285 fn delete_collection_properties(amount: u32) -> Weight;1286 fn set_token_properties(amount: u32) -> Weight;1287 fn delete_token_properties(amount: u32) -> Weight;1288 fn set_token_property_permissions(amount: u32) -> Weight;1289 fn transfer() -> Weight;1290 fn approve() -> Weight;1291 fn transfer_from() -> Weight;1292 fn burn_from() -> Weight;12931294 1295 1296 1297 1298 fn burn_recursively_self_raw() -> Weight;1299 1300 1301 1302 fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305 Self::burn_recursively_self_raw()1306 .saturating_mul(max_selfs.max(1) as u64)1307 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308 }1309}13101311pub trait RefungibleExtensionsWeightInfo {1312 fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316 fn create_item(1317 &self,1318 sender: T::CrossAccountId,1319 to: T::CrossAccountId,1320 data: CreateItemData,1321 nesting_budget: &dyn Budget,1322 ) -> DispatchResultWithPostInfo;1323 fn create_multiple_items(1324 &self,1325 sender: T::CrossAccountId,1326 to: T::CrossAccountId,1327 data: Vec<CreateItemData>,1328 nesting_budget: &dyn Budget,1329 ) -> DispatchResultWithPostInfo;1330 fn create_multiple_items_ex(1331 &self,1332 sender: T::CrossAccountId,1333 data: CreateItemExData<T::CrossAccountId>,1334 nesting_budget: &dyn Budget,1335 ) -> DispatchResultWithPostInfo;1336 fn burn_item(1337 &self,1338 sender: T::CrossAccountId,1339 token: TokenId,1340 amount: u128,1341 ) -> DispatchResultWithPostInfo;1342 fn burn_item_recursively(1343 &self,1344 sender: T::CrossAccountId,1345 token: TokenId,1346 self_budget: &dyn Budget,1347 breadth_budget: &dyn Budget,1348 ) -> DispatchResultWithPostInfo;1349 fn set_collection_properties(1350 &self,1351 sender: T::CrossAccountId,1352 properties: Vec<Property>,1353 ) -> DispatchResultWithPostInfo;1354 fn delete_collection_properties(1355 &self,1356 sender: &T::CrossAccountId,1357 property_keys: Vec<PropertyKey>,1358 ) -> DispatchResultWithPostInfo;1359 fn set_token_properties(1360 &self,1361 sender: T::CrossAccountId,1362 token_id: TokenId,1363 property: Vec<Property>,1364 nesting_budget: &dyn Budget,1365 ) -> DispatchResultWithPostInfo;1366 fn delete_token_properties(1367 &self,1368 sender: T::CrossAccountId,1369 token_id: TokenId,1370 property_keys: Vec<PropertyKey>,1371 nesting_budget: &dyn Budget,1372 ) -> DispatchResultWithPostInfo;1373 fn set_token_property_permissions(1374 &self,1375 sender: &T::CrossAccountId,1376 property_permissions: Vec<PropertyKeyPermission>,1377 ) -> DispatchResultWithPostInfo;1378 fn transfer(1379 &self,1380 sender: T::CrossAccountId,1381 to: T::CrossAccountId,1382 token: TokenId,1383 amount: u128,1384 nesting_budget: &dyn Budget,1385 ) -> DispatchResultWithPostInfo;1386 fn approve(1387 &self,1388 sender: T::CrossAccountId,1389 spender: T::CrossAccountId,1390 token: TokenId,1391 amount: u128,1392 ) -> DispatchResultWithPostInfo;1393 fn transfer_from(1394 &self,1395 sender: T::CrossAccountId,1396 from: T::CrossAccountId,1397 to: T::CrossAccountId,1398 token: TokenId,1399 amount: u128,1400 nesting_budget: &dyn Budget,1401 ) -> DispatchResultWithPostInfo;1402 fn burn_from(1403 &self,1404 sender: T::CrossAccountId,1405 from: T::CrossAccountId,1406 token: TokenId,1407 amount: u128,1408 nesting_budget: &dyn Budget,1409 ) -> DispatchResultWithPostInfo;14101411 fn check_nesting(1412 &self,1413 sender: T::CrossAccountId,1414 from: (CollectionId, TokenId),1415 under: TokenId,1416 nesting_budget: &dyn Budget,1417 ) -> DispatchResult;14181419 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424 fn collection_tokens(&self) -> Vec<TokenId>;1425 fn token_exists(&self, token: TokenId) -> bool;1426 fn last_token_id(&self) -> TokenId;14271428 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431 1432 fn total_supply(&self) -> u32;1433 1434 fn account_balance(&self, account: T::CrossAccountId) -> u32;1435 1436 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437 1438 fn total_pieces(&self, token: TokenId) -> Option<u128>;1439 fn allowance(1440 &self,1441 sender: T::CrossAccountId,1442 spender: T::CrossAccountId,1443 token: TokenId,1444 ) -> u128;1445 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450 T: Config,1451{1452 fn repartition(1453 &self,1454 owner: &T::CrossAccountId,1455 token: TokenId,1456 amount: u128,1457 ) -> DispatchResultWithPostInfo;1458}145914601461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462 let post_info = PostDispatchInfo {1463 actual_weight: Some(weight),1464 pays_fee: Pays::Yes,1465 };1466 match res {1467 Ok(()) => Ok(post_info),1468 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469 }1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473 fn from(error: PropertiesError) -> Self {1474 match error {1475 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480 }1481 }1482}