difftreelog
doc: unique pallet complete
in: master
2 files changed
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! Implementation of CollectionHelpers contract.
+
use core::marker::PhantomData;
use evm_coder::{execution::*, generate_stubgen, solidity_interface, weight, types::*};
use ethereum as _;
@@ -33,7 +35,8 @@
use sp_std::vec::Vec;
use alloc::format;
-struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);
+/// See [`CollectionHelpersCall`]
+pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);
impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {
fn recorder(&self) -> &SubstrateRecorder<T> {
&self.0
@@ -44,8 +47,14 @@
}
}
+/// @title Contract, which allows users to operate with collections
#[solidity_interface(name = "CollectionHelpers", events(CollectionHelpersEvents))]
impl<T: Config + pallet_nonfungible::Config> EvmCollectionHelpers<T> {
+ /// Create an NFT collection
+ /// @param name Name of the collection
+ /// @param description Informative description of the collection
+ /// @param token_prefix Token prefix to represent the collection tokens in UI and user applications
+ /// @return address Address of the newly created collection
#[weight(<SelfWeightOf<T>>::create_collection())]
fn create_nonfungible_collection(
&mut self,
@@ -100,6 +109,9 @@
Ok(address)
}
+ /// Check if a collection exists
+ /// @param collection_address Address of the collection in question
+ /// @return bool Does the collection exist?
fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {
if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {
let collection_id = id;
@@ -110,6 +122,7 @@
}
}
+/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]
pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);
impl<T: Config + pallet_nonfungible::Config> OnMethodCall<T> for CollectionHelpersOnMethodCall<T> {
fn is_reserved(contract: &sp_core::H160) -> bool {
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 /// * 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_id, amount)1026 } else {1027 fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1028 }1029 })1030 }1031 }1032}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`](eth),31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69 clippy::too_many_arguments,70 clippy::unnecessary_mut_passed,71 clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77 decl_module, decl_storage, decl_error, decl_event,78 dispatch::DispatchResult,79 ensure, fail,80 weights::{Weight},81 transactional,82 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},83 BoundedVec,84};85use scale_info::TypeInfo;86use frame_system::{self as system, ensure_signed};87use sp_runtime::{sp_std::prelude::Vec};88use up_data_structs::{89 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,90 CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,91 SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,92 PropertyKeyPermission,93};94use pallet_evm::account::CrossAccountId;95use pallet_common::{96 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,97 dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,98};99pub mod eth;100101#[cfg(feature = "runtime-benchmarks")]102mod benchmarking;103pub mod weights;104use weights::WeightInfo;105106/// Maximum number of levels of depth in the token nesting tree.107pub const NESTING_BUDGET: u32 = 5;108109decl_error! {110 /// Errors for the common Unique transactions.111 pub enum Error for Module<T: Config> {112 /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].113 CollectionDecimalPointLimitExceeded,114 /// This address is not set as sponsor, use setCollectionSponsor first.115 ConfirmUnsetSponsorFail,116 /// Length of items properties must be greater than 0.117 EmptyArgument,118 /// Repertition is only supported by refungible collection.119 RepartitionCalledOnNonRefungibleCollection,120 }121}122123/// Configuration trait of this pallet.124pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {125 /// Overarching event type.126 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;127128 /// Weight information for extrinsics in this pallet.129 type WeightInfo: WeightInfo;130131 /// Weight information for common pallet operations.132 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;133134 /// Weight info information for extra refungible pallet operations.135 type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;136}137138decl_event! {139 pub enum Event<T>140 where141 <T as frame_system::Config>::AccountId,142 <T as pallet_evm::account::Config>::CrossAccountId,143 {144 /// Collection sponsor was removed145 ///146 /// # Arguments147 /// * collection_id: ID of the affected collection.148 CollectionSponsorRemoved(CollectionId),149150 /// Collection admin was added151 ///152 /// # Arguments153 /// * collection_id: ID of the affected collection.154 /// * admin: Admin address.155 CollectionAdminAdded(CollectionId, CrossAccountId),156157 /// Collection owned was changed158 ///159 /// # Arguments160 /// * collection_id: ID of the affected collection.161 /// * owner: New owner address.162 CollectionOwnedChanged(CollectionId, AccountId),163164 /// Collection sponsor was set165 ///166 /// # Arguments167 /// * collection_id: ID of the affected collection.168 /// * owner: New sponsor address.169 CollectionSponsorSet(CollectionId, AccountId),170171 /// New sponsor was confirm172 ///173 /// # Arguments174 /// * collection_id: ID of the affected collection.175 /// * sponsor: New sponsor address.176 SponsorshipConfirmed(CollectionId, AccountId),177178 /// Collection admin was removed179 ///180 /// # Arguments181 /// * collection_id: ID of the affected collection.182 /// * admin: Removed admin address.183 CollectionAdminRemoved(CollectionId, CrossAccountId),184185 /// Address was removed from the allow list186 ///187 /// # Arguments188 /// * collection_id: ID of the affected collection.189 /// * user: Address of the removed account.190 AllowListAddressRemoved(CollectionId, CrossAccountId),191192 /// Address was added to the allow list193 ///194 /// # Arguments195 /// * collection_id: ID of the affected collection.196 /// * user: Address of the added account.197 AllowListAddressAdded(CollectionId, CrossAccountId),198199 /// Collection limits were set200 ///201 /// # Arguments202 /// * collection_id: ID of the affected collection.203 CollectionLimitSet(CollectionId),204205 /// Collection permissions were set206 ///207 /// # Arguments208 /// * collection_id: ID of the affected collection.209 CollectionPermissionSet(CollectionId),210 }211}212213type SelfWeightOf<T> = <T as Config>::WeightInfo;214215// # Used definitions216//217// ## User control levels218//219// chain-controlled - key is uncontrolled by user220// i.e autoincrementing index221// can use non-cryptographic hash222// real - key is controlled by user223// but it is hard to generate enough colliding values, i.e owner of signed txs224// can use non-cryptographic hash225// controlled - key is completly controlled by users226// i.e maps with mutable keys227// should use cryptographic hash228//229// ## User control level downgrade reasons230//231// ?1 - chain-controlled -> controlled232// collections/tokens can be destroyed, resulting in massive holes233// ?2 - chain-controlled -> controlled234// same as ?1, but can be only added, resulting in easier exploitation235// ?3 - real -> controlled236// no confirmation required, so addresses can be easily generated237decl_storage! {238 trait Store for Module<T: Config> as Unique {239240 //#region Private members241 /// Used for migrations242 ChainVersion: u64;243 //#endregion244245 //#region Tokens transfer sponosoring rate limit baskets246 /// (Collection id (controlled?2), who created (real))247 /// TODO: Off chain worker should remove from this map when collection gets removed248 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;249 /// Collection id (controlled?2), token id (controlled?2)250 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251 /// Collection id (controlled?2), owning user (real)252 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253 /// Collection id (controlled?2), token id (controlled?2)254 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;255 //#endregion256257 /// Variable metadata sponsoring258 /// Collection id (controlled?2), token id (controlled?2)259 #[deprecated]260 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;261 /// Last sponsoring of token property setting // todo:doc rephrase this and the following262 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;263264 /// Last sponsoring of NFT approval in a collection265 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;266 /// Last sponsoring of fungible tokens approval in a collection267 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;268 /// Last sponsoring of RFT approval in a collection269 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;270 }271}272273decl_module! {274 /// Type alias to Pallet, to be used by construct_runtime.275 pub struct Module<T: Config> for enum Call276 where277 origin: T::Origin278 {279 type Error = Error<T>;280281 fn deposit_event() = default;282283 fn on_initialize(_now: T::BlockNumber) -> Weight {284 0285 }286287 fn on_runtime_upgrade() -> Weight {288 let limit = None;289290 <VariableMetaDataBasket<T>>::remove_all(limit);291292 0293 }294295 /// 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}