From bbd154a47827ba0cd5845f42a1d65d9e347b75ac Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 13 Apr 2022 15:12:26 +0000 Subject: [PATCH] test: fix unit --- --- a/Cargo.lock +++ b/Cargo.lock @@ -6829,37 +6829,18 @@ name = "pallet-unique" version = "0.1.0" dependencies = [ - "ethereum", - "evm-coder", - "fp-evm", - "fp-evm-mapping", "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", - "pallet-balances", "pallet-common", - "pallet-ethereum", "pallet-evm", - "pallet-evm-coder-substrate", - "pallet-fungible", - "pallet-nonfungible", - "pallet-randomness-collective-flip", - "pallet-refungible", - "pallet-timestamp", - "pallet-transaction-payment", "parity-scale-codec", - "primitive-types", - "rlp", "scale-info", - "serde", - "sp-api", "sp-core", "sp-io", "sp-runtime", "sp-std", "up-data-structs", - "up-sponsorship", ] [[package]] @@ -11918,6 +11899,35 @@ ] [[package]] +name = "tests" +version = "0.1.0" +dependencies = [ + "fp-evm-mapping", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-common", + "pallet-ethereum", + "pallet-evm", + "pallet-evm-coder-substrate", + "pallet-fungible", + "pallet-nonfungible", + "pallet-refungible", + "pallet-structure", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-unique", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "unique-runtime-common", + "up-data-structs", +] + +[[package]] name = "textwrap" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12677,6 +12687,7 @@ name = "unique-runtime-common" version = "0.9.18" dependencies = [ + "evm-coder", "fp-rpc", "frame-support", "frame-system", @@ -12693,6 +12704,7 @@ "sp-runtime", "sp-std", "up-data-structs", + "up-sponsorship", ] [[package]] --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,16 @@ [workspace] resolver = "2" -members = ['node/*', 'pallets/*', 'client/*', 'primitives/*', 'crates/*'] -exclude = ["runtime/unique", "runtime/quartz"] +members = [ + 'node/*', + 'pallets/*', + 'client/*', + 'primitives/*', + 'crates/*', + 'runtime/tests', +] +exclude = [ + "runtime/unique", + "runtime/quartz" +] [profile.release] panic = 'unwind' --- a/pallets/unique/Cargo.toml +++ b/pallets/unique/Cargo.toml @@ -19,32 +19,14 @@ runtime-benchmarks = ['frame-benchmarking', 'pallet-common/runtime-benchmarks'] std = [ 'codec/std', - 'serde/std', 'frame-support/std', 'frame-system/std', - 'pallet-balances/std', 'pallet-evm/std', - 'pallet-timestamp/std', - 'pallet-randomness-collective-flip/std', - 'pallet-transaction-payment/std', 'pallet-common/std', - 'pallet-fungible/std', - 'pallet-nonfungible/std', - 'pallet-refungible/std', - 'fp-evm/std', 'up-data-structs/std', - 'up-sponsorship/std', - 'fp-evm-mapping/std', 'sp-std/std', - 'sp-api/std', 'sp-runtime/std', 'frame-benchmarking/std', - 'ethereum/std', - 'rlp/std', - - 'primitive-types/std', - 'evm-coder/std', - 'pallet-evm-coder-substrate/std', ] limit-testing = ["up-data-structs/limit-testing"] @@ -69,21 +51,6 @@ branch = "polkadot-v0.9.20" [dependencies.frame-system] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.20" - -[dependencies.pallet-balances] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.20" - -[dependencies.pallet-timestamp] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.20" - -[dependencies.pallet-randomness-collective-flip] default-features = false git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.20" @@ -93,16 +60,6 @@ git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.20" -[dependencies.pallet-transaction-payment] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.20" - -[dependencies.serde] -default-features = false -features = ['derive'] -version = '1.0.130' - [dependencies.sp-runtime] default-features = false git = "https://github.com/paritytech/substrate" @@ -123,28 +80,8 @@ # Local Dependencies [dependencies] up-data-structs = { default-features = false, path = "../../primitives/data-structs" } - scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } -ethereum = { version = "0.12.0", default-features = false } -rlp = { default-features = false, version = "0.5.0" } -sp-api = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" } - -up-sponsorship = { version = "0.1.0", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.20" } -fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.20" } -evm-coder = { default-features = false, path = "../../crates/evm-coder" } -pallet-evm-coder-substrate = { default-features = false, path = "../../pallets/evm-coder-substrate" } -primitive-types = { version = "0.11.1", default-features = false, features = [ - "serde_no_std", -] } - -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.20" } -pallet-ethereum = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.20" } -fp-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.20" } -hex-literal = "0.3.3" - +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.18-2" } pallet-common = { default-features = false, path = "../common" } -pallet-fungible = { default-features = false, path = "../fungible" } -pallet-nonfungible = { default-features = false, path = "../nonfungible" } -pallet-refungible = { default-features = false, path = "../refungible" } --- a/pallets/unique/src/common.rs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use core::marker::PhantomData; -use frame_support::{weights::Weight}; -use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight}; - -use pallet_fungible::{common::CommonWeights as FungibleWeights}; -use pallet_nonfungible::{common::CommonWeights as NonfungibleWeights}; -use pallet_refungible::{common::CommonWeights as RefungibleWeights}; -use up_data_structs::CreateItemExData; - -use crate::Config; - -macro_rules! max_weight_of { - ($method:ident ( $($args:tt)* )) => { - >::$method($($args)*) - .max(>::$method($($args)*)) - .max(>::$method($($args)*)) - }; -} - -pub struct CommonWeights(PhantomData); -impl CommonWeightInfo for CommonWeights { - fn create_item() -> Weight { - dispatch_weight::() + max_weight_of!(create_item()) - } - - fn create_multiple_items(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(create_multiple_items(amount)) - } - - fn create_multiple_items_ex(data: &CreateItemExData) -> Weight { - dispatch_weight::() + max_weight_of!(create_multiple_items_ex(data)) - } - - fn burn_item() -> Weight { - dispatch_weight::() + max_weight_of!(burn_item()) - } - - fn transfer() -> Weight { - dispatch_weight::() + max_weight_of!(transfer()) - } - - fn approve() -> Weight { - dispatch_weight::() + max_weight_of!(approve()) - } - - fn transfer_from() -> Weight { - dispatch_weight::() + max_weight_of!(transfer_from()) - } - - fn set_variable_metadata(bytes: u32) -> Weight { - dispatch_weight::() + max_weight_of!(set_variable_metadata(bytes)) - } - - fn burn_from() -> Weight { - dispatch_weight::() + max_weight_of!(burn_from()) - } -} --- a/pallets/unique/src/eth/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -pub mod sponsoring; --- a/pallets/unique/src/eth/sponsoring.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -//! Implements EVM sponsoring logic via TransactionValidityHack - -use crate::{Config, sponsorship::*}; -use evm_coder::{Call, abi::AbiReader}; -use pallet_common::{CollectionHandle, eth::map_eth_to_id}; -use sp_core::H160; -use sp_std::prelude::*; -use up_sponsorship::SponsorshipHandler; -use core::marker::PhantomData; -use core::convert::TryInto; -use pallet_evm::account::CrossAccountId; -use up_data_structs::{TokenId, CreateItemData, CreateNftData}; - -use pallet_nonfungible::erc::{ - UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, -}; -use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; - -pub struct UniqueEthSponsorshipHandler(PhantomData<*const T>); -impl SponsorshipHandler)> - for UniqueEthSponsorshipHandler -{ - fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { - let collection_id = map_eth_to_id(&call.0)?; - let collection = >::new(collection_id)?; - let sponsor = collection.sponsorship.sponsor()?.clone(); - let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; - Some(T::CrossAccountId::from_sub(match &collection.mode { - crate::CollectionMode::NFT => { - let call = >::parse(method_id, &mut reader).ok()??; - match call { - UniqueNFTCall::ERC721UniqueExtensions( - ERC721UniqueExtensionsCall::Transfer { token_id, .. }, - ) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) - } - UniqueNFTCall::ERC721Mintable( - ERC721MintableCall::Mint { token_id, .. } - | ERC721MintableCall::MintWithTokenUri { token_id, .. }, - ) => { - let _token_id: TokenId = token_id.try_into().ok()?; - withdraw_create_item::( - &collection, - &who, - &CreateItemData::NFT(CreateNftData::default()), - ) - .map(|()| sponsor) - } - UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) - } - UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_approve::(&collection, who.as_sub(), &token_id) - .map(|()| sponsor) - } - _ => None, - } - } - crate::CollectionMode::Fungible(_) => { - let call = >::parse(method_id, &mut reader).ok()??; - #[allow(clippy::single_match)] - match call { - UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { - withdraw_transfer::(&collection, who, &TokenId::default()) - .map(|()| sponsor) - } - UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &TokenId::default()) - .map(|()| sponsor) - } - UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { - withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) - .map(|()| sponsor) - } - _ => None, - } - } - _ => None, - }?)) - } -} --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -22,10 +22,6 @@ clippy::unused_unit )] -extern crate alloc; - -pub use serde::{Serialize, Deserialize}; - use frame_support::{ decl_module, decl_storage, decl_error, decl_event, dispatch::DispatchResult, @@ -50,20 +46,6 @@ CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo, dispatch::dispatch_call, dispatch::CollectionDispatch, }; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -mod eth; -mod sponsorship; -pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict}; -pub use eth::sponsoring::UniqueEthSponsorshipHandler; - -pub mod common; -use common::CommonWeights; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -88,20 +70,12 @@ } } -pub trait Config: - system::Config - + pallet_evm_coder_substrate::Config - + pallet_common::Config - + pallet_nonfungible::Config - + pallet_refungible::Config - + pallet_fungible::Config - + Sized - + TypeInfo -{ +pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo { type Event: From> + Into<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + type CommonWeightInfo: CommonWeightInfo; } decl_event! { @@ -689,7 +663,7 @@ /// * owner: Address, initial owner of the NFT. /// /// * data: Token data to store on chain. - #[weight = >::create_item()] + #[weight = T::CommonWeightInfo::create_item()] #[transactional] pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -716,7 +690,7 @@ /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item]. /// /// * owner: Address, initial owner of the NFT. - #[weight = >::create_multiple_items(items_data.len() as u32)] + #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)] #[transactional] pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec) -> DispatchResultWithPostInfo { ensure!(!items_data.is_empty(), Error::::EmptyArgument); @@ -726,7 +700,7 @@ dispatch_call::(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget)) } - #[weight = >::create_multiple_items_ex(&data)] + #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)] #[transactional] pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -774,7 +748,7 @@ /// * collection_id: ID of the collection. /// /// * item_id: ID of NFT to burn. - #[weight = >::burn_item()] + #[weight = T::CommonWeightInfo::burn_item()] #[transactional] pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -806,7 +780,7 @@ /// * item_id: ID of NFT to burn. /// /// * from: owner of item - #[weight = >::burn_from()] + #[weight = T::CommonWeightInfo::burn_from()] #[transactional] pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -838,7 +812,7 @@ /// * Non-Fungible Mode: Ignored /// * Fungible Mode: Must specify transferred amount /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1) - #[weight = >::transfer()] + #[weight = T::CommonWeightInfo::transfer()] #[transactional] pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -862,7 +836,7 @@ /// * collection_id. /// /// * item_id: ID of the item. - #[weight = >::approve()] + #[weight = T::CommonWeightInfo::approve()] #[transactional] pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -889,7 +863,7 @@ /// * item_id: ID of the item. /// /// * value: Amount to transfer. - #[weight = >::transfer_from()] + #[weight = T::CommonWeightInfo::transfer_from()] #[transactional] pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); @@ -910,7 +884,7 @@ /// * collection_id. /// /// * schema: String representing the offchain data schema. - #[weight = >::set_variable_metadata(data.len() as u32)] + #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)] #[transactional] pub fn set_variable_meta_data ( origin, --- a/pallets/unique/src/mock.rs +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -#![allow(clippy::from_over_into)] - -use crate as pallet_template; -use sp_core::{H160, H256}; -use frame_support::{parameter_types, traits::Everything, weights::IdentityFee}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - testing::Header, -}; -use pallet_transaction_payment::{CurrencyAdapter}; -use frame_system as system; -use pallet_evm::{AddressMapping, runner::stack::MaybeMirroredLog, account::CrossAccountId}; -use fp_evm_mapping::EvmBackwardsAddressMapping; -use codec::{Encode, Decode, MaxEncodedLen}; -use scale_info::TypeInfo; -use up_data_structs::ConstU32; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: pallet_template::{Pallet, Call, Storage}, - Balances: pallet_balances::{Pallet, Call, Storage}, - Common: pallet_common::{Pallet, Storage, Event}, - Fungible: pallet_fungible::{Pallet, Storage}, - Refungible: pallet_refungible::{Pallet, Storage}, - Nonfungible: pallet_nonfungible::{Pallet, Storage}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - -impl system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxLocks: u32 = 50; -} -//frame_system::Module; -impl pallet_balances::Config for Test { - type AccountStore = System; - type Balance = u64; - type DustRemoval = (); - type Event = (); - type ExistentialDeposit = ExistentialDeposit; - type WeightInfo = (); - type MaxLocks = MaxLocks; - type MaxReserves = (); - type ReserveIdentifier = [u8; 8]; -} - -parameter_types! { - pub const OperationalFeeMultiplier: u8 = 5; -} - -impl pallet_transaction_payment::Config for Test { - type OnChargeTransaction = CurrencyAdapter, ()>; - type LengthToFee = IdentityFee; - type WeightToFee = IdentityFee; - type FeeMultiplierUpdate = (); - type OperationalFeeMultiplier = OperationalFeeMultiplier; -} - -parameter_types! { - pub const MinimumPeriod: u64 = 1; -} -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - pub const CollectionCreationPrice: u32 = 100; - pub TreasuryAccountId: u64 = 1234; - pub EthereumChainId: u32 = 1111; -} - -pub struct TestEvmAddressMapping; -impl AddressMapping for TestEvmAddressMapping { - fn into_account_id(_addr: sp_core::H160) -> u64 { - unimplemented!() - } -} - -pub struct TestEvmBackwardsAddressMapping; -impl EvmBackwardsAddressMapping for TestEvmBackwardsAddressMapping { - fn from_account_id(_account_id: u64) -> sp_core::H160 { - unimplemented!() - } -} - -#[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, MaxEncodedLen)] -pub struct TestCrossAccountId(u64, sp_core::H160); -impl CrossAccountId for TestCrossAccountId { - fn as_sub(&self) -> &u64 { - &self.0 - } - fn as_eth(&self) -> &sp_core::H160 { - &self.1 - } - fn from_sub(sub: u64) -> Self { - let mut eth = [0; 20]; - eth[12..20].copy_from_slice(&sub.to_be_bytes()); - Self(sub, sp_core::H160(eth)) - } - fn from_eth(eth: sp_core::H160) -> Self { - let mut sub_raw = [0; 8]; - sub_raw.copy_from_slice(ð.0[0..8]); - let sub = u64::from_be_bytes(sub_raw); - Self(sub, eth) - } - fn conv_eq(&self, other: &Self) -> bool { - self.as_sub() == other.as_sub() - } -} - -impl Default for TestCrossAccountId { - fn default() -> Self { - Self::from_sub(0) - } -} - -pub struct TestEtheremTransactionSender; -impl pallet_ethereum::EthereumTransactionSender for TestEtheremTransactionSender { - fn submit_logs_transaction(_source: H160, _logs: Vec) {} -} - -impl pallet_evm_coder_substrate::Config for Test { - type EthereumTransactionSender = TestEtheremTransactionSender; - type GasWeightMapping = (); -} - -impl pallet_common::Config for Test { - type Event = (); - type Currency = Balances; - type CollectionCreationPrice = CollectionCreationPrice; - type TreasuryAccountId = TreasuryAccountId; -} - -impl pallet_evm::account::Config for Test { - type CrossAccountId = TestCrossAccountId; - type EvmAddressMapping = TestEvmAddressMapping; - type EvmBackwardsAddressMapping = TestEvmBackwardsAddressMapping; -} - -impl pallet_fungible::Config for Test { - type WeightInfo = (); -} -impl pallet_refungible::Config for Test { - type WeightInfo = (); -} -impl pallet_nonfungible::Config for Test { - type WeightInfo = (); -} - -impl pallet_template::Config for Test { - type Event = (); - type WeightInfo = (); -} - -// Build genesis storage according to the mock runtime. -pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default() - .build_storage::() - .unwrap() - .into() -} --- a/pallets/unique/src/sponsorship.rs +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use crate::{ - Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, - FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket, - FungibleApproveBasket, RefungibleApproveBasket, -}; -use core::marker::PhantomData; -use up_sponsorship::SponsorshipHandler; -use frame_support::{ - traits::{IsSubType}, - storage::{StorageMap, StorageDoubleMap, StorageNMap}, -}; -use up_data_structs::{ - CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission, - NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, -}; -use sp_runtime::traits::Saturating; -use pallet_common::{CollectionHandle}; -use pallet_evm::account::CrossAccountId; - -pub fn withdraw_transfer( - collection: &CollectionHandle, - who: &T::CrossAccountId, - item_id: &TokenId, -) -> Option<()> { - // preliminary sponsoring correctness check - match collection.mode { - CollectionMode::NFT => { - let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; - if !owner.conv_eq(who) { - return None; - } - } - CollectionMode::Fungible(_) => { - if item_id != &TokenId::default() { - return None; - } - if >::get((collection.id, who)) == 0 { - return None; - } - } - CollectionMode::ReFungible => { - if !>::get((collection.id, who, item_id)) { - return None; - } - } - } - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match collection.mode { - CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, item_id), - CollectionMode::Fungible(_) => { - >::get(collection.id, who.as_sub()) - } - CollectionMode::ReFungible => { - >::get((collection.id, item_id, who.as_sub())) - } - }; - - if let Some(last_tx_block) = last_tx_block { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - match collection.mode { - CollectionMode::NFT => >::insert(collection.id, item_id, block_number), - CollectionMode::Fungible(_) => { - >::insert(collection.id, who.as_sub(), block_number) - } - CollectionMode::ReFungible => >::insert( - (collection.id, item_id, who.as_sub()), - block_number, - ), - }; - - Some(()) -} - -pub fn withdraw_create_item( - collection: &CollectionHandle, - who: &T::CrossAccountId, - _properties: &CreateItemData, -) -> Option<()> { - if _properties.data_size() as u32 > collection.limits.sponsored_data_size() { - return None; - } - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match _properties { - CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - if let Some(last_tx_block) = >::get((collection.id, who.as_sub())) { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - CreateItemBasket::::insert((collection.id, who.as_sub()), block_number); - - Some(()) -} - -pub fn withdraw_set_variable_meta_data( - who: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: &TokenId, - data: &[u8], -) -> Option<()> { - // TODO: make it work for admins - if collection.meta_update_permission != MetaUpdatePermission::ItemOwner { - return None; - } - // preliminary sponsoring correctness check - match collection.mode { - CollectionMode::NFT => { - let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; - if !owner.conv_eq(who) { - return None; - } - } - CollectionMode::Fungible(_) => { - if item_id != &TokenId::default() { - return None; - } - if >::get((collection.id, who)) == 0 { - return None; - } - } - CollectionMode::ReFungible => { - if !>::get((collection.id, who, item_id)) { - return None; - } - } - } - - // Can't sponsor fungible collection, this tx will be rejected - // as invalid - if matches!(collection.mode, CollectionMode::Fungible(_)) { - return None; - } - if data.len() > collection.limits.sponsored_data_size() as usize { - return None; - } - - let block_number = >::block_number() as T::BlockNumber; - let limit = collection.limits.sponsored_data_rate_limit()?; - - if let Some(last_tx_block) = VariableMetaDataBasket::::get(collection.id, item_id) { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - >::insert(collection.id, item_id, block_number); - - Some(()) -} - -pub fn withdraw_approve( - collection: &CollectionHandle, - who: &T::AccountId, - item_id: &TokenId, -) -> Option<()> { - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection.limits.sponsor_approve_timeout(); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, item_id), - CollectionMode::Fungible(_) => >::get(collection.id, who), - CollectionMode::ReFungible => { - >::get((collection.id, item_id, who)) - } - }; - - if let Some(last_tx_block) = last_tx_block { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - match collection.mode { - CollectionMode::NFT => >::insert(collection.id, item_id, block_number), - CollectionMode::Fungible(_) => { - >::insert(collection.id, who, block_number) - } - CollectionMode::ReFungible => { - >::insert((collection.id, item_id, who), block_number) - } - }; - - Some(()) -} - -fn load(id: CollectionId) -> Option<(T::AccountId, CollectionHandle)> { - let collection = CollectionHandle::new(id)?; - let sponsor = collection.sponsorship.sponsor().cloned()?; - Some((sponsor, collection)) -} - -pub struct UniqueSponsorshipHandler(PhantomData); -impl SponsorshipHandler for UniqueSponsorshipHandler -where - T: Config, - C: IsSubType>, -{ - fn get_sponsor(who: &T::AccountId, call: &C) -> Option { - match IsSubType::>::is_sub_type(call)? { - Call::create_item { - collection_id, - data, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_create_item::( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - data, - ) - .map(|()| sponsor) - } - Call::transfer { - collection_id, - item_id, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_transfer::( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - item_id, - ) - .map(|()| sponsor) - } - Call::transfer_from { - collection_id, - item_id, - from, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_transfer::(&collection, from, item_id).map(|()| sponsor) - } - Call::approve { - collection_id, - item_id, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_approve::(&collection, who, item_id).map(|()| sponsor) - } - Call::set_variable_meta_data { - collection_id, - item_id, - data, - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_set_variable_meta_data::( - &T::CrossAccountId::from_sub(who.clone()), - &collection, - item_id, - data, - ) - .map(|()| sponsor) - } - _ => None, - } - } -} - -use crate::SponsorshipPredict; -pub struct UniqueSponsorshipPredict(PhantomData); - -impl SponsorshipPredict for UniqueSponsorshipPredict -where - T: Config, -{ - fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option - where - u64: From<::BlockNumber>, - { - let collection = >::try_get(collection_id).ok()?; - let _ = collection.sponsorship.sponsor()?; - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match collection.mode { - CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, token), - CollectionMode::Fungible(_) => { - >::get(collection.id, who.as_sub()) - } - CollectionMode::ReFungible => { - >::get((collection.id, token, who.as_sub())) - } - }; - - if let Some(last_tx_block) = last_tx_block { - return Some( - last_tx_block - .saturating_add(limit.into()) - .saturating_sub(block_number) - .into(), - ); - } - - let token_exists = match collection.mode { - CollectionMode::NFT => { - >::contains_key((collection.id, token)) - } - CollectionMode::Fungible(_) => true, - CollectionMode::ReFungible => { - >::contains_key((collection.id, token)) - } - }; - - if token_exists { - Some(0) - } else { - None - } - } -} --- a/pallets/unique/src/tests.rs +++ /dev/null @@ -1,2912 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -// Tests to be written here -use super::*; -use crate::mock::*; -use crate::{AccessMode, CollectionMode}; -use up_data_structs::{ - COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData, - CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, - TokenId, MAX_TOKEN_OWNERSHIP, -}; -use frame_support::{assert_noop, assert_ok, assert_err}; -use sp_std::convert::TryInto; -use pallet_balances; - -fn add_balance(user: u64, value: u64) { - const DONOR_USER: u64 = 999; - assert_ok!(>::set_balance( - Origin::root(), - DONOR_USER, - value, - 0 - )); - assert_ok!(>::force_transfer( - Origin::root(), - DONOR_USER, - user, - value - )); -} - -fn default_nft_data() -> CreateNftData { - CreateNftData { - const_data: vec![1, 2, 3].try_into().unwrap(), - variable_data: vec![3, 2, 1].try_into().unwrap(), - } -} - -fn default_fungible_data() -> CreateFungibleData { - CreateFungibleData { value: 5 } -} - -fn default_re_fungible_data() -> CreateReFungibleData { - CreateReFungibleData { - const_data: vec![1, 2, 3].try_into().unwrap(), - variable_data: vec![3, 2, 1].try_into().unwrap(), - pieces: 1023, - } -} - -fn create_test_collection_for_owner( - mode: &CollectionMode, - owner: u64, - id: CollectionId, -) -> CollectionId { - add_balance(owner, CollectionCreationPrice::get() as u64 + 1); - - let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); - let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); - let token_prefix1: Vec = b"token_prefix1\0".to_vec(); - - let data: CreateCollectionData = CreateCollectionData { - name: col_name1.try_into().unwrap(), - description: col_desc1.try_into().unwrap(), - token_prefix: token_prefix1.try_into().unwrap(), - mode: mode.clone(), - ..Default::default() - }; - - let origin1 = Origin::signed(owner); - assert_ok!(TemplateModule::create_collection_ex(origin1, data)); - - let saved_col_name: Vec = "Test1\0".encode_utf16().collect::>(); - let saved_description: Vec = "TestDescription1\0".encode_utf16().collect::>(); - let saved_prefix: Vec = b"token_prefix1\0".to_vec(); - assert_eq!( - >::get(id) - .unwrap() - .owner, - owner - ); - assert_eq!( - >::get(id).unwrap().name, - saved_col_name - ); - assert_eq!( - >::get(id).unwrap().mode, - *mode - ); - assert_eq!( - >::get(id) - .unwrap() - .description, - saved_description - ); - assert_eq!( - >::get(id) - .unwrap() - .token_prefix, - saved_prefix - ); - id -} - -fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId { - create_test_collection_for_owner(&mode, 1, id) -} - -fn create_test_item(collection_id: CollectionId, data: &CreateItemData) { - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::create_item( - origin1, - collection_id, - account(1), - data.clone() - )); -} - -fn account(sub: u64) -> TestCrossAccountId { - TestCrossAccountId::from_sub(sub) -} - -// Use cases tests region -// #region - -#[test] -fn set_version_schema() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - assert_ok!(TemplateModule::set_schema_version( - origin1, - collection_id, - SchemaVersion::Unique - )); - assert_eq!( - >::get(collection_id) - .unwrap() - .schema_version, - SchemaVersion::Unique - ); - }); -} - -#[test] -fn check_not_sufficient_founds() { - new_test_ext().execute_with(|| { - let acc: u64 = 1; - >::set_balance(Origin::root(), acc, 0, 0).unwrap(); - - let name: Vec = "Test1\0".encode_utf16().collect::>(); - let description: Vec = "TestDescription1\0".encode_utf16().collect::>(); - let token_prefix: Vec = b"token_prefix1\0".to_vec(); - - let data: CreateCollectionData<::AccountId> = - CreateCollectionData { - name: name.try_into().unwrap(), - description: description.try_into().unwrap(), - token_prefix: token_prefix.try_into().unwrap(), - mode: CollectionMode::NFT, - ..Default::default() - }; - - let result = TemplateModule::create_collection_ex(Origin::signed(acc), data); - assert_err!(result, >::NotSufficientFounds); - }); -} - -#[test] -fn create_fungible_collection_fails_with_large_decimal_numbers() { - new_test_ext().execute_with(|| { - let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); - let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); - let token_prefix1: Vec = b"token_prefix1\0".to_vec(); - - let data: CreateCollectionData = CreateCollectionData { - name: col_name1.try_into().unwrap(), - description: col_desc1.try_into().unwrap(), - token_prefix: token_prefix1.try_into().unwrap(), - mode: CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1), - ..Default::default() - }; - - let origin1 = Origin::signed(1); - assert_noop!( - TemplateModule::create_collection_ex(origin1, data), - Error::::CollectionDecimalPointLimitExceeded - ); - }); -} - -#[test] -fn create_nft_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.clone().into()); - - let item = >::get((collection_id, 1)).unwrap(); - assert_eq!(item.const_data, data.const_data.into_inner()); - assert_eq!(item.variable_data, data.variable_data.into_inner()); - }); -} - -// Use cases tests region -// #region -#[test] -fn create_nft_multiple_items() { - new_test_ext().execute_with(|| { - create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()]; - - assert_ok!(TemplateModule::create_multiple_items( - origin1, - CollectionId(1), - account(1), - items_data - .clone() - .into_iter() - .map(|d| { d.into() }) - .collect() - )); - for (index, data) in items_data.into_iter().enumerate() { - let item = >::get(( - CollectionId(1), - TokenId((index + 1) as u32), - )) - .unwrap(); - assert_eq!(item.const_data.to_vec(), data.const_data.into_inner()); - assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner()); - } - }); -} - -#[test] -fn create_refungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - - let data = default_re_fungible_data(); - create_test_item(collection_id, &data.clone().into()); - let item = >::get((collection_id, TokenId(1))); - let balance = - >::get((collection_id, TokenId(1), account(1))); - assert_eq!(item.const_data, data.const_data.into_inner()); - assert_eq!(item.variable_data, data.variable_data.into_inner()); - assert_eq!(balance, 1023); - }); -} - -#[test] -fn create_multiple_refungible_items() { - new_test_ext().execute_with(|| { - create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let items_data = vec![ - default_re_fungible_data(), - default_re_fungible_data(), - default_re_fungible_data(), - ]; - - assert_ok!(TemplateModule::create_multiple_items( - origin1, - CollectionId(1), - account(1), - items_data - .clone() - .into_iter() - .map(|d| { d.into() }) - .collect() - )); - for (index, data) in items_data.into_iter().enumerate() { - let item = >::get(( - CollectionId(1), - TokenId((index + 1) as u32), - )); - let balance = - >::get((CollectionId(1), TokenId(1), account(1))); - assert_eq!(item.const_data.to_vec(), data.const_data.into_inner()); - assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner()); - assert_eq!(balance, 1023); - } - }); -} - -#[test] -fn create_fungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - assert_eq!( - >::get((collection_id, account(1))), - 5 - ); - }); -} - -//#[test] -// fn create_multiple_fungible_items() { -// new_test_ext().execute_with(|| { -// default_limits(); - -// create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - -// let origin1 = Origin::signed(1); - -// let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()]; - -// assert_ok!(TemplateModule::create_multiple_items( -// origin1.clone(), -// 1, -// 1, -// items_data.clone().into_iter().map(|d| { d.into() }).collect() -// )); - -// for (index, _) in items_data.iter().enumerate() { -// assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as TokenId).value, 5); -// } -// assert_eq!(TemplateModule::balance_count(1, 1), 3000); -// assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]); -// }); -// } - -#[test] -fn transfer_fungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - assert_eq!( - >::get((CollectionId(1), account(1))), - 5 - ); - - // change owner scenario - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - CollectionId(1), - TokenId(0), - 5 - )); - assert_eq!( - >::get((CollectionId(1), account(1))), - 0 - ); - - // split item scenario - assert_ok!(TemplateModule::transfer( - origin2.clone(), - account(3), - CollectionId(1), - TokenId(0), - 3 - )); - - // split item and new owner has account scenario - assert_ok!(TemplateModule::transfer( - origin2, - account(3), - CollectionId(1), - TokenId(0), - 1 - )); - assert_eq!( - >::get((CollectionId(1), account(2))), - 1 - ); - assert_eq!( - >::get((CollectionId(1), account(3))), - 4 - ); - }); -} - -#[test] -fn transfer_refungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - - // Create RFT 1 in 1023 pieces for account 1 - let data = default_re_fungible_data(); - create_test_item(collection_id, &data.clone().into()); - let item = >::get((collection_id, TokenId(1))); - assert_eq!(item.const_data, data.const_data.into_inner()); - assert_eq!(item.variable_data, data.variable_data.into_inner()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(1))), - 1023 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - // Account 1 transfers all 1023 pieces of RFT 1 to account 2 - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1023 - )); - assert_eq!( - >::get((collection_id, TokenId(1), account(2))), - 1023 - ); - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - assert_eq!( - >::get((collection_id, account(2))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - false - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - true - ); - - // Account 2 transfers 500 pieces of RFT 1 to account 3 - assert_ok!(TemplateModule::transfer( - origin2.clone(), - account(3), - CollectionId(1), - TokenId(1), - 500 - )); - assert_eq!( - >::get((collection_id, TokenId(1), account(2))), - 523 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(3))), - 500 - ); - assert_eq!( - >::get((collection_id, account(2))), - 1 - ); - assert_eq!( - >::get((collection_id, account(3))), - 1 - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - true - ); - assert_eq!( - >::get((collection_id, account(3), TokenId(1))), - true - ); - - // Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance - assert_ok!(TemplateModule::transfer( - origin2, - account(3), - CollectionId(1), - TokenId(1), - 200 - )); - assert_eq!( - >::get((collection_id, TokenId(1), account(2))), - 323 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(3))), - 700 - ); - assert_eq!( - >::get((collection_id, account(2))), - 1 - ); - assert_eq!( - >::get((collection_id, account(3))), - 1 - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - true - ); - assert_eq!( - >::get((collection_id, account(3), TokenId(1))), - true - ); - }); -} - -#[test] -fn transfer_nft_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - let origin1 = Origin::signed(1); - // default scenario - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - assert_eq!( - >::get((collection_id, account(2))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - false - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - true - ); - }); -} - -#[test] -fn transfer_nft_item_wrong_value() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - let origin1 = Origin::signed(1); - - assert_noop!( - TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2) - .map_err(|e| e.error), - >::NonfungibleItemsHaveNoAmount - ); - }); -} - -#[test] -fn transfer_nft_item_zero_value() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - let origin1 = Origin::signed(1); - - // Transferring 0 amount works on NFT... - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 0 - )); - // ... and results in no transfer - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - }); -} - -#[test] -fn nft_approve_and_transfer_from() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - // neg transfer_from - assert_noop!( - TemplateModule::transfer_from( - origin2.clone(), - account(1), - account(2), - CollectionId(1), - TokenId(1), - 1 - ) - .map_err(|e| e.error), - CommonError::::ApprovedValueTooLow - ); - - // do approve - assert_ok!(TemplateModule::approve( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(2) - ); - - assert_ok!(TemplateModule::transfer_from( - origin2, - account(1), - account(3), - CollectionId(1), - TokenId(1), - 1 - )); - assert!( - >::get((CollectionId(1), TokenId(1))).is_none() - ); - }); -} - -#[test] -fn nft_approve_and_transfer_from_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - // Create NFT 1 for account 1 - let data = default_nft_data(); - create_test_item(collection_id, &data.clone().into()); - assert_eq!( - &>::get((collection_id, TokenId(1))) - .unwrap() - .const_data, - &data.const_data.into_inner() - ); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - // Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - CollectionId(1), - true - )); - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - CollectionId(1), - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(3) - )); - - // Account 1 approves account 2 for NFT 1 - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(2) - ); - - // Account 2 transfers NFT 1 from account 1 to account 3 - assert_ok!(TemplateModule::transfer_from( - origin2, - account(1), - account(3), - CollectionId(1), - TokenId(1), - 1 - )); - assert!( - >::get((CollectionId(1), TokenId(1))).is_none() - ); - }); -} - -#[test] -fn refungible_approve_and_transfer_from() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - // Create RFT 1 in 1023 pieces for account 1 - let data = default_re_fungible_data(); - create_test_item(collection_id, &data.into()); - - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(1))), - 1023 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - // Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - CollectionId(1), - true - )); - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - CollectionId(1), - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(3) - )); - - // Account 1 approves account 2 for 1023 pieces of RFT 1 - assert_ok!(TemplateModule::approve( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1023 - )); - assert_eq!( - >::get(( - CollectionId(1), - TokenId(1), - account(1), - account(2) - )), - 1023 - ); - - // Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3 - assert_ok!(TemplateModule::transfer_from( - origin2, - account(1), - account(3), - CollectionId(1), - TokenId(1), - 100 - )); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(3))), - 1 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(1))), - 923 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(3))), - 100 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - assert_eq!( - >::get(( - CollectionId(1), - TokenId(1), - account(1), - account(2) - )), - 923 - ); - }); -} - -#[test] -fn fungible_approve_and_transfer_from() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - CollectionId(1), - true - )); - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - CollectionId(1), - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(3) - )); - - // do approve - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(2), - CollectionId(1), - TokenId(0), - 5 - )); - assert_eq!( - >::get((CollectionId(1), account(1), account(2))), - 5 - ); - assert_ok!(TemplateModule::approve( - origin1, - account(3), - CollectionId(1), - TokenId(0), - 5 - )); - assert_eq!( - >::get((CollectionId(1), account(1), account(2))), - 5 - ); - assert_eq!( - >::get((CollectionId(1), account(1), account(3))), - 5 - ); - - assert_ok!(TemplateModule::transfer_from( - origin2.clone(), - account(1), - account(3), - CollectionId(1), - TokenId(0), - 4 - )); - - assert_eq!( - >::get((CollectionId(1), account(1), account(2))), - 1 - ); - - assert_noop!( - TemplateModule::transfer_from( - origin2, - account(1), - account(3), - CollectionId(1), - TokenId(0), - 4 - ) - .map_err(|e| e.error), - CommonError::::ApprovedValueTooLow - ); - }); -} - -#[test] -fn change_collection_owner() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::change_collection_owner( - origin1, - collection_id, - 2 - )); - assert_eq!( - >::get(collection_id) - .unwrap() - .owner, - 2 - ); - }); -} - -#[test] -fn destroy_collection() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::destroy_collection(origin1, collection_id)); - }); -} - -#[test] -fn burn_nft_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - // check balance (collection with id = 1, user id = 1) - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - - // burn item - assert_ok!(TemplateModule::burn_item( - origin1.clone(), - collection_id, - TokenId(1), - 1 - )); - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - }); -} - -#[test] -fn burn_same_nft_item_twice() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - // check balance (collection with id = 1, user id = 1) - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - - // burn item - assert_ok!(TemplateModule::burn_item( - origin1.clone(), - collection_id, - TokenId(1), - 1 - )); - - // burn item again - assert_noop!( - TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error), - CommonError::::TokenNotFound - ); - - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - }); -} - -#[test] -fn burn_fungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account(2) - )); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - // check balance (collection with id = 1, user id = 1) - assert_eq!( - >::get((collection_id, account(1))), - 5 - ); - - // burn item - assert_ok!(TemplateModule::burn_item( - origin1.clone(), - CollectionId(1), - TokenId(0), - 5 - )); - assert_noop!( - TemplateModule::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error), - CommonError::::TokenValueTooLow - ); - - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - }); -} - -#[test] -fn burn_fungible_item_with_token_id() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account(2) - )); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - // check balance (collection with id = 1, user id = 1) - assert_eq!( - >::get((collection_id, account(1))), - 5 - ); - - // Try to burn item using Token ID - assert_noop!( - TemplateModule::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error), - >::FungibleItemsHaveNoId - ); - }); -} -#[test] -fn burn_refungible_item() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - true - )); - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account(2) - )); - - let data = default_re_fungible_data(); - create_test_item(collection_id, &data.into()); - - // check balance (collection with id = 1, user id = 2) - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, TokenId(1), account(1))), - 1023 - ); - - // burn item - assert_ok!(TemplateModule::burn_item( - origin1.clone(), - collection_id, - TokenId(1), - 1023 - )); - assert_noop!( - TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1023) - .map_err(|e| e.error), - CommonError::::TokenValueTooLow - ); - - assert_eq!( - >::get((collection_id, TokenId(1), account(1))), - 0 - ); - }); -} - -#[test] -fn add_collection_admin() { - new_test_ext().execute_with(|| { - let collection1_id = - create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); - let origin1 = Origin::signed(1); - - // Add collection admins - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection1_id, - account(2) - )); - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection1_id, - account(3) - )); - - // Owner is not an admin by default - assert_eq!( - >::get((CollectionId(1), account(1))), - false - ); - assert!(>::get(( - CollectionId(1), - account(2) - ))); - assert!(>::get(( - CollectionId(1), - account(3) - ))); - }); -} - -#[test] -fn remove_collection_admin() { - new_test_ext().execute_with(|| { - let collection1_id = - create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - // Add collection admins 2 and 3 - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection1_id, - account(2) - )); - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection1_id, - account(3) - )); - - assert!(>::get(( - CollectionId(1), - account(2) - ))); - assert!(>::get(( - CollectionId(1), - account(3) - ))); - - // remove admin 3 - assert_ok!(TemplateModule::remove_collection_admin( - origin2, - CollectionId(1), - account(3) - )); - - // 2 is still admin, 3 is not an admin anymore - assert!(>::get(( - CollectionId(1), - account(2) - ))); - assert_eq!( - >::get((CollectionId(1), account(3))), - false - ); - }); -} - -#[test] -fn balance_of() { - new_test_ext().execute_with(|| { - let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let fungible_collection_id = - create_test_collection(&CollectionMode::Fungible(3), CollectionId(2)); - let re_fungible_collection_id = - create_test_collection(&CollectionMode::ReFungible, CollectionId(3)); - - // check balance before - assert_eq!( - >::get((nft_collection_id, account(1))), - 0 - ); - assert_eq!( - >::get((fungible_collection_id, account(1))), - 0 - ); - assert_eq!( - >::get((re_fungible_collection_id, account(1))), - 0 - ); - - let nft_data = default_nft_data(); - create_test_item(nft_collection_id, &nft_data.into()); - - let fungible_data = default_fungible_data(); - create_test_item(fungible_collection_id, &fungible_data.into()); - - let re_fungible_data = default_re_fungible_data(); - create_test_item(re_fungible_collection_id, &re_fungible_data.into()); - - // check balance (collection with id = 1, user id = 1) - assert_eq!( - >::get((nft_collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((fungible_collection_id, account(1))), - 5 - ); - assert_eq!( - >::get((re_fungible_collection_id, account(1))), - 1 - ); - - assert_eq!( - >::get((nft_collection_id, account(1), TokenId(1))), - true - ); - assert_eq!( - >::get(( - re_fungible_collection_id, - account(1), - TokenId(1) - )), - true - ); - }); -} - -#[test] -fn approve() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - let origin1 = Origin::signed(1); - - // approve - assert_ok!(TemplateModule::approve( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(2) - ); - }); -} - -#[test] -fn transfer_from() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - // approve - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(2) - ); - - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - CollectionId(1), - true - )); - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - CollectionId(1), - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - CollectionId(1), - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - CollectionId(1), - account(3) - )); - - assert_ok!(TemplateModule::transfer_from( - origin2, - account(1), - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - - // after transfer - assert_eq!( - >::get((CollectionId(1), account(1))), - 0 - ); - assert_eq!( - >::get((CollectionId(1), account(2))), - 1 - ); - }); -} - -// #endregion - -// Coverage tests region -// #region - -#[test] -fn owner_can_add_address_to_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(2) - )); - assert!(>::get(( - collection_id, - account(2) - ))); - }); -} - -#[test] -fn admin_can_add_address_to_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection_id, - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin2, - collection_id, - account(3) - )); - assert!(>::get(( - collection_id, - account(3) - ))); - }); -} - -#[test] -fn nonprivileged_user_cannot_add_address_to_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin2 = Origin::signed(2); - assert_noop!( - TemplateModule::add_to_allow_list(origin2, collection_id, account(3)), - CommonError::::NoPermission - ); - }); -} - -#[test] -fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - - assert_noop!( - TemplateModule::add_to_allow_list(origin1, CollectionId(1), account(2)), - CommonError::::CollectionNotFound - ); - }); -} - -#[test] -fn nobody_can_add_address_to_allow_list_of_deleted_collection() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::destroy_collection( - origin1.clone(), - collection_id - )); - assert_noop!( - TemplateModule::add_to_allow_list(origin1, collection_id, account(2)), - CommonError::::CollectionNotFound - ); - }); -} - -// If address is already added to allow list, nothing happens -#[test] -fn address_is_already_added_to_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(2) - )); - assert!(>::get(( - collection_id, - account(2) - ))); - }); -} - -#[test] -fn owner_can_remove_address_from_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - assert_ok!(TemplateModule::remove_from_allow_list( - origin1, - collection_id, - account(2) - )); - assert_eq!( - >::get((collection_id, account(2))), - false - ); - }); -} - -#[test] -fn admin_can_remove_address_from_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - // Owner adds admin - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account(2) - )); - - // Owner adds address 3 to allow list - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(3) - )); - - // Admin removes address 3 from allow list - assert_ok!(TemplateModule::remove_from_allow_list( - origin2, - collection_id, - account(3) - )); - assert_eq!( - >::get((collection_id, account(3))), - false - ); - }); -} - -#[test] -fn nonprivileged_user_cannot_remove_address_from_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(2) - )); - assert_noop!( - TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)), - CommonError::::NoPermission - ); - assert!(>::get(( - collection_id, - account(2) - ))); - }); -} - -#[test] -fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - - assert_noop!( - TemplateModule::remove_from_allow_list(origin1, CollectionId(1), account(2)), - CommonError::::CollectionNotFound - ); - }); -} - -#[test] -fn nobody_can_remove_address_from_allow_list_of_deleted_collection() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - // Add account 2 to allow list - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - // Account 2 is in collection allow-list - assert!(>::get(( - collection_id, - account(2) - ))); - - // Destroy collection - assert_ok!(TemplateModule::destroy_collection(origin1, collection_id)); - - // Attempt to remove account 2 from collection allow-list => error - assert_noop!( - TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)), - CommonError::::CollectionNotFound - ); - - // Account 2 is not found in collection allow-list anyway - assert_eq!( - >::get((collection_id, account(2))), - false - ); - }); -} - -// If address is already removed from allow list, nothing happens -#[test] -fn address_is_already_removed_from_allow_list() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - assert_ok!(TemplateModule::remove_from_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - assert_eq!( - >::get((collection_id, account(2))), - false - ); - assert_ok!(TemplateModule::remove_from_allow_list( - origin1, - collection_id, - account(2) - )); - assert_eq!( - >::get((collection_id, account(2))), - false - ); - }); -} - -// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests) -#[test] -fn allow_list_test_1() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - assert_noop!( - TemplateModule::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -#[test] -fn allow_list_test_2() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - // do approve - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(1), - collection_id, - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(1) - ); - - assert_ok!(TemplateModule::remove_from_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - - assert_noop!( - TemplateModule::transfer_from( - origin1, - account(1), - account(3), - CollectionId(1), - TokenId(1), - 1 - ) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests) -#[test] -fn allow_list_test_3() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - - assert_noop!( - TemplateModule::transfer(origin1, account(3), collection_id, TokenId(1), 1) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -#[test] -fn allow_list_test_4() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - // do approve - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(1), - collection_id, - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(1) - ); - - assert_ok!(TemplateModule::remove_from_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - assert_noop!( - TemplateModule::transfer_from( - origin1, - account(1), - account(3), - collection_id, - TokenId(1), - 1 - ) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -// If Public Access mode is set to AllowList, tokens can’t be destroyed by a non-allowlisted address (even if it owned them before enabling AllowList mode) -#[test] -fn allow_list_test_5() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_noop!( - TemplateModule::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method). -#[test] -fn allow_list_test_6() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - - // do approve - assert_noop!( - TemplateModule::approve(origin1, account(1), CollectionId(1), TokenId(1), 1) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and -// tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) -#[test] -fn allow_list_test_7() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - }); -} - -#[test] -fn allow_list_test_8() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - // Create NFT for account 1 - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - let origin1 = Origin::signed(1); - - // Toggle Allow List mode and add accounts 1 and 2 - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(1) - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account(2) - )); - - // Sself-approve account 1 for NFT 1 - assert_ok!(TemplateModule::approve( - origin1.clone(), - account(1), - CollectionId(1), - TokenId(1), - 1 - )); - assert_eq!( - >::get((CollectionId(1), TokenId(1))).unwrap(), - account(1) - ); - - // Transfer from 1 to 2 - assert_ok!(TemplateModule::transfer_from( - origin1, - account(1), - account(2), - CollectionId(1), - TokenId(1), - 1 - )); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner. -#[test] -fn allow_list_test_9() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1, - collection_id, - false - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin. -#[test] -fn allow_list_test_10() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - false - )); - - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection_id, - account(2) - )); - - assert_ok!(TemplateModule::create_item( - origin2, - collection_id, - account(2), - default_nft_data().into() - )); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and allow listed address. -#[test] -fn allow_list_test_11() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - false - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(2) - )); - - assert_noop!( - TemplateModule::create_item( - origin2, - CollectionId(1), - account(2), - default_nft_data().into() - ) - .map_err(|e| e.error), - CommonError::::PublicMintingNotAllowed - ); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-allow listed address. -#[test] -fn allow_list_test_12() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1, - collection_id, - false - )); - - assert_noop!( - TemplateModule::create_item( - origin2, - CollectionId(1), - account(2), - default_nft_data().into() - ) - .map_err(|e| e.error), - CommonError::::PublicMintingNotAllowed - ); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner. -#[test] -fn allow_list_test_13() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1, - collection_id, - true - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin. -#[test] -fn allow_list_test_14() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - true - )); - - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection_id, - account(2) - )); - - assert_ok!(TemplateModule::create_item( - origin2, - collection_id, - account(2), - default_nft_data().into() - )); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-allow listed address. -#[test] -fn allow_list_test_15() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1, - collection_id, - true - )); - - assert_noop!( - TemplateModule::create_item( - origin2, - collection_id, - account(2), - default_nft_data().into() - ) - .map_err(|e| e.error), - CommonError::::AddressNotInAllowlist - ); - }); -} - -// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by non-privileged and allow listed address. -#[test] -fn allow_list_test_16() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - true - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1, - collection_id, - account(2) - )); - - assert_ok!(TemplateModule::create_item( - origin2, - collection_id, - account(2), - default_nft_data().into() - )); - }); -} - -// Total number of collections. Positive test -#[test] -fn total_number_collections_bound() { - new_test_ext().execute_with(|| { - create_test_collection(&CollectionMode::NFT, CollectionId(1)); - }); -} - -#[test] -fn create_max_collections() { - new_test_ext().execute_with(|| { - for i in 1..COLLECTION_NUMBER_LIMIT { - create_test_collection(&CollectionMode::NFT, CollectionId(i)); - } - }); -} - -// Total number of collections. Negative test -#[test] -fn total_number_collections_bound_neg() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - - for i in 1..=COLLECTION_NUMBER_LIMIT { - create_test_collection(&CollectionMode::NFT, CollectionId(i)); - } - - let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); - let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); - let token_prefix1: Vec = b"token_prefix1\0".to_vec(); - - let data: CreateCollectionData = CreateCollectionData { - name: col_name1.try_into().unwrap(), - description: col_desc1.try_into().unwrap(), - token_prefix: token_prefix1.try_into().unwrap(), - mode: CollectionMode::NFT, - ..Default::default() - }; - - // 11-th collection in chain. Expects error - assert_noop!( - TemplateModule::create_collection_ex(origin1, data), - CommonError::::TotalCollectionsLimitExceeded - ); - }); -} - -// Owned tokens by a single address. Positive test -#[test] -fn owned_tokens_bound() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let data = default_nft_data(); - create_test_item(collection_id, &data.clone().into()); - create_test_item(collection_id, &data.into()); - }); -} - -// Owned tokens by a single address. Negotive test -#[test] -fn owned_tokens_bound_neg() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - for _ in 1..=MAX_TOKEN_OWNERSHIP { - let data = default_nft_data(); - create_test_item(collection_id, &data.clone().into()); - } - - let data = default_nft_data(); - assert_noop!( - TemplateModule::create_item(origin1, CollectionId(1), account(1), data.into()) - .map_err(|e| e.error), - CommonError::::AccountTokenLimitExceeded - ); - }); -} - -// Number of collection admins. Positive test -#[test] -fn collection_admins_bound() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account(2) - )); - assert_ok!(TemplateModule::add_collection_admin( - origin1, - collection_id, - account(3) - )); - }); -} - -// Number of collection admins. Negotive test -#[test] -fn collection_admins_bound_neg() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - for i in 0..COLLECTION_ADMINS_LIMIT { - assert_ok!(TemplateModule::add_collection_admin( - origin1.clone(), - collection_id, - account((2 + i).into()) - )); - } - assert_noop!( - TemplateModule::add_collection_admin( - origin1, - collection_id, - account((3 + COLLECTION_ADMINS_LIMIT).into()) - ), - CommonError::::CollectionAdminCountExceeded - ); - }); -} -// #endregion - -#[test] -fn set_const_on_chain_schema() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::set_const_on_chain_schema( - origin1, - collection_id, - b"test const on chain schema".to_vec().try_into().unwrap() - )); - - assert_eq!( - >::get(collection_id) - .unwrap() - .const_on_chain_schema, - b"test const on chain schema".to_vec() - ); - assert_eq!( - >::get(collection_id) - .unwrap() - .variable_on_chain_schema, - b"".to_vec() - ); - }); -} - -#[test] -fn set_variable_on_chain_schema() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - assert_ok!(TemplateModule::set_variable_on_chain_schema( - origin1, - collection_id, - b"test variable on chain schema" - .to_vec() - .try_into() - .unwrap() - )); - - assert_eq!( - >::get(collection_id) - .unwrap() - .const_on_chain_schema, - b"".to_vec() - ); - assert_eq!( - >::get(collection_id) - .unwrap() - .variable_on_chain_schema, - b"test variable on chain schema".to_vec() - ); - }); -} - -#[test] -fn set_variable_meta_data_on_nft_token_stores_variable_meta_data() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(CollectionId(1), &data.into()); - - let variable_data = b"test data".to_vec(); - assert_ok!(TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(1), - variable_data.clone().try_into().unwrap() - )); - - assert_eq!( - >::get((collection_id, 1)) - .unwrap() - .variable_data, - variable_data - ); - }); -} - -#[test] -fn set_variable_meta_data_on_re_fungible_token_stores_variable_meta_data() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_re_fungible_data(); - create_test_item(collection_id, &data.into()); - - let variable_data = b"test data".to_vec(); - assert_ok!(TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(1), - variable_data.clone().try_into().unwrap() - )); - - assert_eq!( - >::get((collection_id, TokenId(1))).variable_data, - variable_data - ); - }); -} - -#[test] -fn set_variable_meta_data_on_fungible_token_fails() { - new_test_ext().execute_with(|| { - let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_fungible_data(); - create_test_item(collection_id, &data.into()); - - let variable_data = b"test data".to_vec(); - assert_noop!( - TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(0), - variable_data.try_into().unwrap() - ) - .map_err(|e| e.error), - >::FungibleItemsDontHaveData - ); - }); -} - -#[test] -fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() { - new_test_ext().execute_with(|| { - //default_limits(); - - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_meta_update_permission_flag( - origin1.clone(), - collection_id, - MetaUpdatePermission::ItemOwner, - )); - - let variable_data = b"ten chars.".to_vec(); - assert_ok!(TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(1), - variable_data.clone().try_into().unwrap() - )); - - assert_eq!( - >::get((collection_id, TokenId(1))) - .unwrap() - .variable_data, - variable_data - ); - }); -} - -#[test] -fn collection_transfer_flag_works() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - assert_ok!(TemplateModule::set_transfers_enabled_flag( - origin1, - collection_id, - true - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - let origin1 = Origin::signed(1); - - // default scenario - assert_ok!(TemplateModule::transfer( - origin1, - account(2), - collection_id, - TokenId(1), - 1 - )); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - false - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - true - ); - assert_eq!( - >::get((collection_id, account(1))), - 0 - ); - assert_eq!( - >::get((collection_id, account(2))), - 1 - ); - }); -} - -#[test] -fn set_variable_meta_data_on_nft_with_admin_flag() { - new_test_ext().execute_with(|| { - // default_limits(); - - let collection_id = - create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_mint_permission( - origin2.clone(), - collection_id, - true - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin2.clone(), - collection_id, - account(1) - )); - - assert_ok!(TemplateModule::add_collection_admin( - origin2.clone(), - collection_id, - account(1) - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_meta_update_permission_flag( - origin2.clone(), - collection_id, - MetaUpdatePermission::Admin, - )); - - let variable_data = b"test.".to_vec(); - assert_ok!(TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(1), - variable_data.clone().try_into().unwrap() - )); - - assert_eq!( - >::get((collection_id, 1)) - .unwrap() - .variable_data, - variable_data - ); - }); -} - -#[test] -fn set_variable_meta_data_on_nft_with_admin_flag_neg() { - new_test_ext().execute_with(|| { - // default_limits(); - - let collection_id = - create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); - - let origin1 = Origin::signed(1); - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_mint_permission( - origin2.clone(), - collection_id, - true - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin2.clone(), - collection_id, - account(1) - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_meta_update_permission_flag( - origin2.clone(), - collection_id, - MetaUpdatePermission::Admin, - )); - - let variable_data = b"test.".to_vec(); - assert_noop!( - TemplateModule::set_variable_meta_data( - origin1, - collection_id, - TokenId(1), - variable_data.try_into().unwrap() - ) - .map_err(|e| e.error), - CommonError::::NoPermission - ); - }); -} - -#[test] -fn set_variable_meta_flag_after_freeze() { - new_test_ext().execute_with(|| { - // default_limits(); - - let collection_id = - create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); - - let origin2 = Origin::signed(2); - - assert_ok!(TemplateModule::set_meta_update_permission_flag( - origin2.clone(), - collection_id, - MetaUpdatePermission::None, - )); - assert_noop!( - TemplateModule::set_meta_update_permission_flag( - origin2.clone(), - collection_id, - MetaUpdatePermission::Admin - ), - CommonError::::MetadataFlagFrozen - ); - }); -} - -#[test] -fn set_variable_meta_data_on_nft_with_none_flag_neg() { - new_test_ext().execute_with(|| { - // default_limits(); - - let collection_id = - create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); - let origin1 = Origin::signed(1); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - - assert_ok!(TemplateModule::set_meta_update_permission_flag( - origin1.clone(), - collection_id, - MetaUpdatePermission::None, - )); - - let variable_data = b"test.".to_vec(); - assert_noop!( - TemplateModule::set_variable_meta_data( - origin1.clone(), - collection_id, - TokenId(1), - variable_data.try_into().unwrap() - ) - .map_err(|e| e.error), - CommonError::::NoPermission - ); - }); -} - -#[test] -fn collection_transfer_flag_works_neg() { - new_test_ext().execute_with(|| { - let origin1 = Origin::signed(1); - - let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); - assert_ok!(TemplateModule::set_transfers_enabled_flag( - origin1, - collection_id, - false - )); - - let data = default_nft_data(); - create_test_item(collection_id, &data.into()); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - - let origin1 = Origin::signed(1); - - // default scenario - assert_noop!( - TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1) - .map_err(|e| e.error), - CommonError::::TransferNotAllowed - ); - assert_eq!( - >::get((collection_id, account(1))), - 1 - ); - assert_eq!( - >::get((collection_id, account(2))), - 0 - ); - assert_eq!( - >::get((collection_id, account(1), TokenId(1))), - true - ); - assert_eq!( - >::get((collection_id, account(2), TokenId(1))), - false - ); - }); -} - -#[test] -fn collection_sponsoring() { - new_test_ext().execute_with(|| { - // default_limits(); - let user1 = 1_u64; - let user2 = 777_u64; - let origin1 = Origin::signed(user1); - let origin2 = Origin::signed(user2); - let account2 = account(user2); - - let collection_id = - create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1)); - assert_ok!(TemplateModule::set_collection_sponsor( - origin1.clone(), - collection_id, - user1 - )); - assert_ok!(TemplateModule::confirm_sponsorship( - origin1.clone(), - collection_id - )); - - // Expect error while have no permissions - assert!(TemplateModule::create_item( - origin2.clone(), - collection_id, - account2.clone(), - default_nft_data().into() - ) - .is_err()); - - assert_ok!(TemplateModule::set_public_access_mode( - origin1.clone(), - collection_id, - AccessMode::AllowList - )); - assert_ok!(TemplateModule::add_to_allow_list( - origin1.clone(), - collection_id, - account2.clone() - )); - assert_ok!(TemplateModule::set_mint_permission( - origin1.clone(), - collection_id, - true - )); - - assert_ok!(TemplateModule::create_item( - origin2, - collection_id, - account2, - default_nft_data().into() - )); - }); -} --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -104,4 +104,6 @@ branch = "unique-polkadot-v0.9.20" [dependencies] -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.20" } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.18-2" } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +up-sponsorship = { default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring", branch = 'polkadot-v0.9.18' } --- /dev/null +++ b/runtime/common/src/eth_sponsoring.rs @@ -0,0 +1,107 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! Implements EVM sponsoring logic via TransactionValidityHack + +use evm_coder::{Call, abi::AbiReader}; +use pallet_common::{CollectionHandle, eth::map_eth_to_id}; +use sp_core::H160; +use sp_std::prelude::*; +use up_sponsorship::SponsorshipHandler; +use core::marker::PhantomData; +use core::convert::TryInto; +use pallet_evm::account::CrossAccountId; +use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode}; +use pallet_unique::Config as UniqueConfig; + +use crate::sponsoring::*; + +use pallet_nonfungible::erc::{ + UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, +}; +use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; +use pallet_fungible::Config as FungibleConfig; +use pallet_nonfungible::Config as NonfungibleConfig; +use pallet_refungible::Config as RefungibleConfig; + +pub struct UniqueEthSponsorshipHandler(PhantomData<*const T>); +impl + SponsorshipHandler)> for UniqueEthSponsorshipHandler +{ + fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { + let collection_id = map_eth_to_id(&call.0)?; + let collection = >::new(collection_id)?; + let sponsor = collection.sponsorship.sponsor()?.clone(); + let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; + Some(T::CrossAccountId::from_sub(match &collection.mode { + CollectionMode::NFT => { + let call = >::parse(method_id, &mut reader).ok()??; + match call { + UniqueNFTCall::ERC721UniqueExtensions( + ERC721UniqueExtensionsCall::Transfer { token_id, .. }, + ) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721Mintable( + ERC721MintableCall::Mint { token_id, .. } + | ERC721MintableCall::MintWithTokenUri { token_id, .. }, + ) => { + let _token_id: TokenId = token_id.try_into().ok()?; + withdraw_create_item::( + &collection, + &who, + &CreateItemData::NFT(CreateNftData::default()), + ) + .map(|()| sponsor) + } + UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_approve::(&collection, who.as_sub(), &token_id) + .map(|()| sponsor) + } + _ => None, + } + } + CollectionMode::Fungible(_) => { + let call = >::parse(method_id, &mut reader).ok()??; + #[allow(clippy::single_match)] + match call { + UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { + withdraw_transfer::(&collection, who, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { + withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) + .map(|()| sponsor) + } + _ => None, + } + } + _ => None, + }?)) + } +} --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -2,5 +2,8 @@ pub mod constants; pub mod dispatch; +pub mod eth_sponsoring; pub mod runtime_apis; +pub mod sponsoring; pub mod types; +pub mod weights; --- /dev/null +++ b/runtime/common/src/sponsoring.rs @@ -0,0 +1,375 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use core::marker::PhantomData; +use up_sponsorship::SponsorshipHandler; +use frame_support::{ + traits::{IsSubType}, + storage::{StorageMap, StorageDoubleMap, StorageNMap}, +}; +use up_data_structs::{ + CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MetaUpdatePermission, + NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, + CreateItemData, +}; +use sp_runtime::traits::Saturating; +use pallet_common::{CollectionHandle}; +use pallet_evm::account::CrossAccountId; +use pallet_unique::{ + Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket, + NftApproveBasket, VariableMetaDataBasket, CreateItemBasket, ReFungibleTransferBasket, + FungibleTransferBasket, NftTransferBasket, +}; +use pallet_fungible::Config as FungibleConfig; +use pallet_nonfungible::Config as NonfungibleConfig; +use pallet_refungible::Config as RefungibleConfig; + +pub fn withdraw_transfer< + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +>( + collection: &CollectionHandle, + who: &T::CrossAccountId, + item_id: &TokenId, +) -> Option<()> { + // preliminary sponsoring correctness check + match collection.mode { + CollectionMode::NFT => { + let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; + if !owner.conv_eq(who) { + return None; + } + } + CollectionMode::Fungible(_) => { + if item_id != &TokenId::default() { + return None; + } + if >::get((collection.id, who)) == 0 { + return None; + } + } + CollectionMode::ReFungible => { + if !>::get((collection.id, who, item_id)) { + return None; + } + } + } + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match collection.mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => { + >::get(collection.id, who.as_sub()) + } + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who.as_sub())) + } + }; + + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who.as_sub(), block_number) + } + CollectionMode::ReFungible => >::insert( + (collection.id, item_id, who.as_sub()), + block_number, + ), + }; + + Some(()) +} + +pub fn withdraw_create_item< + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +>( + collection: &CollectionHandle, + who: &T::CrossAccountId, + _properties: &CreateItemData, +) -> Option<()> { + if _properties.data_size() as u32 > collection.limits.sponsored_data_size() { + return None; + } + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match _properties { + CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + if let Some(last_tx_block) = >::get((collection.id, who.as_sub())) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + CreateItemBasket::::insert((collection.id, who.as_sub()), block_number); + + Some(()) +} + +pub fn withdraw_set_variable_meta_data< + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +>( + who: &T::CrossAccountId, + collection: &CollectionHandle, + item_id: &TokenId, + data: &[u8], +) -> Option<()> { + // TODO: make it work for admins + if collection.meta_update_permission != MetaUpdatePermission::ItemOwner { + return None; + } + // preliminary sponsoring correctness check + match collection.mode { + CollectionMode::NFT => { + let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; + if !owner.conv_eq(who) { + return None; + } + } + CollectionMode::Fungible(_) => { + if item_id != &TokenId::default() { + return None; + } + if >::get((collection.id, who)) == 0 { + return None; + } + } + CollectionMode::ReFungible => { + if !>::get((collection.id, who, item_id)) { + return None; + } + } + } + + // Can't sponsor fungible collection, this tx will be rejected + // as invalid + if matches!(collection.mode, CollectionMode::Fungible(_)) { + return None; + } + if data.len() > collection.limits.sponsored_data_size() as usize { + return None; + } + + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsored_data_rate_limit()?; + + if let Some(last_tx_block) = VariableMetaDataBasket::::get(collection.id, item_id) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + >::insert(collection.id, item_id, block_number); + + Some(()) +} + +pub fn withdraw_approve( + collection: &CollectionHandle, + who: &T::AccountId, + item_id: &TokenId, +) -> Option<()> { + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsor_approve_timeout(); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => >::get(collection.id, who), + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who)) + } + }; + + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who, block_number) + } + CollectionMode::ReFungible => { + >::insert((collection.id, item_id, who), block_number) + } + }; + + Some(()) +} + +fn load(id: CollectionId) -> Option<(T::AccountId, CollectionHandle)> { + let collection = CollectionHandle::new(id)?; + let sponsor = collection.sponsorship.sponsor().cloned()?; + Some((sponsor, collection)) +} + +pub struct UniqueSponsorshipHandler(PhantomData); +impl SponsorshipHandler for UniqueSponsorshipHandler +where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + C: IsSubType>, +{ + fn get_sponsor(who: &T::AccountId, call: &C) -> Option { + match IsSubType::>::is_sub_type(call)? { + UniqueCall::create_item { + collection_id, + data, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_create_item::( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + data, + ) + .map(|()| sponsor) + } + UniqueCall::transfer { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_transfer::( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + item_id, + ) + .map(|()| sponsor) + } + UniqueCall::transfer_from { + collection_id, + item_id, + from, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_transfer::(&collection, from, item_id).map(|()| sponsor) + } + UniqueCall::approve { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_approve::(&collection, who, item_id).map(|()| sponsor) + } + UniqueCall::set_variable_meta_data { + collection_id, + item_id, + data, + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_set_variable_meta_data::( + &T::CrossAccountId::from_sub(who.clone()), + &collection, + item_id, + data, + ) + .map(|()| sponsor) + } + _ => None, + } + } +} + +use crate::SponsorshipPredict; +pub struct UniqueSponsorshipPredict(PhantomData); + +impl SponsorshipPredict for UniqueSponsorshipPredict +where + T: Config, +{ + fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option + where + u64: From<::BlockNumber>, + { + let collection = >::try_get(collection_id).ok()?; + let _ = collection.sponsorship.sponsor()?; + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match collection.mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, token), + CollectionMode::Fungible(_) => { + >::get(collection.id, who.as_sub()) + } + CollectionMode::ReFungible => { + >::get((collection.id, token, who.as_sub())) + } + }; + + if let Some(last_tx_block) = last_tx_block { + return Some( + last_tx_block + .saturating_add(limit.into()) + .saturating_sub(block_number) + .into(), + ); + } + + let token_exists = match collection.mode { + CollectionMode::NFT => { + >::contains_key((collection.id, token)) + } + CollectionMode::Fungible(_) => true, + CollectionMode::ReFungible => { + >::contains_key((collection.id, token)) + } + }; + + if token_exists { + Some(0) + } else { + None + } + } +} --- /dev/null +++ b/runtime/common/src/weights.rs @@ -0,0 +1,76 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use core::marker::PhantomData; +use frame_support::{weights::Weight}; +use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight}; + +use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights}; +use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights}; +use pallet_refungible::{Config as RefungibleConfig, common::CommonWeights as RefungibleWeights}; +use up_data_structs::CreateItemExData; + +macro_rules! max_weight_of { + ($method:ident ( $($args:tt)* )) => { + >::$method($($args)*) + .max(>::$method($($args)*)) + .max(>::$method($($args)*)) + }; +} + +pub struct CommonWeights(PhantomData) +where + T: FungibleConfig + NonfungibleConfig + RefungibleConfig; +impl CommonWeightInfo for CommonWeights +where + T: FungibleConfig + NonfungibleConfig + RefungibleConfig, +{ + fn create_item() -> Weight { + dispatch_weight::() + max_weight_of!(create_item()) + } + + fn create_multiple_items(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(create_multiple_items(amount)) + } + + fn create_multiple_items_ex(data: &CreateItemExData) -> Weight { + dispatch_weight::() + max_weight_of!(create_multiple_items_ex(data)) + } + + fn burn_item() -> Weight { + dispatch_weight::() + max_weight_of!(burn_item()) + } + + fn transfer() -> Weight { + dispatch_weight::() + max_weight_of!(transfer()) + } + + fn approve() -> Weight { + dispatch_weight::() + max_weight_of!(approve()) + } + + fn transfer_from() -> Weight { + dispatch_weight::() + max_weight_of!(transfer_from()) + } + + fn set_variable_metadata(bytes: u32) -> Weight { + dispatch_weight::() + max_weight_of!(set_variable_metadata(bytes)) + } + + fn burn_from() -> Weight { + dispatch_weight::() + max_weight_of!(burn_from()) + } +} --- a/runtime/opal/src/lib.rs +++ b/runtime/opal/src/lib.rs @@ -120,6 +120,9 @@ types::*, constants::*, dispatch::{CollectionDispatchT, CollectionDispatch}, + sponsoring::UniqueSponsorshipHandler, + eth_sponsoring::UniqueEthSponsorshipHandler, + weights::CommonWeights, }; pub const RUNTIME_NAME: &str = "opal"; @@ -902,6 +905,7 @@ impl pallet_unique::Config for Runtime { type Event = Event; type WeightInfo = pallet_unique::weights::SubstrateWeight; + type CommonWeightInfo = CommonWeights; } parameter_types! { @@ -923,11 +927,11 @@ // } type EvmSponsorshipHandler = ( - pallet_unique::UniqueEthSponsorshipHandler, + UniqueEthSponsorshipHandler, pallet_evm_contract_helpers::HelpersContractSponsoring, ); type SponsorshipHandler = ( - pallet_unique::UniqueSponsorshipHandler, + UniqueSponsorshipHandler, //pallet_contract_helpers::ContractSponsorshipHandler, pallet_evm_transaction_payment::BridgeSponsorshipHandler, ); --- /dev/null +++ b/runtime/tests/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "tests" +version = "0.1.0" +edition = "2021" + +[dependencies] +unique-runtime-common = { path = '../common' } +up-data-structs = { default-features = false, path = '../../primitives/data-structs' } + +sp-core = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +sp-std = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +sp-io = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +sp-runtime = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } + +fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.18-2" } + +frame-support = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +frame-system = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } + +pallet-balances = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +pallet-transaction-payment = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } +pallet-timestamp = { git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.18' } + +pallet-evm = { default-features = false, git = 'https://github.com/uniquenetwork/frontier.git', branch = 'unique-polkadot-v0.9.18-2' } +pallet-ethereum = { default-features = false, git = 'https://github.com/uniquenetwork/frontier.git', branch = 'unique-polkadot-v0.9.18-2' } + +pallet-common = { path = '../../pallets/common' } +pallet-structure = { path = '../../pallets/structure' } +pallet-fungible = { path = '../../pallets/fungible' } +pallet-nonfungible = { path = '../../pallets/nonfungible' } +pallet-refungible = { path = '../../pallets/refungible' } +pallet-unique = { path = '../../pallets/unique' } + +pallet-evm-coder-substrate = { path = '../../pallets/evm-coder-substrate' } + +parity-scale-codec = "*" +scale-info = "*" --- /dev/null +++ b/runtime/tests/src/lib.rs @@ -0,0 +1,238 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +#![allow(clippy::from_over_into)] + +use sp_core::{H160, H256}; +use frame_support::{ + parameter_types, + traits::{Everything, ConstU32}, + weights::IdentityFee, +}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + testing::Header, +}; +use pallet_transaction_payment::{CurrencyAdapter}; +use frame_system as system; +use pallet_evm::{AddressMapping, runner::stack::MaybeMirroredLog, account::CrossAccountId}; +use fp_evm_mapping::EvmBackwardsAddressMapping; +use parity_scale_codec::{Encode, Decode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use unique_runtime_common::{dispatch::CollectionDispatchT, weights::CommonWeights}; +use up_data_structs::mapping::{CrossTokenAddressMapping, EvmTokenAddressMapping}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +#[cfg(test)] +mod tests; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Unique: pallet_unique::{Pallet, Call, Storage}, + Balances: pallet_balances::{Pallet, Call, Storage}, + Common: pallet_common::{Pallet, Storage, Event}, + Fungible: pallet_fungible::{Pallet, Storage}, + Refungible: pallet_refungible::{Pallet, Storage}, + Nonfungible: pallet_nonfungible::{Pallet, Storage}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 50; +} +//frame_system::Module; +impl pallet_balances::Config for Test { + type AccountStore = System; + type Balance = u64; + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = ExistentialDeposit; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const OperationalFeeMultiplier: u8 = 5; +} + +impl pallet_transaction_payment::Config for Test { + type OnChargeTransaction = CurrencyAdapter, ()>; + type LengthToFee = IdentityFee; + type WeightToFee = IdentityFee; + type FeeMultiplierUpdate = (); + type OperationalFeeMultiplier = OperationalFeeMultiplier; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const CollectionCreationPrice: u32 = 100; + pub TreasuryAccountId: u64 = 1234; + pub EthereumChainId: u32 = 1111; +} + +pub struct TestEvmAddressMapping; +impl AddressMapping for TestEvmAddressMapping { + fn into_account_id(_addr: sp_core::H160) -> u64 { + unimplemented!() + } +} + +pub struct TestEvmBackwardsAddressMapping; +impl EvmBackwardsAddressMapping for TestEvmBackwardsAddressMapping { + fn from_account_id(_account_id: u64) -> sp_core::H160 { + unimplemented!() + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, TypeInfo, MaxEncodedLen)] +pub struct TestCrossAccountId(u64, sp_core::H160); +impl CrossAccountId for TestCrossAccountId { + fn as_sub(&self) -> &u64 { + &self.0 + } + fn as_eth(&self) -> &sp_core::H160 { + &self.1 + } + fn from_sub(sub: u64) -> Self { + let mut eth = [0; 20]; + eth[12..20].copy_from_slice(&sub.to_be_bytes()); + Self(sub, sp_core::H160(eth)) + } + fn from_eth(eth: sp_core::H160) -> Self { + let mut sub_raw = [0; 8]; + sub_raw.copy_from_slice(ð.0[0..8]); + let sub = u64::from_be_bytes(sub_raw); + Self(sub, eth) + } + fn conv_eq(&self, other: &Self) -> bool { + self.as_sub() == other.as_sub() + } +} + +impl Default for TestCrossAccountId { + fn default() -> Self { + Self::from_sub(0) + } +} + +pub struct TestEtheremTransactionSender; +impl pallet_ethereum::EthereumTransactionSender for TestEtheremTransactionSender { + fn submit_logs_transaction(_source: H160, _logs: Vec) {} +} + +impl pallet_evm_coder_substrate::Config for Test { + type EthereumTransactionSender = TestEtheremTransactionSender; + type GasWeightMapping = (); +} + +impl pallet_common::Config for Test { + type Event = (); + type Currency = Balances; + type CollectionCreationPrice = CollectionCreationPrice; + type TreasuryAccountId = TreasuryAccountId; + + type CollectionDispatch = CollectionDispatchT; + type EvmTokenAddressMapping = EvmTokenAddressMapping; + type CrossTokenAddressMapping = CrossTokenAddressMapping; +} + +impl pallet_evm::account::Config for Test { + type CrossAccountId = TestCrossAccountId; + type EvmAddressMapping = TestEvmAddressMapping; + type EvmBackwardsAddressMapping = TestEvmBackwardsAddressMapping; +} + +impl pallet_structure::Config for Test { + type WeightInfo = (); + type Event = (); + type Call = Call; +} +impl pallet_fungible::Config for Test { + type WeightInfo = (); +} +impl pallet_refungible::Config for Test { + type WeightInfo = (); +} +impl pallet_nonfungible::Config for Test { + type WeightInfo = (); +} + +impl pallet_unique::Config for Test { + type Event = (); + type WeightInfo = (); + type CommonWeightInfo = CommonWeights; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into() +} --- /dev/null +++ b/runtime/tests/src/tests.rs @@ -0,0 +1,2889 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +// Tests to be written here +use crate::{Test, TestCrossAccountId, CollectionCreationPrice, Origin, Unique, new_test_ext}; +use up_data_structs::{ + COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData, + CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, + TokenId, MAX_TOKEN_OWNERSHIP, CreateCollectionData, CollectionField, SchemaVersion, + CollectionMode, AccessMode, +}; +use frame_support::{assert_noop, assert_ok, assert_err}; +use sp_std::convert::TryInto; +use pallet_evm::account::CrossAccountId; +use pallet_common::Error as CommonError; +use pallet_unique::Error as UniqueError; + +fn add_balance(user: u64, value: u64) { + const DONOR_USER: u64 = 999; + assert_ok!(>::set_balance( + Origin::root(), + DONOR_USER, + value, + 0 + )); + assert_ok!(>::force_transfer( + Origin::root(), + DONOR_USER, + user, + value + )); +} + +fn default_nft_data() -> CreateNftData { + CreateNftData { + const_data: vec![1, 2, 3].try_into().unwrap(), + variable_data: vec![3, 2, 1].try_into().unwrap(), + } +} + +fn default_fungible_data() -> CreateFungibleData { + CreateFungibleData { value: 5 } +} + +fn default_re_fungible_data() -> CreateReFungibleData { + CreateReFungibleData { + const_data: vec![1, 2, 3].try_into().unwrap(), + variable_data: vec![3, 2, 1].try_into().unwrap(), + pieces: 1023, + } +} + +fn create_test_collection_for_owner( + mode: &CollectionMode, + owner: u64, + id: CollectionId, +) -> CollectionId { + add_balance(owner, CollectionCreationPrice::get() as u64 + 1); + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + + let data: CreateCollectionData = CreateCollectionData { + name: col_name1.try_into().unwrap(), + description: col_desc1.try_into().unwrap(), + token_prefix: token_prefix1.try_into().unwrap(), + mode: mode.clone(), + ..Default::default() + }; + + let origin1 = Origin::signed(owner); + assert_ok!(Unique::create_collection_ex(origin1, data)); + + let saved_col_name: Vec = "Test1\0".encode_utf16().collect::>(); + let saved_description: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let saved_prefix: Vec = b"token_prefix1\0".to_vec(); + assert_eq!( + >::get(id) + .unwrap() + .owner, + owner + ); + assert_eq!( + >::get(id).unwrap().name, + saved_col_name + ); + assert_eq!( + >::get(id).unwrap().mode, + *mode + ); + assert_eq!( + >::get(id) + .unwrap() + .description, + saved_description + ); + assert_eq!( + >::get(id) + .unwrap() + .token_prefix, + saved_prefix + ); + id +} + +fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId { + create_test_collection_for_owner(&mode, 1, id) +} + +fn create_test_item(collection_id: CollectionId, data: &CreateItemData) { + let origin1 = Origin::signed(1); + assert_ok!(Unique::create_item( + origin1, + collection_id, + account(1), + data.clone() + )); +} + +fn account(sub: u64) -> TestCrossAccountId { + TestCrossAccountId::from_sub(sub) +} + +// Use cases tests region +// #region + +#[test] +fn set_version_schema() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + assert_ok!(Unique::set_schema_version( + origin1, + collection_id, + SchemaVersion::Unique + )); + assert_eq!( + >::get(collection_id) + .unwrap() + .schema_version, + SchemaVersion::Unique + ); + }); +} + +#[test] +fn check_not_sufficient_founds() { + new_test_ext().execute_with(|| { + let acc: u64 = 1; + >::set_balance(Origin::root(), acc, 0, 0).unwrap(); + + let name: Vec = "Test1\0".encode_utf16().collect::>(); + let description: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix: Vec = b"token_prefix1\0".to_vec(); + + let data: CreateCollectionData<::AccountId> = + CreateCollectionData { + name: name.try_into().unwrap(), + description: description.try_into().unwrap(), + token_prefix: token_prefix.try_into().unwrap(), + mode: CollectionMode::NFT, + ..Default::default() + }; + + let result = Unique::create_collection_ex(Origin::signed(acc), data); + assert_err!(result, >::NotSufficientFounds); + }); +} + +#[test] +fn create_fungible_collection_fails_with_large_decimal_numbers() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + + let data: CreateCollectionData = CreateCollectionData { + name: col_name1.try_into().unwrap(), + description: col_desc1.try_into().unwrap(), + token_prefix: token_prefix1.try_into().unwrap(), + mode: CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1), + ..Default::default() + }; + + let origin1 = Origin::signed(1); + assert_noop!( + Unique::create_collection_ex(origin1, data), + UniqueError::::CollectionDecimalPointLimitExceeded + ); + }); +} + +#[test] +fn create_nft_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.clone().into()); + + let item = >::get((collection_id, 1)).unwrap(); + assert_eq!(item.const_data, data.const_data.into_inner()); + assert_eq!(item.variable_data, data.variable_data.into_inner()); + }); +} + +// Use cases tests region +// #region +#[test] +fn create_nft_multiple_items() { + new_test_ext().execute_with(|| { + create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()]; + + assert_ok!(Unique::create_multiple_items( + origin1, + CollectionId(1), + account(1), + items_data + .clone() + .into_iter() + .map(|d| { d.into() }) + .collect() + )); + for (index, data) in items_data.into_iter().enumerate() { + let item = >::get(( + CollectionId(1), + TokenId((index + 1) as u32), + )) + .unwrap(); + assert_eq!(item.const_data.to_vec(), data.const_data.into_inner()); + assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner()); + } + }); +} + +#[test] +fn create_refungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + + let data = default_re_fungible_data(); + create_test_item(collection_id, &data.clone().into()); + let item = >::get((collection_id, TokenId(1))); + let balance = + >::get((collection_id, TokenId(1), account(1))); + assert_eq!(item.const_data, data.const_data.into_inner()); + assert_eq!(item.variable_data, data.variable_data.into_inner()); + assert_eq!(balance, 1023); + }); +} + +#[test] +fn create_multiple_refungible_items() { + new_test_ext().execute_with(|| { + create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let items_data = vec![ + default_re_fungible_data(), + default_re_fungible_data(), + default_re_fungible_data(), + ]; + + assert_ok!(Unique::create_multiple_items( + origin1, + CollectionId(1), + account(1), + items_data + .clone() + .into_iter() + .map(|d| { d.into() }) + .collect() + )); + for (index, data) in items_data.into_iter().enumerate() { + let item = >::get(( + CollectionId(1), + TokenId((index + 1) as u32), + )); + let balance = + >::get((CollectionId(1), TokenId(1), account(1))); + assert_eq!(item.const_data.to_vec(), data.const_data.into_inner()); + assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner()); + assert_eq!(balance, 1023); + } + }); +} + +#[test] +fn create_fungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + assert_eq!( + >::get((collection_id, account(1))), + 5 + ); + }); +} + +//#[test] +// fn create_multiple_fungible_items() { +// new_test_ext().execute_with(|| { +// default_limits(); + +// create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + +// let origin1 = Origin::signed(1); + +// let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()]; + +// assert_ok!(Unique::create_multiple_items( +// origin1.clone(), +// 1, +// 1, +// items_data.clone().into_iter().map(|d| { d.into() }).collect() +// )); + +// for (index, _) in items_data.iter().enumerate() { +// assert_eq!(Unique::fungible_item_id(1, (index + 1) as TokenId).value, 5); +// } +// assert_eq!(Unique::balance_count(1, 1), 3000); +// assert_eq!(Unique::address_tokens(1, 1), [1, 2, 3]); +// }); +// } + +#[test] +fn transfer_fungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + assert_eq!( + >::get((CollectionId(1), account(1))), + 5 + ); + + // change owner scenario + assert_ok!(Unique::transfer( + origin1, + account(2), + CollectionId(1), + TokenId(0), + 5 + )); + assert_eq!( + >::get((CollectionId(1), account(1))), + 0 + ); + + // split item scenario + assert_ok!(Unique::transfer( + origin2.clone(), + account(3), + CollectionId(1), + TokenId(0), + 3 + )); + + // split item and new owner has account scenario + assert_ok!(Unique::transfer( + origin2, + account(3), + CollectionId(1), + TokenId(0), + 1 + )); + assert_eq!( + >::get((CollectionId(1), account(2))), + 1 + ); + assert_eq!( + >::get((CollectionId(1), account(3))), + 4 + ); + }); +} + +#[test] +fn transfer_refungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + + // Create RFT 1 in 1023 pieces for account 1 + let data = default_re_fungible_data(); + create_test_item(collection_id, &data.clone().into()); + let item = >::get((collection_id, TokenId(1))); + assert_eq!(item.const_data, data.const_data.into_inner()); + assert_eq!(item.variable_data, data.variable_data.into_inner()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(1))), + 1023 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + // Account 1 transfers all 1023 pieces of RFT 1 to account 2 + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + assert_ok!(Unique::transfer( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1023 + )); + assert_eq!( + >::get((collection_id, TokenId(1), account(2))), + 1023 + ); + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + assert_eq!( + >::get((collection_id, account(2))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + false + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + true + ); + + // Account 2 transfers 500 pieces of RFT 1 to account 3 + assert_ok!(Unique::transfer( + origin2.clone(), + account(3), + CollectionId(1), + TokenId(1), + 500 + )); + assert_eq!( + >::get((collection_id, TokenId(1), account(2))), + 523 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(3))), + 500 + ); + assert_eq!( + >::get((collection_id, account(2))), + 1 + ); + assert_eq!( + >::get((collection_id, account(3))), + 1 + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + true + ); + assert_eq!( + >::get((collection_id, account(3), TokenId(1))), + true + ); + + // Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance + assert_ok!(Unique::transfer( + origin2, + account(3), + CollectionId(1), + TokenId(1), + 200 + )); + assert_eq!( + >::get((collection_id, TokenId(1), account(2))), + 323 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(3))), + 700 + ); + assert_eq!( + >::get((collection_id, account(2))), + 1 + ); + assert_eq!( + >::get((collection_id, account(3))), + 1 + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + true + ); + assert_eq!( + >::get((collection_id, account(3), TokenId(1))), + true + ); + }); +} + +#[test] +fn transfer_nft_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + let origin1 = Origin::signed(1); + // default scenario + assert_ok!(Unique::transfer( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + assert_eq!( + >::get((collection_id, account(2))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + false + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + true + ); + }); +} + +#[test] +fn transfer_nft_item_wrong_value() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + let origin1 = Origin::signed(1); + + assert_noop!( + Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2) + .map_err(|e| e.error), + >::NonfungibleItemsHaveNoAmount + ); + }); +} + +#[test] +fn transfer_nft_item_zero_value() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + let origin1 = Origin::signed(1); + + // Transferring 0 amount works on NFT... + assert_ok!(Unique::transfer( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 0 + )); + // ... and results in no transfer + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + }); +} + +#[test] +fn nft_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + // neg transfer_from + assert_noop!( + Unique::transfer_from( + origin2.clone(), + account(1), + account(2), + CollectionId(1), + TokenId(1), + 1 + ) + .map_err(|e| e.error), + CommonError::::ApprovedValueTooLow + ); + + // do approve + assert_ok!(Unique::approve( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(2) + ); + + assert_ok!(Unique::transfer_from( + origin2, + account(1), + account(3), + CollectionId(1), + TokenId(1), + 1 + )); + assert!( + >::get((CollectionId(1), TokenId(1))).is_none() + ); + }); +} + +#[test] +fn nft_approve_and_transfer_from_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + // Create NFT 1 for account 1 + let data = default_nft_data(); + create_test_item(collection_id, &data.clone().into()); + assert_eq!( + &>::get((collection_id, TokenId(1))) + .unwrap() + .const_data, + &data.const_data.into_inner() + ); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + // Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + CollectionId(1), + true + )); + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + CollectionId(1), + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(3) + )); + + // Account 1 approves account 2 for NFT 1 + assert_ok!(Unique::approve( + origin1.clone(), + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(2) + ); + + // Account 2 transfers NFT 1 from account 1 to account 3 + assert_ok!(Unique::transfer_from( + origin2, + account(1), + account(3), + CollectionId(1), + TokenId(1), + 1 + )); + assert!( + >::get((CollectionId(1), TokenId(1))).is_none() + ); + }); +} + +#[test] +fn refungible_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + // Create RFT 1 in 1023 pieces for account 1 + let data = default_re_fungible_data(); + create_test_item(collection_id, &data.into()); + + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(1))), + 1023 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + // Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + CollectionId(1), + true + )); + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + CollectionId(1), + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(3) + )); + + // Account 1 approves account 2 for 1023 pieces of RFT 1 + assert_ok!(Unique::approve( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1023 + )); + assert_eq!( + >::get(( + CollectionId(1), + TokenId(1), + account(1), + account(2) + )), + 1023 + ); + + // Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3 + assert_ok!(Unique::transfer_from( + origin2, + account(1), + account(3), + CollectionId(1), + TokenId(1), + 100 + )); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(3))), + 1 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(1))), + 923 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(3))), + 100 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + assert_eq!( + >::get(( + CollectionId(1), + TokenId(1), + account(1), + account(2) + )), + 923 + ); + }); +} + +#[test] +fn fungible_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + CollectionId(1), + true + )); + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + CollectionId(1), + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(3) + )); + + // do approve + assert_ok!(Unique::approve( + origin1.clone(), + account(2), + CollectionId(1), + TokenId(0), + 5 + )); + assert_eq!( + >::get((CollectionId(1), account(1), account(2))), + 5 + ); + assert_ok!(Unique::approve( + origin1, + account(3), + CollectionId(1), + TokenId(0), + 5 + )); + assert_eq!( + >::get((CollectionId(1), account(1), account(2))), + 5 + ); + assert_eq!( + >::get((CollectionId(1), account(1), account(3))), + 5 + ); + + assert_ok!(Unique::transfer_from( + origin2.clone(), + account(1), + account(3), + CollectionId(1), + TokenId(0), + 4 + )); + + assert_eq!( + >::get((CollectionId(1), account(1), account(2))), + 1 + ); + + assert_noop!( + Unique::transfer_from( + origin2, + account(1), + account(3), + CollectionId(1), + TokenId(0), + 4 + ) + .map_err(|e| e.error), + CommonError::::ApprovedValueTooLow + ); + }); +} + +#[test] +fn change_collection_owner() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::change_collection_owner(origin1, collection_id, 2)); + assert_eq!( + >::get(collection_id) + .unwrap() + .owner, + 2 + ); + }); +} + +#[test] +fn destroy_collection() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::destroy_collection(origin1, collection_id)); + }); +} + +#[test] +fn burn_nft_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + // check balance (collection with id = 1, user id = 1) + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + + // burn item + assert_ok!(Unique::burn_item( + origin1.clone(), + collection_id, + TokenId(1), + 1 + )); + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + }); +} + +#[test] +fn burn_same_nft_item_twice() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + // check balance (collection with id = 1, user id = 1) + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + + // burn item + assert_ok!(Unique::burn_item( + origin1.clone(), + collection_id, + TokenId(1), + 1 + )); + + // burn item again + assert_noop!( + Unique::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error), + CommonError::::TokenNotFound + ); + + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + }); +} + +#[test] +fn burn_fungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account(2) + )); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + // check balance (collection with id = 1, user id = 1) + assert_eq!( + >::get((collection_id, account(1))), + 5 + ); + + // burn item + assert_ok!(Unique::burn_item( + origin1.clone(), + CollectionId(1), + TokenId(0), + 5 + )); + assert_noop!( + Unique::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error), + CommonError::::TokenValueTooLow + ); + + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + }); +} + +#[test] +fn burn_fungible_item_with_token_id() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account(2) + )); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + // check balance (collection with id = 1, user id = 1) + assert_eq!( + >::get((collection_id, account(1))), + 5 + ); + + // Try to burn item using Token ID + assert_noop!( + Unique::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error), + >::FungibleItemsHaveNoId + ); + }); +} +#[test] +fn burn_refungible_item() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + let origin1 = Origin::signed(1); + + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + true + )); + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account(2) + )); + + let data = default_re_fungible_data(); + create_test_item(collection_id, &data.into()); + + // check balance (collection with id = 1, user id = 2) + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, TokenId(1), account(1))), + 1023 + ); + + // burn item + assert_ok!(Unique::burn_item( + origin1.clone(), + collection_id, + TokenId(1), + 1023 + )); + assert_noop!( + Unique::burn_item(origin1, collection_id, TokenId(1), 1023).map_err(|e| e.error), + CommonError::::TokenValueTooLow + ); + + assert_eq!( + >::get((collection_id, TokenId(1), account(1))), + 0 + ); + }); +} + +#[test] +fn add_collection_admin() { + new_test_ext().execute_with(|| { + let collection1_id = + create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); + let origin1 = Origin::signed(1); + + // Add collection admins + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection1_id, + account(2) + )); + assert_ok!(Unique::add_collection_admin( + origin1, + collection1_id, + account(3) + )); + + // Owner is not an admin by default + assert_eq!( + >::get((CollectionId(1), account(1))), + false + ); + assert!(>::get(( + CollectionId(1), + account(2) + ))); + assert!(>::get(( + CollectionId(1), + account(3) + ))); + }); +} + +#[test] +fn remove_collection_admin() { + new_test_ext().execute_with(|| { + let collection1_id = + create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + // Add collection admins 2 and 3 + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection1_id, + account(2) + )); + assert_ok!(Unique::add_collection_admin( + origin1, + collection1_id, + account(3) + )); + + assert!(>::get(( + CollectionId(1), + account(2) + ))); + assert!(>::get(( + CollectionId(1), + account(3) + ))); + + // remove admin 3 + assert_ok!(Unique::remove_collection_admin( + origin2, + CollectionId(1), + account(3) + )); + + // 2 is still admin, 3 is not an admin anymore + assert!(>::get(( + CollectionId(1), + account(2) + ))); + assert_eq!( + >::get((CollectionId(1), account(3))), + false + ); + }); +} + +#[test] +fn balance_of() { + new_test_ext().execute_with(|| { + let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let fungible_collection_id = + create_test_collection(&CollectionMode::Fungible(3), CollectionId(2)); + let re_fungible_collection_id = + create_test_collection(&CollectionMode::ReFungible, CollectionId(3)); + + // check balance before + assert_eq!( + >::get((nft_collection_id, account(1))), + 0 + ); + assert_eq!( + >::get((fungible_collection_id, account(1))), + 0 + ); + assert_eq!( + >::get((re_fungible_collection_id, account(1))), + 0 + ); + + let nft_data = default_nft_data(); + create_test_item(nft_collection_id, &nft_data.into()); + + let fungible_data = default_fungible_data(); + create_test_item(fungible_collection_id, &fungible_data.into()); + + let re_fungible_data = default_re_fungible_data(); + create_test_item(re_fungible_collection_id, &re_fungible_data.into()); + + // check balance (collection with id = 1, user id = 1) + assert_eq!( + >::get((nft_collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((fungible_collection_id, account(1))), + 5 + ); + assert_eq!( + >::get((re_fungible_collection_id, account(1))), + 1 + ); + + assert_eq!( + >::get((nft_collection_id, account(1), TokenId(1))), + true + ); + assert_eq!( + >::get(( + re_fungible_collection_id, + account(1), + TokenId(1) + )), + true + ); + }); +} + +#[test] +fn approve() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + let origin1 = Origin::signed(1); + + // approve + assert_ok!(Unique::approve( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(2) + ); + }); +} + +#[test] +fn transfer_from() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + // approve + assert_ok!(Unique::approve( + origin1.clone(), + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(2) + ); + + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + CollectionId(1), + true + )); + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + CollectionId(1), + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + CollectionId(1), + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin1, + CollectionId(1), + account(3) + )); + + assert_ok!(Unique::transfer_from( + origin2, + account(1), + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + + // after transfer + assert_eq!( + >::get((CollectionId(1), account(1))), + 0 + ); + assert_eq!( + >::get((CollectionId(1), account(2))), + 1 + ); + }); +} + +// #endregion + +// Coverage tests region +// #region + +#[test] +fn owner_can_add_address_to_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(2) + )); + assert!(>::get(( + collection_id, + account(2) + ))); + }); +} + +#[test] +fn admin_can_add_address_to_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::add_collection_admin( + origin1, + collection_id, + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin2, + collection_id, + account(3) + )); + assert!(>::get(( + collection_id, + account(3) + ))); + }); +} + +#[test] +fn nonprivileged_user_cannot_add_address_to_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin2 = Origin::signed(2); + assert_noop!( + Unique::add_to_allow_list(origin2, collection_id, account(3)), + CommonError::::NoPermission + ); + }); +} + +#[test] +fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + + assert_noop!( + Unique::add_to_allow_list(origin1, CollectionId(1), account(2)), + CommonError::::CollectionNotFound + ); + }); +} + +#[test] +fn nobody_can_add_address_to_allow_list_of_deleted_collection() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::destroy_collection(origin1.clone(), collection_id)); + assert_noop!( + Unique::add_to_allow_list(origin1, collection_id, account(2)), + CommonError::::CollectionNotFound + ); + }); +} + +// If address is already added to allow list, nothing happens +#[test] +fn address_is_already_added_to_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(2) + )); + assert!(>::get(( + collection_id, + account(2) + ))); + }); +} + +#[test] +fn owner_can_remove_address_from_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + assert_ok!(Unique::remove_from_allow_list( + origin1, + collection_id, + account(2) + )); + assert_eq!( + >::get((collection_id, account(2))), + false + ); + }); +} + +#[test] +fn admin_can_remove_address_from_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + // Owner adds admin + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account(2) + )); + + // Owner adds address 3 to allow list + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(3) + )); + + // Admin removes address 3 from allow list + assert_ok!(Unique::remove_from_allow_list( + origin2, + collection_id, + account(3) + )); + assert_eq!( + >::get((collection_id, account(3))), + false + ); + }); +} + +#[test] +fn nonprivileged_user_cannot_remove_address_from_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(2) + )); + assert_noop!( + Unique::remove_from_allow_list(origin2, collection_id, account(2)), + CommonError::::NoPermission + ); + assert!(>::get(( + collection_id, + account(2) + ))); + }); +} + +#[test] +fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + + assert_noop!( + Unique::remove_from_allow_list(origin1, CollectionId(1), account(2)), + CommonError::::CollectionNotFound + ); + }); +} + +#[test] +fn nobody_can_remove_address_from_allow_list_of_deleted_collection() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + // Add account 2 to allow list + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + // Account 2 is in collection allow-list + assert!(>::get(( + collection_id, + account(2) + ))); + + // Destroy collection + assert_ok!(Unique::destroy_collection(origin1, collection_id)); + + // Attempt to remove account 2 from collection allow-list => error + assert_noop!( + Unique::remove_from_allow_list(origin2, collection_id, account(2)), + CommonError::::CollectionNotFound + ); + + // Account 2 is not found in collection allow-list anyway + assert_eq!( + >::get((collection_id, account(2))), + false + ); + }); +} + +// If address is already removed from allow list, nothing happens +#[test] +fn address_is_already_removed_from_allow_list() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + assert_ok!(Unique::remove_from_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + assert_eq!( + >::get((collection_id, account(2))), + false + ); + assert_ok!(Unique::remove_from_allow_list( + origin1, + collection_id, + account(2) + )); + assert_eq!( + >::get((collection_id, account(2))), + false + ); + }); +} + +// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests) +#[test] +fn allow_list_test_1() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + assert_noop!( + Unique::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +#[test] +fn allow_list_test_2() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + // do approve + assert_ok!(Unique::approve( + origin1.clone(), + account(1), + collection_id, + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(1) + ); + + assert_ok!(Unique::remove_from_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + + assert_noop!( + Unique::transfer_from( + origin1, + account(1), + account(3), + CollectionId(1), + TokenId(1), + 1 + ) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests) +#[test] +fn allow_list_test_3() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + + assert_noop!( + Unique::transfer(origin1, account(3), collection_id, TokenId(1), 1) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +#[test] +fn allow_list_test_4() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + // do approve + assert_ok!(Unique::approve( + origin1.clone(), + account(1), + collection_id, + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(1) + ); + + assert_ok!(Unique::remove_from_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + assert_noop!( + Unique::transfer_from( + origin1, + account(1), + account(3), + collection_id, + TokenId(1), + 1 + ) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +// If Public Access mode is set to AllowList, tokens can’t be destroyed by a non-allowlisted address (even if it owned them before enabling AllowList mode) +#[test] +fn allow_list_test_5() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_noop!( + Unique::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1).map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method). +#[test] +fn allow_list_test_6() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + + // do approve + assert_noop!( + Unique::approve(origin1, account(1), CollectionId(1), TokenId(1), 1) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and +// tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) +#[test] +fn allow_list_test_7() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + let origin1 = Origin::signed(1); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + assert_ok!(Unique::transfer( + origin1, + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + }); +} + +#[test] +fn allow_list_test_8() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + // Create NFT for account 1 + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + let origin1 = Origin::signed(1); + + // Toggle Allow List mode and add accounts 1 and 2 + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(1) + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account(2) + )); + + // Sself-approve account 1 for NFT 1 + assert_ok!(Unique::approve( + origin1.clone(), + account(1), + CollectionId(1), + TokenId(1), + 1 + )); + assert_eq!( + >::get((CollectionId(1), TokenId(1))).unwrap(), + account(1) + ); + + // Transfer from 1 to 2 + assert_ok!(Unique::transfer_from( + origin1, + account(1), + account(2), + CollectionId(1), + TokenId(1), + 1 + )); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner. +#[test] +fn allow_list_test_9() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + let origin1 = Origin::signed(1); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission(origin1, collection_id, false)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin. +#[test] +fn allow_list_test_10() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + false + )); + + assert_ok!(Unique::add_collection_admin( + origin1, + collection_id, + account(2) + )); + + assert_ok!(Unique::create_item( + origin2, + collection_id, + account(2), + default_nft_data().into() + )); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and allow listed address. +#[test] +fn allow_list_test_11() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + false + )); + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(2) + )); + + assert_noop!( + Unique::create_item( + origin2, + CollectionId(1), + account(2), + default_nft_data().into() + ) + .map_err(|e| e.error), + CommonError::::PublicMintingNotAllowed + ); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-allow listed address. +#[test] +fn allow_list_test_12() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission(origin1, collection_id, false)); + + assert_noop!( + Unique::create_item( + origin2, + CollectionId(1), + account(2), + default_nft_data().into() + ) + .map_err(|e| e.error), + CommonError::::PublicMintingNotAllowed + ); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner. +#[test] +fn allow_list_test_13() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission(origin1, collection_id, true)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin. +#[test] +fn allow_list_test_14() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + true + )); + + assert_ok!(Unique::add_collection_admin( + origin1, + collection_id, + account(2) + )); + + assert_ok!(Unique::create_item( + origin2, + collection_id, + account(2), + default_nft_data().into() + )); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-allow listed address. +#[test] +fn allow_list_test_15() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission(origin1, collection_id, true)); + + assert_noop!( + Unique::create_item( + origin2, + collection_id, + account(2), + default_nft_data().into() + ) + .map_err(|e| e.error), + CommonError::::AddressNotInAllowlist + ); + }); +} + +// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by non-privileged and allow listed address. +#[test] +fn allow_list_test_16() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + true + )); + assert_ok!(Unique::add_to_allow_list( + origin1, + collection_id, + account(2) + )); + + assert_ok!(Unique::create_item( + origin2, + collection_id, + account(2), + default_nft_data().into() + )); + }); +} + +// Total number of collections. Positive test +#[test] +fn total_number_collections_bound() { + new_test_ext().execute_with(|| { + create_test_collection(&CollectionMode::NFT, CollectionId(1)); + }); +} + +#[test] +fn create_max_collections() { + new_test_ext().execute_with(|| { + for i in 1..COLLECTION_NUMBER_LIMIT { + create_test_collection(&CollectionMode::NFT, CollectionId(i)); + } + }); +} + +// Total number of collections. Negative test +#[test] +fn total_number_collections_bound_neg() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + + for i in 1..=COLLECTION_NUMBER_LIMIT { + create_test_collection(&CollectionMode::NFT, CollectionId(i)); + } + + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + + let data: CreateCollectionData = CreateCollectionData { + name: col_name1.try_into().unwrap(), + description: col_desc1.try_into().unwrap(), + token_prefix: token_prefix1.try_into().unwrap(), + mode: CollectionMode::NFT, + ..Default::default() + }; + + // 11-th collection in chain. Expects error + assert_noop!( + Unique::create_collection_ex(origin1, data), + CommonError::::TotalCollectionsLimitExceeded + ); + }); +} + +// Owned tokens by a single address. Positive test +#[test] +fn owned_tokens_bound() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let data = default_nft_data(); + create_test_item(collection_id, &data.clone().into()); + create_test_item(collection_id, &data.into()); + }); +} + +// Owned tokens by a single address. Negotive test +#[test] +fn owned_tokens_bound_neg() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + for _ in 1..=MAX_TOKEN_OWNERSHIP { + let data = default_nft_data(); + create_test_item(collection_id, &data.clone().into()); + } + + let data = default_nft_data(); + assert_noop!( + Unique::create_item(origin1, CollectionId(1), account(1), data.into()) + .map_err(|e| e.error), + CommonError::::AccountTokenLimitExceeded + ); + }); +} + +// Number of collection admins. Positive test +#[test] +fn collection_admins_bound() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account(2) + )); + assert_ok!(Unique::add_collection_admin( + origin1, + collection_id, + account(3) + )); + }); +} + +// Number of collection admins. Negotive test +#[test] +fn collection_admins_bound_neg() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + for i in 0..COLLECTION_ADMINS_LIMIT { + assert_ok!(Unique::add_collection_admin( + origin1.clone(), + collection_id, + account((2 + i).into()) + )); + } + assert_noop!( + Unique::add_collection_admin( + origin1, + collection_id, + account((3 + COLLECTION_ADMINS_LIMIT).into()) + ), + CommonError::::CollectionAdminCountExceeded + ); + }); +} +// #endregion + +#[test] +fn set_const_on_chain_schema() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::set_const_on_chain_schema( + origin1, + collection_id, + b"test const on chain schema".to_vec().try_into().unwrap() + )); + + assert_eq!( + >::get(( + collection_id, + CollectionField::ConstOnChainSchema + )), + b"test const on chain schema".to_vec() + ); + assert_eq!( + >::get(( + collection_id, + CollectionField::VariableOnChainSchema + )), + b"".to_vec() + ); + }); +} + +#[test] +fn set_variable_on_chain_schema() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + assert_ok!(Unique::set_variable_on_chain_schema( + origin1, + collection_id, + b"test variable on chain schema" + .to_vec() + .try_into() + .unwrap() + )); + + assert_eq!( + >::get(( + collection_id, + CollectionField::ConstOnChainSchema + )), + b"".to_vec() + ); + assert_eq!( + >::get(( + collection_id, + CollectionField::VariableOnChainSchema + )), + b"test variable on chain schema".to_vec() + ); + }); +} + +#[test] +fn set_variable_meta_data_on_nft_token_stores_variable_meta_data() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(CollectionId(1), &data.into()); + + let variable_data = b"test data".to_vec(); + assert_ok!(Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(1), + variable_data.clone().try_into().unwrap() + )); + + assert_eq!( + >::get((collection_id, 1)) + .unwrap() + .variable_data, + variable_data + ); + }); +} + +#[test] +fn set_variable_meta_data_on_re_fungible_token_stores_variable_meta_data() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_re_fungible_data(); + create_test_item(collection_id, &data.into()); + + let variable_data = b"test data".to_vec(); + assert_ok!(Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(1), + variable_data.clone().try_into().unwrap() + )); + + assert_eq!( + >::get((collection_id, TokenId(1))).variable_data, + variable_data + ); + }); +} + +#[test] +fn set_variable_meta_data_on_fungible_token_fails() { + new_test_ext().execute_with(|| { + let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_fungible_data(); + create_test_item(collection_id, &data.into()); + + let variable_data = b"test data".to_vec(); + assert_noop!( + Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(0), + variable_data.try_into().unwrap() + ) + .map_err(|e| e.error), + >::FungibleItemsDontHaveData + ); + }); +} + +#[test] +fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() { + new_test_ext().execute_with(|| { + //default_limits(); + + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_meta_update_permission_flag( + origin1.clone(), + collection_id, + MetaUpdatePermission::ItemOwner, + )); + + let variable_data = b"ten chars.".to_vec(); + assert_ok!(Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(1), + variable_data.clone().try_into().unwrap() + )); + + assert_eq!( + >::get((collection_id, TokenId(1))) + .unwrap() + .variable_data, + variable_data + ); + }); +} + +#[test] +fn collection_transfer_flag_works() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + assert_ok!(Unique::set_transfers_enabled_flag( + origin1, + collection_id, + true + )); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + let origin1 = Origin::signed(1); + + // default scenario + assert_ok!(Unique::transfer( + origin1, + account(2), + collection_id, + TokenId(1), + 1 + )); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + false + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + true + ); + assert_eq!( + >::get((collection_id, account(1))), + 0 + ); + assert_eq!( + >::get((collection_id, account(2))), + 1 + ); + }); +} + +#[test] +fn set_variable_meta_data_on_nft_with_admin_flag() { + new_test_ext().execute_with(|| { + // default_limits(); + + let collection_id = + create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_mint_permission( + origin2.clone(), + collection_id, + true + )); + assert_ok!(Unique::add_to_allow_list( + origin2.clone(), + collection_id, + account(1) + )); + + assert_ok!(Unique::add_collection_admin( + origin2.clone(), + collection_id, + account(1) + )); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_meta_update_permission_flag( + origin2.clone(), + collection_id, + MetaUpdatePermission::Admin, + )); + + let variable_data = b"test.".to_vec(); + assert_ok!(Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(1), + variable_data.clone().try_into().unwrap() + )); + + assert_eq!( + >::get((collection_id, 1)) + .unwrap() + .variable_data, + variable_data + ); + }); +} + +#[test] +fn set_variable_meta_data_on_nft_with_admin_flag_neg() { + new_test_ext().execute_with(|| { + // default_limits(); + + let collection_id = + create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); + + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_mint_permission( + origin2.clone(), + collection_id, + true + )); + assert_ok!(Unique::add_to_allow_list( + origin2.clone(), + collection_id, + account(1) + )); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_meta_update_permission_flag( + origin2.clone(), + collection_id, + MetaUpdatePermission::Admin, + )); + + let variable_data = b"test.".to_vec(); + assert_noop!( + Unique::set_variable_meta_data( + origin1, + collection_id, + TokenId(1), + variable_data.try_into().unwrap() + ) + .map_err(|e| e.error), + CommonError::::NoPermission + ); + }); +} + +#[test] +fn set_variable_meta_flag_after_freeze() { + new_test_ext().execute_with(|| { + // default_limits(); + + let collection_id = + create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1)); + + let origin2 = Origin::signed(2); + + assert_ok!(Unique::set_meta_update_permission_flag( + origin2.clone(), + collection_id, + MetaUpdatePermission::None, + )); + assert_noop!( + Unique::set_meta_update_permission_flag( + origin2.clone(), + collection_id, + MetaUpdatePermission::Admin + ), + CommonError::::MetadataFlagFrozen + ); + }); +} + +#[test] +fn set_variable_meta_data_on_nft_with_none_flag_neg() { + new_test_ext().execute_with(|| { + // default_limits(); + + let collection_id = + create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1)); + let origin1 = Origin::signed(1); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + + assert_ok!(Unique::set_meta_update_permission_flag( + origin1.clone(), + collection_id, + MetaUpdatePermission::None, + )); + + let variable_data = b"test.".to_vec(); + assert_noop!( + Unique::set_variable_meta_data( + origin1.clone(), + collection_id, + TokenId(1), + variable_data.try_into().unwrap() + ) + .map_err(|e| e.error), + CommonError::::NoPermission + ); + }); +} + +#[test] +fn collection_transfer_flag_works_neg() { + new_test_ext().execute_with(|| { + let origin1 = Origin::signed(1); + + let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1)); + assert_ok!(Unique::set_transfers_enabled_flag( + origin1, + collection_id, + false + )); + + let data = default_nft_data(); + create_test_item(collection_id, &data.into()); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + + let origin1 = Origin::signed(1); + + // default scenario + assert_noop!( + Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1) + .map_err(|e| e.error), + CommonError::::TransferNotAllowed + ); + assert_eq!( + >::get((collection_id, account(1))), + 1 + ); + assert_eq!( + >::get((collection_id, account(2))), + 0 + ); + assert_eq!( + >::get((collection_id, account(1), TokenId(1))), + true + ); + assert_eq!( + >::get((collection_id, account(2), TokenId(1))), + false + ); + }); +} + +#[test] +fn collection_sponsoring() { + new_test_ext().execute_with(|| { + // default_limits(); + let user1 = 1_u64; + let user2 = 777_u64; + let origin1 = Origin::signed(user1); + let origin2 = Origin::signed(user2); + let account2 = account(user2); + + let collection_id = + create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1)); + assert_ok!(Unique::set_collection_sponsor( + origin1.clone(), + collection_id, + user1 + )); + assert_ok!(Unique::confirm_sponsorship(origin1.clone(), collection_id)); + + // Expect error while have no permissions + assert!(Unique::create_item( + origin2.clone(), + collection_id, + account2.clone(), + default_nft_data().into() + ) + .is_err()); + + assert_ok!(Unique::set_public_access_mode( + origin1.clone(), + collection_id, + AccessMode::AllowList + )); + assert_ok!(Unique::add_to_allow_list( + origin1.clone(), + collection_id, + account2.clone() + )); + assert_ok!(Unique::set_mint_permission( + origin1.clone(), + collection_id, + true + )); + + assert_ok!(Unique::create_item( + origin2, + collection_id, + account2, + default_nft_data().into() + )); + }); +} -- gitstuff