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.rsdiffbeforeafterboth--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -185,6 +185,7 @@
pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions
pub variable_on_chain_schema: Vec<u8>, //
pub const_on_chain_schema: Vec<u8>, //
+ pub meta_update_permission: MetaUpdatePermission,
pub transfers_enabled: bool,
}
@@ -305,6 +306,20 @@
pub pieces: u128,
}
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum MetaUpdatePermission {
+ ItemOwner,
+ Admin,
+ None,
+}
+
+impl Default for MetaUpdatePermission {
+ fn default() -> Self {
+ Self::ItemOwner
+ }
+}
+
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum CreateItemData {
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.tsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import { ApiPromise } from '@polkadot/api';7import BN from 'bn.js';8import chai from 'chai';9import chaiAsPromised from 'chai-as-promised';10import privateKey from './substrate/privateKey';11import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';12import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, normalizeAccountId} from './util/helpers';1314chai.use(chaiAsPromised);15const expect = chai.expect;1617describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {18 it('Add collection admin.', async () => {19 await usingApi(async (api) => {20 const collectionId = await createCollectionExpectSuccess();21 const alice = privateKey('//Alice');22 const bob = privateKey('//Bob');2324 const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();25 expect(collection.Owner).to.be.equal(alice.address);2627 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));28 await submitTransactionAsync(alice, changeAdminTx);2930 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));31 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(bob.address));32 });33 });3435 it('Add admin using added collection admin.', async () => {36 await usingApi(async (api) => {37 const collectionId = await createCollectionExpectSuccess();38 const Alice = privateKey('//Alice');39 const Bob = privateKey('//Bob');40 const Charlie = privateKey('//CHARLIE');4142 const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();43 expect(collection.Owner).to.be.equal(Alice.address);4445 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address));46 await submitTransactionAsync(Alice, changeAdminTx);4748 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));49 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Bob.address));5051 const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Charlie.address));52 await submitTransactionAsync(Bob, changeAdminTxCharlie);53 const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId));54 expect(adminListAfterAddNewAdmin).to.be.contains(normalizeAccountId(Bob.address));55 expect(adminListAfterAddNewAdmin).to.be.contains(normalizeAccountId(Charlie.address));56 });57 });58});5960describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {61 it("Not owner can't add collection admin.", async () => {62 await usingApi(async (api) => {63 const collectionId = await createCollectionExpectSuccess();64 const alice = privateKey('//Alice');65 const nonOwner = privateKey('//Bob_stash');6667 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(alice.address));68 await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected;6970 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));71 expect(adminListAfterAddAdmin).not.to.be.contains(normalizeAccountId(alice.address));7273 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)74 await createCollectionExpectSuccess();75 });76 });77 it("Can't add collection admin of not existing collection.", async () => {78 await usingApi(async (api) => {79 // tslint:disable-next-line: no-bitwise80 const collectionId = (1 << 32) - 1;81 const alice = privateKey('//Alice');82 const bob = privateKey('//Bob');8384 const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));85 await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;8687 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)88 await createCollectionExpectSuccess();89 });90 });9192 it("Can't add an admin to a destroyed collection.", async () => {93 await usingApi(async (api) => {94 const collectionId = await createCollectionExpectSuccess();95 const Alice = privateKey('//Alice');96 const Bob = privateKey('//Bob');97 await destroyCollectionExpectSuccess(collectionId);98 const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address));99 await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected;100101 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)102 await createCollectionExpectSuccess();103 });104 });105106 it('Add an admin to a collection that has reached the maximum number of admins limit', async () => {107 await usingApi(async (api: ApiPromise) => {108 const Alice = privateKey('//Alice');109 const accounts = [110 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz',111 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP',112 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5',113 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P',114 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK',115 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam',116 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM',117 ];118 const collectionId = await createCollectionExpectSuccess();119120 const chainAdminLimit = (api.consts.nft.collectionAdminsLimit as any).toNumber();121 expect(chainAdminLimit).to.be.equal(5);122123 for (let i = 0; i < chainAdminLimit; i++) {124 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(accounts[i]));125 await submitTransactionAsync(Alice, changeAdminTx);126 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));127 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(accounts[i]));128 }129130 const tx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(accounts[chainAdminLimit]));131 await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected;132 });133 });134});1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56import { ApiPromise } from '@polkadot/api';7import chai from 'chai';8import chaiAsPromised from 'chai-as-promised';9import privateKey from './substrate/privateKey';10import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';11import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, normalizeAccountId} from './util/helpers';1213chai.use(chaiAsPromised);14const expect = chai.expect;1516describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {17 it('Add collection admin.', async () => {18 await usingApi(async (api) => {19 const collectionId = await createCollectionExpectSuccess();20 const alice = privateKey('//Alice');21 const bob = privateKey('//Bob');2223 const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();24 expect(collection.Owner).to.be.equal(alice.address);2526 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));27 await submitTransactionAsync(alice, changeAdminTx);2829 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));30 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(bob.address));31 });32 });3334 it('Add admin using added collection admin.', async () => {35 await usingApi(async (api) => {36 const collectionId = await createCollectionExpectSuccess();37 const Alice = privateKey('//Alice');38 const Bob = privateKey('//Bob');39 const Charlie = privateKey('//CHARLIE');4041 const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();42 expect(collection.Owner).to.be.equal(Alice.address);4344 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address));45 await submitTransactionAsync(Alice, changeAdminTx);4647 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));48 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Bob.address));4950 const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Charlie.address));51 await submitTransactionAsync(Bob, changeAdminTxCharlie);52 const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId));53 expect(adminListAfterAddNewAdmin).to.be.contains(normalizeAccountId(Bob.address));54 expect(adminListAfterAddNewAdmin).to.be.contains(normalizeAccountId(Charlie.address));55 });56 });57});5859describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {60 it("Not owner can't add collection admin.", async () => {61 await usingApi(async (api) => {62 const collectionId = await createCollectionExpectSuccess();63 const alice = privateKey('//Alice');64 const nonOwner = privateKey('//Bob_stash');6566 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(alice.address));67 await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected;6869 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));70 expect(adminListAfterAddAdmin).not.to.be.contains(normalizeAccountId(alice.address));7172 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)73 await createCollectionExpectSuccess();74 });75 });76 it("Can't add collection admin of not existing collection.", async () => {77 await usingApi(async (api) => {78 // tslint:disable-next-line: no-bitwise79 const collectionId = (1 << 32) - 1;80 const alice = privateKey('//Alice');81 const bob = privateKey('//Bob');8283 const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(bob.address));84 await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;8586 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)87 await createCollectionExpectSuccess();88 });89 });9091 it("Can't add an admin to a destroyed collection.", async () => {92 await usingApi(async (api) => {93 const collectionId = await createCollectionExpectSuccess();94 const Alice = privateKey('//Alice');95 const Bob = privateKey('//Bob');96 await destroyCollectionExpectSuccess(collectionId);97 const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address));98 await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected;99100 // Verifying that nothing bad happened (network is live, new collections can be created, etc.)101 await createCollectionExpectSuccess();102 });103 });104105 it('Add an admin to a collection that has reached the maximum number of admins limit', async () => {106 await usingApi(async (api: ApiPromise) => {107 const Alice = privateKey('//Alice');108 const accounts = [109 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz',110 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP',111 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5',112 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P',113 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK',114 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam',115 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM',116 ];117 const collectionId = await createCollectionExpectSuccess();118119 const chainAdminLimit = (api.consts.nft.collectionAdminsLimit as any).toNumber();120 expect(chainAdminLimit).to.be.equal(5);121122 for (let i = 0; i < chainAdminLimit; i++) {123 const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(accounts[i]));124 await submitTransactionAsync(Alice, changeAdminTx);125 const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));126 expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(accounts[i]));127 }128129 const tx = api.tx.nft.addCollectionAdmin(collectionId, normalizeAccountId(accounts[chainAdminLimit]));130 await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected;131 });132 });133});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);