difftreelog
Merge branch 'develop' into feature/CORE-189
in: master
9 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -196,6 +196,7 @@
const_on_chain_schema: vec![],
variable_on_chain_schema: vec![],
limits: CollectionLimits::default(),
+ meta_update_permission: MetaUpdatePermission::ItemOwner,
transfers_enabled: true,
},
)],
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -43,7 +43,7 @@
OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,
MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,
CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,
- FungibleItemType, ReFungibleItemType,
+ MetaUpdatePermission, FungibleItemType, ReFungibleItemType,
};
#[cfg(test)]
@@ -145,6 +145,10 @@
BadCreateRefungibleCall,
/// Gas limit exceeded
OutOfGas,
+ /// Metadata update denied by collection settings
+ MetadataUpdateDenied,
+ /// Metadata update flag become unmutable with None option
+ MetadataFlagFrozen,
/// Collection settings not allowing items transferring
TransferNotAllowed,
/// Can't transfer tokens to ethereum zero address
@@ -541,6 +545,7 @@
variable_on_chain_schema: Vec::new(),
const_on_chain_schema: Vec::new(),
limits,
+ meta_update_permission: MetaUpdatePermission::default(),
transfers_enabled: true,
};
@@ -940,6 +945,36 @@
target_collection.save()
}
+ // TODO! transaction weight
+ /// Set meta_update_permission value for particular collection
+ ///
+ /// # Permissions
+ ///
+ /// * Collection Owner.
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: ID of the collection.
+ ///
+ /// * value: New flag value.
+ #[weight = <T as Config>::WeightInfo::burn_item()]
+ #[transactional]
+ pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {
+
+ let sender = ensure_signed(origin)?;
+ let mut target_collection = Self::get_collection(collection_id)?;
+
+ ensure!(
+ target_collection.meta_update_permission != MetaUpdatePermission::None,
+ Error::<T>::MetadataFlagFrozen
+ );
+ Self::check_owner_permissions(&target_collection, &sender)?;
+
+ target_collection.meta_update_permission = value;
+
+ target_collection.save()
+ }
+
/// Destroys a concrete instance of NFT.
///
/// # Permissions
@@ -1485,10 +1520,11 @@
Error::<T>::TokenVariableDataLimitExceeded
);
- // Modify permissions check
ensure!(
- Self::is_item_owner(sender, collection, item_id)?
- || Self::is_owner_or_admin_permissions(collection, sender)?,
+ (Self::is_item_owner(sender, collection, item_id)?
+ && collection.meta_update_permission == MetaUpdatePermission::ItemOwner)
+ || (Self::is_owner_or_admin_permissions(collection, sender)?
+ && collection.meta_update_permission == MetaUpdatePermission::Admin),
Error::<T>::NoPermission
);
@@ -1504,6 +1540,26 @@
Ok(())
}
+ pub fn meta_update_check(
+ sender: &T::CrossAccountId,
+ collection: &CollectionHandle<T>,
+ item_id: TokenId,
+ ) -> DispatchResult {
+ match collection.meta_update_permission {
+ MetaUpdatePermission::ItemOwner => ensure!(
+ Self::is_item_owner(sender, collection, item_id)?,
+ Error::<T>::NoPermission
+ ),
+ MetaUpdatePermission::Admin => ensure!(
+ Self::is_owner_or_admin_permissions(collection, sender)?,
+ Error::<T>::NoPermission
+ ),
+ MetaUpdatePermission::None => fail!(Error::<T>::MetadataUpdateDenied),
+ }
+
+ Ok(())
+ }
+
pub fn get_variable_metadata(
collection: &CollectionHandle<T>,
item_id: TokenId,
pallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -2005,56 +2005,285 @@
}
#[test]
-fn collection_transfer_flag_works() {
+fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() {
new_test_ext().execute_with(|| {
- let origin1 = Origin::signed(1);
+ //default_limits();
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
- assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
+ let origin1 = Origin::signed(1);
+
let data = default_nft_data();
- create_test_item(collection_id, &data.into());
- assert_eq!(TemplateModule::balance_count(1, 1), 1);
- assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+ create_test_item(1, &data.into());
- let origin1 = Origin::signed(1);
+ TemplateModule::set_meta_update_permission_flag(
+ origin1.clone(),
+ collection_id,
+ MetaUpdatePermission::ItemOwner,
+ );
- // default scenario
- assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
- assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
- assert_eq!(TemplateModule::balance_count(1, 1), 0);
- assert_eq!(TemplateModule::balance_count(1, 2), 1);
+ let variable_data = b"ten chars.".to_vec();
+ assert_ok!(TemplateModule::set_variable_meta_data(
+ origin1,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ));
- assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
+ assert_eq!(
+ TemplateModule::nft_item_id(collection_id, 1)
+ .unwrap()
+ .variable_data,
+ variable_data
+ );
});
}
#[test]
-fn collection_transfer_flag_works_neg() {
+fn set_variable_meta_data_on_nft_with_item_owner_permission_flag_neg() {
new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
- let collection_id = create_test_collection(&CollectionMode::NFT, 1);
- assert_ok!(TemplateModule::set_transfers_enabled_flag(
- origin1, 1, false
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin2.clone(),
+ collection_id,
+ true
+ ));
+ assert_ok!(TemplateModule::add_to_white_list(
+ origin2.clone(),
+ collection_id,
+ account(1)
));
let data = default_nft_data();
- create_test_item(collection_id, &data.into());
- assert_eq!(TemplateModule::balance_count(1, 1), 1);
- assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+ create_test_item(1, &data.into());
- let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::ItemOwner,
+ ));
- // default scenario
+ let variable_data = b"ten chars.++".to_vec();
assert_noop!(
- TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
- Error::<Test>::TransferNotAllowed
+ TemplateModule::set_variable_meta_data(
+ origin2,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::TokenVariableDataLimitExceeded
);
- assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
- assert_eq!(TemplateModule::balance_count(1, 1), 1);
- assert_eq!(TemplateModule::balance_count(1, 2), 0);
- assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+ #[test]
+ fn collection_transfer_flag_works() {
+ new_test_ext().execute_with(|| {
+ let origin1 = Origin::signed(1);
+
+ let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+ assert_ok!(TemplateModule::set_transfers_enabled_flag(origin1, 1, true));
+
+ let data = default_nft_data();
+ create_test_item(collection_id, &data.into());
+ assert_eq!(TemplateModule::balance_count(1, 1), 1);
+ assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+
+ let origin1 = Origin::signed(1);
+
+ // default scenario
+ assert_ok!(TemplateModule::transfer(origin1, account(2), 1, 1, 1000));
+ assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(2));
+ assert_eq!(TemplateModule::balance_count(1, 1), 0);
+ assert_eq!(TemplateModule::balance_count(1, 2), 1);
+
+ assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
+ });
+ }
+
+ #[test]
+ fn set_variable_meta_data_on_nft_with_admin_flag() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin2.clone(),
+ collection_id,
+ true
+ ));
+ assert_ok!(TemplateModule::add_to_white_list(
+ origin2.clone(),
+ collection_id,
+ account(1)
+ ));
+
+ assert_ok!(TemplateModule::add_collection_admin(
+ origin2.clone(),
+ collection_id,
+ account(1)
+ ));
+
+ let data = default_nft_data();
+ create_test_item(1, &data.into());
+
+ assert_ok!(TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin,
+ ));
+
+ let variable_data = b"test set_variable_meta_data method.".to_vec();
+ assert_ok!(TemplateModule::set_variable_meta_data(
+ origin1,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ));
+
+ assert_eq!(
+ TemplateModule::nft_item_id(collection_id, 1)
+ .unwrap()
+ .variable_data,
+ variable_data
+ );
+ });
+ }
+
+ #[test]
+ fn set_variable_meta_data_on_nft_with_admin_flag_neg() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin2.clone(),
+ collection_id,
+ true
+ ));
+ assert_ok!(TemplateModule::add_to_white_list(
+ origin2.clone(),
+ collection_id,
+ account(1)
+ ));
+
+ let data = default_nft_data();
+ create_test_item(1, &data.into());
+
+ assert_ok!(TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin,
+ ));
+
+ let variable_data = b"test set_variable_meta_data method.".to_vec();
+ assert_noop!(
+ TemplateModule::set_variable_meta_data(
+ origin1,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::NoPermission
+ );
+ });
+ }
+
+ #[test]
+ fn set_variable_meta_flag_after_freeze() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 2, 1);
+
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::None,
+ ));
+ assert_noop!(
+ TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin
+ ),
+ Error::<Test>::MetadataFlagFrozen
+ );
+ });
+ }
+
+ #[test]
+ fn set_variable_meta_data_on_nft_with_none_flag_neg() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id = create_test_collection_for_owner(&CollectionMode::NFT, 1, 1);
+ let origin1 = Origin::signed(1);
+
+ let data = default_nft_data();
+ create_test_item(1, &data.into());
+
+ assert_ok!(TemplateModule::set_meta_update_permission_flag(
+ origin1.clone(),
+ collection_id,
+ MetaUpdatePermission::None,
+ ));
+
+ let variable_data = b"test set_variable_meta_data method.".to_vec();
+ assert_noop!(
+ TemplateModule::set_variable_meta_data(
+ origin1.clone(),
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::MetadataUpdateDenied
+ );
+ });
+ }
+
+ #[test]
+ fn collection_transfer_flag_works_neg() {
+ new_test_ext().execute_with(|| {
+ let origin1 = Origin::signed(1);
+
+ let collection_id = create_test_collection(&CollectionMode::NFT, 1);
+ assert_ok!(TemplateModule::set_transfers_enabled_flag(
+ origin1, 1, false
+ ));
+
+ let data = default_nft_data();
+ create_test_item(collection_id, &data.into());
+ assert_eq!(TemplateModule::balance_count(1, 1), 1);
+ assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+
+ let origin1 = Origin::signed(1);
+
+ // default scenario
+ assert_noop!(
+ TemplateModule::transfer(origin1, account(2), 1, 1, 1000),
+ Error::<Test>::TransferNotAllowed
+ );
+ assert_eq!(TemplateModule::nft_item_id(1, 1).unwrap().owner, account(1));
+ assert_eq!(TemplateModule::balance_count(1, 1), 1);
+ assert_eq!(TemplateModule::balance_count(1, 2), 0);
+
+ assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
+ });
+ }
});
}
primitives/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "serde")]4pub use serde::{Serialize, Deserialize};56use sp_runtime::sp_std::prelude::Vec;7use codec::{Decode, Encode, MaxEncodedLen};8pub use frame_support::{9 BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,10 dispatch::DispatchResult,11 ensure, fail, parameter_types,12 traits::{13 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,14 Randomness, IsSubType, WithdrawReasons,15 },16 weights::{17 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},18 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,19 WeightToFeePolynomial, DispatchClass,20 },21 StorageValue, transactional,22};23use derivative::Derivative;2425pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;26pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;27pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;28pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;2930pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {31 10000032} else {33 1034};35pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {36 204837} else {38 1039};40pub const COLLECTION_ADMINS_LIMIT: u64 = 5;41pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {42 100000043} else {44 1045};4647// Timeouts for item types in passed blocks48pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;49pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;50pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5152// Schema limits53pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;54pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;55pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;5657pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;58pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;59pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6061/// How much items can be created per single62/// create_many call63pub const MAX_ITEMS_PER_BATCH: u32 = 200;6465parameter_types! {66 pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;67}6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]75pub enum CollectionMode {76 Invalid,77 NFT,78 // decimal points79 Fungible(DecimalPoints),80 ReFungible,81}8283impl Default for CollectionMode {84 fn default() -> Self {85 Self::Invalid86 }87}8889impl CollectionMode {90 pub fn id(&self) -> u8 {91 match self {92 CollectionMode::Invalid => 0,93 CollectionMode::NFT => 1,94 CollectionMode::Fungible(_) => 2,95 CollectionMode::ReFungible => 3,96 }97 }98}99100pub trait SponsoringResolve<AccountId, Call> {101 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;102}103104#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]105#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]106pub enum AccessMode {107 Normal,108 WhiteList,109}110impl Default for AccessMode {111 fn default() -> Self {112 Self::Normal113 }114}115116#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]118pub enum SchemaVersion {119 ImageURL,120 Unique,121}122impl Default for SchemaVersion {123 fn default() -> Self {124 Self::ImageURL125 }126}127128#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]130pub struct Ownership<AccountId> {131 pub owner: AccountId,132 pub fraction: u128,133}134135#[derive(Encode, Decode, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]137pub enum SponsorshipState<AccountId> {138 /// The fees are applied to the transaction sender139 Disabled,140 Unconfirmed(AccountId),141 /// Transactions are sponsored by specified account142 Confirmed(AccountId),143}144145impl<AccountId> SponsorshipState<AccountId> {146 pub fn sponsor(&self) -> Option<&AccountId> {147 match self {148 Self::Confirmed(sponsor) => Some(sponsor),149 _ => None,150 }151 }152153 pub fn pending_sponsor(&self) -> Option<&AccountId> {154 match self {155 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),156 _ => None,157 }158 }159160 pub fn confirmed(&self) -> bool {161 matches!(self, Self::Confirmed(_))162 }163}164165impl<T> Default for SponsorshipState<T> {166 fn default() -> Self {167 Self::Disabled168 }169}170171#[derive(Encode, Decode, Clone, PartialEq)]172#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]173pub struct Collection<T: frame_system::Config> {174 pub owner: T::AccountId,175 pub mode: CollectionMode,176 pub access: AccessMode,177 pub decimal_points: DecimalPoints,178 pub name: Vec<u16>, // 64 include null escape char179 pub description: Vec<u16>, // 256 include null escape char180 pub token_prefix: Vec<u8>, // 16 include null escape char181 pub mint_mode: bool,182 pub offchain_schema: Vec<u8>,183 pub schema_version: SchemaVersion,184 pub sponsorship: SponsorshipState<T::AccountId>,185 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions186 pub variable_on_chain_schema: Vec<u8>, //187 pub const_on_chain_schema: Vec<u8>, //188 pub transfers_enabled: bool,189}190191#[derive(Encode, Decode, Debug, Clone, PartialEq)]192#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]193pub struct NftItemType<AccountId> {194 pub owner: AccountId,195 pub const_data: Vec<u8>,196 pub variable_data: Vec<u8>,197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]201pub struct FungibleItemType {202 pub value: u128,203}204205#[derive(Encode, Decode, Debug, Clone, PartialEq)]206#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]207pub struct ReFungibleItemType<AccountId> {208 pub owner: Vec<Ownership<AccountId>>,209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]215pub struct CollectionLimits<BlockNumber: Encode + Decode> {216 pub account_token_ownership_limit: u32,217 pub sponsored_data_size: u32,218 /// None - setVariableMetadata is not sponsored219 /// Some(v) - setVariableMetadata is sponsored220 /// if there is v block between txs221 pub sponsored_data_rate_limit: Option<BlockNumber>,222 pub token_limit: u32,223224 // Timeouts for item types in passed blocks225 pub sponsor_transfer_timeout: u32,226 pub owner_can_transfer: bool,227 pub owner_can_destroy: bool,228}229230impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {231 fn default() -> Self {232 Self {233 account_token_ownership_limit: 10_000_000,234 token_limit: u32::max_value(),235 sponsored_data_size: u32::MAX,236 sponsored_data_rate_limit: None,237 sponsor_transfer_timeout: 14400,238 owner_can_transfer: true,239 owner_can_destroy: true,240 }241 }242}243244/// BoundedVec doesn't supports serde245#[cfg(feature = "serde1")]246mod bounded_serde {247 use core::convert::TryFrom;248 use frame_support::{BoundedVec, traits::Get};249 use serde::{250 ser::{self, Serialize},251 de::{self, Deserialize, Error},252 };253 use sp_std::vec::Vec;254255 pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>256 where257 D: ser::Serializer,258 V: Serialize,259 {260 let vec: &Vec<_> = &value;261 vec.serialize(serializer)262 }263264 pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>265 where266 D: de::Deserializer<'de>,267 V: de::Deserialize<'de>,268 S: Get<u32>,269 {270 // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?271 let vec = <Vec<V>>::deserialize(deserializer)?;272 let len = vec.len();273 TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))274 }275}276277#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]278#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]279#[derivative(Debug)]280pub struct CreateNftData {281 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]282 #[derivative(Debug = "ignore")]283 pub const_data: BoundedVec<u8, CustomDataLimit>,284 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]285 #[derivative(Debug = "ignore")]286 pub variable_data: BoundedVec<u8, CustomDataLimit>,287}288289#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq)]290#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]291pub struct CreateFungibleData {292 pub value: u128,293}294295#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]296#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]297#[derivative(Debug)]298pub struct CreateReFungibleData {299 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]300 #[derivative(Debug = "ignore")]301 pub const_data: BoundedVec<u8, CustomDataLimit>,302 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]303 #[derivative(Debug = "ignore")]304 pub variable_data: BoundedVec<u8, CustomDataLimit>,305 pub pieces: u128,306}307308#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug)]309#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]310pub enum CreateItemData {311 NFT(CreateNftData),312 Fungible(CreateFungibleData),313 ReFungible(CreateReFungibleData),314}315316impl CreateItemData {317 pub fn data_size(&self) -> usize {318 match self {319 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),320 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),321 _ => 0,322 }323 }324}325326impl From<CreateNftData> for CreateItemData {327 fn from(item: CreateNftData) -> Self {328 CreateItemData::NFT(item)329 }330}331332impl From<CreateReFungibleData> for CreateItemData {333 fn from(item: CreateReFungibleData) -> Self {334 CreateItemData::ReFungible(item)335 }336}337338impl From<CreateFungibleData> for CreateItemData {339 fn from(item: CreateFungibleData) -> Self {340 CreateItemData::Fungible(item)341 }342}1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "serde")]4pub use serde::{Serialize, Deserialize};56use sp_runtime::sp_std::prelude::Vec;7use codec::{Decode, Encode, MaxEncodedLen};8pub use frame_support::{9 BoundedVec, construct_runtime, decl_event, decl_module, decl_storage, decl_error,10 dispatch::DispatchResult,11 ensure, fail, parameter_types,12 traits::{13 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,14 Randomness, IsSubType, WithdrawReasons,15 },16 weights::{17 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},18 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,19 WeightToFeePolynomial, DispatchClass,20 },21 StorageValue, transactional,22};23use derivative::Derivative;2425pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;26pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;27pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;28pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;2930pub const COLLECTION_NUMBER_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {31 10000032} else {33 1034};35pub const CUSTOM_DATA_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {36 204837} else {38 1039};40pub const COLLECTION_ADMINS_LIMIT: u64 = 5;41pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {42 100000043} else {44 1045};4647// Timeouts for item types in passed blocks48pub const NFT_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;49pub const FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;50pub const REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT: u32 = 5;5152// Schema limits53pub const OFFCHAIN_SCHEMA_LIMIT: u32 = 1024;54pub const VARIABLE_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;55pub const CONST_ON_CHAIN_SCHEMA_LIMIT: u32 = 1024;5657pub const MAX_COLLECTION_NAME_LENGTH: usize = 64;58pub const MAX_COLLECTION_DESCRIPTION_LENGTH: usize = 256;59pub const MAX_TOKEN_PREFIX_LENGTH: usize = 16;6061/// How much items can be created per single62/// create_many call63pub const MAX_ITEMS_PER_BATCH: u32 = 200;6465parameter_types! {66 pub const CustomDataLimit: u32 = CUSTOM_DATA_LIMIT;67}6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]75pub enum CollectionMode {76 Invalid,77 NFT,78 // decimal points79 Fungible(DecimalPoints),80 ReFungible,81}8283impl Default for CollectionMode {84 fn default() -> Self {85 Self::Invalid86 }87}8889impl CollectionMode {90 pub fn id(&self) -> u8 {91 match self {92 CollectionMode::Invalid => 0,93 CollectionMode::NFT => 1,94 CollectionMode::Fungible(_) => 2,95 CollectionMode::ReFungible => 3,96 }97 }98}99100pub trait SponsoringResolve<AccountId, Call> {101 fn resolve(who: &AccountId, call: &Call) -> Option<AccountId>;102}103104#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]105#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]106pub enum AccessMode {107 Normal,108 WhiteList,109}110impl Default for AccessMode {111 fn default() -> Self {112 Self::Normal113 }114}115116#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]118pub enum SchemaVersion {119 ImageURL,120 Unique,121}122impl Default for SchemaVersion {123 fn default() -> Self {124 Self::ImageURL125 }126}127128#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]130pub struct Ownership<AccountId> {131 pub owner: AccountId,132 pub fraction: u128,133}134135#[derive(Encode, Decode, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]137pub enum SponsorshipState<AccountId> {138 /// The fees are applied to the transaction sender139 Disabled,140 Unconfirmed(AccountId),141 /// Transactions are sponsored by specified account142 Confirmed(AccountId),143}144145impl<AccountId> SponsorshipState<AccountId> {146 pub fn sponsor(&self) -> Option<&AccountId> {147 match self {148 Self::Confirmed(sponsor) => Some(sponsor),149 _ => None,150 }151 }152153 pub fn pending_sponsor(&self) -> Option<&AccountId> {154 match self {155 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),156 _ => None,157 }158 }159160 pub fn confirmed(&self) -> bool {161 matches!(self, Self::Confirmed(_))162 }163}164165impl<T> Default for SponsorshipState<T> {166 fn default() -> Self {167 Self::Disabled168 }169}170171#[derive(Encode, Decode, Clone, PartialEq)]172#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]173pub struct Collection<T: frame_system::Config> {174 pub owner: T::AccountId,175 pub mode: CollectionMode,176 pub access: AccessMode,177 pub decimal_points: DecimalPoints,178 pub name: Vec<u16>, // 64 include null escape char179 pub description: Vec<u16>, // 256 include null escape char180 pub token_prefix: Vec<u8>, // 16 include null escape char181 pub mint_mode: bool,182 pub offchain_schema: Vec<u8>,183 pub schema_version: SchemaVersion,184 pub sponsorship: SponsorshipState<T::AccountId>,185 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions186 pub variable_on_chain_schema: Vec<u8>, //187 pub const_on_chain_schema: Vec<u8>, //188 pub meta_update_permission: MetaUpdatePermission,189 pub transfers_enabled: bool,190}191192#[derive(Encode, Decode, Debug, Clone, PartialEq)]193#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]194pub struct NftItemType<AccountId> {195 pub owner: AccountId,196 pub const_data: Vec<u8>,197 pub variable_data: Vec<u8>,198}199200#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]201#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]202pub struct FungibleItemType {203 pub value: u128,204}205206#[derive(Encode, Decode, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]208pub struct ReFungibleItemType<AccountId> {209 pub owner: Vec<Ownership<AccountId>>,210 pub const_data: Vec<u8>,211 pub variable_data: Vec<u8>,212}213214#[derive(Encode, Decode, Debug, Clone, PartialEq)]215#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]216pub struct CollectionLimits<BlockNumber: Encode + Decode> {217 pub account_token_ownership_limit: u32,218 pub sponsored_data_size: u32,219 /// None - setVariableMetadata is not sponsored220 /// Some(v) - setVariableMetadata is sponsored221 /// if there is v block between txs222 pub sponsored_data_rate_limit: Option<BlockNumber>,223 pub token_limit: u32,224225 // Timeouts for item types in passed blocks226 pub sponsor_transfer_timeout: u32,227 pub owner_can_transfer: bool,228 pub owner_can_destroy: bool,229}230231impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {232 fn default() -> Self {233 Self {234 account_token_ownership_limit: 10_000_000,235 token_limit: u32::max_value(),236 sponsored_data_size: u32::MAX,237 sponsored_data_rate_limit: None,238 sponsor_transfer_timeout: 14400,239 owner_can_transfer: true,240 owner_can_destroy: true,241 }242 }243}244245/// BoundedVec doesn't supports serde246#[cfg(feature = "serde1")]247mod bounded_serde {248 use core::convert::TryFrom;249 use frame_support::{BoundedVec, traits::Get};250 use serde::{251 ser::{self, Serialize},252 de::{self, Deserialize, Error},253 };254 use sp_std::vec::Vec;255256 pub fn serialize<D, V, S>(value: &BoundedVec<V, S>, serializer: D) -> Result<D::Ok, D::Error>257 where258 D: ser::Serializer,259 V: Serialize,260 {261 let vec: &Vec<_> = &value;262 vec.serialize(serializer)263 }264265 pub fn deserialize<'de, D, V, S>(deserializer: D) -> Result<BoundedVec<V, S>, D::Error>266 where267 D: de::Deserializer<'de>,268 V: de::Deserialize<'de>,269 S: Get<u32>,270 {271 // TODO: Implement custom visitor, which will limit vec size at parse time? Will serde only be used by chainspec?272 let vec = <Vec<V>>::deserialize(deserializer)?;273 let len = vec.len();274 TryFrom::try_from(vec).map_err(|_| D::Error::invalid_length(len, &"lesser size"))275 }276}277278#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]279#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]280#[derivative(Debug)]281pub struct CreateNftData {282 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]283 #[derivative(Debug = "ignore")]284 pub const_data: BoundedVec<u8, CustomDataLimit>,285 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]286 #[derivative(Debug = "ignore")]287 pub variable_data: BoundedVec<u8, CustomDataLimit>,288}289290#[derive(Encode, Decode, MaxEncodedLen, Default, Debug, Clone, PartialEq)]291#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]292pub struct CreateFungibleData {293 pub value: u128,294}295296#[derive(Encode, Decode, MaxEncodedLen, Default, PartialEq, Clone, Derivative)]297#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]298#[derivative(Debug)]299pub struct CreateReFungibleData {300 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]301 #[derivative(Debug = "ignore")]302 pub const_data: BoundedVec<u8, CustomDataLimit>,303 #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]304 #[derivative(Debug = "ignore")]305 pub variable_data: BoundedVec<u8, CustomDataLimit>,306 pub pieces: u128,307}308309#[derive(Encode, Decode, Debug, Clone, PartialEq)]310#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]311pub enum MetaUpdatePermission {312 ItemOwner,313 Admin,314 None,315}316317impl Default for MetaUpdatePermission {318 fn default() -> Self {319 Self::ItemOwner320 }321}322323#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug)]324#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]325pub enum CreateItemData {326 NFT(CreateNftData),327 Fungible(CreateFungibleData),328 ReFungible(CreateReFungibleData),329}330331impl CreateItemData {332 pub fn data_size(&self) -> usize {333 match self {334 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),335 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),336 _ => 0,337 }338 }339}340341impl From<CreateNftData> for CreateItemData {342 fn from(item: CreateNftData) -> Self {343 CreateItemData::NFT(item)344 }345}346347impl From<CreateReFungibleData> for CreateItemData {348 fn from(item: CreateReFungibleData) -> Self {349 CreateItemData::ReFungible(item)350 }351}352353impl From<CreateFungibleData> for CreateItemData {354 fn from(item: CreateFungibleData) -> Self {355 CreateItemData::Fungible(item)356 }357}runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -63,6 +63,7 @@
"Limits": "CollectionLimits",
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>",
+ "MetaUpdatePermission": "MetaUpdatePermission",
"TransfersEnabled": "bool"
},
"RawData": "Vec<u8>",
@@ -94,6 +95,13 @@
"Unique"
]
},
+ "MetaUpdatePermission": {
+ "_enum": [
+ "ItemOwner",
+ "Admin",
+ "None"
+ ]
+ },
"CollectionId": "u32",
"TokenId": "u32",
"ChainLimits": {
tests/src/addCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -4,7 +4,6 @@
//
import { ApiPromise } from '@polkadot/api';
-import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import privateKey from './substrate/privateKey';
tests/src/collision-tests/adminLimitsOff.test.tsdiffbeforeafterboth--- a/tests/src/collision-tests/adminLimitsOff.test.ts
+++ b/tests/src/collision-tests/adminLimitsOff.test.ts
@@ -1,5 +1,4 @@
import { IKeyringPair } from '@polkadot/types/types';
-import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import privateKey from '../substrate/privateKey';
tests/src/metadataUpdate.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/metadataUpdate.test.ts
@@ -0,0 +1,204 @@
+
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ createItemExpectSuccess,
+ createCollectionExpectSuccess,
+ enablePublicMintingExpectSuccess,
+ enableWhiteListExpectSuccess,
+ setMetadataUpdatePermissionFlagExpectSuccess,
+ setVariableMetaDataExpectSuccess,
+ setMintPermissionExpectSuccess,
+ addToWhiteListExpectSuccess,
+ addCollectionAdminExpectSuccess,
+ setVariableMetaDataExpectFailure,
+ setMetadataUpdatePermissionFlagExpectFailure,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+
+describe('Metadata update permissions with ItemOwner flag', () => {
+ it('ItemOwner can set variable metadata with ItemOwner permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+ await setVariableMetaDataExpectSuccess(Alice, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('Admin can\'n set variable metadata with ItemOwner permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('User can\'n set variable metadata with ItemOwner permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
+
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+});
+
+describe('Metadata update permissions with Admin flag', () => {
+ it('Admin can set variable metadata with Admin permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+
+ await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('User can\'n can set variable metadata with Admin permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+
+ await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('ItemOwner can\'n can set variable metadata with Admin permission flag', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ await enablePublicMintingExpectSuccess(Alice, nftCollectionId);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await enableWhiteListExpectSuccess(Alice, nftCollectionId);
+ const newNftTokenId = await createItemExpectSuccess(Bob, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
+
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+});
+
+describe('Metadata update permissions with None flag', () => {
+ it('Nobody can set variable metadata with None flag (Regular)', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('Nobody can set variable metadata with None flag (Admin)', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('Nobody can set variable metadata with None flag (ItemOwner)', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+
+ const data = [1, 2, 254, 255];
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+
+ await setVariableMetaDataExpectFailure(Alice, nftCollectionId, newNftTokenId, data);
+ });
+ });
+
+ it('Nobody can set variable metadata flag after freeze', async () => {
+ await usingApi(async () => {
+ const Alice = privateKey('//Alice');
+
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+ await setMetadataUpdatePermissionFlagExpectFailure(Alice, nftCollectionId, 'Admin');
+ });
+ });
+});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -525,6 +525,28 @@
});
}
+export async function setMetadataUpdatePermissionFlagExpectSuccess(sender: IKeyringPair, collectionId: number, flag: string) {
+
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.true;
+ });
+}
+
+export async function setMetadataUpdatePermissionFlagExpectFailure(sender: IKeyringPair, collectionId: number, flag: string) {
+
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.false;
+ });
+}
+
export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {
await usingApi(async (api) => {
const tx = api.tx.nft.enableContractSponsoring(contractAddress, enable);