1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField, PhantomType,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);196197 #[pallet::pallet]198 #[pallet::storage_version(STORAGE_VERSION)]199 #[pallet::generate_store(pub(super) trait Store)]200 pub struct Pallet<T>(_);201202 #[pallet::extra_constants]203 impl<T: Config> Pallet<T> {204 pub fn collection_admins_limit() -> u32 {205 COLLECTION_ADMINS_LIMIT206 }207 }208209 #[pallet::event]210 #[pallet::generate_deposit(pub fn deposit_event)]211 pub enum Event<T: Config> {212 213 214 215 216 217 218 219 220 221 CollectionCreated(CollectionId, u8, T::AccountId),222223 224 225 226 227 228 CollectionDestroyed(CollectionId),229230 231 232 233 234 235 236 237 238 239 240 241 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),242243 244 245 246 247 248 249 250 251 252 253 254 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),255256 257 258 259 260 261 262 263 264 265 266 267 Transfer(268 CollectionId,269 TokenId,270 T::CrossAccountId,271 T::CrossAccountId,272 u128,273 ),274275 276 277 278 279 280 281 282 283 284 Approved(285 CollectionId,286 TokenId,287 T::CrossAccountId,288 T::CrossAccountId,289 u128,290 ),291 }292293 #[pallet::error]294 pub enum Error<T> {295 296 CollectionNotFound,297 298 MustBeTokenOwner,299 300 NoPermission,301 302 PublicMintingNotAllowed,303 304 AddressNotInAllowlist,305306 307 CollectionNameLimitExceeded,308 309 CollectionDescriptionLimitExceeded,310 311 CollectionTokenPrefixLimitExceeded,312 313 TotalCollectionsLimitExceeded,314 315 TokenVariableDataLimitExceeded,316 317 CollectionAdminCountExceeded,318 319 CollectionLimitBoundsExceeded,320 321 OwnerPermissionsCantBeReverted,322323 324 TransferNotAllowed,325 326 AccountTokenLimitExceeded,327 328 CollectionTokenLimitExceeded,329 330 MetadataFlagFrozen,331332 333 TokenNotFound,334 335 TokenValueTooLow,336 337 ApprovedValueTooLow,338 339 CantApproveMoreThanOwned,340341 342 AddressIsZero,343 344 UnsupportedOperation,345346 347 NotSufficientFounds,348349 350 NestingIsDisabled,351 352 OnlyOwnerAllowedToNest,353 354 SourceCollectionIsNotAllowedToNest,355356 357 CollectionFieldSizeExceeded,358 }359360 #[pallet::storage]361 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;362 #[pallet::storage]363 pub type DestroyedCollectionCount<T> =364 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;365366 367 #[pallet::storage]368 pub type CollectionById<T> = StorageMap<369 Hasher = Blake2_128Concat,370 Key = CollectionId,371 Value = Collection<<T as frame_system::Config>::AccountId>,372 QueryKind = OptionQuery,373 >;374375 376 #[pallet::storage]377 pub type CollectionData<T> = StorageNMap<378 Key = (379 Key<Twox64Concat, CollectionId>,380 Key<Twox64Concat, CollectionField>,381 ),382 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,383 QueryKind = ValueQuery,384 >;385386 #[pallet::storage]387 pub type AdminAmount<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = u32,391 QueryKind = ValueQuery,392 >;393394 395 #[pallet::storage]396 pub type IsAdmin<T: Config> = StorageNMap<397 Key = (398 Key<Blake2_128Concat, CollectionId>,399 Key<Blake2_128Concat, T::CrossAccountId>,400 ),401 Value = bool,402 QueryKind = ValueQuery,403 >;404405 406 #[pallet::storage]407 pub type Allowlist<T: Config> = StorageNMap<408 Key = (409 Key<Blake2_128Concat, CollectionId>,410 Key<Blake2_128Concat, T::CrossAccountId>,411 ),412 Value = bool,413 QueryKind = ValueQuery,414 >;415416 417 #[pallet::storage]418 pub type DummyStorageValue<T: Config> = StorageValue<419 Value = (420 CollectionStats,421 CollectionId,422 TokenId,423 PhantomType<RpcCollection<T::AccountId>>,424 ),425 QueryKind = OptionQuery,426 >;427428 #[pallet::hooks]429 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {430 fn on_runtime_upgrade() -> Weight {431 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {432 use up_data_structs::{CollectionVersion1, CollectionVersion2};433 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {434 Self::set_field_raw(435 id,436 CollectionField::OffchainSchema,437 v.offchain_schema.clone().into_inner(),438 )439 .expect("data has lower bounds than field");440 Self::set_field_raw(441 id,442 CollectionField::VariableOnChainSchema,443 v.variable_on_chain_schema.clone().into_inner(),444 )445 .expect("data has lower bounds than field");446 Self::set_field_raw(447 id,448 CollectionField::ConstOnChainSchema,449 v.const_on_chain_schema.clone().into_inner(),450 )451 .expect("data has lower bounds than field");452453 Some(CollectionVersion2::from(v))454 });455 }456457 0458 }459 }460}461462impl<T: Config> Pallet<T> {463 464 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {465 ensure!(466 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,467 <Error<T>>::AddressIsZero468 );469 Ok(())470 }471 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {472 <IsAdmin<T>>::iter_prefix((collection,))473 .map(|(a, _)| a)474 .collect()475 }476 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {477 <Allowlist<T>>::iter_prefix((collection,))478 .map(|(a, _)| a)479 .collect()480 }481 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {482 <Allowlist<T>>::get((collection, user))483 }484 pub fn collection_stats() -> CollectionStats {485 let created = <CreatedCollectionCount<T>>::get();486 let destroyed = <DestroyedCollectionCount<T>>::get();487 CollectionStats {488 created: created.0,489 destroyed: destroyed.0,490 alive: created.0 - destroyed.0,491 }492 }493494 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {495 let collection = <CollectionById<T>>::get(collection);496 if collection.is_none() {497 return None;498 }499500 let collection = collection.unwrap();501 let limits = collection.limits;502 let effective_limits = CollectionLimits {503 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),504 sponsored_data_size: Some(limits.sponsored_data_size()),505 sponsored_data_rate_limit: Some(506 limits507 .sponsored_data_rate_limit508 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),509 ),510 token_limit: Some(limits.token_limit()),511 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(512 match collection.mode {513 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,514 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,515 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,516 },517 )),518 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),519 owner_can_transfer: Some(limits.owner_can_transfer()),520 owner_can_destroy: Some(limits.owner_can_destroy()),521 transfers_enabled: Some(limits.transfers_enabled()),522 };523524 Some(effective_limits)525 }526527 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {528 let Collection {529 name,530 description,531 owner,532 mode,533 access,534 token_prefix,535 mint_mode,536 schema_version,537 sponsorship,538 limits,539 meta_update_permission,540 } = <CollectionById<T>>::get(collection)?;541 Some(RpcCollection {542 name: name.into_inner(),543 description: description.into_inner(),544 owner,545 mode,546 access,547 token_prefix: token_prefix.into_inner(),548 mint_mode,549 schema_version,550 sponsorship,551 limits,552 meta_update_permission,553 offchain_schema: <CollectionData<T>>::get((554 collection,555 CollectionField::OffchainSchema,556 ))557 .into_inner(),558 const_on_chain_schema: <CollectionData<T>>::get((559 collection,560 CollectionField::ConstOnChainSchema,561 ))562 .into_inner(),563 variable_on_chain_schema: <CollectionData<T>>::get((564 collection,565 CollectionField::VariableOnChainSchema,566 ))567 .into_inner(),568 })569 }570}571572impl<T: Config> Pallet<T> {573 pub fn init_collection(574 owner: T::AccountId,575 data: CreateCollectionData<T::AccountId>,576 ) -> Result<CollectionId, DispatchError> {577 {578 ensure!(579 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,580 Error::<T>::CollectionTokenPrefixLimitExceeded581 );582 }583584 let created_count = <CreatedCollectionCount<T>>::get()585 .0586 .checked_add(1)587 .ok_or(ArithmeticError::Overflow)?;588 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;589 let id = CollectionId(created_count);590591 592 ensure!(593 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,594 <Error<T>>::TotalCollectionsLimitExceeded595 );596597 598599 let collection = Collection {600 owner: owner.clone(),601 name: data.name,602 mode: data.mode.clone(),603 mint_mode: false,604 access: data.access.unwrap_or_default(),605 description: data.description,606 token_prefix: data.token_prefix,607 schema_version: data.schema_version.unwrap_or_default(),608 sponsorship: data609 .pending_sponsor610 .map(SponsorshipState::Unconfirmed)611 .unwrap_or_default(),612 limits: data613 .limits614 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))615 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,616 meta_update_permission: data.meta_update_permission.unwrap_or_default(),617 };618619 620 {621 let mut imbalance =622 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();623 imbalance.subsume(624 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(625 &T::TreasuryAccountId::get(),626 T::CollectionCreationPrice::get(),627 ),628 );629 <T as Config>::Currency::settle(630 &owner,631 imbalance,632 WithdrawReasons::TRANSFER,633 ExistenceRequirement::KeepAlive,634 )635 .map_err(|_| Error::<T>::NotSufficientFounds)?;636 }637638 <CreatedCollectionCount<T>>::put(created_count);639 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));640 <CollectionById<T>>::insert(id, collection);641 Self::set_field_raw(642 id,643 CollectionField::OffchainSchema,644 data.offchain_schema.into_inner(),645 )646 .expect("data has lower bounds than field");647 Self::set_field_raw(648 id,649 CollectionField::VariableOnChainSchema,650 data.variable_on_chain_schema.into_inner(),651 )652 .expect("data has lower bounds than field");653 Self::set_field_raw(654 id,655 CollectionField::ConstOnChainSchema,656 data.const_on_chain_schema.into_inner(),657 )658 .expect("data has lower bounds than field");659 Ok(id)660 }661662 pub fn destroy_collection(663 collection: CollectionHandle<T>,664 sender: &T::CrossAccountId,665 ) -> DispatchResult {666 ensure!(667 collection.limits.owner_can_destroy(),668 <Error<T>>::NoPermission,669 );670 collection.check_is_owner(sender)?;671672 let destroyed_collections = <DestroyedCollectionCount<T>>::get()673 .0674 .checked_add(1)675 .ok_or(ArithmeticError::Overflow)?;676677 678679 <DestroyedCollectionCount<T>>::put(destroyed_collections);680 <CollectionById<T>>::remove(collection.id);681 <CollectionData<T>>::remove_prefix((collection.id,), None);682 <AdminAmount<T>>::remove(collection.id);683 <IsAdmin<T>>::remove_prefix((collection.id,), None);684 <Allowlist<T>>::remove_prefix((collection.id,), None);685686 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));687 Ok(())688 }689690 fn set_field_raw(691 collection_id: CollectionId,692 field: CollectionField,693 value: Vec<u8>,694 ) -> DispatchResult {695 if !value.is_empty() {696 <CollectionData<T>>::insert(697 (collection_id, field),698 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,699 )700 } else {701 <CollectionData<T>>::remove((collection_id, field));702 }703 Ok(())704 }705706 pub fn set_field(707 collection: &CollectionHandle<T>,708 sender: &T::CrossAccountId,709 field: CollectionField,710 value: Vec<u8>,711 ) -> DispatchResult {712 collection.check_is_owner_or_admin(sender)?;713714 715716 Self::set_field_raw(collection.id, field, value)717 }718719 pub fn toggle_allowlist(720 collection: &CollectionHandle<T>,721 sender: &T::CrossAccountId,722 user: &T::CrossAccountId,723 allowed: bool,724 ) -> DispatchResult {725 collection.check_is_owner_or_admin(sender)?;726727 728729 if allowed {730 <Allowlist<T>>::insert((collection.id, user), true);731 } else {732 <Allowlist<T>>::remove((collection.id, user));733 }734735 Ok(())736 }737738 pub fn toggle_admin(739 collection: &CollectionHandle<T>,740 sender: &T::CrossAccountId,741 user: &T::CrossAccountId,742 admin: bool,743 ) -> DispatchResult {744 collection.check_is_owner_or_admin(sender)?;745746 let was_admin = <IsAdmin<T>>::get((collection.id, user));747 if was_admin == admin {748 return Ok(());749 }750 let amount = <AdminAmount<T>>::get(collection.id);751752 if admin {753 let amount = amount754 .checked_add(1)755 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;756 ensure!(757 amount <= Self::collection_admins_limit(),758 <Error<T>>::CollectionAdminCountExceeded,759 );760761 762763 <AdminAmount<T>>::insert(collection.id, amount);764 <IsAdmin<T>>::insert((collection.id, user), true);765 } else {766 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));767 <IsAdmin<T>>::remove((collection.id, user));768 }769770 Ok(())771 }772773 pub fn clamp_limits(774 mode: CollectionMode,775 old_limit: &CollectionLimits,776 mut new_limit: CollectionLimits,777 ) -> Result<CollectionLimits, DispatchError> {778 macro_rules! limit_default {779 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{780 $(781 if let Some($new) = $new.$field {782 let $old = $old.$field($($arg)?);783 let _ = $new;784 let _ = $old;785 $check786 } else {787 $new.$field = $old.$field788 }789 )*790 }};791 }792793 limit_default!(old_limit, new_limit,794 account_token_ownership_limit => ensure!(795 new_limit <= MAX_TOKEN_OWNERSHIP,796 <Error<T>>::CollectionLimitBoundsExceeded,797 ),798 sponsor_transfer_timeout(match mode {799 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,800 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,801 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,802 }) => ensure!(803 new_limit <= MAX_SPONSOR_TIMEOUT,804 <Error<T>>::CollectionLimitBoundsExceeded,805 ),806 sponsored_data_size => ensure!(807 new_limit <= CUSTOM_DATA_LIMIT,808 <Error<T>>::CollectionLimitBoundsExceeded,809 ),810 token_limit => ensure!(811 old_limit >= new_limit && new_limit > 0,812 <Error<T>>::CollectionTokenLimitExceeded813 ),814 owner_can_transfer => ensure!(815 old_limit || !new_limit,816 <Error<T>>::OwnerPermissionsCantBeReverted,817 ),818 owner_can_destroy => ensure!(819 old_limit || !new_limit,820 <Error<T>>::OwnerPermissionsCantBeReverted,821 ),822 sponsored_data_rate_limit => {},823 transfers_enabled => {},824 );825 Ok(new_limit)826 }827}828829#[macro_export]830macro_rules! unsupported {831 () => {832 Err(<Error<T>>::UnsupportedOperation.into())833 };834}835836837pub trait CommonWeightInfo<CrossAccountId> {838 fn create_item() -> Weight;839 fn create_multiple_items(amount: u32) -> Weight;840 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;841 fn burn_item() -> Weight;842 fn transfer() -> Weight;843 fn approve() -> Weight;844 fn transfer_from() -> Weight;845 fn burn_from() -> Weight;846 fn set_variable_metadata(bytes: u32) -> Weight;847}848849pub trait CommonCollectionOperations<T: Config> {850 fn create_item(851 &self,852 sender: T::CrossAccountId,853 to: T::CrossAccountId,854 data: CreateItemData,855 nesting_budget: &dyn Budget,856 ) -> DispatchResultWithPostInfo;857 fn create_multiple_items(858 &self,859 sender: T::CrossAccountId,860 to: T::CrossAccountId,861 data: Vec<CreateItemData>,862 nesting_budget: &dyn Budget,863 ) -> DispatchResultWithPostInfo;864 fn create_multiple_items_ex(865 &self,866 sender: T::CrossAccountId,867 data: CreateItemExData<T::CrossAccountId>,868 nesting_budget: &dyn Budget,869 ) -> DispatchResultWithPostInfo;870 fn burn_item(871 &self,872 sender: T::CrossAccountId,873 token: TokenId,874 amount: u128,875 ) -> DispatchResultWithPostInfo;876877 fn transfer(878 &self,879 sender: T::CrossAccountId,880 to: T::CrossAccountId,881 token: TokenId,882 amount: u128,883 nesting_budget: &dyn Budget,884 ) -> DispatchResultWithPostInfo;885 fn approve(886 &self,887 sender: T::CrossAccountId,888 spender: T::CrossAccountId,889 token: TokenId,890 amount: u128,891 ) -> DispatchResultWithPostInfo;892 fn transfer_from(893 &self,894 sender: T::CrossAccountId,895 from: T::CrossAccountId,896 to: T::CrossAccountId,897 token: TokenId,898 amount: u128,899 nesting_budget: &dyn Budget,900 ) -> DispatchResultWithPostInfo;901 fn burn_from(902 &self,903 sender: T::CrossAccountId,904 from: T::CrossAccountId,905 token: TokenId,906 amount: u128,907 nesting_budget: &dyn Budget,908 ) -> DispatchResultWithPostInfo;909910 fn set_variable_metadata(911 &self,912 sender: T::CrossAccountId,913 token: TokenId,914 data: BoundedVec<u8, CustomDataLimit>,915 ) -> DispatchResultWithPostInfo;916917 fn check_nesting(918 &self,919 sender: T::CrossAccountId,920 from: CollectionId,921 under: TokenId,922 budget: &dyn Budget,923 ) -> DispatchResult;924925 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;926 fn token_exists(&self, token: TokenId) -> bool;927 fn last_token_id(&self) -> TokenId;928929 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;930 fn const_metadata(&self, token: TokenId) -> Vec<u8>;931 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;932933 934 fn collection_tokens(&self) -> u32;935 936 fn account_balance(&self, account: T::CrossAccountId) -> u32;937 938 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;939 fn allowance(940 &self,941 sender: T::CrossAccountId,942 spender: T::CrossAccountId,943 token: TokenId,944 ) -> u128;945}946947948pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {949 let post_info = PostDispatchInfo {950 actual_weight: Some(weight),951 pays_fee: Pays::Yes,952 };953 match res {954 Ok(()) => Ok(post_info),955 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),956 }957}