difftreelog
fix clear VariableMetaDataBasket
in: master
1 file changed
pallets/unique/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![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)]2425use frame_support::{26 decl_module, decl_storage, decl_error, decl_event,27 dispatch::DispatchResult,28 ensure,29 weights::{Weight},30 transactional,31 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32 BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38 CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41 SchemaVersion, SponsorshipState, CreateCollectionData,42 CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo,47 dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56 /// Error for non-fungible-token module.57 pub enum Error for Module<T: Config> {58 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59 CollectionDecimalPointLimitExceeded,60 /// This address is not set as sponsor, use setCollectionSponsor first.61 ConfirmUnsetSponsorFail,62 /// Length of items properties must be greater than 0.63 EmptyArgument,64 }65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970 /// Weight information for extrinsics in this pallet.71 type WeightInfo: WeightInfo;72 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76 pub enum Event<T>77 where78 <T as frame_system::Config>::AccountId,79 <T as pallet_evm::account::Config>::CrossAccountId,80 {81 /// Collection sponsor was removed82 ///83 /// # Arguments84 ///85 /// * collection_id: Globally unique collection identifier.86 CollectionSponsorRemoved(CollectionId),8788 /// Collection admin was added89 ///90 /// # Arguments91 ///92 /// * collection_id: Globally unique collection identifier.93 ///94 /// * admin: Admin address.95 CollectionAdminAdded(CollectionId, CrossAccountId),9697 /// Collection owned was change98 ///99 /// # Arguments100 ///101 /// * collection_id: Globally unique collection identifier.102 ///103 /// * owner: New owner address.104 CollectionOwnedChanged(CollectionId, AccountId),105106 /// Collection sponsor was set107 ///108 /// # Arguments109 ///110 /// * collection_id: Globally unique collection identifier.111 ///112 /// * owner: New sponsor address.113 CollectionSponsorSet(CollectionId, AccountId),114115 /// const on chain schema was set116 ///117 /// # Arguments118 ///119 /// * collection_id: Globally unique collection identifier.120 ConstOnChainSchemaSet(CollectionId),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 remove from allow list141 ///142 /// # Arguments143 ///144 /// * collection_id: Globally unique collection identifier.145 ///146 /// * user: Address.147 AllowListAddressRemoved(CollectionId, CrossAccountId),148149 /// Address was add to allow list150 ///151 /// # Arguments152 ///153 /// * collection_id: Globally unique collection identifier.154 ///155 /// * user: Address.156 AllowListAddressAdded(CollectionId, CrossAccountId),157158 /// Collection limits was set159 ///160 /// # Arguments161 ///162 /// * collection_id: Globally unique collection identifier.163 CollectionLimitSet(CollectionId),164165 /// Mint permission was set166 ///167 /// # Arguments168 ///169 /// * collection_id: Globally unique collection identifier.170 MintPermissionSet(CollectionId),171172 /// Offchain schema was set173 ///174 /// # Arguments175 ///176 /// * collection_id: Globally unique collection identifier.177 OffchainSchemaSet(CollectionId),178179 /// Public access mode was set180 ///181 /// # Arguments182 ///183 /// * collection_id: Globally unique collection identifier.184 ///185 /// * mode: New access state.186 PublicAccessModeSet(CollectionId, AccessMode),187188 /// Schema version was set189 ///190 /// # Arguments191 ///192 /// * collection_id: Globally unique collection identifier.193 SchemaVersionSet(CollectionId),194 }195}196197type SelfWeightOf<T> = <T as Config>::WeightInfo;198199// # Used definitions200//201// ## User control levels202//203// chain-controlled - key is uncontrolled by user204// i.e autoincrementing index205// can use non-cryptographic hash206// real - key is controlled by user207// but it is hard to generate enough colliding values, i.e owner of signed txs208// can use non-cryptographic hash209// controlled - key is completly controlled by users210// i.e maps with mutable keys211// should use cryptographic hash212//213// ## User control level downgrade reasons214//215// ?1 - chain-controlled -> controlled216// collections/tokens can be destroyed, resulting in massive holes217// ?2 - chain-controlled -> controlled218// same as ?1, but can be only added, resulting in easier exploitation219// ?3 - real -> controlled220// no confirmation required, so addresses can be easily generated221decl_storage! {222 trait Store for Module<T: Config> as Unique {223224 //#region Private members225 /// Used for migrations226 ChainVersion: u64;227 //#endregion228229 //#region Tokens transfer rate limit baskets230 /// (Collection id (controlled?2), who created (real))231 /// TODO: Off chain worker should remove from this map when collection gets removed232 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;233 /// Collection id (controlled?2), token id (controlled?2)234 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;235 /// Collection id (controlled?2), owning user (real)236 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;237 /// Collection id (controlled?2), token id (controlled?2)238 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>;239 //#endregion240241 /// Approval sponsoring242 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;243 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244 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>;245 }246}247248decl_module! {249 pub struct Module<T: Config> for enum Call250 where251 origin: T::Origin252 {253 type Error = Error<T>;254255 fn deposit_event() = default;256257 fn on_initialize(_now: T::BlockNumber) -> Weight {258 0259 }260261 /// 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.262 ///263 /// # Permissions264 ///265 /// * Anyone.266 ///267 /// # Arguments268 ///269 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.270 ///271 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.272 ///273 /// * token_prefix: UTF-8 string with token prefix.274 ///275 /// * mode: [CollectionMode] collection type and type dependent data.276 // returns collection ID277 #[weight = <SelfWeightOf<T>>::create_collection()]278 #[transactional]279 #[deprecated]280 pub fn create_collection(origin,281 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,282 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,283 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,284 mode: CollectionMode) -> DispatchResult {285 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {286 name: collection_name,287 description: collection_description,288 token_prefix,289 mode,290 ..Default::default()291 };292 Self::create_collection_ex(origin, data)293 }294295 /// This method creates a collection296 ///297 /// Prefer it to deprecated [`created_collection`] method298 #[weight = <SelfWeightOf<T>>::create_collection()]299 #[transactional]300 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {301 let sender = ensure_signed(origin)?;302303 // =========304305 T::CollectionDispatch::create(sender, data)?;306307 Ok(())308 }309310 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.311 ///312 /// # Permissions313 ///314 /// * Collection Owner.315 ///316 /// # Arguments317 ///318 /// * collection_id: collection to destroy.319 #[weight = <SelfWeightOf<T>>::destroy_collection()]320 #[transactional]321 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {322 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);323 let collection = <CollectionHandle<T>>::try_get(collection_id)?;324325 // =========326327 T::CollectionDispatch::destroy(sender, collection)?;328329 <NftTransferBasket<T>>::remove_prefix(collection_id, None);330 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);331 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);332333 <NftApproveBasket<T>>::remove_prefix(collection_id, None);334 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);335 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);336337 Ok(())338 }339340 /// Add an address to allow list.341 ///342 /// # Permissions343 ///344 /// * Collection Owner345 /// * Collection Admin346 ///347 /// # Arguments348 ///349 /// * collection_id.350 ///351 /// * address.352 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]353 #[transactional]354 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{355356 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);357 let collection = <CollectionHandle<T>>::try_get(collection_id)?;358359 <PalletCommon<T>>::toggle_allowlist(360 &collection,361 &sender,362 &address,363 true,364 )?;365366 Self::deposit_event(Event::<T>::AllowListAddressAdded(367 collection_id,368 address369 ));370371 Ok(())372 }373374 /// Remove an address from allow list.375 ///376 /// # Permissions377 ///378 /// * Collection Owner379 /// * Collection Admin380 ///381 /// # Arguments382 ///383 /// * collection_id.384 ///385 /// * address.386 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]387 #[transactional]388 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{389390 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);391 let collection = <CollectionHandle<T>>::try_get(collection_id)?;392393 <PalletCommon<T>>::toggle_allowlist(394 &collection,395 &sender,396 &address,397 false,398 )?;399400 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(401 collection_id,402 address403 ));404405 Ok(())406 }407408 /// Toggle between normal and allow list access for the methods with access for `Anyone`.409 ///410 /// # Permissions411 ///412 /// * Collection Owner.413 ///414 /// # Arguments415 ///416 /// * collection_id.417 ///418 /// * mode: [AccessMode]419 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]420 #[transactional]421 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult422 {423 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);424425 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;426 target_collection.check_is_owner(&sender)?;427428 target_collection.access = mode.clone();429430 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(431 collection_id,432 mode433 ));434435 target_collection.save()436 }437438 /// Allows Anyone to create tokens if:439 /// * Allow List is enabled, and440 /// * Address is added to allow list, and441 /// * This method was called with True parameter442 ///443 /// # Permissions444 /// * Collection Owner445 ///446 /// # Arguments447 ///448 /// * collection_id.449 ///450 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.451 #[weight = <SelfWeightOf<T>>::set_mint_permission()]452 #[transactional]453 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult454 {455 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);456457 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;458 target_collection.check_is_owner(&sender)?;459460 target_collection.mint_mode = mint_permission;461462 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(463 collection_id464 ));465466 target_collection.save()467 }468469 /// Change the owner of the collection.470 ///471 /// # Permissions472 ///473 /// * Collection Owner.474 ///475 /// # Arguments476 ///477 /// * collection_id.478 ///479 /// * new_owner.480 #[weight = <SelfWeightOf<T>>::change_collection_owner()]481 #[transactional]482 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {483484 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);485486 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;487 target_collection.check_is_owner(&sender)?;488489 target_collection.owner = new_owner.clone();490 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(491 collection_id,492 new_owner493 ));494495 target_collection.save()496 }497498 /// Adds an admin of the Collection.499 /// 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.500 ///501 /// # Permissions502 ///503 /// * Collection Owner.504 /// * Collection Admin.505 ///506 /// # Arguments507 ///508 /// * collection_id: ID of the Collection to add admin for.509 ///510 /// * new_admin_id: Address of new admin to add.511 #[weight = <SelfWeightOf<T>>::add_collection_admin()]512 #[transactional]513 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {514 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);515 let collection = <CollectionHandle<T>>::try_get(collection_id)?;516517 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(518 collection_id,519 new_admin_id.clone()520 ));521522 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)523 }524525 /// 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.526 ///527 /// # Permissions528 ///529 /// * Collection Owner.530 /// * Collection Admin.531 ///532 /// # Arguments533 ///534 /// * collection_id: ID of the Collection to remove admin for.535 ///536 /// * account_id: Address of admin to remove.537 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]538 #[transactional]539 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {540 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);541 let collection = <CollectionHandle<T>>::try_get(collection_id)?;542543 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(544 collection_id,545 account_id.clone()546 ));547548 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)549 }550551 /// # Permissions552 ///553 /// * Collection Owner554 ///555 /// # Arguments556 ///557 /// * collection_id.558 ///559 /// * new_sponsor.560 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]561 #[transactional]562 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {563 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);564565 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;566 target_collection.check_is_owner(&sender)?;567568 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());569570 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(571 collection_id,572 new_sponsor573 ));574575 target_collection.save()576 }577578 /// # Permissions579 ///580 /// * Sponsor.581 ///582 /// # Arguments583 ///584 /// * collection_id.585 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]586 #[transactional]587 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {588 let sender = ensure_signed(origin)?;589590 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;591 ensure!(592 target_collection.sponsorship.pending_sponsor() == Some(&sender),593 Error::<T>::ConfirmUnsetSponsorFail594 );595596 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());597598 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(599 collection_id,600 sender601 ));602603 target_collection.save()604 }605606 /// Switch back to pay-per-own-transaction model.607 ///608 /// # Permissions609 ///610 /// * Collection owner.611 ///612 /// # Arguments613 ///614 /// * collection_id.615 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]616 #[transactional]617 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {618 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);619620 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;621 target_collection.check_is_owner(&sender)?;622623 target_collection.sponsorship = SponsorshipState::Disabled;624625 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(626 collection_id627 ));628 target_collection.save()629 }630631 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.632 ///633 /// # Permissions634 ///635 /// * Collection Owner.636 /// * Collection Admin.637 /// * Anyone if638 /// * Allow List is enabled, and639 /// * Address is added to allow list, and640 /// * MintPermission is enabled (see SetMintPermission method)641 ///642 /// # Arguments643 ///644 /// * collection_id: ID of the collection.645 ///646 /// * owner: Address, initial owner of the NFT.647 ///648 /// * data: Token data to store on chain.649 #[weight = T::CommonWeightInfo::create_item()]650 #[transactional]651 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {652 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);653 let budget = budget::Value::new(2);654655 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))656 }657658 /// This method creates multiple items in a collection created with CreateCollection method.659 ///660 /// # Permissions661 ///662 /// * Collection Owner.663 /// * Collection Admin.664 /// * Anyone if665 /// * Allow List is enabled, and666 /// * Address is added to allow list, and667 /// * MintPermission is enabled (see SetMintPermission method)668 ///669 /// # Arguments670 ///671 /// * collection_id: ID of the collection.672 ///673 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].674 ///675 /// * owner: Address, initial owner of the NFT.676 #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]677 #[transactional]678 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {679 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);680 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681 let budget = budget::Value::new(2);682683 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))684 }685686 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]687 #[transactional]688 pub fn set_collection_properties(689 origin,690 collection_id: CollectionId,691 properties: Vec<Property>692 ) -> DispatchResultWithPostInfo {693 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);694695 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696697 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))698 }699700 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]701 #[transactional]702 pub fn delete_collection_properties(703 origin,704 collection_id: CollectionId,705 property_keys: Vec<PropertyKey>,706 ) -> DispatchResultWithPostInfo {707 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);708709 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))712 }713714 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]715 #[transactional]716 pub fn set_token_properties(717 origin,718 collection_id: CollectionId,719 token_id: TokenId,720 properties: Vec<Property>721 ) -> DispatchResultWithPostInfo {722 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))727 }728729 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]730 #[transactional]731 pub fn delete_token_properties(732 origin,733 collection_id: CollectionId,734 token_id: TokenId,735 property_keys: Vec<PropertyKey>736 ) -> DispatchResultWithPostInfo {737 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);738739 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740741 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))742 }743744 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]745 #[transactional]746 pub fn set_property_permissions(747 origin,748 collection_id: CollectionId,749 property_permissions: Vec<PropertyKeyPermission>,750 ) -> DispatchResultWithPostInfo {751 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);752753 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);754755 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))756 }757758 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]759 #[transactional]760 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {761 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);762 let budget = budget::Value::new(2);763764 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))765 }766767 // TODO! transaction weight768769 /// Set transfers_enabled value for particular collection770 ///771 /// # Permissions772 ///773 /// * Collection Owner.774 ///775 /// # Arguments776 ///777 /// * collection_id: ID of the collection.778 ///779 /// * value: New flag value.780 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]781 #[transactional]782 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {783 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);784 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;785 target_collection.check_is_owner(&sender)?;786787 // =========788789 target_collection.limits.transfers_enabled = Some(value);790 target_collection.save()791 }792793 /// Destroys a concrete instance of NFT.794 ///795 /// # Permissions796 ///797 /// * Collection Owner.798 /// * Collection Admin.799 /// * Current NFT Owner.800 ///801 /// # Arguments802 ///803 /// * collection_id: ID of the collection.804 ///805 /// * item_id: ID of NFT to burn.806 #[weight = T::CommonWeightInfo::burn_item()]807 #[transactional]808 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {809 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);810811 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;812 if value == 1 {813 <NftTransferBasket<T>>::remove(collection_id, item_id);814 <NftApproveBasket<T>>::remove(collection_id, item_id);815 }816 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?817 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());818 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));819 Ok(post_info)820 }821822 /// Destroys a concrete instance of NFT on behalf of the owner823 /// See also: [`approve`]824 ///825 /// # Permissions826 ///827 /// * Collection Owner.828 /// * Collection Admin.829 /// * Current NFT Owner.830 ///831 /// # Arguments832 ///833 /// * collection_id: ID of the collection.834 ///835 /// * item_id: ID of NFT to burn.836 ///837 /// * from: owner of item838 #[weight = T::CommonWeightInfo::burn_from()]839 #[transactional]840 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {841 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);842 let budget = budget::Value::new(2);843844 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))845 }846847 /// Change ownership of the token.848 ///849 /// # Permissions850 ///851 /// * Collection Owner852 /// * Collection Admin853 /// * Current NFT owner854 ///855 /// # Arguments856 ///857 /// * recipient: Address of token recipient.858 ///859 /// * collection_id.860 ///861 /// * item_id: ID of the item862 /// * Non-Fungible Mode: Required.863 /// * Fungible Mode: Ignored.864 /// * Re-Fungible Mode: Required.865 ///866 /// * value: Amount to transfer.867 /// * Non-Fungible Mode: Ignored868 /// * Fungible Mode: Must specify transferred amount869 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)870 #[weight = T::CommonWeightInfo::transfer()]871 #[transactional]872 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {873 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);874 let budget = budget::Value::new(2);875876 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))877 }878879 /// Set, change, or remove approved address to transfer the ownership of the NFT.880 ///881 /// # Permissions882 ///883 /// * Collection Owner884 /// * Collection Admin885 /// * Current NFT owner886 ///887 /// # Arguments888 ///889 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).890 ///891 /// * collection_id.892 ///893 /// * item_id: ID of the item.894 #[weight = T::CommonWeightInfo::approve()]895 #[transactional]896 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {897 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898899 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))900 }901902 /// 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.903 ///904 /// # Permissions905 /// * Collection Owner906 /// * Collection Admin907 /// * Current NFT owner908 /// * Address approved by current NFT owner909 ///910 /// # Arguments911 ///912 /// * from: Address that owns token.913 ///914 /// * recipient: Address of token recipient.915 ///916 /// * collection_id.917 ///918 /// * item_id: ID of the item.919 ///920 /// * value: Amount to transfer.921 #[weight = T::CommonWeightInfo::transfer_from()]922 #[transactional]923 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {924 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);925 let budget = budget::Value::new(2);926927 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))928 }929930 /// Set schema standard931 /// ImageURL932 /// Unique933 ///934 /// # Permissions935 ///936 /// * Collection Owner937 /// * Collection Admin938 ///939 /// # Arguments940 ///941 /// * collection_id.942 ///943 /// * schema: SchemaVersion: enum944 #[weight = <SelfWeightOf<T>>::set_schema_version()]945 #[transactional]946 pub fn set_schema_version(947 origin,948 collection_id: CollectionId,949 version: SchemaVersion950 ) -> DispatchResult {951 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;953 target_collection.check_is_owner_or_admin(&sender)?;954 target_collection.schema_version = version;955956 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(957 collection_id958 ));959960 target_collection.save()961 }962963 /// Set off-chain data schema.964 ///965 /// # Permissions966 ///967 /// * Collection Owner968 /// * Collection Admin969 ///970 /// # Arguments971 ///972 /// * collection_id.973 ///974 /// * schema: String representing the offchain data schema.975 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]976 #[transactional]977 pub fn set_offchain_schema(978 origin,979 collection_id: CollectionId,980 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,981 ) -> DispatchResult {982 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);983 let collection = <CollectionHandle<T>>::try_get(collection_id)?;984985 // =========986987 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;988989 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(990 collection_id991 ));992 Ok(())993 }994995 /// Set const on-chain data schema.996 ///997 /// # Permissions998 ///999 /// * Collection Owner1000 /// * Collection Admin1001 ///1002 /// # Arguments1003 ///1004 /// * collection_id.1005 ///1006 /// * schema: String representing the const on-chain data schema.1007 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1008 #[transactional]1009 pub fn set_const_on_chain_schema (1010 origin,1011 collection_id: CollectionId,1012 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1013 ) -> DispatchResult {1014 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10161017 // =========10181019 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10201021 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1022 collection_id1023 ));1024 Ok(())1025 }10261027 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1028 #[transactional]1029 pub fn set_collection_limits(1030 origin,1031 collection_id: CollectionId,1032 new_limit: CollectionLimits,1033 ) -> DispatchResult {1034 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1035 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1036 target_collection.check_is_owner(&sender)?;1037 let old_limit = &target_collection.limits;10381039 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10401041 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1042 collection_id1043 ));10441045 target_collection.save()1046 }1047 }1048}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)]2425use frame_support::{26 decl_module, decl_storage, decl_error, decl_event,27 dispatch::DispatchResult,28 ensure,29 weights::{Weight},30 transactional,31 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32 BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38 CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41 SchemaVersion, SponsorshipState, CreateCollectionData,42 CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo,47 dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455decl_error! {56 /// Error for non-fungible-token module.57 pub enum Error for Module<T: Config> {58 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.59 CollectionDecimalPointLimitExceeded,60 /// This address is not set as sponsor, use setCollectionSponsor first.61 ConfirmUnsetSponsorFail,62 /// Length of items properties must be greater than 0.63 EmptyArgument,64 }65}6667pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {68 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;6970 /// Weight information for extrinsics in this pallet.71 type WeightInfo: WeightInfo;72 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;73}7475decl_event! {76 pub enum Event<T>77 where78 <T as frame_system::Config>::AccountId,79 <T as pallet_evm::account::Config>::CrossAccountId,80 {81 /// Collection sponsor was removed82 ///83 /// # Arguments84 ///85 /// * collection_id: Globally unique collection identifier.86 CollectionSponsorRemoved(CollectionId),8788 /// Collection admin was added89 ///90 /// # Arguments91 ///92 /// * collection_id: Globally unique collection identifier.93 ///94 /// * admin: Admin address.95 CollectionAdminAdded(CollectionId, CrossAccountId),9697 /// Collection owned was change98 ///99 /// # Arguments100 ///101 /// * collection_id: Globally unique collection identifier.102 ///103 /// * owner: New owner address.104 CollectionOwnedChanged(CollectionId, AccountId),105106 /// Collection sponsor was set107 ///108 /// # Arguments109 ///110 /// * collection_id: Globally unique collection identifier.111 ///112 /// * owner: New sponsor address.113 CollectionSponsorSet(CollectionId, AccountId),114115 /// const on chain schema was set116 ///117 /// # Arguments118 ///119 /// * collection_id: Globally unique collection identifier.120 ConstOnChainSchemaSet(CollectionId),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 remove from allow list141 ///142 /// # Arguments143 ///144 /// * collection_id: Globally unique collection identifier.145 ///146 /// * user: Address.147 AllowListAddressRemoved(CollectionId, CrossAccountId),148149 /// Address was add to allow list150 ///151 /// # Arguments152 ///153 /// * collection_id: Globally unique collection identifier.154 ///155 /// * user: Address.156 AllowListAddressAdded(CollectionId, CrossAccountId),157158 /// Collection limits was set159 ///160 /// # Arguments161 ///162 /// * collection_id: Globally unique collection identifier.163 CollectionLimitSet(CollectionId),164165 /// Mint permission was set166 ///167 /// # Arguments168 ///169 /// * collection_id: Globally unique collection identifier.170 MintPermissionSet(CollectionId),171172 /// Offchain schema was set173 ///174 /// # Arguments175 ///176 /// * collection_id: Globally unique collection identifier.177 OffchainSchemaSet(CollectionId),178179 /// Public access mode was set180 ///181 /// # Arguments182 ///183 /// * collection_id: Globally unique collection identifier.184 ///185 /// * mode: New access state.186 PublicAccessModeSet(CollectionId, AccessMode),187188 /// Schema version was set189 ///190 /// # Arguments191 ///192 /// * collection_id: Globally unique collection identifier.193 SchemaVersionSet(CollectionId),194 }195}196197type SelfWeightOf<T> = <T as Config>::WeightInfo;198199// # Used definitions200//201// ## User control levels202//203// chain-controlled - key is uncontrolled by user204// i.e autoincrementing index205// can use non-cryptographic hash206// real - key is controlled by user207// but it is hard to generate enough colliding values, i.e owner of signed txs208// can use non-cryptographic hash209// controlled - key is completly controlled by users210// i.e maps with mutable keys211// should use cryptographic hash212//213// ## User control level downgrade reasons214//215// ?1 - chain-controlled -> controlled216// collections/tokens can be destroyed, resulting in massive holes217// ?2 - chain-controlled -> controlled218// same as ?1, but can be only added, resulting in easier exploitation219// ?3 - real -> controlled220// no confirmation required, so addresses can be easily generated221decl_storage! {222 trait Store for Module<T: Config> as Unique {223224 //#region Private members225 /// Used for migrations226 ChainVersion: u64;227 //#endregion228229 //#region Tokens transfer rate limit baskets230 /// (Collection id (controlled?2), who created (real))231 /// TODO: Off chain worker should remove from this map when collection gets removed232 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;233 /// Collection id (controlled?2), token id (controlled?2)234 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;235 /// Collection id (controlled?2), owning user (real)236 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;237 /// Collection id (controlled?2), token id (controlled?2)238 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>;239 //#endregion240241 /// Variable metadata sponsoring242 /// Collection id (controlled?2), token id (controlled?2)243 #[deprecated]244 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;245246 /// Approval sponsoring247 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;249 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>;250 }251}252253decl_module! {254 pub struct Module<T: Config> for enum Call255 where256 origin: T::Origin257 {258 type Error = Error<T>;259260 fn deposit_event() = default;261262 fn on_initialize(_now: T::BlockNumber) -> Weight {263 0264 }265266 fn on_runtime_upgrade() -> Weight {267 let limit = None;268269 <VariableMetaDataBasket<T>>::remove_all(limit);270271 0272 }273274 /// 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.275 ///276 /// # Permissions277 ///278 /// * Anyone.279 ///280 /// # Arguments281 ///282 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.283 ///284 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.285 ///286 /// * token_prefix: UTF-8 string with token prefix.287 ///288 /// * mode: [CollectionMode] collection type and type dependent data.289 // returns collection ID290 #[weight = <SelfWeightOf<T>>::create_collection()]291 #[transactional]292 #[deprecated]293 pub fn create_collection(origin,294 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,295 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,296 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,297 mode: CollectionMode) -> DispatchResult {298 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {299 name: collection_name,300 description: collection_description,301 token_prefix,302 mode,303 ..Default::default()304 };305 Self::create_collection_ex(origin, data)306 }307308 /// This method creates a collection309 ///310 /// Prefer it to deprecated [`created_collection`] method311 #[weight = <SelfWeightOf<T>>::create_collection()]312 #[transactional]313 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {314 let sender = ensure_signed(origin)?;315316 // =========317318 T::CollectionDispatch::create(sender, data)?;319320 Ok(())321 }322323 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.324 ///325 /// # Permissions326 ///327 /// * Collection Owner.328 ///329 /// # Arguments330 ///331 /// * collection_id: collection to destroy.332 #[weight = <SelfWeightOf<T>>::destroy_collection()]333 #[transactional]334 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {335 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);336 let collection = <CollectionHandle<T>>::try_get(collection_id)?;337338 // =========339340 T::CollectionDispatch::destroy(sender, collection)?;341342 <NftTransferBasket<T>>::remove_prefix(collection_id, None);343 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);344 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);345346 <NftApproveBasket<T>>::remove_prefix(collection_id, None);347 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);348 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);349350 Ok(())351 }352353 /// Add an address to allow list.354 ///355 /// # Permissions356 ///357 /// * Collection Owner358 /// * Collection Admin359 ///360 /// # Arguments361 ///362 /// * collection_id.363 ///364 /// * address.365 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]366 #[transactional]367 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{368369 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);370 let collection = <CollectionHandle<T>>::try_get(collection_id)?;371372 <PalletCommon<T>>::toggle_allowlist(373 &collection,374 &sender,375 &address,376 true,377 )?;378379 Self::deposit_event(Event::<T>::AllowListAddressAdded(380 collection_id,381 address382 ));383384 Ok(())385 }386387 /// Remove an address from allow list.388 ///389 /// # Permissions390 ///391 /// * Collection Owner392 /// * Collection Admin393 ///394 /// # Arguments395 ///396 /// * collection_id.397 ///398 /// * address.399 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]400 #[transactional]401 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{402403 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);404 let collection = <CollectionHandle<T>>::try_get(collection_id)?;405406 <PalletCommon<T>>::toggle_allowlist(407 &collection,408 &sender,409 &address,410 false,411 )?;412413 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(414 collection_id,415 address416 ));417418 Ok(())419 }420421 /// Toggle between normal and allow list access for the methods with access for `Anyone`.422 ///423 /// # Permissions424 ///425 /// * Collection Owner.426 ///427 /// # Arguments428 ///429 /// * collection_id.430 ///431 /// * mode: [AccessMode]432 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]433 #[transactional]434 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult435 {436 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);437438 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;439 target_collection.check_is_owner(&sender)?;440441 target_collection.access = mode.clone();442443 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(444 collection_id,445 mode446 ));447448 target_collection.save()449 }450451 /// Allows Anyone to create tokens if:452 /// * Allow List is enabled, and453 /// * Address is added to allow list, and454 /// * This method was called with True parameter455 ///456 /// # Permissions457 /// * Collection Owner458 ///459 /// # Arguments460 ///461 /// * collection_id.462 ///463 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.464 #[weight = <SelfWeightOf<T>>::set_mint_permission()]465 #[transactional]466 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult467 {468 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);469470 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;471 target_collection.check_is_owner(&sender)?;472473 target_collection.mint_mode = mint_permission;474475 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(476 collection_id477 ));478479 target_collection.save()480 }481482 /// Change the owner of the collection.483 ///484 /// # Permissions485 ///486 /// * Collection Owner.487 ///488 /// # Arguments489 ///490 /// * collection_id.491 ///492 /// * new_owner.493 #[weight = <SelfWeightOf<T>>::change_collection_owner()]494 #[transactional]495 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {496497 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);498499 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;500 target_collection.check_is_owner(&sender)?;501502 target_collection.owner = new_owner.clone();503 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(504 collection_id,505 new_owner506 ));507508 target_collection.save()509 }510511 /// Adds an admin of the Collection.512 /// 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.513 ///514 /// # Permissions515 ///516 /// * Collection Owner.517 /// * Collection Admin.518 ///519 /// # Arguments520 ///521 /// * collection_id: ID of the Collection to add admin for.522 ///523 /// * new_admin_id: Address of new admin to add.524 #[weight = <SelfWeightOf<T>>::add_collection_admin()]525 #[transactional]526 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {527 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528 let collection = <CollectionHandle<T>>::try_get(collection_id)?;529530 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(531 collection_id,532 new_admin_id.clone()533 ));534535 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)536 }537538 /// 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.539 ///540 /// # Permissions541 ///542 /// * Collection Owner.543 /// * Collection Admin.544 ///545 /// # Arguments546 ///547 /// * collection_id: ID of the Collection to remove admin for.548 ///549 /// * account_id: Address of admin to remove.550 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]551 #[transactional]552 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554 let collection = <CollectionHandle<T>>::try_get(collection_id)?;555556 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(557 collection_id,558 account_id.clone()559 ));560561 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)562 }563564 /// # Permissions565 ///566 /// * Collection Owner567 ///568 /// # Arguments569 ///570 /// * collection_id.571 ///572 /// * new_sponsor.573 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]574 #[transactional]575 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {576 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);577578 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;579 target_collection.check_is_owner(&sender)?;580581 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());582583 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(584 collection_id,585 new_sponsor586 ));587588 target_collection.save()589 }590591 /// # Permissions592 ///593 /// * Sponsor.594 ///595 /// # Arguments596 ///597 /// * collection_id.598 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]599 #[transactional]600 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {601 let sender = ensure_signed(origin)?;602603 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;604 ensure!(605 target_collection.sponsorship.pending_sponsor() == Some(&sender),606 Error::<T>::ConfirmUnsetSponsorFail607 );608609 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());610611 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(612 collection_id,613 sender614 ));615616 target_collection.save()617 }618619 /// Switch back to pay-per-own-transaction model.620 ///621 /// # Permissions622 ///623 /// * Collection owner.624 ///625 /// # Arguments626 ///627 /// * collection_id.628 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]629 #[transactional]630 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {631 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);632633 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634 target_collection.check_is_owner(&sender)?;635636 target_collection.sponsorship = SponsorshipState::Disabled;637638 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(639 collection_id640 ));641 target_collection.save()642 }643644 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.645 ///646 /// # Permissions647 ///648 /// * Collection Owner.649 /// * Collection Admin.650 /// * Anyone if651 /// * Allow List is enabled, and652 /// * Address is added to allow list, and653 /// * MintPermission is enabled (see SetMintPermission method)654 ///655 /// # Arguments656 ///657 /// * collection_id: ID of the collection.658 ///659 /// * owner: Address, initial owner of the NFT.660 ///661 /// * data: Token data to store on chain.662 #[weight = T::CommonWeightInfo::create_item()]663 #[transactional]664 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {665 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);666 let budget = budget::Value::new(2);667668 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))669 }670671 /// This method creates multiple items in a collection created with CreateCollection method.672 ///673 /// # Permissions674 ///675 /// * Collection Owner.676 /// * Collection Admin.677 /// * Anyone if678 /// * Allow List is enabled, and679 /// * Address is added to allow list, and680 /// * MintPermission is enabled (see SetMintPermission method)681 ///682 /// # Arguments683 ///684 /// * collection_id: ID of the collection.685 ///686 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].687 ///688 /// * owner: Address, initial owner of the NFT.689 #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]690 #[transactional]691 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {692 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);693 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);694 let budget = budget::Value::new(2);695696 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))697 }698699 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]700 #[transactional]701 pub fn set_collection_properties(702 origin,703 collection_id: CollectionId,704 properties: Vec<Property>705 ) -> DispatchResultWithPostInfo {706 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);707708 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);709710 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))711 }712713 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]714 #[transactional]715 pub fn delete_collection_properties(716 origin,717 collection_id: CollectionId,718 property_keys: Vec<PropertyKey>,719 ) -> DispatchResultWithPostInfo {720 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);721722 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);723724 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))725 }726727 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]728 #[transactional]729 pub fn set_token_properties(730 origin,731 collection_id: CollectionId,732 token_id: TokenId,733 properties: Vec<Property>734 ) -> DispatchResultWithPostInfo {735 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);736737 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738739 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))740 }741742 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]743 #[transactional]744 pub fn delete_token_properties(745 origin,746 collection_id: CollectionId,747 token_id: TokenId,748 property_keys: Vec<PropertyKey>749 ) -> DispatchResultWithPostInfo {750 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);751752 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);753754 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))755 }756757 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]758 #[transactional]759 pub fn set_property_permissions(760 origin,761 collection_id: CollectionId,762 property_permissions: Vec<PropertyKeyPermission>,763 ) -> DispatchResultWithPostInfo {764 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);765766 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767768 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))769 }770771 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]772 #[transactional]773 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {774 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);775 let budget = budget::Value::new(2);776777 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))778 }779780 // TODO! transaction weight781782 /// Set transfers_enabled value for particular collection783 ///784 /// # Permissions785 ///786 /// * Collection Owner.787 ///788 /// # Arguments789 ///790 /// * collection_id: ID of the collection.791 ///792 /// * value: New flag value.793 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]794 #[transactional]795 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {796 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);797 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;798 target_collection.check_is_owner(&sender)?;799800 // =========801802 target_collection.limits.transfers_enabled = Some(value);803 target_collection.save()804 }805806 /// Destroys a concrete instance of NFT.807 ///808 /// # Permissions809 ///810 /// * Collection Owner.811 /// * Collection Admin.812 /// * Current NFT Owner.813 ///814 /// # Arguments815 ///816 /// * collection_id: ID of the collection.817 ///818 /// * item_id: ID of NFT to burn.819 #[weight = T::CommonWeightInfo::burn_item()]820 #[transactional]821 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {822 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);823824 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;825 if value == 1 {826 <NftTransferBasket<T>>::remove(collection_id, item_id);827 <NftApproveBasket<T>>::remove(collection_id, item_id);828 }829 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?830 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());831 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));832 Ok(post_info)833 }834835 /// Destroys a concrete instance of NFT on behalf of the owner836 /// See also: [`approve`]837 ///838 /// # Permissions839 ///840 /// * Collection Owner.841 /// * Collection Admin.842 /// * Current NFT Owner.843 ///844 /// # Arguments845 ///846 /// * collection_id: ID of the collection.847 ///848 /// * item_id: ID of NFT to burn.849 ///850 /// * from: owner of item851 #[weight = T::CommonWeightInfo::burn_from()]852 #[transactional]853 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {854 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);855 let budget = budget::Value::new(2);856857 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))858 }859860 /// Change ownership of the token.861 ///862 /// # Permissions863 ///864 /// * Collection Owner865 /// * Collection Admin866 /// * Current NFT owner867 ///868 /// # Arguments869 ///870 /// * recipient: Address of token recipient.871 ///872 /// * collection_id.873 ///874 /// * item_id: ID of the item875 /// * Non-Fungible Mode: Required.876 /// * Fungible Mode: Ignored.877 /// * Re-Fungible Mode: Required.878 ///879 /// * value: Amount to transfer.880 /// * Non-Fungible Mode: Ignored881 /// * Fungible Mode: Must specify transferred amount882 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)883 #[weight = T::CommonWeightInfo::transfer()]884 #[transactional]885 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {886 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);887 let budget = budget::Value::new(2);888889 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))890 }891892 /// Set, change, or remove approved address to transfer the ownership of the NFT.893 ///894 /// # Permissions895 ///896 /// * Collection Owner897 /// * Collection Admin898 /// * Current NFT owner899 ///900 /// # Arguments901 ///902 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).903 ///904 /// * collection_id.905 ///906 /// * item_id: ID of the item.907 #[weight = T::CommonWeightInfo::approve()]908 #[transactional]909 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {910 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);911912 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))913 }914915 /// 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.916 ///917 /// # Permissions918 /// * Collection Owner919 /// * Collection Admin920 /// * Current NFT owner921 /// * Address approved by current NFT owner922 ///923 /// # Arguments924 ///925 /// * from: Address that owns token.926 ///927 /// * recipient: Address of token recipient.928 ///929 /// * collection_id.930 ///931 /// * item_id: ID of the item.932 ///933 /// * value: Amount to transfer.934 #[weight = T::CommonWeightInfo::transfer_from()]935 #[transactional]936 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {937 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);938 let budget = budget::Value::new(2);939940 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))941 }942943 /// Set schema standard944 /// ImageURL945 /// Unique946 ///947 /// # Permissions948 ///949 /// * Collection Owner950 /// * Collection Admin951 ///952 /// # Arguments953 ///954 /// * collection_id.955 ///956 /// * schema: SchemaVersion: enum957 #[weight = <SelfWeightOf<T>>::set_schema_version()]958 #[transactional]959 pub fn set_schema_version(960 origin,961 collection_id: CollectionId,962 version: SchemaVersion963 ) -> DispatchResult {964 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);965 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;966 target_collection.check_is_owner_or_admin(&sender)?;967 target_collection.schema_version = version;968969 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(970 collection_id971 ));972973 target_collection.save()974 }975976 /// Set off-chain data schema.977 ///978 /// # Permissions979 ///980 /// * Collection Owner981 /// * Collection Admin982 ///983 /// # Arguments984 ///985 /// * collection_id.986 ///987 /// * schema: String representing the offchain data schema.988 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]989 #[transactional]990 pub fn set_offchain_schema(991 origin,992 collection_id: CollectionId,993 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,994 ) -> DispatchResult {995 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);996 let collection = <CollectionHandle<T>>::try_get(collection_id)?;997998 // =========9991000 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10011002 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1003 collection_id1004 ));1005 Ok(())1006 }10071008 /// Set const on-chain data schema.1009 ///1010 /// # Permissions1011 ///1012 /// * Collection Owner1013 /// * Collection Admin1014 ///1015 /// # Arguments1016 ///1017 /// * collection_id.1018 ///1019 /// * schema: String representing the const on-chain data schema.1020 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1021 #[transactional]1022 pub fn set_const_on_chain_schema (1023 origin,1024 collection_id: CollectionId,1025 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1026 ) -> DispatchResult {1027 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1028 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10291030 // =========10311032 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10331034 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1035 collection_id1036 ));1037 Ok(())1038 }10391040 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1041 #[transactional]1042 pub fn set_collection_limits(1043 origin,1044 collection_id: CollectionId,1045 new_limit: CollectionLimits,1046 ) -> DispatchResult {1047 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1048 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1049 target_collection.check_is_owner(&sender)?;1050 let old_limit = &target_collection.limits;10511052 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10531054 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1055 collection_id1056 ));10571058 target_collection.save()1059 }1060 }1061}