difftreelog
doc: adjust documentation style
in: master
2 files changed
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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 // RMRK71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkBoundedTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 /// Checks that the collection was created with, and must be operated upon through **Unique API**.171 /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181 /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 /// New collection was created288 ///289 /// # Arguments290 ///291 /// * collection_id: Globally unique identifier of newly created collection.292 ///293 /// * mode: [CollectionMode] converted into u8.294 ///295 /// * account_id: Collection owner.296 CollectionCreated(CollectionId, u8, T::AccountId),297298 /// New collection was destroyed299 ///300 /// # Arguments301 ///302 /// * collection_id: Globally unique identifier of collection that has been destroyed.303 CollectionDestroyed(CollectionId),304305 /// New item was created.306 ///307 /// # Arguments308 ///309 /// * collection_id: ID of the collection where the item was created.310 ///311 /// * item_id: ID of the item. Unique within the collection.312 ///313 /// * recipient: Owner of the newly created item.314 ///315 /// * amount: The amount of tokens that were created (always 1 for NFT).316 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),317318 /// Collection item was burned.319 ///320 /// # Arguments321 ///322 /// * collection_id: Identifier of the collection to which the burned NFT belonged.323 ///324 /// * item_id: Identifier of burned NFT.325 ///326 /// * owner: Which user has destroyed their tokens.327 ///328 /// * amount: The amount of tokens that were destroyed (always 1 for NFT).329 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),330331 /// Item was transferred.332 ///333 /// # Arguments334 ///335 /// * collection_id: ID of the collection to which the item belongs.336 ///337 /// * item_id: ID of the item transferred.338 ///339 /// * sender: Original owner of the item.340 ///341 /// * recipient: New owner of the item.342 ///343 /// * amount: The amount of tokens that were transferred (always 1 for NFT).344 Transfer(345 CollectionId,346 TokenId,347 T::CrossAccountId,348 T::CrossAccountId,349 u128,350 ),351352 /// Sponsoring allowance was approved.353 ///354 /// # Arguments355 ///356 /// * collection_id357 ///358 /// * item_id359 ///360 /// * sender361 ///362 /// * spender363 ///364 /// * amount365 Approved(366 CollectionId,367 TokenId,368 T::CrossAccountId,369 T::CrossAccountId,370 u128,371 ),372373 /// Collection property was added or edited.374 ///375 /// # Arguments376 ///377 /// * collection_id: ID of the collection, whose property was just set.378 ///379 /// * property_key: Key of the property that was just set.380 CollectionPropertySet(CollectionId, PropertyKey),381382 /// Collection property was deleted.383 ///384 /// # Arguments385 ///386 /// * collection_id: ID of the collection, whose property was just deleted.387 ///388 /// * property_key: Key of the property that was just deleted.389 CollectionPropertyDeleted(CollectionId, PropertyKey),390391 /// Item property was added or edited.392 ///393 /// # Arguments394 ///395 /// * collection_id: ID of the collection, whose token's property was just set.396 ///397 /// * item_id: ID of the item, whose property was just set.398 ///399 /// * property_key: Key of the property that was just set.400 TokenPropertySet(CollectionId, TokenId, PropertyKey),401402 /// Item property was deleted.403 ///404 /// # Arguments405 ///406 /// * collection_id: ID of the collection, whose token's property was just deleted.407 ///408 /// * item_id: ID of the item, whose property was just deleted.409 ///410 /// * property_key: Key of the property that was just deleted.411 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),412413 /// Token property permission was added or updated for a collection.414 ///415 /// # Arguments416 ///417 /// * collection_id: ID of the collection, whose permissions were just set/updated.418 ///419 /// * property_key: Key of the property of the set/updated permission.420 PropertyPermissionSet(CollectionId, PropertyKey),421 }422423 #[pallet::error]424 pub enum Error<T> {425 /// This collection does not exist.426 CollectionNotFound,427 /// Sender parameter and item owner must be equal.428 MustBeTokenOwner,429 /// No permission to perform action430 NoPermission,431 /// Destroying only empty collections is allowed432 CantDestroyNotEmptyCollection,433 /// Collection is not in mint mode.434 PublicMintingNotAllowed,435 /// Address is not in allow list.436 AddressNotInAllowlist,437438 /// Collection name can not be longer than 63 char.439 CollectionNameLimitExceeded,440 /// Collection description can not be longer than 255 char.441 CollectionDescriptionLimitExceeded,442 /// Token prefix can not be longer than 15 char.443 CollectionTokenPrefixLimitExceeded,444 /// Total collections bound exceeded.445 TotalCollectionsLimitExceeded,446 /// Exceeded max admin count447 CollectionAdminCountExceeded,448 /// Collection limit bounds per collection exceeded449 CollectionLimitBoundsExceeded,450 /// Tried to enable permissions which are only permitted to be disabled451 OwnerPermissionsCantBeReverted,452 /// Collection settings not allowing items transferring453 TransferNotAllowed,454 /// Account token limit exceeded per collection455 AccountTokenLimitExceeded,456 /// Collection token limit exceeded457 CollectionTokenLimitExceeded,458 /// Metadata flag frozen459 MetadataFlagFrozen,460461 /// Item does not exist462 TokenNotFound,463 /// Item is balance not enough464 TokenValueTooLow,465 /// Requested value is more than the approved466 ApprovedValueTooLow,467 /// Tried to approve more than owned468 CantApproveMoreThanOwned,469470 /// Can't transfer tokens to ethereum zero address471 AddressIsZero,472 /// Target collection doesn't support this operation473 UnsupportedOperation,474475 /// Insufficient funds to perform an action476 NotSufficientFounds,477478 /// User does not satisfy the nesting rule479 UserIsNotAllowedToNest,480 /// Only tokens from specific collections may nest tokens under this one481 SourceCollectionIsNotAllowedToNest,482483 /// Tried to store more data than allowed in collection field484 CollectionFieldSizeExceeded,485486 /// Tried to store more property data than allowed487 NoSpaceForProperty,488489 /// Tried to store more property keys than allowed490 PropertyLimitReached,491492 /// Property key is too long493 PropertyKeyIsTooLong,494495 /// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed496 InvalidCharacterInPropertyKey,497498 /// Empty property keys are forbidden499 EmptyPropertyKey,500501 /// Tried to access an external collection with an internal API502 CollectionIsExternal,503504 /// Tried to access an internal collection with an external API505 CollectionIsInternal,506 }507508 /// The number of created collections. Essentially contains the last collection ID.509 #[pallet::storage]510 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;511512 /// The number of destroyed collections513 #[pallet::storage]514 pub type DestroyedCollectionCount<T> =515 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;516517 /// Collection info518 #[pallet::storage]519 pub type CollectionById<T> = StorageMap<520 Hasher = Blake2_128Concat,521 Key = CollectionId,522 Value = Collection<<T as frame_system::Config>::AccountId>,523 QueryKind = OptionQuery,524 >;525526 /// Collection properties527 #[pallet::storage]528 #[pallet::getter(fn collection_properties)]529 pub type CollectionProperties<T> = StorageMap<530 Hasher = Blake2_128Concat,531 Key = CollectionId,532 Value = Properties,533 QueryKind = ValueQuery,534 OnEmpty = up_data_structs::CollectionProperties,535 >;536537 /// Token permissions of a collection538 #[pallet::storage]539 #[pallet::getter(fn property_permissions)]540 pub type CollectionPropertyPermissions<T> = StorageMap<541 Hasher = Blake2_128Concat,542 Key = CollectionId,543 Value = PropertiesPermissionMap,544 QueryKind = ValueQuery,545 >;546547 /// Amount of collection admins548 #[pallet::storage]549 pub type AdminAmount<T> = StorageMap<550 Hasher = Blake2_128Concat,551 Key = CollectionId,552 Value = u32,553 QueryKind = ValueQuery,554 >;555556 /// List of collection admins557 #[pallet::storage]558 pub type IsAdmin<T: Config> = StorageNMap<559 Key = (560 Key<Blake2_128Concat, CollectionId>,561 Key<Blake2_128Concat, T::CrossAccountId>,562 ),563 Value = bool,564 QueryKind = ValueQuery,565 >;566567 /// Allowlisted collection users568 #[pallet::storage]569 pub type Allowlist<T: Config> = StorageNMap<570 Key = (571 Key<Blake2_128Concat, CollectionId>,572 Key<Blake2_128Concat, T::CrossAccountId>,573 ),574 Value = bool,575 QueryKind = ValueQuery,576 >;577578 /// Not used by code, exists only to provide some types to metadata579 #[pallet::storage]580 pub type DummyStorageValue<T: Config> = StorageValue<581 Value = (582 CollectionStats,583 CollectionId,584 TokenId,585 TokenChild,586 PhantomType<(587 TokenData<T::CrossAccountId>,588 RpcCollection<T::AccountId>,589 // RMRK590 RmrkCollectionInfo<T::AccountId>,591 RmrkInstanceInfo<T::AccountId>,592 RmrkResourceInfo,593 RmrkPropertyInfo,594 RmrkBaseInfo<T::AccountId>,595 RmrkPartType,596 RmrkBoundedTheme,597 RmrkNftChild,598 )>,599 ),600 QueryKind = OptionQuery,601 >;602603 #[pallet::hooks]604 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {605 fn on_runtime_upgrade() -> Weight {606 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {607 use up_data_structs::{CollectionVersion1, CollectionVersion2};608 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {609 let mut props = Vec::new();610 if !v.offchain_schema.is_empty() {611 props.push(Property {612 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),613 value: v614 .offchain_schema615 .clone()616 .into_inner()617 .try_into()618 .expect("offchain schema too big"),619 });620 }621 if !v.variable_on_chain_schema.is_empty() {622 props.push(Property {623 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),624 value: v625 .variable_on_chain_schema626 .clone()627 .into_inner()628 .try_into()629 .expect("offchain schema too big"),630 });631 }632 if !v.const_on_chain_schema.is_empty() {633 props.push(Property {634 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),635 value: v636 .const_on_chain_schema637 .clone()638 .into_inner()639 .try_into()640 .expect("offchain schema too big"),641 });642 }643 props.push(Property {644 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),645 value: match v.schema_version {646 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),647 SchemaVersion::Unique => b"Unique".as_slice(),648 }649 .to_vec()650 .try_into()651 .unwrap(),652 });653 Self::set_scoped_collection_properties(654 id,655 PropertyScope::None,656 props.into_iter(),657 )658 .expect("existing data larger than properties");659 let mut new = CollectionVersion2::from(v.clone());660 new.permissions.access = Some(v.access);661 new.permissions.mint_mode = Some(v.mint_mode);662 Some(new)663 });664 }665666 0667 }668 }669}670671impl<T: Config> Pallet<T> {672 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens673 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {674 ensure!(675 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,676 <Error<T>>::AddressIsZero677 );678 Ok(())679 }680 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {681 <IsAdmin<T>>::iter_prefix((collection,))682 .map(|(a, _)| a)683 .collect()684 }685 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {686 <Allowlist<T>>::iter_prefix((collection,))687 .map(|(a, _)| a)688 .collect()689 }690 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {691 <Allowlist<T>>::get((collection, user))692 }693 pub fn collection_stats() -> CollectionStats {694 let created = <CreatedCollectionCount<T>>::get();695 let destroyed = <DestroyedCollectionCount<T>>::get();696 CollectionStats {697 created: created.0,698 destroyed: destroyed.0,699 alive: created.0 - destroyed.0,700 }701 }702703 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {704 let collection = <CollectionById<T>>::get(collection);705 if collection.is_none() {706 return None;707 }708709 let collection = collection.unwrap();710 let limits = collection.limits;711 let effective_limits = CollectionLimits {712 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),713 sponsored_data_size: Some(limits.sponsored_data_size()),714 sponsored_data_rate_limit: Some(715 limits716 .sponsored_data_rate_limit717 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),718 ),719 token_limit: Some(limits.token_limit()),720 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(721 match collection.mode {722 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,723 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,724 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,725 },726 )),727 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),728 owner_can_transfer: Some(limits.owner_can_transfer()),729 owner_can_destroy: Some(limits.owner_can_destroy()),730 transfers_enabled: Some(limits.transfers_enabled()),731 };732733 Some(effective_limits)734 }735736 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {737 let Collection {738 name,739 description,740 owner,741 mode,742 token_prefix,743 sponsorship,744 limits,745 permissions,746 external_collection,747 } = <CollectionById<T>>::get(collection)?;748749 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)750 .into_iter()751 .map(|(key, permission)| PropertyKeyPermission { key, permission })752 .collect();753754 let properties = <CollectionProperties<T>>::get(collection)755 .into_iter()756 .map(|(key, value)| Property { key, value })757 .collect();758759 let permissions = CollectionPermissions {760 access: Some(permissions.access()),761 mint_mode: Some(permissions.mint_mode()),762 nesting: Some(permissions.nesting().clone()),763 };764765 Some(RpcCollection {766 name: name.into_inner(),767 description: description.into_inner(),768 owner,769 mode,770 token_prefix: token_prefix.into_inner(),771 sponsorship,772 limits,773 permissions,774 token_property_permissions,775 properties,776 read_only: external_collection,777 })778 }779}780781macro_rules! limit_default {782 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{783 $(784 if let Some($new) = $new.$field {785 let $old = $old.$field($($arg)?);786 let _ = $new;787 let _ = $old;788 $check789 } else {790 $new.$field = $old.$field791 }792 )*793 }};794}795macro_rules! limit_default_clone {796 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{797 $(798 if let Some($new) = $new.$field.clone() {799 let $old = $old.$field($($arg)?);800 let _ = $new;801 let _ = $old;802 $check803 } else {804 $new.$field = $old.$field.clone()805 }806 )*807 }};808}809810impl<T: Config> Pallet<T> {811 pub fn init_collection(812 owner: T::CrossAccountId,813 data: CreateCollectionData<T::AccountId>,814 is_external: bool,815 ) -> Result<CollectionId, DispatchError> {816 {817 ensure!(818 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,819 Error::<T>::CollectionTokenPrefixLimitExceeded820 );821 }822823 let created_count = <CreatedCollectionCount<T>>::get()824 .0825 .checked_add(1)826 .ok_or(ArithmeticError::Overflow)?;827 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;828 let id = CollectionId(created_count);829830 // bound Total number of collections831 ensure!(832 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,833 <Error<T>>::TotalCollectionsLimitExceeded834 );835836 // =========837838 let collection = Collection {839 owner: owner.as_sub().clone(),840 name: data.name,841 mode: data.mode.clone(),842 description: data.description,843 token_prefix: data.token_prefix,844 sponsorship: data845 .pending_sponsor846 .map(SponsorshipState::Unconfirmed)847 .unwrap_or_default(),848 limits: data849 .limits850 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))851 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,852 permissions: data853 .permissions854 .map(|permissions| {855 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)856 })857 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,858 external_collection: is_external,859 };860861 let mut collection_properties = up_data_structs::CollectionProperties::get();862 collection_properties863 .try_set_from_iter(data.properties.into_iter())864 .map_err(<Error<T>>::from)?;865866 CollectionProperties::<T>::insert(id, collection_properties);867868 let mut token_props_permissions = PropertiesPermissionMap::new();869 token_props_permissions870 .try_set_from_iter(data.token_property_permissions.into_iter())871 .map_err(<Error<T>>::from)?;872873 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);874875 // Take a (non-refundable) deposit of collection creation876 {877 let mut imbalance =878 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();879 imbalance.subsume(880 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(881 &T::TreasuryAccountId::get(),882 T::CollectionCreationPrice::get(),883 ),884 );885 <T as Config>::Currency::settle(886 &owner.as_sub(),887 imbalance,888 WithdrawReasons::TRANSFER,889 ExistenceRequirement::KeepAlive,890 )891 .map_err(|_| Error::<T>::NotSufficientFounds)?;892 }893894 <CreatedCollectionCount<T>>::put(created_count);895 <Pallet<T>>::deposit_event(Event::CollectionCreated(896 id,897 data.mode.id(),898 owner.as_sub().clone(),899 ));900 <PalletEvm<T>>::deposit_log(901 erc::CollectionHelpersEvents::CollectionCreated {902 owner: *owner.as_eth(),903 collection_id: eth::collection_id_to_address(id),904 }905 .to_log(T::ContractAddress::get()),906 );907 <CollectionById<T>>::insert(id, collection);908 Ok(id)909 }910911 pub fn destroy_collection(912 collection: CollectionHandle<T>,913 sender: &T::CrossAccountId,914 ) -> DispatchResult {915 ensure!(916 collection.limits.owner_can_destroy(),917 <Error<T>>::NoPermission,918 );919 collection.check_is_owner(sender)?;920921 let destroyed_collections = <DestroyedCollectionCount<T>>::get()922 .0923 .checked_add(1)924 .ok_or(ArithmeticError::Overflow)?;925926 // =========927928 <DestroyedCollectionCount<T>>::put(destroyed_collections);929 <CollectionById<T>>::remove(collection.id);930 <AdminAmount<T>>::remove(collection.id);931 <IsAdmin<T>>::remove_prefix((collection.id,), None);932 <Allowlist<T>>::remove_prefix((collection.id,), None);933 <CollectionProperties<T>>::remove(collection.id);934935 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));936 Ok(())937 }938939 pub fn set_collection_property(940 collection: &CollectionHandle<T>,941 sender: &T::CrossAccountId,942 property: Property,943 ) -> DispatchResult {944 collection.check_is_owner_or_admin(sender)?;945946 CollectionProperties::<T>::try_mutate(collection.id, |properties| {947 let property = property.clone();948 properties.try_set(property.key, property.value)949 })950 .map_err(<Error<T>>::from)?;951952 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));953954 Ok(())955 }956957 pub fn set_scoped_collection_property(958 collection_id: CollectionId,959 scope: PropertyScope,960 property: Property,961 ) -> DispatchResult {962 CollectionProperties::<T>::try_mutate(collection_id, |properties| {963 properties.try_scoped_set(scope, property.key, property.value)964 })965 .map_err(<Error<T>>::from)?;966967 Ok(())968 }969970 pub fn set_scoped_collection_properties(971 collection_id: CollectionId,972 scope: PropertyScope,973 properties: impl Iterator<Item = Property>,974 ) -> DispatchResult {975 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {976 stored_properties.try_scoped_set_from_iter(scope, properties)977 })978 .map_err(<Error<T>>::from)?;979980 Ok(())981 }982983 #[transactional]984 pub fn set_collection_properties(985 collection: &CollectionHandle<T>,986 sender: &T::CrossAccountId,987 properties: Vec<Property>,988 ) -> DispatchResult {989 for property in properties {990 Self::set_collection_property(collection, sender, property)?;991 }992993 Ok(())994 }995996 pub fn delete_collection_property(997 collection: &CollectionHandle<T>,998 sender: &T::CrossAccountId,999 property_key: PropertyKey,1000 ) -> DispatchResult {1001 collection.check_is_owner_or_admin(sender)?;10021003 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1004 properties.remove(&property_key)1005 })1006 .map_err(<Error<T>>::from)?;10071008 Self::deposit_event(Event::CollectionPropertyDeleted(1009 collection.id,1010 property_key,1011 ));10121013 Ok(())1014 }10151016 #[transactional]1017 pub fn delete_collection_properties(1018 collection: &CollectionHandle<T>,1019 sender: &T::CrossAccountId,1020 property_keys: Vec<PropertyKey>,1021 ) -> DispatchResult {1022 for key in property_keys {1023 Self::delete_collection_property(collection, sender, key)?;1024 }10251026 Ok(())1027 }10281029 // For migrations1030 pub fn set_property_permission_unchecked(1031 collection: CollectionId,1032 property_permission: PropertyKeyPermission,1033 ) -> DispatchResult {1034 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1035 permissions.try_set(property_permission.key, property_permission.permission)1036 })1037 .map_err(<Error<T>>::from)?;1038 Ok(())1039 }10401041 pub fn set_property_permission(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 property_permission: PropertyKeyPermission,1045 ) -> DispatchResult {1046 collection.check_is_owner_or_admin(sender)?;10471048 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1049 let current_permission = all_permissions.get(&property_permission.key);1050 if matches![1051 current_permission,1052 Some(PropertyPermission { mutable: false, .. })1053 ] {1054 return Err(<Error<T>>::NoPermission.into());1055 }10561057 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1058 let property_permission = property_permission.clone();1059 permissions.try_set(property_permission.key, property_permission.permission)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Self::deposit_event(Event::PropertyPermissionSet(1064 collection.id,1065 property_permission.key,1066 ));10671068 Ok(())1069 }10701071 #[transactional]1072 pub fn set_token_property_permissions(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 property_permissions: Vec<PropertyKeyPermission>,1076 ) -> DispatchResult {1077 for prop_pemission in property_permissions {1078 Self::set_property_permission(collection, sender, prop_pemission)?;1079 }10801081 Ok(())1082 }10831084 pub fn get_collection_property(1085 collection_id: CollectionId,1086 key: &PropertyKey,1087 ) -> Option<PropertyValue> {1088 Self::collection_properties(collection_id).get(key).cloned()1089 }10901091 pub fn bytes_keys_to_property_keys(1092 keys: Vec<Vec<u8>>,1093 ) -> Result<Vec<PropertyKey>, DispatchError> {1094 keys.into_iter()1095 .map(|key| -> Result<PropertyKey, DispatchError> {1096 key.try_into()1097 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1098 })1099 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1100 }11011102 pub fn filter_collection_properties(1103 collection_id: CollectionId,1104 keys: Option<Vec<PropertyKey>>,1105 ) -> Result<Vec<Property>, DispatchError> {1106 let properties = Self::collection_properties(collection_id);11071108 let properties = keys1109 .map(|keys| {1110 keys.into_iter()1111 .filter_map(|key| {1112 properties.get(&key).map(|value| Property {1113 key,1114 value: value.clone(),1115 })1116 })1117 .collect()1118 })1119 .unwrap_or_else(|| {1120 properties1121 .into_iter()1122 .map(|(key, value)| Property { key, value })1123 .collect()1124 });11251126 Ok(properties)1127 }11281129 pub fn filter_property_permissions(1130 collection_id: CollectionId,1131 keys: Option<Vec<PropertyKey>>,1132 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1133 let permissions = Self::property_permissions(collection_id);11341135 let key_permissions = keys1136 .map(|keys| {1137 keys.into_iter()1138 .filter_map(|key| {1139 permissions1140 .get(&key)1141 .map(|permission| PropertyKeyPermission {1142 key,1143 permission: permission.clone(),1144 })1145 })1146 .collect()1147 })1148 .unwrap_or_else(|| {1149 permissions1150 .into_iter()1151 .map(|(key, permission)| PropertyKeyPermission { key, permission })1152 .collect()1153 });11541155 Ok(key_permissions)1156 }11571158 pub fn toggle_allowlist(1159 collection: &CollectionHandle<T>,1160 sender: &T::CrossAccountId,1161 user: &T::CrossAccountId,1162 allowed: bool,1163 ) -> DispatchResult {1164 collection.check_is_owner_or_admin(sender)?;11651166 // =========11671168 if allowed {1169 <Allowlist<T>>::insert((collection.id, user), true);1170 } else {1171 <Allowlist<T>>::remove((collection.id, user));1172 }11731174 Ok(())1175 }11761177 pub fn toggle_admin(1178 collection: &CollectionHandle<T>,1179 sender: &T::CrossAccountId,1180 user: &T::CrossAccountId,1181 admin: bool,1182 ) -> DispatchResult {1183 collection.check_is_owner(sender)?;11841185 let was_admin = <IsAdmin<T>>::get((collection.id, user));1186 if was_admin == admin {1187 return Ok(());1188 }1189 let amount = <AdminAmount<T>>::get(collection.id);11901191 if admin {1192 let amount = amount1193 .checked_add(1)1194 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1195 ensure!(1196 amount <= Self::collection_admins_limit(),1197 <Error<T>>::CollectionAdminCountExceeded,1198 );11991200 // =========12011202 <AdminAmount<T>>::insert(collection.id, amount);1203 <IsAdmin<T>>::insert((collection.id, user), true);1204 } else {1205 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1206 <IsAdmin<T>>::remove((collection.id, user));1207 }12081209 Ok(())1210 }12111212 pub fn clamp_limits(1213 mode: CollectionMode,1214 old_limit: &CollectionLimits,1215 mut new_limit: CollectionLimits,1216 ) -> Result<CollectionLimits, DispatchError> {1217 let limits = old_limit;1218 limit_default!(old_limit, new_limit,1219 account_token_ownership_limit => ensure!(1220 new_limit <= MAX_TOKEN_OWNERSHIP,1221 <Error<T>>::CollectionLimitBoundsExceeded,1222 ),1223 sponsored_data_size => ensure!(1224 new_limit <= CUSTOM_DATA_LIMIT,1225 <Error<T>>::CollectionLimitBoundsExceeded,1226 ),12271228 sponsored_data_rate_limit => {},1229 token_limit => ensure!(1230 old_limit >= new_limit && new_limit > 0,1231 <Error<T>>::CollectionTokenLimitExceeded1232 ),12331234 sponsor_transfer_timeout(match mode {1235 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1236 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1237 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1238 }) => ensure!(1239 new_limit <= MAX_SPONSOR_TIMEOUT,1240 <Error<T>>::CollectionLimitBoundsExceeded,1241 ),1242 sponsor_approve_timeout => {},1243 owner_can_transfer => ensure!(1244 !limits.owner_can_transfer_instaled() ||1245 old_limit || !new_limit,1246 <Error<T>>::OwnerPermissionsCantBeReverted,1247 ),1248 owner_can_destroy => ensure!(1249 old_limit || !new_limit,1250 <Error<T>>::OwnerPermissionsCantBeReverted,1251 ),1252 transfers_enabled => {},1253 );1254 Ok(new_limit)1255 }12561257 pub fn clamp_permissions(1258 _mode: CollectionMode,1259 old_limit: &CollectionPermissions,1260 mut new_limit: CollectionPermissions,1261 ) -> Result<CollectionPermissions, DispatchError> {1262 limit_default_clone!(old_limit, new_limit,1263 access => {},1264 mint_mode => {},1265 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1266 );1267 Ok(new_limit)1268 }1269}12701271#[macro_export]1272macro_rules! unsupported {1273 () => {1274 Err(<Error<T>>::UnsupportedOperation.into())1275 };1276}12771278/// Worst cases1279pub trait CommonWeightInfo<CrossAccountId> {1280 fn create_item() -> Weight;1281 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1282 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1283 fn burn_item() -> Weight;1284 fn set_collection_properties(amount: u32) -> Weight;1285 fn delete_collection_properties(amount: u32) -> Weight;1286 fn set_token_properties(amount: u32) -> Weight;1287 fn delete_token_properties(amount: u32) -> Weight;1288 fn set_token_property_permissions(amount: u32) -> Weight;1289 fn transfer() -> Weight;1290 fn approve() -> Weight;1291 fn transfer_from() -> Weight;1292 fn burn_from() -> Weight;12931294 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1295 /// whole users's balance1296 ///1297 /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1298 fn burn_recursively_self_raw() -> Weight;1299 /// Cost of iterating over `amount` children while burning, without counting child burning itself1300 ///1301 /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1302 fn burn_recursively_breadth_raw(amount: u32) -> Weight;13031304 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1305 Self::burn_recursively_self_raw()1306 .saturating_mul(max_selfs.max(1) as u64)1307 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1308 }1309}13101311pub trait RefungibleExtensionsWeightInfo {1312 fn repartition() -> Weight;1313}13141315pub trait CommonCollectionOperations<T: Config> {1316 fn create_item(1317 &self,1318 sender: T::CrossAccountId,1319 to: T::CrossAccountId,1320 data: CreateItemData,1321 nesting_budget: &dyn Budget,1322 ) -> DispatchResultWithPostInfo;1323 fn create_multiple_items(1324 &self,1325 sender: T::CrossAccountId,1326 to: T::CrossAccountId,1327 data: Vec<CreateItemData>,1328 nesting_budget: &dyn Budget,1329 ) -> DispatchResultWithPostInfo;1330 fn create_multiple_items_ex(1331 &self,1332 sender: T::CrossAccountId,1333 data: CreateItemExData<T::CrossAccountId>,1334 nesting_budget: &dyn Budget,1335 ) -> DispatchResultWithPostInfo;1336 fn burn_item(1337 &self,1338 sender: T::CrossAccountId,1339 token: TokenId,1340 amount: u128,1341 ) -> DispatchResultWithPostInfo;1342 fn burn_item_recursively(1343 &self,1344 sender: T::CrossAccountId,1345 token: TokenId,1346 self_budget: &dyn Budget,1347 breadth_budget: &dyn Budget,1348 ) -> DispatchResultWithPostInfo;1349 fn set_collection_properties(1350 &self,1351 sender: T::CrossAccountId,1352 properties: Vec<Property>,1353 ) -> DispatchResultWithPostInfo;1354 fn delete_collection_properties(1355 &self,1356 sender: &T::CrossAccountId,1357 property_keys: Vec<PropertyKey>,1358 ) -> DispatchResultWithPostInfo;1359 fn set_token_properties(1360 &self,1361 sender: T::CrossAccountId,1362 token_id: TokenId,1363 property: Vec<Property>,1364 nesting_budget: &dyn Budget,1365 ) -> DispatchResultWithPostInfo;1366 fn delete_token_properties(1367 &self,1368 sender: T::CrossAccountId,1369 token_id: TokenId,1370 property_keys: Vec<PropertyKey>,1371 nesting_budget: &dyn Budget,1372 ) -> DispatchResultWithPostInfo;1373 fn set_token_property_permissions(1374 &self,1375 sender: &T::CrossAccountId,1376 property_permissions: Vec<PropertyKeyPermission>,1377 ) -> DispatchResultWithPostInfo;1378 fn transfer(1379 &self,1380 sender: T::CrossAccountId,1381 to: T::CrossAccountId,1382 token: TokenId,1383 amount: u128,1384 nesting_budget: &dyn Budget,1385 ) -> DispatchResultWithPostInfo;1386 fn approve(1387 &self,1388 sender: T::CrossAccountId,1389 spender: T::CrossAccountId,1390 token: TokenId,1391 amount: u128,1392 ) -> DispatchResultWithPostInfo;1393 fn transfer_from(1394 &self,1395 sender: T::CrossAccountId,1396 from: T::CrossAccountId,1397 to: T::CrossAccountId,1398 token: TokenId,1399 amount: u128,1400 nesting_budget: &dyn Budget,1401 ) -> DispatchResultWithPostInfo;1402 fn burn_from(1403 &self,1404 sender: T::CrossAccountId,1405 from: T::CrossAccountId,1406 token: TokenId,1407 amount: u128,1408 nesting_budget: &dyn Budget,1409 ) -> DispatchResultWithPostInfo;14101411 fn check_nesting(1412 &self,1413 sender: T::CrossAccountId,1414 from: (CollectionId, TokenId),1415 under: TokenId,1416 nesting_budget: &dyn Budget,1417 ) -> DispatchResult;14181419 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14201421 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));14221423 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1424 fn collection_tokens(&self) -> Vec<TokenId>;1425 fn token_exists(&self, token: TokenId) -> bool;1426 fn last_token_id(&self) -> TokenId;14271428 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1429 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1430 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1431 /// Amount of unique collection tokens1432 fn total_supply(&self) -> u32;1433 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1434 fn account_balance(&self, account: T::CrossAccountId) -> u32;1435 /// Amount of specific token account have (Applicable to fungible/refungible)1436 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1437 /// Amount of token pieces1438 fn total_pieces(&self, token: TokenId) -> Option<u128>;1439 fn allowance(1440 &self,1441 sender: T::CrossAccountId,1442 spender: T::CrossAccountId,1443 token: TokenId,1444 ) -> u128;1445 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1446}14471448pub trait RefungibleExtensions<T>1449where1450 T: Config,1451{1452 fn repartition(1453 &self,1454 owner: &T::CrossAccountId,1455 token: TokenId,1456 amount: u128,1457 ) -> DispatchResultWithPostInfo;1458}14591460// Flexible enough for implementing CommonCollectionOperations1461pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1462 let post_info = PostDispatchInfo {1463 actual_weight: Some(weight),1464 pays_fee: Pays::Yes,1465 };1466 match res {1467 Ok(()) => Ok(post_info),1468 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1469 }1470}14711472impl<T: Config> From<PropertiesError> for Error<T> {1473 fn from(error: PropertiesError) -> Self {1474 match error {1475 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1476 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1477 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1478 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1479 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1480 }1481 }1482}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)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 // RMRK71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkBoundedTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> DispatchResult {152 <CollectionById<T>>::insert(self.id, self.collection);153 Ok(())154 }155156 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {157 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);158 Ok(())159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return Ok(false);164 }165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 Ok(true)168 }169170 /// Checks that the collection was created with, and must be operated upon through **Unique API**.171 /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.172 pub fn check_is_internal(&self) -> DispatchResult {173 if self.external_collection {174 return Err(<Error<T>>::CollectionIsExternal)?;175 }176177 Ok(())178 }179180 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.181 /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.182 pub fn check_is_external(&self) -> DispatchResult {183 if !self.external_collection {184 return Err(<Error<T>>::CollectionIsInternal)?;185 }186187 Ok(())188 }189}190191impl<T: Config> Deref for CollectionHandle<T> {192 type Target = Collection<T::AccountId>;193194 fn deref(&self) -> &Self::Target {195 &self.collection196 }197}198199impl<T: Config> DerefMut for CollectionHandle<T> {200 fn deref_mut(&mut self) -> &mut Self::Target {201 &mut self.collection202 }203}204205impl<T: Config> CollectionHandle<T> {206 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {207 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);208 Ok(())209 }210 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {211 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))212 }213 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {214 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);215 Ok(())216 }217 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {218 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)219 }220 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {221 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)222 }223 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {224 ensure!(225 <Allowlist<T>>::get((self.id, user)),226 <Error<T>>::AddressNotInAllowlist227 );228 Ok(())229 }230}231232#[frame_support::pallet]233pub mod pallet {234 use super::*;235 use pallet_evm::account;236 use dispatch::CollectionDispatch;237 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};238 use frame_system::pallet_prelude::*;239 use frame_support::traits::Currency;240 use up_data_structs::{TokenId, mapping::TokenAddressMapping};241 use scale_info::TypeInfo;242 use weights::WeightInfo;243244 #[pallet::config]245 pub trait Config:246 frame_system::Config247 + pallet_evm_coder_substrate::Config248 + pallet_evm::Config249 + TypeInfo250 + account::Config251 {252 type WeightInfo: WeightInfo;253 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;254255 type Currency: Currency<Self::AccountId>;256257 #[pallet::constant]258 type CollectionCreationPrice: Get<259 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,260 >;261 type CollectionDispatch: CollectionDispatch<Self>;262263 type TreasuryAccountId: Get<Self::AccountId>;264 type ContractAddress: Get<H160>;265266 type EvmTokenAddressMapping: TokenAddressMapping<H160>;267 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;268 }269270 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);271272 #[pallet::pallet]273 #[pallet::storage_version(STORAGE_VERSION)]274 #[pallet::generate_store(pub(super) trait Store)]275 pub struct Pallet<T>(_);276277 #[pallet::extra_constants]278 impl<T: Config> Pallet<T> {279 pub fn collection_admins_limit() -> u32 {280 COLLECTION_ADMINS_LIMIT281 }282 }283284 #[pallet::event]285 #[pallet::generate_deposit(pub fn deposit_event)]286 pub enum Event<T: Config> {287 /// New collection was created288 ///289 /// # Arguments290 ///291 /// * collection_id - Globally unique identifier of newly created collection.292 /// * mode - [CollectionMode] converted into u8.293 /// * account_id - Collection owner.294 CollectionCreated(CollectionId, u8, T::AccountId),295296 /// New collection was destroyed297 ///298 /// # Arguments299 ///300 /// * collection_id - Globally unique identifier of collection that has been destroyed.301 CollectionDestroyed(CollectionId),302303 /// New item was created.304 ///305 /// # Arguments306 ///307 /// * collection_id - ID of the collection where the item was created.308 /// * item_id - ID of the item. Unique within the collection.309 /// * recipient - Owner of the newly created item.310 /// * amount - The amount of tokens that were created (always 1 for NFT).311 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),312313 /// Collection item was burned.314 ///315 /// # Arguments316 ///317 /// * collection_id - Identifier of the collection to which the burned NFT belonged.318 /// * item_id - Identifier of burned NFT.319 /// * owner - Which user has destroyed their tokens.320 /// * amount - Amount of tokens that were destroyed (always 1 for NFT).321 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),322323 /// Item was transferred.324 ///325 /// # Arguments326 ///327 /// * collection_id - ID of the collection to which the item belongs.328 /// * item_id - ID of the item transferred.329 /// * sender - Original owner of the item.330 /// * recipient - New owner of the item.331 /// * amount - Amount of tokens that were transferred (always 1 for NFT).332 Transfer(333 CollectionId,334 TokenId,335 T::CrossAccountId,336 T::CrossAccountId,337 u128,338 ),339340 /// Sponsoring allowance was approved.341 ///342 /// # Arguments343 ///344 /// * collection_id - todo:doc flesh out345 /// * item_id346 /// * sender347 /// * spender348 /// * amount349 Approved(350 CollectionId,351 TokenId,352 T::CrossAccountId,353 T::CrossAccountId,354 u128,355 ),356357 /// Collection property was added or edited.358 ///359 /// # Arguments360 ///361 /// * collection_id - ID of the collection, whose property was just set.362 /// * property_key - Key of the property that was just set.363 CollectionPropertySet(CollectionId, PropertyKey),364365 /// Collection property was deleted.366 ///367 /// # Arguments368 ///369 /// * collection_id - ID of the collection, whose property was just deleted.370 /// * property_key - Key of the property that was just deleted.371 CollectionPropertyDeleted(CollectionId, PropertyKey),372373 /// Item property was added or edited.374 ///375 /// # Arguments376 ///377 /// * collection_id - ID of the collection, whose token's property was just set.378 /// * item_id - ID of the item, whose property was just set.379 /// * property_key - Key of the property that was just set.380 TokenPropertySet(CollectionId, TokenId, PropertyKey),381382 /// Item property was deleted.383 ///384 /// # Arguments385 ///386 /// * collection_id - ID of the collection, whose token's property was just deleted.387 /// * item_id - ID of the item, whose property was just deleted.388 /// * property_key - Key of the property that was just deleted.389 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),390391 /// Token property permission was added or updated for a collection.392 ///393 /// # Arguments394 ///395 /// * collection_id - ID of the collection, whose permissions were just set/updated.396 /// * property_key - Key of the property of the set/updated permission.397 PropertyPermissionSet(CollectionId, PropertyKey),398 }399400 #[pallet::error]401 pub enum Error<T> {402 /// This collection does not exist.403 CollectionNotFound,404 /// Sender parameter and item owner must be equal.405 MustBeTokenOwner,406 /// No permission to perform action407 NoPermission,408 /// Destroying only empty collections is allowed409 CantDestroyNotEmptyCollection,410 /// Collection is not in mint mode.411 PublicMintingNotAllowed,412 /// Address is not in allow list.413 AddressNotInAllowlist,414415 /// Collection name can not be longer than 63 char.416 CollectionNameLimitExceeded,417 /// Collection description can not be longer than 255 char.418 CollectionDescriptionLimitExceeded,419 /// Token prefix can not be longer than 15 char.420 CollectionTokenPrefixLimitExceeded,421 /// Total collections bound exceeded.422 TotalCollectionsLimitExceeded,423 /// Exceeded max admin count424 CollectionAdminCountExceeded,425 /// Collection limit bounds per collection exceeded426 CollectionLimitBoundsExceeded,427 /// Tried to enable permissions which are only permitted to be disabled428 OwnerPermissionsCantBeReverted,429 /// Collection settings not allowing items transferring430 TransferNotAllowed,431 /// Account token limit exceeded per collection432 AccountTokenLimitExceeded,433 /// Collection token limit exceeded434 CollectionTokenLimitExceeded,435 /// Metadata flag frozen436 MetadataFlagFrozen,437438 /// Item does not exist439 TokenNotFound,440 /// Item is balance not enough441 TokenValueTooLow,442 /// Requested value is more than the approved443 ApprovedValueTooLow,444 /// Tried to approve more than owned445 CantApproveMoreThanOwned,446447 /// Can't transfer tokens to ethereum zero address448 AddressIsZero,449 /// Target collection doesn't support this operation450 UnsupportedOperation,451452 /// Insufficient funds to perform an action453 NotSufficientFounds,454455 /// User does not satisfy the nesting rule456 UserIsNotAllowedToNest,457 /// Only tokens from specific collections may nest tokens under this one458 SourceCollectionIsNotAllowedToNest,459460 /// Tried to store more data than allowed in collection field461 CollectionFieldSizeExceeded,462463 /// Tried to store more property data than allowed464 NoSpaceForProperty,465466 /// Tried to store more property keys than allowed467 PropertyLimitReached,468469 /// Property key is too long470 PropertyKeyIsTooLong,471472 /// Only ASCII letters, digits, and symbols '_', '-', and '.' are allowed473 InvalidCharacterInPropertyKey,474475 /// Empty property keys are forbidden476 EmptyPropertyKey,477478 /// Tried to access an external collection with an internal API479 CollectionIsExternal,480481 /// Tried to access an internal collection with an external API482 CollectionIsInternal,483 }484485 /// The number of created collections. Essentially contains the last collection ID.486 #[pallet::storage]487 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;488489 /// The number of destroyed collections490 #[pallet::storage]491 pub type DestroyedCollectionCount<T> =492 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;493494 /// Collection info495 #[pallet::storage]496 pub type CollectionById<T> = StorageMap<497 Hasher = Blake2_128Concat,498 Key = CollectionId,499 Value = Collection<<T as frame_system::Config>::AccountId>,500 QueryKind = OptionQuery,501 >;502503 /// Collection properties504 #[pallet::storage]505 #[pallet::getter(fn collection_properties)]506 pub type CollectionProperties<T> = StorageMap<507 Hasher = Blake2_128Concat,508 Key = CollectionId,509 Value = Properties,510 QueryKind = ValueQuery,511 OnEmpty = up_data_structs::CollectionProperties,512 >;513514 /// Token permissions of a collection515 #[pallet::storage]516 #[pallet::getter(fn property_permissions)]517 pub type CollectionPropertyPermissions<T> = StorageMap<518 Hasher = Blake2_128Concat,519 Key = CollectionId,520 Value = PropertiesPermissionMap,521 QueryKind = ValueQuery,522 >;523524 /// Amount of collection admins525 #[pallet::storage]526 pub type AdminAmount<T> = StorageMap<527 Hasher = Blake2_128Concat,528 Key = CollectionId,529 Value = u32,530 QueryKind = ValueQuery,531 >;532533 /// List of collection admins534 #[pallet::storage]535 pub type IsAdmin<T: Config> = StorageNMap<536 Key = (537 Key<Blake2_128Concat, CollectionId>,538 Key<Blake2_128Concat, T::CrossAccountId>,539 ),540 Value = bool,541 QueryKind = ValueQuery,542 >;543544 /// Allowlisted collection users545 #[pallet::storage]546 pub type Allowlist<T: Config> = StorageNMap<547 Key = (548 Key<Blake2_128Concat, CollectionId>,549 Key<Blake2_128Concat, T::CrossAccountId>,550 ),551 Value = bool,552 QueryKind = ValueQuery,553 >;554555 /// Not used by code, exists only to provide some types to metadata556 #[pallet::storage]557 pub type DummyStorageValue<T: Config> = StorageValue<558 Value = (559 CollectionStats,560 CollectionId,561 TokenId,562 TokenChild,563 PhantomType<(564 TokenData<T::CrossAccountId>,565 RpcCollection<T::AccountId>,566 // RMRK567 RmrkCollectionInfo<T::AccountId>,568 RmrkInstanceInfo<T::AccountId>,569 RmrkResourceInfo,570 RmrkPropertyInfo,571 RmrkBaseInfo<T::AccountId>,572 RmrkPartType,573 RmrkBoundedTheme,574 RmrkNftChild,575 )>,576 ),577 QueryKind = OptionQuery,578 >;579580 #[pallet::hooks]581 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {582 fn on_runtime_upgrade() -> Weight {583 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {584 use up_data_structs::{CollectionVersion1, CollectionVersion2};585 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {586 let mut props = Vec::new();587 if !v.offchain_schema.is_empty() {588 props.push(Property {589 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),590 value: v591 .offchain_schema592 .clone()593 .into_inner()594 .try_into()595 .expect("offchain schema too big"),596 });597 }598 if !v.variable_on_chain_schema.is_empty() {599 props.push(Property {600 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),601 value: v602 .variable_on_chain_schema603 .clone()604 .into_inner()605 .try_into()606 .expect("offchain schema too big"),607 });608 }609 if !v.const_on_chain_schema.is_empty() {610 props.push(Property {611 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),612 value: v613 .const_on_chain_schema614 .clone()615 .into_inner()616 .try_into()617 .expect("offchain schema too big"),618 });619 }620 props.push(Property {621 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),622 value: match v.schema_version {623 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),624 SchemaVersion::Unique => b"Unique".as_slice(),625 }626 .to_vec()627 .try_into()628 .unwrap(),629 });630 Self::set_scoped_collection_properties(631 id,632 PropertyScope::None,633 props.into_iter(),634 )635 .expect("existing data larger than properties");636 let mut new = CollectionVersion2::from(v.clone());637 new.permissions.access = Some(v.access);638 new.permissions.mint_mode = Some(v.mint_mode);639 Some(new)640 });641 }642643 0644 }645 }646}647648impl<T: Config> Pallet<T> {649 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens650 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {651 ensure!(652 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,653 <Error<T>>::AddressIsZero654 );655 Ok(())656 }657 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {658 <IsAdmin<T>>::iter_prefix((collection,))659 .map(|(a, _)| a)660 .collect()661 }662 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {663 <Allowlist<T>>::iter_prefix((collection,))664 .map(|(a, _)| a)665 .collect()666 }667 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {668 <Allowlist<T>>::get((collection, user))669 }670 pub fn collection_stats() -> CollectionStats {671 let created = <CreatedCollectionCount<T>>::get();672 let destroyed = <DestroyedCollectionCount<T>>::get();673 CollectionStats {674 created: created.0,675 destroyed: destroyed.0,676 alive: created.0 - destroyed.0,677 }678 }679680 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {681 let collection = <CollectionById<T>>::get(collection);682 if collection.is_none() {683 return None;684 }685686 let collection = collection.unwrap();687 let limits = collection.limits;688 let effective_limits = CollectionLimits {689 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),690 sponsored_data_size: Some(limits.sponsored_data_size()),691 sponsored_data_rate_limit: Some(692 limits693 .sponsored_data_rate_limit694 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),695 ),696 token_limit: Some(limits.token_limit()),697 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(698 match collection.mode {699 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,700 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,701 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,702 },703 )),704 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),705 owner_can_transfer: Some(limits.owner_can_transfer()),706 owner_can_destroy: Some(limits.owner_can_destroy()),707 transfers_enabled: Some(limits.transfers_enabled()),708 };709710 Some(effective_limits)711 }712713 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {714 let Collection {715 name,716 description,717 owner,718 mode,719 token_prefix,720 sponsorship,721 limits,722 permissions,723 external_collection,724 } = <CollectionById<T>>::get(collection)?;725726 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)727 .into_iter()728 .map(|(key, permission)| PropertyKeyPermission { key, permission })729 .collect();730731 let properties = <CollectionProperties<T>>::get(collection)732 .into_iter()733 .map(|(key, value)| Property { key, value })734 .collect();735736 let permissions = CollectionPermissions {737 access: Some(permissions.access()),738 mint_mode: Some(permissions.mint_mode()),739 nesting: Some(permissions.nesting().clone()),740 };741742 Some(RpcCollection {743 name: name.into_inner(),744 description: description.into_inner(),745 owner,746 mode,747 token_prefix: token_prefix.into_inner(),748 sponsorship,749 limits,750 permissions,751 token_property_permissions,752 properties,753 read_only: external_collection,754 })755 }756}757758macro_rules! limit_default {759 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{760 $(761 if let Some($new) = $new.$field {762 let $old = $old.$field($($arg)?);763 let _ = $new;764 let _ = $old;765 $check766 } else {767 $new.$field = $old.$field768 }769 )*770 }};771}772macro_rules! limit_default_clone {773 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{774 $(775 if let Some($new) = $new.$field.clone() {776 let $old = $old.$field($($arg)?);777 let _ = $new;778 let _ = $old;779 $check780 } else {781 $new.$field = $old.$field.clone()782 }783 )*784 }};785}786787impl<T: Config> Pallet<T> {788 pub fn init_collection(789 owner: T::CrossAccountId,790 data: CreateCollectionData<T::AccountId>,791 is_external: bool,792 ) -> Result<CollectionId, DispatchError> {793 {794 ensure!(795 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,796 Error::<T>::CollectionTokenPrefixLimitExceeded797 );798 }799800 let created_count = <CreatedCollectionCount<T>>::get()801 .0802 .checked_add(1)803 .ok_or(ArithmeticError::Overflow)?;804 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;805 let id = CollectionId(created_count);806807 // bound Total number of collections808 ensure!(809 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,810 <Error<T>>::TotalCollectionsLimitExceeded811 );812813 // =========814815 let collection = Collection {816 owner: owner.as_sub().clone(),817 name: data.name,818 mode: data.mode.clone(),819 description: data.description,820 token_prefix: data.token_prefix,821 sponsorship: data822 .pending_sponsor823 .map(SponsorshipState::Unconfirmed)824 .unwrap_or_default(),825 limits: data826 .limits827 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))828 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,829 permissions: data830 .permissions831 .map(|permissions| {832 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)833 })834 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,835 external_collection: is_external,836 };837838 let mut collection_properties = up_data_structs::CollectionProperties::get();839 collection_properties840 .try_set_from_iter(data.properties.into_iter())841 .map_err(<Error<T>>::from)?;842843 CollectionProperties::<T>::insert(id, collection_properties);844845 let mut token_props_permissions = PropertiesPermissionMap::new();846 token_props_permissions847 .try_set_from_iter(data.token_property_permissions.into_iter())848 .map_err(<Error<T>>::from)?;849850 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);851852 // Take a (non-refundable) deposit of collection creation853 {854 let mut imbalance =855 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();856 imbalance.subsume(857 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(858 &T::TreasuryAccountId::get(),859 T::CollectionCreationPrice::get(),860 ),861 );862 <T as Config>::Currency::settle(863 &owner.as_sub(),864 imbalance,865 WithdrawReasons::TRANSFER,866 ExistenceRequirement::KeepAlive,867 )868 .map_err(|_| Error::<T>::NotSufficientFounds)?;869 }870871 <CreatedCollectionCount<T>>::put(created_count);872 <Pallet<T>>::deposit_event(Event::CollectionCreated(873 id,874 data.mode.id(),875 owner.as_sub().clone(),876 ));877 <PalletEvm<T>>::deposit_log(878 erc::CollectionHelpersEvents::CollectionCreated {879 owner: *owner.as_eth(),880 collection_id: eth::collection_id_to_address(id),881 }882 .to_log(T::ContractAddress::get()),883 );884 <CollectionById<T>>::insert(id, collection);885 Ok(id)886 }887888 pub fn destroy_collection(889 collection: CollectionHandle<T>,890 sender: &T::CrossAccountId,891 ) -> DispatchResult {892 ensure!(893 collection.limits.owner_can_destroy(),894 <Error<T>>::NoPermission,895 );896 collection.check_is_owner(sender)?;897898 let destroyed_collections = <DestroyedCollectionCount<T>>::get()899 .0900 .checked_add(1)901 .ok_or(ArithmeticError::Overflow)?;902903 // =========904905 <DestroyedCollectionCount<T>>::put(destroyed_collections);906 <CollectionById<T>>::remove(collection.id);907 <AdminAmount<T>>::remove(collection.id);908 <IsAdmin<T>>::remove_prefix((collection.id,), None);909 <Allowlist<T>>::remove_prefix((collection.id,), None);910 <CollectionProperties<T>>::remove(collection.id);911912 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));913 Ok(())914 }915916 pub fn set_collection_property(917 collection: &CollectionHandle<T>,918 sender: &T::CrossAccountId,919 property: Property,920 ) -> DispatchResult {921 collection.check_is_owner_or_admin(sender)?;922923 CollectionProperties::<T>::try_mutate(collection.id, |properties| {924 let property = property.clone();925 properties.try_set(property.key, property.value)926 })927 .map_err(<Error<T>>::from)?;928929 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));930931 Ok(())932 }933934 pub fn set_scoped_collection_property(935 collection_id: CollectionId,936 scope: PropertyScope,937 property: Property,938 ) -> DispatchResult {939 CollectionProperties::<T>::try_mutate(collection_id, |properties| {940 properties.try_scoped_set(scope, property.key, property.value)941 })942 .map_err(<Error<T>>::from)?;943944 Ok(())945 }946947 pub fn set_scoped_collection_properties(948 collection_id: CollectionId,949 scope: PropertyScope,950 properties: impl Iterator<Item = Property>,951 ) -> DispatchResult {952 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {953 stored_properties.try_scoped_set_from_iter(scope, properties)954 })955 .map_err(<Error<T>>::from)?;956957 Ok(())958 }959960 #[transactional]961 pub fn set_collection_properties(962 collection: &CollectionHandle<T>,963 sender: &T::CrossAccountId,964 properties: Vec<Property>,965 ) -> DispatchResult {966 for property in properties {967 Self::set_collection_property(collection, sender, property)?;968 }969970 Ok(())971 }972973 pub fn delete_collection_property(974 collection: &CollectionHandle<T>,975 sender: &T::CrossAccountId,976 property_key: PropertyKey,977 ) -> DispatchResult {978 collection.check_is_owner_or_admin(sender)?;979980 CollectionProperties::<T>::try_mutate(collection.id, |properties| {981 properties.remove(&property_key)982 })983 .map_err(<Error<T>>::from)?;984985 Self::deposit_event(Event::CollectionPropertyDeleted(986 collection.id,987 property_key,988 ));989990 Ok(())991 }992993 #[transactional]994 pub fn delete_collection_properties(995 collection: &CollectionHandle<T>,996 sender: &T::CrossAccountId,997 property_keys: Vec<PropertyKey>,998 ) -> DispatchResult {999 for key in property_keys {1000 Self::delete_collection_property(collection, sender, key)?;1001 }10021003 Ok(())1004 }10051006 // For migrations1007 pub fn set_property_permission_unchecked(1008 collection: CollectionId,1009 property_permission: PropertyKeyPermission,1010 ) -> DispatchResult {1011 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1012 permissions.try_set(property_permission.key, property_permission.permission)1013 })1014 .map_err(<Error<T>>::from)?;1015 Ok(())1016 }10171018 pub fn set_property_permission(1019 collection: &CollectionHandle<T>,1020 sender: &T::CrossAccountId,1021 property_permission: PropertyKeyPermission,1022 ) -> DispatchResult {1023 collection.check_is_owner_or_admin(sender)?;10241025 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1026 let current_permission = all_permissions.get(&property_permission.key);1027 if matches![1028 current_permission,1029 Some(PropertyPermission { mutable: false, .. })1030 ] {1031 return Err(<Error<T>>::NoPermission.into());1032 }10331034 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1035 let property_permission = property_permission.clone();1036 permissions.try_set(property_permission.key, property_permission.permission)1037 })1038 .map_err(<Error<T>>::from)?;10391040 Self::deposit_event(Event::PropertyPermissionSet(1041 collection.id,1042 property_permission.key,1043 ));10441045 Ok(())1046 }10471048 #[transactional]1049 pub fn set_token_property_permissions(1050 collection: &CollectionHandle<T>,1051 sender: &T::CrossAccountId,1052 property_permissions: Vec<PropertyKeyPermission>,1053 ) -> DispatchResult {1054 for prop_pemission in property_permissions {1055 Self::set_property_permission(collection, sender, prop_pemission)?;1056 }10571058 Ok(())1059 }10601061 pub fn get_collection_property(1062 collection_id: CollectionId,1063 key: &PropertyKey,1064 ) -> Option<PropertyValue> {1065 Self::collection_properties(collection_id).get(key).cloned()1066 }10671068 pub fn bytes_keys_to_property_keys(1069 keys: Vec<Vec<u8>>,1070 ) -> Result<Vec<PropertyKey>, DispatchError> {1071 keys.into_iter()1072 .map(|key| -> Result<PropertyKey, DispatchError> {1073 key.try_into()1074 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1075 })1076 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1077 }10781079 pub fn filter_collection_properties(1080 collection_id: CollectionId,1081 keys: Option<Vec<PropertyKey>>,1082 ) -> Result<Vec<Property>, DispatchError> {1083 let properties = Self::collection_properties(collection_id);10841085 let properties = keys1086 .map(|keys| {1087 keys.into_iter()1088 .filter_map(|key| {1089 properties.get(&key).map(|value| Property {1090 key,1091 value: value.clone(),1092 })1093 })1094 .collect()1095 })1096 .unwrap_or_else(|| {1097 properties1098 .into_iter()1099 .map(|(key, value)| Property { key, value })1100 .collect()1101 });11021103 Ok(properties)1104 }11051106 pub fn filter_property_permissions(1107 collection_id: CollectionId,1108 keys: Option<Vec<PropertyKey>>,1109 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1110 let permissions = Self::property_permissions(collection_id);11111112 let key_permissions = keys1113 .map(|keys| {1114 keys.into_iter()1115 .filter_map(|key| {1116 permissions1117 .get(&key)1118 .map(|permission| PropertyKeyPermission {1119 key,1120 permission: permission.clone(),1121 })1122 })1123 .collect()1124 })1125 .unwrap_or_else(|| {1126 permissions1127 .into_iter()1128 .map(|(key, permission)| PropertyKeyPermission { key, permission })1129 .collect()1130 });11311132 Ok(key_permissions)1133 }11341135 pub fn toggle_allowlist(1136 collection: &CollectionHandle<T>,1137 sender: &T::CrossAccountId,1138 user: &T::CrossAccountId,1139 allowed: bool,1140 ) -> DispatchResult {1141 collection.check_is_owner_or_admin(sender)?;11421143 // =========11441145 if allowed {1146 <Allowlist<T>>::insert((collection.id, user), true);1147 } else {1148 <Allowlist<T>>::remove((collection.id, user));1149 }11501151 Ok(())1152 }11531154 pub fn toggle_admin(1155 collection: &CollectionHandle<T>,1156 sender: &T::CrossAccountId,1157 user: &T::CrossAccountId,1158 admin: bool,1159 ) -> DispatchResult {1160 collection.check_is_owner(sender)?;11611162 let was_admin = <IsAdmin<T>>::get((collection.id, user));1163 if was_admin == admin {1164 return Ok(());1165 }1166 let amount = <AdminAmount<T>>::get(collection.id);11671168 if admin {1169 let amount = amount1170 .checked_add(1)1171 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1172 ensure!(1173 amount <= Self::collection_admins_limit(),1174 <Error<T>>::CollectionAdminCountExceeded,1175 );11761177 // =========11781179 <AdminAmount<T>>::insert(collection.id, amount);1180 <IsAdmin<T>>::insert((collection.id, user), true);1181 } else {1182 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1183 <IsAdmin<T>>::remove((collection.id, user));1184 }11851186 Ok(())1187 }11881189 pub fn clamp_limits(1190 mode: CollectionMode,1191 old_limit: &CollectionLimits,1192 mut new_limit: CollectionLimits,1193 ) -> Result<CollectionLimits, DispatchError> {1194 let limits = old_limit;1195 limit_default!(old_limit, new_limit,1196 account_token_ownership_limit => ensure!(1197 new_limit <= MAX_TOKEN_OWNERSHIP,1198 <Error<T>>::CollectionLimitBoundsExceeded,1199 ),1200 sponsored_data_size => ensure!(1201 new_limit <= CUSTOM_DATA_LIMIT,1202 <Error<T>>::CollectionLimitBoundsExceeded,1203 ),12041205 sponsored_data_rate_limit => {},1206 token_limit => ensure!(1207 old_limit >= new_limit && new_limit > 0,1208 <Error<T>>::CollectionTokenLimitExceeded1209 ),12101211 sponsor_transfer_timeout(match mode {1212 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1213 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1214 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1215 }) => ensure!(1216 new_limit <= MAX_SPONSOR_TIMEOUT,1217 <Error<T>>::CollectionLimitBoundsExceeded,1218 ),1219 sponsor_approve_timeout => {},1220 owner_can_transfer => ensure!(1221 !limits.owner_can_transfer_instaled() ||1222 old_limit || !new_limit,1223 <Error<T>>::OwnerPermissionsCantBeReverted,1224 ),1225 owner_can_destroy => ensure!(1226 old_limit || !new_limit,1227 <Error<T>>::OwnerPermissionsCantBeReverted,1228 ),1229 transfers_enabled => {},1230 );1231 Ok(new_limit)1232 }12331234 pub fn clamp_permissions(1235 _mode: CollectionMode,1236 old_limit: &CollectionPermissions,1237 mut new_limit: CollectionPermissions,1238 ) -> Result<CollectionPermissions, DispatchError> {1239 limit_default_clone!(old_limit, new_limit,1240 access => {},1241 mint_mode => {},1242 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1243 );1244 Ok(new_limit)1245 }1246}12471248#[macro_export]1249macro_rules! unsupported {1250 () => {1251 Err(<Error<T>>::UnsupportedOperation.into())1252 };1253}12541255/// Worst cases1256pub trait CommonWeightInfo<CrossAccountId> {1257 fn create_item() -> Weight;1258 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1259 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1260 fn burn_item() -> Weight;1261 fn set_collection_properties(amount: u32) -> Weight;1262 fn delete_collection_properties(amount: u32) -> Weight;1263 fn set_token_properties(amount: u32) -> Weight;1264 fn delete_token_properties(amount: u32) -> Weight;1265 fn set_token_property_permissions(amount: u32) -> Weight;1266 fn transfer() -> Weight;1267 fn approve() -> Weight;1268 fn transfer_from() -> Weight;1269 fn burn_from() -> Weight;12701271 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1272 /// whole users's balance1273 ///1274 /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead1275 fn burn_recursively_self_raw() -> Weight;1276 /// Cost of iterating over `amount` children while burning, without counting child burning itself1277 ///1278 /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead1279 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12801281 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1282 Self::burn_recursively_self_raw()1283 .saturating_mul(max_selfs.max(1) as u64)1284 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1285 }1286}12871288pub trait RefungibleExtensionsWeightInfo {1289 fn repartition() -> Weight;1290}12911292pub trait CommonCollectionOperations<T: Config> {1293 fn create_item(1294 &self,1295 sender: T::CrossAccountId,1296 to: T::CrossAccountId,1297 data: CreateItemData,1298 nesting_budget: &dyn Budget,1299 ) -> DispatchResultWithPostInfo;1300 fn create_multiple_items(1301 &self,1302 sender: T::CrossAccountId,1303 to: T::CrossAccountId,1304 data: Vec<CreateItemData>,1305 nesting_budget: &dyn Budget,1306 ) -> DispatchResultWithPostInfo;1307 fn create_multiple_items_ex(1308 &self,1309 sender: T::CrossAccountId,1310 data: CreateItemExData<T::CrossAccountId>,1311 nesting_budget: &dyn Budget,1312 ) -> DispatchResultWithPostInfo;1313 fn burn_item(1314 &self,1315 sender: T::CrossAccountId,1316 token: TokenId,1317 amount: u128,1318 ) -> DispatchResultWithPostInfo;1319 fn burn_item_recursively(1320 &self,1321 sender: T::CrossAccountId,1322 token: TokenId,1323 self_budget: &dyn Budget,1324 breadth_budget: &dyn Budget,1325 ) -> DispatchResultWithPostInfo;1326 fn set_collection_properties(1327 &self,1328 sender: T::CrossAccountId,1329 properties: Vec<Property>,1330 ) -> DispatchResultWithPostInfo;1331 fn delete_collection_properties(1332 &self,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResultWithPostInfo;1336 fn set_token_properties(1337 &self,1338 sender: T::CrossAccountId,1339 token_id: TokenId,1340 property: Vec<Property>,1341 nesting_budget: &dyn Budget,1342 ) -> DispatchResultWithPostInfo;1343 fn delete_token_properties(1344 &self,1345 sender: T::CrossAccountId,1346 token_id: TokenId,1347 property_keys: Vec<PropertyKey>,1348 nesting_budget: &dyn Budget,1349 ) -> DispatchResultWithPostInfo;1350 fn set_token_property_permissions(1351 &self,1352 sender: &T::CrossAccountId,1353 property_permissions: Vec<PropertyKeyPermission>,1354 ) -> DispatchResultWithPostInfo;1355 fn transfer(1356 &self,1357 sender: T::CrossAccountId,1358 to: T::CrossAccountId,1359 token: TokenId,1360 amount: u128,1361 nesting_budget: &dyn Budget,1362 ) -> DispatchResultWithPostInfo;1363 fn approve(1364 &self,1365 sender: T::CrossAccountId,1366 spender: T::CrossAccountId,1367 token: TokenId,1368 amount: u128,1369 ) -> DispatchResultWithPostInfo;1370 fn transfer_from(1371 &self,1372 sender: T::CrossAccountId,1373 from: T::CrossAccountId,1374 to: T::CrossAccountId,1375 token: TokenId,1376 amount: u128,1377 nesting_budget: &dyn Budget,1378 ) -> DispatchResultWithPostInfo;1379 fn burn_from(1380 &self,1381 sender: T::CrossAccountId,1382 from: T::CrossAccountId,1383 token: TokenId,1384 amount: u128,1385 nesting_budget: &dyn Budget,1386 ) -> DispatchResultWithPostInfo;13871388 fn check_nesting(1389 &self,1390 sender: T::CrossAccountId,1391 from: (CollectionId, TokenId),1392 under: TokenId,1393 nesting_budget: &dyn Budget,1394 ) -> DispatchResult;13951396 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13971398 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13991400 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1401 fn collection_tokens(&self) -> Vec<TokenId>;1402 fn token_exists(&self, token: TokenId) -> bool;1403 fn last_token_id(&self) -> TokenId;14041405 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1406 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1407 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1408 /// Amount of unique collection tokens1409 fn total_supply(&self) -> u32;1410 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1411 fn account_balance(&self, account: T::CrossAccountId) -> u32;1412 /// Amount of specific token account have (Applicable to fungible/refungible)1413 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1414 /// Amount of token pieces1415 fn total_pieces(&self, token: TokenId) -> Option<u128>;1416 fn allowance(1417 &self,1418 sender: T::CrossAccountId,1419 spender: T::CrossAccountId,1420 token: TokenId,1421 ) -> u128;1422 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1423}14241425pub trait RefungibleExtensions<T>1426where1427 T: Config,1428{1429 fn repartition(1430 &self,1431 owner: &T::CrossAccountId,1432 token: TokenId,1433 amount: u128,1434 ) -> DispatchResultWithPostInfo;1435}14361437// Flexible enough for implementing CommonCollectionOperations1438pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1439 let post_info = PostDispatchInfo {1440 actual_weight: Some(weight),1441 pays_fee: Pays::Yes,1442 };1443 match res {1444 Ok(()) => Ok(post_info),1445 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1446 }1447}14481449impl<T: Config> From<PropertiesError> for Error<T> {1450 fn from(error: PropertiesError) -> Self {1451 match error {1452 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1453 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1454 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1455 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1456 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1457 }1458 }1459}pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -261,12 +261,9 @@
/// # Arguments
///
/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.
- ///
- /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.
- ///
- /// * token_prefix: UTF-8 string with token prefix.
- ///
- /// * mode: [CollectionMode] collection type and type dependent data.
+ /// * collection_description - UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.
+ /// * token_prefix - UTF-8 string with token prefix.
+ /// * mode - [CollectionMode] collection type and type dependent data.
// returns collection ID
#[weight = <SelfWeightOf<T>>::create_collection()]
#[transactional]
@@ -316,7 +313,7 @@
///
/// # Arguments
///
- /// * collection_id: collection to destroy.
+ /// * collection_id - collection to destroy.
#[weight = <SelfWeightOf<T>>::destroy_collection()]
#[transactional]
pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
@@ -349,7 +346,6 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * address.
#[weight = <SelfWeightOf<T>>::add_to_allow_list()]
#[transactional]
@@ -384,7 +380,6 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * address.
#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]
#[transactional]
@@ -418,7 +413,6 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * new_owner.
#[weight = <SelfWeightOf<T>>::change_collection_owner()]
#[transactional]
@@ -448,10 +442,9 @@
/// * Collection Admin
///
/// # Arguments
- ///
- /// * collection_id: ID of the Collection to add admin for.
///
- /// * new_admin: Address of new admin to add.
+ /// * collection_id - ID of the Collection to add admin for.
+ /// * new_admin - Address of new admin to add.
#[weight = <SelfWeightOf<T>>::add_collection_admin()]
#[transactional]
pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {
@@ -476,9 +469,8 @@
///
/// # Arguments
///
- /// * collection_id: ID of the Collection to remove admin for.
- ///
- /// * account_id: Address of admin to remove.
+ /// * collection_id - ID of the Collection to remove admin for.
+ /// * account_id - Address of admin to remove.
#[weight = <SelfWeightOf<T>>::remove_collection_admin()]
#[transactional]
pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
@@ -504,7 +496,6 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * new_sponsor.
#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]
#[transactional]
@@ -529,7 +520,7 @@
///
/// # Permissions
///
- /// * The sponsor to-be
+ /// * Sponsor-to-be
///
/// # Arguments
///
@@ -593,11 +584,9 @@
///
/// # Arguments
///
- /// * collection_id: ID of the collection.
- ///
- /// * owner: Address, initial owner of the NFT.
- ///
- /// * data: Token data to store on chain.
+ /// * collection_id - ID of the collection.
+ /// * owner - Address, initial owner of the NFT.
+ /// * data - Token data to store on chain.
#[weight = T::CommonWeightInfo::create_item()]
#[transactional]
pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {
@@ -620,11 +609,9 @@
///
/// # Arguments
///
- /// * collection_id: ID of the collection.
- ///
- /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].
- ///
- /// * owner: Address, initial owner of the NFT.
+ /// * collection_id - ID of the collection.
+ /// * owner - Address, initial owner of the NFT.
+ /// * items_data - Array items properties. Each property is an array of bytes itself, see [`create_item`].
#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]
#[transactional]
pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {
@@ -645,8 +632,7 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
+ /// * properties - Vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]
#[transactional]
pub fn set_collection_properties(
@@ -671,8 +657,7 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * property_keys: a vector of keys of the properties to be deleted.
+ /// * property_keys - Vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_collection_properties(
@@ -699,10 +684,8 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * token_id.
- ///
- /// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.
+ /// * properties - Vector of key-value pairs stored as the token's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.
#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]
#[transactional]
pub fn set_token_properties(
@@ -731,10 +714,8 @@
/// # Arguments
///
/// * collection_id.
- ///
/// * token_id.
- ///
- /// * property_keys: a vector of keys of the properties to be deleted.
+ /// * property_keys - Vector of keys of the properties to be deleted.
#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]
#[transactional]
pub fn delete_token_properties(
@@ -761,8 +742,7 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.
+ /// * property_permissions - Vector of permissions for property keys. Keys support Latin letters, `-`, `_`, and `.` as symbols.
#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]
#[transactional]
pub fn set_token_property_permissions(
@@ -789,10 +769,9 @@
/// * MintPermission is enabled (see SetMintPermission method)
///
/// # Arguments
- ///
- /// * collection_id: ID of the collection.
///
- /// * data: explicit item creation data.
+ /// * collection_id - ID of the collection.
+ /// * data - Explicit item creation data.
#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]
#[transactional]
pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {
@@ -810,9 +789,8 @@
///
/// # Arguments
///
- /// * collection_id: ID of the collection.
- ///
- /// * value: New flag value.
+ /// * collection_id - ID of the collection.
+ /// * value - New flag value.
#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]
#[transactional]
pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {
@@ -837,9 +815,8 @@
///
/// # Arguments
///
- /// * collection_id: ID of the collection.
- ///
- /// * item_id: ID of NFT to burn.
+ /// * collection_id - ID of the collection.
+ /// * item_id - ID of NFT to burn.
#[weight = T::CommonWeightInfo::burn_item()]
#[transactional]
pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
@@ -866,12 +843,10 @@
/// * Current NFT Owner.
///
/// # Arguments
- ///
- /// * collection_id: ID of the collection.
///
- /// * item_id: ID of NFT to burn.
- ///
- /// * from: owner of item
+ /// * collection_id - ID of the collection.
+ /// * item_id - ID of NFT to burn.
+ /// * from - The owner of the item from whom it is taken away.
#[weight = T::CommonWeightInfo::burn_from()]
#[transactional]
pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {
@@ -891,16 +866,16 @@
///
/// # Arguments
///
- /// * recipient: Address of token recipient.
+ /// * recipient - Address of token recipient.
///
/// * collection_id.
///
- /// * item_id: ID of the item
+ /// * item_id - ID of the item
/// * Non-Fungible Mode: Required.
/// * Fungible Mode: Ignored.
/// * Re-Fungible Mode: Required.
///
- /// * value: Amount to transfer.
+ /// * value - Amount to transfer.
/// * Non-Fungible Mode: Ignored
/// * Fungible Mode: Must specify transferred amount
/// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)
@@ -923,11 +898,9 @@
///
/// # Arguments
///
- /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).
- ///
+ /// * approved - Address that is approved to transfer this NFT or zero (if needed to remove approval).
/// * collection_id.
- ///
- /// * item_id: ID of the item.
+ /// * item_id - ID of the item.
#[weight = T::CommonWeightInfo::approve()]
#[transactional]
pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {
@@ -947,15 +920,11 @@
///
/// # Arguments
///
- /// * from: Address that owns token.
- ///
- /// * recipient: Address of token recipient.
- ///
+ /// * from - Address that currently owns the token.
+ /// * recipient - Address of the new token-owner-to-be.
/// * collection_id.
- ///
- /// * item_id: ID of the item.
- ///
- /// * value: Amount to transfer.
+ /// * item_id - ID of the item to be transferred.
+ /// * value - Amount to transfer.
#[weight = T::CommonWeightInfo::transfer_from()]
#[transactional]
pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {
@@ -966,7 +935,7 @@
}
/// Set specific limits of a collection. Empty, or None fields mean chain default.
- ///.
+ ///
/// # Permissions
///
/// * Collection Owner
@@ -975,8 +944,7 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * new_limit: The new limits of the collection. They will overwrite the current ones.
+ /// * new_limit - New limits of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_limits(
@@ -1009,8 +977,7 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * new_permission: The new permissions of the collection. They will overwrite the current ones.
+ /// * new_permission - New permissions of the collection. They will overwrite the current ones.
#[weight = <SelfWeightOf<T>>::set_collection_limits()]
#[transactional]
pub fn set_collection_permissions(
@@ -1042,16 +1009,14 @@
/// # Arguments
///
/// * collection_id.
- ///
- /// * token: the ID of the RFT.
- ///
- /// * amount: The new number of parts into which the token shall be partitioned.
+ /// * token_id - ID of the RFT.
+ /// * amount - New number of parts into which the token shall be partitioned.
#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
#[transactional]
pub fn repartition(
origin,
collection_id: CollectionId,
- token: TokenId,
+ token_id: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);