Files
pezkuwi-subxt/substrate/frame/nfts/src/tests.rs
T
Liam Aharon 9d62618928 pallet-uniques: decrement total_deposit when clearing collection metadata (#3976)
Decrements `total_deposit` when collection metadata is cleared in
`pallet-nfts` and `pallet-uniques`.
2024-04-06 05:10:46 +00:00

3879 lines
101 KiB
Rust

// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Tests for Nfts pallet.
use crate::{mock::*, Event, SystemConfig, *};
use enumflags2::BitFlags;
use frame_support::{
assert_noop, assert_ok,
traits::{
tokens::nonfungibles_v2::{Create, Destroy, Inspect, Mutate},
Currency, Get,
},
};
use pallet_balances::Error as BalancesError;
use sp_core::{bounded::BoundedVec, Pair};
use sp_runtime::{
traits::{Dispatchable, IdentifyAccount},
MultiSignature, MultiSigner,
};
use sp_std::prelude::*;
type AccountIdOf<Test> = <Test as frame_system::Config>::AccountId;
fn account(id: u8) -> AccountIdOf<Test> {
[id; 32].into()
}
fn items() -> Vec<(AccountIdOf<Test>, u32, u32)> {
let mut r: Vec<_> = Account::<Test>::iter().map(|x| x.0).collect();
r.sort();
let mut s: Vec<_> = Item::<Test>::iter().map(|x| (x.2.owner, x.0, x.1)).collect();
s.sort();
assert_eq!(r, s);
for collection in Item::<Test>::iter()
.map(|x| x.0)
.scan(None, |s, item| {
if s.map_or(false, |last| last == item) {
*s = Some(item);
Some(None)
} else {
Some(Some(item))
}
})
.flatten()
{
let details = Collection::<Test>::get(collection).unwrap();
let items = Item::<Test>::iter_prefix(collection).count() as u32;
assert_eq!(details.items, items);
}
r
}
fn collections() -> Vec<(AccountIdOf<Test>, u32)> {
let mut r: Vec<_> = CollectionAccount::<Test>::iter().map(|x| (x.0, x.1)).collect();
r.sort();
let mut s: Vec<_> = Collection::<Test>::iter().map(|x| (x.1.owner, x.0)).collect();
s.sort();
assert_eq!(r, s);
r
}
macro_rules! bvec {
($( $x:tt )*) => {
vec![$( $x )*].try_into().unwrap()
}
}
fn attributes(
collection: u32,
) -> Vec<(Option<u32>, AttributeNamespace<AccountIdOf<Test>>, Vec<u8>, Vec<u8>)> {
let mut s: Vec<_> = Attribute::<Test>::iter_prefix((collection,))
.map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into()))
.collect();
s.sort_by_key(|k: &(Option<u32>, AttributeNamespace<AccountIdOf<Test>>, Vec<u8>, Vec<u8>)| k.0);
s.sort_by_key(|k: &(Option<u32>, AttributeNamespace<AccountIdOf<Test>>, Vec<u8>, Vec<u8>)| {
k.2.clone()
});
s
}
fn approvals(collection_id: u32, item_id: u32) -> Vec<(AccountIdOf<Test>, Option<u64>)> {
let item = Item::<Test>::get(collection_id, item_id).unwrap();
let s: Vec<_> = item.approvals.into_iter().collect();
s
}
fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec<AccountIdOf<Test>> {
let approvals = ItemAttributesApprovalsOf::<Test>::get(collection_id, item_id);
let s: Vec<_> = approvals.into_iter().collect();
s
}
fn events() -> Vec<Event<Test>> {
let result = System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| if let mock::RuntimeEvent::Nfts(inner) = e { Some(inner) } else { None })
.collect::<Vec<_>>();
System::reset_events();
result
}
fn collection_config_from_disabled_settings(
settings: BitFlags<CollectionSetting>,
) -> CollectionConfigFor<Test> {
CollectionConfig {
settings: CollectionSettings::from_disabled(settings),
max_supply: None,
mint_settings: MintSettings::default(),
}
}
fn collection_config_with_all_settings_enabled() -> CollectionConfigFor<Test> {
CollectionConfig {
settings: CollectionSettings::all_enabled(),
max_supply: None,
mint_settings: MintSettings::default(),
}
}
fn default_collection_config() -> CollectionConfigFor<Test> {
collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into())
}
fn default_item_config() -> ItemConfig {
ItemConfig { settings: ItemSettings::all_enabled() }
}
fn item_config_from_disabled_settings(settings: BitFlags<ItemSetting>) -> ItemConfig {
ItemConfig { settings: ItemSettings::from_disabled(settings) }
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
assert_eq!(items(), vec![]);
});
}
#[test]
fn basic_minting_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_eq!(collections(), vec![(account(1), 0)]);
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
assert_eq!(items(), vec![(account(1), 0, 42)]);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(2),
default_collection_config()
));
assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]);
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None));
assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]);
});
}
#[test]
fn lifecycle_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
assert_ok!(Nfts::create(
RuntimeOrigin::signed(account(1)),
account(1),
collection_config_with_all_settings_enabled()
));
assert_eq!(Balances::reserved_balance(&account(1)), 2);
assert_eq!(collections(), vec![(account(1), 0)]);
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0, 0]
));
assert_eq!(Balances::reserved_balance(&account(1)), 5);
assert!(CollectionMetadataOf::<Test>::contains_key(0));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(10),
default_item_config()
));
assert_eq!(Balances::reserved_balance(&account(1)), 6);
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
69,
account(20),
default_item_config()
));
assert_eq!(Balances::reserved_balance(&account(1)), 7);
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None));
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(1)), 8);
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2)));
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)), 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)), 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.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);
assert!(!Collection::<Test>::contains_key(0));
assert!(!CollectionConfigOf::<Test>::contains_key(0));
assert!(!Item::<Test>::contains_key(0, 42));
assert!(!Item::<Test>::contains_key(0, 69));
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![]);
});
}
#[test]
fn destroy_with_bad_witness_should_not_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()
));
let w = Collection::<Test>::get(0).unwrap().destroy_witness();
assert_noop!(
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(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
assert_eq!(Nfts::owner(0, 42).unwrap(), account(1));
assert_eq!(collections(), vec![(account(1), 0)]);
assert_eq!(items(), vec![(account(1), 0, 42)]);
// validate minting start and end settings
assert_ok!(Nfts::update_mint_settings(
RuntimeOrigin::signed(account(1)),
0,
MintSettings {
start_block: Some(2),
end_block: Some(3),
mint_type: MintType::Public,
..Default::default()
}
));
System::set_block_number(1);
assert_noop!(
Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(1), None),
Error::<Test>::MintNotStarted
);
System::set_block_number(4);
assert_noop!(
Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(1), None),
Error::<Test>::MintEnded
);
// validate price
assert_ok!(Nfts::update_mint_settings(
RuntimeOrigin::signed(account(1)),
0,
MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() }
));
Balances::make_free_balance_be(&account(2), 100);
assert_noop!(
Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(2), None,),
Error::<Test>::WitnessRequired
);
assert_noop!(
Nfts::mint(
RuntimeOrigin::signed(account(2)),
0,
43,
account(2),
Some(MintWitness { ..Default::default() })
),
Error::<Test>::BadWitness
);
assert_noop!(
Nfts::mint(
RuntimeOrigin::signed(account(2)),
0,
43,
account(2),
Some(MintWitness { mint_price: Some(0), ..Default::default() })
),
Error::<Test>::BadWitness
);
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(2)),
0,
43,
account(2),
Some(MintWitness { mint_price: Some(1), ..Default::default() })
));
assert_eq!(Balances::total_balance(&account(2)), 99);
// validate types
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::update_mint_settings(
RuntimeOrigin::signed(account(1)),
1,
MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() }
));
assert_noop!(
Nfts::mint(RuntimeOrigin::signed(account(3)), 1, 42, account(3), None),
Error::<Test>::WitnessRequired
);
assert_noop!(
Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 42, account(2), None),
Error::<Test>::WitnessRequired
);
assert_noop!(
Nfts::mint(
RuntimeOrigin::signed(account(2)),
1,
42,
account(2),
Some(MintWitness { owned_item: Some(42), ..Default::default() })
),
Error::<Test>::BadWitness
);
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(2)),
1,
42,
account(2),
Some(MintWitness { owned_item: Some(43), ..Default::default() })
));
assert!(events().contains(&Event::<Test>::PalletAttributeSet {
collection: 0,
item: Some(43),
attribute: PalletAttributes::<<Test as Config>::CollectionId>::UsedToClaim(1),
value: Nfts::construct_attribute_value(vec![]).unwrap(),
}));
// can't mint twice
assert_noop!(
Nfts::mint(
RuntimeOrigin::signed(account(2)),
1,
46,
account(2),
Some(MintWitness { owned_item: Some(43), ..Default::default() })
),
Error::<Test>::AlreadyClaimed
);
});
}
#[test]
fn transfer_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3)));
assert_eq!(items(), vec![(account(3), 0, 42)]);
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)),
Error::<Test>::NoPermission
);
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(3)),
0,
42,
account(2),
None
));
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)));
// validate we can't transfer non-transferable items
let collection_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_from_disabled_settings(
CollectionSetting::TransferableItems | CollectionSetting::DepositRequired
)
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
1,
1,
account(42),
default_item_config()
));
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(1)), collection_id, 42, account(3)),
Error::<Test>::ItemsNonTransferable
);
});
}
#[test]
fn locking_transfer_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42));
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)),
Error::<Test>::ItemLocked
);
assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42));
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(account(1)),
0,
CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into())
));
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)),
Error::<Test>::ItemsNonTransferable
);
assert_ok!(Nfts::force_collection_config(
RuntimeOrigin::root(),
0,
collection_config_with_all_settings_enabled(),
));
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)));
});
}
#[test]
fn origin_guards_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
Balances::make_free_balance_be(&account(2), 100);
assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0)));
assert_noop!(
Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(2)),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::set_team(
RuntimeOrigin::signed(account(2)),
0,
Some(account(2)),
Some(account(2)),
Some(account(2)),
),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::lock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42),
Error::<Test>::NoPermission
);
assert_noop!(
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(1)), 0, 43),
Error::<Test>::NoPermission
);
let w = Nfts::get_destroy_witness(&0).unwrap();
assert_noop!(
Nfts::destroy(RuntimeOrigin::signed(account(2)), 0, w),
Error::<Test>::NoPermission
);
});
}
#[test]
fn transfer_owner_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
Balances::make_free_balance_be(&account(3), 100);
assert_ok!(Nfts::create(
RuntimeOrigin::signed(account(1)),
account(1),
collection_config_with_all_settings_enabled()
));
assert_eq!(collections(), vec![(account(1), 0)]);
assert_noop!(
Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2)),
Error::<Test>::Unaccepted
);
assert_eq!(System::consumers(&account(2)), 0);
assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0)));
assert_eq!(System::consumers(&account(2)), 1);
assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2)));
assert_eq!(System::consumers(&account(2)), 1); // one consumer is added due to deposit repatriation
assert_eq!(collections(), vec![(account(2), 0)]);
assert_eq!(Balances::total_balance(&account(1)), 98);
assert_eq!(Balances::total_balance(&account(2)), 102);
assert_eq!(Balances::reserved_balance(&account(1)), 0);
assert_eq!(Balances::reserved_balance(&account(2)), 2);
assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(1)), Some(0)));
assert_noop!(
Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(1)),
Error::<Test>::NoPermission
);
// Mint and set metadata now and make sure that deposit gets transferred back.
assert_ok!(Nfts::set_collection_metadata(
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(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)]);
assert_eq!(Balances::total_balance(&account(2)), 58);
assert_eq!(Balances::total_balance(&account(3)), 144);
assert_eq!(Balances::reserved_balance(&account(2)), 0);
assert_eq!(Balances::reserved_balance(&account(3)), 44);
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)));
// 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.
assert_noop!(
Nfts::transfer_ownership(RuntimeOrigin::signed(account(3)), 0, account(2)),
Error::<Test>::Unaccepted
);
});
}
#[test]
fn set_team_should_work() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config(),
));
assert_ok!(Nfts::set_team(
RuntimeOrigin::signed(account(1)),
0,
Some(account(2)),
Some(account(3)),
Some(account(4)),
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 42, account(2), None));
// admin can't transfer/burn items he doesn't own
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
);
assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42));
// validate we can set any role to None
assert_ok!(Nfts::set_team(
RuntimeOrigin::signed(account(1)),
0,
Some(account(2)),
Some(account(3)),
None,
));
assert_noop!(
Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42),
Error::<Test>::NoPermission
);
// set all the roles to None
assert_ok!(Nfts::set_team(RuntimeOrigin::signed(account(1)), 0, None, None, None,));
// validate we can't set the roles back
assert_noop!(
Nfts::set_team(
RuntimeOrigin::signed(account(1)),
0,
Some(account(2)),
Some(account(3)),
None,
),
Error::<Test>::NoPermission
);
// only the root account can change the roles from None to Some()
assert_ok!(Nfts::set_team(
RuntimeOrigin::root(),
0,
Some(account(2)),
Some(account(3)),
None,
));
});
}
#[test]
fn set_collection_metadata_should_work() {
new_test_ext().execute_with(|| {
// Cannot add metadata to unknown item
assert_noop!(
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 20]),
Error::<Test>::NoPermission,
);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
// Cannot add metadata to unowned item
assert_noop!(
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(2)), 0, bvec![0u8; 20]),
Error::<Test>::NoPermission,
);
// Successfully add metadata and take deposit
Balances::make_free_balance_be(&account(1), 30);
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0u8; 20]
));
assert_eq!(Balances::free_balance(&account(1)), 9);
assert!(CollectionMetadataOf::<Test>::contains_key(0));
// Force origin works, too.
assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18]));
// Update deposit
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0u8; 15]
));
assert_eq!(Balances::free_balance(&account(1)), 14);
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0u8; 25]
));
assert_eq!(Balances::free_balance(&account(1)), 4);
// Cannot over-reserve
assert_noop!(
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 40]),
BalancesError::<Test, _>::InsufficientBalance,
);
// Can't set or clear metadata once frozen
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0u8; 15]
));
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(account(1)),
0,
CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into())
));
assert_noop!(
Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 15]),
Error::<Test, _>::LockedCollectionMetadata,
);
assert_noop!(
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0),
Error::<Test>::LockedCollectionMetadata
);
// Clear Metadata
assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15]));
assert_noop!(
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(2)), 0),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 1),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0),
Error::<Test>::LockedCollectionMetadata
);
assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0));
assert!(!CollectionMetadataOf::<Test>::contains_key(0));
});
}
#[test]
fn set_item_metadata_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 30);
// Cannot add metadata to unknown item
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
// Cannot add metadata to unowned item
assert_noop!(
Nfts::set_metadata(RuntimeOrigin::signed(account(2)), 0, 42, bvec![0u8; 20]),
Error::<Test>::NoPermission,
);
// Successfully add metadata and take deposit
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20]));
assert_eq!(Balances::free_balance(&account(1)), 8);
assert!(ItemMetadataOf::<Test>::contains_key(0, 42));
// Force origin works, too.
assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18]));
// Update deposit
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15]));
assert_eq!(Balances::free_balance(&account(1)), 13);
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 25]));
assert_eq!(Balances::free_balance(&account(1)), 3);
// Cannot over-reserve
assert_noop!(
Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 40]),
BalancesError::<Test, _>::InsufficientBalance,
);
// Can't set or clear metadata once frozen
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15]));
assert_ok!(Nfts::lock_item_properties(
RuntimeOrigin::signed(account(1)),
0,
42,
true,
false
));
assert_noop!(
Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15]),
Error::<Test, _>::LockedItemMetadata,
);
assert_noop!(
Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 0, 42),
Error::<Test>::LockedItemMetadata,
);
// Clear Metadata
assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15]));
assert_noop!(
Nfts::clear_metadata(RuntimeOrigin::signed(account(2)), 0, 42),
Error::<Test>::NoPermission,
);
assert_noop!(
Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 1, 42),
Error::<Test>::NoPermission,
);
assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42));
assert!(!ItemMetadataOf::<Test>::contains_key(0, 42));
});
}
#[test]
fn set_collection_owner_attributes_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
None,
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![1],
bvec![0],
));
assert_eq!(
attributes(0),
vec![
(None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(1)), 10);
assert_eq!(Collection::<Test>::get(0).unwrap().owner_deposit, 9);
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
None,
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0; 10],
));
assert_eq!(
attributes(0),
vec![
(None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(1)), 19);
assert_eq!(Collection::<Test>::get(0).unwrap().owner_deposit, 18);
assert_ok!(Nfts::clear_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![1],
));
assert_eq!(
attributes(0),
vec![
(None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0; 10]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
]
);
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![]);
assert_eq!(Balances::reserved_balance(account(1)), 0);
});
}
#[test]
fn set_collection_system_attributes_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None));
let collection_id = 0;
let attribute_key = [0u8];
let attribute_value = [0u8];
assert_ok!(<Nfts as Mutate<AccountIdOf<Test>, ItemConfig>>::set_collection_attribute(
&collection_id,
&attribute_key,
&attribute_value
));
assert_eq!(attributes(0), vec![(None, AttributeNamespace::Pallet, bvec![0], bvec![0])]);
assert_eq!(
<Nfts as Inspect<AccountIdOf<Test>>>::system_attribute(
&collection_id,
None,
&attribute_key
),
Some(attribute_value.to_vec())
);
// test typed system attribute
let typed_attribute_key = [0u8; 32];
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
struct TypedAttributeValue(u32);
let typed_attribute_value = TypedAttributeValue(42);
assert_ok!(
<Nfts as Mutate<AccountIdOf<Test>, ItemConfig>>::set_typed_collection_attribute(
&collection_id,
&typed_attribute_key,
&typed_attribute_value
)
);
assert_eq!(
<Nfts as Inspect<AccountIdOf<Test>>>::typed_system_attribute(
&collection_id,
None,
&typed_attribute_key
),
Some(typed_attribute_value)
);
// check storage
assert_eq!(
attributes(collection_id),
[
(None, AttributeNamespace::Pallet, bvec![0], bvec![0]),
(
None,
AttributeNamespace::Pallet,
bvec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0
],
bvec![42, 0, 0, 0]
)
]
);
assert_ok!(Nfts::burn(RuntimeOrigin::root(), collection_id, 0));
let w = Nfts::get_destroy_witness(&0).unwrap();
assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), collection_id, w));
assert_eq!(attributes(collection_id), vec![]);
})
}
#[test]
fn set_item_owner_attributes_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
Balances::make_free_balance_be(&account(3), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
0,
account(2),
default_item_config()
));
// can't set for the collection
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
None,
AttributeNamespace::ItemOwner,
bvec![0],
bvec![0],
),
Error::<Test>::NoPermission,
);
// can't set for the non-owned item
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![0],
bvec![0],
),
Error::<Test>::NoPermission,
);
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![1],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![2],
bvec![0],
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]),
(Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(2)), 9);
// validate an attribute can be updated
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![0],
bvec![0; 10],
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]),
(Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]),
(Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(2)), 18);
// validate only item's owner (or the root) can remove an attribute
assert_noop!(
Nfts::clear_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![1],
),
Error::<Test>::NoPermission,
);
assert_ok!(Nfts::clear_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![1],
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]),
(Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0])
]
);
assert_eq!(Balances::reserved_balance(account(2)), 15);
// transfer item
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 0, account(3)));
// validate the attribute are still here & the deposit belongs to the previous owner
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 10]),
(Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0])
]
);
let key: BoundedVec<_, _> = bvec![0];
let (_, deposit) =
Attribute::<Test>::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap();
assert_eq!(deposit.account, Some(account(2)));
assert_eq!(deposit.amount, 12);
// on attribute update the deposit should be returned to the previous owner
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(3)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![0],
bvec![0; 11],
));
let (_, deposit) =
Attribute::<Test>::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap();
assert_eq!(deposit.account, Some(account(3)));
assert_eq!(deposit.amount, 13);
assert_eq!(Balances::reserved_balance(account(2)), 3);
assert_eq!(Balances::reserved_balance(account(3)), 13);
// validate attributes on item deletion
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::ItemOwner, bvec![0], bvec![0; 11]),
(Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0])
]
);
assert_ok!(Nfts::clear_attribute(
RuntimeOrigin::signed(account(3)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![0],
));
assert_ok!(Nfts::clear_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![2],
));
assert_eq!(Balances::reserved_balance(account(2)), 0);
assert_eq!(Balances::reserved_balance(account(3)), 0);
});
}
#[test]
fn set_external_account_attributes_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
0,
account(1),
default_item_config()
));
assert_ok!(Nfts::approve_item_attributes(
RuntimeOrigin::signed(account(1)),
0,
0,
account(2)
));
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::Account(account(1)),
bvec![0],
bvec![0],
),
Error::<Test>::NoPermission,
);
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::Account(account(2)),
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::Account(account(2)),
bvec![1],
bvec![0],
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::Account(account(2)), bvec![0], bvec![0]),
(Some(0), AttributeNamespace::Account(account(2)), bvec![1], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(2)), 6);
// remove permission to set attributes
assert_ok!(Nfts::cancel_item_attributes_approval(
RuntimeOrigin::signed(account(1)),
0,
0,
account(2),
CancelAttributesApprovalWitness { account_attributes: 2 },
));
assert_eq!(attributes(0), vec![]);
assert_eq!(Balances::reserved_balance(account(2)), 0);
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::Account(account(2)),
bvec![0],
bvec![0],
),
Error::<Test>::NoPermission,
);
});
}
#[test]
fn validate_deposit_required_setting() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
Balances::make_free_balance_be(&account(3), 100);
// with the disabled DepositRequired setting, only the collection's owner can set the
// attributes for free.
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
0,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_item_attributes(
RuntimeOrigin::signed(account(2)),
0,
0,
account(3)
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(2)),
0,
Some(0),
AttributeNamespace::ItemOwner,
bvec![1],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(3)),
0,
Some(0),
AttributeNamespace::Account(account(3)),
bvec![2],
bvec![0],
));
assert_ok!(<Nfts as Mutate<<Test as SystemConfig>::AccountId, ItemConfig>>::set_attribute(
&0,
&0,
&[3],
&[0],
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]),
(Some(0), AttributeNamespace::Account(account(3)), bvec![2], bvec![0]),
(Some(0), AttributeNamespace::Pallet, bvec![3], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(1)), 0);
assert_eq!(Balances::reserved_balance(account(2)), 3);
assert_eq!(Balances::reserved_balance(account(3)), 3);
assert_ok!(
<Nfts as Mutate<<Test as SystemConfig>::AccountId, ItemConfig>>::clear_attribute(
&0,
&0,
&[3],
)
);
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]),
(Some(0), AttributeNamespace::Account(account(3)), bvec![2], bvec![0]),
]
);
});
}
#[test]
fn set_attribute_should_respect_lock() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled(),
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
None,
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(1),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
));
assert_eq!(
attributes(0),
vec![
(None, AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
(Some(1), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]),
]
);
assert_eq!(Balances::reserved_balance(account(1)), 11);
assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![]));
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(account(1)),
0,
CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into())
));
let e = Error::<Test>::LockedCollectionAttributes;
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
None,
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
),
e
);
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![1],
));
assert_ok!(Nfts::lock_item_properties(
RuntimeOrigin::signed(account(1)),
0,
0,
false,
true
));
let e = Error::<Test>::LockedItemAttributes;
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(0),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![1],
),
e
);
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(account(1)),
0,
Some(1),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![1],
));
});
}
#[test]
fn preserve_config_for_frozen_items() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None));
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));
assert!(!ItemConfigOf::<Test>::contains_key(0, 1));
// lock the item and ensure the config stays unchanged
assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(account(1)), 0, 0, true, true));
let expect_config = item_config_from_disabled_settings(
ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata,
);
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
assert_eq!(config, expect_config);
assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0));
let config = ItemConfigOf::<Test>::get(0, 0).unwrap();
assert_eq!(config, expect_config);
// can't mint with the different config
assert_noop!(
Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
0,
account(2),
default_item_config()
),
Error::<Test>::InconsistentItemConfig
);
assert_ok!(Nfts::update_mint_settings(
RuntimeOrigin::signed(account(1)),
0,
MintSettings {
default_item_settings: ItemSettings::from_disabled(
ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata
),
..Default::default()
}
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None));
});
}
#[test]
fn force_update_collection_should_work() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
69,
account(2),
default_item_config(),
));
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0; 20]
));
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0; 20]));
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![0; 20]));
assert_eq!(Balances::reserved_balance(account(1)), 65);
// force item status to be free holding
assert_ok!(Nfts::force_collection_config(
RuntimeOrigin::root(),
0,
collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()),
));
assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 142, account(1), None));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
169,
account(2),
default_item_config(),
));
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 142, bvec![0; 20]));
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 169, bvec![0; 20]));
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,
Some(account(2)),
Some(account(5)),
Some(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);
assert_ok!(Nfts::redeposit(
RuntimeOrigin::signed(account(5)),
0,
bvec![0, 42, 50, 69, 100]
));
assert_eq!(Balances::reserved_balance(account(1)), 0);
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 42, bvec![0; 20]));
assert_eq!(Balances::reserved_balance(account(5)), 42);
assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 69, bvec![0; 20]));
assert_eq!(Balances::reserved_balance(account(5)), 21);
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(5)),
0,
bvec![0; 20]
));
assert_eq!(Balances::reserved_balance(account(5)), 0);
// validate new roles
assert_ok!(Nfts::set_team(
RuntimeOrigin::root(),
0,
Some(account(2)),
Some(account(3)),
Some(account(4)),
));
assert_eq!(
CollectionRoleOf::<Test>::get(0, account(2)).unwrap(),
CollectionRoles(CollectionRole::Issuer.into())
);
assert_eq!(
CollectionRoleOf::<Test>::get(0, account(3)).unwrap(),
CollectionRoles(CollectionRole::Admin.into())
);
assert_eq!(
CollectionRoleOf::<Test>::get(0, account(4)).unwrap(),
CollectionRoles(CollectionRole::Freezer.into())
);
assert_ok!(Nfts::set_team(
RuntimeOrigin::root(),
0,
Some(account(3)),
Some(account(2)),
Some(account(3)),
));
assert_eq!(
CollectionRoleOf::<Test>::get(0, account(2)).unwrap(),
CollectionRoles(CollectionRole::Admin.into())
);
assert_eq!(
CollectionRoleOf::<Test>::get(0, account(3)).unwrap(),
CollectionRoles(CollectionRole::Issuer | CollectionRole::Freezer)
);
});
}
#[test]
fn burn_works() {
new_test_ext().execute_with(|| {
Balances::make_free_balance_be(&account(1), 100);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_with_all_settings_enabled()
));
assert_ok!(Nfts::set_team(
RuntimeOrigin::signed(account(1)),
0,
Some(account(2)),
Some(account(3)),
Some(account(4)),
));
assert_noop!(
Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42),
Error::<Test>::UnknownItem
);
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(2)),
0,
42,
account(5),
default_item_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(2)),
0,
69,
account(5),
default_item_config()
));
assert_eq!(Balances::reserved_balance(account(1)), 2);
assert_noop!(
Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42),
Error::<Test>::NoPermission
);
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);
});
}
#[test]
fn approval_lifecycle_works() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)));
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)),
Error::<Test>::NoPermission
);
assert!(Item::<Test>::get(0, 42).unwrap().approvals.is_empty());
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(4)),
0,
42,
account(2),
None
));
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(2)));
// ensure we can't buy an item when the collection has a NonTransferableItems flag
let collection_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_from_disabled_settings(
CollectionSetting::TransferableItems | CollectionSetting::DepositRequired
)
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(account(1)),
1,
collection_id,
account(1),
None,
));
assert_noop!(
Nfts::approve_transfer(
RuntimeOrigin::signed(account(1)),
collection_id,
1,
account(2),
None
),
Error::<Test>::ItemsNonTransferable
);
});
}
#[test]
fn cancel_approval_works() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)),
Error::<Test>::NotDelegate
);
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)),
Error::<Test>::NotDelegate
);
let current_block = 1;
System::set_block_number(current_block);
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
69,
account(2),
default_item_config()
));
// approval expires after 2 blocks.
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
Some(2)
));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)),
Error::<Test>::NoPermission
);
System::set_block_number(current_block + 3);
// 5 can cancel the approval since the deadline has passed.
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)));
assert_eq!(approvals(0, 69), vec![]);
});
}
#[test]
fn approving_multiple_accounts_works() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
let current_block = 1;
System::set_block_number(current_block);
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(4),
None
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(5),
Some(2)
));
assert_eq!(
approvals(0, 42),
vec![(account(3), None), (account(4), None), (account(5), Some(current_block + 2))]
);
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(6)));
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(7)),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(5)), 0, 42, account(8)),
Error::<Test>::NoPermission
);
});
}
#[test]
fn approvals_limit_works() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
for i in 3..13 {
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(i),
None
));
}
// the limit is 10
assert_noop!(
Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None),
Error::<Test>::ReachedApprovalLimit
);
});
}
#[test]
fn approval_deadline_works() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
assert!(System::block_number().is_zero());
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into())
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
// the approval expires after the 2nd block.
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
Some(2)
));
System::set_block_number(3);
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)),
Error::<Test>::ApprovalExpired
);
System::set_block_number(1);
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)));
assert_eq!(System::block_number(), 1);
// make a new approval with a deadline after 4 blocks, so it will expire after the 5th
// block.
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(4)),
0,
42,
account(6),
Some(4)
));
// this should still work.
System::set_block_number(5);
assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(6)), 0, 42, account(5)));
});
}
#[test]
fn cancel_approval_works_with_admin() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)),
Error::<Test>::NotDelegate
);
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)),
Error::<Test>::NotDelegate
);
});
}
#[test]
fn cancel_approval_works_with_force() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)),
Error::<Test>::NotDelegate
);
assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3)));
assert_noop!(
Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)),
Error::<Test>::NotDelegate
);
});
}
#[test]
fn clear_all_transfer_approvals_works() {
new_test_ext().execute_with(|| {
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(account(1)),
0,
42,
account(2),
default_item_config()
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(3),
None
));
assert_ok!(Nfts::approve_transfer(
RuntimeOrigin::signed(account(2)),
0,
42,
account(4),
None
));
assert_noop!(
Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(3)), 0, 42),
Error::<Test>::NoPermission
);
assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(2)), 0, 42));
assert!(events().contains(&Event::<Test>::AllApprovalsCancelled {
collection: 0,
item: 42,
owner: account(2),
}));
assert_eq!(approvals(0, 42), vec![]);
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(5)),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(5)),
Error::<Test>::NoPermission
);
});
}
#[test]
fn max_supply_should_work() {
new_test_ext().execute_with(|| {
let collection_id = 0;
let user_id = account(1);
let max_supply = 1;
// validate set_collection_max_supply
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
default_collection_config()
));
assert_eq!(CollectionConfigOf::<Test>::get(collection_id).unwrap().max_supply, None);
assert!(!events().contains(&Event::<Test>::CollectionMaxSupplySet {
collection: collection_id,
max_supply,
}));
assert_ok!(Nfts::set_collection_max_supply(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
max_supply
));
assert_eq!(
CollectionConfigOf::<Test>::get(collection_id).unwrap().max_supply,
Some(max_supply)
);
assert!(events().contains(&Event::<Test>::CollectionMaxSupplySet {
collection: collection_id,
max_supply,
}));
assert_ok!(Nfts::set_collection_max_supply(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
max_supply + 1
));
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into())
));
assert_noop!(
Nfts::set_collection_max_supply(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
max_supply + 2
),
Error::<Test>::MaxSupplyLocked
);
// validate we can't mint more to max supply
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
0,
user_id.clone(),
None
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
1,
user_id.clone(),
None
));
assert_noop!(
Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
2,
user_id.clone(),
None
),
Error::<Test>::MaxSupplyReached
);
// validate the event gets emitted when we set the max supply on collection create
let collection_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
CollectionConfig { max_supply: Some(max_supply), ..default_collection_config() }
));
assert_eq!(
CollectionConfigOf::<Test>::get(collection_id).unwrap().max_supply,
Some(max_supply)
);
assert!(events().contains(&Event::<Test>::CollectionMaxSupplySet {
collection: collection_id,
max_supply,
}));
});
}
#[test]
fn mint_settings_should_work() {
new_test_ext().execute_with(|| {
let collection_id = 0;
let user_id = account(1);
let item_id = 0;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_id,
user_id.clone(),
None,
));
assert_eq!(
ItemConfigOf::<Test>::get(collection_id, item_id)
.unwrap()
.settings
.get_disabled(),
ItemSettings::all_enabled().get_disabled()
);
let collection_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
CollectionConfig {
mint_settings: MintSettings {
default_item_settings: ItemSettings::from_disabled(
ItemSetting::Transferable | ItemSetting::UnlockedMetadata
),
..Default::default()
},
..default_collection_config()
}
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_id,
user_id.clone(),
None,
));
assert_eq!(
ItemConfigOf::<Test>::get(collection_id, item_id)
.unwrap()
.settings
.get_disabled(),
ItemSettings::from_disabled(ItemSetting::Transferable | ItemSetting::UnlockedMetadata)
.get_disabled()
);
});
}
#[test]
fn set_price_should_work() {
new_test_ext().execute_with(|| {
let user_id = account(1);
let collection_id = 0;
let item_1 = 1;
let item_2 = 2;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
user_id.clone(),
None,
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_2,
user_id.clone(),
None,
));
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
Some(1),
None,
));
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_2,
Some(2),
Some(account(3)),
));
let item = ItemPriceOf::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(item.0, 1);
assert_eq!(item.1, None);
let item = ItemPriceOf::<Test>::get(collection_id, item_2).unwrap();
assert_eq!(item.0, 2);
assert_eq!(item.1, Some(account(3)));
assert!(events().contains(&Event::<Test>::ItemPriceSet {
collection: collection_id,
item: item_1,
price: 1,
whitelisted_buyer: None,
}));
// validate we can unset the price
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_2,
None,
None
));
assert!(events().contains(&Event::<Test>::ItemPriceRemoved {
collection: collection_id,
item: item_2
}));
assert!(!ItemPriceOf::<Test>::contains_key(collection_id, item_2));
// ensure we can't set price when the items are non-transferable
let collection_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
collection_config_from_disabled_settings(
CollectionSetting::TransferableItems | CollectionSetting::DepositRequired
)
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
user_id.clone(),
None,
));
assert_noop!(
Nfts::set_price(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
Some(2),
None
),
Error::<Test>::ItemsNonTransferable
);
});
}
#[test]
fn buy_item_should_work() {
new_test_ext().execute_with(|| {
let user_1 = account(1);
let user_2 = account(2);
let user_3 = account(3);
let collection_id = 0;
let item_1 = 1;
let item_2 = 2;
let item_3 = 3;
let price_1 = 20;
let price_2 = 30;
let initial_balance = 100;
Balances::make_free_balance_be(&user_1, initial_balance);
Balances::make_free_balance_be(&user_2, initial_balance);
Balances::make_free_balance_be(&user_3, initial_balance);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_1.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_1,
user_1.clone(),
None
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_2,
user_1.clone(),
None
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_3,
user_1.clone(),
None
));
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_1,
Some(price_1),
None,
));
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_2,
Some(price_2),
Some(user_3.clone()),
));
// can't buy for less
assert_noop!(
Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_1, 1),
Error::<Test>::BidTooLow
);
// pass the higher price to validate it will still deduct correctly
assert_ok!(Nfts::buy_item(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_1,
price_1 + 1,
));
// validate the new owner & balances
let item = Item::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(item.owner, user_2.clone());
assert_eq!(Balances::total_balance(&user_1.clone()), initial_balance + price_1);
assert_eq!(Balances::total_balance(&user_2.clone()), initial_balance - price_1);
// can't buy from yourself
assert_noop!(
Nfts::buy_item(RuntimeOrigin::signed(user_1.clone()), collection_id, item_2, price_2),
Error::<Test>::NoPermission
);
// can't buy when the item is listed for a specific buyer
assert_noop!(
Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, price_2),
Error::<Test>::NoPermission
);
// can buy when I'm a whitelisted buyer
assert_ok!(Nfts::buy_item(
RuntimeOrigin::signed(user_3.clone()),
collection_id,
item_2,
price_2
));
assert!(events().contains(&Event::<Test>::ItemBought {
collection: collection_id,
item: item_2,
price: price_2,
seller: user_1.clone(),
buyer: user_3.clone(),
}));
// ensure we reset the buyer field
assert!(!ItemPriceOf::<Test>::contains_key(collection_id, item_2));
// can't buy when item is not for sale
assert_noop!(
Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_3, price_2),
Error::<Test>::NotForSale
);
// ensure we can't buy an item when the collection or an item are frozen
{
assert_ok!(Nfts::set_price(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_3,
Some(price_1),
None,
));
// lock the collection
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into())
));
let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::<Test>::buy_item {
collection: collection_id,
item: item_3,
bid_price: price_1,
});
assert_noop!(
buy_item_call.dispatch(RuntimeOrigin::signed(user_2.clone())),
Error::<Test>::ItemsNonTransferable
);
// unlock the collection
assert_ok!(Nfts::force_collection_config(
RuntimeOrigin::root(),
collection_id,
collection_config_with_all_settings_enabled(),
));
// lock the transfer
assert_ok!(Nfts::lock_item_transfer(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_3,
));
let buy_item_call = mock::RuntimeCall::Nfts(crate::Call::<Test>::buy_item {
collection: collection_id,
item: item_3,
bid_price: price_1,
});
assert_noop!(
buy_item_call.dispatch(RuntimeOrigin::signed(user_2)),
Error::<Test>::ItemLocked
);
}
});
}
#[test]
fn pay_tips_should_work() {
new_test_ext().execute_with(|| {
let user_1 = account(1);
let user_2 = account(2);
let user_3 = account(3);
let collection_id = 0;
let item_id = 1;
let tip = 2;
let initial_balance = 100;
Balances::make_free_balance_be(&user_1, initial_balance);
Balances::make_free_balance_be(&user_2, initial_balance);
Balances::make_free_balance_be(&user_3, initial_balance);
assert_ok!(Nfts::pay_tips(
RuntimeOrigin::signed(user_1.clone()),
bvec![
ItemTip {
collection: collection_id,
item: item_id,
receiver: user_2.clone(),
amount: tip
},
ItemTip {
collection: collection_id,
item: item_id,
receiver: user_3.clone(),
amount: tip
},
]
));
assert_eq!(Balances::total_balance(&user_1), initial_balance - tip * 2);
assert_eq!(Balances::total_balance(&user_2), initial_balance + tip);
assert_eq!(Balances::total_balance(&user_3), initial_balance + tip);
let events = events();
assert!(events.contains(&Event::<Test>::TipSent {
collection: collection_id,
item: item_id,
sender: user_1.clone(),
receiver: user_2.clone(),
amount: tip,
}));
assert!(events.contains(&Event::<Test>::TipSent {
collection: collection_id,
item: item_id,
sender: user_1.clone(),
receiver: user_3.clone(),
amount: tip,
}));
});
}
#[test]
fn create_cancel_swap_should_work() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
let user_id = account(1);
let collection_id = 0;
let item_1 = 1;
let item_2 = 2;
let price = 1;
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = 2;
let expect_deadline = 3;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
user_id.clone(),
None,
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_2,
user_id.clone(),
None,
));
// validate desired item and the collection exists
assert_noop!(
Nfts::create_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
collection_id,
Some(item_2 + 1),
Some(price_with_direction.clone()),
duration,
),
Error::<Test>::UnknownItem
);
assert_noop!(
Nfts::create_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
collection_id + 1,
None,
Some(price_with_direction.clone()),
duration,
),
Error::<Test>::UnknownCollection
);
let max_duration: u64 = <Test as Config>::MaxDeadlineDuration::get();
assert_noop!(
Nfts::create_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
max_duration.saturating_add(1),
),
Error::<Test>::WrongDuration
);
assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
duration,
));
let swap = PendingSwapOf::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(swap.desired_collection, collection_id);
assert_eq!(swap.desired_item, Some(item_2));
assert_eq!(swap.price, Some(price_with_direction.clone()));
assert_eq!(swap.deadline, expect_deadline);
assert!(events().contains(&Event::<Test>::SwapCreated {
offered_collection: collection_id,
offered_item: item_1,
desired_collection: collection_id,
desired_item: Some(item_2),
price: Some(price_with_direction.clone()),
deadline: expect_deadline,
}));
// validate we can cancel the swap
assert_ok!(Nfts::cancel_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1
));
assert!(events().contains(&Event::<Test>::SwapCancelled {
offered_collection: collection_id,
offered_item: item_1,
desired_collection: collection_id,
desired_item: Some(item_2),
price: Some(price_with_direction.clone()),
deadline: expect_deadline,
}));
assert!(!PendingSwapOf::<Test>::contains_key(collection_id, item_1));
// validate anyone can cancel the expired swap
assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
duration,
));
assert_noop!(
Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), collection_id, item_1),
Error::<Test>::NoPermission
);
System::set_block_number(expect_deadline + 1);
assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), collection_id, item_1));
// validate optional desired_item param
assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_id),
collection_id,
item_1,
collection_id,
None,
Some(price_with_direction),
duration,
));
let swap = PendingSwapOf::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(swap.desired_item, None);
});
}
#[test]
fn claim_swap_should_work() {
new_test_ext().execute_with(|| {
System::set_block_number(1);
let user_1 = account(1);
let user_2 = account(2);
let collection_id = 0;
let item_1 = 1;
let item_2 = 2;
let item_3 = 3;
let item_4 = 4;
let item_5 = 5;
let price = 100;
let price_direction = PriceDirection::Receive;
let price_with_direction =
PriceWithDirection { amount: price, direction: price_direction.clone() };
let duration = 2;
let initial_balance = 1000;
let deadline = 1 + duration;
Balances::make_free_balance_be(&user_1, initial_balance);
Balances::make_free_balance_be(&user_2, initial_balance);
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_1.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_1,
user_1.clone(),
None,
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_2,
user_2.clone(),
default_item_config(),
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_3,
user_2.clone(),
default_item_config(),
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_4,
user_1.clone(),
None,
));
assert_ok!(Nfts::force_mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_5,
user_2.clone(),
default_item_config(),
));
assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_1,
collection_id,
Some(item_2),
Some(price_with_direction.clone()),
duration,
));
// validate the deadline
System::set_block_number(5);
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_2,
collection_id,
item_1,
Some(price_with_direction.clone()),
),
Error::<Test>::DeadlineExpired
);
System::set_block_number(1);
// validate edge cases
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_2,
collection_id,
item_4, // no swap was created for that asset
Some(price_with_direction.clone()),
),
Error::<Test>::UnknownSwap
);
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_4, // not my item
collection_id,
item_1,
Some(price_with_direction.clone()),
),
Error::<Test>::NoPermission
);
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_5, // my item, but not the one another part wants
collection_id,
item_1,
Some(price_with_direction.clone()),
),
Error::<Test>::UnknownSwap
);
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_2,
collection_id,
item_1,
Some(PriceWithDirection { amount: price + 1, direction: price_direction.clone() }), // wrong price
),
Error::<Test>::UnknownSwap
);
assert_noop!(
Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_2,
collection_id,
item_1,
Some(PriceWithDirection { amount: price, direction: PriceDirection::Send }), // wrong direction
),
Error::<Test>::UnknownSwap
);
assert_ok!(Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_2,
collection_id,
item_1,
Some(price_with_direction.clone()),
));
// validate the new owner
let item = Item::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(item.owner, user_2.clone());
let item = Item::<Test>::get(collection_id, item_2).unwrap();
assert_eq!(item.owner, user_1.clone());
// validate the balances
assert_eq!(Balances::total_balance(&user_1), initial_balance + price);
assert_eq!(Balances::total_balance(&user_2), initial_balance - price);
// ensure we reset the swap
assert!(!PendingSwapOf::<Test>::contains_key(collection_id, item_1));
// validate the event
assert!(events().contains(&Event::<Test>::SwapClaimed {
sent_collection: collection_id,
sent_item: item_2,
sent_item_owner: user_2.clone(),
received_collection: collection_id,
received_item: item_1,
received_item_owner: user_1.clone(),
price: Some(price_with_direction.clone()),
deadline,
}));
// validate the optional desired_item param and another price direction
let price_direction = PriceDirection::Send;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
Balances::make_free_balance_be(&user_1, initial_balance);
Balances::make_free_balance_be(&user_2, initial_balance);
assert_ok!(Nfts::create_swap(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_4,
collection_id,
None,
Some(price_with_direction.clone()),
duration,
));
assert_ok!(Nfts::claim_swap(
RuntimeOrigin::signed(user_2.clone()),
collection_id,
item_1,
collection_id,
item_4,
Some(price_with_direction),
));
let item = Item::<Test>::get(collection_id, item_1).unwrap();
assert_eq!(item.owner, user_1);
let item = Item::<Test>::get(collection_id, item_4).unwrap();
assert_eq!(item.owner, user_2);
assert_eq!(Balances::total_balance(&user_1), initial_balance - price);
assert_eq!(Balances::total_balance(&user_2), initial_balance + price);
});
}
#[test]
fn various_collection_settings() {
new_test_ext().execute_with(|| {
// when we set only one value it's required to call .into() on it
let config =
collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into());
assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(1), config));
let config = CollectionConfigOf::<Test>::get(0).unwrap();
assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems));
assert!(config.is_setting_enabled(CollectionSetting::UnlockedMetadata));
// no need to call .into() for multiple values
let config = collection_config_from_disabled_settings(
CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems,
);
assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(1), config));
let config = CollectionConfigOf::<Test>::get(1).unwrap();
assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems));
assert!(!config.is_setting_enabled(CollectionSetting::UnlockedMetadata));
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
account(1),
default_collection_config()
));
});
}
#[test]
fn collection_locking_should_work() {
new_test_ext().execute_with(|| {
let user_id = account(1);
let collection_id = 0;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
collection_config_with_all_settings_enabled()
));
let lock_config =
collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into());
assert_noop!(
Nfts::lock_collection(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
lock_config.settings,
),
Error::<Test>::WrongSetting
);
// validate partial lock
let lock_config = collection_config_from_disabled_settings(
CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes,
);
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
lock_config.settings,
));
let stored_config = CollectionConfigOf::<Test>::get(collection_id).unwrap();
assert_eq!(stored_config, lock_config);
// validate full lock
assert_ok!(Nfts::lock_collection(
RuntimeOrigin::signed(user_id),
collection_id,
CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()),
));
let stored_config = CollectionConfigOf::<Test>::get(collection_id).unwrap();
let full_lock_config = collection_config_from_disabled_settings(
CollectionSetting::TransferableItems |
CollectionSetting::UnlockedMetadata |
CollectionSetting::UnlockedAttributes,
);
assert_eq!(stored_config, full_lock_config);
});
}
#[test]
fn pallet_level_feature_flags_should_work() {
new_test_ext().execute_with(|| {
Features::set(&PalletFeatures::from_disabled(
PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes,
));
let user_id = account(1);
let collection_id = 0;
let item_id = 1;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_id.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_id,
user_id.clone(),
None,
));
// PalletFeature::Trading
assert_noop!(
Nfts::set_price(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_id,
Some(1),
None
),
Error::<Test>::MethodDisabled
);
assert_noop!(
Nfts::buy_item(RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, 1),
Error::<Test>::MethodDisabled
);
// PalletFeature::Approvals
assert_noop!(
Nfts::approve_transfer(
RuntimeOrigin::signed(user_id.clone()),
collection_id,
item_id,
account(2),
None
),
Error::<Test>::MethodDisabled
);
// PalletFeature::Attributes
assert_noop!(
Nfts::set_attribute(
RuntimeOrigin::signed(user_id),
collection_id,
None,
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![0],
),
Error::<Test>::MethodDisabled
);
})
}
#[test]
fn group_roles_by_account_should_work() {
new_test_ext().execute_with(|| {
assert_eq!(Nfts::group_roles_by_account(vec![]), vec![]);
let account_to_role = Nfts::group_roles_by_account(vec![
(account(3), CollectionRole::Freezer),
(account(1), CollectionRole::Issuer),
(account(2), CollectionRole::Admin),
]);
let expect = vec![
(account(1), CollectionRoles(CollectionRole::Issuer.into())),
(account(2), CollectionRoles(CollectionRole::Admin.into())),
(account(3), CollectionRoles(CollectionRole::Freezer.into())),
];
assert_eq!(account_to_role, expect);
let account_to_role = Nfts::group_roles_by_account(vec![
(account(3), CollectionRole::Freezer),
(account(2), CollectionRole::Issuer),
(account(2), CollectionRole::Admin),
]);
let expect = vec![
(account(2), CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)),
(account(3), CollectionRoles(CollectionRole::Freezer.into())),
];
assert_eq!(account_to_role, expect);
})
}
#[test]
fn add_remove_item_attributes_approval_should_work() {
new_test_ext().execute_with(|| {
let user_1 = account(1);
let user_2 = account(2);
let user_3 = account(3);
let user_4 = account(4);
let collection_id = 0;
let item_id = 0;
assert_ok!(Nfts::force_create(
RuntimeOrigin::root(),
user_1.clone(),
default_collection_config()
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_1.clone(),
None
));
assert_ok!(Nfts::approve_item_attributes(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_2.clone(),
));
assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2.clone()]);
assert_ok!(Nfts::approve_item_attributes(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_3.clone(),
));
assert_ok!(Nfts::approve_item_attributes(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_2.clone(),
));
assert_eq!(
item_attributes_approvals(collection_id, item_id),
vec![user_2.clone(), user_3.clone()]
);
assert_noop!(
Nfts::approve_item_attributes(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_4,
),
Error::<Test>::ReachedApprovalLimit
);
assert_ok!(Nfts::cancel_item_attributes_approval(
RuntimeOrigin::signed(user_1),
collection_id,
item_id,
user_2,
CancelAttributesApprovalWitness { account_attributes: 1 },
));
assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3]);
})
}
#[test]
fn validate_signature() {
new_test_ext().execute_with(|| {
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();
let mint_data: PreSignedMint<u32, u32, AccountId, u32, u64> = PreSignedMint {
collection: 0,
item: 0,
attributes: vec![],
metadata: vec![],
only_account: None,
deadline: 100000,
mint_price: None,
};
let encoded_data = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&encoded_data));
assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1));
let mut wrapped_data: Vec<u8> = Vec::new();
wrapped_data.extend(b"<Bytes>");
wrapped_data.extend(&encoded_data);
wrapped_data.extend(b"</Bytes>");
let signature = MultiSignature::Sr25519(user_1_pair.sign(&wrapped_data));
assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1));
})
}
#[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();
let mint_data = PreSignedMint {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])],
metadata: vec![0, 1],
only_account: None,
deadline: 10000000,
mint_price: Some(10),
};
let message = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
let user_2 = account(2);
let user_3 = account(3);
Balances::make_free_balance_be(&user_0, 100);
Balances::make_free_balance_be(&user_2, 100);
assert_ok!(Nfts::create(
RuntimeOrigin::signed(user_0.clone()),
user_1.clone(),
collection_config_with_all_settings_enabled(),
));
assert_ok!(Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
Box::new(mint_data.clone()),
signature.clone(),
user_1.clone(),
));
assert_eq!(items(), vec![(user_2.clone(), 0, 0)]);
let metadata = ItemMetadataOf::<Test>::get(0, 0).unwrap();
assert_eq!(
metadata.deposit,
ItemMetadataDeposit { account: Some(user_2.clone()), amount: 3 }
);
assert_eq!(metadata.data, vec![0, 1]);
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]),
]
);
let attribute_key: BoundedVec<_, _> = bvec![0];
let (_, deposit) = Attribute::<Test>::get((
0,
Some(0),
AttributeNamespace::CollectionOwner,
&attribute_key,
))
.unwrap();
assert_eq!(deposit.account, Some(user_2.clone()));
assert_eq!(deposit.amount, 3);
assert_eq!(Balances::free_balance(&user_0), 100 - 2 + 10); // 2 - collection deposit, 10 - mint price
assert_eq!(Balances::free_balance(&user_2), 100 - 1 - 3 - 6 - 10); // 1 - item deposit, 3 - metadata, 6 - attributes, 10 - mint price
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
Box::new(mint_data),
signature.clone(),
user_1.clone(),
),
Error::<Test>::AlreadyExists
);
assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0));
assert_eq!(Balances::free_balance(&user_2), 100 - 6 - 10);
// validate the `only_account` field
let mint_data = PreSignedMint {
collection: 0,
item: 0,
attributes: vec![],
metadata: vec![],
only_account: Some(account(2)),
deadline: 10000000,
mint_price: None,
};
// can't mint with the wrong signature
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
Box::new(mint_data.clone()),
signature.clone(),
user_1.clone(),
),
Error::<Test>::WrongSignature
);
let message = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_3),
Box::new(mint_data.clone()),
signature.clone(),
user_1.clone(),
),
Error::<Test>::WrongOrigin
);
// validate signature's expiration
System::set_block_number(10000001);
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
Box::new(mint_data),
signature,
user_1.clone(),
),
Error::<Test>::DeadlineExpired
);
System::set_block_number(1);
// validate the collection
let mint_data = PreSignedMint {
collection: 1,
item: 0,
attributes: vec![],
metadata: vec![],
only_account: Some(account(2)),
deadline: 10000000,
mint_price: None,
};
let message = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
Box::new(mint_data),
signature,
user_1.clone(),
),
Error::<Test>::NoPermission
);
// validate max attributes limit
let mint_data = PreSignedMint {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])],
metadata: vec![0, 1],
only_account: None,
deadline: 10000000,
mint_price: None,
};
let message = Encode::encode(&mint_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::mint_pre_signed(
RuntimeOrigin::signed(user_2),
Box::new(mint_data),
signature,
user_1.clone(),
),
Error::<Test>::MaxAttributesLimitReached
);
})
}
#[test]
fn pre_signed_attributes_should_work() {
new_test_ext().execute_with(|| {
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();
let user_2 = account(2);
let user_3_pair = sp_core::sr25519::Pair::from_string("//Bob", None).unwrap();
let user_3_signer = MultiSigner::Sr25519(user_3_pair.public());
let user_3 = user_3_signer.clone().into_account();
let collection_id = 0;
let item_id = 0;
Balances::make_free_balance_be(&user_1, 100);
Balances::make_free_balance_be(&user_2, 100);
Balances::make_free_balance_be(&user_3, 100);
assert_ok!(Nfts::create(
RuntimeOrigin::signed(user_1.clone()),
user_1.clone(),
collection_config_with_all_settings_enabled(),
));
assert_ok!(Nfts::mint(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
item_id,
user_2.clone(),
None,
));
// validate the CollectionOwner namespace
let pre_signed_data = PreSignedAttributes {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])],
namespace: AttributeNamespace::CollectionOwner,
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_ok!(Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_1.clone(),
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]),
]
);
let attribute_key: BoundedVec<_, _> = bvec![0];
let (_, deposit) = Attribute::<Test>::get((
0,
Some(0),
AttributeNamespace::CollectionOwner,
&attribute_key,
))
.unwrap();
assert_eq!(deposit.account, Some(user_2.clone()));
assert_eq!(deposit.amount, 3);
assert_eq!(Balances::free_balance(&user_1), 100 - 2 - 1); // 2 - collection deposit, 1 - item deposit
assert_eq!(Balances::free_balance(&user_2), 100 - 6); // 6 - attributes
// validate the deposit gets returned on attribute update from collection's owner
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(user_1.clone()),
collection_id,
Some(item_id),
AttributeNamespace::CollectionOwner,
bvec![0],
bvec![1],
));
let (_, deposit) = Attribute::<Test>::get((
0,
Some(0),
AttributeNamespace::CollectionOwner,
&attribute_key,
))
.unwrap();
assert_eq!(deposit.account, None);
assert_eq!(deposit.amount, 3);
// validate we don't partially modify the state
assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]);
let pre_signed_data = PreSignedAttributes {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2; 51], vec![3])],
namespace: AttributeNamespace::Account(user_3.clone()),
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_3_pair.sign(&message));
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_3.clone(),
),
Error::<Test>::IncorrectData
);
// no new approval was set
assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]);
// no new attributes were added
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]),
]
);
// validate the Account namespace
let pre_signed_data = PreSignedAttributes {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])],
namespace: AttributeNamespace::Account(user_3.clone()),
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_3_pair.sign(&message));
assert_ok!(Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_3.clone(),
));
assert_eq!(
attributes(0),
vec![
(Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]),
(Some(0), AttributeNamespace::Account(user_3.clone()), bvec![0], bvec![1]),
(Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]),
(Some(0), AttributeNamespace::Account(user_3.clone()), bvec![2], bvec![3]),
]
);
assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3.clone()]);
let attribute_key: BoundedVec<_, _> = bvec![0];
let (_, deposit) = Attribute::<Test>::get((
0,
Some(0),
AttributeNamespace::Account(user_3.clone()),
&attribute_key,
))
.unwrap();
assert_eq!(deposit.account, Some(user_2.clone()));
assert_eq!(deposit.amount, 3);
assert_eq!(Balances::free_balance(&user_2), 100 - 9);
assert_eq!(Balances::free_balance(&user_3), 100);
// validate the deposit gets returned on attribute update from user_3
assert_ok!(Nfts::set_attribute(
RuntimeOrigin::signed(user_3.clone()),
collection_id,
Some(item_id),
AttributeNamespace::Account(user_3.clone()),
bvec![0],
bvec![1],
));
let (_, deposit) = Attribute::<Test>::get((
0,
Some(0),
AttributeNamespace::Account(user_3.clone()),
&attribute_key,
))
.unwrap();
assert_eq!(deposit.account, Some(user_3.clone()));
assert_eq!(deposit.amount, 3);
assert_eq!(Balances::free_balance(&user_2), 100 - 6);
assert_eq!(Balances::free_balance(&user_3), 100 - 3);
// can't update with the wrong signature
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_1.clone(),
),
Error::<Test>::WrongSignature
);
// can't update if I don't own that item
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_3.clone()),
pre_signed_data.clone(),
signature.clone(),
user_3.clone(),
),
Error::<Test>::NoPermission
);
// can't update the CollectionOwner namespace if the signer is not an owner of that
// collection
let pre_signed_data = PreSignedAttributes {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])],
namespace: AttributeNamespace::CollectionOwner,
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_3_pair.sign(&message));
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_3.clone(),
),
Error::<Test>::NoPermission
);
// validate signature's expiration
System::set_block_number(10000001);
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_3.clone(),
),
Error::<Test>::DeadlineExpired
);
System::set_block_number(1);
// validate item & collection
let pre_signed_data = PreSignedAttributes {
collection: 1,
item: 1,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])],
namespace: AttributeNamespace::CollectionOwner,
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_1.clone(),
),
Error::<Test>::UnknownItem
);
// validate max attributes limit
let pre_signed_data = PreSignedAttributes {
collection: 1,
item: 1,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])],
namespace: AttributeNamespace::CollectionOwner,
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_1.clone(),
),
Error::<Test>::MaxAttributesLimitReached
);
// validate the attribute's value length
let pre_signed_data = PreSignedAttributes {
collection: 0,
item: 0,
attributes: vec![(vec![0], vec![1]), (vec![2], vec![3; 51])],
namespace: AttributeNamespace::CollectionOwner,
deadline: 10000000,
};
let message = Encode::encode(&pre_signed_data);
let signature = MultiSignature::Sr25519(user_1_pair.sign(&message));
assert_noop!(
Nfts::set_attributes_pre_signed(
RuntimeOrigin::signed(user_2.clone()),
pre_signed_data.clone(),
signature.clone(),
user_1.clone(),
),
Error::<Test>::IncorrectData
);
})
}
#[test]
fn basic_create_collection_with_id_should_work() {
new_test_ext().execute_with(|| {
assert_noop!(
Nfts::create_collection_with_id(
0u32,
&account(1),
&account(1),
&default_collection_config(),
),
Error::<Test>::WrongSetting
);
Balances::make_free_balance_be(&account(1), 100);
Balances::make_free_balance_be(&account(2), 100);
assert_ok!(Nfts::create_collection_with_id(
0u32,
&account(1),
&account(1),
&collection_config_with_all_settings_enabled(),
));
assert_eq!(collections(), vec![(account(1), 0)]);
// CollectionId already taken.
assert_noop!(
Nfts::create_collection_with_id(
0u32,
&account(2),
&account(2),
&collection_config_with_all_settings_enabled(),
),
Error::<Test>::CollectionIdInUse
);
});
}
#[test]
fn clear_collection_metadata_works() {
new_test_ext().execute_with(|| {
// Start with an account with 100 tokens, 10 of which are reserved
Balances::make_free_balance_be(&account(1), 100);
Balances::reserve(&account(1), 10).unwrap();
// Creating a collection increases owner deposit by 2
assert_ok!(Nfts::create(
RuntimeOrigin::signed(account(1)),
account(1),
collection_config_with_all_settings_enabled()
));
assert_eq!(Collection::<Test>::get(0).unwrap().owner_deposit, 2);
assert_eq!(Balances::reserved_balance(&account(1)), 12);
// Setting collection metadata increases owner deposit by 10
assert_ok!(Nfts::set_collection_metadata(
RuntimeOrigin::signed(account(1)),
0,
bvec![0, 0, 0, 0, 0, 0, 0, 0, 0],
));
assert_eq!(Collection::<Test>::get(0).unwrap().owner_deposit, 12);
assert_eq!(Balances::reserved_balance(&account(1)), 22);
// Clearing collection metadata decreases owner deposit by 10
assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0));
assert_eq!(Collection::<Test>::get(0).unwrap().owner_deposit, 2);
assert_eq!(Balances::reserved_balance(&account(1)), 12);
// Destroying the collection removes it from storage
assert_ok!(Nfts::destroy(
RuntimeOrigin::signed(account(1)),
0,
DestroyWitness { item_configs: 0, item_metadatas: 0, attributes: 0 }
));
assert_eq!(Collection::<Test>::get(0), None);
assert_eq!(Balances::reserved_balance(&account(1)), 10);
});
}