12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132133134135136137138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 176 177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 228 229 230 231 232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 238 239 240 241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 257 258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 267 268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 329 330 331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use pallet_evm::account;346 use dispatch::CollectionDispatch;347 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};348 use frame_system::pallet_prelude::*;349 use frame_support::traits::Currency;350 use up_data_structs::{TokenId, mapping::TokenAddressMapping};351 use scale_info::TypeInfo;352 use weights::WeightInfo;353354 #[pallet::config]355 pub trait Config:356 frame_system::Config357 + pallet_evm_coder_substrate::Config358 + pallet_evm::Config359 + TypeInfo360 + account::Config361 {362 363 type WeightInfo: WeightInfo;364365 366 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;367368 369 type Currency: Currency<Self::AccountId>;370371 372 #[pallet::constant]373 type CollectionCreationPrice: Get<374 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,375 >;376377 378 type CollectionDispatch: CollectionDispatch<Self>;379380 381 type TreasuryAccountId: Get<Self::AccountId>;382383 384 type ContractAddress: Get<H160>;385386 387 type EvmTokenAddressMapping: TokenAddressMapping<H160>;388389 390 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;391 }392393 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);394395 #[pallet::pallet]396 #[pallet::storage_version(STORAGE_VERSION)]397 #[pallet::generate_store(pub(super) trait Store)]398 pub struct Pallet<T>(_);399400 #[pallet::extra_constants]401 impl<T: Config> Pallet<T> {402 403 pub fn collection_admins_limit() -> u32 {404 COLLECTION_ADMINS_LIMIT405 }406 }407408 #[pallet::event]409 #[pallet::generate_deposit(pub fn deposit_event)]410 pub enum Event<T: Config> {411 412 CollectionCreated(413 414 CollectionId,415 416 u8,417 418 T::AccountId,419 ),420421 422 CollectionDestroyed(423 424 CollectionId,425 ),426427 428 ItemCreated(429 430 CollectionId,431 432 TokenId,433 434 T::CrossAccountId,435 436 u128,437 ),438439 440 ItemDestroyed(441 442 CollectionId,443 444 TokenId,445 446 T::CrossAccountId,447 448 u128,449 ),450451 452 Transfer(453 454 CollectionId,455 456 TokenId,457 458 T::CrossAccountId,459 460 T::CrossAccountId,461 462 u128,463 ),464465 466 Approved(467 468 CollectionId,469 470 TokenId,471 472 T::CrossAccountId,473 474 T::CrossAccountId,475 476 u128,477 ),478479 480 CollectionPropertySet(481 482 CollectionId,483 484 PropertyKey,485 ),486487 488 CollectionPropertyDeleted(489 490 CollectionId,491 492 PropertyKey,493 ),494495 496 TokenPropertySet(497 498 CollectionId,499 500 TokenId,501 502 PropertyKey,503 ),504505 506 TokenPropertyDeleted(507 508 CollectionId,509 510 TokenId,511 512 PropertyKey,513 ),514515 516 PropertyPermissionSet(517 518 CollectionId,519 520 PropertyKey,521 ),522 }523524 #[pallet::error]525 pub enum Error<T> {526 527 CollectionNotFound,528 529 MustBeTokenOwner,530 531 NoPermission,532 533 CantDestroyNotEmptyCollection,534 535 PublicMintingNotAllowed,536 537 AddressNotInAllowlist,538539 540 CollectionNameLimitExceeded,541 542 CollectionDescriptionLimitExceeded,543 544 CollectionTokenPrefixLimitExceeded,545 546 TotalCollectionsLimitExceeded,547 548 CollectionAdminCountExceeded,549 550 CollectionLimitBoundsExceeded,551 552 OwnerPermissionsCantBeReverted,553 554 TransferNotAllowed,555 556 AccountTokenLimitExceeded,557 558 CollectionTokenLimitExceeded,559 560 MetadataFlagFrozen,561562 563 TokenNotFound,564 565 TokenValueTooLow,566 567 ApprovedValueTooLow,568 569 CantApproveMoreThanOwned,570571 572 AddressIsZero,573574 575 UnsupportedOperation,576577 578 NotSufficientFounds,579580 581 UserIsNotAllowedToNest,582 583 SourceCollectionIsNotAllowedToNest,584585 586 CollectionFieldSizeExceeded,587588 589 NoSpaceForProperty,590591 592 PropertyLimitReached,593594 595 PropertyKeyIsTooLong,596597 598 InvalidCharacterInPropertyKey,599600 601 EmptyPropertyKey,602603 604 CollectionIsExternal,605606 607 CollectionIsInternal,608 }609610 611 #[pallet::storage]612 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;613614 615 #[pallet::storage]616 pub type DestroyedCollectionCount<T> =617 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;618619 620 #[pallet::storage]621 pub type CollectionById<T> = StorageMap<622 Hasher = Blake2_128Concat,623 Key = CollectionId,624 Value = Collection<<T as frame_system::Config>::AccountId>,625 QueryKind = OptionQuery,626 >;627628 629 #[pallet::storage]630 #[pallet::getter(fn collection_properties)]631 pub type CollectionProperties<T> = StorageMap<632 Hasher = Blake2_128Concat,633 Key = CollectionId,634 Value = Properties,635 QueryKind = ValueQuery,636 OnEmpty = up_data_structs::CollectionProperties,637 >;638639 640 #[pallet::storage]641 #[pallet::getter(fn property_permissions)]642 pub type CollectionPropertyPermissions<T> = StorageMap<643 Hasher = Blake2_128Concat,644 Key = CollectionId,645 Value = PropertiesPermissionMap,646 QueryKind = ValueQuery,647 >;648649 650 #[pallet::storage]651 pub type AdminAmount<T> = StorageMap<652 Hasher = Blake2_128Concat,653 Key = CollectionId,654 Value = u32,655 QueryKind = ValueQuery,656 >;657658 659 #[pallet::storage]660 pub type IsAdmin<T: Config> = StorageNMap<661 Key = (662 Key<Blake2_128Concat, CollectionId>,663 Key<Blake2_128Concat, T::CrossAccountId>,664 ),665 Value = bool,666 QueryKind = ValueQuery,667 >;668669 670 #[pallet::storage]671 pub type Allowlist<T: Config> = StorageNMap<672 Key = (673 Key<Blake2_128Concat, CollectionId>,674 Key<Blake2_128Concat, T::CrossAccountId>,675 ),676 Value = bool,677 QueryKind = ValueQuery,678 >;679680 681 #[pallet::storage]682 pub type DummyStorageValue<T: Config> = StorageValue<683 Value = (684 CollectionStats,685 CollectionId,686 TokenId,687 TokenChild,688 PhantomType<(689 TokenData<T::CrossAccountId>,690 RpcCollection<T::AccountId>,691 692 RmrkCollectionInfo<T::AccountId>,693 RmrkInstanceInfo<T::AccountId>,694 RmrkResourceInfo,695 RmrkPropertyInfo,696 RmrkBaseInfo<T::AccountId>,697 RmrkPartType,698 RmrkBoundedTheme,699 RmrkNftChild,700 )>,701 ),702 QueryKind = OptionQuery,703 >;704705 #[pallet::hooks]706 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {707 fn on_runtime_upgrade() -> Weight {708 StorageVersion::new(1).put::<Pallet<T>>();709710 Weight::zero()711 }712 }713}714715impl<T: Config> Pallet<T> {716 717 718 719 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {720 ensure!(721 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,722 <Error<T>>::AddressIsZero723 );724 Ok(())725 }726727 728 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {729 <IsAdmin<T>>::iter_prefix((collection,))730 .map(|(a, _)| a)731 .collect()732 }733734 735 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {736 <Allowlist<T>>::iter_prefix((collection,))737 .map(|(a, _)| a)738 .collect()739 }740741 742 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {743 <Allowlist<T>>::get((collection, user))744 }745746 747 pub fn collection_stats() -> CollectionStats {748 let created = <CreatedCollectionCount<T>>::get();749 let destroyed = <DestroyedCollectionCount<T>>::get();750 CollectionStats {751 created: created.0,752 destroyed: destroyed.0,753 alive: created.0 - destroyed.0,754 }755 }756757 758 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {759 let collection = <CollectionById<T>>::get(collection)?;760 let limits = collection.limits;761 let effective_limits = CollectionLimits {762 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),763 sponsored_data_size: Some(limits.sponsored_data_size()),764 sponsored_data_rate_limit: Some(765 limits766 .sponsored_data_rate_limit767 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),768 ),769 token_limit: Some(limits.token_limit()),770 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(771 match collection.mode {772 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,773 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,774 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,775 },776 )),777 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),778 owner_can_transfer: Some(limits.owner_can_transfer()),779 owner_can_destroy: Some(limits.owner_can_destroy()),780 transfers_enabled: Some(limits.transfers_enabled()),781 };782783 Some(effective_limits)784 }785786 787 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {788 let Collection {789 name,790 description,791 owner,792 mode,793 token_prefix,794 sponsorship,795 limits,796 permissions,797 flags,798 } = <CollectionById<T>>::get(collection)?;799800 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)801 .into_iter()802 .map(|(key, permission)| PropertyKeyPermission { key, permission })803 .collect();804805 let properties = <CollectionProperties<T>>::get(collection)806 .into_iter()807 .map(|(key, value)| Property { key, value })808 .collect();809810 let permissions = CollectionPermissions {811 access: Some(permissions.access()),812 mint_mode: Some(permissions.mint_mode()),813 nesting: Some(permissions.nesting().clone()),814 };815816 Some(RpcCollection {817 name: name.into_inner(),818 description: description.into_inner(),819 owner,820 mode,821 token_prefix: token_prefix.into_inner(),822 sponsorship,823 limits,824 permissions,825 token_property_permissions,826 properties,827 read_only: flags.external,828829 flags: RpcCollectionFlags {830 foreign: flags.foreign,831 erc721metadata: flags.erc721metadata,832 },833 })834 }835}836837macro_rules! limit_default {838 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{839 $(840 if let Some($new) = $new.$field {841 let $old = $old.$field($($arg)?);842 let _ = $new;843 let _ = $old;844 $check845 } else {846 $new.$field = $old.$field847 }848 )*849 }};850}851macro_rules! limit_default_clone {852 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{853 $(854 if let Some($new) = $new.$field.clone() {855 let $old = $old.$field($($arg)?);856 let _ = $new;857 let _ = $old;858 $check859 } else {860 $new.$field = $old.$field.clone()861 }862 )*863 }};864}865866impl<T: Config> Pallet<T> {867 868 869 870 871 872 pub fn init_collection(873 owner: T::CrossAccountId,874 payer: T::CrossAccountId,875 data: CreateCollectionData<T::AccountId>,876 flags: CollectionFlags,877 ) -> Result<CollectionId, DispatchError> {878 {879 ensure!(880 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,881 Error::<T>::CollectionTokenPrefixLimitExceeded882 );883 }884885 let created_count = <CreatedCollectionCount<T>>::get()886 .0887 .checked_add(1)888 .ok_or(ArithmeticError::Overflow)?;889 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;890 let id = CollectionId(created_count);891892 893 ensure!(894 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,895 <Error<T>>::TotalCollectionsLimitExceeded896 );897898 899900 let collection = Collection {901 owner: owner.as_sub().clone(),902 name: data.name,903 mode: data.mode.clone(),904 description: data.description,905 token_prefix: data.token_prefix,906 sponsorship: data907 .pending_sponsor908 .map(SponsorshipState::Unconfirmed)909 .unwrap_or_default(),910 limits: data911 .limits912 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))913 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,914 permissions: data915 .permissions916 .map(|permissions| {917 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)918 })919 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,920 flags,921 };922923 let mut collection_properties = up_data_structs::CollectionProperties::get();924 collection_properties925 .try_set_from_iter(data.properties.into_iter())926 .map_err(<Error<T>>::from)?;927928 CollectionProperties::<T>::insert(id, collection_properties);929930 let mut token_props_permissions = PropertiesPermissionMap::new();931 token_props_permissions932 .try_set_from_iter(data.token_property_permissions.into_iter())933 .map_err(<Error<T>>::from)?;934935 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);936937 938 {939 let mut imbalance =940 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();941 imbalance.subsume(942 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(943 &T::TreasuryAccountId::get(),944 T::CollectionCreationPrice::get(),945 ),946 );947 <T as Config>::Currency::settle(948 payer.as_sub(),949 imbalance,950 WithdrawReasons::TRANSFER,951 ExistenceRequirement::KeepAlive,952 )953 .map_err(|_| Error::<T>::NotSufficientFounds)?;954 }955956 <CreatedCollectionCount<T>>::put(created_count);957 <Pallet<T>>::deposit_event(Event::CollectionCreated(958 id,959 data.mode.id(),960 owner.as_sub().clone(),961 ));962 <PalletEvm<T>>::deposit_log(963 erc::CollectionHelpersEvents::CollectionCreated {964 owner: *owner.as_eth(),965 collection_id: eth::collection_id_to_address(id),966 }967 .to_log(T::ContractAddress::get()),968 );969 <CollectionById<T>>::insert(id, collection);970 Ok(id)971 }972973 974 975 976 977 pub fn destroy_collection(978 collection: CollectionHandle<T>,979 sender: &T::CrossAccountId,980 ) -> DispatchResult {981 ensure!(982 collection.limits.owner_can_destroy(),983 <Error<T>>::NoPermission,984 );985 collection.check_is_owner(sender)?;986987 let destroyed_collections = <DestroyedCollectionCount<T>>::get()988 .0989 .checked_add(1)990 .ok_or(ArithmeticError::Overflow)?;991992 993994 <DestroyedCollectionCount<T>>::put(destroyed_collections);995 <CollectionById<T>>::remove(collection.id);996 <AdminAmount<T>>::remove(collection.id);997 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);998 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);999 <CollectionProperties<T>>::remove(collection.id);10001001 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));10021003 <PalletEvm<T>>::deposit_log(1004 erc::CollectionHelpersEvents::CollectionDestroyed {1005 collection_id: eth::collection_id_to_address(collection.id),1006 }1007 .to_log(T::ContractAddress::get()),1008 );1009 Ok(())1010 }10111012 1013 1014 1015 1016 1017 pub fn set_collection_property(1018 collection: &CollectionHandle<T>,1019 sender: &T::CrossAccountId,1020 property: Property,1021 ) -> DispatchResult {1022 collection.check_is_owner_or_admin(sender)?;10231024 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1025 let property = property.clone();1026 properties.try_set(property.key, property.value)1027 })1028 .map_err(<Error<T>>::from)?;10291030 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10311032 Ok(())1033 }10341035 1036 1037 1038 1039 1040 pub fn set_scoped_collection_property(1041 collection_id: CollectionId,1042 scope: PropertyScope,1043 property: Property,1044 ) -> DispatchResult {1045 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1046 properties.try_scoped_set(scope, property.key, property.value)1047 })1048 .map_err(<Error<T>>::from)?;10491050 Ok(())1051 }10521053 1054 1055 1056 1057 1058 pub fn set_scoped_collection_properties(1059 collection_id: CollectionId,1060 scope: PropertyScope,1061 properties: impl Iterator<Item = Property>,1062 ) -> DispatchResult {1063 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1064 stored_properties.try_scoped_set_from_iter(scope, properties)1065 })1066 .map_err(<Error<T>>::from)?;10671068 Ok(())1069 }10701071 1072 1073 1074 1075 1076 #[transactional]1077 pub fn set_collection_properties(1078 collection: &CollectionHandle<T>,1079 sender: &T::CrossAccountId,1080 properties: Vec<Property>,1081 ) -> DispatchResult {1082 for property in properties {1083 Self::set_collection_property(collection, sender, property)?;1084 }10851086 Ok(())1087 }10881089 1090 1091 1092 1093 1094 pub fn delete_collection_property(1095 collection: &CollectionHandle<T>,1096 sender: &T::CrossAccountId,1097 property_key: PropertyKey,1098 ) -> DispatchResult {1099 collection.check_is_owner_or_admin(sender)?;11001101 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1102 properties.remove(&property_key)1103 })1104 .map_err(<Error<T>>::from)?;11051106 Self::deposit_event(Event::CollectionPropertyDeleted(1107 collection.id,1108 property_key,1109 ));11101111 Ok(())1112 }11131114 1115 1116 1117 1118 1119 #[transactional]1120 pub fn delete_collection_properties(1121 collection: &CollectionHandle<T>,1122 sender: &T::CrossAccountId,1123 property_keys: Vec<PropertyKey>,1124 ) -> DispatchResult {1125 for key in property_keys {1126 Self::delete_collection_property(collection, sender, key)?;1127 }11281129 Ok(())1130 }11311132 1133 1134 1135 1136 1137 1138 pub fn set_property_permission_unchecked(1139 collection: CollectionId,1140 property_permission: PropertyKeyPermission,1141 ) -> DispatchResult {1142 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1143 permissions.try_set(property_permission.key, property_permission.permission)1144 })1145 .map_err(<Error<T>>::from)?;1146 Ok(())1147 }11481149 1150 1151 1152 1153 1154 pub fn set_property_permission(1155 collection: &CollectionHandle<T>,1156 sender: &T::CrossAccountId,1157 property_permission: PropertyKeyPermission,1158 ) -> DispatchResult {1159 Self::set_scoped_property_permission(1160 collection,1161 sender,1162 PropertyScope::None,1163 property_permission,1164 )1165 }11661167 1168 1169 1170 1171 1172 1173 pub fn set_scoped_property_permission(1174 collection: &CollectionHandle<T>,1175 sender: &T::CrossAccountId,1176 scope: PropertyScope,1177 property_permission: PropertyKeyPermission,1178 ) -> DispatchResult {1179 collection.check_is_owner_or_admin(sender)?;11801181 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1182 let current_permission = all_permissions.get(&property_permission.key);1183 if matches![1184 current_permission,1185 Some(PropertyPermission { mutable: false, .. })1186 ] {1187 return Err(<Error<T>>::NoPermission.into());1188 }11891190 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1191 let property_permission = property_permission.clone();1192 permissions.try_scoped_set(1193 scope,1194 property_permission.key,1195 property_permission.permission,1196 )1197 })1198 .map_err(<Error<T>>::from)?;11991200 Self::deposit_event(Event::PropertyPermissionSet(1201 collection.id,1202 property_permission.key,1203 ));12041205 Ok(())1206 }12071208 1209 1210 1211 1212 1213 #[transactional]1214 pub fn set_token_property_permissions(1215 collection: &CollectionHandle<T>,1216 sender: &T::CrossAccountId,1217 property_permissions: Vec<PropertyKeyPermission>,1218 ) -> DispatchResult {1219 Self::set_scoped_token_property_permissions(1220 collection,1221 sender,1222 PropertyScope::None,1223 property_permissions,1224 )1225 }12261227 1228 1229 1230 1231 1232 1233 #[transactional]1234 pub fn set_scoped_token_property_permissions(1235 collection: &CollectionHandle<T>,1236 sender: &T::CrossAccountId,1237 scope: PropertyScope,1238 property_permissions: Vec<PropertyKeyPermission>,1239 ) -> DispatchResult {1240 for prop_pemission in property_permissions {1241 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1242 }12431244 Ok(())1245 }12461247 1248 pub fn get_collection_property(1249 collection_id: CollectionId,1250 key: &PropertyKey,1251 ) -> Option<PropertyValue> {1252 Self::collection_properties(collection_id).get(key).cloned()1253 }12541255 1256 pub fn bytes_keys_to_property_keys(1257 keys: Vec<Vec<u8>>,1258 ) -> Result<Vec<PropertyKey>, DispatchError> {1259 keys.into_iter()1260 .map(|key| -> Result<PropertyKey, DispatchError> {1261 key.try_into()1262 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1263 })1264 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1265 }12661267 1268 pub fn filter_collection_properties(1269 collection_id: CollectionId,1270 keys: Option<Vec<PropertyKey>>,1271 ) -> Result<Vec<Property>, DispatchError> {1272 let properties = Self::collection_properties(collection_id);12731274 let properties = keys1275 .map(|keys| {1276 keys.into_iter()1277 .filter_map(|key| {1278 properties.get(&key).map(|value| Property {1279 key,1280 value: value.clone(),1281 })1282 })1283 .collect()1284 })1285 .unwrap_or_else(|| {1286 properties1287 .into_iter()1288 .map(|(key, value)| Property { key, value })1289 .collect()1290 });12911292 Ok(properties)1293 }12941295 1296 pub fn filter_property_permissions(1297 collection_id: CollectionId,1298 keys: Option<Vec<PropertyKey>>,1299 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1300 let permissions = Self::property_permissions(collection_id);13011302 let key_permissions = keys1303 .map(|keys| {1304 keys.into_iter()1305 .filter_map(|key| {1306 permissions1307 .get(&key)1308 .map(|permission| PropertyKeyPermission {1309 key,1310 permission: permission.clone(),1311 })1312 })1313 .collect()1314 })1315 .unwrap_or_else(|| {1316 permissions1317 .into_iter()1318 .map(|(key, permission)| PropertyKeyPermission { key, permission })1319 .collect()1320 });13211322 Ok(key_permissions)1323 }13241325 1326 1327 1328 pub fn toggle_allowlist(1329 collection: &CollectionHandle<T>,1330 sender: &T::CrossAccountId,1331 user: &T::CrossAccountId,1332 allowed: bool,1333 ) -> DispatchResult {1334 collection.check_is_owner_or_admin(sender)?;13351336 13371338 if allowed {1339 <Allowlist<T>>::insert((collection.id, user), true);1340 } else {1341 <Allowlist<T>>::remove((collection.id, user));1342 }13431344 Ok(())1345 }13461347 1348 1349 1350 pub fn toggle_admin(1351 collection: &CollectionHandle<T>,1352 sender: &T::CrossAccountId,1353 user: &T::CrossAccountId,1354 admin: bool,1355 ) -> DispatchResult {1356 collection.check_is_owner(sender)?;13571358 let was_admin = <IsAdmin<T>>::get((collection.id, user));1359 if was_admin == admin {1360 return Ok(());1361 }1362 let amount = <AdminAmount<T>>::get(collection.id);13631364 if admin {1365 let amount = amount1366 .checked_add(1)1367 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1368 ensure!(1369 amount <= Self::collection_admins_limit(),1370 <Error<T>>::CollectionAdminCountExceeded,1371 );13721373 13741375 <AdminAmount<T>>::insert(collection.id, amount);1376 <IsAdmin<T>>::insert((collection.id, user), true);1377 } else {1378 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1379 <IsAdmin<T>>::remove((collection.id, user));1380 }13811382 Ok(())1383 }13841385 1386 pub fn clamp_limits(1387 mode: CollectionMode,1388 old_limit: &CollectionLimits,1389 mut new_limit: CollectionLimits,1390 ) -> Result<CollectionLimits, DispatchError> {1391 let limits = old_limit;1392 limit_default!(old_limit, new_limit,1393 account_token_ownership_limit => ensure!(1394 new_limit <= MAX_TOKEN_OWNERSHIP,1395 <Error<T>>::CollectionLimitBoundsExceeded,1396 ),1397 sponsored_data_size => ensure!(1398 new_limit <= CUSTOM_DATA_LIMIT,1399 <Error<T>>::CollectionLimitBoundsExceeded,1400 ),14011402 sponsored_data_rate_limit => {},1403 token_limit => ensure!(1404 old_limit >= new_limit && new_limit > 0,1405 <Error<T>>::CollectionTokenLimitExceeded1406 ),14071408 sponsor_transfer_timeout(match mode {1409 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1410 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1411 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1412 }) => ensure!(1413 new_limit <= MAX_SPONSOR_TIMEOUT,1414 <Error<T>>::CollectionLimitBoundsExceeded,1415 ),1416 sponsor_approve_timeout => {},1417 owner_can_transfer => ensure!(1418 !limits.owner_can_transfer_instaled() ||1419 old_limit || !new_limit,1420 <Error<T>>::OwnerPermissionsCantBeReverted,1421 ),1422 owner_can_destroy => ensure!(1423 old_limit || !new_limit,1424 <Error<T>>::OwnerPermissionsCantBeReverted,1425 ),1426 transfers_enabled => {},1427 );1428 Ok(new_limit)1429 }14301431 1432 pub fn clamp_permissions(1433 _mode: CollectionMode,1434 old_permission: &CollectionPermissions,1435 mut new_permission: CollectionPermissions,1436 ) -> Result<CollectionPermissions, DispatchError> {1437 limit_default_clone!(old_permission, new_permission,1438 access => {},1439 mint_mode => {},1440 nesting => { },1441 );1442 Ok(new_permission)1443 }1444}144514461447#[macro_export]1448macro_rules! unsupported {1449 ($runtime:path) => {1450 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1451 };1452}145314541455pub trait CommonWeightInfo<CrossAccountId> {1456 1457 fn create_item() -> Weight;14581459 1460 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14611462 1463 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14641465 1466 fn burn_item() -> Weight;14671468 1469 1470 1471 fn set_collection_properties(amount: u32) -> Weight;14721473 1474 1475 1476 fn delete_collection_properties(amount: u32) -> Weight;14771478 1479 1480 1481 fn set_token_properties(amount: u32) -> Weight;14821483 1484 1485 1486 fn delete_token_properties(amount: u32) -> Weight;14871488 1489 1490 1491 fn set_token_property_permissions(amount: u32) -> Weight;14921493 1494 fn transfer() -> Weight;14951496 1497 fn approve() -> Weight;14981499 1500 fn transfer_from() -> Weight;15011502 1503 fn burn_from() -> Weight;15041505 1506 1507 1508 1509 fn burn_recursively_self_raw() -> Weight;15101511 1512 1513 1514 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15151516 1517 1518 1519 1520 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1521 Self::burn_recursively_self_raw()1522 .saturating_mul(max_selfs.max(1) as u64)1523 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1524 }15251526 1527 fn token_owner() -> Weight;1528}152915301531pub trait RefungibleExtensionsWeightInfo {1532 1533 fn repartition() -> Weight;1534}153515361537153815391540pub trait CommonCollectionOperations<T: Config> {1541 1542 1543 1544 1545 1546 1547 fn create_item(1548 &self,1549 sender: T::CrossAccountId,1550 to: T::CrossAccountId,1551 data: CreateItemData,1552 nesting_budget: &dyn Budget,1553 ) -> DispatchResultWithPostInfo;15541555 1556 1557 1558 1559 1560 1561 fn create_multiple_items(1562 &self,1563 sender: T::CrossAccountId,1564 to: T::CrossAccountId,1565 data: Vec<CreateItemData>,1566 nesting_budget: &dyn Budget,1567 ) -> DispatchResultWithPostInfo;15681569 1570 1571 1572 1573 1574 1575 fn create_multiple_items_ex(1576 &self,1577 sender: T::CrossAccountId,1578 data: CreateItemExData<T::CrossAccountId>,1579 nesting_budget: &dyn Budget,1580 ) -> DispatchResultWithPostInfo;15811582 1583 1584 1585 1586 1587 fn burn_item(1588 &self,1589 sender: T::CrossAccountId,1590 token: TokenId,1591 amount: u128,1592 ) -> DispatchResultWithPostInfo;15931594 1595 1596 1597 1598 1599 1600 fn burn_item_recursively(1601 &self,1602 sender: T::CrossAccountId,1603 token: TokenId,1604 self_budget: &dyn Budget,1605 breadth_budget: &dyn Budget,1606 ) -> DispatchResultWithPostInfo;16071608 1609 1610 1611 1612 fn set_collection_properties(1613 &self,1614 sender: T::CrossAccountId,1615 properties: Vec<Property>,1616 ) -> DispatchResultWithPostInfo;16171618 1619 1620 1621 1622 fn delete_collection_properties(1623 &self,1624 sender: &T::CrossAccountId,1625 property_keys: Vec<PropertyKey>,1626 ) -> DispatchResultWithPostInfo;16271628 1629 1630 1631 1632 1633 1634 1635 1636 1637 fn set_token_properties(1638 &self,1639 sender: T::CrossAccountId,1640 token_id: TokenId,1641 properties: Vec<Property>,1642 budget: &dyn Budget,1643 ) -> DispatchResultWithPostInfo;16441645 1646 1647 1648 1649 1650 1651 1652 1653 1654 fn delete_token_properties(1655 &self,1656 sender: T::CrossAccountId,1657 token_id: TokenId,1658 property_keys: Vec<PropertyKey>,1659 budget: &dyn Budget,1660 ) -> DispatchResultWithPostInfo;16611662 1663 1664 1665 1666 1667 1668 fn set_token_property_permissions(1669 &self,1670 sender: &T::CrossAccountId,1671 property_permissions: Vec<PropertyKeyPermission>,1672 ) -> DispatchResultWithPostInfo;16731674 1675 1676 1677 1678 1679 1680 1681 fn transfer(1682 &self,1683 sender: T::CrossAccountId,1684 to: T::CrossAccountId,1685 token: TokenId,1686 amount: u128,1687 budget: &dyn Budget,1688 ) -> DispatchResultWithPostInfo;16891690 1691 1692 1693 1694 1695 1696 fn approve(1697 &self,1698 sender: T::CrossAccountId,1699 spender: T::CrossAccountId,1700 token: TokenId,1701 amount: u128,1702 ) -> DispatchResultWithPostInfo;17031704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 fn transfer_from(1715 &self,1716 sender: T::CrossAccountId,1717 from: T::CrossAccountId,1718 to: T::CrossAccountId,1719 token: TokenId,1720 amount: u128,1721 budget: &dyn Budget,1722 ) -> DispatchResultWithPostInfo;17231724 1725 1726 1727 1728 1729 1730 1731 1732 1733 fn burn_from(1734 &self,1735 sender: T::CrossAccountId,1736 from: T::CrossAccountId,1737 token: TokenId,1738 amount: u128,1739 budget: &dyn Budget,1740 ) -> DispatchResultWithPostInfo;17411742 1743 1744 1745 1746 1747 1748 fn check_nesting(1749 &self,1750 sender: T::CrossAccountId,1751 from: (CollectionId, TokenId),1752 under: TokenId,1753 budget: &dyn Budget,1754 ) -> DispatchResult;17551756 1757 1758 1759 1760 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17611762 1763 1764 1765 1766 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17671768 1769 1770 1771 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17721773 1774 fn collection_tokens(&self) -> Vec<TokenId>;17751776 1777 1778 1779 fn token_exists(&self, token: TokenId) -> bool;17801781 1782 fn last_token_id(&self) -> TokenId;17831784 1785 1786 1787 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17881789 1790 1791 1792 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17931794 1795 1796 1797 1798 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17991800 1801 1802 1803 1804 1805 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18061807 1808 fn total_supply(&self) -> u32;18091810 1811 1812 1813 fn account_balance(&self, account: T::CrossAccountId) -> u32;18141815 1816 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18171818 1819 fn total_pieces(&self, token: TokenId) -> Option<u128>;18201821 1822 1823 1824 1825 1826 fn allowance(1827 &self,1828 sender: T::CrossAccountId,1829 spender: T::CrossAccountId,1830 token: TokenId,1831 ) -> u128;18321833 1834 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1835}183618371838pub trait RefungibleExtensions<T>1839where1840 T: Config,1841{1842 1843 1844 1845 1846 1847 1848 1849 fn repartition(1850 &self,1851 sender: &T::CrossAccountId,1852 token: TokenId,1853 amount: u128,1854 ) -> DispatchResultWithPostInfo;1855}18561857185818591860pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1861 let post_info = PostDispatchInfo {1862 actual_weight: Some(weight),1863 pays_fee: Pays::Yes,1864 };1865 match res {1866 Ok(()) => Ok(post_info),1867 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1868 }1869}18701871impl<T: Config> From<PropertiesError> for Error<T> {1872 fn from(error: PropertiesError) -> Self {1873 match error {1874 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1875 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1876 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1877 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1878 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1879 }1880 }1881}