12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{68 Get,69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 },72 dispatch::Pays,73 transactional, fail,74};75use up_data_structs::{76 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,77 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,79 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,80 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,81 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,82 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,83 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,84 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,85 CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101102pub type SelfWeightOf<T> = <T as Config>::WeightInfo;103104105106107108109110#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]111pub struct CollectionHandle<T: Config> {112 113 pub id: CollectionId,114 collection: Collection<T::AccountId>,115 116 pub recorder: SubstrateRecorder<T>,117}118119impl<T: Config> WithRecorder<T> for CollectionHandle<T> {120 fn recorder(&self) -> &SubstrateRecorder<T> {121 &self.recorder122 }123 fn into_recorder(self) -> SubstrateRecorder<T> {124 self.recorder125 }126}127128impl<T: Config> CollectionHandle<T> {129 130 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {131 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))132 }133134 135 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {136 <CollectionById<T>>::get(id).map(|collection| Self {137 id,138 collection,139 recorder,140 })141 }142143 144 145 pub fn new(id: CollectionId) -> Option<Self> {146 Self::new_with_gas_limit(id, u64::MAX)147 }148149 150 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {151 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)152 }153154 155 pub fn consume_store_reads(156 &self,157 reads: u64,158 ) -> pallet_evm_coder_substrate::execution::Result<()> {159 self.recorder().consume_store_reads(reads)160 }161162 163 pub fn consume_store_writes(164 &self,165 writes: u64,166 ) -> pallet_evm_coder_substrate::execution::Result<()> {167 self.recorder().consume_store_writes(writes)168 }169170 171 pub fn consume_store_reads_and_writes(172 &self,173 reads: u64,174 writes: u64,175 ) -> pallet_evm_coder_substrate::execution::Result<()> {176 self.recorder()177 .consume_store_reads_and_writes(reads, writes)178 }179180 181 pub fn save(&self) -> DispatchResult {182 <CollectionById<T>>::insert(self.id, &self.collection);183 Ok(())184 }185186 187 188 189 190 191 pub fn set_sponsor(192 &mut self,193 sender: &T::CrossAccountId,194 sponsor: T::AccountId,195 ) -> DispatchResult {196 self.check_is_internal()?;197 self.check_is_owner_or_admin(sender)?;198199 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());200201 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));202 <PalletEvm<T>>::deposit_log(203 erc::CollectionHelpersEvents::CollectionChanged {204 collection_id: eth::collection_id_to_address(self.id),205 }206 .to_log(T::ContractAddress::get()),207 );208209 self.save()210 }211212 213 214 215 216 217 218 219 220 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {221 self.check_is_internal()?;222223 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());224225 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));226 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));227 <PalletEvm<T>>::deposit_log(228 erc::CollectionHelpersEvents::CollectionChanged {229 collection_id: eth::collection_id_to_address(self.id),230 }231 .to_log(T::ContractAddress::get()),232 );233234 self.save()235 }236237 238 239 240 241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {242 self.check_is_internal()?;243 ensure!(244 self.collection.sponsorship.pending_sponsor() == Some(sender),245 Error::<T>::ConfirmSponsorshipFail246 );247248 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());249250 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));251 <PalletEvm<T>>::deposit_log(252 erc::CollectionHelpersEvents::CollectionChanged {253 collection_id: eth::collection_id_to_address(self.id),254 }255 .to_log(T::ContractAddress::get()),256 );257258 self.save()259 }260261 262 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {263 self.check_is_internal()?;264 self.check_is_owner_or_admin(sender)?;265266 self.collection.sponsorship = SponsorshipState::Disabled;267268 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));269 <PalletEvm<T>>::deposit_log(270 erc::CollectionHelpersEvents::CollectionChanged {271 collection_id: eth::collection_id_to_address(self.id),272 }273 .to_log(T::ContractAddress::get()),274 );275 self.save()276 }277278 279 280 281 282 pub fn force_remove_sponsor(&mut self) -> DispatchResult {283 self.check_is_internal()?;284285 self.collection.sponsorship = SponsorshipState::Disabled;286287 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));288 <PalletEvm<T>>::deposit_log(289 erc::CollectionHelpersEvents::CollectionChanged {290 collection_id: eth::collection_id_to_address(self.id),291 }292 .to_log(T::ContractAddress::get()),293 );294 self.save()295 }296297 298 299 pub fn check_is_internal(&self) -> DispatchResult {300 if self.flags.external {301 return Err(<Error<T>>::CollectionIsExternal)?;302 }303304 Ok(())305 }306307 308 309 pub fn check_is_external(&self) -> DispatchResult {310 if !self.flags.external {311 return Err(<Error<T>>::CollectionIsInternal)?;312 }313314 Ok(())315 }316}317318impl<T: Config> Deref for CollectionHandle<T> {319 type Target = Collection<T::AccountId>;320321 fn deref(&self) -> &Self::Target {322 &self.collection323 }324}325326impl<T: Config> DerefMut for CollectionHandle<T> {327 fn deref_mut(&mut self) -> &mut Self::Target {328 &mut self.collection329 }330}331332impl<T: Config> CollectionHandle<T> {333 334 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {335 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);336 Ok(())337 }338339 340 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {341 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))342 }343344 345 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {346 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);347 Ok(())348 }349350 351 352 353 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {354 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)355 }356357 358 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {359 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)360 }361362 363 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {364 ensure!(365 <Allowlist<T>>::get((self.id, user)),366 <Error<T>>::AddressNotInAllowlist367 );368 Ok(())369 }370371 372 373 374 pub fn change_owner(375 &mut self,376 caller: T::CrossAccountId,377 new_owner: T::CrossAccountId,378 ) -> DispatchResult {379 self.check_is_internal()?;380 self.check_is_owner(&caller)?;381 self.collection.owner = new_owner.as_sub().clone();382383 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(384 self.id,385 new_owner.as_sub().clone(),386 ));387 <PalletEvm<T>>::deposit_log(388 erc::CollectionHelpersEvents::CollectionChanged {389 collection_id: eth::collection_id_to_address(self.id),390 }391 .to_log(T::ContractAddress::get()),392 );393394 self.save()395 }396}397398#[frame_support::pallet]399pub mod pallet {400401 use super::*;402 use dispatch::CollectionDispatch;403 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};404 use up_data_structs::{TokenId, mapping::TokenAddressMapping};405 use scale_info::TypeInfo;406 use weights::WeightInfo;407408 #[pallet::config]409 pub trait Config:410 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo411 {412 413 type WeightInfo: WeightInfo;414415 416 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;417418 419 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;420421 422 #[pallet::constant]423 type CollectionCreationPrice: Get<424 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,425 >;426427 428 type CollectionDispatch: CollectionDispatch<Self>;429430 431 type TreasuryAccountId: Get<Self::AccountId>;432433 434 #[pallet::constant]435 type ContractAddress: Get<H160>;436437 438 type EvmTokenAddressMapping: TokenAddressMapping<H160>;439440 441 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;442 }443444 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);445 446 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);447448 #[pallet::pallet]449 #[pallet::storage_version(STORAGE_VERSION)]450 pub struct Pallet<T>(_);451452 #[pallet::extra_constants]453 impl<T: Config> Pallet<T> {454 455 pub fn collection_admins_limit() -> u32 {456 COLLECTION_ADMINS_LIMIT457 }458 }459460 #[pallet::genesis_config]461 pub struct GenesisConfig<T>(PhantomData<T>);462463 #[cfg(feature = "std")]464 impl<T: Config> Default for GenesisConfig<T> {465 fn default() -> Self {466 Self(Default::default())467 }468 }469470 #[pallet::genesis_build]471 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {472 fn build(&self) {473 StorageVersion::new(1).put::<Pallet<T>>();474 }475 }476477 impl<T: Config> Pallet<T> {478 479 pub fn deposit_event(event: Event<T>) {480 let event = <T as Config>::RuntimeEvent::from(event);481 let event = event.into();482 <frame_system::Pallet<T>>::deposit_event(event)483 }484 }485486 #[pallet::event]487 pub enum Event<T: Config> {488 489 CollectionCreated(490 491 CollectionId,492 493 u8,494 495 T::AccountId,496 ),497498 499 CollectionDestroyed(500 501 CollectionId,502 ),503504 505 ItemCreated(506 507 CollectionId,508 509 TokenId,510 511 T::CrossAccountId,512 513 u128,514 ),515516 517 ItemDestroyed(518 519 CollectionId,520 521 TokenId,522 523 T::CrossAccountId,524 525 u128,526 ),527528 529 Transfer(530 531 CollectionId,532 533 TokenId,534 535 T::CrossAccountId,536 537 T::CrossAccountId,538 539 u128,540 ),541542 543 Approved(544 545 CollectionId,546 547 TokenId,548 549 T::CrossAccountId,550 551 T::CrossAccountId,552 553 u128,554 ),555556 557 ApprovedForAll(558 559 CollectionId,560 561 T::CrossAccountId,562 563 T::CrossAccountId,564 565 bool,566 ),567568 569 CollectionPropertySet(570 571 CollectionId,572 573 PropertyKey,574 ),575576 577 CollectionPropertyDeleted(578 579 CollectionId,580 581 PropertyKey,582 ),583584 585 TokenPropertySet(586 587 CollectionId,588 589 TokenId,590 591 PropertyKey,592 ),593594 595 TokenPropertyDeleted(596 597 CollectionId,598 599 TokenId,600 601 PropertyKey,602 ),603604 605 PropertyPermissionSet(606 607 CollectionId,608 609 PropertyKey,610 ),611612 613 AllowListAddressAdded(614 615 CollectionId,616 617 T::CrossAccountId,618 ),619620 621 AllowListAddressRemoved(622 623 CollectionId,624 625 T::CrossAccountId,626 ),627628 629 CollectionAdminAdded(630 631 CollectionId,632 633 T::CrossAccountId,634 ),635636 637 CollectionAdminRemoved(638 639 CollectionId,640 641 T::CrossAccountId,642 ),643644 645 CollectionLimitSet(646 647 CollectionId,648 ),649650 651 CollectionOwnerChanged(652 653 CollectionId,654 655 T::AccountId,656 ),657658 659 CollectionPermissionSet(660 661 CollectionId,662 ),663664 665 CollectionSponsorSet(666 667 CollectionId,668 669 T::AccountId,670 ),671672 673 SponsorshipConfirmed(674 675 CollectionId,676 677 T::AccountId,678 ),679680 681 CollectionSponsorRemoved(682 683 CollectionId,684 ),685 }686687 #[pallet::error]688 pub enum Error<T> {689 690 CollectionNotFound,691 692 MustBeTokenOwner,693 694 NoPermission,695 696 CantDestroyNotEmptyCollection,697 698 PublicMintingNotAllowed,699 700 AddressNotInAllowlist,701702 703 CollectionNameLimitExceeded,704 705 CollectionDescriptionLimitExceeded,706 707 CollectionTokenPrefixLimitExceeded,708 709 TotalCollectionsLimitExceeded,710 711 CollectionAdminCountExceeded,712 713 CollectionLimitBoundsExceeded,714 715 OwnerPermissionsCantBeReverted,716 717 TransferNotAllowed,718 719 AccountTokenLimitExceeded,720 721 CollectionTokenLimitExceeded,722 723 MetadataFlagFrozen,724725 726 TokenNotFound,727 728 TokenValueTooLow,729 730 ApprovedValueTooLow,731 732 CantApproveMoreThanOwned,733 734 AddressIsNotEthMirror,735736 737 AddressIsZero,738739 740 UnsupportedOperation,741742 743 NotSufficientFounds,744745 746 UserIsNotAllowedToNest,747 748 SourceCollectionIsNotAllowedToNest,749750 751 CollectionFieldSizeExceeded,752753 754 NoSpaceForProperty,755756 757 PropertyLimitReached,758759 760 PropertyKeyIsTooLong,761762 763 InvalidCharacterInPropertyKey,764765 766 EmptyPropertyKey,767768 769 CollectionIsExternal,770771 772 CollectionIsInternal,773774 775 ConfirmSponsorshipFail,776777 778 UserIsNotCollectionAdmin,779 }780781 782 #[pallet::storage]783 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;784785 786 #[pallet::storage]787 pub type DestroyedCollectionCount<T> =788 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;789790 791 #[pallet::storage]792 pub type CollectionById<T> = StorageMap<793 Hasher = Blake2_128Concat,794 Key = CollectionId,795 Value = Collection<<T as frame_system::Config>::AccountId>,796 QueryKind = OptionQuery,797 >;798799 800 #[pallet::storage]801 #[pallet::getter(fn collection_properties)]802 pub type CollectionProperties<T> = StorageMap<803 Hasher = Blake2_128Concat,804 Key = CollectionId,805 Value = CollectionPropertiesT,806 QueryKind = ValueQuery,807 >;808809 810 #[pallet::storage]811 #[pallet::getter(fn property_permissions)]812 pub type CollectionPropertyPermissions<T> = StorageMap<813 Hasher = Blake2_128Concat,814 Key = CollectionId,815 Value = PropertiesPermissionMap,816 QueryKind = ValueQuery,817 >;818819 820 #[pallet::storage]821 pub type AdminAmount<T> = StorageMap<822 Hasher = Blake2_128Concat,823 Key = CollectionId,824 Value = u32,825 QueryKind = ValueQuery,826 >;827828 829 #[pallet::storage]830 pub type IsAdmin<T: Config> = StorageNMap<831 Key = (832 Key<Blake2_128Concat, CollectionId>,833 Key<Blake2_128Concat, T::CrossAccountId>,834 ),835 Value = bool,836 QueryKind = ValueQuery,837 >;838839 840 #[pallet::storage]841 pub type Allowlist<T: Config> = StorageNMap<842 Key = (843 Key<Blake2_128Concat, CollectionId>,844 Key<Blake2_128Concat, T::CrossAccountId>,845 ),846 Value = bool,847 QueryKind = ValueQuery,848 >;849850 851 #[pallet::storage]852 pub type DummyStorageValue<T: Config> = StorageValue<853 Value = (854 CollectionStats,855 CollectionId,856 TokenId,857 TokenChild,858 PhantomType<(859 TokenData<T::CrossAccountId>,860 RpcCollection<T::AccountId>,861 862 PovInfo,863 )>,864 ),865 QueryKind = OptionQuery,866 >;867}868869870pub enum SetPropertyMode {871 872 ExistingToken,873874 875 NewToken {876 877 mint_target_is_sender: bool,878 },879}880881882pub struct LazyValue<T, F: FnOnce() -> T> {883 value: Option<T>,884 f: Option<F>,885}886887impl<T, F: FnOnce() -> T> LazyValue<T, F> {888 889 pub fn new(f: F) -> Self {890 Self {891 value: None,892 f: Some(f),893 }894 }895896 897 pub fn value(&mut self) -> &T {898 if self.value.is_none() {899 self.value = Some(self.f.take().unwrap()())900 }901902 self.value.as_ref().unwrap()903 }904905 906 pub fn has_value(&self) -> bool {907 self.value.is_some()908 }909}910911fn check_token_permissions<T, FCA, FTO, FTE>(912 collection_admin_permitted: bool,913 token_owner_permitted: bool,914 is_collection_admin: &mut LazyValue<bool, FCA>,915 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,916 is_token_exist: &mut LazyValue<bool, FTE>,917) -> DispatchResult918where919 T: Config,920 FCA: FnOnce() -> bool,921 FTO: FnOnce() -> Result<bool, DispatchError>,922 FTE: FnOnce() -> bool,923{924 if !(collection_admin_permitted && *is_collection_admin.value()925 || token_owner_permitted && (*is_token_owner.value())?)926 {927 fail!(<Error<T>>::NoPermission);928 }929930 let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;931 if !token_certainly_exist && !is_token_exist.value() {932 fail!(<Error<T>>::TokenNotFound);933 }934 Ok(())935}936937impl<T: Config> Pallet<T> {938 939 940 941 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {942 ensure!(943 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,944 <Error<T>>::AddressIsZero945 );946 Ok(())947 }948949 950 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {951 <IsAdmin<T>>::iter_prefix((collection,))952 .map(|(a, _)| a)953 .collect()954 }955956 957 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {958 <Allowlist<T>>::iter_prefix((collection,))959 .map(|(a, _)| a)960 .collect()961 }962963 964 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {965 <Allowlist<T>>::get((collection, user))966 }967968 969 pub fn collection_stats() -> CollectionStats {970 let created = <CreatedCollectionCount<T>>::get();971 let destroyed = <DestroyedCollectionCount<T>>::get();972 CollectionStats {973 created: created.0,974 destroyed: destroyed.0,975 alive: created.0 - destroyed.0,976 }977 }978979 980 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {981 let collection = <CollectionById<T>>::get(collection)?;982 let limits = collection.limits;983 let effective_limits = CollectionLimits {984 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),985 sponsored_data_size: Some(limits.sponsored_data_size()),986 sponsored_data_rate_limit: Some(987 limits988 .sponsored_data_rate_limit989 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),990 ),991 token_limit: Some(limits.token_limit()),992 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(993 match collection.mode {994 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,995 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,996 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,997 },998 )),999 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1000 owner_can_transfer: Some(limits.owner_can_transfer()),1001 owner_can_destroy: Some(limits.owner_can_destroy()),1002 transfers_enabled: Some(limits.transfers_enabled()),1003 };10041005 Some(effective_limits)1006 }10071008 1009 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1010 let Collection {1011 name,1012 description,1013 owner,1014 mode,1015 token_prefix,1016 sponsorship,1017 limits,1018 permissions,1019 flags,1020 } = <CollectionById<T>>::get(collection)?;10211022 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1023 .into_iter()1024 .map(|(key, permission)| PropertyKeyPermission { key, permission })1025 .collect();10261027 let properties = <CollectionProperties<T>>::get(collection)1028 .into_iter()1029 .map(|(key, value)| Property { key, value })1030 .collect();10311032 let permissions = CollectionPermissions {1033 access: Some(permissions.access()),1034 mint_mode: Some(permissions.mint_mode()),1035 nesting: Some(permissions.nesting().clone()),1036 };10371038 Some(RpcCollection {1039 name: name.into_inner(),1040 description: description.into_inner(),1041 owner,1042 mode,1043 token_prefix: token_prefix.into_inner(),1044 sponsorship,1045 limits,1046 permissions,1047 token_property_permissions,1048 properties,1049 read_only: flags.external,10501051 flags: RpcCollectionFlags {1052 foreign: flags.foreign,1053 erc721metadata: flags.erc721metadata,1054 },1055 })1056 }1057}10581059macro_rules! limit_default {1060 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1061 $(1062 if let Some($new) = $new.$field {1063 let $old = $old.$field($($arg)?);1064 let _ = $new;1065 let _ = $old;1066 $check1067 } else {1068 $new.$field = $old.$field1069 }1070 )*1071 }};1072}1073macro_rules! limit_default_clone {1074 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1075 $(1076 if let Some($new) = $new.$field.clone() {1077 let $old = $old.$field($($arg)?);1078 let _ = $new;1079 let _ = $old;1080 $check1081 } else {1082 $new.$field = $old.$field.clone()1083 }1084 )*1085 }};1086}10871088impl<T: Config> Pallet<T> {1089 1090 1091 1092 1093 1094 pub fn init_collection(1095 owner: T::CrossAccountId,1096 payer: T::CrossAccountId,1097 data: CreateCollectionData<T::AccountId>,1098 flags: CollectionFlags,1099 ) -> Result<CollectionId, DispatchError> {1100 {1101 ensure!(1102 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1103 Error::<T>::CollectionTokenPrefixLimitExceeded1104 );1105 }11061107 let created_count = <CreatedCollectionCount<T>>::get()1108 .01109 .checked_add(1)1110 .ok_or(ArithmeticError::Overflow)?;1111 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1112 let id = CollectionId(created_count);11131114 1115 ensure!(1116 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1117 <Error<T>>::TotalCollectionsLimitExceeded1118 );11191120 11211122 let collection = Collection {1123 owner: owner.as_sub().clone(),1124 name: data.name,1125 mode: data.mode.clone(),1126 description: data.description,1127 token_prefix: data.token_prefix,1128 sponsorship: data1129 .pending_sponsor1130 .map(SponsorshipState::Unconfirmed)1131 .unwrap_or_default(),1132 limits: data1133 .limits1134 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1135 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1136 permissions: data1137 .permissions1138 .map(|permissions| {1139 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1140 })1141 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1142 flags,1143 };11441145 let mut collection_properties = CollectionPropertiesT::new();1146 collection_properties1147 .try_set_from_iter(data.properties.into_iter())1148 .map_err(<Error<T>>::from)?;11491150 CollectionProperties::<T>::insert(id, collection_properties);11511152 let mut token_props_permissions = PropertiesPermissionMap::new();1153 token_props_permissions1154 .try_set_from_iter(data.token_property_permissions.into_iter())1155 .map_err(<Error<T>>::from)?;11561157 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11581159 1160 {1161 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1162 imbalance.subsume(<T as Config>::Currency::deposit(1163 &T::TreasuryAccountId::get(),1164 T::CollectionCreationPrice::get(),1165 Precision::Exact,1166 )?);1167 let credit =1168 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1169 .map_err(|_| Error::<T>::NotSufficientFounds)?;11701171 debug_assert!(credit.peek().is_zero())1172 }11731174 <CreatedCollectionCount<T>>::put(created_count);1175 <Pallet<T>>::deposit_event(Event::CollectionCreated(1176 id,1177 data.mode.id(),1178 owner.as_sub().clone(),1179 ));1180 <PalletEvm<T>>::deposit_log(1181 erc::CollectionHelpersEvents::CollectionCreated {1182 owner: *owner.as_eth(),1183 collection_id: eth::collection_id_to_address(id),1184 }1185 .to_log(T::ContractAddress::get()),1186 );1187 <CollectionById<T>>::insert(id, collection);1188 Ok(id)1189 }11901191 1192 1193 1194 1195 pub fn destroy_collection(1196 collection: CollectionHandle<T>,1197 sender: &T::CrossAccountId,1198 ) -> DispatchResult {1199 ensure!(1200 collection.limits.owner_can_destroy(),1201 <Error<T>>::NoPermission,1202 );1203 collection.check_is_owner(sender)?;12041205 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1206 .01207 .checked_add(1)1208 .ok_or(ArithmeticError::Overflow)?;12091210 12111212 <DestroyedCollectionCount<T>>::put(destroyed_collections);1213 <CollectionById<T>>::remove(collection.id);1214 <AdminAmount<T>>::remove(collection.id);1215 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1216 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1217 <CollectionProperties<T>>::remove(collection.id);12181219 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12201221 <PalletEvm<T>>::deposit_log(1222 erc::CollectionHelpersEvents::CollectionDestroyed {1223 collection_id: eth::collection_id_to_address(collection.id),1224 }1225 .to_log(T::ContractAddress::get()),1226 );1227 Ok(())1228 }12291230 1231 1232 1233 1234 1235 1236 1237 1238 #[transactional]1239 fn modify_collection_properties(1240 collection: &CollectionHandle<T>,1241 sender: &T::CrossAccountId,1242 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1243 ) -> DispatchResult {1244 collection.check_is_owner_or_admin(sender)?;12451246 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12471248 for (key, value) in properties_updates {1249 match value {1250 Some(value) => {1251 stored_properties1252 .try_set(key.clone(), value)1253 .map_err(<Error<T>>::from)?;12541255 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1256 <PalletEvm<T>>::deposit_log(1257 erc::CollectionHelpersEvents::CollectionChanged {1258 collection_id: eth::collection_id_to_address(collection.id),1259 }1260 .to_log(T::ContractAddress::get()),1261 );1262 }1263 None => {1264 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12651266 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1267 <PalletEvm<T>>::deposit_log(1268 erc::CollectionHelpersEvents::CollectionChanged {1269 collection_id: eth::collection_id_to_address(collection.id),1270 }1271 .to_log(T::ContractAddress::get()),1272 );1273 }1274 }1275 }12761277 <CollectionProperties<T>>::set(collection.id, stored_properties);12781279 Ok(())1280 }12811282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 #[allow(clippy::too_many_arguments)]1296 pub fn modify_token_properties<FTO, FTE>(1297 collection: &CollectionHandle<T>,1298 sender: &T::CrossAccountId,1299 token_id: TokenId,1300 is_token_exist: &mut LazyValue<bool, FTE>,1301 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1302 mut stored_properties: TokenProperties,1303 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,1304 set_token_properties: impl FnOnce(TokenProperties),1305 log: evm_coder::ethereum::Log,1306 ) -> DispatchResult1307 where1308 FTO: FnOnce() -> Result<bool, DispatchError>,1309 FTE: FnOnce() -> bool,1310 {1311 let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));1312 let permissions = Self::property_permissions(collection.id);13131314 let mut changed = false;1315 for (key, value) in properties_updates {1316 let permission = permissions1317 .get(&key)1318 .cloned()1319 .unwrap_or_else(PropertyPermission::none);13201321 let property_exists = stored_properties.get(&key).is_some();13221323 match permission {1324 PropertyPermission { mutable: false, .. } if property_exists => {1325 return Err(<Error<T>>::NoPermission.into());1326 }13271328 PropertyPermission {1329 collection_admin,1330 token_owner,1331 ..1332 } => check_token_permissions::<T, _, FTO, FTE>(1333 collection_admin,1334 token_owner,1335 &mut is_collection_admin,1336 is_token_owner,1337 is_token_exist,1338 )?,1339 }13401341 match value {1342 Some(value) => {1343 stored_properties1344 .try_set(key.clone(), value)1345 .map_err(<Error<T>>::from)?;13461347 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1348 }1349 None => {1350 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13511352 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1353 }1354 }13551356 changed = true;1357 }13581359 if changed {1360 <PalletEvm<T>>::deposit_log(log);1361 }13621363 set_token_properties(stored_properties);13641365 Ok(())1366 }13671368 1369 1370 1371 1372 1373 1374 pub fn set_allowance_for_all(1375 collection: &CollectionHandle<T>,1376 owner: &T::CrossAccountId,1377 operator: &T::CrossAccountId,1378 approve: bool,1379 set_allowance: impl FnOnce(),1380 log: evm_coder::ethereum::Log,1381 ) -> DispatchResult {1382 if collection.permissions.access() == AccessMode::AllowList {1383 collection.check_allowlist(owner)?;1384 collection.check_allowlist(operator)?;1385 }13861387 Self::ensure_correct_receiver(operator)?;13881389 set_allowance();13901391 <PalletEvm<T>>::deposit_log(log);1392 Self::deposit_event(Event::ApprovedForAll(1393 collection.id,1394 owner.clone(),1395 operator.clone(),1396 approve,1397 ));1398 Ok(())1399 }14001401 1402 1403 1404 1405 1406 pub fn set_collection_property(1407 collection: &CollectionHandle<T>,1408 sender: &T::CrossAccountId,1409 property: Property,1410 ) -> DispatchResult {1411 Self::set_collection_properties(collection, sender, [property].into_iter())1412 }14131414 1415 1416 1417 1418 1419 1420 pub fn set_scoped_collection_property(1421 collection_id: CollectionId,1422 scope: PropertyScope,1423 property: Property,1424 ) -> DispatchResult {1425 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1426 properties.try_scoped_set(scope, property.key, property.value)1427 })1428 .map_err(<Error<T>>::from)?;14291430 Ok(())1431 }14321433 1434 1435 1436 1437 1438 1439 pub fn set_scoped_collection_properties(1440 collection_id: CollectionId,1441 scope: PropertyScope,1442 properties: impl Iterator<Item = Property>,1443 ) -> DispatchResult {1444 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1445 stored_properties.try_scoped_set_from_iter(scope, properties)1446 })1447 .map_err(<Error<T>>::from)?;14481449 Ok(())1450 }14511452 1453 1454 1455 1456 1457 pub fn set_collection_properties(1458 collection: &CollectionHandle<T>,1459 sender: &T::CrossAccountId,1460 properties: impl Iterator<Item = Property>,1461 ) -> DispatchResult {1462 Self::modify_collection_properties(1463 collection,1464 sender,1465 properties.map(|property| (property.key, Some(property.value))),1466 )1467 }14681469 1470 1471 1472 1473 1474 pub fn delete_collection_property(1475 collection: &CollectionHandle<T>,1476 sender: &T::CrossAccountId,1477 property_key: PropertyKey,1478 ) -> DispatchResult {1479 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1480 }14811482 1483 1484 1485 1486 1487 pub fn delete_collection_properties(1488 collection: &CollectionHandle<T>,1489 sender: &T::CrossAccountId,1490 property_keys: impl Iterator<Item = PropertyKey>,1491 ) -> DispatchResult {1492 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1493 }14941495 1496 1497 1498 1499 1500 1501 pub fn set_property_permission_unchecked(1502 collection: CollectionId,1503 property_permission: PropertyKeyPermission,1504 ) -> DispatchResult {1505 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1506 permissions.try_set(property_permission.key, property_permission.permission)1507 })1508 .map_err(<Error<T>>::from)?;1509 Ok(())1510 }15111512 1513 1514 1515 1516 1517 pub fn set_property_permission(1518 collection: &CollectionHandle<T>,1519 sender: &T::CrossAccountId,1520 property_permission: PropertyKeyPermission,1521 ) -> DispatchResult {1522 Self::set_scoped_property_permission(1523 collection,1524 sender,1525 PropertyScope::None,1526 property_permission,1527 )1528 }15291530 1531 1532 1533 1534 1535 1536 pub fn set_scoped_property_permission(1537 collection: &CollectionHandle<T>,1538 sender: &T::CrossAccountId,1539 scope: PropertyScope,1540 property_permission: PropertyKeyPermission,1541 ) -> DispatchResult {1542 collection.check_is_owner_or_admin(sender)?;15431544 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1545 let current_permission = all_permissions.get(&property_permission.key);1546 if matches![1547 current_permission,1548 Some(PropertyPermission { mutable: false, .. })1549 ] {1550 return Err(<Error<T>>::NoPermission.into());1551 }15521553 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1554 let property_permission = property_permission.clone();1555 permissions.try_scoped_set(1556 scope,1557 property_permission.key,1558 property_permission.permission,1559 )1560 })1561 .map_err(<Error<T>>::from)?;15621563 Self::deposit_event(Event::PropertyPermissionSet(1564 collection.id,1565 property_permission.key,1566 ));1567 <PalletEvm<T>>::deposit_log(1568 erc::CollectionHelpersEvents::CollectionChanged {1569 collection_id: eth::collection_id_to_address(collection.id),1570 }1571 .to_log(T::ContractAddress::get()),1572 );15731574 Ok(())1575 }15761577 1578 1579 1580 1581 1582 #[transactional]1583 pub fn set_token_property_permissions(1584 collection: &CollectionHandle<T>,1585 sender: &T::CrossAccountId,1586 property_permissions: Vec<PropertyKeyPermission>,1587 ) -> DispatchResult {1588 Self::set_scoped_token_property_permissions(1589 collection,1590 sender,1591 PropertyScope::None,1592 property_permissions,1593 )1594 }15951596 1597 1598 1599 1600 1601 1602 #[transactional]1603 pub fn set_scoped_token_property_permissions(1604 collection: &CollectionHandle<T>,1605 sender: &T::CrossAccountId,1606 scope: PropertyScope,1607 property_permissions: Vec<PropertyKeyPermission>,1608 ) -> DispatchResult {1609 for prop_pemission in property_permissions {1610 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1611 }16121613 Ok(())1614 }16151616 1617 pub fn get_collection_property(1618 collection_id: CollectionId,1619 key: &PropertyKey,1620 ) -> Option<PropertyValue> {1621 Self::collection_properties(collection_id).get(key).cloned()1622 }16231624 1625 pub fn bytes_keys_to_property_keys(1626 keys: Vec<Vec<u8>>,1627 ) -> Result<Vec<PropertyKey>, DispatchError> {1628 keys.into_iter()1629 .map(|key| -> Result<PropertyKey, DispatchError> {1630 key.try_into()1631 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1632 })1633 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1634 }16351636 1637 pub fn filter_collection_properties(1638 collection_id: CollectionId,1639 keys: Option<Vec<PropertyKey>>,1640 ) -> Result<Vec<Property>, DispatchError> {1641 let properties = Self::collection_properties(collection_id);16421643 let properties = keys1644 .map(|keys| {1645 keys.into_iter()1646 .filter_map(|key| {1647 properties.get(&key).map(|value| Property {1648 key,1649 value: value.clone(),1650 })1651 })1652 .collect()1653 })1654 .unwrap_or_else(|| {1655 properties1656 .into_iter()1657 .map(|(key, value)| Property { key, value })1658 .collect()1659 });16601661 Ok(properties)1662 }16631664 1665 pub fn filter_property_permissions(1666 collection_id: CollectionId,1667 keys: Option<Vec<PropertyKey>>,1668 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1669 let permissions = Self::property_permissions(collection_id);16701671 let key_permissions = keys1672 .map(|keys| {1673 keys.into_iter()1674 .filter_map(|key| {1675 permissions1676 .get(&key)1677 .map(|permission| PropertyKeyPermission {1678 key,1679 permission: permission.clone(),1680 })1681 })1682 .collect()1683 })1684 .unwrap_or_else(|| {1685 permissions1686 .into_iter()1687 .map(|(key, permission)| PropertyKeyPermission { key, permission })1688 .collect()1689 });16901691 Ok(key_permissions)1692 }16931694 1695 1696 1697 pub fn toggle_allowlist(1698 collection: &CollectionHandle<T>,1699 sender: &T::CrossAccountId,1700 user: &T::CrossAccountId,1701 allowed: bool,1702 ) -> DispatchResult {1703 collection.check_is_owner_or_admin(sender)?;17041705 17061707 if allowed {1708 <Allowlist<T>>::insert((collection.id, user), true);1709 Self::deposit_event(Event::<T>::AllowListAddressAdded(1710 collection.id,1711 user.clone(),1712 ));1713 } else {1714 <Allowlist<T>>::remove((collection.id, user));1715 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1716 collection.id,1717 user.clone(),1718 ));1719 }17201721 <PalletEvm<T>>::deposit_log(1722 erc::CollectionHelpersEvents::CollectionChanged {1723 collection_id: eth::collection_id_to_address(collection.id),1724 }1725 .to_log(T::ContractAddress::get()),1726 );17271728 Ok(())1729 }17301731 1732 1733 1734 pub fn toggle_admin(1735 collection: &CollectionHandle<T>,1736 sender: &T::CrossAccountId,1737 user: &T::CrossAccountId,1738 admin: bool,1739 ) -> DispatchResult {1740 collection.check_is_internal()?;1741 collection.check_is_owner(sender)?;17421743 let is_admin = <IsAdmin<T>>::get((collection.id, user));1744 if is_admin == admin {1745 if admin {1746 return Ok(());1747 } else {1748 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1749 }1750 }1751 let amount = <AdminAmount<T>>::get(collection.id);17521753 17541755 if admin {1756 let amount = amount1757 .checked_add(1)1758 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1759 ensure!(1760 amount <= Self::collection_admins_limit(),1761 <Error<T>>::CollectionAdminCountExceeded,1762 );17631764 <AdminAmount<T>>::insert(collection.id, amount);1765 <IsAdmin<T>>::insert((collection.id, user), true);17661767 Self::deposit_event(Event::<T>::CollectionAdminAdded(1768 collection.id,1769 user.clone(),1770 ));1771 } else {1772 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1773 <IsAdmin<T>>::remove((collection.id, user));17741775 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1776 collection.id,1777 user.clone(),1778 ));1779 }17801781 <PalletEvm<T>>::deposit_log(1782 erc::CollectionHelpersEvents::CollectionChanged {1783 collection_id: eth::collection_id_to_address(collection.id),1784 }1785 .to_log(T::ContractAddress::get()),1786 );17871788 Ok(())1789 }17901791 1792 pub fn update_limits(1793 user: &T::CrossAccountId,1794 collection: &mut CollectionHandle<T>,1795 new_limit: CollectionLimits,1796 ) -> DispatchResult {1797 collection.check_is_internal()?;1798 collection.check_is_owner_or_admin(user)?;17991800 collection.limits =1801 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;18021803 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1804 <PalletEvm<T>>::deposit_log(1805 erc::CollectionHelpersEvents::CollectionChanged {1806 collection_id: eth::collection_id_to_address(collection.id),1807 }1808 .to_log(T::ContractAddress::get()),1809 );18101811 collection.save()1812 }18131814 1815 fn clamp_limits(1816 mode: CollectionMode,1817 old_limit: &CollectionLimits,1818 mut new_limit: CollectionLimits,1819 ) -> Result<CollectionLimits, DispatchError> {1820 let limits = old_limit;1821 limit_default!(old_limit, new_limit,1822 account_token_ownership_limit => ensure!(1823 new_limit <= MAX_TOKEN_OWNERSHIP,1824 <Error<T>>::CollectionLimitBoundsExceeded,1825 ),1826 sponsored_data_size => ensure!(1827 new_limit <= CUSTOM_DATA_LIMIT,1828 <Error<T>>::CollectionLimitBoundsExceeded,1829 ),18301831 sponsored_data_rate_limit => {},1832 token_limit => ensure!(1833 old_limit >= new_limit && new_limit > 0,1834 <Error<T>>::CollectionTokenLimitExceeded1835 ),18361837 sponsor_transfer_timeout(match mode {1838 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1839 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1840 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1841 }) => ensure!(1842 new_limit <= MAX_SPONSOR_TIMEOUT,1843 <Error<T>>::CollectionLimitBoundsExceeded,1844 ),1845 sponsor_approve_timeout => {},1846 owner_can_transfer => ensure!(1847 !limits.owner_can_transfer_instaled() ||1848 old_limit || !new_limit,1849 <Error<T>>::OwnerPermissionsCantBeReverted,1850 ),1851 owner_can_destroy => ensure!(1852 old_limit || !new_limit,1853 <Error<T>>::OwnerPermissionsCantBeReverted,1854 ),1855 transfers_enabled => {},1856 );1857 Ok(new_limit)1858 }18591860 1861 pub fn update_permissions(1862 user: &T::CrossAccountId,1863 collection: &mut CollectionHandle<T>,1864 new_permission: CollectionPermissions,1865 ) -> DispatchResult {1866 collection.check_is_internal()?;1867 collection.check_is_owner_or_admin(user)?;1868 collection.permissions = Self::clamp_permissions(1869 collection.mode.clone(),1870 &collection.permissions,1871 new_permission,1872 )?;18731874 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1875 <PalletEvm<T>>::deposit_log(1876 erc::CollectionHelpersEvents::CollectionChanged {1877 collection_id: eth::collection_id_to_address(collection.id),1878 }1879 .to_log(T::ContractAddress::get()),1880 );18811882 collection.save()1883 }18841885 1886 fn clamp_permissions(1887 _mode: CollectionMode,1888 old_permission: &CollectionPermissions,1889 mut new_permission: CollectionPermissions,1890 ) -> Result<CollectionPermissions, DispatchError> {1891 limit_default_clone!(old_permission, new_permission,1892 access => {},1893 mint_mode => {},1894 nesting => { },1895 );1896 Ok(new_permission)1897 }18981899 1900 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1901 CollectionProperties::<T>::mutate(collection_id, |properties| {1902 properties.recompute_consumed_space();1903 });19041905 Ok(())1906 }1907}190819091910#[macro_export]1911macro_rules! unsupported {1912 ($runtime:path) => {1913 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1914 };1915}191619171918pub trait CommonWeightInfo<CrossAccountId> {1919 1920 fn create_item(data: &CreateItemData) -> Weight {1921 Self::create_multiple_items(from_ref(data))1922 }19231924 1925 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19261927 1928 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19291930 1931 fn burn_item() -> Weight;19321933 1934 1935 1936 fn set_collection_properties(amount: u32) -> Weight;19371938 1939 1940 1941 fn delete_collection_properties(amount: u32) -> Weight;19421943 1944 1945 1946 fn set_token_properties(amount: u32) -> Weight;19471948 1949 1950 1951 fn delete_token_properties(amount: u32) -> Weight;19521953 1954 1955 1956 fn set_token_property_permissions(amount: u32) -> Weight;19571958 1959 fn transfer() -> Weight;19601961 1962 fn approve() -> Weight;19631964 1965 fn approve_from() -> Weight;19661967 1968 fn transfer_from() -> Weight;19691970 1971 fn burn_from() -> Weight;19721973 1974 1975 1976 1977 fn burn_recursively_self_raw() -> Weight;19781979 1980 1981 1982 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19831984 1985 1986 1987 1988 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1989 Self::burn_recursively_self_raw()1990 .saturating_mul(max_selfs.max(1) as u64)1991 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1992 }19931994 1995 fn token_owner() -> Weight;19961997 1998 fn set_allowance_for_all() -> Weight;19992000 2001 fn force_repair_item() -> Weight;2002}200320042005pub trait RefungibleExtensionsWeightInfo {2006 2007 fn repartition() -> Weight;2008}200920102011201220132014pub trait CommonCollectionOperations<T: Config> {2015 2016 2017 2018 2019 2020 2021 fn create_item(2022 &self,2023 sender: T::CrossAccountId,2024 to: T::CrossAccountId,2025 data: CreateItemData,2026 nesting_budget: &dyn Budget,2027 ) -> DispatchResultWithPostInfo;20282029 2030 2031 2032 2033 2034 2035 fn create_multiple_items(2036 &self,2037 sender: T::CrossAccountId,2038 to: T::CrossAccountId,2039 data: Vec<CreateItemData>,2040 nesting_budget: &dyn Budget,2041 ) -> DispatchResultWithPostInfo;20422043 2044 2045 2046 2047 2048 2049 fn create_multiple_items_ex(2050 &self,2051 sender: T::CrossAccountId,2052 data: CreateItemExData<T::CrossAccountId>,2053 nesting_budget: &dyn Budget,2054 ) -> DispatchResultWithPostInfo;20552056 2057 2058 2059 2060 2061 fn burn_item(2062 &self,2063 sender: T::CrossAccountId,2064 token: TokenId,2065 amount: u128,2066 ) -> DispatchResultWithPostInfo;20672068 2069 2070 2071 2072 2073 2074 fn burn_item_recursively(2075 &self,2076 sender: T::CrossAccountId,2077 token: TokenId,2078 self_budget: &dyn Budget,2079 breadth_budget: &dyn Budget,2080 ) -> DispatchResultWithPostInfo;20812082 2083 2084 2085 2086 fn set_collection_properties(2087 &self,2088 sender: T::CrossAccountId,2089 properties: Vec<Property>,2090 ) -> DispatchResultWithPostInfo;20912092 2093 2094 2095 2096 fn delete_collection_properties(2097 &self,2098 sender: &T::CrossAccountId,2099 property_keys: Vec<PropertyKey>,2100 ) -> DispatchResultWithPostInfo;21012102 2103 2104 2105 2106 2107 2108 2109 2110 2111 fn set_token_properties(2112 &self,2113 sender: T::CrossAccountId,2114 token_id: TokenId,2115 properties: Vec<Property>,2116 budget: &dyn Budget,2117 ) -> DispatchResultWithPostInfo;21182119 2120 2121 2122 2123 2124 2125 2126 2127 2128 fn delete_token_properties(2129 &self,2130 sender: T::CrossAccountId,2131 token_id: TokenId,2132 property_keys: Vec<PropertyKey>,2133 budget: &dyn Budget,2134 ) -> DispatchResultWithPostInfo;21352136 2137 2138 2139 2140 2141 2142 fn set_token_property_permissions(2143 &self,2144 sender: &T::CrossAccountId,2145 property_permissions: Vec<PropertyKeyPermission>,2146 ) -> DispatchResultWithPostInfo;21472148 2149 2150 2151 2152 2153 2154 2155 fn transfer(2156 &self,2157 sender: T::CrossAccountId,2158 to: T::CrossAccountId,2159 token: TokenId,2160 amount: u128,2161 budget: &dyn Budget,2162 ) -> DispatchResultWithPostInfo;21632164 2165 2166 2167 2168 2169 2170 fn approve(2171 &self,2172 sender: T::CrossAccountId,2173 spender: T::CrossAccountId,2174 token: TokenId,2175 amount: u128,2176 ) -> DispatchResultWithPostInfo;21772178 2179 2180 2181 2182 2183 2184 2185 fn approve_from(2186 &self,2187 sender: T::CrossAccountId,2188 from: T::CrossAccountId,2189 to: T::CrossAccountId,2190 token: TokenId,2191 amount: u128,2192 ) -> DispatchResultWithPostInfo;21932194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 fn transfer_from(2205 &self,2206 sender: T::CrossAccountId,2207 from: T::CrossAccountId,2208 to: T::CrossAccountId,2209 token: TokenId,2210 amount: u128,2211 budget: &dyn Budget,2212 ) -> DispatchResultWithPostInfo;22132214 2215 2216 2217 2218 2219 2220 2221 2222 2223 fn burn_from(2224 &self,2225 sender: T::CrossAccountId,2226 from: T::CrossAccountId,2227 token: TokenId,2228 amount: u128,2229 budget: &dyn Budget,2230 ) -> DispatchResultWithPostInfo;22312232 2233 2234 2235 2236 2237 2238 fn check_nesting(2239 &self,2240 sender: T::CrossAccountId,2241 from: (CollectionId, TokenId),2242 under: TokenId,2243 budget: &dyn Budget,2244 ) -> DispatchResult;22452246 2247 2248 2249 2250 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22512252 2253 2254 2255 2256 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22572258 2259 2260 2261 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22622263 2264 fn collection_tokens(&self) -> Vec<TokenId>;22652266 2267 2268 2269 fn token_exists(&self, token: TokenId) -> bool;22702271 2272 fn last_token_id(&self) -> TokenId;22732274 2275 2276 2277 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22782279 2280 2281 2282 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22832284 2285 2286 2287 2288 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22892290 2291 2292 2293 2294 2295 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22962297 2298 fn total_supply(&self) -> u32;22992300 2301 2302 2303 fn account_balance(&self, account: T::CrossAccountId) -> u32;23042305 2306 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23072308 2309 fn total_pieces(&self, token: TokenId) -> Option<u128>;23102311 2312 2313 2314 2315 2316 fn allowance(2317 &self,2318 sender: T::CrossAccountId,2319 spender: T::CrossAccountId,2320 token: TokenId,2321 ) -> u128;23222323 2324 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23252326 2327 2328 2329 2330 fn set_allowance_for_all(2331 &self,2332 owner: T::CrossAccountId,2333 operator: T::CrossAccountId,2334 approve: bool,2335 ) -> DispatchResultWithPostInfo;23362337 2338 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23392340 2341 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2342}234323442345pub trait RefungibleExtensions<T>2346where2347 T: Config,2348{2349 2350 2351 2352 2353 2354 2355 2356 fn repartition(2357 &self,2358 sender: &T::CrossAccountId,2359 token: TokenId,2360 amount: u128,2361 ) -> DispatchResultWithPostInfo;2362}23632364236523662367pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2368 let post_info = PostDispatchInfo {2369 actual_weight: Some(weight),2370 pays_fee: Pays::Yes,2371 };2372 match res {2373 Ok(()) => Ok(post_info),2374 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2375 }2376}23772378impl<T: Config> From<PropertiesError> for Error<T> {2379 fn from(error: PropertiesError) -> Self {2380 match error {2381 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2382 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2383 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2384 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2385 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2386 }2387 }2388}23892390#[cfg(feature = "tests")]2391pub mod tests {2392 use crate::{DispatchResult, DispatchError, LazyValue, Config};23932394 const fn to_bool(u: u8) -> bool {2395 u != 02396 }23972398 #[derive(Debug)]2399 pub struct TestCase {2400 pub collection_admin: bool,2401 pub is_collection_admin: bool,2402 pub token_owner: bool,2403 pub is_token_owner: bool,2404 pub no_permission: bool,2405 }24062407 impl TestCase {2408 const fn new(2409 collection_admin: u8,2410 is_collection_admin: u8,2411 token_owner: u8,2412 is_token_owner: u8,2413 no_permission: u8,2414 ) -> Self {2415 Self {2416 collection_admin: to_bool(collection_admin),2417 is_collection_admin: to_bool(is_collection_admin),2418 token_owner: to_bool(token_owner),2419 is_token_owner: to_bool(is_token_owner),2420 no_permission: to_bool(no_permission),2421 }2422 }2423 }24242425 #[rustfmt::skip]2426 pub const table: [TestCase; 16] = [2427 2428 2429 2430 2431 2432 TestCase::new(0, 0, 0, 0, 1),2433 TestCase::new(0, 0, 0, 1, 1),2434 TestCase::new(0, 0, 1, 0, 1),2435 TestCase::new(0, 0, 1, 1, 0),2436 TestCase::new(0, 1, 0, 0, 1),2437 TestCase::new(0, 1, 0, 1, 1),2438 TestCase::new(0, 1, 1, 0, 1),2439 TestCase::new(0, 1, 1, 1, 0),2440 TestCase::new(1, 0, 0, 0, 1),2441 TestCase::new(1, 0, 0, 1, 1),2442 TestCase::new(1, 0, 1, 0, 1),2443 TestCase::new(1, 0, 1, 1, 0),2444 TestCase::new(1, 1, 0, 0, 0),2445 TestCase::new(1, 1, 0, 1, 0),2446 TestCase::new(1, 1, 1, 0, 0),2447 TestCase::new(1, 1, 1, 1, 0),2448 ];24492450 pub fn check_token_permissions<T, FCA, FTO, FTE>(2451 collection_admin_permitted: bool,2452 token_owner_permitted: bool,2453 is_collection_admin: &mut LazyValue<bool, FCA>,2454 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2455 check_token_existence: &mut LazyValue<bool, FTE>,2456 ) -> DispatchResult2457 where2458 T: Config,2459 FCA: FnOnce() -> bool,2460 FTO: FnOnce() -> Result<bool, DispatchError>,2461 FTE: FnOnce() -> bool,2462 {2463 crate::check_token_permissions::<T, FCA, FTO, FTE>(2464 collection_admin_permitted,2465 token_owner_permitted,2466 is_collection_admin,2467 check_token_ownership,2468 check_token_existence,2469 )2470 }2471}