difftreelog
doc: adjust documentation style
in: master
2 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -288,59 +288,47 @@
///
/// # Arguments
///
- /// * collection_id: Globally unique identifier of newly created collection.
- ///
- /// * mode: [CollectionMode] converted into u8.
- ///
- /// * account_id: Collection owner.
+ /// * collection_id - Globally unique identifier of newly created collection.
+ /// * mode - [CollectionMode] converted into u8.
+ /// * account_id - Collection owner.
CollectionCreated(CollectionId, u8, T::AccountId),
/// New collection was destroyed
///
/// # Arguments
///
- /// * collection_id: Globally unique identifier of collection that has been destroyed.
+ /// * collection_id - Globally unique identifier of collection that has been destroyed.
CollectionDestroyed(CollectionId),
/// New item was created.
///
/// # Arguments
///
- /// * collection_id: ID of the collection where the item was created.
- ///
- /// * item_id: ID of the item. Unique within the collection.
- ///
- /// * recipient: Owner of the newly created item.
- ///
- /// * amount: The amount of tokens that were created (always 1 for NFT).
+ /// * collection_id - ID of the collection where the item was created.
+ /// * item_id - ID of the item. Unique within the collection.
+ /// * recipient - Owner of the newly created item.
+ /// * amount - The amount of tokens that were created (always 1 for NFT).
ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),
/// Collection item was burned.
///
/// # Arguments
///
- /// * collection_id: Identifier of the collection to which the burned NFT belonged.
- ///
- /// * item_id: Identifier of burned NFT.
- ///
- /// * owner: Which user has destroyed their tokens.
- ///
- /// * amount: The amount of tokens that were destroyed (always 1 for NFT).
+ /// * collection_id - Identifier of the collection to which the burned NFT belonged.
+ /// * item_id - Identifier of burned NFT.
+ /// * owner - Which user has destroyed their tokens.
+ /// * amount - Amount of tokens that were destroyed (always 1 for NFT).
ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),
/// Item was transferred.
///
/// # Arguments
///
- /// * collection_id: ID of the collection to which the item belongs.
- ///
- /// * item_id: ID of the item transferred.
- ///
- /// * sender: Original owner of the item.
- ///
- /// * recipient: New owner of the item.
- ///
- /// * amount: The amount of tokens that were transferred (always 1 for NFT).
+ /// * collection_id - ID of the collection to which the item belongs.
+ /// * item_id - ID of the item transferred.
+ /// * sender - Original owner of the item.
+ /// * recipient - New owner of the item.
+ /// * amount - Amount of tokens that were transferred (always 1 for NFT).
Transfer(
CollectionId,
TokenId,
@@ -352,15 +340,11 @@
/// Sponsoring allowance was approved.
///
/// # Arguments
- ///
- /// * collection_id
///
+ /// * collection_id - todo:doc flesh out
/// * item_id
- ///
/// * sender
- ///
/// * spender
- ///
/// * amount
Approved(
CollectionId,
@@ -374,49 +358,42 @@
///
/// # Arguments
///
- /// * collection_id: ID of the collection, whose property was just set.
- ///
- /// * property_key: Key of the property that was just set.
+ /// * collection_id - ID of the collection, whose property was just set.
+ /// * property_key - Key of the property that was just set.
CollectionPropertySet(CollectionId, PropertyKey),
/// Collection property was deleted.
///
/// # Arguments
///
- /// * collection_id: ID of the collection, whose property was just deleted.
- ///
- /// * property_key: Key of the property that was just deleted.
+ /// * collection_id - ID of the collection, whose property was just deleted.
+ /// * property_key - Key of the property that was just deleted.
CollectionPropertyDeleted(CollectionId, PropertyKey),
/// Item property was added or edited.
///
/// # Arguments
///
- /// * collection_id: ID of the collection, whose token's property was just set.
- ///
- /// * item_id: ID of the item, whose property was just set.
- ///
- /// * property_key: Key of the property that was just set.
+ /// * collection_id - ID of the collection, whose token's property was just set.
+ /// * item_id - ID of the item, whose property was just set.
+ /// * property_key - Key of the property that was just set.
TokenPropertySet(CollectionId, TokenId, PropertyKey),
/// Item property was deleted.
///
/// # Arguments
///
- /// * collection_id: ID of the collection, whose token's property was just deleted.
- ///
- /// * item_id: ID of the item, whose property was just deleted.
- ///
- /// * property_key: Key of the property that was just deleted.
+ /// * collection_id - ID of the collection, whose token's property was just deleted.
+ /// * item_id - ID of the item, whose property was just deleted.
+ /// * property_key - Key of the property that was just deleted.
TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),
/// Token property permission was added or updated for a collection.
///
/// # Arguments
///
- /// * collection_id: ID of the collection, whose permissions were just set/updated.
- ///
- /// * property_key: Key of the property of the set/updated permission.
+ /// * collection_id - ID of the collection, whose permissions were just set/updated.
+ /// * property_key - Key of the property of the set/updated permission.
PropertyPermissionSet(CollectionId, PropertyKey),
}
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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28 decl_module, decl_storage, decl_error, decl_event,29 dispatch::DispatchResult,30 ensure, fail,31 weights::{Weight},32 transactional,33 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},34 BoundedVec,35};36use scale_info::TypeInfo;37use frame_system::{self as system, ensure_signed};38use sp_runtime::{sp_std::prelude::Vec};39use up_data_structs::{40 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,41 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,42 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,43 PropertyKeyPermission,44};45use pallet_evm::account::CrossAccountId;46use pallet_common::{47 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,48 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,49};50pub mod eth;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;54pub mod weights;55use weights::WeightInfo;5657const NESTING_BUDGET: u32 = 5;5859decl_error! {60 /// Error for non-fungible-token module.61 pub enum Error for Module<T: Config> {62 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.63 CollectionDecimalPointLimitExceeded,64 /// This address is not set as sponsor, use setCollectionSponsor first.65 ConfirmUnsetSponsorFail,66 /// Length of items properties must be greater than 0.67 EmptyArgument,68 /// Repertition is only supported by refungible collection69 RepartitionCalledOnNonRefungibleCollection,70 }71}7273pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {74 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7576 /// Weight information for extrinsics in this pallet.77 type WeightInfo: WeightInfo;78 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;79 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;80}8182decl_event! {83 pub enum Event<T>84 where85 <T as frame_system::Config>::AccountId,86 <T as pallet_evm::account::Config>::CrossAccountId,87 {88 /// Collection sponsor was removed89 ///90 /// # Arguments91 ///92 /// * collection_id: Globally unique collection identifier.93 CollectionSponsorRemoved(CollectionId),9495 /// Collection admin was added96 ///97 /// # Arguments98 ///99 /// * collection_id: Globally unique collection identifier.100 ///101 /// * admin: Admin address.102 CollectionAdminAdded(CollectionId, CrossAccountId),103104 /// Collection owned was changed105 ///106 /// # Arguments107 ///108 /// * collection_id: Globally unique collection identifier.109 ///110 /// * owner: New owner address.111 CollectionOwnedChanged(CollectionId, AccountId),112113 /// Collection sponsor was set114 ///115 /// # Arguments116 ///117 /// * collection_id: Globally unique collection identifier.118 ///119 /// * owner: New sponsor address.120 CollectionSponsorSet(CollectionId, AccountId),121122 /// New sponsor was confirm123 ///124 /// # Arguments125 ///126 /// * collection_id: Globally unique collection identifier.127 ///128 /// * sponsor: New sponsor address.129 SponsorshipConfirmed(CollectionId, AccountId),130131 /// Collection admin was removed132 ///133 /// # Arguments134 ///135 /// * collection_id: Globally unique collection identifier.136 ///137 /// * admin: Admin address.138 CollectionAdminRemoved(CollectionId, CrossAccountId),139140 /// Address was removed from the allow list141 ///142 /// # Arguments143 ///144 /// * collection_id: Globally unique collection identifier.145 ///146 /// * user: Address.147 AllowListAddressRemoved(CollectionId, CrossAccountId),148149 /// Address was added to the allow list150 ///151 /// # Arguments152 ///153 /// * collection_id: Globally unique collection identifier.154 ///155 /// * user: Address.156 AllowListAddressAdded(CollectionId, CrossAccountId),157158 /// Collection limits were set159 ///160 /// # Arguments161 ///162 /// * collection_id: Globally unique collection identifier.163 CollectionLimitSet(CollectionId),164165 /// Collection permissions were set166 ///167 /// # Arguments168 ///169 /// * collection_id: Globally unique collection identifier.170 CollectionPermissionSet(CollectionId),171 }172}173174type SelfWeightOf<T> = <T as Config>::WeightInfo;175176// # Used definitions177//178// ## User control levels179//180// chain-controlled - key is uncontrolled by user181// i.e autoincrementing index182// can use non-cryptographic hash183// real - key is controlled by user184// but it is hard to generate enough colliding values, i.e owner of signed txs185// can use non-cryptographic hash186// controlled - key is completly controlled by users187// i.e maps with mutable keys188// should use cryptographic hash189//190// ## User control level downgrade reasons191//192// ?1 - chain-controlled -> controlled193// collections/tokens can be destroyed, resulting in massive holes194// ?2 - chain-controlled -> controlled195// same as ?1, but can be only added, resulting in easier exploitation196// ?3 - real -> controlled197// no confirmation required, so addresses can be easily generated198decl_storage! {199 trait Store for Module<T: Config> as Unique {200201 //#region Private members202 /// Used for migrations203 ChainVersion: u64;204 //#endregion205206 //#region Tokens transfer sponosoring rate limit baskets207 /// (Collection id (controlled?2), who created (real))208 /// TODO: Off chain worker should remove from this map when collection gets removed209 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;210 /// Collection id (controlled?2), token id (controlled?2)211 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;212 /// Collection id (controlled?2), owning user (real)213 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;214 /// Collection id (controlled?2), token id (controlled?2)215 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>;216 //#endregion217218 /// Variable metadata sponsoring219 /// Collection id (controlled?2), token id (controlled?2)220 #[deprecated]221 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;222 /// Last sponsoring of token property setting // todo:doc rephrase this and the following223 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;224225 /// Last sponsoring of NFT approval in a collection226 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;227 /// Last sponsoring of fungible tokens approval in a collection228 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;229 /// Last sponsoring of RFT approval in a collection230 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>;231 }232}233234decl_module! {235 pub struct Module<T: Config> for enum Call236 where237 origin: T::Origin238 {239 type Error = Error<T>;240241 fn deposit_event() = default;242243 fn on_initialize(_now: T::BlockNumber) -> Weight {244 0245 }246247 fn on_runtime_upgrade() -> Weight {248 let limit = None;249250 <VariableMetaDataBasket<T>>::remove_all(limit);251252 0253 }254255 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.256 ///257 /// # Permissions258 ///259 /// * Anyone.260 ///261 /// # Arguments262 ///263 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.264 ///265 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.266 ///267 /// * token_prefix: UTF-8 string with token prefix.268 ///269 /// * mode: [CollectionMode] collection type and type dependent data.270 // returns collection ID271 #[weight = <SelfWeightOf<T>>::create_collection()]272 #[transactional]273 #[deprecated]274 pub fn create_collection(origin,275 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,276 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,277 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,278 mode: CollectionMode) -> DispatchResult {279 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {280 name: collection_name,281 description: collection_description,282 token_prefix,283 mode,284 ..Default::default()285 };286 Self::create_collection_ex(origin, data)287 }288289 /// Create a collection with explicit parameters.290 /// Prefer it to the deprecated [`created_collection`] method.291 ///292 /// # Permissions293 ///294 /// * Anyone.295 ///296 /// # Arguments297 ///298 /// * data: explicit create-collection data.299 #[weight = <SelfWeightOf<T>>::create_collection()]300 #[transactional]301 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {302 let sender = ensure_signed(origin)?;303304 // =========305306 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;307308 Ok(())309 }310311 /// Destroy the collection if no tokens exist within.312 ///313 /// # Permissions314 ///315 /// * Collection Owner316 ///317 /// # Arguments318 ///319 /// * collection_id: collection to destroy.320 #[weight = <SelfWeightOf<T>>::destroy_collection()]321 #[transactional]322 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {323 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);324 let collection = <CollectionHandle<T>>::try_get(collection_id)?;325 collection.check_is_internal()?;326327 // =========328329 T::CollectionDispatch::destroy(sender, collection)?;330331 <NftTransferBasket<T>>::remove_prefix(collection_id, None);332 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);333 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);334335 <NftApproveBasket<T>>::remove_prefix(collection_id, None);336 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);337 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);338339 Ok(())340 }341342 /// Add an address to allow list.343 ///344 /// # Permissions345 ///346 /// * Collection Owner347 /// * Collection Admin348 ///349 /// # Arguments350 ///351 /// * collection_id.352 ///353 /// * address.354 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]355 #[transactional]356 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{357358 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);359 let collection = <CollectionHandle<T>>::try_get(collection_id)?;360 collection.check_is_internal()?;361362 <PalletCommon<T>>::toggle_allowlist(363 &collection,364 &sender,365 &address,366 true,367 )?;368369 Self::deposit_event(Event::<T>::AllowListAddressAdded(370 collection_id,371 address372 ));373374 Ok(())375 }376377 /// Remove an address from allow list.378 ///379 /// # Permissions380 ///381 /// * Collection Owner382 /// * Collection Admin383 ///384 /// # Arguments385 ///386 /// * collection_id.387 ///388 /// * address.389 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]390 #[transactional]391 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{392393 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);394 let collection = <CollectionHandle<T>>::try_get(collection_id)?;395 collection.check_is_internal()?;396397 <PalletCommon<T>>::toggle_allowlist(398 &collection,399 &sender,400 &address,401 false,402 )?;403404 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(405 collection_id,406 address407 ));408409 Ok(())410 }411412 /// Change the owner of the collection.413 ///414 /// # Permissions415 ///416 /// * Collection Owner417 ///418 /// # Arguments419 ///420 /// * collection_id.421 ///422 /// * new_owner.423 #[weight = <SelfWeightOf<T>>::change_collection_owner()]424 #[transactional]425 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {426427 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);428429 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;430 target_collection.check_is_internal()?;431 target_collection.check_is_owner(&sender)?;432433 target_collection.owner = new_owner.clone();434 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(435 collection_id,436 new_owner437 ));438439 target_collection.save()440 }441442 /// Adds an admin of the collection.443 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.444 ///445 /// # Permissions446 ///447 /// * Collection Owner448 /// * Collection Admin449 ///450 /// # Arguments451 ///452 /// * collection_id: ID of the Collection to add admin for.453 ///454 /// * new_admin: Address of new admin to add.455 #[weight = <SelfWeightOf<T>>::add_collection_admin()]456 #[transactional]457 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {458 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459 let collection = <CollectionHandle<T>>::try_get(collection_id)?;460 collection.check_is_internal()?;461462 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(463 collection_id,464 new_admin.clone()465 ));466467 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)468 }469470 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.471 ///472 /// # Permissions473 ///474 /// * Collection Owner475 /// * Collection Admin476 ///477 /// # Arguments478 ///479 /// * collection_id: ID of the Collection to remove admin for.480 ///481 /// * account_id: Address of admin to remove.482 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]483 #[transactional]484 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {485 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);486 let collection = <CollectionHandle<T>>::try_get(collection_id)?;487 collection.check_is_internal()?;488489 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(490 collection_id,491 account_id.clone()492 ));493494 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)495 }496497 /// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.498 ///499 /// # Permissions500 ///501 /// * Collection Owner502 /// * Collection Admin503 ///504 /// # Arguments505 ///506 /// * collection_id.507 ///508 /// * new_sponsor.509 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]510 #[transactional]511 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {512 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);513514 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;515 target_collection.check_is_owner_or_admin(&sender)?;516 target_collection.check_is_internal()?;517518 target_collection.set_sponsor(new_sponsor.clone())?;519520 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(521 collection_id,522 new_sponsor523 ));524525 target_collection.save()526 }527528 /// Confirm own sponsorship of a collection.529 ///530 /// # Permissions531 ///532 /// * The sponsor to-be533 ///534 /// # Arguments535 ///536 /// * collection_id.537 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]538 #[transactional]539 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {540 let sender = ensure_signed(origin)?;541542 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;543 target_collection.check_is_internal()?;544 ensure!(545 target_collection.confirm_sponsorship(&sender)?,546 Error::<T>::ConfirmUnsetSponsorFail547 );548549 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(550 collection_id,551 sender552 ));553554 target_collection.save()555 }556557 /// Switch back to pay-per-own-transaction model.558 ///559 /// # Permissions560 ///561 /// * Collection Owner562 ///563 /// # Arguments564 ///565 /// * collection_id.566 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]567 #[transactional]568 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {569 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);570571 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;572 target_collection.check_is_internal()?;573 target_collection.check_is_owner(&sender)?;574575 target_collection.sponsorship = SponsorshipState::Disabled;576577 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(578 collection_id579 ));580 target_collection.save()581 }582583 /// Create a concrete instance of NFT Collection created with CreateCollection method.584 ///585 /// # Permissions586 ///587 /// * Collection Owner588 /// * Collection Admin589 /// * Anyone if590 /// * Allow List is enabled, and591 /// * Address is added to allow list, and592 /// * MintPermission is enabled (see SetMintPermission method)593 ///594 /// # Arguments595 ///596 /// * collection_id: ID of the collection.597 ///598 /// * owner: Address, initial owner of the NFT.599 ///600 /// * data: Token data to store on chain.601 #[weight = T::CommonWeightInfo::create_item()]602 #[transactional]603 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {604 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);605 let budget = budget::Value::new(NESTING_BUDGET);606607 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))608 }609610 /// Create multiple items in a collection created with CreateCollection method.611 ///612 /// # Permissions613 ///614 /// * Collection Owner615 /// * Collection Admin616 /// * Anyone if617 /// * Allow List is enabled, and618 /// * Address is added to allow list, and619 /// * MintPermission is enabled (see SetMintPermission method)620 ///621 /// # Arguments622 ///623 /// * collection_id: ID of the collection.624 ///625 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].626 ///627 /// * owner: Address, initial owner of the NFT.628 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]629 #[transactional]630 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {631 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);632 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633 let budget = budget::Value::new(NESTING_BUDGET);634635 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))636 }637638 /// Add or change collection properties.639 ///640 /// # Permissions641 ///642 /// * Collection Owner643 /// * Collection Admin644 ///645 /// # Arguments646 ///647 /// * collection_id.648 ///649 /// * properties: a vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.650 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]651 #[transactional]652 pub fn set_collection_properties(653 origin,654 collection_id: CollectionId,655 properties: Vec<Property>656 ) -> DispatchResultWithPostInfo {657 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);658659 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);660661 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))662 }663664 /// Delete specified collection properties.665 ///666 /// # Permissions667 ///668 /// * Collection Owner669 /// * Collection Admin670 ///671 /// # Arguments672 ///673 /// * collection_id.674 ///675 /// * property_keys: a vector of keys of the properties to be deleted.676 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]677 #[transactional]678 pub fn delete_collection_properties(679 origin,680 collection_id: CollectionId,681 property_keys: Vec<PropertyKey>,682 ) -> DispatchResultWithPostInfo {683 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);684685 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);686687 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))688 }689690 /// Add or change token properties according to collection's permissions.691 ///692 /// # Permissions693 ///694 /// * Depends on collection's token property permissions and specified property mutability:695 /// * Collection Owner696 /// * Collection Admin697 /// * Token Owner698 ///699 /// # Arguments700 ///701 /// * collection_id.702 ///703 /// * token_id.704 ///705 /// * properties: a vector of key-value pairs stored as the token's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.706 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]707 #[transactional]708 pub fn set_token_properties(709 origin,710 collection_id: CollectionId,711 token_id: TokenId,712 properties: Vec<Property>713 ) -> DispatchResultWithPostInfo {714 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);715716 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);717 let budget = budget::Value::new(NESTING_BUDGET);718719 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))720 }721722 /// Delete specified token properties.723 ///724 /// # Permissions725 ///726 /// * Depends on collection's token property permissions and specified property mutability:727 /// * Collection Owner728 /// * Collection Admin729 /// * Token Owner730 ///731 /// # Arguments732 ///733 /// * collection_id.734 ///735 /// * token_id.736 ///737 /// * property_keys: a vector of keys of the properties to be deleted.738 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]739 #[transactional]740 pub fn delete_token_properties(741 origin,742 collection_id: CollectionId,743 token_id: TokenId,744 property_keys: Vec<PropertyKey>745 ) -> DispatchResultWithPostInfo {746 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);747748 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);749 let budget = budget::Value::new(NESTING_BUDGET);750751 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))752 }753754 /// Add or change token property permissions of a collection.755 ///756 /// # Permissions757 ///758 /// * Collection Owner759 /// * Collection Admin760 ///761 /// # Arguments762 ///763 /// * collection_id.764 ///765 /// * property_permissions: a vector of permissions for property keys. Keys support Latin letters, '-', '_', and '.' as symbols.766 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]767 #[transactional]768 pub fn set_token_property_permissions(769 origin,770 collection_id: CollectionId,771 property_permissions: Vec<PropertyKeyPermission>,772 ) -> DispatchResultWithPostInfo {773 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);774775 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);776777 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))778 }779780 /// Create multiple items inside a collection with explicitly specified initial parameters.781 ///782 /// # Permissions783 ///784 /// * Collection Owner785 /// * Collection Admin786 /// * Anyone if787 /// * Allow List is enabled, and788 /// * Address is added to allow list, and789 /// * MintPermission is enabled (see SetMintPermission method)790 ///791 /// # Arguments792 ///793 /// * collection_id: ID of the collection.794 ///795 /// * data: explicit item creation data.796 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]797 #[transactional]798 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {799 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);800 let budget = budget::Value::new(NESTING_BUDGET);801802 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))803 }804805 /// Set transfers_enabled value for particular collection.806 ///807 /// # Permissions808 ///809 /// * Collection Owner810 ///811 /// # Arguments812 ///813 /// * collection_id: ID of the collection.814 ///815 /// * value: New flag value.816 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]817 #[transactional]818 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {819 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;821 target_collection.check_is_internal()?;822 target_collection.check_is_owner(&sender)?;823824 // =========825826 target_collection.limits.transfers_enabled = Some(value);827 target_collection.save()828 }829830 /// Destroy a concrete instance of NFT.831 ///832 /// # Permissions833 ///834 /// * Collection Owner835 /// * Collection Admin836 /// * Current NFT Owner837 ///838 /// # Arguments839 ///840 /// * collection_id: ID of the collection.841 ///842 /// * item_id: ID of NFT to burn.843 #[weight = T::CommonWeightInfo::burn_item()]844 #[transactional]845 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {846 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);847848 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;849 if value == 1 {850 <NftTransferBasket<T>>::remove(collection_id, item_id);851 <NftApproveBasket<T>>::remove(collection_id, item_id);852 }853 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?854 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());855 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));856 Ok(post_info)857 }858859 /// Destroy a concrete instance of NFT on behalf of the owner.860 /// See also: [`approve`]861 ///862 /// # Permissions863 ///864 /// * Collection Owner.865 /// * Collection Admin.866 /// * Current NFT Owner.867 ///868 /// # Arguments869 ///870 /// * collection_id: ID of the collection.871 ///872 /// * item_id: ID of NFT to burn.873 ///874 /// * from: owner of item875 #[weight = T::CommonWeightInfo::burn_from()]876 #[transactional]877 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {878 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879 let budget = budget::Value::new(NESTING_BUDGET);880881 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))882 }883884 /// Change ownership of the token.885 ///886 /// # Permissions887 ///888 /// * Collection Owner889 /// * Collection Admin890 /// * Current NFT owner891 ///892 /// # Arguments893 ///894 /// * recipient: Address of token recipient.895 ///896 /// * collection_id.897 ///898 /// * item_id: ID of the item899 /// * Non-Fungible Mode: Required.900 /// * Fungible Mode: Ignored.901 /// * Re-Fungible Mode: Required.902 ///903 /// * value: Amount to transfer.904 /// * Non-Fungible Mode: Ignored905 /// * Fungible Mode: Must specify transferred amount906 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)907 #[weight = T::CommonWeightInfo::transfer()]908 #[transactional]909 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {910 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);911 let budget = budget::Value::new(NESTING_BUDGET);912913 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))914 }915916 /// Set, change, or remove approved address to transfer the ownership of the NFT.917 ///918 /// # Permissions919 ///920 /// * Collection Owner921 /// * Collection Admin922 /// * Current NFT owner923 ///924 /// # Arguments925 ///926 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).927 ///928 /// * collection_id.929 ///930 /// * item_id: ID of the item.931 #[weight = T::CommonWeightInfo::approve()]932 #[transactional]933 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {934 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);935936 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))937 }938939 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.940 ///941 /// # Permissions942 ///943 /// * Collection Owner944 /// * Collection Admin945 /// * Current NFT owner946 /// * Address approved by current NFT owner947 ///948 /// # Arguments949 ///950 /// * from: Address that owns token.951 ///952 /// * recipient: Address of token recipient.953 ///954 /// * collection_id.955 ///956 /// * item_id: ID of the item.957 ///958 /// * value: Amount to transfer.959 #[weight = T::CommonWeightInfo::transfer_from()]960 #[transactional]961 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {962 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);963 let budget = budget::Value::new(NESTING_BUDGET);964965 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))966 }967968 /// Set specific limits of a collection. Empty, or None fields mean chain default.969 ///.970 /// # Permissions971 ///972 /// * Collection Owner973 /// * Collection Admin974 ///975 /// # Arguments976 ///977 /// * collection_id.978 ///979 /// * new_limit: The new limits of the collection. They will overwrite the current ones.980 #[weight = <SelfWeightOf<T>>::set_collection_limits()]981 #[transactional]982 pub fn set_collection_limits(983 origin,984 collection_id: CollectionId,985 new_limit: CollectionLimits,986 ) -> DispatchResult {987 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);988 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;989 target_collection.check_is_internal()?;990 target_collection.check_is_owner_or_admin(&sender)?;991 let old_limit = &target_collection.limits;992993 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;994995 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(996 collection_id997 ));998999 target_collection.save()1000 }10011002 /// Set specific permissions of a collection. Empty, or None fields mean chain default.1003 ///1004 /// # Permissions1005 ///1006 /// * Collection Owner1007 /// * Collection Admin1008 ///1009 /// # Arguments1010 ///1011 /// * collection_id.1012 ///1013 /// * new_permission: The new permissions of the collection. They will overwrite the current ones.1014 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1015 #[transactional]1016 pub fn set_collection_permissions(1017 origin,1018 collection_id: CollectionId,1019 new_permission: CollectionPermissions,1020 ) -> DispatchResult {1021 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1022 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1023 target_collection.check_is_internal()?;1024 target_collection.check_is_owner_or_admin(&sender)?;1025 let old_limit = &target_collection.permissions;10261027 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10281029 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1030 collection_id1031 ));10321033 target_collection.save()1034 }10351036 /// Re-partition a refungible token, while owning all of its parts.1037 ///1038 /// # Permissions1039 ///1040 /// * Token Owner (must own every part)1041 ///1042 /// # Arguments1043 ///1044 /// * collection_id.1045 ///1046 /// * token: the ID of the RFT.1047 ///1048 /// * amount: The new number of parts into which the token shall be partitioned.1049 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1050 #[transactional]1051 pub fn repartition(1052 origin,1053 collection_id: CollectionId,1054 token: TokenId,1055 amount: u128,1056 ) -> DispatchResultWithPostInfo {1057 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1058 dispatch_tx::<T, _>(collection_id, |d| {1059 if let Some(refungible_extensions) = d.refungible_extensions() {1060 refungible_extensions.repartition(&sender, token, amount)1061 } else {1062 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1063 }1064 })1065 }1066 }1067}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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28 decl_module, decl_storage, decl_error, decl_event,29 dispatch::DispatchResult,30 ensure, fail,31 weights::{Weight},32 transactional,33 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},34 BoundedVec,35};36use scale_info::TypeInfo;37use frame_system::{self as system, ensure_signed};38use sp_runtime::{sp_std::prelude::Vec};39use up_data_structs::{40 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,41 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,42 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,43 PropertyKeyPermission,44};45use pallet_evm::account::CrossAccountId;46use pallet_common::{47 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,48 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,49};50pub mod eth;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;54pub mod weights;55use weights::WeightInfo;5657const NESTING_BUDGET: u32 = 5;5859decl_error! {60 /// Error for non-fungible-token module.61 pub enum Error for Module<T: Config> {62 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.63 CollectionDecimalPointLimitExceeded,64 /// This address is not set as sponsor, use setCollectionSponsor first.65 ConfirmUnsetSponsorFail,66 /// Length of items properties must be greater than 0.67 EmptyArgument,68 /// Repertition is only supported by refungible collection69 RepartitionCalledOnNonRefungibleCollection,70 }71}7273pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {74 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7576 /// Weight information for extrinsics in this pallet.77 type WeightInfo: WeightInfo;78 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;79 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;80}8182decl_event! {83 pub enum Event<T>84 where85 <T as frame_system::Config>::AccountId,86 <T as pallet_evm::account::Config>::CrossAccountId,87 {88 /// Collection sponsor was removed89 ///90 /// # Arguments91 ///92 /// * collection_id: Globally unique collection identifier.93 CollectionSponsorRemoved(CollectionId),9495 /// Collection admin was added96 ///97 /// # Arguments98 ///99 /// * collection_id: Globally unique collection identifier.100 ///101 /// * admin: Admin address.102 CollectionAdminAdded(CollectionId, CrossAccountId),103104 /// Collection owned was changed105 ///106 /// # Arguments107 ///108 /// * collection_id: Globally unique collection identifier.109 ///110 /// * owner: New owner address.111 CollectionOwnedChanged(CollectionId, AccountId),112113 /// Collection sponsor was set114 ///115 /// # Arguments116 ///117 /// * collection_id: Globally unique collection identifier.118 ///119 /// * owner: New sponsor address.120 CollectionSponsorSet(CollectionId, AccountId),121122 /// New sponsor was confirm123 ///124 /// # Arguments125 ///126 /// * collection_id: Globally unique collection identifier.127 ///128 /// * sponsor: New sponsor address.129 SponsorshipConfirmed(CollectionId, AccountId),130131 /// Collection admin was removed132 ///133 /// # Arguments134 ///135 /// * collection_id: Globally unique collection identifier.136 ///137 /// * admin: Admin address.138 CollectionAdminRemoved(CollectionId, CrossAccountId),139140 /// Address was removed from the allow list141 ///142 /// # Arguments143 ///144 /// * collection_id: Globally unique collection identifier.145 ///146 /// * user: Address.147 AllowListAddressRemoved(CollectionId, CrossAccountId),148149 /// Address was added to the allow list150 ///151 /// # Arguments152 ///153 /// * collection_id: Globally unique collection identifier.154 ///155 /// * user: Address.156 AllowListAddressAdded(CollectionId, CrossAccountId),157158 /// Collection limits were set159 ///160 /// # Arguments161 ///162 /// * collection_id: Globally unique collection identifier.163 CollectionLimitSet(CollectionId),164165 /// Collection permissions were set166 ///167 /// # Arguments168 ///169 /// * collection_id: Globally unique collection identifier.170 CollectionPermissionSet(CollectionId),171 }172}173174type SelfWeightOf<T> = <T as Config>::WeightInfo;175176// # Used definitions177//178// ## User control levels179//180// chain-controlled - key is uncontrolled by user181// i.e autoincrementing index182// can use non-cryptographic hash183// real - key is controlled by user184// but it is hard to generate enough colliding values, i.e owner of signed txs185// can use non-cryptographic hash186// controlled - key is completly controlled by users187// i.e maps with mutable keys188// should use cryptographic hash189//190// ## User control level downgrade reasons191//192// ?1 - chain-controlled -> controlled193// collections/tokens can be destroyed, resulting in massive holes194// ?2 - chain-controlled -> controlled195// same as ?1, but can be only added, resulting in easier exploitation196// ?3 - real -> controlled197// no confirmation required, so addresses can be easily generated198decl_storage! {199 trait Store for Module<T: Config> as Unique {200201 //#region Private members202 /// Used for migrations203 ChainVersion: u64;204 //#endregion205206 //#region Tokens transfer sponosoring rate limit baskets207 /// (Collection id (controlled?2), who created (real))208 /// TODO: Off chain worker should remove from this map when collection gets removed209 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;210 /// Collection id (controlled?2), token id (controlled?2)211 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;212 /// Collection id (controlled?2), owning user (real)213 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;214 /// Collection id (controlled?2), token id (controlled?2)215 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>;216 //#endregion217218 /// Variable metadata sponsoring219 /// Collection id (controlled?2), token id (controlled?2)220 #[deprecated]221 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;222 /// Last sponsoring of token property setting // todo:doc rephrase this and the following223 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;224225 /// Last sponsoring of NFT approval in a collection226 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;227 /// Last sponsoring of fungible tokens approval in a collection228 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;229 /// Last sponsoring of RFT approval in a collection230 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>;231 }232}233234decl_module! {235 pub struct Module<T: Config> for enum Call236 where237 origin: T::Origin238 {239 type Error = Error<T>;240241 fn deposit_event() = default;242243 fn on_initialize(_now: T::BlockNumber) -> Weight {244 0245 }246247 fn on_runtime_upgrade() -> Weight {248 let limit = None;249250 <VariableMetaDataBasket<T>>::remove_all(limit);251252 0253 }254255 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.256 ///257 /// # Permissions258 ///259 /// * Anyone.260 ///261 /// # Arguments262 ///263 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.264 /// * collection_description - UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.265 /// * token_prefix - UTF-8 string with token prefix.266 /// * mode - [CollectionMode] collection type and type dependent data.267 // returns collection ID268 #[weight = <SelfWeightOf<T>>::create_collection()]269 #[transactional]270 #[deprecated]271 pub fn create_collection(origin,272 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,273 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,274 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,275 mode: CollectionMode) -> DispatchResult {276 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {277 name: collection_name,278 description: collection_description,279 token_prefix,280 mode,281 ..Default::default()282 };283 Self::create_collection_ex(origin, data)284 }285286 /// Create a collection with explicit parameters.287 /// Prefer it to the deprecated [`created_collection`] method.288 ///289 /// # Permissions290 ///291 /// * Anyone.292 ///293 /// # Arguments294 ///295 /// * data: explicit create-collection data.296 #[weight = <SelfWeightOf<T>>::create_collection()]297 #[transactional]298 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {299 let sender = ensure_signed(origin)?;300301 // =========302303 T::CollectionDispatch::create(T::CrossAccountId::from_sub(sender), data)?;304305 Ok(())306 }307308 /// Destroy the collection if no tokens exist within.309 ///310 /// # Permissions311 ///312 /// * Collection Owner313 ///314 /// # Arguments315 ///316 /// * collection_id - collection to destroy.317 #[weight = <SelfWeightOf<T>>::destroy_collection()]318 #[transactional]319 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {320 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);321 let collection = <CollectionHandle<T>>::try_get(collection_id)?;322 collection.check_is_internal()?;323324 // =========325326 T::CollectionDispatch::destroy(sender, collection)?;327328 <NftTransferBasket<T>>::remove_prefix(collection_id, None);329 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);330 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);331332 <NftApproveBasket<T>>::remove_prefix(collection_id, None);333 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);334 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);335336 Ok(())337 }338339 /// Add an address to allow list.340 ///341 /// # Permissions342 ///343 /// * Collection Owner344 /// * Collection Admin345 ///346 /// # Arguments347 ///348 /// * collection_id.349 /// * address.350 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]351 #[transactional]352 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{353354 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);355 let collection = <CollectionHandle<T>>::try_get(collection_id)?;356 collection.check_is_internal()?;357358 <PalletCommon<T>>::toggle_allowlist(359 &collection,360 &sender,361 &address,362 true,363 )?;364365 Self::deposit_event(Event::<T>::AllowListAddressAdded(366 collection_id,367 address368 ));369370 Ok(())371 }372373 /// Remove an address from allow list.374 ///375 /// # Permissions376 ///377 /// * Collection Owner378 /// * Collection Admin379 ///380 /// # Arguments381 ///382 /// * collection_id.383 /// * address.384 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]385 #[transactional]386 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{387388 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389 let collection = <CollectionHandle<T>>::try_get(collection_id)?;390 collection.check_is_internal()?;391392 <PalletCommon<T>>::toggle_allowlist(393 &collection,394 &sender,395 &address,396 false,397 )?;398399 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(400 collection_id,401 address402 ));403404 Ok(())405 }406407 /// Change the owner of the collection.408 ///409 /// # Permissions410 ///411 /// * Collection Owner412 ///413 /// # Arguments414 ///415 /// * collection_id.416 /// * new_owner.417 #[weight = <SelfWeightOf<T>>::change_collection_owner()]418 #[transactional]419 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {420421 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);422423 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;424 target_collection.check_is_internal()?;425 target_collection.check_is_owner(&sender)?;426427 target_collection.owner = new_owner.clone();428 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(429 collection_id,430 new_owner431 ));432433 target_collection.save()434 }435436 /// Adds an admin of the collection.437 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.438 ///439 /// # Permissions440 ///441 /// * Collection Owner442 /// * Collection Admin443 ///444 /// # Arguments445 ///446 /// * collection_id - ID of the Collection to add admin for.447 /// * new_admin - Address of new admin to add.448 #[weight = <SelfWeightOf<T>>::add_collection_admin()]449 #[transactional]450 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin: T::CrossAccountId) -> DispatchResult {451 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);452 let collection = <CollectionHandle<T>>::try_get(collection_id)?;453 collection.check_is_internal()?;454455 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(456 collection_id,457 new_admin.clone()458 ));459460 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin, true)461 }462463 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.464 ///465 /// # Permissions466 ///467 /// * Collection Owner468 /// * Collection Admin469 ///470 /// # Arguments471 ///472 /// * collection_id - ID of the Collection to remove admin for.473 /// * account_id - Address of admin to remove.474 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]475 #[transactional]476 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {477 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);478 let collection = <CollectionHandle<T>>::try_get(collection_id)?;479 collection.check_is_internal()?;480481 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(482 collection_id,483 account_id.clone()484 ));485486 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)487 }488489 /// Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.490 ///491 /// # Permissions492 ///493 /// * Collection Owner494 /// * Collection Admin495 ///496 /// # Arguments497 ///498 /// * collection_id.499 /// * new_sponsor.500 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]501 #[transactional]502 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {503 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);504505 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;506 target_collection.check_is_owner_or_admin(&sender)?;507 target_collection.check_is_internal()?;508509 target_collection.set_sponsor(new_sponsor.clone())?;510511 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(512 collection_id,513 new_sponsor514 ));515516 target_collection.save()517 }518519 /// Confirm own sponsorship of a collection.520 ///521 /// # Permissions522 ///523 /// * Sponsor-to-be524 ///525 /// # Arguments526 ///527 /// * collection_id.528 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]529 #[transactional]530 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {531 let sender = ensure_signed(origin)?;532533 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;534 target_collection.check_is_internal()?;535 ensure!(536 target_collection.confirm_sponsorship(&sender)?,537 Error::<T>::ConfirmUnsetSponsorFail538 );539540 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(541 collection_id,542 sender543 ));544545 target_collection.save()546 }547548 /// Switch back to pay-per-own-transaction model.549 ///550 /// # Permissions551 ///552 /// * Collection Owner553 ///554 /// # Arguments555 ///556 /// * collection_id.557 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]558 #[transactional]559 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {560 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);561562 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;563 target_collection.check_is_internal()?;564 target_collection.check_is_owner(&sender)?;565566 target_collection.sponsorship = SponsorshipState::Disabled;567568 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(569 collection_id570 ));571 target_collection.save()572 }573574 /// Create a concrete instance of NFT Collection created with CreateCollection method.575 ///576 /// # Permissions577 ///578 /// * Collection Owner579 /// * Collection Admin580 /// * Anyone if581 /// * Allow List is enabled, and582 /// * Address is added to allow list, and583 /// * MintPermission is enabled (see SetMintPermission method)584 ///585 /// # Arguments586 ///587 /// * collection_id - ID of the collection.588 /// * owner - Address, initial owner of the NFT.589 /// * data - Token data to store on chain.590 #[weight = T::CommonWeightInfo::create_item()]591 #[transactional]592 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {593 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);594 let budget = budget::Value::new(NESTING_BUDGET);595596 dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))597 }598599 /// Create multiple items in a collection created with CreateCollection method.600 ///601 /// # Permissions602 ///603 /// * Collection Owner604 /// * Collection Admin605 /// * Anyone if606 /// * Allow List is enabled, and607 /// * Address is added to allow list, and608 /// * MintPermission is enabled (see SetMintPermission method)609 ///610 /// # Arguments611 ///612 /// * collection_id - ID of the collection.613 /// * owner - Address, initial owner of the NFT.614 /// * items_data - Array items properties. Each property is an array of bytes itself, see [`create_item`].615 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]616 #[transactional]617 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {618 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);619 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);620 let budget = budget::Value::new(NESTING_BUDGET);621622 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))623 }624625 /// Add or change collection properties.626 ///627 /// # Permissions628 ///629 /// * Collection Owner630 /// * Collection Admin631 ///632 /// # Arguments633 ///634 /// * collection_id.635 /// * properties - Vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, '-', '_', and '.' as symbols.636 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]637 #[transactional]638 pub fn set_collection_properties(639 origin,640 collection_id: CollectionId,641 properties: Vec<Property>642 ) -> DispatchResultWithPostInfo {643 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);644645 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);646647 dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))648 }649650 /// Delete specified collection properties.651 ///652 /// # Permissions653 ///654 /// * Collection Owner655 /// * Collection Admin656 ///657 /// # Arguments658 ///659 /// * collection_id.660 /// * property_keys - Vector of keys of the properties to be deleted.661 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]662 #[transactional]663 pub fn delete_collection_properties(664 origin,665 collection_id: CollectionId,666 property_keys: Vec<PropertyKey>,667 ) -> DispatchResultWithPostInfo {668 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);669670 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);671672 dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))673 }674675 /// Add or change token properties according to collection's permissions.676 ///677 /// # Permissions678 ///679 /// * Depends on collection's token property permissions and specified property mutability:680 /// * Collection Owner681 /// * Collection Admin682 /// * Token Owner683 ///684 /// # Arguments685 ///686 /// * collection_id.687 /// * token_id.688 /// * properties - Vector of key-value pairs stored as the token's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.689 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]690 #[transactional]691 pub fn set_token_properties(692 origin,693 collection_id: CollectionId,694 token_id: TokenId,695 properties: Vec<Property>696 ) -> DispatchResultWithPostInfo {697 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);698699 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);700 let budget = budget::Value::new(NESTING_BUDGET);701702 dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))703 }704705 /// Delete specified token properties.706 ///707 /// # Permissions708 ///709 /// * Depends on collection's token property permissions and specified property mutability:710 /// * Collection Owner711 /// * Collection Admin712 /// * Token Owner713 ///714 /// # Arguments715 ///716 /// * collection_id.717 /// * token_id.718 /// * property_keys - Vector of keys of the properties to be deleted.719 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]720 #[transactional]721 pub fn delete_token_properties(722 origin,723 collection_id: CollectionId,724 token_id: TokenId,725 property_keys: Vec<PropertyKey>726 ) -> DispatchResultWithPostInfo {727 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);728729 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);730 let budget = budget::Value::new(NESTING_BUDGET);731732 dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))733 }734735 /// Add or change token property permissions of a collection.736 ///737 /// # Permissions738 ///739 /// * Collection Owner740 /// * Collection Admin741 ///742 /// # Arguments743 ///744 /// * collection_id.745 /// * property_permissions - Vector of permissions for property keys. Keys support Latin letters, `-`, `_`, and `.` as symbols.746 #[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]747 #[transactional]748 pub fn set_token_property_permissions(749 origin,750 collection_id: CollectionId,751 property_permissions: Vec<PropertyKeyPermission>,752 ) -> DispatchResultWithPostInfo {753 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);754755 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);756757 dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))758 }759760 /// Create multiple items inside a collection with explicitly specified initial parameters.761 ///762 /// # Permissions763 ///764 /// * Collection Owner765 /// * Collection Admin766 /// * Anyone if767 /// * Allow List is enabled, and768 /// * Address is added to allow list, and769 /// * MintPermission is enabled (see SetMintPermission method)770 ///771 /// # Arguments772 ///773 /// * collection_id - ID of the collection.774 /// * data - Explicit item creation data.775 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]776 #[transactional]777 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {778 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);779 let budget = budget::Value::new(NESTING_BUDGET);780781 dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))782 }783784 /// Set transfers_enabled value for particular collection.785 ///786 /// # Permissions787 ///788 /// * Collection Owner789 ///790 /// # Arguments791 ///792 /// * collection_id - ID of the collection.793 /// * value - New flag value.794 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]795 #[transactional]796 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {797 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);798 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;799 target_collection.check_is_internal()?;800 target_collection.check_is_owner(&sender)?;801802 // =========803804 target_collection.limits.transfers_enabled = Some(value);805 target_collection.save()806 }807808 /// Destroy a concrete instance of NFT.809 ///810 /// # Permissions811 ///812 /// * Collection Owner813 /// * Collection Admin814 /// * Current NFT Owner815 ///816 /// # Arguments817 ///818 /// * collection_id - ID of the collection.819 /// * item_id - ID of NFT to burn.820 #[weight = T::CommonWeightInfo::burn_item()]821 #[transactional]822 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {823 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824825 let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;826 if value == 1 {827 <NftTransferBasket<T>>::remove(collection_id, item_id);828 <NftApproveBasket<T>>::remove(collection_id, item_id);829 }830 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?831 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());832 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));833 Ok(post_info)834 }835836 /// Destroy a concrete instance of NFT on behalf of the owner.837 /// See also: [`approve`]838 ///839 /// # Permissions840 ///841 /// * Collection Owner.842 /// * Collection Admin.843 /// * Current NFT Owner.844 ///845 /// # Arguments846 ///847 /// * collection_id - ID of the collection.848 /// * item_id - ID of NFT to burn.849 /// * from - The owner of the item from whom it is taken away.850 #[weight = T::CommonWeightInfo::burn_from()]851 #[transactional]852 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {853 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);854 let budget = budget::Value::new(NESTING_BUDGET);855856 dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))857 }858859 /// Change ownership of the token.860 ///861 /// # Permissions862 ///863 /// * Collection Owner864 /// * Collection Admin865 /// * Current NFT owner866 ///867 /// # Arguments868 ///869 /// * recipient - Address of token recipient.870 ///871 /// * collection_id.872 ///873 /// * item_id - ID of the item874 /// * Non-Fungible Mode: Required.875 /// * Fungible Mode: Ignored.876 /// * Re-Fungible Mode: Required.877 ///878 /// * value - Amount to transfer.879 /// * Non-Fungible Mode: Ignored880 /// * Fungible Mode: Must specify transferred amount881 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)882 #[weight = T::CommonWeightInfo::transfer()]883 #[transactional]884 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {885 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886 let budget = budget::Value::new(NESTING_BUDGET);887888 dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))889 }890891 /// Set, change, or remove approved address to transfer the ownership of the NFT.892 ///893 /// # Permissions894 ///895 /// * Collection Owner896 /// * Collection Admin897 /// * Current NFT owner898 ///899 /// # Arguments900 ///901 /// * approved - Address that is approved to transfer this NFT or zero (if needed to remove approval).902 /// * collection_id.903 /// * item_id - ID of the item.904 #[weight = T::CommonWeightInfo::approve()]905 #[transactional]906 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {907 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);908909 dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))910 }911912 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.913 ///914 /// # Permissions915 ///916 /// * Collection Owner917 /// * Collection Admin918 /// * Current NFT owner919 /// * Address approved by current NFT owner920 ///921 /// # Arguments922 ///923 /// * from - Address that currently owns the token.924 /// * recipient - Address of the new token-owner-to-be.925 /// * collection_id.926 /// * item_id - ID of the item to be transferred.927 /// * value - Amount to transfer.928 #[weight = T::CommonWeightInfo::transfer_from()]929 #[transactional]930 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {931 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);932 let budget = budget::Value::new(NESTING_BUDGET);933934 dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))935 }936937 /// Set specific limits of a collection. Empty, or None fields mean chain default.938 ///939 /// # Permissions940 ///941 /// * Collection Owner942 /// * Collection Admin943 ///944 /// # Arguments945 ///946 /// * collection_id.947 /// * new_limit - New limits of the collection. They will overwrite the current ones.948 #[weight = <SelfWeightOf<T>>::set_collection_limits()]949 #[transactional]950 pub fn set_collection_limits(951 origin,952 collection_id: CollectionId,953 new_limit: CollectionLimits,954 ) -> DispatchResult {955 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);956 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;957 target_collection.check_is_internal()?;958 target_collection.check_is_owner_or_admin(&sender)?;959 let old_limit = &target_collection.limits;960961 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;962963 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(964 collection_id965 ));966967 target_collection.save()968 }969970 /// Set specific permissions of a collection. Empty, or None fields mean chain default.971 ///972 /// # Permissions973 ///974 /// * Collection Owner975 /// * Collection Admin976 ///977 /// # Arguments978 ///979 /// * collection_id.980 /// * new_permission - New permissions of the collection. They will overwrite the current ones.981 #[weight = <SelfWeightOf<T>>::set_collection_limits()]982 #[transactional]983 pub fn set_collection_permissions(984 origin,985 collection_id: CollectionId,986 new_permission: CollectionPermissions,987 ) -> DispatchResult {988 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;990 target_collection.check_is_internal()?;991 target_collection.check_is_owner_or_admin(&sender)?;992 let old_limit = &target_collection.permissions;993994 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;995996 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(997 collection_id998 ));9991000 target_collection.save()1001 }10021003 /// Re-partition a refungible token, while owning all of its parts.1004 ///1005 /// # Permissions1006 ///1007 /// * Token Owner (must own every part)1008 ///1009 /// # Arguments1010 ///1011 /// * collection_id.1012 /// * token_id - ID of the RFT.1013 /// * amount - New number of parts into which the token shall be partitioned.1014 #[weight = T::RefungibleExtensionsWeightInfo::repartition()]1015 #[transactional]1016 pub fn repartition(1017 origin,1018 collection_id: CollectionId,1019 token_id: TokenId,1020 amount: u128,1021 ) -> DispatchResultWithPostInfo {1022 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1023 dispatch_tx::<T, _>(collection_id, |d| {1024 if let Some(refungible_extensions) = d.refungible_extensions() {1025 refungible_extensions.repartition(&sender, token, amount)1026 } else {1027 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1028 }1029 })1030 }1031 }1032}