difftreelog
CORE-238 cargo fmt
in: master
2 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -123,7 +123,7 @@
fn effective_collection_limits(
&self,
collection_id: CollectionId,
- at: Option<BlockHash>
+ at: Option<BlockHash>,
) -> Result<Option<CollectionLimits>>;
}
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency},27 BoundedVec,28};29use pallet_evm::GasWeightMapping;30use up_data_structs::{31 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,32 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,33 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,34 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, SponsoringRateLimit37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41pub mod account;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;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 account::CrossAccountId;167 use frame_support::traits::Currency;168 use up_data_structs::TokenId;169 use scale_info::TypeInfo;170171 #[pallet::config]172 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {173 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;174175 type CrossAccountId: CrossAccountId<Self::AccountId>;176177 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;178 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;179180 type Currency: Currency<Self::AccountId>;181182 #[pallet::constant]183 type CollectionCreationPrice: Get<184 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,185 >;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 /// New collection was created205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique identifier of newly created collection.209 ///210 /// * mode: [CollectionMode] converted into u8.211 ///212 /// * account_id: Collection owner.213 CollectionCreated(CollectionId, u8, T::AccountId),214215 /// New collection was destroyed216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique identifier of collection.220 CollectionDestroyed(CollectionId),221222 /// New item was created.223 ///224 /// # Arguments225 ///226 /// * collection_id: Id of the collection where item was created.227 ///228 /// * item_id: Id of an item. Unique within the collection.229 ///230 /// * recipient: Owner of newly created item231 ///232 /// * amount: Always 1 for NFT233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 /// Collection item was burned.236 ///237 /// # Arguments238 ///239 /// * collection_id.240 ///241 /// * item_id: Identifier of burned NFT.242 ///243 /// * owner: which user has destroyed its tokens244 ///245 /// * amount: Always 1 for NFT246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 /// Item was transferred249 ///250 /// * collection_id: Id of collection to which item is belong251 ///252 /// * item_id: Id of an item253 ///254 /// * sender: Original owner of item255 ///256 /// * recipient: New owner of item257 ///258 /// * amount: Always 1 for NFT259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 /// * collection_id268 ///269 /// * item_id270 ///271 /// * sender272 ///273 /// * spender274 ///275 /// * amount276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 /// This collection does not exist.288 CollectionNotFound,289 /// Sender parameter and item owner must be equal.290 MustBeTokenOwner,291 /// No permission to perform action292 NoPermission,293 /// Collection is not in mint mode.294 PublicMintingNotAllowed,295 /// Address is not in allow list.296 AddressNotInAllowlist,297298 /// Collection name can not be longer than 63 char.299 CollectionNameLimitExceeded,300 /// Collection description can not be longer than 255 char.301 CollectionDescriptionLimitExceeded,302 /// Token prefix can not be longer than 15 char.303 CollectionTokenPrefixLimitExceeded,304 /// Total collections bound exceeded.305 TotalCollectionsLimitExceeded,306 /// variable_data exceeded data limit.307 TokenVariableDataLimitExceeded,308 /// Exceeded max admin count309 CollectionAdminCountExceeded,310 /// Collection limit bounds per collection exceeded311 CollectionLimitBoundsExceeded,312 /// Tried to enable permissions which are only permitted to be disabled313 OwnerPermissionsCantBeReverted,314315 /// Collection settings not allowing items transferring316 TransferNotAllowed,317 /// Account token limit exceeded per collection318 AccountTokenLimitExceeded,319 /// Collection token limit exceeded320 CollectionTokenLimitExceeded,321 /// Metadata flag frozen322 MetadataFlagFrozen,323324 /// Item not exists.325 TokenNotFound,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Requested value more than approved.329 ApprovedValueTooLow,330 /// Tried to approve more than owned331 CantApproveMoreThanOwned,332333 /// Can't transfer tokens to ethereum zero address334 AddressIsZero,335 /// Target collection doesn't supports this operation336 UnsupportedOperation,337338 /// Not sufficient founds to perform action339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 /// Collection info349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 /// List of collection admins366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 /// Allowlisted collection users377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 /// Not used by code, exists only to provide some types to metadata388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424425 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {426 let collection = <CollectionById<T>>::get(collection);427 if collection.is_none() {428 return None;429 }430 431 let limits = collection.unwrap().limits;432 let effective_limits = CollectionLimits {433 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),434 sponsored_data_size: Some(limits.sponsored_data_size()),435 sponsored_data_rate_limit: Some(436 limits.sponsored_data_rate_limit437 .unwrap_or(SponsoringRateLimit::SponsoringDisabled)),438 token_limit: Some(limits.token_limit()),439 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(MAX_SPONSOR_TIMEOUT)),440 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),441 owner_can_transfer: Some(limits.owner_can_transfer()),442 owner_can_destroy: Some(limits.owner_can_destroy()),443 transfers_enabled: Some(limits.transfers_enabled()),444 };445446 Some(effective_limits)447 }448}449450impl<T: Config> Pallet<T> {451 pub fn init_collection(452 owner: T::AccountId,453 data: CreateCollectionData<T::AccountId>,454 ) -> Result<CollectionId, DispatchError> {455 {456 ensure!(457 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,458 Error::<T>::CollectionTokenPrefixLimitExceeded459 );460 }461462 let created_count = <CreatedCollectionCount<T>>::get()463 .0464 .checked_add(1)465 .ok_or(ArithmeticError::Overflow)?;466 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;467 let id = CollectionId(created_count);468469 // bound Total number of collections470 ensure!(471 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,472 <Error<T>>::TotalCollectionsLimitExceeded473 );474475 // =========476477 let collection = Collection {478 owner: owner.clone(),479 name: data.name,480 mode: data.mode.clone(),481 mint_mode: false,482 access: data.access.unwrap_or_default(),483 description: data.description,484 token_prefix: data.token_prefix,485 offchain_schema: data.offchain_schema,486 schema_version: data.schema_version.unwrap_or_default(),487 sponsorship: data488 .pending_sponsor489 .map(SponsorshipState::Unconfirmed)490 .unwrap_or_default(),491 variable_on_chain_schema: data.variable_on_chain_schema,492 const_on_chain_schema: data.const_on_chain_schema,493 limits: data494 .limits495 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))496 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,497 meta_update_permission: data.meta_update_permission.unwrap_or_default(),498 };499500 // Take a (non-refundable) deposit of collection creation501 {502 let mut imbalance =503 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();504 imbalance.subsume(505 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(506 &T::TreasuryAccountId::get(),507 T::CollectionCreationPrice::get(),508 ),509 );510 <T as Config>::Currency::settle(511 &owner,512 imbalance,513 WithdrawReasons::TRANSFER,514 ExistenceRequirement::KeepAlive,515 )516 .map_err(|_| Error::<T>::NotSufficientFounds)?;517 }518519 <CreatedCollectionCount<T>>::put(created_count);520 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));521 <CollectionById<T>>::insert(id, collection);522 Ok(id)523 }524525 pub fn destroy_collection(526 collection: CollectionHandle<T>,527 sender: &T::CrossAccountId,528 ) -> DispatchResult {529 ensure!(530 collection.limits.owner_can_destroy(),531 <Error<T>>::NoPermission,532 );533 collection.check_is_owner(sender)?;534535 let destroyed_collections = <DestroyedCollectionCount<T>>::get()536 .0537 .checked_add(1)538 .ok_or(ArithmeticError::Overflow)?;539540 // =========541542 <DestroyedCollectionCount<T>>::put(destroyed_collections);543 <CollectionById<T>>::remove(collection.id);544 <AdminAmount<T>>::remove(collection.id);545 <IsAdmin<T>>::remove_prefix((collection.id,), None);546 <Allowlist<T>>::remove_prefix((collection.id,), None);547548 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));549 Ok(())550 }551552 pub fn toggle_allowlist(553 collection: &CollectionHandle<T>,554 sender: &T::CrossAccountId,555 user: &T::CrossAccountId,556 allowed: bool,557 ) -> DispatchResult {558 collection.check_is_owner_or_admin(sender)?;559560 // =========561562 if allowed {563 <Allowlist<T>>::insert((collection.id, user), true);564 } else {565 <Allowlist<T>>::remove((collection.id, user));566 }567568 Ok(())569 }570571 pub fn toggle_admin(572 collection: &CollectionHandle<T>,573 sender: &T::CrossAccountId,574 user: &T::CrossAccountId,575 admin: bool,576 ) -> DispatchResult {577 collection.check_is_owner_or_admin(sender)?;578579 let was_admin = <IsAdmin<T>>::get((collection.id, user));580 if was_admin == admin {581 return Ok(());582 }583 let amount = <AdminAmount<T>>::get(collection.id);584585 if admin {586 let amount = amount587 .checked_add(1)588 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;589 ensure!(590 amount <= Self::collection_admins_limit(),591 <Error<T>>::CollectionAdminCountExceeded,592 );593594 // =========595596 <AdminAmount<T>>::insert(collection.id, amount);597 <IsAdmin<T>>::insert((collection.id, user), true);598 } else {599 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));600 <IsAdmin<T>>::remove((collection.id, user));601 }602603 Ok(())604 }605606 pub fn clamp_limits(607 mode: CollectionMode,608 old_limit: &CollectionLimits,609 mut new_limit: CollectionLimits,610 ) -> Result<CollectionLimits, DispatchError> {611 macro_rules! limit_default {612 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{613 $(614 if let Some($new) = $new.$field {615 let $old = $old.$field($($arg)?);616 let _ = $new;617 let _ = $old;618 $check619 } else {620 $new.$field = $old.$field621 }622 )*623 }};624 }625626 limit_default!(old_limit, new_limit,627 account_token_ownership_limit => ensure!(628 new_limit <= MAX_TOKEN_OWNERSHIP,629 <Error<T>>::CollectionLimitBoundsExceeded,630 ),631 sponsor_transfer_timeout(match mode {632 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,633 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,634 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,635 }) => ensure!(636 new_limit <= MAX_SPONSOR_TIMEOUT,637 <Error<T>>::CollectionLimitBoundsExceeded,638 ),639 sponsored_data_size => ensure!(640 new_limit <= CUSTOM_DATA_LIMIT,641 <Error<T>>::CollectionLimitBoundsExceeded,642 ),643 token_limit => ensure!(644 old_limit >= new_limit && new_limit > 0,645 <Error<T>>::CollectionTokenLimitExceeded646 ),647 owner_can_transfer => ensure!(648 old_limit || !new_limit,649 <Error<T>>::OwnerPermissionsCantBeReverted,650 ),651 owner_can_destroy => ensure!(652 old_limit || !new_limit,653 <Error<T>>::OwnerPermissionsCantBeReverted,654 ),655 sponsored_data_rate_limit => {},656 transfers_enabled => {},657 );658 Ok(new_limit)659 }660}661662#[macro_export]663macro_rules! unsupported {664 () => {665 Err(<Error<T>>::UnsupportedOperation.into())666 };667}668669/// Worst cases670pub trait CommonWeightInfo<CrossAccountId> {671 fn create_item() -> Weight;672 fn create_multiple_items(amount: u32) -> Weight;673 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;674 fn burn_item() -> Weight;675 fn transfer() -> Weight;676 fn approve() -> Weight;677 fn transfer_from() -> Weight;678 fn burn_from() -> Weight;679 fn set_variable_metadata(bytes: u32) -> Weight;680}681682pub trait CommonCollectionOperations<T: Config> {683 fn create_item(684 &self,685 sender: T::CrossAccountId,686 to: T::CrossAccountId,687 data: CreateItemData,688 ) -> DispatchResultWithPostInfo;689 fn create_multiple_items(690 &self,691 sender: T::CrossAccountId,692 to: T::CrossAccountId,693 data: Vec<CreateItemData>,694 ) -> DispatchResultWithPostInfo;695 fn create_multiple_items_ex(696 &self,697 sender: T::CrossAccountId,698 data: CreateItemExData<T::CrossAccountId>,699 ) -> DispatchResultWithPostInfo;700 fn burn_item(701 &self,702 sender: T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResultWithPostInfo;706707 fn transfer(708 &self,709 sender: T::CrossAccountId,710 to: T::CrossAccountId,711 token: TokenId,712 amount: u128,713 ) -> DispatchResultWithPostInfo;714 fn approve(715 &self,716 sender: T::CrossAccountId,717 spender: T::CrossAccountId,718 token: TokenId,719 amount: u128,720 ) -> DispatchResultWithPostInfo;721 fn transfer_from(722 &self,723 sender: T::CrossAccountId,724 from: T::CrossAccountId,725 to: T::CrossAccountId,726 token: TokenId,727 amount: u128,728 ) -> DispatchResultWithPostInfo;729 fn burn_from(730 &self,731 sender: T::CrossAccountId,732 from: T::CrossAccountId,733 token: TokenId,734 amount: u128,735 ) -> DispatchResultWithPostInfo;736737 fn set_variable_metadata(738 &self,739 sender: T::CrossAccountId,740 token: TokenId,741 data: BoundedVec<u8, CustomDataLimit>,742 ) -> DispatchResultWithPostInfo;743744 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;745 fn token_exists(&self, token: TokenId) -> bool;746 fn last_token_id(&self) -> TokenId;747748 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;749 fn const_metadata(&self, token: TokenId) -> Vec<u8>;750 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;751752 /// How many tokens collection contains (Applicable to nonfungible/refungible)753 fn collection_tokens(&self) -> u32;754 /// Amount of different tokens account has (Applicable to nonfungible/refungible)755 fn account_balance(&self, account: T::CrossAccountId) -> u32;756 /// Amount of specific token account have (Applicable to fungible/refungible)757 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;758 fn allowance(759 &self,760 sender: T::CrossAccountId,761 spender: T::CrossAccountId,762 token: TokenId,763 ) -> u128;764}765766// Flexible enough for implementing CommonCollectionOperations767pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {768 let post_info = PostDispatchInfo {769 actual_weight: Some(weight),770 pays_fee: Pays::Yes,771 };772 match res {773 Ok(()) => Ok(post_info),774 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),775 }776}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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 account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency},27 BoundedVec,28};29use pallet_evm::GasWeightMapping;30use up_data_structs::{31 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,32 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,33 TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,34 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,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41pub mod account;42#[cfg(feature = "runtime-benchmarks")]43pub mod benchmarking;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 account::CrossAccountId;167 use frame_support::traits::Currency;168 use up_data_structs::TokenId;169 use scale_info::TypeInfo;170171 #[pallet::config]172 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {173 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;174175 type CrossAccountId: CrossAccountId<Self::AccountId>;176177 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;178 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;179180 type Currency: Currency<Self::AccountId>;181182 #[pallet::constant]183 type CollectionCreationPrice: Get<184 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,185 >;186187 type TreasuryAccountId: Get<Self::AccountId>;188 }189190 #[pallet::pallet]191 #[pallet::generate_store(pub(super) trait Store)]192 pub struct Pallet<T>(_);193194 #[pallet::extra_constants]195 impl<T: Config> Pallet<T> {196 pub fn collection_admins_limit() -> u32 {197 COLLECTION_ADMINS_LIMIT198 }199 }200201 #[pallet::event]202 #[pallet::generate_deposit(pub fn deposit_event)]203 pub enum Event<T: Config> {204 /// New collection was created205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique identifier of newly created collection.209 ///210 /// * mode: [CollectionMode] converted into u8.211 ///212 /// * account_id: Collection owner.213 CollectionCreated(CollectionId, u8, T::AccountId),214215 /// New collection was destroyed216 ///217 /// # Arguments218 ///219 /// * collection_id: Globally unique identifier of collection.220 CollectionDestroyed(CollectionId),221222 /// New item was created.223 ///224 /// # Arguments225 ///226 /// * collection_id: Id of the collection where item was created.227 ///228 /// * item_id: Id of an item. Unique within the collection.229 ///230 /// * recipient: Owner of newly created item231 ///232 /// * amount: Always 1 for NFT233 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),234235 /// Collection item was burned.236 ///237 /// # Arguments238 ///239 /// * collection_id.240 ///241 /// * item_id: Identifier of burned NFT.242 ///243 /// * owner: which user has destroyed its tokens244 ///245 /// * amount: Always 1 for NFT246 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),247248 /// Item was transferred249 ///250 /// * collection_id: Id of collection to which item is belong251 ///252 /// * item_id: Id of an item253 ///254 /// * sender: Original owner of item255 ///256 /// * recipient: New owner of item257 ///258 /// * amount: Always 1 for NFT259 Transfer(260 CollectionId,261 TokenId,262 T::CrossAccountId,263 T::CrossAccountId,264 u128,265 ),266267 /// * collection_id268 ///269 /// * item_id270 ///271 /// * sender272 ///273 /// * spender274 ///275 /// * amount276 Approved(277 CollectionId,278 TokenId,279 T::CrossAccountId,280 T::CrossAccountId,281 u128,282 ),283 }284285 #[pallet::error]286 pub enum Error<T> {287 /// This collection does not exist.288 CollectionNotFound,289 /// Sender parameter and item owner must be equal.290 MustBeTokenOwner,291 /// No permission to perform action292 NoPermission,293 /// Collection is not in mint mode.294 PublicMintingNotAllowed,295 /// Address is not in allow list.296 AddressNotInAllowlist,297298 /// Collection name can not be longer than 63 char.299 CollectionNameLimitExceeded,300 /// Collection description can not be longer than 255 char.301 CollectionDescriptionLimitExceeded,302 /// Token prefix can not be longer than 15 char.303 CollectionTokenPrefixLimitExceeded,304 /// Total collections bound exceeded.305 TotalCollectionsLimitExceeded,306 /// variable_data exceeded data limit.307 TokenVariableDataLimitExceeded,308 /// Exceeded max admin count309 CollectionAdminCountExceeded,310 /// Collection limit bounds per collection exceeded311 CollectionLimitBoundsExceeded,312 /// Tried to enable permissions which are only permitted to be disabled313 OwnerPermissionsCantBeReverted,314315 /// Collection settings not allowing items transferring316 TransferNotAllowed,317 /// Account token limit exceeded per collection318 AccountTokenLimitExceeded,319 /// Collection token limit exceeded320 CollectionTokenLimitExceeded,321 /// Metadata flag frozen322 MetadataFlagFrozen,323324 /// Item not exists.325 TokenNotFound,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Requested value more than approved.329 ApprovedValueTooLow,330 /// Tried to approve more than owned331 CantApproveMoreThanOwned,332333 /// Can't transfer tokens to ethereum zero address334 AddressIsZero,335 /// Target collection doesn't supports this operation336 UnsupportedOperation,337338 /// Not sufficient founds to perform action339 NotSufficientFounds,340 }341342 #[pallet::storage]343 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;344 #[pallet::storage]345 pub type DestroyedCollectionCount<T> =346 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;347348 /// Collection info349 #[pallet::storage]350 pub type CollectionById<T> = StorageMap<351 Hasher = Blake2_128Concat,352 Key = CollectionId,353 Value = Collection<<T as frame_system::Config>::AccountId>,354 QueryKind = OptionQuery,355 >;356357 #[pallet::storage]358 pub type AdminAmount<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = u32,362 QueryKind = ValueQuery,363 >;364365 /// List of collection admins366 #[pallet::storage]367 pub type IsAdmin<T: Config> = StorageNMap<368 Key = (369 Key<Blake2_128Concat, CollectionId>,370 Key<Blake2_128Concat, T::CrossAccountId>,371 ),372 Value = bool,373 QueryKind = ValueQuery,374 >;375376 /// Allowlisted collection users377 #[pallet::storage]378 pub type Allowlist<T: Config> = StorageNMap<379 Key = (380 Key<Blake2_128Concat, CollectionId>,381 Key<Blake2_128Concat, T::CrossAccountId>,382 ),383 Value = bool,384 QueryKind = ValueQuery,385 >;386387 /// Not used by code, exists only to provide some types to metadata388 #[pallet::storage]389 pub type DummyStorageValue<T> =390 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;391}392393impl<T: Config> Pallet<T> {394 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens395 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {396 ensure!(397 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,398 <Error<T>>::AddressIsZero399 );400 Ok(())401 }402 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {403 <IsAdmin<T>>::iter_prefix((collection,))404 .map(|(a, _)| a)405 .collect()406 }407 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {408 <Allowlist<T>>::iter_prefix((collection,))409 .map(|(a, _)| a)410 .collect()411 }412 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {413 <Allowlist<T>>::get((collection, user))414 }415 pub fn collection_stats() -> CollectionStats {416 let created = <CreatedCollectionCount<T>>::get();417 let destroyed = <DestroyedCollectionCount<T>>::get();418 CollectionStats {419 created: created.0,420 destroyed: destroyed.0,421 alive: created.0 - destroyed.0,422 }423 }424425 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {426 let collection = <CollectionById<T>>::get(collection);427 if collection.is_none() {428 return None;429 }430431 let limits = collection.unwrap().limits;432 let effective_limits = CollectionLimits {433 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),434 sponsored_data_size: Some(limits.sponsored_data_size()),435 sponsored_data_rate_limit: Some(436 limits437 .sponsored_data_rate_limit438 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),439 ),440 token_limit: Some(limits.token_limit()),441 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(MAX_SPONSOR_TIMEOUT)),442 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),443 owner_can_transfer: Some(limits.owner_can_transfer()),444 owner_can_destroy: Some(limits.owner_can_destroy()),445 transfers_enabled: Some(limits.transfers_enabled()),446 };447448 Some(effective_limits)449 }450}451452impl<T: Config> Pallet<T> {453 pub fn init_collection(454 owner: T::AccountId,455 data: CreateCollectionData<T::AccountId>,456 ) -> Result<CollectionId, DispatchError> {457 {458 ensure!(459 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,460 Error::<T>::CollectionTokenPrefixLimitExceeded461 );462 }463464 let created_count = <CreatedCollectionCount<T>>::get()465 .0466 .checked_add(1)467 .ok_or(ArithmeticError::Overflow)?;468 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;469 let id = CollectionId(created_count);470471 // bound Total number of collections472 ensure!(473 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,474 <Error<T>>::TotalCollectionsLimitExceeded475 );476477 // =========478479 let collection = Collection {480 owner: owner.clone(),481 name: data.name,482 mode: data.mode.clone(),483 mint_mode: false,484 access: data.access.unwrap_or_default(),485 description: data.description,486 token_prefix: data.token_prefix,487 offchain_schema: data.offchain_schema,488 schema_version: data.schema_version.unwrap_or_default(),489 sponsorship: data490 .pending_sponsor491 .map(SponsorshipState::Unconfirmed)492 .unwrap_or_default(),493 variable_on_chain_schema: data.variable_on_chain_schema,494 const_on_chain_schema: data.const_on_chain_schema,495 limits: data496 .limits497 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))498 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,499 meta_update_permission: data.meta_update_permission.unwrap_or_default(),500 };501502 // Take a (non-refundable) deposit of collection creation503 {504 let mut imbalance =505 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();506 imbalance.subsume(507 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(508 &T::TreasuryAccountId::get(),509 T::CollectionCreationPrice::get(),510 ),511 );512 <T as Config>::Currency::settle(513 &owner,514 imbalance,515 WithdrawReasons::TRANSFER,516 ExistenceRequirement::KeepAlive,517 )518 .map_err(|_| Error::<T>::NotSufficientFounds)?;519 }520521 <CreatedCollectionCount<T>>::put(created_count);522 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));523 <CollectionById<T>>::insert(id, collection);524 Ok(id)525 }526527 pub fn destroy_collection(528 collection: CollectionHandle<T>,529 sender: &T::CrossAccountId,530 ) -> DispatchResult {531 ensure!(532 collection.limits.owner_can_destroy(),533 <Error<T>>::NoPermission,534 );535 collection.check_is_owner(sender)?;536537 let destroyed_collections = <DestroyedCollectionCount<T>>::get()538 .0539 .checked_add(1)540 .ok_or(ArithmeticError::Overflow)?;541542 // =========543544 <DestroyedCollectionCount<T>>::put(destroyed_collections);545 <CollectionById<T>>::remove(collection.id);546 <AdminAmount<T>>::remove(collection.id);547 <IsAdmin<T>>::remove_prefix((collection.id,), None);548 <Allowlist<T>>::remove_prefix((collection.id,), None);549550 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));551 Ok(())552 }553554 pub fn toggle_allowlist(555 collection: &CollectionHandle<T>,556 sender: &T::CrossAccountId,557 user: &T::CrossAccountId,558 allowed: bool,559 ) -> DispatchResult {560 collection.check_is_owner_or_admin(sender)?;561562 // =========563564 if allowed {565 <Allowlist<T>>::insert((collection.id, user), true);566 } else {567 <Allowlist<T>>::remove((collection.id, user));568 }569570 Ok(())571 }572573 pub fn toggle_admin(574 collection: &CollectionHandle<T>,575 sender: &T::CrossAccountId,576 user: &T::CrossAccountId,577 admin: bool,578 ) -> DispatchResult {579 collection.check_is_owner_or_admin(sender)?;580581 let was_admin = <IsAdmin<T>>::get((collection.id, user));582 if was_admin == admin {583 return Ok(());584 }585 let amount = <AdminAmount<T>>::get(collection.id);586587 if admin {588 let amount = amount589 .checked_add(1)590 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;591 ensure!(592 amount <= Self::collection_admins_limit(),593 <Error<T>>::CollectionAdminCountExceeded,594 );595596 // =========597598 <AdminAmount<T>>::insert(collection.id, amount);599 <IsAdmin<T>>::insert((collection.id, user), true);600 } else {601 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));602 <IsAdmin<T>>::remove((collection.id, user));603 }604605 Ok(())606 }607608 pub fn clamp_limits(609 mode: CollectionMode,610 old_limit: &CollectionLimits,611 mut new_limit: CollectionLimits,612 ) -> Result<CollectionLimits, DispatchError> {613 macro_rules! limit_default {614 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{615 $(616 if let Some($new) = $new.$field {617 let $old = $old.$field($($arg)?);618 let _ = $new;619 let _ = $old;620 $check621 } else {622 $new.$field = $old.$field623 }624 )*625 }};626 }627628 limit_default!(old_limit, new_limit,629 account_token_ownership_limit => ensure!(630 new_limit <= MAX_TOKEN_OWNERSHIP,631 <Error<T>>::CollectionLimitBoundsExceeded,632 ),633 sponsor_transfer_timeout(match mode {634 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,635 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,636 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,637 }) => ensure!(638 new_limit <= MAX_SPONSOR_TIMEOUT,639 <Error<T>>::CollectionLimitBoundsExceeded,640 ),641 sponsored_data_size => ensure!(642 new_limit <= CUSTOM_DATA_LIMIT,643 <Error<T>>::CollectionLimitBoundsExceeded,644 ),645 token_limit => ensure!(646 old_limit >= new_limit && new_limit > 0,647 <Error<T>>::CollectionTokenLimitExceeded648 ),649 owner_can_transfer => ensure!(650 old_limit || !new_limit,651 <Error<T>>::OwnerPermissionsCantBeReverted,652 ),653 owner_can_destroy => ensure!(654 old_limit || !new_limit,655 <Error<T>>::OwnerPermissionsCantBeReverted,656 ),657 sponsored_data_rate_limit => {},658 transfers_enabled => {},659 );660 Ok(new_limit)661 }662}663664#[macro_export]665macro_rules! unsupported {666 () => {667 Err(<Error<T>>::UnsupportedOperation.into())668 };669}670671/// Worst cases672pub trait CommonWeightInfo<CrossAccountId> {673 fn create_item() -> Weight;674 fn create_multiple_items(amount: u32) -> Weight;675 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;676 fn burn_item() -> Weight;677 fn transfer() -> Weight;678 fn approve() -> Weight;679 fn transfer_from() -> Weight;680 fn burn_from() -> Weight;681 fn set_variable_metadata(bytes: u32) -> Weight;682}683684pub trait CommonCollectionOperations<T: Config> {685 fn create_item(686 &self,687 sender: T::CrossAccountId,688 to: T::CrossAccountId,689 data: CreateItemData,690 ) -> DispatchResultWithPostInfo;691 fn create_multiple_items(692 &self,693 sender: T::CrossAccountId,694 to: T::CrossAccountId,695 data: Vec<CreateItemData>,696 ) -> DispatchResultWithPostInfo;697 fn create_multiple_items_ex(698 &self,699 sender: T::CrossAccountId,700 data: CreateItemExData<T::CrossAccountId>,701 ) -> DispatchResultWithPostInfo;702 fn burn_item(703 &self,704 sender: T::CrossAccountId,705 token: TokenId,706 amount: u128,707 ) -> DispatchResultWithPostInfo;708709 fn transfer(710 &self,711 sender: T::CrossAccountId,712 to: T::CrossAccountId,713 token: TokenId,714 amount: u128,715 ) -> DispatchResultWithPostInfo;716 fn approve(717 &self,718 sender: T::CrossAccountId,719 spender: T::CrossAccountId,720 token: TokenId,721 amount: u128,722 ) -> DispatchResultWithPostInfo;723 fn transfer_from(724 &self,725 sender: T::CrossAccountId,726 from: T::CrossAccountId,727 to: T::CrossAccountId,728 token: TokenId,729 amount: u128,730 ) -> DispatchResultWithPostInfo;731 fn burn_from(732 &self,733 sender: T::CrossAccountId,734 from: T::CrossAccountId,735 token: TokenId,736 amount: u128,737 ) -> DispatchResultWithPostInfo;738739 fn set_variable_metadata(740 &self,741 sender: T::CrossAccountId,742 token: TokenId,743 data: BoundedVec<u8, CustomDataLimit>,744 ) -> DispatchResultWithPostInfo;745746 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;747 fn token_exists(&self, token: TokenId) -> bool;748 fn last_token_id(&self) -> TokenId;749750 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;751 fn const_metadata(&self, token: TokenId) -> Vec<u8>;752 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;753754 /// How many tokens collection contains (Applicable to nonfungible/refungible)755 fn collection_tokens(&self) -> u32;756 /// Amount of different tokens account has (Applicable to nonfungible/refungible)757 fn account_balance(&self, account: T::CrossAccountId) -> u32;758 /// Amount of specific token account have (Applicable to fungible/refungible)759 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;760 fn allowance(761 &self,762 sender: T::CrossAccountId,763 spender: T::CrossAccountId,764 token: TokenId,765 ) -> u128;766}767768// Flexible enough for implementing CommonCollectionOperations769pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {770 let post_info = PostDispatchInfo {771 actual_weight: Some(weight),772 pays_fee: Pays::Yes,773 };774 match res {775 Ok(()) => Ok(post_info),776 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),777 }778}