difftreelog
fix usage of nesting_budget in pallet-unique
in: master
2 files changed
pallets/unique/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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69 clippy::too_many_arguments,70 clippy::unnecessary_mut_passed,71 clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::pallet_prelude::*;77use frame_system::pallet_prelude::*;78pub use pallet::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87 use frame_support::{88 dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},89 ensure, fail,90 storage::Key,91 BoundedVec,92 };93 use frame_system::{ensure_root, ensure_signed};94 use pallet_common::{95 dispatch::{dispatch_tx, CollectionDispatch},96 CollectionHandle, CommonCollectionOperations, CommonWeightInfo, Pallet as PalletCommon,97 RefungibleExtensionsWeightInfo,98 };99 use pallet_evm::account::CrossAccountId;100 use pallet_structure::weights::WeightInfo as StructureWeightInfo;101 use scale_info::TypeInfo;102 use sp_std::{vec, vec::Vec};103 use up_data_structs::{104 budget, CollectionId, CollectionLimits, CollectionMode, CollectionPermissions,105 CreateCollectionData, CreateItemData, CreateItemExData, Property, PropertyKey,106 PropertyKeyPermission, TokenId, COLLECTION_ADMINS_LIMIT, MAX_COLLECTION_DESCRIPTION_LENGTH,107 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_PROPERTIES_SIZE, MAX_PROPERTIES_PER_ITEM,108 MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, MAX_TOKEN_PREFIX_LENGTH,109 MAX_TOKEN_PROPERTIES_SIZE,110 };111 use weights::WeightInfo;112113 use super::*;114115 /// Errors for the common Unique transactions.116 #[pallet::error]117 pub enum Error<T> {118 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].119 CollectionDecimalPointLimitExceeded,120 /// Length of items properties must be greater than 0.121 EmptyArgument,122 /// Repertition is only supported by refungible collection.123 RepartitionCalledOnNonRefungibleCollection,124 }125126 /// Configuration trait of this pallet.127 #[pallet::config]128 pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {129 /// Weight information for extrinsics in this pallet.130 type WeightInfo: WeightInfo;131132 /// Weight information for common pallet operations.133 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135 type StructureWeightInfo: StructureWeightInfo;136137 /// Weight info information for extra refungible pallet operations.138 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;139 }140141 #[pallet::pallet]142 pub struct Pallet<T>(_);143144 pub type SelfWeightOf<T> = <T as Config>::WeightInfo;145146 // # Used definitions147 //148 // ## User control levels149 //150 // chain-controlled - key is uncontrolled by user151 // i.e autoincrementing index152 // can use non-cryptographic hash153 // real - key is controlled by user154 // but it is hard to generate enough colliding values, i.e owner of signed txs155 // can use non-cryptographic hash156 // controlled - key is completly controlled by users157 // i.e maps with mutable keys158 // should use cryptographic hash159 //160 // ## User control level downgrade reasons161 //162 // ?1 - chain-controlled -> controlled163 // collections/tokens can be destroyed, resulting in massive holes164 // ?2 - chain-controlled -> controlled165 // same as ?1, but can be only added, resulting in easier exploitation166 // ?3 - real -> controlled167 // no confirmation required, so addresses can be easily generated168169 //#region Private members170 /// Used for migrations171 #[pallet::storage]172 pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;173 //#endregion174175 //#region Tokens transfer sponosoring rate limit baskets176 /// (Collection id (controlled?2), who created (real))177 /// TODO: Off chain worker should remove from this map when collection gets removed178 #[pallet::storage]179 #[pallet::getter(fn create_item_busket)]180 pub type CreateItemBasket<T: Config> = StorageMap<181 Hasher = Blake2_128Concat,182 Key = (CollectionId, T::AccountId),183 Value = BlockNumberFor<T>,184 QueryKind = OptionQuery,185 >;186 /// Collection id (controlled?2), token id (controlled?2)187 #[pallet::storage]188 #[pallet::getter(fn nft_transfer_basket)]189 pub type NftTransferBasket<T: Config> = StorageDoubleMap<190 Hasher1 = Blake2_128Concat,191 Key1 = CollectionId,192 Hasher2 = Blake2_128Concat,193 Key2 = TokenId,194 Value = BlockNumberFor<T>,195 QueryKind = OptionQuery,196 >;197 /// Collection id (controlled?2), owning user (real)198 #[pallet::storage]199 #[pallet::getter(fn fungible_transfer_basket)]200 pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<201 Hasher1 = Blake2_128Concat,202 Key1 = CollectionId,203 Hasher2 = Twox64Concat,204 Key2 = T::AccountId,205 Value = BlockNumberFor<T>,206 QueryKind = OptionQuery,207 >;208 /// Collection id (controlled?2), token id (controlled?2)209 #[pallet::storage]210 #[pallet::getter(fn refungible_transfer_basket)]211 pub type ReFungibleTransferBasket<T: Config> = StorageNMap<212 Key = (213 Key<Blake2_128Concat, CollectionId>,214 Key<Blake2_128Concat, TokenId>,215 Key<Twox64Concat, T::AccountId>,216 ),217 Value = BlockNumberFor<T>,218 QueryKind = OptionQuery,219 >;220 //#endregion221222 /// Last sponsoring of token property setting // todo:doc rephrase this and the following223 #[pallet::storage]224 #[pallet::getter(fn token_property_basket)]225 pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<226 Hasher1 = Blake2_128Concat,227 Key1 = CollectionId,228 Hasher2 = Blake2_128Concat,229 Key2 = TokenId,230 Value = BlockNumberFor<T>,231 QueryKind = OptionQuery,232 >;233234 /// Last sponsoring of NFT approval in a collection235 #[pallet::storage]236 #[pallet::getter(fn nft_approve_basket)]237 pub type NftApproveBasket<T: Config> = StorageDoubleMap<238 Hasher1 = Blake2_128Concat,239 Key1 = CollectionId,240 Hasher2 = Blake2_128Concat,241 Key2 = TokenId,242 Value = BlockNumberFor<T>,243 QueryKind = OptionQuery,244 >;245 /// Last sponsoring of fungible tokens approval in a collection246 #[pallet::storage]247 #[pallet::getter(fn fungible_approve_basket)]248 pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<249 Hasher1 = Blake2_128Concat,250 Key1 = CollectionId,251 Hasher2 = Twox64Concat,252 Key2 = T::AccountId,253 Value = BlockNumberFor<T>,254 QueryKind = OptionQuery,255 >;256 /// Last sponsoring of RFT approval in a collection257 #[pallet::storage]258 #[pallet::getter(fn refungible_approve_basket)]259 pub type RefungibleApproveBasket<T: Config> = StorageNMap<260 Key = (261 Key<Blake2_128Concat, CollectionId>,262 Key<Blake2_128Concat, TokenId>,263 Key<Twox64Concat, T::AccountId>,264 ),265 Value = BlockNumberFor<T>,266 QueryKind = OptionQuery,267 >;268269 #[pallet::extra_constants]270 impl<T: Config> Pallet<T> {271 /// A maximum number of levels of depth in the token nesting tree.272 fn nesting_budget() -> u32 {273 5274 }275276 /// Maximal length of a collection name.277 fn max_collection_name_length() -> u32 {278 MAX_COLLECTION_NAME_LENGTH279 }280281 /// Maximal length of a collection description.282 fn max_collection_description_length() -> u32 {283 MAX_COLLECTION_DESCRIPTION_LENGTH284 }285286 /// Maximal length of a token prefix.287 fn max_token_prefix_length() -> u32 {288 MAX_TOKEN_PREFIX_LENGTH289 }290291 /// Maximum admins per collection.292 fn collection_admins_limit() -> u32 {293 COLLECTION_ADMINS_LIMIT294 }295296 /// Maximal length of a property key.297 fn max_property_key_length() -> u32 {298 MAX_PROPERTY_KEY_LENGTH299 }300301 /// Maximal length of a property value.302 fn max_property_value_length() -> u32 {303 MAX_PROPERTY_VALUE_LENGTH304 }305306 /// A maximum number of token properties.307 fn max_properties_per_item() -> u32 {308 MAX_PROPERTIES_PER_ITEM309 }310311 /// Maximum size for all collection properties.312 fn max_collection_properties_size() -> u32 {313 MAX_COLLECTION_PROPERTIES_SIZE314 }315316 /// Maximum size of all token properties.317 fn max_token_properties_size() -> u32 {318 MAX_TOKEN_PROPERTIES_SIZE319 }320321 /// Default NFT collection limit.322 fn nft_default_collection_limits() -> CollectionLimits {323 CollectionLimits::with_default_limits(CollectionMode::NFT)324 }325326 /// Default RFT collection limit.327 fn rft_default_collection_limits() -> CollectionLimits {328 CollectionLimits::with_default_limits(CollectionMode::ReFungible)329 }330331 /// Default FT collection limit.332 fn ft_default_collection_limits() -> CollectionLimits {333 CollectionLimits::with_default_limits(CollectionMode::Fungible(0))334 }335 }336337 /// Type alias to Pallet, to be used by construct_runtime.338 #[pallet::call]339 impl<T: Config> Pallet<T> {340 /// Create a collection of tokens.341 ///342 /// Each Token may have multiple properties encoded as an array of bytes343 /// of certain length. The initial owner of the collection is set344 /// to the address that signed the transaction and can be changed later.345 ///346 /// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.347 ///348 /// # Permissions349 ///350 /// * Anyone - becomes the owner of the new collection.351 ///352 /// # Arguments353 ///354 /// * `collection_name`: Wide-character string with collection name355 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).356 /// * `collection_description`: Wide-character string with collection description357 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).358 /// * `token_prefix`: Byte string containing the token prefix to mark a collection359 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).360 /// * `mode`: Type of items stored in the collection and type dependent data.361 ///362 /// returns collection ID363 ///364 /// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.365 #[pallet::call_index(0)]366 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]367 pub fn create_collection(368 origin: OriginFor<T>,369 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,370 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,371 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,372 mode: CollectionMode,373 ) -> DispatchResult {374 let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {375 name: collection_name,376 description: collection_description,377 token_prefix,378 mode,379 ..Default::default()380 };381 Self::create_collection_ex(origin, data)382 }383384 /// Create a collection with explicit parameters.385 ///386 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.387 ///388 /// # Permissions389 ///390 /// * Anyone - becomes the owner of the new collection.391 ///392 /// # Arguments393 ///394 /// * `data`: Explicit data of a collection used for its creation.395 #[pallet::call_index(1)]396 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]397 pub fn create_collection_ex(398 origin: OriginFor<T>,399 data: CreateCollectionData<T::CrossAccountId>,400 ) -> DispatchResult {401 let sender = ensure_signed(origin)?;402403 // =========404 let sender = T::CrossAccountId::from_sub(sender);405 let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;406407 Ok(())408 }409410 /// Destroy a collection if no tokens exist within.411 ///412 /// # Permissions413 ///414 /// * Collection owner415 ///416 /// # Arguments417 ///418 /// * `collection_id`: Collection to destroy.419 #[pallet::call_index(2)]420 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]421 pub fn destroy_collection(422 origin: OriginFor<T>,423 collection_id: CollectionId,424 ) -> DispatchResult {425 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);426427 Self::destroy_collection_internal(sender, collection_id)428 }429430 /// Add an address to allow list.431 ///432 /// # Permissions433 ///434 /// * Collection owner435 /// * Collection admin436 ///437 /// # Arguments438 ///439 /// * `collection_id`: ID of the modified collection.440 /// * `address`: ID of the address to be added to the allowlist.441 #[pallet::call_index(3)]442 #[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]443 pub fn add_to_allow_list(444 origin: OriginFor<T>,445 collection_id: CollectionId,446 address: T::CrossAccountId,447 ) -> DispatchResult {448 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {449 fail!(<pallet_common::Error<T>>::UnsupportedOperation);450 }451452 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);453 let collection = <CollectionHandle<T>>::try_get(collection_id)?;454 collection.check_is_internal()?;455456 <PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;457458 Ok(())459 }460461 /// Remove an address from allow list.462 ///463 /// # Permissions464 ///465 /// * Collection owner466 /// * Collection admin467 ///468 /// # Arguments469 ///470 /// * `collection_id`: ID of the modified collection.471 /// * `address`: ID of the address to be removed from the allowlist.472 #[pallet::call_index(4)]473 #[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]474 pub fn remove_from_allow_list(475 origin: OriginFor<T>,476 collection_id: CollectionId,477 address: T::CrossAccountId,478 ) -> DispatchResult {479 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {480 fail!(<pallet_common::Error<T>>::UnsupportedOperation);481 }482483 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);484 let collection = <CollectionHandle<T>>::try_get(collection_id)?;485 collection.check_is_internal()?;486487 <PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;488489 Ok(())490 }491492 /// Change the owner of the collection.493 ///494 /// # Permissions495 ///496 /// * Collection owner497 ///498 /// # Arguments499 ///500 /// * `collection_id`: ID of the modified collection.501 /// * `new_owner`: ID of the account that will become the owner.502 #[pallet::call_index(5)]503 #[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]504 pub fn change_collection_owner(505 origin: OriginFor<T>,506 collection_id: CollectionId,507 new_owner: T::AccountId,508 ) -> DispatchResult {509 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {510 fail!(<pallet_common::Error<T>>::UnsupportedOperation);511 }512 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);513 let new_owner = T::CrossAccountId::from_sub(new_owner);514 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;515 target_collection.change_owner(sender, new_owner)516 }517518 /// Add an admin to a collection.519 ///520 /// NFT Collection can be controlled by multiple admin addresses521 /// (some which can also be servers, for example). Admins can issue522 /// and burn NFTs, as well as add and remove other admins,523 /// but cannot change NFT or Collection ownership.524 ///525 /// # Permissions526 ///527 /// * Collection owner528 /// * Collection admin529 ///530 /// # Arguments531 ///532 /// * `collection_id`: ID of the Collection to add an admin for.533 /// * `new_admin`: Address of new admin to add.534 #[pallet::call_index(6)]535 #[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]536 pub fn add_collection_admin(537 origin: OriginFor<T>,538 collection_id: CollectionId,539 new_admin_id: T::CrossAccountId,540 ) -> DispatchResult {541 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {542 fail!(<pallet_common::Error<T>>::UnsupportedOperation);543 }544 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);545 let collection = <CollectionHandle<T>>::try_get(collection_id)?;546 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)547 }548549 /// Remove admin of a collection.550 ///551 /// An admin address can remove itself. List of admins may become empty,552 /// in which case only Collection Owner will be able to add an Admin.553 ///554 /// # Permissions555 ///556 /// * Collection owner557 /// * Collection admin558 ///559 /// # Arguments560 ///561 /// * `collection_id`: ID of the collection to remove the admin for.562 /// * `account_id`: Address of the admin to remove.563 #[pallet::call_index(7)]564 #[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]565 pub fn remove_collection_admin(566 origin: OriginFor<T>,567 collection_id: CollectionId,568 account_id: T::CrossAccountId,569 ) -> DispatchResult {570 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {571 fail!(<pallet_common::Error<T>>::UnsupportedOperation);572 }573 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);574 let collection = <CollectionHandle<T>>::try_get(collection_id)?;575 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)576 }577578 /// Set (invite) a new collection sponsor.579 ///580 /// If successful, confirmation from the sponsor-to-be will be pending.581 ///582 /// # Permissions583 ///584 /// * Collection owner585 /// * Collection admin586 ///587 /// # Arguments588 ///589 /// * `collection_id`: ID of the modified collection.590 /// * `new_sponsor`: ID of the account of the sponsor-to-be.591 #[pallet::call_index(8)]592 #[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]593 pub fn set_collection_sponsor(594 origin: OriginFor<T>,595 collection_id: CollectionId,596 new_sponsor: T::AccountId,597 ) -> DispatchResult {598 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {599 fail!(<pallet_common::Error<T>>::UnsupportedOperation);600 }601 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);602 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;603 target_collection.set_sponsor(&sender, new_sponsor.clone())604 }605606 /// Confirm own sponsorship of a collection, becoming the sponsor.607 ///608 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].609 /// Sponsor can pay the fees of a transaction instead of the sender,610 /// but only within specified limits.611 ///612 /// # Permissions613 ///614 /// * Sponsor-to-be615 ///616 /// # Arguments617 ///618 /// * `collection_id`: ID of the collection with the pending sponsor.619 #[pallet::call_index(9)]620 #[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]621 pub fn confirm_sponsorship(622 origin: OriginFor<T>,623 collection_id: CollectionId,624 ) -> DispatchResult {625 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {626 fail!(<pallet_common::Error<T>>::UnsupportedOperation);627 }628 let sender = ensure_signed(origin)?;629 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;630 target_collection.confirm_sponsorship(&sender)631 }632633 /// Remove a collection's a sponsor, making everyone pay for their own transactions.634 ///635 /// # Permissions636 ///637 /// * Collection owner638 ///639 /// # Arguments640 ///641 /// * `collection_id`: ID of the collection with the sponsor to remove.642 #[pallet::call_index(10)]643 #[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]644 pub fn remove_collection_sponsor(645 origin: OriginFor<T>,646 collection_id: CollectionId,647 ) -> DispatchResult {648 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {649 fail!(<pallet_common::Error<T>>::UnsupportedOperation);650 }651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;653 target_collection.remove_sponsor(&sender)654 }655656 /// Mint an item within a collection.657 ///658 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].659 ///660 /// # Permissions661 ///662 /// * Collection owner663 /// * Collection admin664 /// * Anyone if665 /// * Allow List is enabled, and666 /// * Address is added to allow list, and667 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])668 ///669 /// # Arguments670 ///671 /// * `collection_id`: ID of the collection to which an item would belong.672 /// * `owner`: Address of the initial owner of the item.673 /// * `data`: Token data describing the item to store on chain.674 #[pallet::call_index(11)]675 #[pallet::weight(T::CommonWeightInfo::create_item(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]676 pub fn create_item(677 origin: OriginFor<T>,678 collection_id: CollectionId,679 owner: T::CrossAccountId,680 data: CreateItemData,681 ) -> DispatchResultWithPostInfo {682 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);683 let budget = Self::structure_nesting_budget();684685 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {686 d.create_item(sender, owner, data, &budget)687 })688 }689690 /// Create multiple items within a collection.691 ///692 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].693 ///694 /// # Permissions695 ///696 /// * Collection owner697 /// * Collection admin698 /// * Anyone if699 /// * Allow List is enabled, and700 /// * Address is added to the allow list, and701 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])702 ///703 /// # Arguments704 ///705 /// * `collection_id`: ID of the collection to which the tokens would belong.706 /// * `owner`: Address of the initial owner of the tokens.707 /// * `items_data`: Vector of data describing each item to be created.708 #[pallet::call_index(12)]709 #[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data) + <Pallet<T>>::nesting_budget_predispatch_weight())]710 pub fn create_multiple_items(711 origin: OriginFor<T>,712 collection_id: CollectionId,713 owner: T::CrossAccountId,714 items_data: Vec<CreateItemData>,715 ) -> DispatchResultWithPostInfo {716 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);717 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718 let budget = Self::structure_nesting_budget();719720 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {721 d.create_multiple_items(sender, owner, items_data, &budget)722 })723 }724725 /// Add or change collection properties.726 ///727 /// # Permissions728 ///729 /// * Collection owner730 /// * Collection admin731 ///732 /// # Arguments733 ///734 /// * `collection_id`: ID of the modified collection.735 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.736 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.737 #[pallet::call_index(13)]738 #[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]739 pub fn set_collection_properties(740 origin: OriginFor<T>,741 collection_id: CollectionId,742 properties: Vec<Property>,743 ) -> DispatchResultWithPostInfo {744 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);745746 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);747748 dispatch_tx::<T, _>(collection_id, |d| {749 d.set_collection_properties(sender, properties)750 })751 }752753 /// Delete specified collection properties.754 ///755 /// # Permissions756 ///757 /// * Collection Owner758 /// * Collection Admin759 ///760 /// # Arguments761 ///762 /// * `collection_id`: ID of the modified collection.763 /// * `property_keys`: Vector of keys of the properties to be deleted.764 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.765 #[pallet::call_index(14)]766 #[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]767 pub fn delete_collection_properties(768 origin: OriginFor<T>,769 collection_id: CollectionId,770 property_keys: Vec<PropertyKey>,771 ) -> DispatchResultWithPostInfo {772 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);773774 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775776 dispatch_tx::<T, _>(collection_id, |d| {777 d.delete_collection_properties(&sender, property_keys)778 })779 }780781 /// Add or change token properties according to collection's permissions.782 /// Currently properties only work with NFTs.783 ///784 /// # Permissions785 ///786 /// * Depends on collection's token property permissions and specified property mutability:787 /// * Collection owner788 /// * Collection admin789 /// * Token owner790 ///791 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].792 ///793 /// # Arguments794 ///795 /// * `collection_id: ID of the collection to which the token belongs.796 /// * `token_id`: ID of the modified token.797 /// * `properties`: Vector of key-value pairs stored as the token's metadata.798 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.799 #[pallet::call_index(15)]800 #[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]801 pub fn set_token_properties(802 origin: OriginFor<T>,803 collection_id: CollectionId,804 token_id: TokenId,805 properties: Vec<Property>,806 ) -> DispatchResultWithPostInfo {807 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);808809 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810 let budget = Self::structure_nesting_budget();811812 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {813 d.set_token_properties(sender, token_id, properties, &budget)814 })815 }816817 /// Delete specified token properties. Currently properties only work with NFTs.818 ///819 /// # Permissions820 ///821 /// * Depends on collection's token property permissions and specified property mutability:822 /// * Collection owner823 /// * Collection admin824 /// * Token owner825 ///826 /// # Arguments827 ///828 /// * `collection_id`: ID of the collection to which the token belongs.829 /// * `token_id`: ID of the modified token.830 /// * `property_keys`: Vector of keys of the properties to be deleted.831 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.832 #[pallet::call_index(16)]833 #[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]834 pub fn delete_token_properties(835 origin: OriginFor<T>,836 collection_id: CollectionId,837 token_id: TokenId,838 property_keys: Vec<PropertyKey>,839 ) -> DispatchResultWithPostInfo {840 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);841842 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);843 let budget = Self::structure_nesting_budget();844845 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {846 d.delete_token_properties(sender, token_id, property_keys, &budget)847 })848 }849850 /// Add or change token property permissions of a collection.851 ///852 /// Without a permission for a particular key, a property with that key853 /// cannot be created in a token.854 ///855 /// # Permissions856 ///857 /// * Collection owner858 /// * Collection admin859 ///860 /// # Arguments861 ///862 /// * `collection_id`: ID of the modified collection.863 /// * `property_permissions`: Vector of permissions for property keys.864 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.865 #[pallet::call_index(17)]866 #[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]867 pub fn set_token_property_permissions(868 origin: OriginFor<T>,869 collection_id: CollectionId,870 property_permissions: Vec<PropertyKeyPermission>,871 ) -> DispatchResultWithPostInfo {872 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);873874 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);875876 dispatch_tx::<T, _>(collection_id, |d| {877 d.set_token_property_permissions(&sender, property_permissions)878 })879 }880881 /// Create multiple items within a collection with explicitly specified initial parameters.882 ///883 /// # Permissions884 ///885 /// * Collection owner886 /// * Collection admin887 /// * Anyone if888 /// * Allow List is enabled, and889 /// * Address is added to allow list, and890 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])891 ///892 /// # Arguments893 ///894 /// * `collection_id`: ID of the collection to which the tokens would belong.895 /// * `data`: Explicit item creation data.896 #[pallet::call_index(18)]897 #[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]898 pub fn create_multiple_items_ex(899 origin: OriginFor<T>,900 collection_id: CollectionId,901 data: CreateItemExData<T::CrossAccountId>,902 ) -> DispatchResultWithPostInfo {903 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);904 let budget = Self::structure_nesting_budget();905906 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {907 d.create_multiple_items_ex(sender, data, &budget)908 })909 }910911 /// Completely allow or disallow transfers for a particular collection.912 ///913 /// # Permissions914 ///915 /// * Collection owner916 ///917 /// # Arguments918 ///919 /// * `collection_id`: ID of the collection.920 /// * `value`: New value of the flag, are transfers allowed?921 #[pallet::call_index(19)]922 #[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]923 pub fn set_transfers_enabled_flag(924 origin: OriginFor<T>,925 collection_id: CollectionId,926 value: bool,927 ) -> DispatchResult {928 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {929 fail!(<pallet_common::Error<T>>::UnsupportedOperation);930 }931 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);932 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;933 target_collection.check_is_internal()?;934 target_collection.check_is_owner(&sender)?;935936 // =========937938 target_collection.limits.transfers_enabled = Some(value);939 target_collection.save()940 }941942 /// Destroy an item.943 ///944 /// # Permissions945 ///946 /// * Collection owner947 /// * Collection admin948 /// * Current item owner949 ///950 /// # Arguments951 ///952 /// * `collection_id`: ID of the collection to which the item belongs.953 /// * `item_id`: ID of item to burn.954 /// * `value`: Number of pieces of the item to destroy.955 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.956 /// * Fungible Mode: The desired number of pieces to burn.957 /// * Re-Fungible Mode: The desired number of pieces to burn.958 #[pallet::call_index(20)]959 #[pallet::weight(T::CommonWeightInfo::burn_item())]960 pub fn burn_item(961 origin: OriginFor<T>,962 collection_id: CollectionId,963 item_id: TokenId,964 value: u128,965 ) -> DispatchResultWithPostInfo {966 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);967968 let post_info =969 dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;970 if value == 1 {971 <NftTransferBasket<T>>::remove(collection_id, item_id);972 <NftApproveBasket<T>>::remove(collection_id, item_id);973 }974 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?975 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());976 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));977 Ok(post_info)978 }979980 /// Destroy a token on behalf of the owner as a non-owner account.981 ///982 /// See also: [`approve`][`Pallet::approve`].983 ///984 /// After this method executes, one approval is removed from the total so that985 /// the approved address will not be able to transfer this item again from this owner.986 ///987 /// # Permissions988 ///989 /// * Collection owner990 /// * Collection admin991 /// * Current token owner992 /// * Address approved by current item owner993 ///994 /// # Arguments995 ///996 /// * `from`: The owner of the burning item.997 /// * `collection_id`: ID of the collection to which the item belongs.998 /// * `item_id`: ID of item to burn.999 /// * `value`: Number of pieces to burn.1000 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1001 /// * Fungible Mode: The desired number of pieces to burn.1002 /// * Re-Fungible Mode: The desired number of pieces to burn.1003 #[pallet::call_index(21)]1004 #[pallet::weight(T::CommonWeightInfo::burn_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1005 pub fn burn_from(1006 origin: OriginFor<T>,1007 collection_id: CollectionId,1008 from: T::CrossAccountId,1009 item_id: TokenId,1010 value: u128,1011 ) -> DispatchResultWithPostInfo {1012 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013 let budget = Self::structure_nesting_budget();10141015 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {1016 d.burn_from(sender, from, item_id, value, &budget)1017 })1018 }10191020 /// Change ownership of the token.1021 ///1022 /// # Permissions1023 ///1024 /// * Collection owner1025 /// * Collection admin1026 /// * Current token owner1027 ///1028 /// # Arguments1029 ///1030 /// * `recipient`: Address of token recipient.1031 /// * `collection_id`: ID of the collection the item belongs to.1032 /// * `item_id`: ID of the item.1033 /// * Non-Fungible Mode: Required.1034 /// * Fungible Mode: Ignored.1035 /// * Re-Fungible Mode: Required.1036 ///1037 /// * `value`: Amount to transfer.1038 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1039 /// * Fungible Mode: The desired number of pieces to transfer.1040 /// * Re-Fungible Mode: The desired number of pieces to transfer.1041 #[pallet::call_index(22)]1042 #[pallet::weight(T::CommonWeightInfo::transfer() + <Pallet<T>>::nesting_budget_predispatch_weight())]1043 pub fn transfer(1044 origin: OriginFor<T>,1045 recipient: T::CrossAccountId,1046 collection_id: CollectionId,1047 item_id: TokenId,1048 value: u128,1049 ) -> DispatchResultWithPostInfo {1050 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1051 let budget = Self::structure_nesting_budget();10521053 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {1054 d.transfer(sender, recipient, item_id, value, &budget)1055 })1056 }10571058 /// Allow a non-permissioned address to transfer or burn an item.1059 ///1060 /// # Permissions1061 ///1062 /// * Collection owner1063 /// * Collection admin1064 /// * Current item owner1065 ///1066 /// # Arguments1067 ///1068 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1069 /// * `collection_id`: ID of the collection the item belongs to.1070 /// * `item_id`: ID of the item transactions on which are now approved.1071 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1072 /// Set to 0 to revoke the approval.1073 #[pallet::call_index(23)]1074 #[pallet::weight(T::CommonWeightInfo::approve())]1075 pub fn approve(1076 origin: OriginFor<T>,1077 spender: T::CrossAccountId,1078 collection_id: CollectionId,1079 item_id: TokenId,1080 amount: u128,1081 ) -> DispatchResultWithPostInfo {1082 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10831084 dispatch_tx::<T, _>(collection_id, |d| {1085 d.approve(sender, spender, item_id, amount)1086 })1087 }10881089 /// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1090 ///1091 /// # Permissions1092 ///1093 /// * Collection owner1094 /// * Collection admin1095 /// * Current item owner1096 ///1097 /// # Arguments1098 ///1099 /// * `from`: Owner's account eth mirror1100 /// * `to`: Account to be approved to make specific transactions on non-owned tokens.1101 /// * `collection_id`: ID of the collection the item belongs to.1102 /// * `item_id`: ID of the item transactions on which are now approved.1103 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1104 /// Set to 0 to revoke the approval.1105 #[pallet::call_index(24)]1106 #[pallet::weight(T::CommonWeightInfo::approve_from())]1107 pub fn approve_from(1108 origin: OriginFor<T>,1109 from: T::CrossAccountId,1110 to: T::CrossAccountId,1111 collection_id: CollectionId,1112 item_id: TokenId,1113 amount: u128,1114 ) -> DispatchResultWithPostInfo {1115 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11161117 dispatch_tx::<T, _>(collection_id, |d| {1118 d.approve_from(sender, from, to, item_id, amount)1119 })1120 }11211122 /// Change ownership of an item on behalf of the owner as a non-owner account.1123 ///1124 /// See the [`approve`][`Pallet::approve`] method for additional information.1125 ///1126 /// After this method executes, one approval is removed from the total so that1127 /// the approved address will not be able to transfer this item again from this owner.1128 ///1129 /// # Permissions1130 ///1131 /// * Collection owner1132 /// * Collection admin1133 /// * Current item owner1134 /// * Address approved by current item owner1135 ///1136 /// # Arguments1137 ///1138 /// * `from`: Address that currently owns the token.1139 /// * `recipient`: Address of the new token-owner-to-be.1140 /// * `collection_id`: ID of the collection the item.1141 /// * `item_id`: ID of the item to be transferred.1142 /// * `value`: Amount to transfer.1143 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1144 /// * Fungible Mode: The desired number of pieces to transfer.1145 /// * Re-Fungible Mode: The desired number of pieces to transfer.1146 #[pallet::call_index(25)]1147 #[pallet::weight(T::CommonWeightInfo::transfer_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1148 pub fn transfer_from(1149 origin: OriginFor<T>,1150 from: T::CrossAccountId,1151 recipient: T::CrossAccountId,1152 collection_id: CollectionId,1153 item_id: TokenId,1154 value: u128,1155 ) -> DispatchResultWithPostInfo {1156 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1157 let budget = Self::structure_nesting_budget();11581159 Self::dispatch_tx_with_nesting_budget(collection_id, &budget, |d| {1160 d.transfer_from(sender, from, recipient, item_id, value, &budget)1161 })1162 }11631164 /// Set specific limits of a collection. Empty, or None fields mean chain default.1165 ///1166 /// # Permissions1167 ///1168 /// * Collection owner1169 /// * Collection admin1170 ///1171 /// # Arguments1172 ///1173 /// * `collection_id`: ID of the modified collection.1174 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1175 /// will not overwrite the old ones.1176 #[pallet::call_index(26)]1177 #[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1178 pub fn set_collection_limits(1179 origin: OriginFor<T>,1180 collection_id: CollectionId,1181 new_limit: CollectionLimits,1182 ) -> DispatchResult {1183 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1184 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1185 }1186 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1187 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1188 <PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1189 }11901191 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1192 ///1193 /// # Permissions1194 ///1195 /// * Collection owner1196 /// * Collection admin1197 ///1198 /// # Arguments1199 ///1200 /// * `collection_id`: ID of the modified collection.1201 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1202 /// will not overwrite the old ones.1203 #[pallet::call_index(27)]1204 #[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1205 pub fn set_collection_permissions(1206 origin: OriginFor<T>,1207 collection_id: CollectionId,1208 new_permission: CollectionPermissions,1209 ) -> DispatchResult {1210 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1211 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1212 }1213 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1214 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1215 <PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1216 }12171218 /// Re-partition a refungible token, while owning all of its parts/pieces.1219 ///1220 /// # Permissions1221 ///1222 /// * Token owner (must own every part)1223 ///1224 /// # Arguments1225 ///1226 /// * `collection_id`: ID of the collection the RFT belongs to.1227 /// * `token_id`: ID of the RFT.1228 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1229 #[pallet::call_index(28)]1230 #[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1231 pub fn repartition(1232 origin: OriginFor<T>,1233 collection_id: CollectionId,1234 token_id: TokenId,1235 amount: u128,1236 ) -> DispatchResultWithPostInfo {1237 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1238 dispatch_tx::<T, _>(collection_id, |d| {1239 if let Some(refungible_extensions) = d.refungible_extensions() {1240 refungible_extensions.repartition(&sender, token_id, amount)1241 } else {1242 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1243 }1244 })1245 }12461247 /// Sets or unsets the approval of a given operator.1248 ///1249 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1250 ///1251 /// # Arguments1252 ///1253 /// * `owner`: Token owner1254 /// * `operator`: Operator1255 /// * `approve`: Should operator status be granted or revoked?1256 #[pallet::call_index(29)]1257 #[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1258 pub fn set_allowance_for_all(1259 origin: OriginFor<T>,1260 collection_id: CollectionId,1261 operator: T::CrossAccountId,1262 approve: bool,1263 ) -> DispatchResultWithPostInfo {1264 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1265 dispatch_tx::<T, _>(collection_id, |d| {1266 d.set_allowance_for_all(sender, operator, approve)1267 })1268 }12691270 /// Repairs a collection if the data was somehow corrupted.1271 ///1272 /// # Arguments1273 ///1274 /// * `collection_id`: ID of the collection to repair.1275 #[pallet::call_index(30)]1276 #[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1277 pub fn force_repair_collection(1278 origin: OriginFor<T>,1279 collection_id: CollectionId,1280 ) -> DispatchResult {1281 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1282 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1283 }1284 ensure_root(origin)?;1285 <PalletCommon<T>>::repair_collection(collection_id)1286 }12871288 /// Repairs a token if the data was somehow corrupted.1289 ///1290 /// # Arguments1291 ///1292 /// * `collection_id`: ID of the collection the item belongs to.1293 /// * `item_id`: ID of the item.1294 #[pallet::call_index(31)]1295 #[pallet::weight(T::CommonWeightInfo::force_repair_item())]1296 pub fn force_repair_item(1297 origin: OriginFor<T>,1298 collection_id: CollectionId,1299 item_id: TokenId,1300 ) -> DispatchResultWithPostInfo {1301 ensure_root(origin)?;1302 dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1303 }1304 }13051306 impl<T: Config> Pallet<T> {1307 /// Force set `sponsor` for `collection`.1308 ///1309 /// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1310 /// from the `sponsor` is not required.1311 ///1312 /// # Arguments1313 ///1314 /// * `sponsor`: ID of the account of the sponsor-to-be.1315 /// * `collection_id`: ID of the modified collection.1316 pub fn force_set_sponsor(1317 sponsor: T::AccountId,1318 collection_id: CollectionId,1319 ) -> DispatchResult {1320 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1321 target_collection.force_set_sponsor(sponsor)1322 }13231324 /// Force remove `sponsor` for `collection`.1325 ///1326 /// Differs from `remove_sponsor` in that1327 /// it doesn't require consent from the `owner` of the collection.1328 ///1329 /// # Arguments1330 ///1331 /// * `collection_id`: ID of the modified collection.1332 pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1333 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1334 target_collection.force_remove_sponsor()1335 }13361337 #[inline(always)]1338 pub(crate) fn destroy_collection_internal(1339 sender: T::CrossAccountId,1340 collection_id: CollectionId,1341 ) -> DispatchResult {1342 T::CollectionDispatch::destroy(sender, collection_id)?;13431344 // TODO: basket cleanup should be moved elsewhere1345 // Maybe runtime dispatch.rs should perform it?13461347 let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1348 let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1349 let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13501351 let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1352 let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1353 let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13541355 Ok(())1356 }13571358 fn structure_nesting_budget() -> budget::Value {1359 budget::Value::new(Self::nesting_budget())1360 }13611362 fn nesting_budget_weight(value: &budget::Value) -> Weight {1363 T::StructureWeightInfo::find_parent().saturating_mul(value.remaining() as u64)1364 }13651366 fn nesting_budget_predispatch_weight() -> Weight {1367 Self::nesting_budget_weight(&Self::structure_nesting_budget())1368 }13691370 pub fn dispatch_tx_with_nesting_budget<1371 C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,1372 >(1373 collection: CollectionId,1374 budget: &budget::Value,1375 call: C,1376 ) -> DispatchResultWithPostInfo {1377 let mut result = dispatch_tx::<T, _>(collection, call);13781379 match &mut result {1380 Ok(PostDispatchInfo {1381 actual_weight: Some(weight),1382 ..1383 })1384 | Err(DispatchErrorWithPostInfo {1385 post_info: PostDispatchInfo {1386 actual_weight: Some(weight),1387 ..1388 },1389 ..1390 }) => *weight += Self::nesting_budget_weight(budget),1391 _ => {}1392 }13931394 result1395 }1396 }1397}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//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69 clippy::too_many_arguments,70 clippy::unnecessary_mut_passed,71 clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::pallet_prelude::*;77use frame_system::pallet_prelude::*;78pub use pallet::*;79pub mod eth;8081#[cfg(feature = "runtime-benchmarks")]82pub mod benchmarking;83pub mod weights;8485#[frame_support::pallet]86pub mod pallet {87 use frame_support::{88 dispatch::{DispatchErrorWithPostInfo, DispatchResult, PostDispatchInfo},89 ensure, fail,90 storage::Key,91 BoundedVec,92 };93 use frame_system::{ensure_root, ensure_signed};94 use pallet_common::{95 dispatch::{dispatch_tx, CollectionDispatch},96 CollectionHandle, CommonWeightInfo, Pallet as PalletCommon, RefungibleExtensionsWeightInfo,97 };98 use pallet_evm::account::CrossAccountId;99 use pallet_structure::weights::WeightInfo as StructureWeightInfo;100 use scale_info::TypeInfo;101 use sp_std::{vec, vec::Vec};102 use up_data_structs::{103 budget, CollectionId, CollectionLimits, CollectionMode, CollectionPermissions,104 CreateCollectionData, CreateItemData, CreateItemExData, Property, PropertyKey,105 PropertyKeyPermission, TokenId, COLLECTION_ADMINS_LIMIT, MAX_COLLECTION_DESCRIPTION_LENGTH,106 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_PROPERTIES_SIZE, MAX_PROPERTIES_PER_ITEM,107 MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, MAX_TOKEN_PREFIX_LENGTH,108 MAX_TOKEN_PROPERTIES_SIZE,109 };110 use weights::WeightInfo;111112 use super::*;113114 /// Errors for the common Unique transactions.115 #[pallet::error]116 pub enum Error<T> {117 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].118 CollectionDecimalPointLimitExceeded,119 /// Length of items properties must be greater than 0.120 EmptyArgument,121 /// Repertition is only supported by refungible collection.122 RepartitionCalledOnNonRefungibleCollection,123 }124125 /// Configuration trait of this pallet.126 #[pallet::config]127 pub trait Config: frame_system::Config + pallet_common::Config + Sized + TypeInfo {128 /// Weight information for extrinsics in this pallet.129 type WeightInfo: WeightInfo;130131 /// Weight information for common pallet operations.132 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;133134 type StructureWeightInfo: StructureWeightInfo;135136 /// Weight info information for extra refungible pallet operations.137 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;138 }139140 #[pallet::pallet]141 pub struct Pallet<T>(_);142143 pub type SelfWeightOf<T> = <T as Config>::WeightInfo;144145 // # Used definitions146 //147 // ## User control levels148 //149 // chain-controlled - key is uncontrolled by user150 // i.e autoincrementing index151 // can use non-cryptographic hash152 // real - key is controlled by user153 // but it is hard to generate enough colliding values, i.e owner of signed txs154 // can use non-cryptographic hash155 // controlled - key is completly controlled by users156 // i.e maps with mutable keys157 // should use cryptographic hash158 //159 // ## User control level downgrade reasons160 //161 // ?1 - chain-controlled -> controlled162 // collections/tokens can be destroyed, resulting in massive holes163 // ?2 - chain-controlled -> controlled164 // same as ?1, but can be only added, resulting in easier exploitation165 // ?3 - real -> controlled166 // no confirmation required, so addresses can be easily generated167168 //#region Private members169 /// Used for migrations170 #[pallet::storage]171 pub type ChainVersion<T> = StorageValue<_, u64, ValueQuery>;172 //#endregion173174 //#region Tokens transfer sponosoring rate limit baskets175 /// (Collection id (controlled?2), who created (real))176 /// TODO: Off chain worker should remove from this map when collection gets removed177 #[pallet::storage]178 #[pallet::getter(fn create_item_busket)]179 pub type CreateItemBasket<T: Config> = StorageMap<180 Hasher = Blake2_128Concat,181 Key = (CollectionId, T::AccountId),182 Value = BlockNumberFor<T>,183 QueryKind = OptionQuery,184 >;185 /// Collection id (controlled?2), token id (controlled?2)186 #[pallet::storage]187 #[pallet::getter(fn nft_transfer_basket)]188 pub type NftTransferBasket<T: Config> = StorageDoubleMap<189 Hasher1 = Blake2_128Concat,190 Key1 = CollectionId,191 Hasher2 = Blake2_128Concat,192 Key2 = TokenId,193 Value = BlockNumberFor<T>,194 QueryKind = OptionQuery,195 >;196 /// Collection id (controlled?2), owning user (real)197 #[pallet::storage]198 #[pallet::getter(fn fungible_transfer_basket)]199 pub type FungibleTransferBasket<T: Config> = StorageDoubleMap<200 Hasher1 = Blake2_128Concat,201 Key1 = CollectionId,202 Hasher2 = Twox64Concat,203 Key2 = T::AccountId,204 Value = BlockNumberFor<T>,205 QueryKind = OptionQuery,206 >;207 /// Collection id (controlled?2), token id (controlled?2)208 #[pallet::storage]209 #[pallet::getter(fn refungible_transfer_basket)]210 pub type ReFungibleTransferBasket<T: Config> = StorageNMap<211 Key = (212 Key<Blake2_128Concat, CollectionId>,213 Key<Blake2_128Concat, TokenId>,214 Key<Twox64Concat, T::AccountId>,215 ),216 Value = BlockNumberFor<T>,217 QueryKind = OptionQuery,218 >;219 //#endregion220221 /// Last sponsoring of token property setting // todo:doc rephrase this and the following222 #[pallet::storage]223 #[pallet::getter(fn token_property_basket)]224 pub type TokenPropertyBasket<T: Config> = StorageDoubleMap<225 Hasher1 = Blake2_128Concat,226 Key1 = CollectionId,227 Hasher2 = Blake2_128Concat,228 Key2 = TokenId,229 Value = BlockNumberFor<T>,230 QueryKind = OptionQuery,231 >;232233 /// Last sponsoring of NFT approval in a collection234 #[pallet::storage]235 #[pallet::getter(fn nft_approve_basket)]236 pub type NftApproveBasket<T: Config> = StorageDoubleMap<237 Hasher1 = Blake2_128Concat,238 Key1 = CollectionId,239 Hasher2 = Blake2_128Concat,240 Key2 = TokenId,241 Value = BlockNumberFor<T>,242 QueryKind = OptionQuery,243 >;244 /// Last sponsoring of fungible tokens approval in a collection245 #[pallet::storage]246 #[pallet::getter(fn fungible_approve_basket)]247 pub type FungibleApproveBasket<T: Config> = StorageDoubleMap<248 Hasher1 = Blake2_128Concat,249 Key1 = CollectionId,250 Hasher2 = Twox64Concat,251 Key2 = T::AccountId,252 Value = BlockNumberFor<T>,253 QueryKind = OptionQuery,254 >;255 /// Last sponsoring of RFT approval in a collection256 #[pallet::storage]257 #[pallet::getter(fn refungible_approve_basket)]258 pub type RefungibleApproveBasket<T: Config> = StorageNMap<259 Key = (260 Key<Blake2_128Concat, CollectionId>,261 Key<Blake2_128Concat, TokenId>,262 Key<Twox64Concat, T::AccountId>,263 ),264 Value = BlockNumberFor<T>,265 QueryKind = OptionQuery,266 >;267268 #[pallet::extra_constants]269 impl<T: Config> Pallet<T> {270 /// A maximum number of levels of depth in the token nesting tree.271 fn nesting_budget() -> u32 {272 5273 }274275 /// Maximal length of a collection name.276 fn max_collection_name_length() -> u32 {277 MAX_COLLECTION_NAME_LENGTH278 }279280 /// Maximal length of a collection description.281 fn max_collection_description_length() -> u32 {282 MAX_COLLECTION_DESCRIPTION_LENGTH283 }284285 /// Maximal length of a token prefix.286 fn max_token_prefix_length() -> u32 {287 MAX_TOKEN_PREFIX_LENGTH288 }289290 /// Maximum admins per collection.291 fn collection_admins_limit() -> u32 {292 COLLECTION_ADMINS_LIMIT293 }294295 /// Maximal length of a property key.296 fn max_property_key_length() -> u32 {297 MAX_PROPERTY_KEY_LENGTH298 }299300 /// Maximal length of a property value.301 fn max_property_value_length() -> u32 {302 MAX_PROPERTY_VALUE_LENGTH303 }304305 /// A maximum number of token properties.306 fn max_properties_per_item() -> u32 {307 MAX_PROPERTIES_PER_ITEM308 }309310 /// Maximum size for all collection properties.311 fn max_collection_properties_size() -> u32 {312 MAX_COLLECTION_PROPERTIES_SIZE313 }314315 /// Maximum size of all token properties.316 fn max_token_properties_size() -> u32 {317 MAX_TOKEN_PROPERTIES_SIZE318 }319320 /// Default NFT collection limit.321 fn nft_default_collection_limits() -> CollectionLimits {322 CollectionLimits::with_default_limits(CollectionMode::NFT)323 }324325 /// Default RFT collection limit.326 fn rft_default_collection_limits() -> CollectionLimits {327 CollectionLimits::with_default_limits(CollectionMode::ReFungible)328 }329330 /// Default FT collection limit.331 fn ft_default_collection_limits() -> CollectionLimits {332 CollectionLimits::with_default_limits(CollectionMode::Fungible(0))333 }334 }335336 /// Type alias to Pallet, to be used by construct_runtime.337 #[pallet::call]338 impl<T: Config> Pallet<T> {339 /// Create a collection of tokens.340 ///341 /// Each Token may have multiple properties encoded as an array of bytes342 /// of certain length. The initial owner of the collection is set343 /// to the address that signed the transaction and can be changed later.344 ///345 /// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.346 ///347 /// # Permissions348 ///349 /// * Anyone - becomes the owner of the new collection.350 ///351 /// # Arguments352 ///353 /// * `collection_name`: Wide-character string with collection name354 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).355 /// * `collection_description`: Wide-character string with collection description356 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).357 /// * `token_prefix`: Byte string containing the token prefix to mark a collection358 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).359 /// * `mode`: Type of items stored in the collection and type dependent data.360 ///361 /// returns collection ID362 ///363 /// Deprecated: `create_collection_ex` is more up-to-date and advanced, prefer it instead.364 #[pallet::call_index(0)]365 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]366 pub fn create_collection(367 origin: OriginFor<T>,368 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,369 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,370 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,371 mode: CollectionMode,372 ) -> DispatchResult {373 let data: CreateCollectionData<T::CrossAccountId> = CreateCollectionData {374 name: collection_name,375 description: collection_description,376 token_prefix,377 mode,378 ..Default::default()379 };380 Self::create_collection_ex(origin, data)381 }382383 /// Create a collection with explicit parameters.384 ///385 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.386 ///387 /// # Permissions388 ///389 /// * Anyone - becomes the owner of the new collection.390 ///391 /// # Arguments392 ///393 /// * `data`: Explicit data of a collection used for its creation.394 #[pallet::call_index(1)]395 #[pallet::weight(<SelfWeightOf<T>>::create_collection())]396 pub fn create_collection_ex(397 origin: OriginFor<T>,398 data: CreateCollectionData<T::CrossAccountId>,399 ) -> DispatchResult {400 let sender = ensure_signed(origin)?;401402 // =========403 let sender = T::CrossAccountId::from_sub(sender);404 let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?;405406 Ok(())407 }408409 /// Destroy a collection if no tokens exist within.410 ///411 /// # Permissions412 ///413 /// * Collection owner414 ///415 /// # Arguments416 ///417 /// * `collection_id`: Collection to destroy.418 #[pallet::call_index(2)]419 #[pallet::weight(<SelfWeightOf<T>>::destroy_collection())]420 pub fn destroy_collection(421 origin: OriginFor<T>,422 collection_id: CollectionId,423 ) -> DispatchResult {424 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);425426 Self::destroy_collection_internal(sender, collection_id)427 }428429 /// Add an address to allow list.430 ///431 /// # Permissions432 ///433 /// * Collection owner434 /// * Collection admin435 ///436 /// # Arguments437 ///438 /// * `collection_id`: ID of the modified collection.439 /// * `address`: ID of the address to be added to the allowlist.440 #[pallet::call_index(3)]441 #[pallet::weight(<SelfWeightOf<T>>::add_to_allow_list())]442 pub fn add_to_allow_list(443 origin: OriginFor<T>,444 collection_id: CollectionId,445 address: T::CrossAccountId,446 ) -> DispatchResult {447 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {448 fail!(<pallet_common::Error<T>>::UnsupportedOperation);449 }450451 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);452 let collection = <CollectionHandle<T>>::try_get(collection_id)?;453 collection.check_is_internal()?;454455 <PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, true)?;456457 Ok(())458 }459460 /// Remove an address from allow list.461 ///462 /// # Permissions463 ///464 /// * Collection owner465 /// * Collection admin466 ///467 /// # Arguments468 ///469 /// * `collection_id`: ID of the modified collection.470 /// * `address`: ID of the address to be removed from the allowlist.471 #[pallet::call_index(4)]472 #[pallet::weight(<SelfWeightOf<T>>::remove_from_allow_list())]473 pub fn remove_from_allow_list(474 origin: OriginFor<T>,475 collection_id: CollectionId,476 address: T::CrossAccountId,477 ) -> DispatchResult {478 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {479 fail!(<pallet_common::Error<T>>::UnsupportedOperation);480 }481482 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);483 let collection = <CollectionHandle<T>>::try_get(collection_id)?;484 collection.check_is_internal()?;485486 <PalletCommon<T>>::toggle_allowlist(&collection, &sender, &address, false)?;487488 Ok(())489 }490491 /// Change the owner of the collection.492 ///493 /// # Permissions494 ///495 /// * Collection owner496 ///497 /// # Arguments498 ///499 /// * `collection_id`: ID of the modified collection.500 /// * `new_owner`: ID of the account that will become the owner.501 #[pallet::call_index(5)]502 #[pallet::weight(<SelfWeightOf<T>>::change_collection_owner())]503 pub fn change_collection_owner(504 origin: OriginFor<T>,505 collection_id: CollectionId,506 new_owner: T::AccountId,507 ) -> DispatchResult {508 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {509 fail!(<pallet_common::Error<T>>::UnsupportedOperation);510 }511 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);512 let new_owner = T::CrossAccountId::from_sub(new_owner);513 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;514 target_collection.change_owner(sender, new_owner)515 }516517 /// Add an admin to a collection.518 ///519 /// NFT Collection can be controlled by multiple admin addresses520 /// (some which can also be servers, for example). Admins can issue521 /// and burn NFTs, as well as add and remove other admins,522 /// but cannot change NFT or Collection ownership.523 ///524 /// # Permissions525 ///526 /// * Collection owner527 /// * Collection admin528 ///529 /// # Arguments530 ///531 /// * `collection_id`: ID of the Collection to add an admin for.532 /// * `new_admin`: Address of new admin to add.533 #[pallet::call_index(6)]534 #[pallet::weight(<SelfWeightOf<T>>::add_collection_admin())]535 pub fn add_collection_admin(536 origin: OriginFor<T>,537 collection_id: CollectionId,538 new_admin_id: T::CrossAccountId,539 ) -> DispatchResult {540 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {541 fail!(<pallet_common::Error<T>>::UnsupportedOperation);542 }543 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);544 let collection = <CollectionHandle<T>>::try_get(collection_id)?;545 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)546 }547548 /// Remove admin of a collection.549 ///550 /// An admin address can remove itself. List of admins may become empty,551 /// in which case only Collection Owner will be able to add an Admin.552 ///553 /// # Permissions554 ///555 /// * Collection owner556 /// * Collection admin557 ///558 /// # Arguments559 ///560 /// * `collection_id`: ID of the collection to remove the admin for.561 /// * `account_id`: Address of the admin to remove.562 #[pallet::call_index(7)]563 #[pallet::weight(<SelfWeightOf<T>>::remove_collection_admin())]564 pub fn remove_collection_admin(565 origin: OriginFor<T>,566 collection_id: CollectionId,567 account_id: T::CrossAccountId,568 ) -> DispatchResult {569 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {570 fail!(<pallet_common::Error<T>>::UnsupportedOperation);571 }572 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);573 let collection = <CollectionHandle<T>>::try_get(collection_id)?;574 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)575 }576577 /// Set (invite) a new collection sponsor.578 ///579 /// If successful, confirmation from the sponsor-to-be will be pending.580 ///581 /// # Permissions582 ///583 /// * Collection owner584 /// * Collection admin585 ///586 /// # Arguments587 ///588 /// * `collection_id`: ID of the modified collection.589 /// * `new_sponsor`: ID of the account of the sponsor-to-be.590 #[pallet::call_index(8)]591 #[pallet::weight(<SelfWeightOf<T>>::set_collection_sponsor())]592 pub fn set_collection_sponsor(593 origin: OriginFor<T>,594 collection_id: CollectionId,595 new_sponsor: T::AccountId,596 ) -> DispatchResult {597 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {598 fail!(<pallet_common::Error<T>>::UnsupportedOperation);599 }600 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602 target_collection.set_sponsor(&sender, new_sponsor.clone())603 }604605 /// Confirm own sponsorship of a collection, becoming the sponsor.606 ///607 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].608 /// Sponsor can pay the fees of a transaction instead of the sender,609 /// but only within specified limits.610 ///611 /// # Permissions612 ///613 /// * Sponsor-to-be614 ///615 /// # Arguments616 ///617 /// * `collection_id`: ID of the collection with the pending sponsor.618 #[pallet::call_index(9)]619 #[pallet::weight(<SelfWeightOf<T>>::confirm_sponsorship())]620 pub fn confirm_sponsorship(621 origin: OriginFor<T>,622 collection_id: CollectionId,623 ) -> DispatchResult {624 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {625 fail!(<pallet_common::Error<T>>::UnsupportedOperation);626 }627 let sender = ensure_signed(origin)?;628 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;629 target_collection.confirm_sponsorship(&sender)630 }631632 /// Remove a collection's a sponsor, making everyone pay for their own transactions.633 ///634 /// # Permissions635 ///636 /// * Collection owner637 ///638 /// # Arguments639 ///640 /// * `collection_id`: ID of the collection with the sponsor to remove.641 #[pallet::call_index(10)]642 #[pallet::weight(<SelfWeightOf<T>>::remove_collection_sponsor())]643 pub fn remove_collection_sponsor(644 origin: OriginFor<T>,645 collection_id: CollectionId,646 ) -> DispatchResult {647 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {648 fail!(<pallet_common::Error<T>>::UnsupportedOperation);649 }650 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);651 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;652 target_collection.remove_sponsor(&sender)653 }654655 /// Mint an item within a collection.656 ///657 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].658 ///659 /// # Permissions660 ///661 /// * Collection owner662 /// * Collection admin663 /// * Anyone if664 /// * Allow List is enabled, and665 /// * Address is added to allow list, and666 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])667 ///668 /// # Arguments669 ///670 /// * `collection_id`: ID of the collection to which an item would belong.671 /// * `owner`: Address of the initial owner of the item.672 /// * `data`: Token data describing the item to store on chain.673 #[pallet::call_index(11)]674 #[pallet::weight(T::CommonWeightInfo::create_item(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]675 pub fn create_item(676 origin: OriginFor<T>,677 collection_id: CollectionId,678 owner: T::CrossAccountId,679 data: CreateItemData,680 ) -> DispatchResultWithPostInfo {681 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682 let budget = Self::structure_nesting_budget();683684 Self::refund_nesting_budget(685 dispatch_tx::<T, _>(collection_id, |d| {686 d.create_item(sender, owner, data, &budget)687 }),688 budget,689 )690 }691692 /// Create multiple items within a collection.693 ///694 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].695 ///696 /// # Permissions697 ///698 /// * Collection owner699 /// * Collection admin700 /// * Anyone if701 /// * Allow List is enabled, and702 /// * Address is added to the allow list, and703 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])704 ///705 /// # Arguments706 ///707 /// * `collection_id`: ID of the collection to which the tokens would belong.708 /// * `owner`: Address of the initial owner of the tokens.709 /// * `items_data`: Vector of data describing each item to be created.710 #[pallet::call_index(12)]711 #[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data) + <Pallet<T>>::nesting_budget_predispatch_weight())]712 pub fn create_multiple_items(713 origin: OriginFor<T>,714 collection_id: CollectionId,715 owner: T::CrossAccountId,716 items_data: Vec<CreateItemData>,717 ) -> DispatchResultWithPostInfo {718 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);719 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);720 let budget = Self::structure_nesting_budget();721722 Self::refund_nesting_budget(723 dispatch_tx::<T, _>(collection_id, |d| {724 d.create_multiple_items(sender, owner, items_data, &budget)725 }),726 budget,727 )728 }729730 /// Add or change collection properties.731 ///732 /// # Permissions733 ///734 /// * Collection owner735 /// * Collection admin736 ///737 /// # Arguments738 ///739 /// * `collection_id`: ID of the modified collection.740 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.741 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.742 #[pallet::call_index(13)]743 #[pallet::weight(T::CommonWeightInfo::set_collection_properties(properties.len() as u32))]744 pub fn set_collection_properties(745 origin: OriginFor<T>,746 collection_id: CollectionId,747 properties: Vec<Property>,748 ) -> DispatchResultWithPostInfo {749 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);750751 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);752753 dispatch_tx::<T, _>(collection_id, |d| {754 d.set_collection_properties(sender, properties)755 })756 }757758 /// Delete specified collection properties.759 ///760 /// # Permissions761 ///762 /// * Collection Owner763 /// * Collection Admin764 ///765 /// # Arguments766 ///767 /// * `collection_id`: ID of the modified collection.768 /// * `property_keys`: Vector of keys of the properties to be deleted.769 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.770 #[pallet::call_index(14)]771 #[pallet::weight(T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32))]772 pub fn delete_collection_properties(773 origin: OriginFor<T>,774 collection_id: CollectionId,775 property_keys: Vec<PropertyKey>,776 ) -> DispatchResultWithPostInfo {777 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);778779 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);780781 dispatch_tx::<T, _>(collection_id, |d| {782 d.delete_collection_properties(&sender, property_keys)783 })784 }785786 /// Add or change token properties according to collection's permissions.787 /// Currently properties only work with NFTs.788 ///789 /// # Permissions790 ///791 /// * Depends on collection's token property permissions and specified property mutability:792 /// * Collection owner793 /// * Collection admin794 /// * Token owner795 ///796 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].797 ///798 /// # Arguments799 ///800 /// * `collection_id: ID of the collection to which the token belongs.801 /// * `token_id`: ID of the modified token.802 /// * `properties`: Vector of key-value pairs stored as the token's metadata.803 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.804 #[pallet::call_index(15)]805 #[pallet::weight(T::CommonWeightInfo::set_token_properties(properties.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]806 pub fn set_token_properties(807 origin: OriginFor<T>,808 collection_id: CollectionId,809 token_id: TokenId,810 properties: Vec<Property>,811 ) -> DispatchResultWithPostInfo {812 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);813814 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);815 let budget = Self::structure_nesting_budget();816817 Self::refund_nesting_budget(818 dispatch_tx::<T, _>(collection_id, |d| {819 d.set_token_properties(sender, token_id, properties, &budget)820 }),821 budget,822 )823 }824825 /// Delete specified token properties. Currently properties only work with NFTs.826 ///827 /// # Permissions828 ///829 /// * Depends on collection's token property permissions and specified property mutability:830 /// * Collection owner831 /// * Collection admin832 /// * Token owner833 ///834 /// # Arguments835 ///836 /// * `collection_id`: ID of the collection to which the token belongs.837 /// * `token_id`: ID of the modified token.838 /// * `property_keys`: Vector of keys of the properties to be deleted.839 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.840 #[pallet::call_index(16)]841 #[pallet::weight(T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32) + <Pallet<T>>::nesting_budget_predispatch_weight())]842 pub fn delete_token_properties(843 origin: OriginFor<T>,844 collection_id: CollectionId,845 token_id: TokenId,846 property_keys: Vec<PropertyKey>,847 ) -> DispatchResultWithPostInfo {848 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);849850 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);851 let budget = Self::structure_nesting_budget();852853 Self::refund_nesting_budget(854 dispatch_tx::<T, _>(collection_id, |d| {855 d.delete_token_properties(sender, token_id, property_keys, &budget)856 }),857 budget,858 )859 }860861 /// Add or change token property permissions of a collection.862 ///863 /// Without a permission for a particular key, a property with that key864 /// cannot be created in a token.865 ///866 /// # Permissions867 ///868 /// * Collection owner869 /// * Collection admin870 ///871 /// # Arguments872 ///873 /// * `collection_id`: ID of the modified collection.874 /// * `property_permissions`: Vector of permissions for property keys.875 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.876 #[pallet::call_index(17)]877 #[pallet::weight(T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32))]878 pub fn set_token_property_permissions(879 origin: OriginFor<T>,880 collection_id: CollectionId,881 property_permissions: Vec<PropertyKeyPermission>,882 ) -> DispatchResultWithPostInfo {883 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);884885 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886887 dispatch_tx::<T, _>(collection_id, |d| {888 d.set_token_property_permissions(&sender, property_permissions)889 })890 }891892 /// Create multiple items within a collection with explicitly specified initial parameters.893 ///894 /// # Permissions895 ///896 /// * Collection owner897 /// * Collection admin898 /// * Anyone if899 /// * Allow List is enabled, and900 /// * Address is added to allow list, and901 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])902 ///903 /// # Arguments904 ///905 /// * `collection_id`: ID of the collection to which the tokens would belong.906 /// * `data`: Explicit item creation data.907 #[pallet::call_index(18)]908 #[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data) + <Pallet<T>>::nesting_budget_predispatch_weight())]909 pub fn create_multiple_items_ex(910 origin: OriginFor<T>,911 collection_id: CollectionId,912 data: CreateItemExData<T::CrossAccountId>,913 ) -> DispatchResultWithPostInfo {914 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915 let budget = Self::structure_nesting_budget();916917 Self::refund_nesting_budget(918 dispatch_tx::<T, _>(collection_id, |d| {919 d.create_multiple_items_ex(sender, data, &budget)920 }),921 budget,922 )923 }924925 /// Completely allow or disallow transfers for a particular collection.926 ///927 /// # Permissions928 ///929 /// * Collection owner930 ///931 /// # Arguments932 ///933 /// * `collection_id`: ID of the collection.934 /// * `value`: New value of the flag, are transfers allowed?935 #[pallet::call_index(19)]936 #[pallet::weight(<SelfWeightOf<T>>::set_transfers_enabled_flag())]937 pub fn set_transfers_enabled_flag(938 origin: OriginFor<T>,939 collection_id: CollectionId,940 value: bool,941 ) -> DispatchResult {942 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {943 fail!(<pallet_common::Error<T>>::UnsupportedOperation);944 }945 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);946 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;947 target_collection.check_is_internal()?;948 target_collection.check_is_owner(&sender)?;949950 // =========951952 target_collection.limits.transfers_enabled = Some(value);953 target_collection.save()954 }955956 /// Destroy an item.957 ///958 /// # Permissions959 ///960 /// * Collection owner961 /// * Collection admin962 /// * Current item owner963 ///964 /// # Arguments965 ///966 /// * `collection_id`: ID of the collection to which the item belongs.967 /// * `item_id`: ID of item to burn.968 /// * `value`: Number of pieces of the item to destroy.969 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.970 /// * Fungible Mode: The desired number of pieces to burn.971 /// * Re-Fungible Mode: The desired number of pieces to burn.972 #[pallet::call_index(20)]973 #[pallet::weight(T::CommonWeightInfo::burn_item())]974 pub fn burn_item(975 origin: OriginFor<T>,976 collection_id: CollectionId,977 item_id: TokenId,978 value: u128,979 ) -> DispatchResultWithPostInfo {980 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);981982 let post_info =983 dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;984 if value == 1 {985 <NftTransferBasket<T>>::remove(collection_id, item_id);986 <NftApproveBasket<T>>::remove(collection_id, item_id);987 }988 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?989 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());990 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));991 Ok(post_info)992 }993994 /// Destroy a token on behalf of the owner as a non-owner account.995 ///996 /// See also: [`approve`][`Pallet::approve`].997 ///998 /// After this method executes, one approval is removed from the total so that999 /// the approved address will not be able to transfer this item again from this owner.1000 ///1001 /// # Permissions1002 ///1003 /// * Collection owner1004 /// * Collection admin1005 /// * Current token owner1006 /// * Address approved by current item owner1007 ///1008 /// # Arguments1009 ///1010 /// * `from`: The owner of the burning item.1011 /// * `collection_id`: ID of the collection to which the item belongs.1012 /// * `item_id`: ID of item to burn.1013 /// * `value`: Number of pieces to burn.1014 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1015 /// * Fungible Mode: The desired number of pieces to burn.1016 /// * Re-Fungible Mode: The desired number of pieces to burn.1017 #[pallet::call_index(21)]1018 #[pallet::weight(T::CommonWeightInfo::burn_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1019 pub fn burn_from(1020 origin: OriginFor<T>,1021 collection_id: CollectionId,1022 from: T::CrossAccountId,1023 item_id: TokenId,1024 value: u128,1025 ) -> DispatchResultWithPostInfo {1026 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1027 let budget = Self::structure_nesting_budget();10281029 Self::refund_nesting_budget(1030 dispatch_tx::<T, _>(collection_id, |d| {1031 d.burn_from(sender, from, item_id, value, &budget)1032 }),1033 budget,1034 )1035 }10361037 /// Change ownership of the token.1038 ///1039 /// # Permissions1040 ///1041 /// * Collection owner1042 /// * Collection admin1043 /// * Current token owner1044 ///1045 /// # Arguments1046 ///1047 /// * `recipient`: Address of token recipient.1048 /// * `collection_id`: ID of the collection the item belongs to.1049 /// * `item_id`: ID of the item.1050 /// * Non-Fungible Mode: Required.1051 /// * Fungible Mode: Ignored.1052 /// * Re-Fungible Mode: Required.1053 ///1054 /// * `value`: Amount to transfer.1055 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1056 /// * Fungible Mode: The desired number of pieces to transfer.1057 /// * Re-Fungible Mode: The desired number of pieces to transfer.1058 #[pallet::call_index(22)]1059 #[pallet::weight(T::CommonWeightInfo::transfer() + <Pallet<T>>::nesting_budget_predispatch_weight())]1060 pub fn transfer(1061 origin: OriginFor<T>,1062 recipient: T::CrossAccountId,1063 collection_id: CollectionId,1064 item_id: TokenId,1065 value: u128,1066 ) -> DispatchResultWithPostInfo {1067 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1068 let budget = Self::structure_nesting_budget();10691070 Self::refund_nesting_budget(1071 dispatch_tx::<T, _>(collection_id, |d| {1072 d.transfer(sender, recipient, item_id, value, &budget)1073 }),1074 budget,1075 )1076 }10771078 /// Allow a non-permissioned address to transfer or burn an item.1079 ///1080 /// # Permissions1081 ///1082 /// * Collection owner1083 /// * Collection admin1084 /// * Current item owner1085 ///1086 /// # Arguments1087 ///1088 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.1089 /// * `collection_id`: ID of the collection the item belongs to.1090 /// * `item_id`: ID of the item transactions on which are now approved.1091 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1092 /// Set to 0 to revoke the approval.1093 #[pallet::call_index(23)]1094 #[pallet::weight(T::CommonWeightInfo::approve())]1095 pub fn approve(1096 origin: OriginFor<T>,1097 spender: T::CrossAccountId,1098 collection_id: CollectionId,1099 item_id: TokenId,1100 amount: u128,1101 ) -> DispatchResultWithPostInfo {1102 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11031104 dispatch_tx::<T, _>(collection_id, |d| {1105 d.approve(sender, spender, item_id, amount)1106 })1107 }11081109 /// Allow a non-permissioned address to transfer or burn an item from owner's eth mirror.1110 ///1111 /// # Permissions1112 ///1113 /// * Collection owner1114 /// * Collection admin1115 /// * Current item owner1116 ///1117 /// # Arguments1118 ///1119 /// * `from`: Owner's account eth mirror1120 /// * `to`: Account to be approved to make specific transactions on non-owned tokens.1121 /// * `collection_id`: ID of the collection the item belongs to.1122 /// * `item_id`: ID of the item transactions on which are now approved.1123 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).1124 /// Set to 0 to revoke the approval.1125 #[pallet::call_index(24)]1126 #[pallet::weight(T::CommonWeightInfo::approve_from())]1127 pub fn approve_from(1128 origin: OriginFor<T>,1129 from: T::CrossAccountId,1130 to: T::CrossAccountId,1131 collection_id: CollectionId,1132 item_id: TokenId,1133 amount: u128,1134 ) -> DispatchResultWithPostInfo {1135 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11361137 dispatch_tx::<T, _>(collection_id, |d| {1138 d.approve_from(sender, from, to, item_id, amount)1139 })1140 }11411142 /// Change ownership of an item on behalf of the owner as a non-owner account.1143 ///1144 /// See the [`approve`][`Pallet::approve`] method for additional information.1145 ///1146 /// After this method executes, one approval is removed from the total so that1147 /// the approved address will not be able to transfer this item again from this owner.1148 ///1149 /// # Permissions1150 ///1151 /// * Collection owner1152 /// * Collection admin1153 /// * Current item owner1154 /// * Address approved by current item owner1155 ///1156 /// # Arguments1157 ///1158 /// * `from`: Address that currently owns the token.1159 /// * `recipient`: Address of the new token-owner-to-be.1160 /// * `collection_id`: ID of the collection the item.1161 /// * `item_id`: ID of the item to be transferred.1162 /// * `value`: Amount to transfer.1163 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1164 /// * Fungible Mode: The desired number of pieces to transfer.1165 /// * Re-Fungible Mode: The desired number of pieces to transfer.1166 #[pallet::call_index(25)]1167 #[pallet::weight(T::CommonWeightInfo::transfer_from() + <Pallet<T>>::nesting_budget_predispatch_weight())]1168 pub fn transfer_from(1169 origin: OriginFor<T>,1170 from: T::CrossAccountId,1171 recipient: T::CrossAccountId,1172 collection_id: CollectionId,1173 item_id: TokenId,1174 value: u128,1175 ) -> DispatchResultWithPostInfo {1176 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1177 let budget = Self::structure_nesting_budget();11781179 Self::refund_nesting_budget(1180 dispatch_tx::<T, _>(collection_id, |d| {1181 d.transfer_from(sender, from, recipient, item_id, value, &budget)1182 }),1183 budget,1184 )1185 }11861187 /// Set specific limits of a collection. Empty, or None fields mean chain default.1188 ///1189 /// # Permissions1190 ///1191 /// * Collection owner1192 /// * Collection admin1193 ///1194 /// # Arguments1195 ///1196 /// * `collection_id`: ID of the modified collection.1197 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1198 /// will not overwrite the old ones.1199 #[pallet::call_index(26)]1200 #[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1201 pub fn set_collection_limits(1202 origin: OriginFor<T>,1203 collection_id: CollectionId,1204 new_limit: CollectionLimits,1205 ) -> DispatchResult {1206 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1207 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1208 }1209 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1210 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1211 <PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)1212 }12131214 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1215 ///1216 /// # Permissions1217 ///1218 /// * Collection owner1219 /// * Collection admin1220 ///1221 /// # Arguments1222 ///1223 /// * `collection_id`: ID of the modified collection.1224 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1225 /// will not overwrite the old ones.1226 #[pallet::call_index(27)]1227 #[pallet::weight(<SelfWeightOf<T>>::set_collection_limits())]1228 pub fn set_collection_permissions(1229 origin: OriginFor<T>,1230 collection_id: CollectionId,1231 new_permission: CollectionPermissions,1232 ) -> DispatchResult {1233 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1234 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1235 }1236 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1237 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1238 <PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)1239 }12401241 /// Re-partition a refungible token, while owning all of its parts/pieces.1242 ///1243 /// # Permissions1244 ///1245 /// * Token owner (must own every part)1246 ///1247 /// # Arguments1248 ///1249 /// * `collection_id`: ID of the collection the RFT belongs to.1250 /// * `token_id`: ID of the RFT.1251 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1252 #[pallet::call_index(28)]1253 #[pallet::weight(T::RefungibleExtensionsWeightInfo::repartition())]1254 pub fn repartition(1255 origin: OriginFor<T>,1256 collection_id: CollectionId,1257 token_id: TokenId,1258 amount: u128,1259 ) -> DispatchResultWithPostInfo {1260 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1261 dispatch_tx::<T, _>(collection_id, |d| {1262 if let Some(refungible_extensions) = d.refungible_extensions() {1263 refungible_extensions.repartition(&sender, token_id, amount)1264 } else {1265 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1266 }1267 })1268 }12691270 /// Sets or unsets the approval of a given operator.1271 ///1272 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.1273 ///1274 /// # Arguments1275 ///1276 /// * `owner`: Token owner1277 /// * `operator`: Operator1278 /// * `approve`: Should operator status be granted or revoked?1279 #[pallet::call_index(29)]1280 #[pallet::weight(T::CommonWeightInfo::set_allowance_for_all())]1281 pub fn set_allowance_for_all(1282 origin: OriginFor<T>,1283 collection_id: CollectionId,1284 operator: T::CrossAccountId,1285 approve: bool,1286 ) -> DispatchResultWithPostInfo {1287 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1288 dispatch_tx::<T, _>(collection_id, |d| {1289 d.set_allowance_for_all(sender, operator, approve)1290 })1291 }12921293 /// Repairs a collection if the data was somehow corrupted.1294 ///1295 /// # Arguments1296 ///1297 /// * `collection_id`: ID of the collection to repair.1298 #[pallet::call_index(30)]1299 #[pallet::weight(<SelfWeightOf<T>>::force_repair_collection())]1300 pub fn force_repair_collection(1301 origin: OriginFor<T>,1302 collection_id: CollectionId,1303 ) -> DispatchResult {1304 if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1305 fail!(<pallet_common::Error<T>>::UnsupportedOperation);1306 }1307 ensure_root(origin)?;1308 <PalletCommon<T>>::repair_collection(collection_id)1309 }13101311 /// Repairs a token if the data was somehow corrupted.1312 ///1313 /// # Arguments1314 ///1315 /// * `collection_id`: ID of the collection the item belongs to.1316 /// * `item_id`: ID of the item.1317 #[pallet::call_index(31)]1318 #[pallet::weight(T::CommonWeightInfo::force_repair_item())]1319 pub fn force_repair_item(1320 origin: OriginFor<T>,1321 collection_id: CollectionId,1322 item_id: TokenId,1323 ) -> DispatchResultWithPostInfo {1324 ensure_root(origin)?;1325 dispatch_tx::<T, _>(collection_id, |d| d.repair_item(item_id))1326 }1327 }13281329 impl<T: Config> Pallet<T> {1330 /// Force set `sponsor` for `collection`.1331 ///1332 /// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1333 /// from the `sponsor` is not required.1334 ///1335 /// # Arguments1336 ///1337 /// * `sponsor`: ID of the account of the sponsor-to-be.1338 /// * `collection_id`: ID of the modified collection.1339 pub fn force_set_sponsor(1340 sponsor: T::AccountId,1341 collection_id: CollectionId,1342 ) -> DispatchResult {1343 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1344 target_collection.force_set_sponsor(sponsor)1345 }13461347 /// Force remove `sponsor` for `collection`.1348 ///1349 /// Differs from `remove_sponsor` in that1350 /// it doesn't require consent from the `owner` of the collection.1351 ///1352 /// # Arguments1353 ///1354 /// * `collection_id`: ID of the modified collection.1355 pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1356 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1357 target_collection.force_remove_sponsor()1358 }13591360 #[inline(always)]1361 pub(crate) fn destroy_collection_internal(1362 sender: T::CrossAccountId,1363 collection_id: CollectionId,1364 ) -> DispatchResult {1365 T::CollectionDispatch::destroy(sender, collection_id)?;13661367 // TODO: basket cleanup should be moved elsewhere1368 // Maybe runtime dispatch.rs should perform it?13691370 let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1371 let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1372 let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13731374 let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1375 let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1376 let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);13771378 Ok(())1379 }13801381 fn structure_nesting_budget() -> budget::Value {1382 budget::Value::new(Self::nesting_budget())1383 }13841385 fn nesting_budget_predispatch_weight() -> Weight {1386 T::StructureWeightInfo::find_parent().saturating_mul(Self::nesting_budget() as u64)1387 }13881389 pub fn refund_nesting_budget(1390 mut result: DispatchResultWithPostInfo,1391 budget: budget::Value,1392 ) -> DispatchResultWithPostInfo {1393 let refund_amount = budget.refund_amount();1394 let consumed = Self::nesting_budget() - refund_amount;13951396 match &mut result {1397 Ok(PostDispatchInfo {1398 actual_weight: Some(weight),1399 ..1400 })1401 | Err(DispatchErrorWithPostInfo {1402 post_info: PostDispatchInfo {1403 actual_weight: Some(weight),1404 ..1405 },1406 ..1407 }) => {1408 *weight += T::StructureWeightInfo::find_parent().saturating_mul(consumed as u64)1409 }1410 _ => {}1411 }14121413 result1414 }1415 }1416}primitives/data-structs/src/budget.rsdiffbeforeafterboth--- a/primitives/data-structs/src/budget.rs
+++ b/primitives/data-structs/src/budget.rs
@@ -22,7 +22,7 @@
pub fn new(v: u32) -> Self {
Self(Cell::new(v))
}
- pub fn remaining(&self) -> u32 {
+ pub fn refund_amount(self) -> u32 {
self.0.get()
}
}