difftreelog
refactor remove outdated events
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)]2425extern crate alloc;2627use frame_support::{28 decl_module, decl_storage, decl_error, decl_event,29 dispatch::DispatchResult,30 ensure,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,41 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,42 CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,43 CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,44 PropertyKeyPermission,45};46use pallet_evm::account::CrossAccountId;47use pallet_common::{48 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,49 dispatch::CollectionDispatch,50};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758decl_error! {59 /// Error for non-fungible-token module.60 pub enum Error for Module<T: Config> {61 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.62 CollectionDecimalPointLimitExceeded,63 /// This address is not set as sponsor, use setCollectionSponsor first.64 ConfirmUnsetSponsorFail,65 /// Length of items properties must be greater than 0.66 EmptyArgument,67 }68}6970pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {71 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7273 /// Weight information for extrinsics in this pallet.74 type WeightInfo: WeightInfo;75 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;76}7778decl_event! {79 pub enum Event<T>80 where81 <T as frame_system::Config>::AccountId,82 <T as pallet_evm::account::Config>::CrossAccountId,83 {84 /// Collection sponsor was removed85 ///86 /// # Arguments87 ///88 /// * collection_id: Globally unique collection identifier.89 CollectionSponsorRemoved(CollectionId),9091 /// Collection admin was added92 ///93 /// # Arguments94 ///95 /// * collection_id: Globally unique collection identifier.96 ///97 /// * admin: Admin address.98 CollectionAdminAdded(CollectionId, CrossAccountId),99100 /// Collection owned was change101 ///102 /// # Arguments103 ///104 /// * collection_id: Globally unique collection identifier.105 ///106 /// * owner: New owner address.107 CollectionOwnedChanged(CollectionId, AccountId),108109 /// Collection sponsor was set110 ///111 /// # Arguments112 ///113 /// * collection_id: Globally unique collection identifier.114 ///115 /// * owner: New sponsor address.116 CollectionSponsorSet(CollectionId, AccountId),117118 /// const on chain schema was set119 ///120 /// # Arguments121 ///122 /// * collection_id: Globally unique collection identifier.123 ConstOnChainSchemaSet(CollectionId),124125 /// New sponsor was confirm126 ///127 /// # Arguments128 ///129 /// * collection_id: Globally unique collection identifier.130 ///131 /// * sponsor: New sponsor address.132 SponsorshipConfirmed(CollectionId, AccountId),133134 /// Collection admin was removed135 ///136 /// # Arguments137 ///138 /// * collection_id: Globally unique collection identifier.139 ///140 /// * admin: Admin address.141 CollectionAdminRemoved(CollectionId, CrossAccountId),142143 /// Address was remove from allow list144 ///145 /// # Arguments146 ///147 /// * collection_id: Globally unique collection identifier.148 ///149 /// * user: Address.150 AllowListAddressRemoved(CollectionId, CrossAccountId),151152 /// Address was add to allow list153 ///154 /// # Arguments155 ///156 /// * collection_id: Globally unique collection identifier.157 ///158 /// * user: Address.159 AllowListAddressAdded(CollectionId, CrossAccountId),160161 /// Collection limits was set162 ///163 /// # Arguments164 ///165 /// * collection_id: Globally unique collection identifier.166 CollectionLimitSet(CollectionId),167168 CollectionPermissionSet(CollectionId),169170 /// Mint permission was set171 ///172 /// # Arguments173 ///174 /// * collection_id: Globally unique collection identifier.175 MintPermissionSet(CollectionId),176177 /// Offchain schema was set178 ///179 /// # Arguments180 ///181 /// * collection_id: Globally unique collection identifier.182 OffchainSchemaSet(CollectionId),183184 /// Public access mode was set185 ///186 /// # Arguments187 ///188 /// * collection_id: Globally unique collection identifier.189 ///190 /// * mode: New access state.191 PublicAccessModeSet(CollectionId, AccessMode),192193 /// Schema version was set194 ///195 /// # Arguments196 ///197 /// * collection_id: Globally unique collection identifier.198 SchemaVersionSet(CollectionId),199 }200}201202type SelfWeightOf<T> = <T as Config>::WeightInfo;203204// # Used definitions205//206// ## User control levels207//208// chain-controlled - key is uncontrolled by user209// i.e autoincrementing index210// can use non-cryptographic hash211// real - key is controlled by user212// but it is hard to generate enough colliding values, i.e owner of signed txs213// can use non-cryptographic hash214// controlled - key is completly controlled by users215// i.e maps with mutable keys216// should use cryptographic hash217//218// ## User control level downgrade reasons219//220// ?1 - chain-controlled -> controlled221// collections/tokens can be destroyed, resulting in massive holes222// ?2 - chain-controlled -> controlled223// same as ?1, but can be only added, resulting in easier exploitation224// ?3 - real -> controlled225// no confirmation required, so addresses can be easily generated226decl_storage! {227 trait Store for Module<T: Config> as Unique {228229 //#region Private members230 /// Used for migrations231 ChainVersion: u64;232 //#endregion233234 //#region Tokens transfer rate limit baskets235 /// (Collection id (controlled?2), who created (real))236 /// TODO: Off chain worker should remove from this map when collection gets removed237 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;238 /// Collection id (controlled?2), token id (controlled?2)239 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;240 /// Collection id (controlled?2), owning user (real)241 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;242 /// Collection id (controlled?2), token id (controlled?2)243 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>;244 //#endregion245246 /// Variable metadata sponsoring247 /// Collection id (controlled?2), token id (controlled?2)248 #[deprecated]249 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251252 /// Approval sponsoring253 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;254 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;255 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>;256 }257}258259decl_module! {260 pub struct Module<T: Config> for enum Call261 where262 origin: T::Origin263 {264 type Error = Error<T>;265266 fn deposit_event() = default;267268 fn on_initialize(_now: T::BlockNumber) -> Weight {269 0270 }271272 fn on_runtime_upgrade() -> Weight {273 let limit = None;274275 <VariableMetaDataBasket<T>>::remove_all(limit);276277 0278 }279280 /// 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.281 ///282 /// # Permissions283 ///284 /// * Anyone.285 ///286 /// # Arguments287 ///288 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.289 ///290 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.291 ///292 /// * token_prefix: UTF-8 string with token prefix.293 ///294 /// * mode: [CollectionMode] collection type and type dependent data.295 // returns collection ID296 #[weight = <SelfWeightOf<T>>::create_collection()]297 #[transactional]298 #[deprecated]299 pub fn create_collection(origin,300 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,301 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,302 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,303 mode: CollectionMode) -> DispatchResult {304 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {305 name: collection_name,306 description: collection_description,307 token_prefix,308 mode,309 ..Default::default()310 };311 Self::create_collection_ex(origin, data)312 }313314 /// This method creates a collection315 ///316 /// Prefer it to deprecated [`created_collection`] method317 #[weight = <SelfWeightOf<T>>::create_collection()]318 #[transactional]319 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {320 let sender = ensure_signed(origin)?;321322 // =========323324 T::CollectionDispatch::create(sender, data)?;325326 Ok(())327 }328329 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.330 ///331 /// # Permissions332 ///333 /// * Collection Owner.334 ///335 /// # Arguments336 ///337 /// * collection_id: collection to destroy.338 #[weight = <SelfWeightOf<T>>::destroy_collection()]339 #[transactional]340 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {341 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);342 let collection = <CollectionHandle<T>>::try_get(collection_id)?;343344 // =========345346 T::CollectionDispatch::destroy(sender, collection)?;347348 <NftTransferBasket<T>>::remove_prefix(collection_id, None);349 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);350 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);351352 <NftApproveBasket<T>>::remove_prefix(collection_id, None);353 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);354 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);355356 Ok(())357 }358359 /// Add an address to allow list.360 ///361 /// # Permissions362 ///363 /// * Collection Owner364 /// * Collection Admin365 ///366 /// # Arguments367 ///368 /// * collection_id.369 ///370 /// * address.371 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]372 #[transactional]373 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{374375 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);376 let collection = <CollectionHandle<T>>::try_get(collection_id)?;377378 <PalletCommon<T>>::toggle_allowlist(379 &collection,380 &sender,381 &address,382 true,383 )?;384385 Self::deposit_event(Event::<T>::AllowListAddressAdded(386 collection_id,387 address388 ));389390 Ok(())391 }392393 /// Remove an address from allow list.394 ///395 /// # Permissions396 ///397 /// * Collection Owner398 /// * Collection Admin399 ///400 /// # Arguments401 ///402 /// * collection_id.403 ///404 /// * address.405 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]406 #[transactional]407 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{408409 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);410 let collection = <CollectionHandle<T>>::try_get(collection_id)?;411412 <PalletCommon<T>>::toggle_allowlist(413 &collection,414 &sender,415 &address,416 false,417 )?;418419 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(420 collection_id,421 address422 ));423424 Ok(())425 }426427 /// Change the owner of the collection.428 ///429 /// # Permissions430 ///431 /// * Collection Owner.432 ///433 /// # Arguments434 ///435 /// * collection_id.436 ///437 /// * new_owner.438 #[weight = <SelfWeightOf<T>>::change_collection_owner()]439 #[transactional]440 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {441442 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);443444 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;445 target_collection.check_is_owner(&sender)?;446447 target_collection.owner = new_owner.clone();448 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(449 collection_id,450 new_owner451 ));452453 target_collection.save()454 }455456 /// Adds an admin of the Collection.457 /// 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.458 ///459 /// # Permissions460 ///461 /// * Collection Owner.462 /// * Collection Admin.463 ///464 /// # Arguments465 ///466 /// * collection_id: ID of the Collection to add admin for.467 ///468 /// * new_admin_id: Address of new admin to add.469 #[weight = <SelfWeightOf<T>>::add_collection_admin()]470 #[transactional]471 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {472 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);473 let collection = <CollectionHandle<T>>::try_get(collection_id)?;474475 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(476 collection_id,477 new_admin_id.clone()478 ));479480 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)481 }482483 /// 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.484 ///485 /// # Permissions486 ///487 /// * Collection Owner.488 /// * Collection Admin.489 ///490 /// # Arguments491 ///492 /// * collection_id: ID of the Collection to remove admin for.493 ///494 /// * account_id: Address of admin to remove.495 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]496 #[transactional]497 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499 let collection = <CollectionHandle<T>>::try_get(collection_id)?;500501 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(502 collection_id,503 account_id.clone()504 ));505506 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)507 }508509 /// # Permissions510 ///511 /// * Collection Owner512 ///513 /// # Arguments514 ///515 /// * collection_id.516 ///517 /// * new_sponsor.518 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]519 #[transactional]520 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {521 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);522523 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;524 target_collection.check_is_owner(&sender)?;525526 target_collection.set_sponsor(new_sponsor.clone());527528 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(529 collection_id,530 new_sponsor531 ));532533 target_collection.save()534 }535536 /// # Permissions537 ///538 /// * Sponsor.539 ///540 /// # Arguments541 ///542 /// * collection_id.543 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]544 #[transactional]545 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {546 let sender = ensure_signed(origin)?;547548 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;549 ensure!(550 target_collection.confirm_sponsorship(&sender),551 Error::<T>::ConfirmUnsetSponsorFail552 );553554 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(555 collection_id,556 sender557 ));558559 target_collection.save()560 }561562 /// Switch back to pay-per-own-transaction model.563 ///564 /// # Permissions565 ///566 /// * Collection owner.567 ///568 /// # Arguments569 ///570 /// * collection_id.571 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]572 #[transactional]573 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {574 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575576 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;577 target_collection.check_is_owner(&sender)?;578579 target_collection.sponsorship = SponsorshipState::Disabled;580581 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(582 collection_id583 ));584 target_collection.save()585 }586587 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.588 ///589 /// # Permissions590 ///591 /// * Collection Owner.592 /// * Collection Admin.593 /// * Anyone if594 /// * Allow List is enabled, and595 /// * Address is added to allow list, and596 /// * MintPermission is enabled (see SetMintPermission method)597 ///598 /// # Arguments599 ///600 /// * collection_id: ID of the collection.601 ///602 /// * owner: Address, initial owner of the NFT.603 ///604 /// * data: Token data to store on chain.605 #[weight = T::CommonWeightInfo::create_item()]606 #[transactional]607 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {608 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);609 let budget = budget::Value::new(2);610611 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))612 }613614 /// This method creates multiple items in a collection created with CreateCollection method.615 ///616 /// # Permissions617 ///618 /// * Collection Owner.619 /// * Collection Admin.620 /// * Anyone if621 /// * Allow List is enabled, and622 /// * Address is added to allow list, and623 /// * MintPermission is enabled (see SetMintPermission method)624 ///625 /// # Arguments626 ///627 /// * collection_id: ID of the collection.628 ///629 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].630 ///631 /// * owner: Address, initial owner of the NFT.632 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]633 #[transactional]634 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {635 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);636 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);637 let budget = budget::Value::new(2);638639 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))640 }641642 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]643 #[transactional]644 pub fn set_collection_properties(645 origin,646 collection_id: CollectionId,647 properties: Vec<Property>648 ) -> DispatchResultWithPostInfo {649 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);650651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652653 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))654 }655656 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]657 #[transactional]658 pub fn delete_collection_properties(659 origin,660 collection_id: CollectionId,661 property_keys: Vec<PropertyKey>,662 ) -> DispatchResultWithPostInfo {663 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);664665 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);666667 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))668 }669670 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]671 #[transactional]672 pub fn set_token_properties(673 origin,674 collection_id: CollectionId,675 token_id: TokenId,676 properties: Vec<Property>677 ) -> DispatchResultWithPostInfo {678 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);679680 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);681682 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))683 }684685 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]686 #[transactional]687 pub fn delete_token_properties(688 origin,689 collection_id: CollectionId,690 token_id: TokenId,691 property_keys: Vec<PropertyKey>692 ) -> DispatchResultWithPostInfo {693 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);694695 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696697 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))698 }699700 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]701 #[transactional]702 pub fn set_property_permissions(703 origin,704 collection_id: CollectionId,705 property_permissions: Vec<PropertyKeyPermission>,706 ) -> DispatchResultWithPostInfo {707 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);708709 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))712 }713714 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]715 #[transactional]716 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {717 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718 let budget = budget::Value::new(2);719720 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))721 }722723 // TODO! transaction weight724725 /// Set transfers_enabled value for particular collection726 ///727 /// # Permissions728 ///729 /// * Collection Owner.730 ///731 /// # Arguments732 ///733 /// * collection_id: ID of the collection.734 ///735 /// * value: New flag value.736 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]737 #[transactional]738 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {739 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;741 target_collection.check_is_owner(&sender)?;742743 // =========744745 target_collection.limits.transfers_enabled = Some(value);746 target_collection.save()747 }748749 /// Destroys a concrete instance of NFT.750 ///751 /// # Permissions752 ///753 /// * Collection Owner.754 /// * Collection Admin.755 /// * Current NFT Owner.756 ///757 /// # Arguments758 ///759 /// * collection_id: ID of the collection.760 ///761 /// * item_id: ID of NFT to burn.762 #[weight = T::CommonWeightInfo::burn_item()]763 #[transactional]764 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {765 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);766767 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;768 if value == 1 {769 <NftTransferBasket<T>>::remove(collection_id, item_id);770 <NftApproveBasket<T>>::remove(collection_id, item_id);771 }772 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?773 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());774 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));775 Ok(post_info)776 }777778 /// Destroys a concrete instance of NFT on behalf of the owner779 /// See also: [`approve`]780 ///781 /// # Permissions782 ///783 /// * Collection Owner.784 /// * Collection Admin.785 /// * Current NFT Owner.786 ///787 /// # Arguments788 ///789 /// * collection_id: ID of the collection.790 ///791 /// * item_id: ID of NFT to burn.792 ///793 /// * from: owner of item794 #[weight = T::CommonWeightInfo::burn_from()]795 #[transactional]796 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {797 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);798 let budget = budget::Value::new(2);799800 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))801 }802803 /// Change ownership of the token.804 ///805 /// # Permissions806 ///807 /// * Collection Owner808 /// * Collection Admin809 /// * Current NFT owner810 ///811 /// # Arguments812 ///813 /// * recipient: Address of token recipient.814 ///815 /// * collection_id.816 ///817 /// * item_id: ID of the item818 /// * Non-Fungible Mode: Required.819 /// * Fungible Mode: Ignored.820 /// * Re-Fungible Mode: Required.821 ///822 /// * value: Amount to transfer.823 /// * Non-Fungible Mode: Ignored824 /// * Fungible Mode: Must specify transferred amount825 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)826 #[weight = T::CommonWeightInfo::transfer()]827 #[transactional]828 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {829 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);830 let budget = budget::Value::new(2);831832 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))833 }834835 /// Set, change, or remove approved address to transfer the ownership of the NFT.836 ///837 /// # Permissions838 ///839 /// * Collection Owner840 /// * Collection Admin841 /// * Current NFT owner842 ///843 /// # Arguments844 ///845 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).846 ///847 /// * collection_id.848 ///849 /// * item_id: ID of the item.850 #[weight = T::CommonWeightInfo::approve()]851 #[transactional]852 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {853 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);854855 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))856 }857858 /// 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.859 ///860 /// # Permissions861 /// * Collection Owner862 /// * Collection Admin863 /// * Current NFT owner864 /// * Address approved by current NFT owner865 ///866 /// # Arguments867 ///868 /// * from: Address that owns token.869 ///870 /// * recipient: Address of token recipient.871 ///872 /// * collection_id.873 ///874 /// * item_id: ID of the item.875 ///876 /// * value: Amount to transfer.877 #[weight = T::CommonWeightInfo::transfer_from()]878 #[transactional]879 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {880 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881 let budget = budget::Value::new(2);882883 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))884 }885886 #[weight = <SelfWeightOf<T>>::set_collection_limits()]887 #[transactional]888 pub fn set_collection_limits(889 origin,890 collection_id: CollectionId,891 new_limit: CollectionLimits,892 ) -> DispatchResult {893 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);894 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;895 target_collection.check_is_owner(&sender)?;896 let old_limit = &target_collection.limits;897898 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;899900 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(901 collection_id902 ));903904 target_collection.save()905 }906907 #[weight = <SelfWeightOf<T>>::set_collection_limits()]908 #[transactional]909 pub fn set_collection_permissions(910 origin,911 collection_id: CollectionId,912 new_limit: CollectionPermissions,913 ) -> DispatchResult {914 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;916 target_collection.check_is_owner(&sender)?;917 let old_limit = &target_collection.permissions;918919 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;920921 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(922 collection_id923 ));924925 target_collection.save()926 }927 }928}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627use frame_support::{28 decl_module, decl_storage, decl_error, decl_event,29 dispatch::DispatchResult,30 ensure,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,41 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,42 CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,43 CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,44 PropertyKeyPermission,45};46use pallet_evm::account::CrossAccountId;47use pallet_common::{48 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,49 dispatch::CollectionDispatch,50};51pub mod eth;5253#[cfg(feature = "runtime-benchmarks")]54mod benchmarking;55pub mod weights;56use weights::WeightInfo;5758decl_error! {59 /// Error for non-fungible-token module.60 pub enum Error for Module<T: Config> {61 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.62 CollectionDecimalPointLimitExceeded,63 /// This address is not set as sponsor, use setCollectionSponsor first.64 ConfirmUnsetSponsorFail,65 /// Length of items properties must be greater than 0.66 EmptyArgument,67 }68}6970pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {71 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7273 /// Weight information for extrinsics in this pallet.74 type WeightInfo: WeightInfo;75 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;76}7778decl_event! {79 pub enum Event<T>80 where81 <T as frame_system::Config>::AccountId,82 <T as pallet_evm::account::Config>::CrossAccountId,83 {84 /// Collection sponsor was removed85 ///86 /// # Arguments87 ///88 /// * collection_id: Globally unique collection identifier.89 CollectionSponsorRemoved(CollectionId),9091 /// Collection admin was added92 ///93 /// # Arguments94 ///95 /// * collection_id: Globally unique collection identifier.96 ///97 /// * admin: Admin address.98 CollectionAdminAdded(CollectionId, CrossAccountId),99100 /// Collection owned was change101 ///102 /// # Arguments103 ///104 /// * collection_id: Globally unique collection identifier.105 ///106 /// * owner: New owner address.107 CollectionOwnedChanged(CollectionId, AccountId),108109 /// Collection sponsor was set110 ///111 /// # Arguments112 ///113 /// * collection_id: Globally unique collection identifier.114 ///115 /// * owner: New sponsor address.116 CollectionSponsorSet(CollectionId, AccountId),117118 /// New sponsor was confirm119 ///120 /// # Arguments121 ///122 /// * collection_id: Globally unique collection identifier.123 ///124 /// * sponsor: New sponsor address.125 SponsorshipConfirmed(CollectionId, AccountId),126127 /// Collection admin was removed128 ///129 /// # Arguments130 ///131 /// * collection_id: Globally unique collection identifier.132 ///133 /// * admin: Admin address.134 CollectionAdminRemoved(CollectionId, CrossAccountId),135136 /// Address was remove from allow list137 ///138 /// # Arguments139 ///140 /// * collection_id: Globally unique collection identifier.141 ///142 /// * user: Address.143 AllowListAddressRemoved(CollectionId, CrossAccountId),144145 /// Address was add to allow list146 ///147 /// # Arguments148 ///149 /// * collection_id: Globally unique collection identifier.150 ///151 /// * user: Address.152 AllowListAddressAdded(CollectionId, CrossAccountId),153154 /// Collection limits was set155 ///156 /// # Arguments157 ///158 /// * collection_id: Globally unique collection identifier.159 CollectionLimitSet(CollectionId),160161 CollectionPermissionSet(CollectionId),162 }163}164165type SelfWeightOf<T> = <T as Config>::WeightInfo;166167// # Used definitions168//169// ## User control levels170//171// chain-controlled - key is uncontrolled by user172// i.e autoincrementing index173// can use non-cryptographic hash174// real - key is controlled by user175// but it is hard to generate enough colliding values, i.e owner of signed txs176// can use non-cryptographic hash177// controlled - key is completly controlled by users178// i.e maps with mutable keys179// should use cryptographic hash180//181// ## User control level downgrade reasons182//183// ?1 - chain-controlled -> controlled184// collections/tokens can be destroyed, resulting in massive holes185// ?2 - chain-controlled -> controlled186// same as ?1, but can be only added, resulting in easier exploitation187// ?3 - real -> controlled188// no confirmation required, so addresses can be easily generated189decl_storage! {190 trait Store for Module<T: Config> as Unique {191192 //#region Private members193 /// Used for migrations194 ChainVersion: u64;195 //#endregion196197 //#region Tokens transfer rate limit baskets198 /// (Collection id (controlled?2), who created (real))199 /// TODO: Off chain worker should remove from this map when collection gets removed200 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;201 /// Collection id (controlled?2), token id (controlled?2)202 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;203 /// Collection id (controlled?2), owning user (real)204 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;205 /// Collection id (controlled?2), token id (controlled?2)206 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>;207 //#endregion208209 /// Variable metadata sponsoring210 /// Collection id (controlled?2), token id (controlled?2)211 #[deprecated]212 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;213 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;214215 /// Approval sponsoring216 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;217 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;218 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>;219 }220}221222decl_module! {223 pub struct Module<T: Config> for enum Call224 where225 origin: T::Origin226 {227 type Error = Error<T>;228229 fn deposit_event() = default;230231 fn on_initialize(_now: T::BlockNumber) -> Weight {232 0233 }234235 fn on_runtime_upgrade() -> Weight {236 let limit = None;237238 <VariableMetaDataBasket<T>>::remove_all(limit);239240 0241 }242243 /// 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.244 ///245 /// # Permissions246 ///247 /// * Anyone.248 ///249 /// # Arguments250 ///251 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.252 ///253 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.254 ///255 /// * token_prefix: UTF-8 string with token prefix.256 ///257 /// * mode: [CollectionMode] collection type and type dependent data.258 // returns collection ID259 #[weight = <SelfWeightOf<T>>::create_collection()]260 #[transactional]261 #[deprecated]262 pub fn create_collection(origin,263 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,264 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,265 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,266 mode: CollectionMode) -> DispatchResult {267 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {268 name: collection_name,269 description: collection_description,270 token_prefix,271 mode,272 ..Default::default()273 };274 Self::create_collection_ex(origin, data)275 }276277 /// This method creates a collection278 ///279 /// Prefer it to deprecated [`created_collection`] method280 #[weight = <SelfWeightOf<T>>::create_collection()]281 #[transactional]282 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {283 let sender = ensure_signed(origin)?;284285 // =========286287 T::CollectionDispatch::create(sender, data)?;288289 Ok(())290 }291292 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.293 ///294 /// # Permissions295 ///296 /// * Collection Owner.297 ///298 /// # Arguments299 ///300 /// * collection_id: collection to destroy.301 #[weight = <SelfWeightOf<T>>::destroy_collection()]302 #[transactional]303 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {304 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305 let collection = <CollectionHandle<T>>::try_get(collection_id)?;306307 // =========308309 T::CollectionDispatch::destroy(sender, collection)?;310311 <NftTransferBasket<T>>::remove_prefix(collection_id, None);312 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);313 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);314315 <NftApproveBasket<T>>::remove_prefix(collection_id, None);316 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);317 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);318319 Ok(())320 }321322 /// Add an address to allow list.323 ///324 /// # Permissions325 ///326 /// * Collection Owner327 /// * Collection Admin328 ///329 /// # Arguments330 ///331 /// * collection_id.332 ///333 /// * address.334 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]335 #[transactional]336 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{337338 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339 let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341 <PalletCommon<T>>::toggle_allowlist(342 &collection,343 &sender,344 &address,345 true,346 )?;347348 Self::deposit_event(Event::<T>::AllowListAddressAdded(349 collection_id,350 address351 ));352353 Ok(())354 }355356 /// Remove an address from allow list.357 ///358 /// # Permissions359 ///360 /// * Collection Owner361 /// * Collection Admin362 ///363 /// # Arguments364 ///365 /// * collection_id.366 ///367 /// * address.368 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]369 #[transactional]370 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{371372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);373 let collection = <CollectionHandle<T>>::try_get(collection_id)?;374375 <PalletCommon<T>>::toggle_allowlist(376 &collection,377 &sender,378 &address,379 false,380 )?;381382 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(383 collection_id,384 address385 ));386387 Ok(())388 }389390 /// Change the owner of the collection.391 ///392 /// # Permissions393 ///394 /// * Collection Owner.395 ///396 /// # Arguments397 ///398 /// * collection_id.399 ///400 /// * new_owner.401 #[weight = <SelfWeightOf<T>>::change_collection_owner()]402 #[transactional]403 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {404405 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);406407 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;408 target_collection.check_is_owner(&sender)?;409410 target_collection.owner = new_owner.clone();411 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(412 collection_id,413 new_owner414 ));415416 target_collection.save()417 }418419 /// Adds an admin of the Collection.420 /// 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.421 ///422 /// # Permissions423 ///424 /// * Collection Owner.425 /// * Collection Admin.426 ///427 /// # Arguments428 ///429 /// * collection_id: ID of the Collection to add admin for.430 ///431 /// * new_admin_id: Address of new admin to add.432 #[weight = <SelfWeightOf<T>>::add_collection_admin()]433 #[transactional]434 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {435 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436 let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(439 collection_id,440 new_admin_id.clone()441 ));442443 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)444 }445446 /// 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.447 ///448 /// # Permissions449 ///450 /// * Collection Owner.451 /// * Collection Admin.452 ///453 /// # Arguments454 ///455 /// * collection_id: ID of the Collection to remove admin for.456 ///457 /// * account_id: Address of admin to remove.458 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]459 #[transactional]460 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {461 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);462 let collection = <CollectionHandle<T>>::try_get(collection_id)?;463464 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(465 collection_id,466 account_id.clone()467 ));468469 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)470 }471472 /// # Permissions473 ///474 /// * Collection Owner475 ///476 /// # Arguments477 ///478 /// * collection_id.479 ///480 /// * new_sponsor.481 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]482 #[transactional]483 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {484 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.set_sponsor(new_sponsor.clone());490491 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(492 collection_id,493 new_sponsor494 ));495496 target_collection.save()497 }498499 /// # Permissions500 ///501 /// * Sponsor.502 ///503 /// # Arguments504 ///505 /// * collection_id.506 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]507 #[transactional]508 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {509 let sender = ensure_signed(origin)?;510511 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;512 ensure!(513 target_collection.confirm_sponsorship(&sender),514 Error::<T>::ConfirmUnsetSponsorFail515 );516517 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(518 collection_id,519 sender520 ));521522 target_collection.save()523 }524525 /// Switch back to pay-per-own-transaction model.526 ///527 /// # Permissions528 ///529 /// * Collection owner.530 ///531 /// # Arguments532 ///533 /// * collection_id.534 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]535 #[transactional]536 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {537 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);538539 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;540 target_collection.check_is_owner(&sender)?;541542 target_collection.sponsorship = SponsorshipState::Disabled;543544 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(545 collection_id546 ));547 target_collection.save()548 }549550 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.551 ///552 /// # Permissions553 ///554 /// * Collection Owner.555 /// * Collection Admin.556 /// * Anyone if557 /// * Allow List is enabled, and558 /// * Address is added to allow list, and559 /// * MintPermission is enabled (see SetMintPermission method)560 ///561 /// # Arguments562 ///563 /// * collection_id: ID of the collection.564 ///565 /// * owner: Address, initial owner of the NFT.566 ///567 /// * data: Token data to store on chain.568 #[weight = T::CommonWeightInfo::create_item()]569 #[transactional]570 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {571 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);572 let budget = budget::Value::new(2);573574 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))575 }576577 /// This method creates multiple items in a collection created with CreateCollection method.578 ///579 /// # Permissions580 ///581 /// * Collection Owner.582 /// * Collection Admin.583 /// * Anyone if584 /// * Allow List is enabled, and585 /// * Address is added to allow list, and586 /// * MintPermission is enabled (see SetMintPermission method)587 ///588 /// # Arguments589 ///590 /// * collection_id: ID of the collection.591 ///592 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].593 ///594 /// * owner: Address, initial owner of the NFT.595 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]596 #[transactional]597 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {598 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);599 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);600 let budget = budget::Value::new(2);601602 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))603 }604605 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]606 #[transactional]607 pub fn set_collection_properties(608 origin,609 collection_id: CollectionId,610 properties: Vec<Property>611 ) -> DispatchResultWithPostInfo {612 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);613614 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);615616 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))617 }618619 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]620 #[transactional]621 pub fn delete_collection_properties(622 origin,623 collection_id: CollectionId,624 property_keys: Vec<PropertyKey>,625 ) -> DispatchResultWithPostInfo {626 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);627628 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);629630 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))631 }632633 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]634 #[transactional]635 pub fn set_token_properties(636 origin,637 collection_id: CollectionId,638 token_id: TokenId,639 properties: Vec<Property>640 ) -> DispatchResultWithPostInfo {641 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);642643 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);644645 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))646 }647648 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]649 #[transactional]650 pub fn delete_token_properties(651 origin,652 collection_id: CollectionId,653 token_id: TokenId,654 property_keys: Vec<PropertyKey>655 ) -> DispatchResultWithPostInfo {656 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);657658 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);659660 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))661 }662663 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]664 #[transactional]665 pub fn set_property_permissions(666 origin,667 collection_id: CollectionId,668 property_permissions: Vec<PropertyKeyPermission>,669 ) -> DispatchResultWithPostInfo {670 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);671672 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);673674 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))675 }676677 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]678 #[transactional]679 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {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_ex(sender, data, &budget))684 }685686 // TODO! transaction weight687688 /// Set transfers_enabled value for particular collection689 ///690 /// # Permissions691 ///692 /// * Collection Owner.693 ///694 /// # Arguments695 ///696 /// * collection_id: ID of the collection.697 ///698 /// * value: New flag value.699 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]700 #[transactional]701 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {702 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);703 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;704 target_collection.check_is_owner(&sender)?;705706 // =========707708 target_collection.limits.transfers_enabled = Some(value);709 target_collection.save()710 }711712 /// Destroys a concrete instance of NFT.713 ///714 /// # Permissions715 ///716 /// * Collection Owner.717 /// * Collection Admin.718 /// * Current NFT Owner.719 ///720 /// # Arguments721 ///722 /// * collection_id: ID of the collection.723 ///724 /// * item_id: ID of NFT to burn.725 #[weight = T::CommonWeightInfo::burn_item()]726 #[transactional]727 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {728 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);729730 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;731 if value == 1 {732 <NftTransferBasket<T>>::remove(collection_id, item_id);733 <NftApproveBasket<T>>::remove(collection_id, item_id);734 }735 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?736 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());737 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));738 Ok(post_info)739 }740741 /// Destroys a concrete instance of NFT on behalf of the owner742 /// See also: [`approve`]743 ///744 /// # Permissions745 ///746 /// * Collection Owner.747 /// * Collection Admin.748 /// * Current NFT Owner.749 ///750 /// # Arguments751 ///752 /// * collection_id: ID of the collection.753 ///754 /// * item_id: ID of NFT to burn.755 ///756 /// * from: owner of item757 #[weight = T::CommonWeightInfo::burn_from()]758 #[transactional]759 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {760 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);761 let budget = budget::Value::new(2);762763 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))764 }765766 /// Change ownership of the token.767 ///768 /// # Permissions769 ///770 /// * Collection Owner771 /// * Collection Admin772 /// * Current NFT owner773 ///774 /// # Arguments775 ///776 /// * recipient: Address of token recipient.777 ///778 /// * collection_id.779 ///780 /// * item_id: ID of the item781 /// * Non-Fungible Mode: Required.782 /// * Fungible Mode: Ignored.783 /// * Re-Fungible Mode: Required.784 ///785 /// * value: Amount to transfer.786 /// * Non-Fungible Mode: Ignored787 /// * Fungible Mode: Must specify transferred amount788 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)789 #[weight = T::CommonWeightInfo::transfer()]790 #[transactional]791 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {792 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793 let budget = budget::Value::new(2);794795 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))796 }797798 /// Set, change, or remove approved address to transfer the ownership of the NFT.799 ///800 /// # Permissions801 ///802 /// * Collection Owner803 /// * Collection Admin804 /// * Current NFT owner805 ///806 /// # Arguments807 ///808 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).809 ///810 /// * collection_id.811 ///812 /// * item_id: ID of the item.813 #[weight = T::CommonWeightInfo::approve()]814 #[transactional]815 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {816 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);817818 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))819 }820821 /// 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.822 ///823 /// # Permissions824 /// * Collection Owner825 /// * Collection Admin826 /// * Current NFT owner827 /// * Address approved by current NFT owner828 ///829 /// # Arguments830 ///831 /// * from: Address that owns token.832 ///833 /// * recipient: Address of token recipient.834 ///835 /// * collection_id.836 ///837 /// * item_id: ID of the item.838 ///839 /// * value: Amount to transfer.840 #[weight = T::CommonWeightInfo::transfer_from()]841 #[transactional]842 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {843 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);844 let budget = budget::Value::new(2);845846 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))847 }848849 #[weight = <SelfWeightOf<T>>::set_collection_limits()]850 #[transactional]851 pub fn set_collection_limits(852 origin,853 collection_id: CollectionId,854 new_limit: CollectionLimits,855 ) -> DispatchResult {856 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);857 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;858 target_collection.check_is_owner(&sender)?;859 let old_limit = &target_collection.limits;860861 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;862863 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(864 collection_id865 ));866867 target_collection.save()868 }869870 #[weight = <SelfWeightOf<T>>::set_collection_limits()]871 #[transactional]872 pub fn set_collection_permissions(873 origin,874 collection_id: CollectionId,875 new_limit: CollectionPermissions,876 ) -> DispatchResult {877 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);878 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;879 target_collection.check_is_owner(&sender)?;880 let old_limit = &target_collection.permissions;881882 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;883884 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(885 collection_id886 ));887888 target_collection.save()889 }890 }891}