mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-29 14:57:56 +00:00
[NFTs] Rework permissions model (#13482)
* Disallow admin to transfer or burn items he doesn't own * lock_collection should be accessible by collection's owner only * Allow admin to access lock_item_properties() * Fix do_lock_item_properties * Move update_mint_settings() to Issuer * Rename check_owner to check_origin * Typo * Make admin to be in charge of managing the metadata * Make admin the main attributes manager * offchain mint should be signed by Issuer * Remove the special case when the Issuer calls the mint() function * Rework burn and destroy methods * Return back item_metadatas * Don't repatriate the deposit on transfer * A bit more tests * One more test * Add migration * Chore * Clippy * Rename to owned_item * Address comments * Replace .filter_map with .find_map * Improve version validation in pre_upgrade() * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_nfts --------- Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -215,21 +215,44 @@ fn lifecycle_should_work() {
|
||||
assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().items, 3);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_metadatas, 0);
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_configs, 3);
|
||||
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 8);
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2)));
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 1);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 8);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![42, 42]));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 10);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 11);
|
||||
assert!(ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![69, 69]));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 13);
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 14);
|
||||
assert!(ItemMetadataOf::<Test>::contains_key(0, 69));
|
||||
assert!(ItemConfigOf::<Test>::contains_key(0, 69));
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_eq!(w.item_metadatas, 2);
|
||||
assert_eq!(w.item_configs, 3);
|
||||
assert_noop!(
|
||||
Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w),
|
||||
Error::<Test>::CollectionNotEmpty
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::set_attribute(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Some(69),
|
||||
AttributeNamespace::CollectionOwner,
|
||||
bvec![0],
|
||||
bvec![0],
|
||||
));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70));
|
||||
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_eq!(w.items, 3);
|
||||
assert_eq!(w.item_metadatas, 2);
|
||||
assert_eq!(w.attributes, 1);
|
||||
assert_eq!(w.item_metadatas, 0);
|
||||
assert_eq!(w.item_configs, 0);
|
||||
assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 0);
|
||||
|
||||
@@ -240,6 +263,8 @@ fn lifecycle_should_work() {
|
||||
assert!(!CollectionMetadataOf::<Test>::contains_key(0));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 69));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 69));
|
||||
assert_eq!(attributes(0), vec![]);
|
||||
assert_eq!(collections(), vec![]);
|
||||
assert_eq!(items(), vec![]);
|
||||
});
|
||||
@@ -256,14 +281,51 @@ fn destroy_with_bad_witness_should_not_work() {
|
||||
));
|
||||
|
||||
let w = Collection::<Test>::get(0).unwrap().destroy_witness();
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
|
||||
assert_noop!(
|
||||
Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w),
|
||||
Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
DestroyWitness { item_configs: 1, ..w }
|
||||
),
|
||||
Error::<Test>::BadWitness
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroy_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Balances::make_free_balance_be(&account(1), 100);
|
||||
assert_ok!(Nfts::create(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
account(1),
|
||||
collection_config_with_all_settings_enabled()
|
||||
));
|
||||
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None));
|
||||
assert_noop!(
|
||||
Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Nfts::get_destroy_witness(&0).unwrap()
|
||||
),
|
||||
Error::<Test>::CollectionNotEmpty
|
||||
);
|
||||
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42));
|
||||
assert_eq!(Collection::<Test>::get(0).unwrap().item_configs, 1);
|
||||
assert_eq!(ItemConfigOf::<Test>::iter_prefix(0).count() as u32, 1);
|
||||
assert!(ItemConfigOf::<Test>::contains_key(0, 42));
|
||||
assert_ok!(Nfts::destroy(
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
Nfts::get_destroy_witness(&0).unwrap()
|
||||
));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 42));
|
||||
assert_eq!(ItemConfigOf::<Test>::iter_prefix(0).count() as u32, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mint_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -491,8 +553,9 @@ fn origin_guards_should_work() {
|
||||
Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 69, account(2), None),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 43, account(2), None));
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42, None),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 43),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
@@ -536,13 +599,13 @@ fn transfer_owner_should_work() {
|
||||
|
||||
// Mint and set metadata now and make sure that deposit gets transferred back.
|
||||
assert_ok!(Nfts::set_collection_metadata(
|
||||
RuntimeOrigin::signed(account(2)),
|
||||
RuntimeOrigin::signed(account(1)),
|
||||
0,
|
||||
bvec![0u8; 20],
|
||||
));
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 1);
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(2)), 0, 42, bvec![0u8; 20]));
|
||||
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20]));
|
||||
assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(3)), Some(0)));
|
||||
assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(3)));
|
||||
assert_eq!(collections(), vec![(account(3), 0)]);
|
||||
@@ -552,8 +615,9 @@ fn transfer_owner_should_work() {
|
||||
assert_eq!(Balances::reserved_balance(&account(3)), 44);
|
||||
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)));
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 0);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 1);
|
||||
// reserved_balance of accounts 1 & 2 should be unchanged:
|
||||
assert_eq!(Balances::reserved_balance(&account(1)), 1);
|
||||
assert_eq!(Balances::reserved_balance(&account(2)), 0);
|
||||
|
||||
// 2's acceptance from before is reset when it became an owner, so it cannot be transferred
|
||||
// without a fresh acceptance.
|
||||
@@ -583,8 +647,14 @@ fn set_team_should_work() {
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 42, account(2), None));
|
||||
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
|
||||
assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
|
||||
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42, None));
|
||||
assert_noop!(
|
||||
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -594,7 +664,7 @@ fn set_collection_metadata_should_work() {
|
||||
// Cannot add metadata to unknown item
|
||||
assert_noop!(
|
||||
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 20]),
|
||||
Error::<Test>::NoConfig,
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
assert_ok!(Nfts::force_create(
|
||||
RuntimeOrigin::root(),
|
||||
@@ -668,7 +738,7 @@ fn set_collection_metadata_should_work() {
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 1),
|
||||
Error::<Test>::UnknownCollection
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0),
|
||||
@@ -743,7 +813,7 @@ fn set_item_metadata_should_work() {
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 1, 42),
|
||||
Error::<Test>::MetadataNotFound,
|
||||
Error::<Test>::NoPermission,
|
||||
);
|
||||
assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42));
|
||||
assert!(!ItemMetadataOf::<Test>::contains_key(0, 42));
|
||||
@@ -832,6 +902,7 @@ fn set_collection_owner_attributes_should_work() {
|
||||
);
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 16);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0));
|
||||
let w = Nfts::get_destroy_witness(&0).unwrap();
|
||||
assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w));
|
||||
assert_eq!(attributes(0), vec![]);
|
||||
@@ -997,7 +1068,7 @@ fn set_item_owner_attributes_should_work() {
|
||||
assert_eq!(Balances::reserved_balance(account(3)), 13);
|
||||
|
||||
// validate attributes on item deletion
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0, None));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0));
|
||||
assert_eq!(
|
||||
attributes(0),
|
||||
vec![
|
||||
@@ -1317,7 +1388,7 @@ fn preserve_config_for_frozen_items() {
|
||||
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None));
|
||||
|
||||
// if the item is not locked/frozen then the config gets deleted on item burn
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1, Some(account(1))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1));
|
||||
assert!(!ItemConfigOf::<Test>::contains_key(0, 1));
|
||||
|
||||
// lock the item and ensure the config stays unchanged
|
||||
@@ -1329,7 +1400,7 @@ fn preserve_config_for_frozen_items() {
|
||||
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
|
||||
assert_eq!(config, expect_config);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0, Some(account(1))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0));
|
||||
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
|
||||
assert_eq!(config, expect_config);
|
||||
|
||||
@@ -1405,6 +1476,7 @@ fn force_update_collection_should_work() {
|
||||
|
||||
Balances::make_free_balance_be(&account(5), 100);
|
||||
assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, account(5)));
|
||||
assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, account(2), account(5), account(4)));
|
||||
assert_eq!(collections(), vec![(account(5), 0)]);
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 2);
|
||||
assert_eq!(Balances::reserved_balance(account(5)), 63);
|
||||
@@ -1475,7 +1547,7 @@ fn burn_works() {
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(5))),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
|
||||
@@ -1496,16 +1568,12 @@ fn burn_works() {
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 2);
|
||||
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42, None),
|
||||
Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42),
|
||||
Error::<Test>::NoPermission
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(6))),
|
||||
Error::<Test>::WrongOwner
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42, Some(account(5))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 69, Some(account(5))));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69));
|
||||
assert_eq!(Balances::reserved_balance(account(1)), 0);
|
||||
});
|
||||
}
|
||||
@@ -1819,21 +1887,21 @@ fn cancel_approval_works_with_admin() {
|
||||
None
|
||||
));
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 1, 42, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 43, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)),
|
||||
Error::<Test>::UnknownItem
|
||||
);
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(4)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)),
|
||||
Error::<Test>::NotDelegate
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(3)));
|
||||
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)));
|
||||
assert_noop!(
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(1)), 0, 42, account(1)),
|
||||
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)),
|
||||
Error::<Test>::NotDelegate
|
||||
);
|
||||
});
|
||||
@@ -3013,6 +3081,7 @@ fn add_remove_item_attributes_approval_should_work() {
|
||||
#[test]
|
||||
fn pre_signed_mints_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user_0 = account(0);
|
||||
let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap();
|
||||
let user_1_signer = MultiSigner::Sr25519(user_1_pair.public());
|
||||
let user_1 = user_1_signer.clone().into_account();
|
||||
@@ -3029,10 +3098,10 @@ fn pre_signed_mints_should_work() {
|
||||
let user_2 = account(2);
|
||||
let user_3 = account(3);
|
||||
|
||||
Balances::make_free_balance_be(&user_1, 100);
|
||||
Balances::make_free_balance_be(&user_0, 100);
|
||||
Balances::make_free_balance_be(&user_2, 100);
|
||||
assert_ok!(Nfts::create(
|
||||
RuntimeOrigin::signed(user_1.clone()),
|
||||
RuntimeOrigin::signed(user_0.clone()),
|
||||
user_1.clone(),
|
||||
collection_config_with_all_settings_enabled(),
|
||||
));
|
||||
@@ -3069,7 +3138,7 @@ fn pre_signed_mints_should_work() {
|
||||
assert_eq!(deposit.account, Some(user_2.clone()));
|
||||
assert_eq!(deposit.amount, 3);
|
||||
|
||||
assert_eq!(Balances::free_balance(&user_1), 100 - 2); // 2 - collection deposit
|
||||
assert_eq!(Balances::free_balance(&user_0), 100 - 2); // 2 - collection deposit
|
||||
assert_eq!(Balances::free_balance(&user_2), 100 - 1 - 3 - 6); // 1 - item deposit, 3 - metadata, 6 - attributes
|
||||
|
||||
assert_noop!(
|
||||
@@ -3082,7 +3151,7 @@ fn pre_signed_mints_should_work() {
|
||||
Error::<Test>::AlreadyExists
|
||||
);
|
||||
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0, Some(user_2.clone())));
|
||||
assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0));
|
||||
assert_eq!(Balances::free_balance(&user_2), 100 - 6);
|
||||
|
||||
// validate the `only_account` field
|
||||
|
||||
Reference in New Issue
Block a user