difftreelog
doc: fix misinterpretations
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 `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 /// DEPRECATED - use create_collection_ex. 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 [`create_collection_ex`](Call::create_collection_ex) instead.302 ///303 /// # Permissions304 ///305 /// * Anyone - becomes the owner of the new collection.306 ///307 /// # Arguments308 ///309 /// * `collection_name`: UTF-16 string with collection name (limit 64 characters),310 /// will be stored as zero-terminated.311 /// * `collection_description`: UTF-16 string with collection description (limit 256 characters),312 /// will be stored as zero-terminated.313 /// * `token_prefix`: UTF-8 string with token prefix.314 /// * `mode`: [`CollectionMode`] and type dependent data.315 // returns collection ID316 #[weight = <SelfWeightOf<T>>::create_collection()]317 #[transactional]318 #[deprecated]319 pub fn create_collection(320 origin,321 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,322 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,323 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,324 mode: CollectionMode325 ) -> DispatchResult {326 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {327 name: collection_name,328 description: collection_description,329 token_prefix,330 mode,331 ..Default::default()332 };333 Self::create_collection_ex(origin, data)334 }335336 /// Create a collection with explicit parameters.337 /// Prefer it to the deprecated [`create_collection`](Call::create_collection) method.338 ///339 /// # Permissions340 ///341 /// * Anyone - becomes the owner of the new collection.342 ///343 /// # Arguments344 ///345 /// * `data`: Explicit data of a collection used for its creation.346 #[weight = <SelfWeightOf<T>>::create_collection()]347 #[transactional]348 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {349 let sender = ensure_signed(origin)?;350351 // =========352353 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;354355 Ok(())356 }357358 /// Destroy a collection if no tokens exist within.359 ///360 /// # Permissions361 ///362 /// * Collection owner363 ///364 /// # Arguments365 ///366 /// * `collection_id`: Collection to destroy.367 #[weight = <SelfWeightOf<T>>::destroy_collection()]368 #[transactional]369 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {370 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);371 let collection = <CollectionHandle<T>>::try_get(collection_id)?;372 collection.check_is_internal()?;373374 // =========375376 T::CollectionDispatch::destroy(sender, collection)?;377378 <NftTransferBasket<T>>::remove_prefix(collection_id, None);379 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);380 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);381382 <NftApproveBasket<T>>::remove_prefix(collection_id, None);383 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);384 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);385386 Ok(())387 }388389 /// Add an address to allow list.390 ///391 /// # Permissions392 ///393 /// * Collection owner394 /// * Collection admin395 ///396 /// # Arguments397 ///398 /// * `collection_id`: ID of the modified collection.399 /// * `address`: ID of the address to be added to the allowlist.400 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]401 #[transactional]402 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{403404 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);405 let collection = <CollectionHandle<T>>::try_get(collection_id)?;406 collection.check_is_internal()?;407408 <PalletCommon<T>>::toggle_allowlist(409 &collection,410 &sender,411 &address,412 true,413 )?;414415 Self::deposit_event(Event::<T>::AllowListAddressAdded(416 collection_id,417 address418 ));419420 Ok(())421 }422423 /// Remove an address from allow list.424 ///425 /// # Permissions426 ///427 /// * Collection owner428 /// * Collection admin429 ///430 /// # Arguments431 ///432 /// * `collection_id`: ID of the modified collection.433 /// * `address`: ID of the address to be removed from the allowlist.434 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]435 #[transactional]436 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{437438 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);439 let collection = <CollectionHandle<T>>::try_get(collection_id)?;440 collection.check_is_internal()?;441442 <PalletCommon<T>>::toggle_allowlist(443 &collection,444 &sender,445 &address,446 false,447 )?;448449 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(450 collection_id,451 address452 ));453454 Ok(())455 }456457 /// Change the owner of the collection.458 ///459 /// # Permissions460 ///461 /// * Collection owner462 ///463 /// # Arguments464 ///465 /// * `collection_id`: ID of the modified collection.466 /// * `new_owner`: ID of the account that will become the owner.467 #[weight = <SelfWeightOf<T>>::change_collection_owner()]468 #[transactional]469 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {470471 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);472473 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;474 target_collection.check_is_internal()?;475 target_collection.check_is_owner(&sender)?;476477 target_collection.owner = new_owner.clone();478 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(479 collection_id,480 new_owner481 ));482483 target_collection.save()484 }485486 /// Add an admin to a collection.487 ///488 /// NFT Collection can be controlled by multiple admin addresses489 /// (some which can also be servers, for example). Admins can issue490 /// and burn NFTs, as well as add and remove other admins,491 /// but cannot change NFT or Collection ownership.492 ///493 /// # Permissions494 ///495 /// * Collection owner496 /// * Collection admin497 ///498 /// # Arguments499 ///500 /// * `collection_id`: ID of the Collection to add an admin for.501 /// * `new_admin`: Address of new admin to add.502 #[weight = <SelfWeightOf<T>>::add_collection_admin()]503 #[transactional]504 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {505 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);506 let collection = <CollectionHandle<T>>::try_get(collection_id)?;507 collection.check_is_internal()?;508509 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(510 collection_id,511 new_admin.clone()512 ));513514 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)515 }516517 /// Remove admin of a collection.518 ///519 /// An admin address can remove itself. List of admins may become empty,520 /// in which case only Collection Owner will be able to add an Admin.521 ///522 /// # Permissions523 ///524 /// * Collection owner525 /// * Collection admin526 ///527 /// # Arguments528 ///529 /// * `collection_id`: ID of the collection to remove the admin for.530 /// * `account_id`: Address of the admin to remove.531 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]532 #[transactional]533 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {534 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);535 let collection = <CollectionHandle<T>>::try_get(collection_id)?;536 collection.check_is_internal()?;537538 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(539 collection_id,540 account_id.clone()541 ));542543 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)544 }545546 /// Set (invite) a new collection sponsor.547 /// If successful, confirmation from the sponsor-to-be will be pending.548 ///549 /// # Permissions550 ///551 /// * Collection owner552 /// * Collection admin553 ///554 /// # Arguments555 ///556 /// * `collection_id`: ID of the modified collection.557 /// * `new_sponsor`: ID of the account of the sponsor-to-be.558 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]559 #[transactional]560 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {561 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);562563 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;564 target_collection.check_is_owner_or_admin(&sender)?;565 target_collection.check_is_internal()?;566567 target_collection.set_sponsor(new_sponsor.clone())?;568569 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(570 collection_id,571 new_sponsor572 ));573574 target_collection.save()575 }576577 /// Confirm own sponsorship of a collection, becoming the sponsor.578 /// An invitation must be pending, see [`set_collection_sponsor`](Call::set_collection_sponsor).579 ///580 /// Sponsor can pay the fees of a transaction instead of the sender,581 /// but only within specified limits.582 ///583 /// # Permissions584 ///585 /// * Sponsor-to-be586 ///587 /// # Arguments588 ///589 /// * `collection_id`: ID of the collection with the pending sponsor.590 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]591 #[transactional]592 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {593 let sender = ensure_signed(origin)?;594595 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;596 target_collection.check_is_internal()?;597 ensure!(598 target_collection.confirm_sponsorship(&sender)?,599 Error::<T>::ConfirmUnsetSponsorFail600 );601602 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(603 collection_id,604 sender605 ));606607 target_collection.save()608 }609610 /// Remove a sponsor from a collection, making everyone pay for their own transactions.611 ///612 /// # Permissions613 ///614 /// * Collection owner615 ///616 /// # Arguments617 ///618 /// * `collection_id`: ID of the collection with the sponsor to remove.619 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]620 #[transactional]621 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {622 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623624 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;625 target_collection.check_is_internal()?;626 target_collection.check_is_owner(&sender)?;627628 target_collection.sponsorship = SponsorshipState::Disabled;629630 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(631 collection_id632 ));633 target_collection.save()634 }635636 /// Mint an item within a collection.637 ///638 /// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).639 ///640 /// # Permissions641 ///642 /// * Collection owner643 /// * Collection admin644 /// * Anyone if645 /// * Allow List is enabled, and646 /// * Address is added to allow list, and647 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))648 ///649 /// # Arguments650 ///651 /// * `collection_id`: ID of the collection to which an item would belong.652 /// * `owner`: Address of the initial owner of the item.653 /// * `data`: Token data describing the item to store on chain.654 #[weight = T::CommonWeightInfo::create_item()]655 #[transactional]656 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {657 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);658 let budget = budget::Value::new(NESTING_BUDGET);659660 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))661 }662663 /// Create multiple items within a collection.664 ///665 /// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).666 ///667 /// # Permissions668 ///669 /// * Collection owner670 /// * Collection admin671 /// * Anyone if672 /// * Allow List is enabled, and673 /// * Address is added to the allow list, and674 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))675 ///676 /// # Arguments677 ///678 /// * `collection_id`: ID of the collection to which the tokens would belong.679 /// * `owner`: Address of the initial owner of the tokens.680 /// * `items_data`: Vector of data describing each item to be created.681 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]682 #[transactional]683 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {684 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);685 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);686 let budget = budget::Value::new(NESTING_BUDGET);687688 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))689 }690691 /// Add or change collection properties.692 ///693 /// # Permissions694 ///695 /// * Collection owner696 /// * Collection admin697 ///698 /// # Arguments699 ///700 /// * `collection_id`: ID of the modified collection.701 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.702 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.703 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]704 #[transactional]705 pub fn set_collection_properties(706 origin,707 collection_id: CollectionId,708 properties: Vec<Property>709 ) -> DispatchResultWithPostInfo {710 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);711712 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);713714 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))715 }716717 /// Delete specified collection properties.718 ///719 /// # Permissions720 ///721 /// * Collection Owner722 /// * Collection Admin723 ///724 /// # Arguments725 ///726 /// * `collection_id`: ID of the modified collection.727 /// * `property_keys`: Vector of keys of the properties to be deleted.728 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.729 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]730 #[transactional]731 pub fn delete_collection_properties(732 origin,733 collection_id: CollectionId,734 property_keys: Vec<PropertyKey>,735 ) -> DispatchResultWithPostInfo {736 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);737738 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))741 }742743 /// Add or change token properties according to collection's permissions.744 /// Currently properties only work with NFTs.745 ///746 /// # Permissions747 ///748 /// * Depends on collection's token property permissions and specified property mutability:749 /// * Collection owner750 /// * Collection admin751 /// * Token owner752 ///753 /// See [`set_token_property_permissions`](Call::set_token_property_permissions).754 ///755 /// # Arguments756 ///757 /// * `collection_id: ID of the collection to which the token belongs.758 /// * `token_id`: ID of the modified token.759 /// * `properties`: Vector of key-value pairs stored as the token's metadata.760 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.761 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]762 #[transactional]763 pub fn set_token_properties(764 origin,765 collection_id: CollectionId,766 token_id: TokenId,767 properties: Vec<Property>768 ) -> DispatchResultWithPostInfo {769 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);770771 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772 let budget = budget::Value::new(NESTING_BUDGET);773774 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))775 }776777 /// Delete specified token properties. Currently properties only work with NFTs.778 ///779 /// # Permissions780 ///781 /// * Depends on collection's token property permissions and specified property mutability:782 /// * Collection owner783 /// * Collection admin784 /// * Token owner785 ///786 /// # Arguments787 ///788 /// * `collection_id`: ID of the collection to which the token belongs.789 /// * `token_id`: ID of the modified token.790 /// * `property_keys`: Vector of keys of the properties to be deleted.791 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.792 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]793 #[transactional]794 pub fn delete_token_properties(795 origin,796 collection_id: CollectionId,797 token_id: TokenId,798 property_keys: Vec<PropertyKey>799 ) -> DispatchResultWithPostInfo {800 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);801802 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);803 let budget = budget::Value::new(NESTING_BUDGET);804805 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))806 }807808 /// Add or change token property permissions of a collection.809 ///810 /// Without a permission for a particular key, a property with that key811 /// cannot be created in a token.812 ///813 /// # Permissions814 ///815 /// * Collection owner816 /// * Collection admin817 ///818 /// # Arguments819 ///820 /// * `collection_id`: ID of the modified collection.821 /// * `property_permissions`: Vector of permissions for property keys.822 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.823 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]824 #[transactional]825 pub fn set_token_property_permissions(826 origin,827 collection_id: CollectionId,828 property_permissions: Vec<PropertyKeyPermission>,829 ) -> DispatchResultWithPostInfo {830 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);831832 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);833834 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))835 }836837 /// Create multiple items within a collection with explicitly specified initial parameters.838 ///839 /// # Permissions840 ///841 /// * Collection owner842 /// * Collection admin843 /// * Anyone if844 /// * Allow List is enabled, and845 /// * Address is added to allow list, and846 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))847 ///848 /// # Arguments849 ///850 /// * `collection_id`: ID of the collection to which the tokens would belong.851 /// * `data`: Explicit item creation data.852 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]853 #[transactional]854 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {855 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856 let budget = budget::Value::new(NESTING_BUDGET);857858 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))859 }860861 /// Completely allow or disallow transfers for a particular collection.862 ///863 /// # Permissions864 ///865 /// * Collection owner866 ///867 /// # Arguments868 ///869 /// * `collection_id`: ID of the collection.870 /// * `value`: New value of the flag, are transfers allowed?871 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]872 #[transactional]873 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {874 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);875 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;876 target_collection.check_is_internal()?;877 target_collection.check_is_owner(&sender)?;878879 // =========880881 target_collection.limits.transfers_enabled = Some(value);882 target_collection.save()883 }884885 /// Destroy an item.886 ///887 /// # Permissions888 ///889 /// * Collection owner890 /// * Collection admin891 /// * Current item owner892 ///893 /// # Arguments894 ///895 /// * `collection_id`: ID of the collection to which the item belongs.896 /// * `item_id`: ID of item to burn.897 /// * `value`: Number of parts of the item to destroy.898 /// * Non-Fungible Mode: There is always 1 NFT.899 /// * Fungible Mode: The desired number of parts to burn.900 /// * Re-Fungible Mode: The desired number of parts to burn.901 #[weight = T::CommonWeightInfo::burn_item()]902 #[transactional]903 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {904 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);905906 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;907 if value == 1 {908 <NftTransferBasket<T>>::remove(collection_id, item_id);909 <NftApproveBasket<T>>::remove(collection_id, item_id);910 }911 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?912 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());913 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));914 Ok(post_info)915 }916917 /// Destroy a token on behalf of the owner as a non-owner account.918 /// See also: [`approve`](Call::approve).919 ///920 /// After this method executes, one approval is removed from the total so that921 /// the approved address will not be able to transfer this item again from this owner.922 ///923 /// # Permissions924 ///925 /// * Collection owner926 /// * Collection admin927 /// * Current token owner928 /// * Address approved by current item owner929 ///930 /// # Arguments931 ///932 /// * `from`: The owner of the burning item.933 /// * `collection_id`: ID of the collection to which the item belongs.934 /// * `item_id`: ID of item to burn.935 /// * `value`: Number of parts to burn.936 /// * Non-Fungible Mode: There is always 1 NFT.937 /// * Fungible Mode: The desired number of parts to burn.938 /// * Re-Fungible Mode: The desired number of parts to burn.939 #[weight = T::CommonWeightInfo::burn_from()]940 #[transactional]941 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {942 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);943 let budget = budget::Value::new(NESTING_BUDGET);944945 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))946 }947948 /// Change ownership of the token.949 ///950 /// # Permissions951 ///952 /// * Collection owner953 /// * Collection admin954 /// * Current token owner955 ///956 /// # Arguments957 ///958 /// * `recipient`: Address of token recipient.959 /// * `collection_id`: ID of the collection the item belongs to.960 /// * `item_id`: ID of the item.961 /// * Non-Fungible Mode: Required.962 /// * Fungible Mode: Ignored.963 /// * Re-Fungible Mode: Required.964 ///965 /// * `value`: Amount to transfer.966 /// * Non-Fungible Mode: There is always 1 NFT.967 /// * Fungible Mode: The desired number of parts to transfer.968 /// * Re-Fungible Mode: The desired number of parts to transfer.969 #[weight = T::CommonWeightInfo::transfer()]970 #[transactional]971 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {972 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973 let budget = budget::Value::new(NESTING_BUDGET);974975 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))976 }977978 /// Allow a non-permissioned address to transfer or burn an item.979 ///980 /// # Permissions981 ///982 /// * Collection owner983 /// * Collection admin984 /// * Current item owner985 ///986 /// # Arguments987 ///988 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.989 /// * `collection_id`: ID of the collection the item belongs to.990 /// * `item_id`: ID of the item transactions on which are now approved.991 /// * `amount`: Number of approved transactions overwriting the current number,992 /// e.g. set to `0` to remove approval.993 #[weight = T::CommonWeightInfo::approve()]994 #[transactional]995 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {996 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997998 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))999 }10001001 /// Change ownership of an item on behalf of the owner as a non-owner account.1002 /// See the [`approve`](Call::approve) method for additional information.1003 ///1004 /// After this method executes, one approval is removed from the total so that1005 /// the approved address will not be able to transfer this item again from this owner.1006 ///1007 /// # Permissions1008 ///1009 /// * Collection owner1010 /// * Collection admin1011 /// * Current item owner1012 /// * Address approved by current item owner1013 ///1014 /// # Arguments1015 ///1016 /// * `from`: Address that currently owns the token.1017 /// * `recipient`: Address of the new token-owner-to-be.1018 /// * `collection_id`: ID of the collection the item.1019 /// * `item_id`: ID of the item to be transferred.1020 /// * `value`: Amount of parts to transfer.1021 /// * Non-Fungible Mode: There is always 1 NFT.1022 /// * Fungible Mode: The desired number of parts to transfer.1023 /// * Re-Fungible Mode: The desired number of parts to transfer.1024 #[weight = T::CommonWeightInfo::transfer_from()]1025 #[transactional]1026 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1027 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1028 let budget = budget::Value::new(NESTING_BUDGET);10291030 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1031 }10321033 /// Set specific limits of a collection. Empty, or None fields mean chain default.1034 ///1035 /// # Permissions1036 ///1037 /// * Collection owner1038 /// * Collection admin1039 ///1040 /// # Arguments1041 ///1042 /// * `collection_id`: ID of the modified collection.1043 /// * `new_limit`: New limits of the collection. They will overwrite the current ones.1044 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1045 #[transactional]1046 pub fn set_collection_limits(1047 origin,1048 collection_id: CollectionId,1049 new_limit: CollectionLimits,1050 ) -> DispatchResult {1051 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1052 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1053 target_collection.check_is_internal()?;1054 target_collection.check_is_owner_or_admin(&sender)?;1055 let old_limit = &target_collection.limits;10561057 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10581059 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1060 collection_id1061 ));10621063 target_collection.save()1064 }10651066 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1067 ///1068 /// # Permissions1069 ///1070 /// * Collection owner1071 /// * Collection admin1072 ///1073 /// # Arguments1074 ///1075 /// * `collection_id`: ID of the modified collection.1076 /// * `new_permission`: New permissions of the collection. They will overwrite the current ones.1077 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1078 #[transactional]1079 pub fn set_collection_permissions(1080 origin,1081 collection_id: CollectionId,1082 new_permission: CollectionPermissions,1083 ) -> DispatchResult {1084 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1085 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1086 target_collection.check_is_internal()?;1087 target_collection.check_is_owner_or_admin(&sender)?;1088 let old_limit = &target_collection.permissions;10891090 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10911092 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1093 collection_id1094 ));10951096 target_collection.save()1097 }10981099 /// Re-partition a refungible token, while owning all of its parts.1100 ///1101 /// # Permissions1102 ///1103 /// * Token owner (must own every part)1104 ///1105 /// # Arguments1106 ///1107 /// * `collection_id`: ID of the collection the RFT belongs to.1108 /// * `token_id`: ID of the RFT.1109 /// * `amount`: New number of parts into which the token shall be partitioned.1110 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1111 #[transactional]1112 pub fn repartition(1113 origin,1114 collection_id: CollectionId,1115 token_id: TokenId,1116 amount: u128,1117 ) -> DispatchResultWithPostInfo {1118 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1119 dispatch_tx::<T, _>(collection_id, |d| {1120 if let Some(refungible_extensions) = d.refungible_extensions() {1121 refungible_extensions.repartition(&sender, token_id, amount)1122 } else {1123 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1124 }1125 })1126 }1127 }1128}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 `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 /// DEPRECATED - use createCollectionEx. 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 [`create_collection_ex`](Call::create_collection_ex) instead.302 ///303 /// # Permissions304 ///305 /// * Anyone - becomes the owner of the new collection.306 ///307 /// # Arguments308 ///309 /// * `collection_name`: UTF-16 string with collection name (limit 64 characters),310 /// will be stored as zero-terminated.311 /// * `collection_description`: UTF-16 string with collection description (limit 256 characters),312 /// will be stored as zero-terminated.313 /// * `token_prefix`: UTF-8 string with token prefix.314 /// * `mode`: [`CollectionMode`] and type dependent data.315 // returns collection ID316 #[weight = <SelfWeightOf<T>>::create_collection()]317 #[transactional]318 #[deprecated]319 pub fn create_collection(320 origin,321 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,322 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,323 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,324 mode: CollectionMode325 ) -> DispatchResult {326 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {327 name: collection_name,328 description: collection_description,329 token_prefix,330 mode,331 ..Default::default()332 };333 Self::create_collection_ex(origin, data)334 }335336 /// Create a collection with explicit parameters.337 /// Prefer it to the deprecated [`create_collection`](Call::create_collection) method.338 ///339 /// # Permissions340 ///341 /// * Anyone - becomes the owner of the new collection.342 ///343 /// # Arguments344 ///345 /// * `data`: Explicit data of a collection used for its creation.346 #[weight = <SelfWeightOf<T>>::create_collection()]347 #[transactional]348 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {349 let sender = ensure_signed(origin)?;350351 // =========352353 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;354355 Ok(())356 }357358 /// Destroy a collection if no tokens exist within.359 ///360 /// # Permissions361 ///362 /// * Collection owner363 ///364 /// # Arguments365 ///366 /// * `collection_id`: Collection to destroy.367 #[weight = <SelfWeightOf<T>>::destroy_collection()]368 #[transactional]369 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {370 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);371 let collection = <CollectionHandle<T>>::try_get(collection_id)?;372 collection.check_is_internal()?;373374 // =========375376 T::CollectionDispatch::destroy(sender, collection)?;377378 <NftTransferBasket<T>>::remove_prefix(collection_id, None);379 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);380 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);381382 <NftApproveBasket<T>>::remove_prefix(collection_id, None);383 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);384 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);385386 Ok(())387 }388389 /// Add an address to allow list.390 ///391 /// # Permissions392 ///393 /// * Collection owner394 /// * Collection admin395 ///396 /// # Arguments397 ///398 /// * `collection_id`: ID of the modified collection.399 /// * `address`: ID of the address to be added to the allowlist.400 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]401 #[transactional]402 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{403404 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);405 let collection = <CollectionHandle<T>>::try_get(collection_id)?;406 collection.check_is_internal()?;407408 <PalletCommon<T>>::toggle_allowlist(409 &collection,410 &sender,411 &address,412 true,413 )?;414415 Self::deposit_event(Event::<T>::AllowListAddressAdded(416 collection_id,417 address418 ));419420 Ok(())421 }422423 /// Remove an address from allow list.424 ///425 /// # Permissions426 ///427 /// * Collection owner428 /// * Collection admin429 ///430 /// # Arguments431 ///432 /// * `collection_id`: ID of the modified collection.433 /// * `address`: ID of the address to be removed from the allowlist.434 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]435 #[transactional]436 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{437438 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);439 let collection = <CollectionHandle<T>>::try_get(collection_id)?;440 collection.check_is_internal()?;441442 <PalletCommon<T>>::toggle_allowlist(443 &collection,444 &sender,445 &address,446 false,447 )?;448449 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(450 collection_id,451 address452 ));453454 Ok(())455 }456457 /// Change the owner of the collection.458 ///459 /// # Permissions460 ///461 /// * Collection owner462 ///463 /// # Arguments464 ///465 /// * `collection_id`: ID of the modified collection.466 /// * `new_owner`: ID of the account that will become the owner.467 #[weight = <SelfWeightOf<T>>::change_collection_owner()]468 #[transactional]469 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {470471 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);472473 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;474 target_collection.check_is_internal()?;475 target_collection.check_is_owner(&sender)?;476477 target_collection.owner = new_owner.clone();478 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(479 collection_id,480 new_owner481 ));482483 target_collection.save()484 }485486 /// Add an admin to a collection.487 ///488 /// NFT Collection can be controlled by multiple admin addresses489 /// (some which can also be servers, for example). Admins can issue490 /// and burn NFTs, as well as add and remove other admins,491 /// but cannot change NFT or Collection ownership.492 ///493 /// # Permissions494 ///495 /// * Collection owner496 /// * Collection admin497 ///498 /// # Arguments499 ///500 /// * `collection_id`: ID of the Collection to add an admin for.501 /// * `new_admin`: Address of new admin to add.502 #[weight = <SelfWeightOf<T>>::add_collection_admin()]503 #[transactional]504 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {505 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);506 let collection = <CollectionHandle<T>>::try_get(collection_id)?;507 collection.check_is_internal()?;508509 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(510 collection_id,511 new_admin.clone()512 ));513514 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)515 }516517 /// Remove admin of a collection.518 ///519 /// An admin address can remove itself. List of admins may become empty,520 /// in which case only Collection Owner will be able to add an Admin.521 ///522 /// # Permissions523 ///524 /// * Collection owner525 /// * Collection admin526 ///527 /// # Arguments528 ///529 /// * `collection_id`: ID of the collection to remove the admin for.530 /// * `account_id`: Address of the admin to remove.531 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]532 #[transactional]533 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {534 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);535 let collection = <CollectionHandle<T>>::try_get(collection_id)?;536 collection.check_is_internal()?;537538 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(539 collection_id,540 account_id.clone()541 ));542543 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)544 }545546 /// Set (invite) a new collection sponsor.547 /// If successful, confirmation from the sponsor-to-be will be pending.548 ///549 /// # Permissions550 ///551 /// * Collection owner552 /// * Collection admin553 ///554 /// # Arguments555 ///556 /// * `collection_id`: ID of the modified collection.557 /// * `new_sponsor`: ID of the account of the sponsor-to-be.558 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]559 #[transactional]560 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {561 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);562563 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;564 target_collection.check_is_owner_or_admin(&sender)?;565 target_collection.check_is_internal()?;566567 target_collection.set_sponsor(new_sponsor.clone())?;568569 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(570 collection_id,571 new_sponsor572 ));573574 target_collection.save()575 }576577 /// Confirm own sponsorship of a collection, becoming the sponsor.578 /// An invitation must be pending, see [`set_collection_sponsor`](Call::set_collection_sponsor).579 ///580 /// Sponsor can pay the fees of a transaction instead of the sender,581 /// but only within specified limits.582 ///583 /// # Permissions584 ///585 /// * Sponsor-to-be586 ///587 /// # Arguments588 ///589 /// * `collection_id`: ID of the collection with the pending sponsor.590 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]591 #[transactional]592 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {593 let sender = ensure_signed(origin)?;594595 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;596 target_collection.check_is_internal()?;597 ensure!(598 target_collection.confirm_sponsorship(&sender)?,599 Error::<T>::ConfirmUnsetSponsorFail600 );601602 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(603 collection_id,604 sender605 ));606607 target_collection.save()608 }609610 /// Remove a sponsor from a collection, making everyone pay for their own transactions.611 ///612 /// # Permissions613 ///614 /// * Collection owner615 ///616 /// # Arguments617 ///618 /// * `collection_id`: ID of the collection with the sponsor to remove.619 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]620 #[transactional]621 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {622 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623624 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;625 target_collection.check_is_internal()?;626 target_collection.check_is_owner(&sender)?;627628 target_collection.sponsorship = SponsorshipState::Disabled;629630 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(631 collection_id632 ));633 target_collection.save()634 }635636 /// Mint an item within a collection.637 ///638 /// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).639 ///640 /// # Permissions641 ///642 /// * Collection owner643 /// * Collection admin644 /// * Anyone if645 /// * Allow List is enabled, and646 /// * Address is added to allow list, and647 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))648 ///649 /// # Arguments650 ///651 /// * `collection_id`: ID of the collection to which an item would belong.652 /// * `owner`: Address of the initial owner of the item.653 /// * `data`: Token data describing the item to store on chain.654 #[weight = T::CommonWeightInfo::create_item()]655 #[transactional]656 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {657 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);658 let budget = budget::Value::new(NESTING_BUDGET);659660 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))661 }662663 /// Create multiple items within a collection.664 ///665 /// A collection must exist first, see [`create_collection_ex`](Call::create_collection_ex).666 ///667 /// # Permissions668 ///669 /// * Collection owner670 /// * Collection admin671 /// * Anyone if672 /// * Allow List is enabled, and673 /// * Address is added to the allow list, and674 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))675 ///676 /// # Arguments677 ///678 /// * `collection_id`: ID of the collection to which the tokens would belong.679 /// * `owner`: Address of the initial owner of the tokens.680 /// * `items_data`: Vector of data describing each item to be created.681 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]682 #[transactional]683 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {684 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);685 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);686 let budget = budget::Value::new(NESTING_BUDGET);687688 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))689 }690691 /// Add or change collection properties.692 ///693 /// # Permissions694 ///695 /// * Collection owner696 /// * Collection admin697 ///698 /// # Arguments699 ///700 /// * `collection_id`: ID of the modified collection.701 /// * `properties`: Vector of key-value pairs stored as the collection's metadata.702 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.703 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]704 #[transactional]705 pub fn set_collection_properties(706 origin,707 collection_id: CollectionId,708 properties: Vec<Property>709 ) -> DispatchResultWithPostInfo {710 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);711712 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);713714 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))715 }716717 /// Delete specified collection properties.718 ///719 /// # Permissions720 ///721 /// * Collection Owner722 /// * Collection Admin723 ///724 /// # Arguments725 ///726 /// * `collection_id`: ID of the modified collection.727 /// * `property_keys`: Vector of keys of the properties to be deleted.728 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.729 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]730 #[transactional]731 pub fn delete_collection_properties(732 origin,733 collection_id: CollectionId,734 property_keys: Vec<PropertyKey>,735 ) -> DispatchResultWithPostInfo {736 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);737738 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))741 }742743 /// Add or change token properties according to collection's permissions.744 /// Currently properties only work with NFTs.745 ///746 /// # Permissions747 ///748 /// * Depends on collection's token property permissions and specified property mutability:749 /// * Collection owner750 /// * Collection admin751 /// * Token owner752 ///753 /// See [`set_token_property_permissions`](Call::set_token_property_permissions).754 ///755 /// # Arguments756 ///757 /// * `collection_id: ID of the collection to which the token belongs.758 /// * `token_id`: ID of the modified token.759 /// * `properties`: Vector of key-value pairs stored as the token's metadata.760 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.761 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]762 #[transactional]763 pub fn set_token_properties(764 origin,765 collection_id: CollectionId,766 token_id: TokenId,767 properties: Vec<Property>768 ) -> DispatchResultWithPostInfo {769 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);770771 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772 let budget = budget::Value::new(NESTING_BUDGET);773774 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))775 }776777 /// Delete specified token properties. Currently properties only work with NFTs.778 ///779 /// # Permissions780 ///781 /// * Depends on collection's token property permissions and specified property mutability:782 /// * Collection owner783 /// * Collection admin784 /// * Token owner785 ///786 /// # Arguments787 ///788 /// * `collection_id`: ID of the collection to which the token belongs.789 /// * `token_id`: ID of the modified token.790 /// * `property_keys`: Vector of keys of the properties to be deleted.791 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.792 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]793 #[transactional]794 pub fn delete_token_properties(795 origin,796 collection_id: CollectionId,797 token_id: TokenId,798 property_keys: Vec<PropertyKey>799 ) -> DispatchResultWithPostInfo {800 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);801802 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);803 let budget = budget::Value::new(NESTING_BUDGET);804805 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))806 }807808 /// Add or change token property permissions of a collection.809 ///810 /// Without a permission for a particular key, a property with that key811 /// cannot be created in a token.812 ///813 /// # Permissions814 ///815 /// * Collection owner816 /// * Collection admin817 ///818 /// # Arguments819 ///820 /// * `collection_id`: ID of the modified collection.821 /// * `property_permissions`: Vector of permissions for property keys.822 /// Keys support Latin letters, `-`, `_`, and `.` as symbols.823 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]824 #[transactional]825 pub fn set_token_property_permissions(826 origin,827 collection_id: CollectionId,828 property_permissions: Vec<PropertyKeyPermission>,829 ) -> DispatchResultWithPostInfo {830 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);831832 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);833834 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))835 }836837 /// Create multiple items within a collection with explicitly specified initial parameters.838 ///839 /// # Permissions840 ///841 /// * Collection owner842 /// * Collection admin843 /// * Anyone if844 /// * Allow List is enabled, and845 /// * Address is added to allow list, and846 /// * MintPermission is enabled (see [`set_collection_permissions`](Call::set_collection_permissions))847 ///848 /// # Arguments849 ///850 /// * `collection_id`: ID of the collection to which the tokens would belong.851 /// * `data`: Explicit item creation data.852 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]853 #[transactional]854 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {855 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856 let budget = budget::Value::new(NESTING_BUDGET);857858 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))859 }860861 /// Completely allow or disallow transfers for a particular collection.862 ///863 /// # Permissions864 ///865 /// * Collection owner866 ///867 /// # Arguments868 ///869 /// * `collection_id`: ID of the collection.870 /// * `value`: New value of the flag, are transfers allowed?871 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]872 #[transactional]873 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {874 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);875 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;876 target_collection.check_is_internal()?;877 target_collection.check_is_owner(&sender)?;878879 // =========880881 target_collection.limits.transfers_enabled = Some(value);882 target_collection.save()883 }884885 /// Destroy an item.886 ///887 /// # Permissions888 ///889 /// * Collection owner890 /// * Collection admin891 /// * Current item owner892 ///893 /// # Arguments894 ///895 /// * `collection_id`: ID of the collection to which the item belongs.896 /// * `item_id`: ID of item to burn.897 /// * `value`: Number of pieces of the item to destroy.898 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.899 /// * Fungible Mode: The desired number of pieces to burn.900 /// * Re-Fungible Mode: The desired number of pieces to burn.901 #[weight = T::CommonWeightInfo::burn_item()]902 #[transactional]903 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {904 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);905906 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;907 if value == 1 {908 <NftTransferBasket<T>>::remove(collection_id, item_id);909 <NftApproveBasket<T>>::remove(collection_id, item_id);910 }911 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?912 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());913 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));914 Ok(post_info)915 }916917 /// Destroy a token on behalf of the owner as a non-owner account.918 /// See also: [`approve`](Call::approve).919 ///920 /// After this method executes, one approval is removed from the total so that921 /// the approved address will not be able to transfer this item again from this owner.922 ///923 /// # Permissions924 ///925 /// * Collection owner926 /// * Collection admin927 /// * Current token owner928 /// * Address approved by current item owner929 ///930 /// # Arguments931 ///932 /// * `from`: The owner of the burning item.933 /// * `collection_id`: ID of the collection to which the item belongs.934 /// * `item_id`: ID of item to burn.935 /// * `value`: Number of pieces to burn.936 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.937 /// * Fungible Mode: The desired number of pieces to burn.938 /// * Re-Fungible Mode: The desired number of pieces to burn.939 #[weight = T::CommonWeightInfo::burn_from()]940 #[transactional]941 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {942 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);943 let budget = budget::Value::new(NESTING_BUDGET);944945 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))946 }947948 /// Change ownership of the token.949 ///950 /// # Permissions951 ///952 /// * Collection owner953 /// * Collection admin954 /// * Current token owner955 ///956 /// # Arguments957 ///958 /// * `recipient`: Address of token recipient.959 /// * `collection_id`: ID of the collection the item belongs to.960 /// * `item_id`: ID of the item.961 /// * Non-Fungible Mode: Required.962 /// * Fungible Mode: Ignored.963 /// * Re-Fungible Mode: Required.964 ///965 /// * `value`: Amount to transfer.966 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.967 /// * Fungible Mode: The desired number of pieces to transfer.968 /// * Re-Fungible Mode: The desired number of pieces to transfer.969 #[weight = T::CommonWeightInfo::transfer()]970 #[transactional]971 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {972 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973 let budget = budget::Value::new(NESTING_BUDGET);974975 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))976 }977978 /// Allow a non-permissioned address to transfer or burn an item.979 ///980 /// # Permissions981 ///982 /// * Collection owner983 /// * Collection admin984 /// * Current item owner985 ///986 /// # Arguments987 ///988 /// * `spender`: Account to be approved to make specific transactions on non-owned tokens.989 /// * `collection_id`: ID of the collection the item belongs to.990 /// * `item_id`: ID of the item transactions on which are now approved.991 /// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).992 /// Set to 0 to revoke the approval.993 #[weight = T::CommonWeightInfo::approve()]994 #[transactional]995 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {996 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);997998 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))999 }10001001 /// Change ownership of an item on behalf of the owner as a non-owner account.1002 /// See the [`approve`](Call::approve) method for additional information.1003 ///1004 /// After this method executes, one approval is removed from the total so that1005 /// the approved address will not be able to transfer this item again from this owner.1006 ///1007 /// # Permissions1008 ///1009 /// * Collection owner1010 /// * Collection admin1011 /// * Current item owner1012 /// * Address approved by current item owner1013 ///1014 /// # Arguments1015 ///1016 /// * `from`: Address that currently owns the token.1017 /// * `recipient`: Address of the new token-owner-to-be.1018 /// * `collection_id`: ID of the collection the item.1019 /// * `item_id`: ID of the item to be transferred.1020 /// * `value`: Amount to transfer.1021 /// * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1022 /// * Fungible Mode: The desired number of pieces to transfer.1023 /// * Re-Fungible Mode: The desired number of pieces to transfer.1024 #[weight = T::CommonWeightInfo::transfer_from()]1025 #[transactional]1026 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1027 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1028 let budget = budget::Value::new(NESTING_BUDGET);10291030 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1031 }10321033 /// Set specific limits of a collection. Empty, or None fields mean chain default.1034 ///1035 /// # Permissions1036 ///1037 /// * Collection owner1038 /// * Collection admin1039 ///1040 /// # Arguments1041 ///1042 /// * `collection_id`: ID of the modified collection.1043 /// * `new_limit`: New limits of the collection. Fields that are not set (None)1044 /// will not overwrite the old ones.1045 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1046 #[transactional]1047 pub fn set_collection_limits(1048 origin,1049 collection_id: CollectionId,1050 new_limit: CollectionLimits,1051 ) -> DispatchResult {1052 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1053 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1054 target_collection.check_is_internal()?;1055 target_collection.check_is_owner_or_admin(&sender)?;1056 let old_limit = &target_collection.limits;10571058 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10591060 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1061 collection_id1062 ));10631064 target_collection.save()1065 }10661067 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1068 ///1069 /// # Permissions1070 ///1071 /// * Collection owner1072 /// * Collection admin1073 ///1074 /// # Arguments1075 ///1076 /// * `collection_id`: ID of the modified collection.1077 /// * `new_permission`: New permissions of the collection. Fields that are not set (None)1078 /// will not overwrite the old ones.1079 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1080 #[transactional]1081 pub fn set_collection_permissions(1082 origin,1083 collection_id: CollectionId,1084 new_permission: CollectionPermissions,1085 ) -> DispatchResult {1086 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1088 target_collection.check_is_internal()?;1089 target_collection.check_is_owner_or_admin(&sender)?;1090 let old_limit = &target_collection.permissions;10911092 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10931094 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1095 collection_id1096 ));10971098 target_collection.save()1099 }11001101 /// Re-partition a refungible token, while owning all of its parts/pieces.1102 ///1103 /// # Permissions1104 ///1105 /// * Token owner (must own every part)1106 ///1107 /// # Arguments1108 ///1109 /// * `collection_id`: ID of the collection the RFT belongs to.1110 /// * `token_id`: ID of the RFT.1111 /// * `amount`: New number of parts/pieces into which the token shall be partitioned.1112 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1113 #[transactional]1114 pub fn repartition(1115 origin,1116 collection_id: CollectionId,1117 token_id: TokenId,1118 amount: u128,1119 ) -> DispatchResultWithPostInfo {1120 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1121 dispatch_tx::<T, _>(collection_id, |d| {1122 if let Some(refungible_extensions) = d.refungible_extensions() {1123 refungible_extensions.repartition(&sender, token_id, amount)1124 } else {1125 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1126 }1127 })1128 }1129 }1130}