difftreelog
Add extrinsic: delete token property
in: master
12 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -294,6 +294,8 @@
TokenPropertySet(CollectionId, TokenId, Property),
+ TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),
+
PropertyPermissionSet(CollectionId, PropertyKeyPermission),
}
@@ -738,10 +740,9 @@
) -> DispatchResult {
collection.check_is_owner_or_admin(sender)?;
- CollectionProperties::<T>::try_mutate(
- collection.id,
- |properties| properties.try_set_property(property.clone())
- )?;
+ CollectionProperties::<T>::try_mutate(collection.id, |properties| {
+ properties.try_set_property(property.clone())
+ })?;
Self::deposit_event(Event::CollectionPropertySet(collection.id, property));
@@ -763,13 +764,16 @@
pub fn set_property_permission(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
- property_permission: PropertyKeyPermission
+ property_permission: PropertyKeyPermission,
) -> DispatchResult {
collection.check_is_owner_or_admin(sender)?;
let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
let current_permission = all_permissions.get(&property_permission.key);
- if matches![current_permission, Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)] {
+ if matches![
+ current_permission,
+ Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)
+ ] {
return Err(<Error<T>>::NoPermission.into());
}
@@ -779,7 +783,10 @@
})
.map_err(|_| PropertiesError::PropertyLimitReached)?;
- Self::deposit_event(Event::PropertyPermissionSet(collection.id, property_permission));
+ Self::deposit_event(Event::PropertyPermissionSet(
+ collection.id,
+ property_permission,
+ ));
Ok(())
}
@@ -787,7 +794,7 @@
pub fn set_property_permissions(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
- property_permissions: Vec<PropertyKeyPermission>
+ property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResult {
for prop_pemission in property_permissions {
Self::set_property_permission(collection, sender, prop_pemission)?;
@@ -950,6 +957,7 @@
fn burn_item() -> Weight;
fn set_collection_properties(amount: u32) -> Weight;
fn set_token_properties(amount: u32) -> Weight;
+ fn delete_token_properties(amount: u32) -> Weight;
fn set_property_permissions(amount: u32) -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
@@ -996,6 +1004,12 @@
token_id: TokenId,
property: Vec<Property>,
) -> DispatchResultWithPostInfo;
+ fn delete_token_properties(
+ &self,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ property_keys: Vec<PropertyKey>,
+ ) -> DispatchResultWithPostInfo;
fn set_property_permissions(
&self,
sender: &T::CrossAccountId,
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -21,7 +21,7 @@
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::ArithmeticError;
use sp_std::{vec::Vec, vec};
-use up_data_structs::{CustomDataLimit, Property, PropertyKeyPermission,};
+use up_data_structs::{CustomDataLimit, Property, PropertyKey, PropertyKeyPermission};
use crate::{
Allowance, Balance, Config, Error, FungibleHandle, Pallet, SelfWeightOf, weights::WeightInfo,
@@ -58,6 +58,10 @@
<SelfWeightOf<T>>::set_token_properties(amount)
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::delete_token_properties(amount)
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
<SelfWeightOf<T>>::set_property_permissions(amount)
}
@@ -262,6 +266,15 @@
fail!(<Error<T>>::PropertiesNotAllowed)
}
+ fn delete_token_properties(
+ &self,
+ _sender: T::CrossAccountId,
+ _token_id: TokenId,
+ _property_keys: Vec<PropertyKey>,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
fn set_variable_metadata(
&self,
_sender: T::CrossAccountId,
pallets/fungible/src/weights.rsdiffbeforeafterboth--- a/pallets/fungible/src/weights.rs
+++ b/pallets/fungible/src/weights.rs
@@ -37,6 +37,7 @@
fn burn_item() -> Weight;
fn set_collection_properties(amount: u32) -> Weight;
fn set_token_properties(amount: u32) -> Weight;
+ fn delete_token_properties(amount: u32) -> Weight;
fn set_property_permissions(amount: u32) -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
@@ -73,17 +74,22 @@
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
- fn set_collection_properties(amount: u32) -> Weight {
+ fn set_collection_properties(_amount: u32) -> Weight {
+ // Error
+ 0
+ }
+
+ fn set_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_token_properties(amount: u32) -> Weight {
+ fn delete_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_property_permissions(amount: u32) -> Weight {
+ fn set_property_permissions(_amount: u32) -> Weight {
// Error
0
}
@@ -146,17 +152,22 @@
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
- fn set_collection_properties(amount: u32) -> Weight {
+ fn set_collection_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_token_properties(amount: u32) -> Weight {
+ fn set_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_property_permissions(amount: u32) -> Weight {
+ fn delete_token_properties(_amount: u32) -> Weight {
+ // Error
+ 0
+ }
+
+ fn set_property_permissions(_amount: u32) -> Weight {
// Error
0
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -18,7 +18,8 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
use up_data_structs::{
- TokenId, CustomDataLimit, CreateItemExData, CollectionId, budget::Budget, Property, PropertyKeyPermission,
+ TokenId, CustomDataLimit, CreateItemExData, CollectionId, budget::Budget, Property,
+ PropertyKey, PropertyKeyPermission,
};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
@@ -58,6 +59,10 @@
<SelfWeightOf<T>>::set_token_properties(amount)
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::delete_token_properties(amount)
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
<SelfWeightOf<T>>::set_property_permissions(amount)
}
@@ -162,7 +167,7 @@
with_weight(
<Pallet<T>>::set_collection_properties(self, &sender, properties),
- weight
+ weight,
)
}
@@ -176,7 +181,21 @@
with_weight(
<Pallet<T>>::set_token_properties(self, &sender, token_id, properties),
- weight
+ weight,
+ )
+ }
+
+ fn delete_token_properties(
+ &self,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ property_keys: Vec<PropertyKey>,
+ ) -> DispatchResultWithPostInfo {
+ let weight = <CommonWeights<T>>::delete_token_properties(property_keys.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::delete_token_properties(self, &sender, token_id, property_keys),
+ weight,
)
}
@@ -185,11 +204,12 @@
sender: &T::CrossAccountId,
property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResultWithPostInfo {
- let weight = <CommonWeights<T>>::set_property_permissions(property_permissions.len() as u32);
+ let weight =
+ <CommonWeights<T>>::set_property_permissions(property_permissions.len() as u32);
with_weight(
<Pallet<T>>::set_property_permissions(self, sender, property_permissions),
- weight
+ weight,
)
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -21,7 +21,7 @@
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
- PropertyKeyPermission,
+ PropertyKey, PropertyKeyPermission,
};
use pallet_evm::account::CrossAccountId;
use pallet_common::{
@@ -261,8 +261,63 @@
token_id: TokenId,
property: Property,
) -> DispatchResult {
+ Self::check_token_change_permission(collection, sender, token_id, &property.key)?;
+
+ <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+ properties.try_set_property(property.clone())
+ })?;
+
+ <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
+ collection.id,
+ token_id,
+ property,
+ ));
+
+ Ok(())
+ }
+
+ pub fn set_token_properties(
+ collection: &NonfungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ properties: Vec<Property>,
+ ) -> DispatchResult {
+ for property in properties {
+ Self::set_token_property(collection, sender, token_id, property)?;
+ }
+
+ Ok(())
+ }
+
+ pub fn delete_token_property(
+ collection: &NonfungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property_key: PropertyKey,
+ ) -> DispatchResult {
+ Self::check_token_change_permission(collection, sender, token_id, &property_key)?;
+
+ <TokenProperties<T>>::mutate((collection.id, token_id), |properties| {
+ properties.remove_property(&property_key);
+ });
+
+ <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(
+ collection.id,
+ token_id,
+ property_key,
+ ));
+
+ Ok(())
+ }
+
+ fn check_token_change_permission(
+ collection: &NonfungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property_key: &PropertyKey,
+ ) -> DispatchResult {
let permission = <PalletCommon<T>>::property_permission(collection.id)
- .get(&property.key)
+ .get(property_key)
.map(|p| p.clone())
.unwrap_or(PropertyPermission::None);
@@ -275,43 +330,29 @@
};
let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))
- .get_property(&property.key)
+ .get_property(property_key)
.is_some();
match (permission, is_property_exists) {
- (PropertyPermission::AdminConst, false) => {
- collection.check_is_owner_or_admin(sender)?
- }
- (PropertyPermission::Admin, _) => collection.check_is_owner_or_admin(sender)?,
- (PropertyPermission::ItemOwnerConst, false) => check_token_owner()?,
- (PropertyPermission::ItemOwner, _) => check_token_owner()?,
+ (PropertyPermission::AdminConst, false) => collection.check_is_owner_or_admin(sender),
+ (PropertyPermission::Admin, _) => collection.check_is_owner_or_admin(sender),
+ (PropertyPermission::ItemOwnerConst, false) => check_token_owner(),
+ (PropertyPermission::ItemOwner, _) => check_token_owner(),
(PropertyPermission::ItemOwnerOrAdmin, _) => {
- check_token_owner().or(collection.check_is_owner_or_admin(sender))?;
+ check_token_owner().or(collection.check_is_owner_or_admin(sender))
}
- _ => return Err(<CommonError<T>>::NoPermission.into()),
+ _ => Err(<CommonError<T>>::NoPermission.into()),
}
-
- <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
- properties.try_set_property(property.clone())
- })?;
-
- <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
- collection.id,
- token_id,
- property,
- ));
-
- Ok(())
}
- pub fn set_token_properties(
+ pub fn delete_token_properties(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
token_id: TokenId,
- properties: Vec<Property>,
+ property_keys: Vec<PropertyKey>,
) -> DispatchResult {
- for property in properties {
- Self::set_token_property(collection, sender, token_id, property)?;
+ for key in property_keys {
+ Self::delete_token_property(collection, sender, token_id, key)?;
}
Ok(())
@@ -328,13 +369,9 @@
pub fn set_property_permissions(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
- property_permissions: Vec<PropertyKeyPermission>
+ property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResult {
- <PalletCommon<T>>::set_property_permissions(
- collection,
- sender,
- property_permissions,
- )
+ <PalletCommon<T>>::set_property_permissions(collection, sender, property_permissions)
}
pub fn transfer(
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -38,6 +38,7 @@
fn burn_item() -> Weight;
fn set_collection_properties(amount: u32) -> Weight;
fn set_token_properties(amount: u32) -> Weight;
+ fn delete_token_properties(amount: u32) -> Weight;
fn set_property_permissions(amount: u32) -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
@@ -104,6 +105,11 @@
(50_000_000 as Weight).saturating_mul(amount as Weight)
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ // TODO calculate appropriate weight
+ (50_000_000 as Weight).saturating_mul(amount as Weight)
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
// TODO calculate appropriate weight
(50_000_000 as Weight).saturating_mul(amount as Weight)
@@ -209,6 +215,11 @@
(50_000_000 as Weight).saturating_mul(amount as Weight)
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ // TODO calculate appropriate weight
+ (50_000_000 as Weight).saturating_mul(amount as Weight)
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
// TODO calculate appropriate weight
(50_000_000 as Weight).saturating_mul(amount as Weight)
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -20,7 +20,7 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, fail, weights::Weight, BoundedVec};
use up_data_structs::{
CollectionId, TokenId, CustomDataLimit, CreateItemExData, CreateRefungibleExData,
- budget::Budget, Property, PropertyKeyPermission,
+ budget::Budget, Property, PropertyKey, PropertyKeyPermission,
};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
@@ -74,6 +74,10 @@
<SelfWeightOf<T>>::set_token_properties(amount)
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::delete_token_properties(amount)
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
<SelfWeightOf<T>>::set_property_permissions(amount)
}
@@ -281,6 +285,15 @@
fail!(<Error<T>>::PropertiesNotAllowed)
}
+ fn delete_token_properties(
+ &self,
+ _sender: T::CrossAccountId,
+ _token_id: TokenId,
+ _property_keys: Vec<PropertyKey>,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
fn set_variable_metadata(
&self,
sender: T::CrossAccountId,
pallets/refungible/src/weights.rsdiffbeforeafterboth--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -40,6 +40,7 @@
fn burn_item_fully() -> Weight;
fn set_collection_properties(amount: u32) -> Weight;
fn set_token_properties(amount: u32) -> Weight;
+ fn delete_token_properties(amount: u32) -> Weight;
fn set_property_permissions(amount: u32) -> Weight;
fn transfer_normal() -> Weight;
fn transfer_creating() -> Weight;
@@ -133,17 +134,22 @@
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
- fn set_collection_properties(amount: u32) -> Weight {
+ fn set_collection_properties(_amount: u32) -> Weight {
+ // Error
+ 0
+ }
+
+ fn set_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_token_properties(amount: u32) -> Weight {
+ fn delete_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_property_permissions(amount: u32) -> Weight {
+ fn set_property_permissions(_amount: u32) -> Weight {
// Error
0
}
@@ -317,17 +323,22 @@
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
- fn set_collection_properties(amount: u32) -> Weight {
+ fn set_collection_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_token_properties(amount: u32) -> Weight {
+ fn set_token_properties(_amount: u32) -> Weight {
// Error
0
}
- fn set_property_permissions(amount: u32) -> Weight {
+ fn delete_token_properties(_amount: u32) -> Weight {
+ // Error
+ 0
+ }
+
+ fn set_property_permissions(_amount: u32) -> Weight {
// Error
0
}
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 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, 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, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,42 CreateItemExData, budget, CollectionField, Property, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, Error as CommonError, 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),194195 /// Variable on chain schema was set196 ///197 /// # Arguments198 ///199 /// * collection_id: Globally unique collection identifier.200 VariableOnChainSchemaSet(CollectionId),201 }202}203204type SelfWeightOf<T> = <T as Config>::WeightInfo;205206// # Used definitions207//208// ## User control levels209//210// chain-controlled - key is uncontrolled by user211// i.e autoincrementing index212// can use non-cryptographic hash213// real - key is controlled by user214// but it is hard to generate enough colliding values, i.e owner of signed txs215// can use non-cryptographic hash216// controlled - key is completly controlled by users217// i.e maps with mutable keys218// should use cryptographic hash219//220// ## User control level downgrade reasons221//222// ?1 - chain-controlled -> controlled223// collections/tokens can be destroyed, resulting in massive holes224// ?2 - chain-controlled -> controlled225// same as ?1, but can be only added, resulting in easier exploitation226// ?3 - real -> controlled227// no confirmation required, so addresses can be easily generated228decl_storage! {229 trait Store for Module<T: Config> as Unique {230231 //#region Private members232 /// Used for migrations233 ChainVersion: u64;234 //#endregion235236 //#region Tokens transfer rate limit baskets237 /// (Collection id (controlled?2), who created (real))238 /// TODO: Off chain worker should remove from this map when collection gets removed239 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;240 /// Collection id (controlled?2), token id (controlled?2)241 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;242 /// Collection id (controlled?2), owning user (real)243 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244 /// Collection id (controlled?2), token id (controlled?2)245 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>;246 //#endregion247248 /// Variable metadata sponsoring249 /// Collection id (controlled?2), token id (controlled?2)250 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251 /// Approval sponsoring252 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;253 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 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>;255 }256}257258decl_module! {259 pub struct Module<T: Config> for enum Call260 where261 origin: T::Origin262 {263 type Error = Error<T>;264265 fn deposit_event() = default;266267 fn on_initialize(_now: T::BlockNumber) -> Weight {268 0269 }270271 /// 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.272 ///273 /// # Permissions274 ///275 /// * Anyone.276 ///277 /// # Arguments278 ///279 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.280 ///281 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.282 ///283 /// * token_prefix: UTF-8 string with token prefix.284 ///285 /// * mode: [CollectionMode] collection type and type dependent data.286 // returns collection ID287 #[weight = <SelfWeightOf<T>>::create_collection()]288 #[transactional]289 #[deprecated]290 pub fn create_collection(origin,291 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,292 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,293 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,294 mode: CollectionMode) -> DispatchResult {295 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {296 name: collection_name,297 description: collection_description,298 token_prefix,299 mode,300 ..Default::default()301 };302 Self::create_collection_ex(origin, data)303 }304305 /// This method creates a collection306 ///307 /// Prefer it to deprecated [`created_collection`] method308 #[weight = <SelfWeightOf<T>>::create_collection()]309 #[transactional]310 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {311 let sender = ensure_signed(origin)?;312313 // =========314315 T::CollectionDispatch::create(sender, data)?;316317 Ok(())318 }319320 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.321 ///322 /// # Permissions323 ///324 /// * Collection Owner.325 ///326 /// # Arguments327 ///328 /// * collection_id: collection to destroy.329 #[weight = <SelfWeightOf<T>>::destroy_collection()]330 #[transactional]331 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {332 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333 let collection = <CollectionHandle<T>>::try_get(collection_id)?;334335 // =========336337 T::CollectionDispatch::destroy(sender, collection)?;338339 <NftTransferBasket<T>>::remove_prefix(collection_id, None);340 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);341 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);342343 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);344 <NftApproveBasket<T>>::remove_prefix(collection_id, None);345 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);346 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);347348 Ok(())349 }350351 /// Add an address to allow list.352 ///353 /// # Permissions354 ///355 /// * Collection Owner356 /// * Collection Admin357 ///358 /// # Arguments359 ///360 /// * collection_id.361 ///362 /// * address.363 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]364 #[transactional]365 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{366367 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);368 let collection = <CollectionHandle<T>>::try_get(collection_id)?;369370 <PalletCommon<T>>::toggle_allowlist(371 &collection,372 &sender,373 &address,374 true,375 )?;376377 Self::deposit_event(Event::<T>::AllowListAddressAdded(378 collection_id,379 address380 ));381382 Ok(())383 }384385 /// Remove an address from allow list.386 ///387 /// # Permissions388 ///389 /// * Collection Owner390 /// * Collection Admin391 ///392 /// # Arguments393 ///394 /// * collection_id.395 ///396 /// * address.397 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]398 #[transactional]399 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{400401 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);402 let collection = <CollectionHandle<T>>::try_get(collection_id)?;403404 <PalletCommon<T>>::toggle_allowlist(405 &collection,406 &sender,407 &address,408 false,409 )?;410411 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(412 collection_id,413 address414 ));415416 Ok(())417 }418419 /// Toggle between normal and allow list access for the methods with access for `Anyone`.420 ///421 /// # Permissions422 ///423 /// * Collection Owner.424 ///425 /// # Arguments426 ///427 /// * collection_id.428 ///429 /// * mode: [AccessMode]430 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]431 #[transactional]432 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult433 {434 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);435436 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;437 target_collection.check_is_owner(&sender)?;438439 target_collection.access = mode.clone();440441 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(442 collection_id,443 mode444 ));445446 target_collection.save()447 }448449 /// Allows Anyone to create tokens if:450 /// * Allow List is enabled, and451 /// * Address is added to allow list, and452 /// * This method was called with True parameter453 ///454 /// # Permissions455 /// * Collection Owner456 ///457 /// # Arguments458 ///459 /// * collection_id.460 ///461 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.462 #[weight = <SelfWeightOf<T>>::set_mint_permission()]463 #[transactional]464 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult465 {466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.mint_mode = mint_permission;472473 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(474 collection_id475 ));476477 target_collection.save()478 }479480 /// Change the owner of the collection.481 ///482 /// # Permissions483 ///484 /// * Collection Owner.485 ///486 /// # Arguments487 ///488 /// * collection_id.489 ///490 /// * new_owner.491 #[weight = <SelfWeightOf<T>>::change_collection_owner()]492 #[transactional]493 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {494495 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496497 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;498 target_collection.check_is_owner(&sender)?;499500 target_collection.owner = new_owner.clone();501 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(502 collection_id,503 new_owner504 ));505506 target_collection.save()507 }508509 /// Adds an admin of the Collection.510 /// 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.511 ///512 /// # Permissions513 ///514 /// * Collection Owner.515 /// * Collection Admin.516 ///517 /// # Arguments518 ///519 /// * collection_id: ID of the Collection to add admin for.520 ///521 /// * new_admin_id: Address of new admin to add.522 #[weight = <SelfWeightOf<T>>::add_collection_admin()]523 #[transactional]524 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {525 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);526 let collection = <CollectionHandle<T>>::try_get(collection_id)?;527528 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(529 collection_id,530 new_admin_id.clone()531 ));532533 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)534 }535536 /// 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.537 ///538 /// # Permissions539 ///540 /// * Collection Owner.541 /// * Collection Admin.542 ///543 /// # Arguments544 ///545 /// * collection_id: ID of the Collection to remove admin for.546 ///547 /// * account_id: Address of admin to remove.548 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]549 #[transactional]550 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552 let collection = <CollectionHandle<T>>::try_get(collection_id)?;553554 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(555 collection_id,556 account_id.clone()557 ));558559 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)560 }561562 /// # Permissions563 ///564 /// * Collection Owner565 ///566 /// # Arguments567 ///568 /// * collection_id.569 ///570 /// * new_sponsor.571 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]572 #[transactional]573 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> 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::Unconfirmed(new_sponsor.clone());580581 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(582 collection_id,583 new_sponsor584 ));585586 target_collection.save()587 }588589 /// # Permissions590 ///591 /// * Sponsor.592 ///593 /// # Arguments594 ///595 /// * collection_id.596 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]597 #[transactional]598 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {599 let sender = ensure_signed(origin)?;600601 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602 ensure!(603 target_collection.sponsorship.pending_sponsor() == Some(&sender),604 Error::<T>::ConfirmUnsetSponsorFail605 );606607 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());608609 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(610 collection_id,611 sender612 ));613614 target_collection.save()615 }616617 /// Switch back to pay-per-own-transaction model.618 ///619 /// # Permissions620 ///621 /// * Collection owner.622 ///623 /// # Arguments624 ///625 /// * collection_id.626 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]627 #[transactional]628 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {629 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;632 target_collection.check_is_owner(&sender)?;633634 target_collection.sponsorship = SponsorshipState::Disabled;635636 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(637 collection_id638 ));639 target_collection.save()640 }641642 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.643 ///644 /// # Permissions645 ///646 /// * Collection Owner.647 /// * Collection Admin.648 /// * Anyone if649 /// * Allow List is enabled, and650 /// * Address is added to allow list, and651 /// * MintPermission is enabled (see SetMintPermission method)652 ///653 /// # Arguments654 ///655 /// * collection_id: ID of the collection.656 ///657 /// * owner: Address, initial owner of the NFT.658 ///659 /// * data: Token data to store on chain.660 #[weight = T::CommonWeightInfo::create_item()]661 #[transactional]662 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664 let budget = budget::Value::new(2);665666 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))667 }668669 /// This method creates multiple items in a collection created with CreateCollection method.670 ///671 /// # Permissions672 ///673 /// * Collection Owner.674 /// * Collection Admin.675 /// * Anyone if676 /// * Allow List is enabled, and677 /// * Address is added to allow list, and678 /// * MintPermission is enabled (see SetMintPermission method)679 ///680 /// # Arguments681 ///682 /// * collection_id: ID of the collection.683 ///684 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].685 ///686 /// * owner: Address, initial owner of the NFT.687 #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]688 #[transactional]689 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {690 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);691 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);692 let budget = budget::Value::new(2);693694 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))695 }696697 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]698 #[transactional]699 pub fn set_collection_properties(700 origin,701 collection_id: CollectionId,702 properties: Vec<Property>703 ) -> DispatchResultWithPostInfo {704 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);705706 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707708 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))709 }710711 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]712 #[transactional]713 pub fn set_token_properties(714 origin,715 collection_id: CollectionId,716 token_id: TokenId,717 properties: Vec<Property>718 ) -> DispatchResultWithPostInfo {719 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);720721 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);722723 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))724 }725726 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]727 #[transactional]728 pub fn set_property_permissions(729 origin,730 collection_id: CollectionId,731 property_permissions: Vec<PropertyKeyPermission>,732 ) -> DispatchResultWithPostInfo {733 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);734735 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736737 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))738 }739740 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]741 #[transactional]742 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {743 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);744 let budget = budget::Value::new(2);745746 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))747 }748749 // TODO! transaction weight750751 /// Set transfers_enabled value for particular collection752 ///753 /// # Permissions754 ///755 /// * Collection Owner.756 ///757 /// # Arguments758 ///759 /// * collection_id: ID of the collection.760 ///761 /// * value: New flag value.762 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]763 #[transactional]764 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {765 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);766 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;767 target_collection.check_is_owner(&sender)?;768769 // =========770771 target_collection.limits.transfers_enabled = Some(value);772 target_collection.save()773 }774775 /// Destroys a concrete instance of NFT.776 ///777 /// # Permissions778 ///779 /// * Collection Owner.780 /// * Collection Admin.781 /// * Current NFT Owner.782 ///783 /// # Arguments784 ///785 /// * collection_id: ID of the collection.786 ///787 /// * item_id: ID of NFT to burn.788 #[weight = T::CommonWeightInfo::burn_item()]789 #[transactional]790 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {791 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);792793 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;794 if value == 1 {795 <NftTransferBasket<T>>::remove(collection_id, item_id);796 <NftApproveBasket<T>>::remove(collection_id, item_id);797 }798 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?799 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());800 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));801 Ok(post_info)802 }803804 /// Destroys a concrete instance of NFT on behalf of the owner805 /// See also: [`approve`]806 ///807 /// # Permissions808 ///809 /// * Collection Owner.810 /// * Collection Admin.811 /// * Current NFT Owner.812 ///813 /// # Arguments814 ///815 /// * collection_id: ID of the collection.816 ///817 /// * item_id: ID of NFT to burn.818 ///819 /// * from: owner of item820 #[weight = T::CommonWeightInfo::burn_from()]821 #[transactional]822 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {823 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824 let budget = budget::Value::new(2);825826 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))827 }828829 /// Change ownership of the token.830 ///831 /// # Permissions832 ///833 /// * Collection Owner834 /// * Collection Admin835 /// * Current NFT owner836 ///837 /// # Arguments838 ///839 /// * recipient: Address of token recipient.840 ///841 /// * collection_id.842 ///843 /// * item_id: ID of the item844 /// * Non-Fungible Mode: Required.845 /// * Fungible Mode: Ignored.846 /// * Re-Fungible Mode: Required.847 ///848 /// * value: Amount to transfer.849 /// * Non-Fungible Mode: Ignored850 /// * Fungible Mode: Must specify transferred amount851 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)852 #[weight = T::CommonWeightInfo::transfer()]853 #[transactional]854 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {855 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);856 let budget = budget::Value::new(2);857858 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))859 }860861 /// Set, change, or remove approved address to transfer the ownership of the NFT.862 ///863 /// # Permissions864 ///865 /// * Collection Owner866 /// * Collection Admin867 /// * Current NFT owner868 ///869 /// # Arguments870 ///871 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).872 ///873 /// * collection_id.874 ///875 /// * item_id: ID of the item.876 #[weight = T::CommonWeightInfo::approve()]877 #[transactional]878 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {879 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);880881 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))882 }883884 /// 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.885 ///886 /// # Permissions887 /// * Collection Owner888 /// * Collection Admin889 /// * Current NFT owner890 /// * Address approved by current NFT owner891 ///892 /// # Arguments893 ///894 /// * from: Address that owns token.895 ///896 /// * recipient: Address of token recipient.897 ///898 /// * collection_id.899 ///900 /// * item_id: ID of the item.901 ///902 /// * value: Amount to transfer.903 #[weight = T::CommonWeightInfo::transfer_from()]904 #[transactional]905 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {906 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);907 let budget = budget::Value::new(2);908909 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))910 }911912 /// Set off-chain data schema.913 ///914 /// # Permissions915 ///916 /// * Collection Owner917 /// * Collection Admin918 ///919 /// # Arguments920 ///921 /// * collection_id.922 ///923 /// * schema: String representing the offchain data schema.924 #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]925 #[transactional]926 pub fn set_variable_meta_data (927 origin,928 collection_id: CollectionId,929 item_id: TokenId,930 data: BoundedVec<u8, CustomDataLimit>,931 ) -> DispatchResultWithPostInfo {932 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);933934 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))935 }936937 /// Set meta_update_permission value for particular collection938 ///939 /// # Permissions940 ///941 /// * Collection Owner.942 ///943 /// # Arguments944 ///945 /// * collection_id: ID of the collection.946 ///947 /// * value: New flag value.948 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]949 #[transactional]950 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {951 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;953954 ensure!(955 target_collection.meta_update_permission != MetaUpdatePermission::None,956 <CommonError<T>>::MetadataFlagFrozen,957 );958 target_collection.check_is_owner(&sender)?;959960 target_collection.meta_update_permission = value;961962 target_collection.save()963 }964965 /// Set schema standard966 /// ImageURL967 /// Unique968 ///969 /// # Permissions970 ///971 /// * Collection Owner972 /// * Collection Admin973 ///974 /// # Arguments975 ///976 /// * collection_id.977 ///978 /// * schema: SchemaVersion: enum979 #[weight = <SelfWeightOf<T>>::set_schema_version()]980 #[transactional]981 pub fn set_schema_version(982 origin,983 collection_id: CollectionId,984 version: SchemaVersion985 ) -> DispatchResult {986 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);987 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;988 target_collection.check_is_owner_or_admin(&sender)?;989 target_collection.schema_version = version;990991 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(992 collection_id993 ));994995 target_collection.save()996 }997998 /// Set off-chain data schema.999 ///1000 /// # Permissions1001 ///1002 /// * Collection Owner1003 /// * Collection Admin1004 ///1005 /// # Arguments1006 ///1007 /// * collection_id.1008 ///1009 /// * schema: String representing the offchain data schema.1010 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1011 #[transactional]1012 pub fn set_offchain_schema(1013 origin,1014 collection_id: CollectionId,1015 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1016 ) -> DispatchResult {1017 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1018 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10191020 // =========10211022 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10231024 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1025 collection_id1026 ));1027 Ok(())1028 }10291030 /// Set const on-chain data schema.1031 ///1032 /// # Permissions1033 ///1034 /// * Collection Owner1035 /// * Collection Admin1036 ///1037 /// # Arguments1038 ///1039 /// * collection_id.1040 ///1041 /// * schema: String representing the const on-chain data schema.1042 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1043 #[transactional]1044 pub fn set_const_on_chain_schema (1045 origin,1046 collection_id: CollectionId,1047 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1048 ) -> DispatchResult {1049 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1050 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10511052 // =========10531054 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10551056 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1057 collection_id1058 ));1059 Ok(())1060 }10611062 /// Set variable on-chain data schema.1063 ///1064 /// # Permissions1065 ///1066 /// * Collection Owner1067 /// * Collection Admin1068 ///1069 /// # Arguments1070 ///1071 /// * collection_id.1072 ///1073 /// * schema: String representing the variable on-chain data schema.1074 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1075 #[transactional]1076 pub fn set_variable_on_chain_schema (1077 origin,1078 collection_id: CollectionId,1079 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1080 ) -> DispatchResult {1081 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1082 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10831084 // =========10851086 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;10871088 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1089 collection_id1090 ));1091 Ok(())1092 }10931094 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1095 #[transactional]1096 pub fn set_collection_limits(1097 origin,1098 collection_id: CollectionId,1099 new_limit: CollectionLimits,1100 ) -> DispatchResult {1101 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1102 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1103 target_collection.check_is_owner(&sender)?;1104 let old_limit = &target_collection.limits;11051106 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11071108 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1109 collection_id1110 ));11111112 target_collection.save()1113 }1114 }1115}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 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, 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, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,42 CreateItemExData, budget, CollectionField, Property, PropertyKey, PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, Error as CommonError, 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),194195 /// Variable on chain schema was set196 ///197 /// # Arguments198 ///199 /// * collection_id: Globally unique collection identifier.200 VariableOnChainSchemaSet(CollectionId),201 }202}203204type SelfWeightOf<T> = <T as Config>::WeightInfo;205206// # Used definitions207//208// ## User control levels209//210// chain-controlled - key is uncontrolled by user211// i.e autoincrementing index212// can use non-cryptographic hash213// real - key is controlled by user214// but it is hard to generate enough colliding values, i.e owner of signed txs215// can use non-cryptographic hash216// controlled - key is completly controlled by users217// i.e maps with mutable keys218// should use cryptographic hash219//220// ## User control level downgrade reasons221//222// ?1 - chain-controlled -> controlled223// collections/tokens can be destroyed, resulting in massive holes224// ?2 - chain-controlled -> controlled225// same as ?1, but can be only added, resulting in easier exploitation226// ?3 - real -> controlled227// no confirmation required, so addresses can be easily generated228decl_storage! {229 trait Store for Module<T: Config> as Unique {230231 //#region Private members232 /// Used for migrations233 ChainVersion: u64;234 //#endregion235236 //#region Tokens transfer rate limit baskets237 /// (Collection id (controlled?2), who created (real))238 /// TODO: Off chain worker should remove from this map when collection gets removed239 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;240 /// Collection id (controlled?2), token id (controlled?2)241 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;242 /// Collection id (controlled?2), owning user (real)243 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;244 /// Collection id (controlled?2), token id (controlled?2)245 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>;246 //#endregion247248 /// Variable metadata sponsoring249 /// Collection id (controlled?2), token id (controlled?2)250 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;251 /// Approval sponsoring252 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;253 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 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>;255 }256}257258decl_module! {259 pub struct Module<T: Config> for enum Call260 where261 origin: T::Origin262 {263 type Error = Error<T>;264265 fn deposit_event() = default;266267 fn on_initialize(_now: T::BlockNumber) -> Weight {268 0269 }270271 /// 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.272 ///273 /// # Permissions274 ///275 /// * Anyone.276 ///277 /// # Arguments278 ///279 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.280 ///281 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.282 ///283 /// * token_prefix: UTF-8 string with token prefix.284 ///285 /// * mode: [CollectionMode] collection type and type dependent data.286 // returns collection ID287 #[weight = <SelfWeightOf<T>>::create_collection()]288 #[transactional]289 #[deprecated]290 pub fn create_collection(origin,291 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,292 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,293 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,294 mode: CollectionMode) -> DispatchResult {295 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {296 name: collection_name,297 description: collection_description,298 token_prefix,299 mode,300 ..Default::default()301 };302 Self::create_collection_ex(origin, data)303 }304305 /// This method creates a collection306 ///307 /// Prefer it to deprecated [`created_collection`] method308 #[weight = <SelfWeightOf<T>>::create_collection()]309 #[transactional]310 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {311 let sender = ensure_signed(origin)?;312313 // =========314315 T::CollectionDispatch::create(sender, data)?;316317 Ok(())318 }319320 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.321 ///322 /// # Permissions323 ///324 /// * Collection Owner.325 ///326 /// # Arguments327 ///328 /// * collection_id: collection to destroy.329 #[weight = <SelfWeightOf<T>>::destroy_collection()]330 #[transactional]331 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {332 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333 let collection = <CollectionHandle<T>>::try_get(collection_id)?;334335 // =========336337 T::CollectionDispatch::destroy(sender, collection)?;338339 <NftTransferBasket<T>>::remove_prefix(collection_id, None);340 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);341 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);342343 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);344 <NftApproveBasket<T>>::remove_prefix(collection_id, None);345 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);346 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);347348 Ok(())349 }350351 /// Add an address to allow list.352 ///353 /// # Permissions354 ///355 /// * Collection Owner356 /// * Collection Admin357 ///358 /// # Arguments359 ///360 /// * collection_id.361 ///362 /// * address.363 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]364 #[transactional]365 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{366367 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);368 let collection = <CollectionHandle<T>>::try_get(collection_id)?;369370 <PalletCommon<T>>::toggle_allowlist(371 &collection,372 &sender,373 &address,374 true,375 )?;376377 Self::deposit_event(Event::<T>::AllowListAddressAdded(378 collection_id,379 address380 ));381382 Ok(())383 }384385 /// Remove an address from allow list.386 ///387 /// # Permissions388 ///389 /// * Collection Owner390 /// * Collection Admin391 ///392 /// # Arguments393 ///394 /// * collection_id.395 ///396 /// * address.397 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]398 #[transactional]399 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{400401 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);402 let collection = <CollectionHandle<T>>::try_get(collection_id)?;403404 <PalletCommon<T>>::toggle_allowlist(405 &collection,406 &sender,407 &address,408 false,409 )?;410411 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(412 collection_id,413 address414 ));415416 Ok(())417 }418419 /// Toggle between normal and allow list access for the methods with access for `Anyone`.420 ///421 /// # Permissions422 ///423 /// * Collection Owner.424 ///425 /// # Arguments426 ///427 /// * collection_id.428 ///429 /// * mode: [AccessMode]430 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]431 #[transactional]432 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult433 {434 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);435436 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;437 target_collection.check_is_owner(&sender)?;438439 target_collection.access = mode.clone();440441 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(442 collection_id,443 mode444 ));445446 target_collection.save()447 }448449 /// Allows Anyone to create tokens if:450 /// * Allow List is enabled, and451 /// * Address is added to allow list, and452 /// * This method was called with True parameter453 ///454 /// # Permissions455 /// * Collection Owner456 ///457 /// # Arguments458 ///459 /// * collection_id.460 ///461 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.462 #[weight = <SelfWeightOf<T>>::set_mint_permission()]463 #[transactional]464 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult465 {466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.mint_mode = mint_permission;472473 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(474 collection_id475 ));476477 target_collection.save()478 }479480 /// Change the owner of the collection.481 ///482 /// # Permissions483 ///484 /// * Collection Owner.485 ///486 /// # Arguments487 ///488 /// * collection_id.489 ///490 /// * new_owner.491 #[weight = <SelfWeightOf<T>>::change_collection_owner()]492 #[transactional]493 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {494495 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);496497 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;498 target_collection.check_is_owner(&sender)?;499500 target_collection.owner = new_owner.clone();501 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(502 collection_id,503 new_owner504 ));505506 target_collection.save()507 }508509 /// Adds an admin of the Collection.510 /// 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.511 ///512 /// # Permissions513 ///514 /// * Collection Owner.515 /// * Collection Admin.516 ///517 /// # Arguments518 ///519 /// * collection_id: ID of the Collection to add admin for.520 ///521 /// * new_admin_id: Address of new admin to add.522 #[weight = <SelfWeightOf<T>>::add_collection_admin()]523 #[transactional]524 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {525 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);526 let collection = <CollectionHandle<T>>::try_get(collection_id)?;527528 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(529 collection_id,530 new_admin_id.clone()531 ));532533 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)534 }535536 /// 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.537 ///538 /// # Permissions539 ///540 /// * Collection Owner.541 /// * Collection Admin.542 ///543 /// # Arguments544 ///545 /// * collection_id: ID of the Collection to remove admin for.546 ///547 /// * account_id: Address of admin to remove.548 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]549 #[transactional]550 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552 let collection = <CollectionHandle<T>>::try_get(collection_id)?;553554 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(555 collection_id,556 account_id.clone()557 ));558559 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)560 }561562 /// # Permissions563 ///564 /// * Collection Owner565 ///566 /// # Arguments567 ///568 /// * collection_id.569 ///570 /// * new_sponsor.571 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]572 #[transactional]573 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> 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::Unconfirmed(new_sponsor.clone());580581 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(582 collection_id,583 new_sponsor584 ));585586 target_collection.save()587 }588589 /// # Permissions590 ///591 /// * Sponsor.592 ///593 /// # Arguments594 ///595 /// * collection_id.596 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]597 #[transactional]598 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {599 let sender = ensure_signed(origin)?;600601 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;602 ensure!(603 target_collection.sponsorship.pending_sponsor() == Some(&sender),604 Error::<T>::ConfirmUnsetSponsorFail605 );606607 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());608609 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(610 collection_id,611 sender612 ));613614 target_collection.save()615 }616617 /// Switch back to pay-per-own-transaction model.618 ///619 /// # Permissions620 ///621 /// * Collection owner.622 ///623 /// # Arguments624 ///625 /// * collection_id.626 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]627 #[transactional]628 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {629 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;632 target_collection.check_is_owner(&sender)?;633634 target_collection.sponsorship = SponsorshipState::Disabled;635636 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(637 collection_id638 ));639 target_collection.save()640 }641642 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.643 ///644 /// # Permissions645 ///646 /// * Collection Owner.647 /// * Collection Admin.648 /// * Anyone if649 /// * Allow List is enabled, and650 /// * Address is added to allow list, and651 /// * MintPermission is enabled (see SetMintPermission method)652 ///653 /// # Arguments654 ///655 /// * collection_id: ID of the collection.656 ///657 /// * owner: Address, initial owner of the NFT.658 ///659 /// * data: Token data to store on chain.660 #[weight = T::CommonWeightInfo::create_item()]661 #[transactional]662 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664 let budget = budget::Value::new(2);665666 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))667 }668669 /// This method creates multiple items in a collection created with CreateCollection method.670 ///671 /// # Permissions672 ///673 /// * Collection Owner.674 /// * Collection Admin.675 /// * Anyone if676 /// * Allow List is enabled, and677 /// * Address is added to allow list, and678 /// * MintPermission is enabled (see SetMintPermission method)679 ///680 /// # Arguments681 ///682 /// * collection_id: ID of the collection.683 ///684 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].685 ///686 /// * owner: Address, initial owner of the NFT.687 #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]688 #[transactional]689 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {690 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);691 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);692 let budget = budget::Value::new(2);693694 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))695 }696697 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]698 #[transactional]699 pub fn set_collection_properties(700 origin,701 collection_id: CollectionId,702 properties: Vec<Property>703 ) -> DispatchResultWithPostInfo {704 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);705706 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707708 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))709 }710711 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]712 #[transactional]713 pub fn set_token_properties(714 origin,715 collection_id: CollectionId,716 token_id: TokenId,717 properties: Vec<Property>718 ) -> DispatchResultWithPostInfo {719 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);720721 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);722723 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))724 }725726 #[weight = T::CommonWeightInfo::delete_token_properties(properties.len() as u32)]727 #[transactional]728 pub fn delete_token_properties(729 origin,730 collection_id: CollectionId,731 token_id: TokenId,732 properties: Vec<PropertyKey>733 ) -> DispatchResultWithPostInfo {734 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);735736 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);737738 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, properties))739 }740741 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]742 #[transactional]743 pub fn set_property_permissions(744 origin,745 collection_id: CollectionId,746 property_permissions: Vec<PropertyKeyPermission>,747 ) -> DispatchResultWithPostInfo {748 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);749750 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751752 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))753 }754755 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]756 #[transactional]757 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {758 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);759 let budget = budget::Value::new(2);760761 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))762 }763764 // TODO! transaction weight765766 /// Set transfers_enabled value for particular collection767 ///768 /// # Permissions769 ///770 /// * Collection Owner.771 ///772 /// # Arguments773 ///774 /// * collection_id: ID of the collection.775 ///776 /// * value: New flag value.777 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]778 #[transactional]779 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;782 target_collection.check_is_owner(&sender)?;783784 // =========785786 target_collection.limits.transfers_enabled = Some(value);787 target_collection.save()788 }789790 /// Destroys a concrete instance of NFT.791 ///792 /// # Permissions793 ///794 /// * Collection Owner.795 /// * Collection Admin.796 /// * Current NFT Owner.797 ///798 /// # Arguments799 ///800 /// * collection_id: ID of the collection.801 ///802 /// * item_id: ID of NFT to burn.803 #[weight = T::CommonWeightInfo::burn_item()]804 #[transactional]805 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {806 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);807808 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;809 if value == 1 {810 <NftTransferBasket<T>>::remove(collection_id, item_id);811 <NftApproveBasket<T>>::remove(collection_id, item_id);812 }813 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?814 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());815 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));816 Ok(post_info)817 }818819 /// Destroys a concrete instance of NFT on behalf of the owner820 /// See also: [`approve`]821 ///822 /// # Permissions823 ///824 /// * Collection Owner.825 /// * Collection Admin.826 /// * Current NFT Owner.827 ///828 /// # Arguments829 ///830 /// * collection_id: ID of the collection.831 ///832 /// * item_id: ID of NFT to burn.833 ///834 /// * from: owner of item835 #[weight = T::CommonWeightInfo::burn_from()]836 #[transactional]837 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {838 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);839 let budget = budget::Value::new(2);840841 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))842 }843844 /// Change ownership of the token.845 ///846 /// # Permissions847 ///848 /// * Collection Owner849 /// * Collection Admin850 /// * Current NFT owner851 ///852 /// # Arguments853 ///854 /// * recipient: Address of token recipient.855 ///856 /// * collection_id.857 ///858 /// * item_id: ID of the item859 /// * Non-Fungible Mode: Required.860 /// * Fungible Mode: Ignored.861 /// * Re-Fungible Mode: Required.862 ///863 /// * value: Amount to transfer.864 /// * Non-Fungible Mode: Ignored865 /// * Fungible Mode: Must specify transferred amount866 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)867 #[weight = T::CommonWeightInfo::transfer()]868 #[transactional]869 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {870 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871 let budget = budget::Value::new(2);872873 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))874 }875876 /// Set, change, or remove approved address to transfer the ownership of the NFT.877 ///878 /// # Permissions879 ///880 /// * Collection Owner881 /// * Collection Admin882 /// * Current NFT owner883 ///884 /// # Arguments885 ///886 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).887 ///888 /// * collection_id.889 ///890 /// * item_id: ID of the item.891 #[weight = T::CommonWeightInfo::approve()]892 #[transactional]893 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {894 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);895896 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))897 }898899 /// 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.900 ///901 /// # Permissions902 /// * Collection Owner903 /// * Collection Admin904 /// * Current NFT owner905 /// * Address approved by current NFT owner906 ///907 /// # Arguments908 ///909 /// * from: Address that owns token.910 ///911 /// * recipient: Address of token recipient.912 ///913 /// * collection_id.914 ///915 /// * item_id: ID of the item.916 ///917 /// * value: Amount to transfer.918 #[weight = T::CommonWeightInfo::transfer_from()]919 #[transactional]920 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {921 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);922 let budget = budget::Value::new(2);923924 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))925 }926927 /// Set off-chain data schema.928 ///929 /// # Permissions930 ///931 /// * Collection Owner932 /// * Collection Admin933 ///934 /// # Arguments935 ///936 /// * collection_id.937 ///938 /// * schema: String representing the offchain data schema.939 #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]940 #[transactional]941 pub fn set_variable_meta_data (942 origin,943 collection_id: CollectionId,944 item_id: TokenId,945 data: BoundedVec<u8, CustomDataLimit>,946 ) -> DispatchResultWithPostInfo {947 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948949 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))950 }951952 /// Set meta_update_permission value for particular collection953 ///954 /// # Permissions955 ///956 /// * Collection Owner.957 ///958 /// # Arguments959 ///960 /// * collection_id: ID of the collection.961 ///962 /// * value: New flag value.963 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]964 #[transactional]965 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {966 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);967 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;968969 ensure!(970 target_collection.meta_update_permission != MetaUpdatePermission::None,971 <CommonError<T>>::MetadataFlagFrozen,972 );973 target_collection.check_is_owner(&sender)?;974975 target_collection.meta_update_permission = value;976977 target_collection.save()978 }979980 /// Set schema standard981 /// ImageURL982 /// Unique983 ///984 /// # Permissions985 ///986 /// * Collection Owner987 /// * Collection Admin988 ///989 /// # Arguments990 ///991 /// * collection_id.992 ///993 /// * schema: SchemaVersion: enum994 #[weight = <SelfWeightOf<T>>::set_schema_version()]995 #[transactional]996 pub fn set_schema_version(997 origin,998 collection_id: CollectionId,999 version: SchemaVersion1000 ) -> DispatchResult {1001 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1002 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1003 target_collection.check_is_owner_or_admin(&sender)?;1004 target_collection.schema_version = version;10051006 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(1007 collection_id1008 ));10091010 target_collection.save()1011 }10121013 /// Set off-chain data schema.1014 ///1015 /// # Permissions1016 ///1017 /// * Collection Owner1018 /// * Collection Admin1019 ///1020 /// # Arguments1021 ///1022 /// * collection_id.1023 ///1024 /// * schema: String representing the offchain data schema.1025 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1026 #[transactional]1027 pub fn set_offchain_schema(1028 origin,1029 collection_id: CollectionId,1030 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1031 ) -> DispatchResult {1032 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1033 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10341035 // =========10361037 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10381039 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1040 collection_id1041 ));1042 Ok(())1043 }10441045 /// Set const on-chain data schema.1046 ///1047 /// # Permissions1048 ///1049 /// * Collection Owner1050 /// * Collection Admin1051 ///1052 /// # Arguments1053 ///1054 /// * collection_id.1055 ///1056 /// * schema: String representing the const on-chain data schema.1057 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1058 #[transactional]1059 pub fn set_const_on_chain_schema (1060 origin,1061 collection_id: CollectionId,1062 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1063 ) -> DispatchResult {1064 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1065 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10661067 // =========10681069 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10701071 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1072 collection_id1073 ));1074 Ok(())1075 }10761077 /// Set variable on-chain data schema.1078 ///1079 /// # Permissions1080 ///1081 /// * Collection Owner1082 /// * Collection Admin1083 ///1084 /// # Arguments1085 ///1086 /// * collection_id.1087 ///1088 /// * schema: String representing the variable on-chain data schema.1089 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1090 #[transactional]1091 pub fn set_variable_on_chain_schema (1092 origin,1093 collection_id: CollectionId,1094 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1095 ) -> DispatchResult {1096 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1097 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10981099 // =========11001101 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;11021103 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1104 collection_id1105 ));1106 Ok(())1107 }11081109 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1110 #[transactional]1111 pub fn set_collection_limits(1112 origin,1113 collection_id: CollectionId,1114 new_limit: CollectionLimits,1115 ) -> DispatchResult {1116 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1117 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1118 target_collection.check_is_owner(&sender)?;1119 let old_limit = &target_collection.limits;11201121 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11221123 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1124 collection_id1125 ));11261127 target_collection.save()1128 }1129 }1130}primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -720,6 +720,17 @@
Ok(())
}
+ pub fn remove_property(&mut self, key: &PropertyKey) {
+ let property = self.map.get(key);
+
+ if let Some(value) = property {
+ let value_len = value.len() as u32;
+
+ self.map.remove(key);
+ self.consumed_space -= value_len;
+ }
+ }
+
pub fn get_property(&self, key: &PropertyKey) -> Option<&PropertyValue> {
self.map.get(key)
}
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -16,9 +16,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
-use up_data_structs::{
- CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
-};
+use up_data_structs::{CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits};
use sp_std::vec::Vec;
use codec::Decode;
use sp_runtime::DispatchError;
runtime/common/src/weights.rsdiffbeforeafterboth--- a/runtime/common/src/weights.rs
+++ b/runtime/common/src/weights.rs
@@ -62,6 +62,10 @@
dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
}
+ fn delete_token_properties(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
+ }
+
fn set_property_permissions(amount: u32) -> Weight {
dispatch_weight::<T>() + max_weight_of!(set_property_permissions(amount))
}