difftreelog
doc: adjust create_collection deprecation
in: master
1 file 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 `createCollectionEx`.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::{77 decl_module, decl_storage, decl_error, decl_event,78 dispatch::DispatchResult,79 ensure, fail,80 weights::{Weight},81 transactional,82 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},83 BoundedVec,84};85use scale_info::TypeInfo;86use frame_system::{self as system, ensure_signed};87use sp_runtime::{sp_std::prelude::Vec};88use up_data_structs::{89 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,90 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,91 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,92 PropertyKeyPermission,93};94use pallet_evm::account::CrossAccountId;95use pallet_common::{96 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,97 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,98};99pub mod eth;100101#[cfg(feature = "runtime-benchmarks")]102mod benchmarking;103pub mod weights;104use weights::WeightInfo;105106/// Maximum number of levels of depth in the token nesting tree.107pub const NESTING_BUDGET: u32 = 5;108109decl_error! {110 /// Errors for the common Unique transactions.111 pub enum Error for Module<T: Config> {112 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].113 CollectionDecimalPointLimitExceeded,114 /// This address is not set as sponsor, use setCollectionSponsor first.115 ConfirmUnsetSponsorFail,116 /// Length of items properties must be greater than 0.117 EmptyArgument,118 /// Repertition is only supported by refungible collection.119 RepartitionCalledOnNonRefungibleCollection,120 }121}122123/// Configuration trait of this pallet.124pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {125 /// Overarching event type.126 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;127128 /// 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 /// Weight info information for extra refungible pallet operations.135 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;136}137138decl_event! {139 pub enum Event<T>140 where141 <T as frame_system::Config>::AccountId,142 <T as pallet_evm::account::Config>::CrossAccountId,143 {144 /// Collection sponsor was removed145 ///146 /// # Arguments147 /// * collection_id: ID of the affected collection.148 CollectionSponsorRemoved(CollectionId),149150 /// Collection admin was added151 ///152 /// # Arguments153 /// * collection_id: ID of the affected collection.154 /// * admin: Admin address.155 CollectionAdminAdded(CollectionId, CrossAccountId),156157 /// Collection owned was changed158 ///159 /// # Arguments160 /// * collection_id: ID of the affected collection.161 /// * owner: New owner address.162 CollectionOwnedChanged(CollectionId, AccountId),163164 /// Collection sponsor was set165 ///166 /// # Arguments167 /// * collection_id: ID of the affected collection.168 /// * owner: New sponsor address.169 CollectionSponsorSet(CollectionId, AccountId),170171 /// New sponsor was confirm172 ///173 /// # Arguments174 /// * collection_id: ID of the affected collection.175 /// * sponsor: New sponsor address.176 SponsorshipConfirmed(CollectionId, AccountId),177178 /// Collection admin was removed179 ///180 /// # Arguments181 /// * collection_id: ID of the affected collection.182 /// * admin: Removed admin address.183 CollectionAdminRemoved(CollectionId, CrossAccountId),184185 /// Address was removed from the allow list186 ///187 /// # Arguments188 /// * collection_id: ID of the affected collection.189 /// * user: Address of the removed account.190 AllowListAddressRemoved(CollectionId, CrossAccountId),191192 /// Address was added to the allow list193 ///194 /// # Arguments195 /// * collection_id: ID of the affected collection.196 /// * user: Address of the added account.197 AllowListAddressAdded(CollectionId, CrossAccountId),198199 /// Collection limits were set200 ///201 /// # Arguments202 /// * collection_id: ID of the affected collection.203 CollectionLimitSet(CollectionId),204205 /// Collection permissions were set206 ///207 /// # Arguments208 /// * collection_id: ID of the affected collection.209 CollectionPermissionSet(CollectionId),210 }211}212213type SelfWeightOf<T> = <T as Config>::WeightInfo;214215// # Used definitions216//217// ## User control levels218//219// chain-controlled - key is uncontrolled by user220// i.e autoincrementing index221// can use non-cryptographic hash222// real - key is controlled by user223// but it is hard to generate enough colliding values, i.e owner of signed txs224// can use non-cryptographic hash225// controlled - key is completly controlled by users226// i.e maps with mutable keys227// should use cryptographic hash228//229// ## User control level downgrade reasons230//231// ?1 - chain-controlled -> controlled232// collections/tokens can be destroyed, resulting in massive holes233// ?2 - chain-controlled -> controlled234// same as ?1, but can be only added, resulting in easier exploitation235// ?3 - real -> controlled236// no confirmation required, so addresses can be easily generated237decl_storage! {238 trait Store for Module<T: Config> as Unique {239240 //#region Private members241 /// Used for migrations242 ChainVersion: u64;243 //#endregion244245 //#region Tokens transfer sponosoring rate limit baskets246 /// (Collection id (controlled?2), who created (real))247 /// TODO: Off chain worker should remove from this map when collection gets removed248 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;249 /// Collection id (controlled?2), token id (controlled?2)250 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251 /// Collection id (controlled?2), owning user (real)252 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253 /// Collection id (controlled?2), token id (controlled?2)254 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;255 //#endregion256257 /// Variable metadata sponsoring258 /// Collection id (controlled?2), token id (controlled?2)259 #[deprecated]260 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;261 /// Last sponsoring of token property setting // todo:doc rephrase this and the following262 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;263264 /// Last sponsoring of NFT approval in a collection265 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;266 /// Last sponsoring of fungible tokens approval in a collection267 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;268 /// Last sponsoring of RFT approval in a collection269 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;270 }271}272273decl_module! {274 /// Type alias to Pallet, to be used by construct_runtime.275 pub struct Module<T: Config> for enum Call276 where277 origin: T::Origin278 {279 type Error = Error<T>;280281 fn deposit_event() = default;282283 fn on_initialize(_now: T::BlockNumber) -> Weight {284 0285 }286287 fn on_runtime_upgrade() -> Weight {288 let limit = None;289290 <VariableMetaDataBasket<T>>::remove_all(limit);291292 0293 }294295 /// Create a collection of tokens.296 ///297 /// Each Token may have multiple properties encoded as an array of bytes298 /// of certain length. The initial owner of the collection is set299 /// to the address that signed the transaction and can be changed later.300 ///301 /// Deprecated! Prefer [`create_collection_ex`][`Pallet::create_collection_ex`] instead.302 ///303 /// # Permissions304 ///305 /// * Anyone - becomes the owner of the new collection.306 ///307 /// # Arguments308 ///309 /// * `collection_name`: Wide-character string with collection name310 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).311 /// * `collection_description`: Wide-character string with collection description312 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).313 /// * `token_prefix`: Byte string containing the token prefix to mark a collection314 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).315 /// * `mode`: Type of items stored in the collection and type dependent data.316 // returns collection ID317 #[weight = <SelfWeightOf<T>>::create_collection()]318 #[transactional]319 #[deprecated]320 pub fn create_collection(321 origin,322 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,323 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,324 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,325 mode: CollectionMode326 ) -> DispatchResult {327 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328 name: collection_name,329 description: collection_description,330 token_prefix,331 mode,332 ..Default::default()333 };334 Self::create_collection_ex(origin, data)335 }336337 /// Create a collection with explicit parameters.338 ///339 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.340 ///341 /// # Permissions342 ///343 /// * Anyone - becomes the owner of the new collection.344 ///345 /// # Arguments346 ///347 /// * `data`: Explicit data of a collection used for its creation.348 #[weight = <SelfWeightOf<T>>::create_collection()]349 #[transactional]350 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {351 let sender = ensure_signed(origin)?;352353 // =========354355 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;356357 Ok(())358 }359360 /// Destroy a collection if no tokens exist within.361 ///362 /// # Permissions363 ///364 /// * Collection owner365 ///366 /// # Arguments367 ///368 /// * `collection_id`: Collection to destroy.369 #[weight = <SelfWeightOf<T>>::destroy_collection()]370 #[transactional]371 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373 let collection = <CollectionHandle<T>>::try_get(collection_id)?;374 collection.check_is_internal()?;375376 // =========377378 T::CollectionDispatch::destroy(sender, collection)?;379380 <NftTransferBasket<T>>::remove_prefix(collection_id, None);381 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);382 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);383384 <NftApproveBasket<T>>::remove_prefix(collection_id, None);385 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);386 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);387388 Ok(())389 }390391 /// Add an address to allow list.392 ///393 /// # Permissions394 ///395 /// * Collection owner396 /// * Collection admin397 ///398 /// # Arguments399 ///400 /// * `collection_id`: ID of the modified collection.401 /// * `address`: ID of the address to be added to the allowlist.402 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]403 #[transactional]404 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407 let collection = <CollectionHandle<T>>::try_get(collection_id)?;408 collection.check_is_internal()?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 true,415 )?;416417 Self::deposit_event(Event::<T>::AllowListAddressAdded(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Remove an address from allow list.426 ///427 /// # Permissions428 ///429 /// * Collection owner430 /// * Collection admin431 ///432 /// # Arguments433 ///434 /// * `collection_id`: ID of the modified collection.435 /// * `address`: ID of the address to be removed from the allowlist.436 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]437 #[transactional]438 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{439440 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441 let collection = <CollectionHandle<T>>::try_get(collection_id)?;442 collection.check_is_internal()?;443444 <PalletCommon<T>>::toggle_allowlist(445 &collection,446 &sender,447 &address,448 false,449 )?;450451 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(452 collection_id,453 address454 ));455456 Ok(())457 }458459 /// Change the owner of the collection.460 ///461 /// # Permissions462 ///463 /// * Collection owner464 ///465 /// # Arguments466 ///467 /// * `collection_id`: ID of the modified collection.468 /// * `new_owner`: ID of the account that will become the owner.469 #[weight = <SelfWeightOf<T>>::change_collection_owner()]470 #[transactional]471 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {472473 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);474475 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;476 target_collection.check_is_internal()?;477 target_collection.check_is_owner(&sender)?;478479 target_collection.owner = new_owner.clone();480 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(481 collection_id,482 new_owner483 ));484485 target_collection.save()486 }487488 /// Add an admin to a collection.489 ///490 /// NFT Collection can be controlled by multiple admin addresses491 /// (some which can also be servers, for example). Admins can issue492 /// and burn NFTs, as well as add and remove other admins,493 /// but cannot change NFT or Collection ownership.494 ///495 /// # Permissions496 ///497 /// * Collection owner498 /// * Collection admin499 ///500 /// # Arguments501 ///502 /// * `collection_id`: ID of the Collection to add an admin for.503 /// * `new_admin`: Address of new admin to add.504 #[weight = <SelfWeightOf<T>>::add_collection_admin()]505 #[transactional]506 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {507 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);508 let collection = <CollectionHandle<T>>::try_get(collection_id)?;509 collection.check_is_internal()?;510511 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(512 collection_id,513 new_admin.clone()514 ));515516 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)517 }518519 /// Remove admin of a collection.520 ///521 /// An admin address can remove itself. List of admins may become empty,522 /// in which case only Collection Owner will be able to add an Admin.523 ///524 /// # Permissions525 ///526 /// * Collection owner527 /// * Collection admin528 ///529 /// # Arguments530 ///531 /// * `collection_id`: ID of the collection to remove the admin for.532 /// * `account_id`: Address of the admin to remove.533 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]534 #[transactional]535 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {536 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);537 let collection = <CollectionHandle<T>>::try_get(collection_id)?;538 collection.check_is_internal()?;539540 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(541 collection_id,542 account_id.clone()543 ));544545 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)546 }547548 /// Set (invite) a new collection sponsor.549 ///550 /// If successful, confirmation from the sponsor-to-be will be pending.551 ///552 /// # Permissions553 ///554 /// * Collection owner555 /// * Collection admin556 ///557 /// # Arguments558 ///559 /// * `collection_id`: ID of the modified collection.560 /// * `new_sponsor`: ID of the account of the sponsor-to-be.561 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]562 #[transactional]563 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {564 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);565566 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;567 target_collection.check_is_owner_or_admin(&sender)?;568 target_collection.check_is_internal()?;569570 target_collection.set_sponsor(new_sponsor.clone())?;571572 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(573 collection_id,574 new_sponsor575 ));576577 target_collection.save()578 }579580 /// Confirm own sponsorship of a collection, becoming the sponsor.581 ///582 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].583 /// Sponsor can pay the fees of a transaction instead of the sender,584 /// but only within specified limits.585 ///586 /// # Permissions587 ///588 /// * Sponsor-to-be589 ///590 /// # Arguments591 ///592 /// * `collection_id`: ID of the collection with the pending sponsor.593 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]594 #[transactional]595 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {596 let sender = ensure_signed(origin)?;597598 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;599 target_collection.check_is_internal()?;600 ensure!(601 target_collection.confirm_sponsorship(&sender)?,602 Error::<T>::ConfirmUnsetSponsorFail603 );604605 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(606 collection_id,607 sender608 ));609610 target_collection.save()611 }612613 /// Remove a collection's a sponsor, making everyone pay for their own transactions.614 ///615 /// # Permissions616 ///617 /// * Collection owner618 ///619 /// # Arguments620 ///621 /// * `collection_id`: ID of the collection with the sponsor to remove.622 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]623 #[transactional]624 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {625 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);626627 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;628 target_collection.check_is_internal()?;629 target_collection.check_is_owner(&sender)?;630631 target_collection.sponsorship = SponsorshipState::Disabled;632633 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(634 collection_id635 ));636 target_collection.save()637 }638639 /// Mint an item within a collection.640 ///641 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].642 ///643 /// # Permissions644 ///645 /// * Collection owner646 /// * Collection admin647 /// * Anyone if648 /// * Allow List is enabled, and649 /// * Address is added to allow list, and650 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])651 ///652 /// # Arguments653 ///654 /// * `collection_id`: ID of the collection to which an item would belong.655 /// * `owner`: Address of the initial owner of the item.656 /// * `data`: Token data describing the item to store on chain.657 #[weight = T::CommonWeightInfo::create_item()]658 #[transactional]659 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {660 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661 let budget = budget::Value::new(NESTING_BUDGET);662663 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))664 }665666 /// Create multiple items within a collection.667 ///668 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].669 ///670 /// # Permissions671 ///672 /// * Collection owner673 /// * Collection admin674 /// * Anyone if675 /// * Allow List is enabled, and676 /// * Address is added to the allow list, and677 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])678 ///679 /// # Arguments680 ///681 /// * `collection_id`: ID of the collection to which the tokens would belong.682 /// * `owner`: Address of the initial owner of the tokens.683 /// * `items_data`: Vector of data describing each item to be created.684 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]685 #[transactional]686 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {687 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);688 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);689 let budget = budget::Value::new(NESTING_BUDGET);690691 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))692 }693694 /// Add or change collection properties.695 ///696 /// # Permissions697 ///698 /// * Collection owner699 /// * Collection admin700 ///701 /// # Arguments702 ///703 /// * `collection_id`: ID of the modified collection.704 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.705 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.706 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]707 #[transactional]708 pub fn set_collection_properties(709 origin,710 collection_id: CollectionId,711 properties: Vec<Property>712 ) -> DispatchResultWithPostInfo {713 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);714715 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))718 }719720 /// Delete specified collection properties.721 ///722 /// # Permissions723 ///724 /// * Collection Owner725 /// * Collection Admin726 ///727 /// # Arguments728 ///729 /// * `collection_id`: ID of the modified collection.730 /// * `property_keys`: Vector of keys of the properties to be deleted.731 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.732 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]733 #[transactional]734 pub fn delete_collection_properties(735 origin,736 collection_id: CollectionId,737 property_keys: Vec<PropertyKey>,738 ) -> DispatchResultWithPostInfo {739 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);740741 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);742743 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))744 }745746 /// Add or change token properties according to collection's permissions.747 /// Currently properties only work with NFTs.748 ///749 /// # Permissions750 ///751 /// * Depends on collection's token property permissions and specified property mutability:752 /// * Collection owner753 /// * Collection admin754 /// * Token owner755 ///756 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].757 ///758 /// # Arguments759 ///760 /// * `collection_id: ID of the collection to which the token belongs.761 /// * `token_id`: ID of the modified token.762 /// * `properties`: Vector of key-value pairs stored as the token's metadata.763 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.764 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]765 #[transactional]766 pub fn set_token_properties(767 origin,768 collection_id: CollectionId,769 token_id: TokenId,770 properties: Vec<Property>771 ) -> DispatchResultWithPostInfo {772 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);773774 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775 let budget = budget::Value::new(NESTING_BUDGET);776777 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))778 }779780 /// Delete specified token properties. Currently properties only work with NFTs.781 ///782 /// # Permissions783 ///784 /// * Depends on collection's token property permissions and specified property mutability:785 /// * Collection owner786 /// * Collection admin787 /// * Token owner788 ///789 /// # Arguments790 ///791 /// * `collection_id`: ID of the collection to which the token belongs.792 /// * `token_id`: ID of the modified token.793 /// * `property_keys`: Vector of keys of the properties to be deleted.794 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.795 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]796 #[transactional]797 pub fn delete_token_properties(798 origin,799 collection_id: CollectionId,800 token_id: TokenId,801 property_keys: Vec<PropertyKey>802 ) -> DispatchResultWithPostInfo {803 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);804805 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806 let budget = budget::Value::new(NESTING_BUDGET);807808 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))809 }810811 /// Add or change token property permissions of a collection.812 ///813 /// Without a permission for a particular key, a property with that key814 /// cannot be created in a token.815 ///816 /// # Permissions817 ///818 /// * Collection owner819 /// * Collection admin820 ///821 /// # Arguments822 ///823 /// * `collection_id`: ID of the modified collection.824 /// * `property_permissions`: Vector of permissions for property keys.825 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.826 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]827 #[transactional]828 pub fn set_token_property_permissions(829 origin,830 collection_id: CollectionId,831 property_permissions: Vec<PropertyKeyPermission>,832 ) -> DispatchResultWithPostInfo {833 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);834835 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);836837 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))838 }839840 /// Create multiple items within a collection with explicitly specified initial parameters.841 ///842 /// # Permissions843 ///844 /// * Collection owner845 /// * Collection admin846 /// * Anyone if847 /// * Allow List is enabled, and848 /// * Address is added to allow list, and849 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])850 ///851 /// # Arguments852 ///853 /// * `collection_id`: ID of the collection to which the tokens would belong.854 /// * `data`: Explicit item creation data.855 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]856 #[transactional]857 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {858 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);859 let budget = budget::Value::new(NESTING_BUDGET);860861 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))862 }863864 /// Completely allow or disallow transfers for a particular collection.865 ///866 /// # Permissions867 ///868 /// * Collection owner869 ///870 /// # Arguments871 ///872 /// * `collection_id`: ID of the collection.873 /// * `value`: New value of the flag, are transfers allowed?874 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]875 #[transactional]876 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {877 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;879 target_collection.check_is_internal()?;880 target_collection.check_is_owner(&sender)?;881882 // =========883884 target_collection.limits.transfers_enabled = Some(value);885 target_collection.save()886 }887888 /// Destroy an item.889 ///890 /// # Permissions891 ///892 /// * Collection owner893 /// * Collection admin894 /// * Current item owner895 ///896 /// # Arguments897 ///898 /// * `collection_id`: ID of the collection to which the item belongs.899 /// * `item_id`: ID of item to burn.900 /// * `value`: Number of pieces of the item to destroy.901 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.902 /// * Fungible Mode: The desired number of pieces to burn.903 /// * Re-Fungible Mode: The desired number of pieces to burn.904 #[weight = T::CommonWeightInfo::burn_item()]905 #[transactional]906 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {907 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);908909 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;910 if value == 1 {911 <NftTransferBasket<T>>::remove(collection_id, item_id);912 <NftApproveBasket<T>>::remove(collection_id, item_id);913 }914 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?915 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());916 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));917 Ok(post_info)918 }919920 /// Destroy a token on behalf of the owner as a non-owner account.921 ///922 /// See also: [`approve`][`Pallet::approve`].923 ///924 /// After this method executes, one approval is removed from the total so that925 /// the approved address will not be able to transfer this item again from this owner.926 ///927 /// # Permissions928 ///929 /// * Collection owner930 /// * Collection admin931 /// * Current token owner932 /// * Address approved by current item owner933 ///934 /// # Arguments935 ///936 /// * `from`: The owner of the burning item.937 /// * `collection_id`: ID of the collection to which the item belongs.938 /// * `item_id`: ID of item to burn.939 /// * `value`: Number of pieces to burn.940 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.941 /// * Fungible Mode: The desired number of pieces to burn.942 /// * Re-Fungible Mode: The desired number of pieces to burn.943 #[weight = T::CommonWeightInfo::burn_from()]944 #[transactional]945 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {946 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947 let budget = budget::Value::new(NESTING_BUDGET);948949 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))950 }951952 /// Change ownership of the token.953 ///954 /// # Permissions955 ///956 /// * Collection owner957 /// * Collection admin958 /// * Current token owner959 ///960 /// # Arguments961 ///962 /// * `recipient`: Address of token recipient.963 /// * `collection_id`: ID of the collection the item belongs to.964 /// * `item_id`: ID of the item.965 /// * Non-Fungible Mode: Required.966 /// * Fungible Mode: Ignored.967 /// * Re-Fungible Mode: Required.968 ///969 /// * `value`: Amount to transfer.970 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.971 /// * Fungible Mode: The desired number of pieces to transfer.972 /// * Re-Fungible Mode: The desired number of pieces to transfer.973 #[weight = T::CommonWeightInfo::transfer()]974 #[transactional]975 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977 let budget = budget::Value::new(NESTING_BUDGET);978979 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980 }981982 /// Allow a non-permissioned address to transfer or burn an item.983 ///984 /// # Permissions985 ///986 /// * Collection owner987 /// * Collection admin988 /// * Current item owner989 ///990 /// # Arguments991 ///992 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993 /// * `collection_id`: ID of the collection the item belongs to.994 /// * `item_id`: ID of the item transactions on which are now approved.995 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996 /// Set to 0 to revoke the approval.997 #[weight = T::CommonWeightInfo::approve()]998 #[transactional]999 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {1000 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10011002 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1003 }10041005 /// Change ownership of an item on behalf of the owner as a non-owner account.1006 ///1007 /// See the [`approve`][`Pallet::approve`] method for additional information.1008 ///1009 /// After this method executes, one approval is removed from the total so that1010 /// the approved address will not be able to transfer this item again from this owner.1011 ///1012 /// # Permissions1013 ///1014 /// * Collection owner1015 /// * Collection admin1016 /// * Current item owner1017 /// * Address approved by current item owner1018 ///1019 /// # Arguments1020 ///1021 /// * `from`: Address that currently owns the token.1022 /// * `recipient`: Address of the new token-owner-to-be.1023 /// * `collection_id`: ID of the collection the item.1024 /// * `item_id`: ID of the item to be transferred.1025 /// * `value`: Amount to transfer.1026 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1027 /// * Fungible Mode: The desired number of pieces to transfer.1028 /// * Re-Fungible Mode: The desired number of pieces to transfer.1029 #[weight = T::CommonWeightInfo::transfer_from()]1030 #[transactional]1031 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1032 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1033 let budget = budget::Value::new(NESTING_BUDGET);10341035 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1036 }10371038 /// Set specific limits of a collection. Empty, or None fields mean chain default.1039 ///1040 /// # Permissions1041 ///1042 /// * Collection owner1043 /// * Collection admin1044 ///1045 /// # Arguments1046 ///1047 /// * `collection_id`: ID of the modified collection.1048 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1049 /// will not overwrite the old ones.1050 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1051 #[transactional]1052 pub fn set_collection_limits(1053 origin,1054 collection_id: CollectionId,1055 new_limit: CollectionLimits,1056 ) -> DispatchResult {1057 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1058 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1059 target_collection.check_is_internal()?;1060 target_collection.check_is_owner_or_admin(&sender)?;1061 let old_limit = &target_collection.limits;10621063 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10641065 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1066 collection_id1067 ));10681069 target_collection.save()1070 }10711072 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1073 ///1074 /// # Permissions1075 ///1076 /// * Collection owner1077 /// * Collection admin1078 ///1079 /// # Arguments1080 ///1081 /// * `collection_id`: ID of the modified collection.1082 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1083 /// will not overwrite the old ones.1084 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1085 #[transactional]1086 pub fn set_collection_permissions(1087 origin,1088 collection_id: CollectionId,1089 new_permission: CollectionPermissions,1090 ) -> DispatchResult {1091 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1092 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1093 target_collection.check_is_internal()?;1094 target_collection.check_is_owner_or_admin(&sender)?;1095 let old_limit = &target_collection.permissions;10961097 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10981099 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1100 collection_id1101 ));11021103 target_collection.save()1104 }11051106 /// Re-partition a refungible token, while owning all of its parts/pieces.1107 ///1108 /// # Permissions1109 ///1110 /// * Token owner (must own every part)1111 ///1112 /// # Arguments1113 ///1114 /// * `collection_id`: ID of the collection the RFT belongs to.1115 /// * `token_id`: ID of the RFT.1116 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1117 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1118 #[transactional]1119 pub fn repartition(1120 origin,1121 collection_id: CollectionId,1122 token_id: TokenId,1123 amount: u128,1124 ) -> DispatchResultWithPostInfo {1125 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1126 dispatch_tx::<T, _>(collection_id, |d| {1127 if let Some(refungible_extensions) = d.refungible_extensions() {1128 refungible_extensions.repartition(&sender, token_id, amount)1129 } else {1130 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1131 }1132 })1133 }1134 }1135}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::{77 decl_module, decl_storage, decl_error, decl_event,78 dispatch::DispatchResult,79 ensure, fail,80 weights::{Weight},81 transactional,82 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},83 BoundedVec,84};85use scale_info::TypeInfo;86use frame_system::{self as system, ensure_signed};87use sp_runtime::{sp_std::prelude::Vec};88use up_data_structs::{89 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,90 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,91 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,92 PropertyKeyPermission,93};94use pallet_evm::account::CrossAccountId;95use pallet_common::{96 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,97 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,98};99pub mod eth;100101#[cfg(feature = "runtime-benchmarks")]102mod benchmarking;103pub mod weights;104use weights::WeightInfo;105106/// Maximum number of levels of depth in the token nesting tree.107pub const NESTING_BUDGET: u32 = 5;108109decl_error! {110 /// Errors for the common Unique transactions.111 pub enum Error for Module<T: Config> {112 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].113 CollectionDecimalPointLimitExceeded,114 /// This address is not set as sponsor, use setCollectionSponsor first.115 ConfirmUnsetSponsorFail,116 /// Length of items properties must be greater than 0.117 EmptyArgument,118 /// Repertition is only supported by refungible collection.119 RepartitionCalledOnNonRefungibleCollection,120 }121}122123/// Configuration trait of this pallet.124pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {125 /// Overarching event type.126 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;127128 /// 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 /// Weight info information for extra refungible pallet operations.135 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;136}137138decl_event! {139 pub enum Event<T>140 where141 <T as frame_system::Config>::AccountId,142 <T as pallet_evm::account::Config>::CrossAccountId,143 {144 /// Collection sponsor was removed145 ///146 /// # Arguments147 /// * collection_id: ID of the affected collection.148 CollectionSponsorRemoved(CollectionId),149150 /// Collection admin was added151 ///152 /// # Arguments153 /// * collection_id: ID of the affected collection.154 /// * admin: Admin address.155 CollectionAdminAdded(CollectionId, CrossAccountId),156157 /// Collection owned was changed158 ///159 /// # Arguments160 /// * collection_id: ID of the affected collection.161 /// * owner: New owner address.162 CollectionOwnedChanged(CollectionId, AccountId),163164 /// Collection sponsor was set165 ///166 /// # Arguments167 /// * collection_id: ID of the affected collection.168 /// * owner: New sponsor address.169 CollectionSponsorSet(CollectionId, AccountId),170171 /// New sponsor was confirm172 ///173 /// # Arguments174 /// * collection_id: ID of the affected collection.175 /// * sponsor: New sponsor address.176 SponsorshipConfirmed(CollectionId, AccountId),177178 /// Collection admin was removed179 ///180 /// # Arguments181 /// * collection_id: ID of the affected collection.182 /// * admin: Removed admin address.183 CollectionAdminRemoved(CollectionId, CrossAccountId),184185 /// Address was removed from the allow list186 ///187 /// # Arguments188 /// * collection_id: ID of the affected collection.189 /// * user: Address of the removed account.190 AllowListAddressRemoved(CollectionId, CrossAccountId),191192 /// Address was added to the allow list193 ///194 /// # Arguments195 /// * collection_id: ID of the affected collection.196 /// * user: Address of the added account.197 AllowListAddressAdded(CollectionId, CrossAccountId),198199 /// Collection limits were set200 ///201 /// # Arguments202 /// * collection_id: ID of the affected collection.203 CollectionLimitSet(CollectionId),204205 /// Collection permissions were set206 ///207 /// # Arguments208 /// * collection_id: ID of the affected collection.209 CollectionPermissionSet(CollectionId),210 }211}212213type SelfWeightOf<T> = <T as Config>::WeightInfo;214215// # Used definitions216//217// ## User control levels218//219// chain-controlled - key is uncontrolled by user220// i.e autoincrementing index221// can use non-cryptographic hash222// real - key is controlled by user223// but it is hard to generate enough colliding values, i.e owner of signed txs224// can use non-cryptographic hash225// controlled - key is completly controlled by users226// i.e maps with mutable keys227// should use cryptographic hash228//229// ## User control level downgrade reasons230//231// ?1 - chain-controlled -> controlled232// collections/tokens can be destroyed, resulting in massive holes233// ?2 - chain-controlled -> controlled234// same as ?1, but can be only added, resulting in easier exploitation235// ?3 - real -> controlled236// no confirmation required, so addresses can be easily generated237decl_storage! {238 trait Store for Module<T: Config> as Unique {239240 //#region Private members241 /// Used for migrations242 ChainVersion: u64;243 //#endregion244245 //#region Tokens transfer sponosoring rate limit baskets246 /// (Collection id (controlled?2), who created (real))247 /// TODO: Off chain worker should remove from this map when collection gets removed248 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;249 /// Collection id (controlled?2), token id (controlled?2)250 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251 /// Collection id (controlled?2), owning user (real)252 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253 /// Collection id (controlled?2), token id (controlled?2)254 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;255 //#endregion256257 /// Variable metadata sponsoring258 /// Collection id (controlled?2), token id (controlled?2)259 #[deprecated]260 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;261 /// Last sponsoring of token property setting // todo:doc rephrase this and the following262 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;263264 /// Last sponsoring of NFT approval in a collection265 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;266 /// Last sponsoring of fungible tokens approval in a collection267 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;268 /// Last sponsoring of RFT approval in a collection269 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;270 }271}272273decl_module! {274 /// Type alias to Pallet, to be used by construct_runtime.275 pub struct Module<T: Config> for enum Call276 where277 origin: T::Origin278 {279 type Error = Error<T>;280281 fn deposit_event() = default;282283 fn on_initialize(_now: T::BlockNumber) -> Weight {284 0285 }286287 fn on_runtime_upgrade() -> Weight {288 let limit = None;289290 <VariableMetaDataBasket<T>>::remove_all(limit);291292 0293 }294295 /// Create a collection of tokens.296 ///297 /// Each Token may have multiple properties encoded as an array of bytes298 /// of certain length. The initial owner of the collection is set299 /// to the address that signed the transaction and can be changed later.300 ///301 /// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.302 ///303 /// # Permissions304 ///305 /// * Anyone - becomes the owner of the new collection.306 ///307 /// # Arguments308 ///309 /// * `collection_name`: Wide-character string with collection name310 /// (limit [`MAX_COLLECTION_NAME_LENGTH`]).311 /// * `collection_description`: Wide-character string with collection description312 /// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).313 /// * `token_prefix`: Byte string containing the token prefix to mark a collection314 /// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).315 /// * `mode`: Type of items stored in the collection and type dependent data.316 // returns collection ID317 #[weight = <SelfWeightOf<T>>::create_collection()]318 #[transactional]319 #[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]320 pub fn create_collection(321 origin,322 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,323 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,324 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,325 mode: CollectionMode326 ) -> DispatchResult {327 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328 name: collection_name,329 description: collection_description,330 token_prefix,331 mode,332 ..Default::default()333 };334 Self::create_collection_ex(origin, data)335 }336337 /// Create a collection with explicit parameters.338 ///339 /// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.340 ///341 /// # Permissions342 ///343 /// * Anyone - becomes the owner of the new collection.344 ///345 /// # Arguments346 ///347 /// * `data`: Explicit data of a collection used for its creation.348 #[weight = <SelfWeightOf<T>>::create_collection()]349 #[transactional]350 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {351 let sender = ensure_signed(origin)?;352353 // =========354355 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;356357 Ok(())358 }359360 /// Destroy a collection if no tokens exist within.361 ///362 /// # Permissions363 ///364 /// * Collection owner365 ///366 /// # Arguments367 ///368 /// * `collection_id`: Collection to destroy.369 #[weight = <SelfWeightOf<T>>::destroy_collection()]370 #[transactional]371 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373 let collection = <CollectionHandle<T>>::try_get(collection_id)?;374 collection.check_is_internal()?;375376 // =========377378 T::CollectionDispatch::destroy(sender, collection)?;379380 <NftTransferBasket<T>>::remove_prefix(collection_id, None);381 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);382 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);383384 <NftApproveBasket<T>>::remove_prefix(collection_id, None);385 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);386 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);387388 Ok(())389 }390391 /// Add an address to allow list.392 ///393 /// # Permissions394 ///395 /// * Collection owner396 /// * Collection admin397 ///398 /// # Arguments399 ///400 /// * `collection_id`: ID of the modified collection.401 /// * `address`: ID of the address to be added to the allowlist.402 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]403 #[transactional]404 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{405406 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407 let collection = <CollectionHandle<T>>::try_get(collection_id)?;408 collection.check_is_internal()?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 true,415 )?;416417 Self::deposit_event(Event::<T>::AllowListAddressAdded(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Remove an address from allow list.426 ///427 /// # Permissions428 ///429 /// * Collection owner430 /// * Collection admin431 ///432 /// # Arguments433 ///434 /// * `collection_id`: ID of the modified collection.435 /// * `address`: ID of the address to be removed from the allowlist.436 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]437 #[transactional]438 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{439440 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441 let collection = <CollectionHandle<T>>::try_get(collection_id)?;442 collection.check_is_internal()?;443444 <PalletCommon<T>>::toggle_allowlist(445 &collection,446 &sender,447 &address,448 false,449 )?;450451 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(452 collection_id,453 address454 ));455456 Ok(())457 }458459 /// Change the owner of the collection.460 ///461 /// # Permissions462 ///463 /// * Collection owner464 ///465 /// # Arguments466 ///467 /// * `collection_id`: ID of the modified collection.468 /// * `new_owner`: ID of the account that will become the owner.469 #[weight = <SelfWeightOf<T>>::change_collection_owner()]470 #[transactional]471 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {472473 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);474475 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;476 target_collection.check_is_internal()?;477 target_collection.check_is_owner(&sender)?;478479 target_collection.owner = new_owner.clone();480 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(481 collection_id,482 new_owner483 ));484485 target_collection.save()486 }487488 /// Add an admin to a collection.489 ///490 /// NFT Collection can be controlled by multiple admin addresses491 /// (some which can also be servers, for example). Admins can issue492 /// and burn NFTs, as well as add and remove other admins,493 /// but cannot change NFT or Collection ownership.494 ///495 /// # Permissions496 ///497 /// * Collection owner498 /// * Collection admin499 ///500 /// # Arguments501 ///502 /// * `collection_id`: ID of the Collection to add an admin for.503 /// * `new_admin`: Address of new admin to add.504 #[weight = <SelfWeightOf<T>>::add_collection_admin()]505 #[transactional]506 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {507 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);508 let collection = <CollectionHandle<T>>::try_get(collection_id)?;509 collection.check_is_internal()?;510511 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(512 collection_id,513 new_admin.clone()514 ));515516 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)517 }518519 /// Remove admin of a collection.520 ///521 /// An admin address can remove itself. List of admins may become empty,522 /// in which case only Collection Owner will be able to add an Admin.523 ///524 /// # Permissions525 ///526 /// * Collection owner527 /// * Collection admin528 ///529 /// # Arguments530 ///531 /// * `collection_id`: ID of the collection to remove the admin for.532 /// * `account_id`: Address of the admin to remove.533 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]534 #[transactional]535 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {536 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);537 let collection = <CollectionHandle<T>>::try_get(collection_id)?;538 collection.check_is_internal()?;539540 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(541 collection_id,542 account_id.clone()543 ));544545 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)546 }547548 /// Set (invite) a new collection sponsor.549 ///550 /// If successful, confirmation from the sponsor-to-be will be pending.551 ///552 /// # Permissions553 ///554 /// * Collection owner555 /// * Collection admin556 ///557 /// # Arguments558 ///559 /// * `collection_id`: ID of the modified collection.560 /// * `new_sponsor`: ID of the account of the sponsor-to-be.561 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]562 #[transactional]563 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {564 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);565566 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;567 target_collection.check_is_owner_or_admin(&sender)?;568 target_collection.check_is_internal()?;569570 target_collection.set_sponsor(new_sponsor.clone())?;571572 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(573 collection_id,574 new_sponsor575 ));576577 target_collection.save()578 }579580 /// Confirm own sponsorship of a collection, becoming the sponsor.581 ///582 /// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].583 /// Sponsor can pay the fees of a transaction instead of the sender,584 /// but only within specified limits.585 ///586 /// # Permissions587 ///588 /// * Sponsor-to-be589 ///590 /// # Arguments591 ///592 /// * `collection_id`: ID of the collection with the pending sponsor.593 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]594 #[transactional]595 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {596 let sender = ensure_signed(origin)?;597598 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;599 target_collection.check_is_internal()?;600 ensure!(601 target_collection.confirm_sponsorship(&sender)?,602 Error::<T>::ConfirmUnsetSponsorFail603 );604605 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(606 collection_id,607 sender608 ));609610 target_collection.save()611 }612613 /// Remove a collection's a sponsor, making everyone pay for their own transactions.614 ///615 /// # Permissions616 ///617 /// * Collection owner618 ///619 /// # Arguments620 ///621 /// * `collection_id`: ID of the collection with the sponsor to remove.622 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]623 #[transactional]624 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {625 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);626627 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;628 target_collection.check_is_internal()?;629 target_collection.check_is_owner(&sender)?;630631 target_collection.sponsorship = SponsorshipState::Disabled;632633 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(634 collection_id635 ));636 target_collection.save()637 }638639 /// Mint an item within a collection.640 ///641 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].642 ///643 /// # Permissions644 ///645 /// * Collection owner646 /// * Collection admin647 /// * Anyone if648 /// * Allow List is enabled, and649 /// * Address is added to allow list, and650 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])651 ///652 /// # Arguments653 ///654 /// * `collection_id`: ID of the collection to which an item would belong.655 /// * `owner`: Address of the initial owner of the item.656 /// * `data`: Token data describing the item to store on chain.657 #[weight = T::CommonWeightInfo::create_item()]658 #[transactional]659 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {660 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661 let budget = budget::Value::new(NESTING_BUDGET);662663 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))664 }665666 /// Create multiple items within a collection.667 ///668 /// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].669 ///670 /// # Permissions671 ///672 /// * Collection owner673 /// * Collection admin674 /// * Anyone if675 /// * Allow List is enabled, and676 /// * Address is added to the allow list, and677 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])678 ///679 /// # Arguments680 ///681 /// * `collection_id`: ID of the collection to which the tokens would belong.682 /// * `owner`: Address of the initial owner of the tokens.683 /// * `items_data`: Vector of data describing each item to be created.684 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]685 #[transactional]686 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {687 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);688 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);689 let budget = budget::Value::new(NESTING_BUDGET);690691 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))692 }693694 /// Add or change collection properties.695 ///696 /// # Permissions697 ///698 /// * Collection owner699 /// * Collection admin700 ///701 /// # Arguments702 ///703 /// * `collection_id`: ID of the modified collection.704 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.705 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.706 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]707 #[transactional]708 pub fn set_collection_properties(709 origin,710 collection_id: CollectionId,711 properties: Vec<Property>712 ) -> DispatchResultWithPostInfo {713 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);714715 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))718 }719720 /// Delete specified collection properties.721 ///722 /// # Permissions723 ///724 /// * Collection Owner725 /// * Collection Admin726 ///727 /// # Arguments728 ///729 /// * `collection_id`: ID of the modified collection.730 /// * `property_keys`: Vector of keys of the properties to be deleted.731 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.732 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]733 #[transactional]734 pub fn delete_collection_properties(735 origin,736 collection_id: CollectionId,737 property_keys: Vec<PropertyKey>,738 ) -> DispatchResultWithPostInfo {739 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);740741 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);742743 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))744 }745746 /// Add or change token properties according to collection's permissions.747 /// Currently properties only work with NFTs.748 ///749 /// # Permissions750 ///751 /// * Depends on collection's token property permissions and specified property mutability:752 /// * Collection owner753 /// * Collection admin754 /// * Token owner755 ///756 /// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].757 ///758 /// # Arguments759 ///760 /// * `collection_id: ID of the collection to which the token belongs.761 /// * `token_id`: ID of the modified token.762 /// * `properties`: Vector of key-value pairs stored as the token's metadata.763 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.764 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]765 #[transactional]766 pub fn set_token_properties(767 origin,768 collection_id: CollectionId,769 token_id: TokenId,770 properties: Vec<Property>771 ) -> DispatchResultWithPostInfo {772 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);773774 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775 let budget = budget::Value::new(NESTING_BUDGET);776777 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))778 }779780 /// Delete specified token properties. Currently properties only work with NFTs.781 ///782 /// # Permissions783 ///784 /// * Depends on collection's token property permissions and specified property mutability:785 /// * Collection owner786 /// * Collection admin787 /// * Token owner788 ///789 /// # Arguments790 ///791 /// * `collection_id`: ID of the collection to which the token belongs.792 /// * `token_id`: ID of the modified token.793 /// * `property_keys`: Vector of keys of the properties to be deleted.794 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.795 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]796 #[transactional]797 pub fn delete_token_properties(798 origin,799 collection_id: CollectionId,800 token_id: TokenId,801 property_keys: Vec<PropertyKey>802 ) -> DispatchResultWithPostInfo {803 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);804805 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806 let budget = budget::Value::new(NESTING_BUDGET);807808 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))809 }810811 /// Add or change token property permissions of a collection.812 ///813 /// Without a permission for a particular key, a property with that key814 /// cannot be created in a token.815 ///816 /// # Permissions817 ///818 /// * Collection owner819 /// * Collection admin820 ///821 /// # Arguments822 ///823 /// * `collection_id`: ID of the modified collection.824 /// * `property_permissions`: Vector of permissions for property keys.825 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.826 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]827 #[transactional]828 pub fn set_token_property_permissions(829 origin,830 collection_id: CollectionId,831 property_permissions: Vec<PropertyKeyPermission>,832 ) -> DispatchResultWithPostInfo {833 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);834835 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);836837 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))838 }839840 /// Create multiple items within a collection with explicitly specified initial parameters.841 ///842 /// # Permissions843 ///844 /// * Collection owner845 /// * Collection admin846 /// * Anyone if847 /// * Allow List is enabled, and848 /// * Address is added to allow list, and849 /// * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])850 ///851 /// # Arguments852 ///853 /// * `collection_id`: ID of the collection to which the tokens would belong.854 /// * `data`: Explicit item creation data.855 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]856 #[transactional]857 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {858 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);859 let budget = budget::Value::new(NESTING_BUDGET);860861 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))862 }863864 /// Completely allow or disallow transfers for a particular collection.865 ///866 /// # Permissions867 ///868 /// * Collection owner869 ///870 /// # Arguments871 ///872 /// * `collection_id`: ID of the collection.873 /// * `value`: New value of the flag, are transfers allowed?874 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]875 #[transactional]876 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {877 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;879 target_collection.check_is_internal()?;880 target_collection.check_is_owner(&sender)?;881882 // =========883884 target_collection.limits.transfers_enabled = Some(value);885 target_collection.save()886 }887888 /// Destroy an item.889 ///890 /// # Permissions891 ///892 /// * Collection owner893 /// * Collection admin894 /// * Current item owner895 ///896 /// # Arguments897 ///898 /// * `collection_id`: ID of the collection to which the item belongs.899 /// * `item_id`: ID of item to burn.900 /// * `value`: Number of pieces of the item to destroy.901 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.902 /// * Fungible Mode: The desired number of pieces to burn.903 /// * Re-Fungible Mode: The desired number of pieces to burn.904 #[weight = T::CommonWeightInfo::burn_item()]905 #[transactional]906 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {907 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);908909 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;910 if value == 1 {911 <NftTransferBasket<T>>::remove(collection_id, item_id);912 <NftApproveBasket<T>>::remove(collection_id, item_id);913 }914 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?915 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());916 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));917 Ok(post_info)918 }919920 /// Destroy a token on behalf of the owner as a non-owner account.921 ///922 /// See also: [`approve`][`Pallet::approve`].923 ///924 /// After this method executes, one approval is removed from the total so that925 /// the approved address will not be able to transfer this item again from this owner.926 ///927 /// # Permissions928 ///929 /// * Collection owner930 /// * Collection admin931 /// * Current token owner932 /// * Address approved by current item owner933 ///934 /// # Arguments935 ///936 /// * `from`: The owner of the burning item.937 /// * `collection_id`: ID of the collection to which the item belongs.938 /// * `item_id`: ID of item to burn.939 /// * `value`: Number of pieces to burn.940 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.941 /// * Fungible Mode: The desired number of pieces to burn.942 /// * Re-Fungible Mode: The desired number of pieces to burn.943 #[weight = T::CommonWeightInfo::burn_from()]944 #[transactional]945 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {946 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947 let budget = budget::Value::new(NESTING_BUDGET);948949 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))950 }951952 /// Change ownership of the token.953 ///954 /// # Permissions955 ///956 /// * Collection owner957 /// * Collection admin958 /// * Current token owner959 ///960 /// # Arguments961 ///962 /// * `recipient`: Address of token recipient.963 /// * `collection_id`: ID of the collection the item belongs to.964 /// * `item_id`: ID of the item.965 /// * Non-Fungible Mode: Required.966 /// * Fungible Mode: Ignored.967 /// * Re-Fungible Mode: Required.968 ///969 /// * `value`: Amount to transfer.970 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.971 /// * Fungible Mode: The desired number of pieces to transfer.972 /// * Re-Fungible Mode: The desired number of pieces to transfer.973 #[weight = T::CommonWeightInfo::transfer()]974 #[transactional]975 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977 let budget = budget::Value::new(NESTING_BUDGET);978979 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980 }981982 /// Allow a non-permissioned address to transfer or burn an item.983 ///984 /// # Permissions985 ///986 /// * Collection owner987 /// * Collection admin988 /// * Current item owner989 ///990 /// # Arguments991 ///992 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993 /// * `collection_id`: ID of the collection the item belongs to.994 /// * `item_id`: ID of the item transactions on which are now approved.995 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996 /// Set to 0 to revoke the approval.997 #[weight = T::CommonWeightInfo::approve()]998 #[transactional]999 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {1000 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10011002 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1003 }10041005 /// Change ownership of an item on behalf of the owner as a non-owner account.1006 ///1007 /// See the [`approve`][`Pallet::approve`] method for additional information.1008 ///1009 /// After this method executes, one approval is removed from the total so that1010 /// the approved address will not be able to transfer this item again from this owner.1011 ///1012 /// # Permissions1013 ///1014 /// * Collection owner1015 /// * Collection admin1016 /// * Current item owner1017 /// * Address approved by current item owner1018 ///1019 /// # Arguments1020 ///1021 /// * `from`: Address that currently owns the token.1022 /// * `recipient`: Address of the new token-owner-to-be.1023 /// * `collection_id`: ID of the collection the item.1024 /// * `item_id`: ID of the item to be transferred.1025 /// * `value`: Amount to transfer.1026 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1027 /// * Fungible Mode: The desired number of pieces to transfer.1028 /// * Re-Fungible Mode: The desired number of pieces to transfer.1029 #[weight = T::CommonWeightInfo::transfer_from()]1030 #[transactional]1031 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1032 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1033 let budget = budget::Value::new(NESTING_BUDGET);10341035 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1036 }10371038 /// Set specific limits of a collection. Empty, or None fields mean chain default.1039 ///1040 /// # Permissions1041 ///1042 /// * Collection owner1043 /// * Collection admin1044 ///1045 /// # Arguments1046 ///1047 /// * `collection_id`: ID of the modified collection.1048 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1049 /// will not overwrite the old ones.1050 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1051 #[transactional]1052 pub fn set_collection_limits(1053 origin,1054 collection_id: CollectionId,1055 new_limit: CollectionLimits,1056 ) -> DispatchResult {1057 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1058 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1059 target_collection.check_is_internal()?;1060 target_collection.check_is_owner_or_admin(&sender)?;1061 let old_limit = &target_collection.limits;10621063 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10641065 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1066 collection_id1067 ));10681069 target_collection.save()1070 }10711072 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1073 ///1074 /// # Permissions1075 ///1076 /// * Collection owner1077 /// * Collection admin1078 ///1079 /// # Arguments1080 ///1081 /// * `collection_id`: ID of the modified collection.1082 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1083 /// will not overwrite the old ones.1084 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1085 #[transactional]1086 pub fn set_collection_permissions(1087 origin,1088 collection_id: CollectionId,1089 new_permission: CollectionPermissions,1090 ) -> DispatchResult {1091 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1092 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1093 target_collection.check_is_internal()?;1094 target_collection.check_is_owner_or_admin(&sender)?;1095 let old_limit = &target_collection.permissions;10961097 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10981099 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1100 collection_id1101 ));11021103 target_collection.save()1104 }11051106 /// Re-partition a refungible token, while owning all of its parts/pieces.1107 ///1108 /// # Permissions1109 ///1110 /// * Token owner (must own every part)1111 ///1112 /// # Arguments1113 ///1114 /// * `collection_id`: ID of the collection the RFT belongs to.1115 /// * `token_id`: ID of the RFT.1116 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1117 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1118 #[transactional]1119 pub fn repartition(1120 origin,1121 collection_id: CollectionId,1122 token_id: TokenId,1123 amount: u128,1124 ) -> DispatchResultWithPostInfo {1125 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1126 dispatch_tx::<T, _>(collection_id, |d| {1127 if let Some(refungible_extensions) = d.refungible_extensions() {1128 refungible_extensions.repartition(&sender, token_id, amount)1129 } else {1130 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1131 }1132 })1133 }1134 }1135}