difftreelog
Add first draft of Properties
in: master
14 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -18,7 +18,7 @@
use core::ops::{Deref, DerefMut};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use sp_std::vec::Vec;
+use sp_std::{vec::Vec, collections::btree_map::BTreeMap};
use pallet_evm::account::CrossAccountId;
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},
@@ -35,7 +35,8 @@
FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,
CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,
CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,
- PhantomType,
+ PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,
+ PropertiesError,
};
pub use pallet::*;
use sp_core::H160;
@@ -288,6 +289,10 @@
T::CrossAccountId,
u128,
),
+
+ CollectionPropertySet(CollectionId, Property),
+
+ TokenPropertySet(CollectionId, TokenId, Property),
}
#[pallet::error]
@@ -319,7 +324,6 @@
CollectionLimitBoundsExceeded,
/// Tried to enable permissions which are only permitted to be disabled
OwnerPermissionsCantBeReverted,
-
/// Collection settings not allowing items transferring
TransferNotAllowed,
/// Account token limit exceeded per collection
@@ -372,6 +376,25 @@
QueryKind = OptionQuery,
>;
+ /// Collection properties
+ #[pallet::storage]
+ pub type CollectionProperties<T> = StorageMap<
+ Hasher = Blake2_128Concat,
+ Key = CollectionId,
+ Value = Properties,
+ QueryKind = ValueQuery,
+ OnEmpty = up_data_structs::CollectionProperties,
+ >;
+
+ #[pallet::storage]
+ #[pallet::getter(fn property_permission)]
+ pub type CollectionPropertyPermissions<T> = StorageMap<
+ Hasher = Blake2_128Concat,
+ Key = CollectionId,
+ Value = PropertiesPermissionMap,
+ QueryKind = ValueQuery,
+ >;
+
/// Large variable-size collection fields are extracted here
#[pallet::storage]
pub type CollectionData<T> = StorageNMap<
@@ -538,6 +561,7 @@
sponsorship,
limits,
meta_update_permission,
+ ..
} = <CollectionById<T>>::get(collection)?;
Some(RpcCollection {
name: name.into_inner(),
@@ -615,8 +639,25 @@
.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
.unwrap_or_else(|| Ok(CollectionLimits::default()))?,
meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+ // token_property_permissions: data.token_property_permissions.unwrap_or_default(),
+ // properties: Properties::from_collection_props_vec(data.properties)?
};
+ CollectionProperties::<T>::insert(
+ id,
+ Properties::from_collection_props_vec(data.properties)?,
+ );
+
+ let token_props_permissions: PropertiesPermissionMap = data
+ .token_property_permissions
+ .into_iter()
+ .map(|property| (property.key, property.permission))
+ .collect::<BTreeMap<_, _>>()
+ .try_into()
+ .map_err(|_| PropertiesError::PropertyLimitReached)?;
+
+ CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);
+
// Take a (non-refundable) deposit of collection creation
{
let mut imbalance =
@@ -688,6 +729,34 @@
Ok(())
}
+ pub fn change_collection_property(
+ collection: &CollectionHandle<T>,
+ sender: &T::CrossAccountId,
+ property: Property,
+ ) -> DispatchResult {
+ collection.check_is_owner_or_admin(sender)?;
+
+ CollectionProperties::<T>::get(collection.id).try_change_property(property)?;
+
+ Ok(())
+ }
+
+ pub fn change_property_permission(
+ collection: &CollectionHandle<T>,
+ sender: &T::CrossAccountId,
+ property_key: PropertyKey,
+ permission: PropertyPermission,
+ ) -> DispatchResult {
+ collection.check_is_owner_or_admin(sender)?;
+
+ CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {
+ permissions.try_insert(property_key, permission)
+ })
+ .map_err(|_| PropertiesError::PropertyLimitReached)?;
+
+ Ok(())
+ }
+
fn set_field_raw(
collection_id: CollectionId,
field: CollectionField,
@@ -840,6 +909,7 @@
fn create_multiple_items(amount: u32) -> Weight;
fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;
fn burn_item() -> Weight;
+ fn set_property() -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
fn transfer_from() -> Weight;
@@ -875,6 +945,19 @@
amount: u128,
) -> DispatchResultWithPostInfo;
+ fn change_collection_property(
+ &self,
+ sender: T::CrossAccountId,
+ property: Property,
+ ) -> DispatchResultWithPostInfo;
+
+ fn change_token_property(
+ &self,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ property: Property,
+ ) -> DispatchResultWithPostInfo;
+
fn transfer(
&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;
+use up_data_structs::{CustomDataLimit, Property};
use crate::{
Allowance, Balance, Config, Error, FungibleHandle, Pallet, SelfWeightOf, weights::WeightInfo,
@@ -50,6 +50,10 @@
<SelfWeightOf<T>>::burn_item()
}
+ fn set_property() -> Weight {
+ <SelfWeightOf<T>>::set_property()
+ }
+
fn transfer() -> Weight {
<SelfWeightOf<T>>::transfer()
}
@@ -225,6 +229,23 @@
)
}
+ fn change_collection_property(
+ &self,
+ _sender: T::CrossAccountId,
+ _property: Property,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
+ fn change_token_property(
+ &self,
+ _sender: T::CrossAccountId,
+ _token_id: TokenId,
+ _property: Property,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
fn set_variable_metadata(
&self,
_sender: T::CrossAccountId,
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -61,6 +61,8 @@
FungibleItemsDontHaveData,
/// Fungible token does not support nested
FungibleDisallowsNesting,
+ /// Item properties are not allowed
+ PropertiesNotAllowed,
}
#[pallet::config]
pallets/fungible/src/weights.rsdiffbeforeafterboth--- a/pallets/fungible/src/weights.rs
+++ b/pallets/fungible/src/weights.rs
@@ -35,6 +35,7 @@
fn create_item() -> Weight;
fn create_multiple_items_ex(b: u32, ) -> Weight;
fn burn_item() -> Weight;
+ fn set_property() -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
fn transfer_from() -> Weight;
@@ -69,6 +70,12 @@
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
+
+ fn set_property() -> Weight {
+ // Error
+ 0
+ }
+
// Storage: Fungible Balance (r:2 w:2)
fn transfer() -> Weight {
(17_713_000 as Weight)
@@ -126,6 +133,12 @@
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
+
+ fn set_property() -> Weight {
+ // Error
+ 0
+ }
+
// Storage: Fungible Balance (r:2 w:2)
fn transfer() -> Weight {
(17_713_000 as Weight)
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -17,7 +17,9 @@
use core::marker::PhantomData;
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, BoundedVec};
-use up_data_structs::{TokenId, CustomDataLimit, CreateItemExData, CollectionId, budget::Budget};
+use up_data_structs::{
+ TokenId, CustomDataLimit, CreateItemExData, CollectionId, budget::Budget, Property,
+};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
use sp_std::vec::Vec;
@@ -48,6 +50,10 @@
<SelfWeightOf<T>>::burn_item()
}
+ fn set_property() -> Weight {
+ <SelfWeightOf<T>>::set_property()
+ }
+
fn transfer() -> Weight {
<SelfWeightOf<T>>::transfer()
}
@@ -235,6 +241,32 @@
}
}
+ fn change_collection_property(
+ &self,
+ sender: T::CrossAccountId,
+ property: Property,
+ ) -> DispatchResultWithPostInfo {
+ // let token_id = None;
+ with_weight(
+ // <Pallet<T>>::change_property(self, &sender, token_id, property),
+ Ok(()),
+ <CommonWeights<T>>::set_property(),
+ )
+ }
+
+ fn change_token_property(
+ &self,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ property: Property,
+ ) -> DispatchResultWithPostInfo {
+ with_weight(
+ // <Pallet<T>>::change_property(self, &sender, Some(token_id), property),
+ Ok(()),
+ <CommonWeights<T>>::set_property(),
+ )
+ }
+
fn set_variable_metadata(
&self,
sender: T::CrossAccountId,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -20,7 +20,7 @@
use frame_support::{BoundedVec, ensure, fail};
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
- mapping::TokenAddressMapping, NestingRule, budget::Budget,
+ mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
};
use pallet_evm::account::CrossAccountId;
use pallet_common::{
@@ -94,6 +94,14 @@
QueryKind = OptionQuery,
>;
+ #[pallet::storage]
+ pub type TokenProperties<T: Config> = StorageNMap<
+ Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+ Value = up_data_structs::Properties,
+ QueryKind = ValueQuery,
+ OnEmpty = up_data_structs::TokenProperties,
+ >;
+
/// Used to enumerate tokens owned by account
#[pallet::storage]
pub type Owned<T: Config> = StorageNMap<
@@ -246,6 +254,56 @@
Ok(())
}
+ pub fn change_token_property(
+ collection: &NonfungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property: Property,
+ ) -> DispatchResult {
+ let permission = <PalletCommon<T>>::property_permission(collection.id)
+ .get(&property.key)
+ .map(|p| p.clone())
+ .unwrap_or(PropertyPermission::None);
+
+ let check_token_owner = || -> DispatchResult {
+ let token_data = <TokenData<T>>::get((collection.id, token_id))
+ .ok_or(<CommonError<T>>::TokenNotFound)?;
+
+ ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);
+
+ Ok(())
+ };
+
+ let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))
+ .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::ItemOwnerOrAdmin, _) => {
+ check_token_owner().or(collection.check_is_owner_or_admin(sender))?;
+ }
+ _ => return Err(<CommonError<T>>::NoPermission.into()),
+ }
+
+ <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+ properties.try_change_property(property.clone())
+ })?;
+
+ <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
+ collection.id,
+ token_id,
+ property,
+ ));
+
+ Ok(())
+ }
+
pub fn transfer(
collection: &NonfungibleHandle<T>,
from: &T::CrossAccountId,
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -36,6 +36,7 @@
fn create_multiple_items(b: u32, ) -> Weight;
fn create_multiple_items_ex(b: u32, ) -> Weight;
fn burn_item() -> Weight;
+ fn set_property() -> Weight;
fn transfer() -> Weight;
fn approve() -> Weight;
fn transfer_from() -> Weight;
@@ -90,6 +91,12 @@
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
+
+ fn set_property() -> Weight {
+ // TODO calculate appropriate weight
+ 50_000_000 as Weight
+ }
+
// Storage: Nonfungible TokenData (r:1 w:1)
// Storage: Nonfungible AccountBalance (r:2 w:2)
// Storage: Nonfungible Allowance (r:1 w:0)
@@ -179,6 +186,12 @@
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
+
+ fn set_property() -> Weight {
+ // TODO calculate appropriate weight
+ 50_000_000 as Weight
+ }
+
// Storage: Nonfungible TokenData (r:1 w:1)
// Storage: Nonfungible AccountBalance (r:2 w:2)
// Storage: Nonfungible Allowance (r:1 w:0)
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,
+ budget::Budget, Property,
};
use pallet_common::{CommonCollectionOperations, CommonWeightInfo, with_weight};
use sp_runtime::DispatchError;
@@ -66,6 +66,10 @@
max_weight_of!(burn_item_partial(), burn_item_fully())
}
+ fn set_property() -> Weight {
+ <SelfWeightOf<T>>::set_property()
+ }
+
fn transfer() -> Weight {
max_weight_of!(
transfer_normal(),
@@ -244,6 +248,23 @@
)
}
+ fn change_collection_property(
+ &self,
+ _sender: T::CrossAccountId,
+ _property: Property,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
+ fn change_token_property(
+ &self,
+ _sender: T::CrossAccountId,
+ _token_id: TokenId,
+ _property: Property,
+ ) -> DispatchResultWithPostInfo {
+ fail!(<Error<T>>::PropertiesNotAllowed)
+ }
+
fn set_variable_metadata(
&self,
sender: T::CrossAccountId,
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -62,6 +62,8 @@
WrongRefungiblePieces,
/// Refungible token can't nest other tokens
RefungibleDisallowsNesting,
+ /// Item properties are not allowed
+ PropertiesNotAllowed,
}
#[pallet::config]
pallets/refungible/src/weights.rsdiffbeforeafterboth--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -38,6 +38,7 @@
fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight;
fn burn_item_partial() -> Weight;
fn burn_item_fully() -> Weight;
+ fn set_property() -> Weight;
fn transfer_normal() -> Weight;
fn transfer_creating() -> Weight;
fn transfer_removing() -> Weight;
@@ -129,6 +130,12 @@
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
+
+ fn set_property() -> Weight {
+ // Error
+ 0
+ }
+
// Storage: Refungible Balance (r:2 w:2)
fn transfer_normal() -> Weight {
(19_766_000 as Weight)
@@ -297,6 +304,12 @@
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
+
+ fn set_property() -> Weight {
+ // Error
+ 0
+ }
+
// Storage: Refungible Balance (r:2 w:2)
fn transfer_normal() -> Weight {
(19_766_000 as Weight)
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,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::create_multiple_items_ex(&data)]698 #[transactional]699 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {700 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);701 let budget = budget::Value::new(2);702703 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))704 }705706 // TODO! transaction weight707708 /// Set transfers_enabled value for particular collection709 ///710 /// # Permissions711 ///712 /// * Collection Owner.713 ///714 /// # Arguments715 ///716 /// * collection_id: ID of the collection.717 ///718 /// * value: New flag value.719 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]720 #[transactional]721 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {722 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);723 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;724 target_collection.check_is_owner(&sender)?;725726 // =========727728 target_collection.limits.transfers_enabled = Some(value);729 target_collection.save()730 }731732 /// Destroys a concrete instance of NFT.733 ///734 /// # Permissions735 ///736 /// * Collection Owner.737 /// * Collection Admin.738 /// * Current NFT Owner.739 ///740 /// # Arguments741 ///742 /// * collection_id: ID of the collection.743 ///744 /// * item_id: ID of NFT to burn.745 #[weight = T::CommonWeightInfo::burn_item()]746 #[transactional]747 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {748 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);749750 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;751 if value == 1 {752 <NftTransferBasket<T>>::remove(collection_id, item_id);753 <NftApproveBasket<T>>::remove(collection_id, item_id);754 }755 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?756 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());757 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));758 Ok(post_info)759 }760761 /// Destroys a concrete instance of NFT on behalf of the owner762 /// See also: [`approve`]763 ///764 /// # Permissions765 ///766 /// * Collection Owner.767 /// * Collection Admin.768 /// * Current NFT Owner.769 ///770 /// # Arguments771 ///772 /// * collection_id: ID of the collection.773 ///774 /// * item_id: ID of NFT to burn.775 ///776 /// * from: owner of item777 #[weight = T::CommonWeightInfo::burn_from()]778 #[transactional]779 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781 let budget = budget::Value::new(2);782783 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))784 }785786 /// Change ownership of the token.787 ///788 /// # Permissions789 ///790 /// * Collection Owner791 /// * Collection Admin792 /// * Current NFT owner793 ///794 /// # Arguments795 ///796 /// * recipient: Address of token recipient.797 ///798 /// * collection_id.799 ///800 /// * item_id: ID of the item801 /// * Non-Fungible Mode: Required.802 /// * Fungible Mode: Ignored.803 /// * Re-Fungible Mode: Required.804 ///805 /// * value: Amount to transfer.806 /// * Non-Fungible Mode: Ignored807 /// * Fungible Mode: Must specify transferred amount808 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)809 #[weight = T::CommonWeightInfo::transfer()]810 #[transactional]811 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813 let budget = budget::Value::new(2);814815 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))816 }817818 /// Set, change, or remove approved address to transfer the ownership of the NFT.819 ///820 /// # Permissions821 ///822 /// * Collection Owner823 /// * Collection Admin824 /// * Current NFT owner825 ///826 /// # Arguments827 ///828 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).829 ///830 /// * collection_id.831 ///832 /// * item_id: ID of the item.833 #[weight = T::CommonWeightInfo::approve()]834 #[transactional]835 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {836 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);837838 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))839 }840841 /// 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.842 ///843 /// # Permissions844 /// * Collection Owner845 /// * Collection Admin846 /// * Current NFT owner847 /// * Address approved by current NFT owner848 ///849 /// # Arguments850 ///851 /// * from: Address that owns token.852 ///853 /// * recipient: Address of token recipient.854 ///855 /// * collection_id.856 ///857 /// * item_id: ID of the item.858 ///859 /// * value: Amount to transfer.860 #[weight = T::CommonWeightInfo::transfer_from()]861 #[transactional]862 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {863 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864 let budget = budget::Value::new(2);865866 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))867 }868869 /// Set off-chain data schema.870 ///871 /// # Permissions872 ///873 /// * Collection Owner874 /// * Collection Admin875 ///876 /// # Arguments877 ///878 /// * collection_id.879 ///880 /// * schema: String representing the offchain data schema.881 #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]882 #[transactional]883 pub fn set_variable_meta_data (884 origin,885 collection_id: CollectionId,886 item_id: TokenId,887 data: BoundedVec<u8, CustomDataLimit>,888 ) -> DispatchResultWithPostInfo {889 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);890891 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))892 }893894 /// Set meta_update_permission value for particular collection895 ///896 /// # Permissions897 ///898 /// * Collection Owner.899 ///900 /// # Arguments901 ///902 /// * collection_id: ID of the collection.903 ///904 /// * value: New flag value.905 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]906 #[transactional]907 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {908 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);909 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;910911 ensure!(912 target_collection.meta_update_permission != MetaUpdatePermission::None,913 <CommonError<T>>::MetadataFlagFrozen,914 );915 target_collection.check_is_owner(&sender)?;916917 target_collection.meta_update_permission = value;918919 target_collection.save()920 }921922 /// Set schema standard923 /// ImageURL924 /// Unique925 ///926 /// # Permissions927 ///928 /// * Collection Owner929 /// * Collection Admin930 ///931 /// # Arguments932 ///933 /// * collection_id.934 ///935 /// * schema: SchemaVersion: enum936 #[weight = <SelfWeightOf<T>>::set_schema_version()]937 #[transactional]938 pub fn set_schema_version(939 origin,940 collection_id: CollectionId,941 version: SchemaVersion942 ) -> DispatchResult {943 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);944 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;945 target_collection.check_is_owner_or_admin(&sender)?;946 target_collection.schema_version = version;947948 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(949 collection_id950 ));951952 target_collection.save()953 }954955 /// Set off-chain data schema.956 ///957 /// # Permissions958 ///959 /// * Collection Owner960 /// * Collection Admin961 ///962 /// # Arguments963 ///964 /// * collection_id.965 ///966 /// * schema: String representing the offchain data schema.967 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]968 #[transactional]969 pub fn set_offchain_schema(970 origin,971 collection_id: CollectionId,972 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,973 ) -> DispatchResult {974 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);975 let collection = <CollectionHandle<T>>::try_get(collection_id)?;976977 // =========978979 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;980981 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(982 collection_id983 ));984 Ok(())985 }986987 /// Set const on-chain data schema.988 ///989 /// # Permissions990 ///991 /// * Collection Owner992 /// * Collection Admin993 ///994 /// # Arguments995 ///996 /// * collection_id.997 ///998 /// * schema: String representing the const on-chain data schema.999 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1000 #[transactional]1001 pub fn set_const_on_chain_schema (1002 origin,1003 collection_id: CollectionId,1004 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1005 ) -> DispatchResult {1006 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10081009 // =========10101011 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10121013 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1014 collection_id1015 ));1016 Ok(())1017 }10181019 /// Set variable on-chain data schema.1020 ///1021 /// # Permissions1022 ///1023 /// * Collection Owner1024 /// * Collection Admin1025 ///1026 /// # Arguments1027 ///1028 /// * collection_id.1029 ///1030 /// * schema: String representing the variable on-chain data schema.1031 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1032 #[transactional]1033 pub fn set_variable_on_chain_schema (1034 origin,1035 collection_id: CollectionId,1036 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1037 ) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10401041 // =========10421043 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;10441045 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1046 collection_id1047 ));1048 Ok(())1049 }10501051 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1052 #[transactional]1053 pub fn set_collection_limits(1054 origin,1055 collection_id: CollectionId,1056 new_limit: CollectionLimits,1057 ) -> DispatchResult {1058 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1059 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1060 target_collection.check_is_owner(&sender)?;1061 let old_limit = &target_collection.limits;10621063 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10641065 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1066 collection_id1067 ));10681069 target_collection.save()1070 }1071 }1072}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,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::create_multiple_items_ex(&data)]698 #[transactional]699 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {700 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);701 let budget = budget::Value::new(2);702703 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))704 }705706 // TODO! transaction weight707708 /// Set transfers_enabled value for particular collection709 ///710 /// # Permissions711 ///712 /// * Collection Owner.713 ///714 /// # Arguments715 ///716 /// * collection_id: ID of the collection.717 ///718 /// * value: New flag value.719 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]720 #[transactional]721 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {722 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);723 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;724 target_collection.check_is_owner(&sender)?;725726 // =========727728 target_collection.limits.transfers_enabled = Some(value);729 target_collection.save()730 }731732 /// Destroys a concrete instance of NFT.733 ///734 /// # Permissions735 ///736 /// * Collection Owner.737 /// * Collection Admin.738 /// * Current NFT Owner.739 ///740 /// # Arguments741 ///742 /// * collection_id: ID of the collection.743 ///744 /// * item_id: ID of NFT to burn.745 #[weight = T::CommonWeightInfo::burn_item()]746 #[transactional]747 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {748 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);749750 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;751 if value == 1 {752 <NftTransferBasket<T>>::remove(collection_id, item_id);753 <NftApproveBasket<T>>::remove(collection_id, item_id);754 }755 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?756 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());757 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));758 Ok(post_info)759 }760761 /// Destroys a concrete instance of NFT on behalf of the owner762 /// See also: [`approve`]763 ///764 /// # Permissions765 ///766 /// * Collection Owner.767 /// * Collection Admin.768 /// * Current NFT Owner.769 ///770 /// # Arguments771 ///772 /// * collection_id: ID of the collection.773 ///774 /// * item_id: ID of NFT to burn.775 ///776 /// * from: owner of item777 #[weight = T::CommonWeightInfo::burn_from()]778 #[transactional]779 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781 let budget = budget::Value::new(2);782783 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))784 }785786 /// Change ownership of the token.787 ///788 /// # Permissions789 ///790 /// * Collection Owner791 /// * Collection Admin792 /// * Current NFT owner793 ///794 /// # Arguments795 ///796 /// * recipient: Address of token recipient.797 ///798 /// * collection_id.799 ///800 /// * item_id: ID of the item801 /// * Non-Fungible Mode: Required.802 /// * Fungible Mode: Ignored.803 /// * Re-Fungible Mode: Required.804 ///805 /// * value: Amount to transfer.806 /// * Non-Fungible Mode: Ignored807 /// * Fungible Mode: Must specify transferred amount808 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)809 #[weight = T::CommonWeightInfo::transfer()]810 #[transactional]811 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813 let budget = budget::Value::new(2);814815 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))816 }817818 /// Set, change, or remove approved address to transfer the ownership of the NFT.819 ///820 /// # Permissions821 ///822 /// * Collection Owner823 /// * Collection Admin824 /// * Current NFT owner825 ///826 /// # Arguments827 ///828 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).829 ///830 /// * collection_id.831 ///832 /// * item_id: ID of the item.833 #[weight = T::CommonWeightInfo::approve()]834 #[transactional]835 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {836 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);837838 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))839 }840841 /// 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.842 ///843 /// # Permissions844 /// * Collection Owner845 /// * Collection Admin846 /// * Current NFT owner847 /// * Address approved by current NFT owner848 ///849 /// # Arguments850 ///851 /// * from: Address that owns token.852 ///853 /// * recipient: Address of token recipient.854 ///855 /// * collection_id.856 ///857 /// * item_id: ID of the item.858 ///859 /// * value: Amount to transfer.860 #[weight = T::CommonWeightInfo::transfer_from()]861 #[transactional]862 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {863 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864 let budget = budget::Value::new(2);865866 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))867 }868869 /// Set off-chain data schema.870 ///871 /// # Permissions872 ///873 /// * Collection Owner874 /// * Collection Admin875 ///876 /// # Arguments877 ///878 /// * collection_id.879 ///880 /// * schema: String representing the offchain data schema.881 #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]882 #[transactional]883 pub fn set_variable_meta_data (884 origin,885 collection_id: CollectionId,886 item_id: TokenId,887 data: BoundedVec<u8, CustomDataLimit>,888 ) -> DispatchResultWithPostInfo {889 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);890891 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))892 }893894 /// Set meta_update_permission value for particular collection895 ///896 /// # Permissions897 ///898 /// * Collection Owner.899 ///900 /// # Arguments901 ///902 /// * collection_id: ID of the collection.903 ///904 /// * value: New flag value.905 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]906 #[transactional]907 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {908 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);909 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;910911 ensure!(912 target_collection.meta_update_permission != MetaUpdatePermission::None,913 <CommonError<T>>::MetadataFlagFrozen,914 );915 target_collection.check_is_owner(&sender)?;916917 target_collection.meta_update_permission = value;918919 target_collection.save()920 }921922 /// Set schema standard923 /// ImageURL924 /// Unique925 ///926 /// # Permissions927 ///928 /// * Collection Owner929 /// * Collection Admin930 ///931 /// # Arguments932 ///933 /// * collection_id.934 ///935 /// * schema: SchemaVersion: enum936 #[weight = <SelfWeightOf<T>>::set_schema_version()]937 #[transactional]938 pub fn set_schema_version(939 origin,940 collection_id: CollectionId,941 version: SchemaVersion942 ) -> DispatchResult {943 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);944 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;945 target_collection.check_is_owner_or_admin(&sender)?;946 target_collection.schema_version = version;947948 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(949 collection_id950 ));951952 target_collection.save()953 }954955 /// Set off-chain data schema.956 ///957 /// # Permissions958 ///959 /// * Collection Owner960 /// * Collection Admin961 ///962 /// # Arguments963 ///964 /// * collection_id.965 ///966 /// * schema: String representing the offchain data schema.967 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]968 #[transactional]969 pub fn set_offchain_schema(970 origin,971 collection_id: CollectionId,972 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,973 ) -> DispatchResult {974 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);975 let collection = <CollectionHandle<T>>::try_get(collection_id)?;976977 // =========978979 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;980981 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(982 collection_id983 ));984 Ok(())985 }986987 /// Set const on-chain data schema.988 ///989 /// # Permissions990 ///991 /// * Collection Owner992 /// * Collection Admin993 ///994 /// # Arguments995 ///996 /// * collection_id.997 ///998 /// * schema: String representing the const on-chain data schema.999 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1000 #[transactional]1001 pub fn set_const_on_chain_schema (1002 origin,1003 collection_id: CollectionId,1004 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1005 ) -> DispatchResult {1006 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10081009 // =========10101011 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10121013 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1014 collection_id1015 ));1016 Ok(())1017 }10181019 /// Set variable on-chain data schema.1020 ///1021 /// # Permissions1022 ///1023 /// * Collection Owner1024 /// * Collection Admin1025 ///1026 /// # Arguments1027 ///1028 /// * collection_id.1029 ///1030 /// * schema: String representing the variable on-chain data schema.1031 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1032 #[transactional]1033 pub fn set_variable_on_chain_schema (1034 origin,1035 collection_id: CollectionId,1036 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1037 ) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10401041 // =========10421043 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;10441045 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1046 collection_id1047 ));1048 Ok(())1049 }10501051 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1052 #[transactional]1053 pub fn set_collection_limits(1054 origin,1055 collection_id: CollectionId,1056 new_limit: CollectionLimits,1057 ) -> DispatchResult {1058 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1059 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1060 target_collection.check_is_owner(&sender)?;1061 let old_limit = &target_collection.limits;10621063 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10641065 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1066 collection_id1067 ));10681069 target_collection.save()1070 }1071 }1072}primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -22,13 +22,14 @@
};
use frame_support::{
storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet},
+ traits::Get,
};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
use sp_core::U256;
-use sp_runtime::{ArithmeticError, sp_std::prelude::Vec};
+use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, DispatchError};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::{BoundedVec, traits::ConstU32};
use derivative::Derivative;
@@ -85,6 +86,26 @@
pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;
pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;
+pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;
+pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;
+pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;
+
+// pub const MAX_PROPERTY_KEYS_OVERALL_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH * MAX_PROPERTIES_PER_ITEM;
+pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;
+pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;
+
+pub const MAX_COLLECTION_PROPERTIES_ENCODE_LEN: u32 =
+ MAX_PROPERTIES_PER_ITEM * MAX_PROPERTY_KEY_LENGTH + MAX_COLLECTION_PROPERTIES_SIZE;
+
+pub struct MaxPropertiesPermissionsEncodeLen;
+
+impl Get<u32> for MaxPropertiesPermissionsEncodeLen {
+ fn get() -> u32 {
+ MAX_PROPERTIES_PER_ITEM * MAX_PROPERTY_KEY_LENGTH
+ + <PropertyPermission as MaxEncodedLen>::max_encoded_len() as u32
+ }
+}
+
/// How much items can be created per single
/// create_many call
pub const MAX_ITEMS_PER_BATCH: u32 = 200;
@@ -310,31 +331,32 @@
OffchainSchema,
}
-#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)]
-#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-#[derivative(Default(bound = ""))]
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]
+#[derivative(Debug, Default(bound = ""))]
pub struct CreateCollectionData<AccountId> {
#[derivative(Default(value = "CollectionMode::NFT"))]
pub mode: CollectionMode,
pub access: Option<AccessMode>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
pub schema_version: Option<SchemaVersion>,
pub pending_sponsor: Option<AccountId>,
pub limits: Option<CollectionLimits>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
- #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
pub meta_update_permission: Option<MetaUpdatePermission>,
+ pub token_property_permissions: CollectionPropertiesPermissionsVec,
+ pub properties: CollectionPropertiesVec,
}
+pub type CollectionPropertiesPermissionsVec =
+ BoundedVec<PropertyKeyPermission, MaxPropertiesPermissionsEncodeLen>;
+
+pub type CollectionPropertiesVec =
+ BoundedVec<Property, ConstU32<MAX_COLLECTION_PROPERTIES_ENCODE_LEN>>;
+
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct NftItemType<AccountId> {
@@ -607,3 +629,128 @@
0
}
}
+
+pub type PropertyKey = BoundedVec<u8, ConstU32<MAX_PROPERTY_KEY_LENGTH>>;
+pub type PropertyValue = BoundedVec<u8, ConstU32<MAX_PROPERTY_VALUE_LENGTH>>;
+
+#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]
+pub enum PropertyPermission {
+ None,
+ AdminConst,
+ Admin,
+ ItemOwnerConst,
+ ItemOwner,
+ ItemOwnerOrAdmin,
+}
+
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, MaxEncodedLen)]
+pub struct Property {
+ pub key: PropertyKey,
+ pub value: PropertyValue,
+}
+
+#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)]
+pub struct PropertyKeyPermission {
+ pub key: PropertyKey,
+ pub permission: PropertyPermission,
+}
+
+pub enum PropertiesError {
+ NoSpaceForProperty,
+ PropertyLimitReached,
+}
+
+impl From<PropertiesError> for DispatchError {
+ fn from(error: PropertiesError) -> Self {
+ match error {
+ PropertiesError::NoSpaceForProperty => DispatchError::Other("no space for property"),
+ PropertiesError::PropertyLimitReached => {
+ DispatchError::Other("property key limit reached")
+ }
+ }
+ }
+}
+
+pub type PropertiesMap =
+ BoundedBTreeMap<PropertyKey, PropertyValue, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
+pub type PropertiesPermissionMap =
+ BoundedBTreeMap<PropertyKey, PropertyPermission, ConstU32<MAX_PROPERTIES_PER_ITEM>>;
+
+#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, MaxEncodedLen)]
+pub struct Properties {
+ map: PropertiesMap,
+ consumed_space: u32,
+ space_limit: u32,
+}
+
+impl Properties {
+ pub fn new(space_limit: u32) -> Self {
+ Self {
+ map: BoundedBTreeMap::new(),
+ consumed_space: 0,
+ space_limit,
+ }
+ }
+
+ pub fn from_collection_props_vec(
+ data: CollectionPropertiesVec,
+ ) -> Result<Self, PropertiesError> {
+ let mut props = Self::new(MAX_COLLECTION_PROPERTIES_SIZE);
+
+ for property in data.into_iter() {
+ props.try_change_property(property)?;
+ }
+
+ Ok(props)
+ }
+
+ pub fn try_change_property(&mut self, property: Property) -> Result<(), PropertiesError> {
+ let value_len = property.value.len();
+
+ if self.consumed_space as usize + value_len > self.space_limit as usize {
+ return Err(PropertiesError::NoSpaceForProperty);
+ }
+
+ self.map
+ .try_insert(property.key, property.value)
+ .map_err(|_| PropertiesError::PropertyLimitReached)?;
+
+ self.consumed_space += value_len as u32;
+
+ Ok(())
+ }
+
+ pub fn get_property(&self, key: &PropertyKey) -> Option<&PropertyValue> {
+ self.map.get(key)
+ }
+}
+
+pub struct CollectionProperties;
+
+impl Get<Properties> for CollectionProperties {
+ fn get() -> Properties {
+ Properties::new(MAX_COLLECTION_PROPERTIES_SIZE)
+ }
+}
+
+pub struct TokenProperties;
+
+impl Get<Properties> for TokenProperties {
+ fn get() -> Properties {
+ Properties::new(MAX_TOKEN_PROPERTIES_SIZE)
+ }
+}
+
+// #[cfg(not(feature = "std"))]
+// fn properties_map_debug(_properties: &PropertiesMap, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+// write!(f, "<properties>")
+// }
+
+// #[cfg(not(feature = "std"))]
+// fn opt_properties_permissions_map_debug(properties: &Option<PropertiesPermissionMap>, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+// if properties.is_some() {
+// write!(f, "Some(<properties permissions>)")
+// } else {
+// write!(f, "None")
+// }
+// }
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -16,7 +16,9 @@
#![cfg_attr(not(feature = "std"), no_std)]
-use up_data_structs::{CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits};
+use up_data_structs::{
+ CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
+};
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
@@ -54,6 +54,10 @@
dispatch_weight::<T>() + max_weight_of!(burn_item())
}
+ fn set_property() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(set_property())
+ }
+
fn transfer() -> Weight {
dispatch_weight::<T>() + max_weight_of!(transfer())
}