difftreelog
style cargo fmt
in: master
3 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 trasnferred.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}pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -92,7 +92,7 @@
#[pallet::storage]
pub type TokensMinted<T: Config> =
StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
-
+
/// Amount of tokens burnt in a collection.
#[pallet::storage]
pub type TokensBurnt<T: Config> =
@@ -258,7 +258,7 @@
// TODO: ERC721 transfer event
Ok(())
}
-
+
pub fn burn(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -163,9 +163,9 @@
CollectionLimitSet(CollectionId),
/// Collection permissions were set
- ///
+ ///
/// # Arguments
- ///
+ ///
/// * collection_id: Globally unique collection identifier.
CollectionPermissionSet(CollectionId),
}
@@ -219,7 +219,7 @@
/// Collection id (controlled?2), token id (controlled?2)
#[deprecated]
pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
- /// Last sponsoring of token property setting // todo:doc rephrase this and the following
+ /// Last sponsoring of token property setting // todo:doc rephrase this and the following
pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;
/// Last sponsoring of NFT approval in a collection
@@ -294,7 +294,7 @@
/// * Anyone.
///
/// # Arguments
- ///
+ ///
/// * data: explicit create-collection data.
#[weight = <SelfWeightOf<T>>::create_collection()]
#[transactional]
@@ -495,7 +495,7 @@
}
/// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
- ///
+ ///
/// # Permissions
///
/// * Collection Owner
@@ -526,7 +526,7 @@
}
/// Confirm own sponsorship of a collection.
- ///
+ ///
/// # Permissions
///
/// * The sponsor to-be
@@ -699,7 +699,7 @@
/// # 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.
@@ -778,7 +778,7 @@
}
/// Create multiple items inside a collection with explicitly specified initial parameters.
- ///
+ ///
/// # Permissions
///
/// * Collection Owner
@@ -792,7 +792,7 @@
///
/// * collection_id: ID of the collection.
///
- /// * data: explicit item creation data.
+ /// * 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 {
@@ -939,7 +939,7 @@
/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.
///
/// # Permissions
- ///
+ ///
/// * Collection Owner
/// * Collection Admin
/// * Current NFT owner
@@ -968,7 +968,7 @@
/// Set specific limits of a collection. Empty, or None fields mean chain default.
///.
/// # Permissions
- ///
+ ///
/// * Collection Owner
/// * Collection Admin
///
@@ -1002,7 +1002,7 @@
/// Set specific permissions of a collection. Empty, or None fields mean chain default.
///
/// # Permissions
- ///
+ ///
/// * Collection Owner
/// * Collection Admin
///
@@ -1036,15 +1036,15 @@
/// Re-partition a refungible token, while owning all of its parts.
///
/// # Permissions
- ///
+ ///
/// * Token Owner (must own every part)
///
/// # Arguments
///
/// * collection_id.
- ///
- /// * token: the ID of the RFT.
///
+ /// * token: the ID of the RFT.
+ ///
/// * amount: The new number of parts into which the token shall be partitioned.
#[weight = T::RefungibleExtensionsWeightInfo::repartition()]
#[transactional]