difftreelog
test fix unit
in: master
19 files changed
Cargo.lockdiffbeforeafterboth--- 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]]
Cargo.tomldiffbeforeafterboth--- 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'
pallets/unique/Cargo.tomldiffbeforeafterboth--- 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" }
pallets/unique/src/common.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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)* )) => {
- <FungibleWeights<T>>::$method($($args)*)
- .max(<NonfungibleWeights<T>>::$method($($args)*))
- .max(<RefungibleWeights<T>>::$method($($args)*))
- };
-}
-
-pub struct CommonWeights<T: Config>(PhantomData<T>);
-impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
- fn create_item() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_item())
- }
-
- fn create_multiple_items(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_multiple_items(amount))
- }
-
- fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
- }
-
- fn burn_item() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(burn_item())
- }
-
- fn transfer() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(transfer())
- }
-
- fn approve() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(approve())
- }
-
- fn transfer_from() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(transfer_from())
- }
-
- fn set_variable_metadata(bytes: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(set_variable_metadata(bytes))
- }
-
- fn burn_from() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(burn_from())
- }
-}
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-pub mod sponsoring;
pallets/unique/src/eth/sponsoring.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-//! 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<T: Config>(PhantomData<*const T>);
-impl<T: Config> SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)>
- for UniqueEthSponsorshipHandler<T>
-{
- fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
- let collection_id = map_eth_to_id(&call.0)?;
- let collection = <CollectionHandle<T>>::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 = <UniqueNFTCall<T>>::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::<T>(&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::<T>(
- &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::<T>(&collection, &from, &token_id).map(|()| sponsor)
- }
- UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
- let token_id: TokenId = token_id.try_into().ok()?;
- withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
- .map(|()| sponsor)
- }
- _ => None,
- }
- }
- crate::CollectionMode::Fungible(_) => {
- let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
- #[allow(clippy::single_match)]
- match call {
- UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
- withdraw_transfer::<T>(&collection, who, &TokenId::default())
- .map(|()| sponsor)
- }
- UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
- let from = T::CrossAccountId::from_eth(from);
- withdraw_transfer::<T>(&collection, &from, &TokenId::default())
- .map(|()| sponsor)
- }
- UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
- withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
- .map(|()| sponsor)
- }
- _ => None,
- }
- }
- _ => None,
- }?))
- }
-}
pallets/unique/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829use frame_support::{30 decl_module, decl_storage, decl_error, decl_event,31 dispatch::DispatchResult,32 ensure,33 weights::{Weight},34 transactional,35 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},36 BoundedVec,37};38use scale_info::TypeInfo;39use frame_system::{self as system, ensure_signed};40use sp_runtime::{sp_std::prelude::Vec};41use up_data_structs::{42 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,43 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,44 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,45 SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,46 CreateItemExData, budget, CollectionField,47};48use pallet_evm::account::CrossAccountId;49use pallet_common::{50 CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,51 dispatch::dispatch_call, dispatch::CollectionDispatch,52};5354#[cfg(test)]55mod mock;5657#[cfg(test)]58mod tests;5960mod eth;61mod sponsorship;62pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};63pub use eth::sponsoring::UniqueEthSponsorshipHandler;6465pub mod common;66use common::CommonWeights;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273pub trait SponsorshipPredict<T: Config> {74 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>75 where76 u64: From<<T as frame_system::Config>::BlockNumber>;77}7879decl_error! {80 /// Error for non-fungible-token module.81 pub enum Error for Module<T: Config> {82 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.83 CollectionDecimalPointLimitExceeded,84 /// This address is not set as sponsor, use setCollectionSponsor first.85 ConfirmUnsetSponsorFail,86 /// Length of items properties must be greater than 0.87 EmptyArgument,88 }89}9091pub trait Config:92 system::Config93 + pallet_evm_coder_substrate::Config94 + pallet_common::Config95 + pallet_nonfungible::Config96 + pallet_refungible::Config97 + pallet_fungible::Config98 + Sized99 + TypeInfo100{101 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;102103 /// Weight information for extrinsics in this pallet.104 type WeightInfo: WeightInfo;105}106107decl_event! {108 pub enum Event<T>109 where110 <T as frame_system::Config>::AccountId,111 <T as pallet_evm::account::Config>::CrossAccountId,112 {113 /// Collection sponsor was removed114 ///115 /// # Arguments116 ///117 /// * collection_id: Globally unique collection identifier.118 CollectionSponsorRemoved(CollectionId),119120 /// Collection admin was added121 ///122 /// # Arguments123 ///124 /// * collection_id: Globally unique collection identifier.125 ///126 /// * admin: Admin address.127 CollectionAdminAdded(CollectionId, CrossAccountId),128129 /// Collection owned was change130 ///131 /// # Arguments132 ///133 /// * collection_id: Globally unique collection identifier.134 ///135 /// * owner: New owner address.136 CollectionOwnedChanged(CollectionId, AccountId),137138 /// Collection sponsor was set139 ///140 /// # Arguments141 ///142 /// * collection_id: Globally unique collection identifier.143 ///144 /// * owner: New sponsor address.145 CollectionSponsorSet(CollectionId, AccountId),146147 /// const on chain schema was set148 ///149 /// # Arguments150 ///151 /// * collection_id: Globally unique collection identifier.152 ConstOnChainSchemaSet(CollectionId),153154 /// New sponsor was confirm155 ///156 /// # Arguments157 ///158 /// * collection_id: Globally unique collection identifier.159 ///160 /// * sponsor: New sponsor address.161 SponsorshipConfirmed(CollectionId, AccountId),162163 /// Collection admin was removed164 ///165 /// # Arguments166 ///167 /// * collection_id: Globally unique collection identifier.168 ///169 /// * admin: Admin address.170 CollectionAdminRemoved(CollectionId, CrossAccountId),171172 /// Address was remove from allow list173 ///174 /// # Arguments175 ///176 /// * collection_id: Globally unique collection identifier.177 ///178 /// * user: Address.179 AllowListAddressRemoved(CollectionId, CrossAccountId),180181 /// Address was add to allow list182 ///183 /// # Arguments184 ///185 /// * collection_id: Globally unique collection identifier.186 ///187 /// * user: Address.188 AllowListAddressAdded(CollectionId, CrossAccountId),189190 /// Collection limits was set191 ///192 /// # Arguments193 ///194 /// * collection_id: Globally unique collection identifier.195 CollectionLimitSet(CollectionId),196197 /// Mint permission was set198 ///199 /// # Arguments200 ///201 /// * collection_id: Globally unique collection identifier.202 MintPermissionSet(CollectionId),203204 /// Offchain schema was set205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique collection identifier.209 OffchainSchemaSet(CollectionId),210211 /// Public access mode was set212 ///213 /// # Arguments214 ///215 /// * collection_id: Globally unique collection identifier.216 ///217 /// * mode: New access state.218 PublicAccessModeSet(CollectionId, AccessMode),219220 /// Schema version was set221 ///222 /// # Arguments223 ///224 /// * collection_id: Globally unique collection identifier.225 SchemaVersionSet(CollectionId),226227 /// Variable on chain schema was set228 ///229 /// # Arguments230 ///231 /// * collection_id: Globally unique collection identifier.232 VariableOnChainSchemaSet(CollectionId),233 }234}235236type SelfWeightOf<T> = <T as Config>::WeightInfo;237238// # Used definitions239//240// ## User control levels241//242// chain-controlled - key is uncontrolled by user243// i.e autoincrementing index244// can use non-cryptographic hash245// real - key is controlled by user246// but it is hard to generate enough colliding values, i.e owner of signed txs247// can use non-cryptographic hash248// controlled - key is completly controlled by users249// i.e maps with mutable keys250// should use cryptographic hash251//252// ## User control level downgrade reasons253//254// ?1 - chain-controlled -> controlled255// collections/tokens can be destroyed, resulting in massive holes256// ?2 - chain-controlled -> controlled257// same as ?1, but can be only added, resulting in easier exploitation258// ?3 - real -> controlled259// no confirmation required, so addresses can be easily generated260decl_storage! {261 trait Store for Module<T: Config> as Unique {262263 //#region Private members264 /// Used for migrations265 ChainVersion: u64;266 //#endregion267268 //#region Tokens transfer rate limit baskets269 /// (Collection id (controlled?2), who created (real))270 /// TODO: Off chain worker should remove from this map when collection gets removed271 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;272 /// Collection id (controlled?2), token id (controlled?2)273 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;274 /// Collection id (controlled?2), owning user (real)275 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;276 /// Collection id (controlled?2), token id (controlled?2)277 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;278 //#endregion279280 /// Variable metadata sponsoring281 /// Collection id (controlled?2), token id (controlled?2)282 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;283 /// Approval sponsoring284 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;285 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;287 }288}289290decl_module! {291 pub struct Module<T: Config> for enum Call292 where293 origin: T::Origin294 {295 type Error = Error<T>;296297 fn deposit_event() = default;298299 fn on_initialize(_now: T::BlockNumber) -> Weight {300 0301 }302303 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.304 ///305 /// # Permissions306 ///307 /// * Anyone.308 ///309 /// # Arguments310 ///311 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.312 ///313 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.314 ///315 /// * token_prefix: UTF-8 string with token prefix.316 ///317 /// * mode: [CollectionMode] collection type and type dependent data.318 // returns collection ID319 #[weight = <SelfWeightOf<T>>::create_collection()]320 #[transactional]321 #[deprecated]322 pub fn create_collection(origin,323 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,324 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,325 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,326 mode: CollectionMode) -> DispatchResult {327 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {328 name: collection_name,329 description: collection_description,330 token_prefix,331 mode,332 ..Default::default()333 };334 Self::create_collection_ex(origin, data)335 }336337 /// This method creates a collection338 ///339 /// Prefer it to deprecated [`created_collection`] method340 #[weight = <SelfWeightOf<T>>::create_collection()]341 #[transactional]342 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {343 let sender = ensure_signed(origin)?;344345 // =========346347 T::CollectionDispatch::create(sender, data)?;348349 Ok(())350 }351352 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.353 ///354 /// # Permissions355 ///356 /// * Collection Owner.357 ///358 /// # Arguments359 ///360 /// * collection_id: collection to destroy.361 #[weight = <SelfWeightOf<T>>::destroy_collection()]362 #[transactional]363 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365 let collection = <CollectionHandle<T>>::try_get(collection_id)?;366367 // =========368369 T::CollectionDispatch::destroy(sender, collection)?;370371 <NftTransferBasket<T>>::remove_prefix(collection_id, None);372 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);373 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);374375 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);376 <NftApproveBasket<T>>::remove_prefix(collection_id, None);377 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);378 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);379380 Ok(())381 }382383 /// Add an address to allow list.384 ///385 /// # Permissions386 ///387 /// * Collection Owner388 /// * Collection Admin389 ///390 /// # Arguments391 ///392 /// * collection_id.393 ///394 /// * address.395 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]396 #[transactional]397 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{398399 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);400 let collection = <CollectionHandle<T>>::try_get(collection_id)?;401402 <PalletCommon<T>>::toggle_allowlist(403 &collection,404 &sender,405 &address,406 true,407 )?;408409 Self::deposit_event(Event::<T>::AllowListAddressAdded(410 collection_id,411 address412 ));413414 Ok(())415 }416417 /// Remove an address from allow list.418 ///419 /// # Permissions420 ///421 /// * Collection Owner422 /// * Collection Admin423 ///424 /// # Arguments425 ///426 /// * collection_id.427 ///428 /// * address.429 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]430 #[transactional]431 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{432433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);434 let collection = <CollectionHandle<T>>::try_get(collection_id)?;435436 <PalletCommon<T>>::toggle_allowlist(437 &collection,438 &sender,439 &address,440 false,441 )?;442443 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(444 collection_id,445 address446 ));447448 Ok(())449 }450451 /// Toggle between normal and allow list access for the methods with access for `Anyone`.452 ///453 /// # Permissions454 ///455 /// * Collection Owner.456 ///457 /// # Arguments458 ///459 /// * collection_id.460 ///461 /// * mode: [AccessMode]462 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]463 #[transactional]464 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult465 {466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);467468 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;469 target_collection.check_is_owner(&sender)?;470471 target_collection.access = mode.clone();472473 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(474 collection_id,475 mode476 ));477478 target_collection.save()479 }480481 /// Allows Anyone to create tokens if:482 /// * Allow List is enabled, and483 /// * Address is added to allow list, and484 /// * This method was called with True parameter485 ///486 /// # Permissions487 /// * Collection Owner488 ///489 /// # Arguments490 ///491 /// * collection_id.492 ///493 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.494 #[weight = <SelfWeightOf<T>>::set_mint_permission()]495 #[transactional]496 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult497 {498 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);499500 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;501 target_collection.check_is_owner(&sender)?;502503 target_collection.mint_mode = mint_permission;504505 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(506 collection_id507 ));508509 target_collection.save()510 }511512 /// Change the owner of the collection.513 ///514 /// # Permissions515 ///516 /// * Collection Owner.517 ///518 /// # Arguments519 ///520 /// * collection_id.521 ///522 /// * new_owner.523 #[weight = <SelfWeightOf<T>>::change_collection_owner()]524 #[transactional]525 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {526527 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);528529 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;530 target_collection.check_is_owner(&sender)?;531532 target_collection.owner = new_owner.clone();533 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(534 collection_id,535 new_owner536 ));537538 target_collection.save()539 }540541 /// Adds an admin of the Collection.542 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.543 ///544 /// # Permissions545 ///546 /// * Collection Owner.547 /// * Collection Admin.548 ///549 /// # Arguments550 ///551 /// * collection_id: ID of the Collection to add admin for.552 ///553 /// * new_admin_id: Address of new admin to add.554 #[weight = <SelfWeightOf<T>>::add_collection_admin()]555 #[transactional]556 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {557 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);558 let collection = <CollectionHandle<T>>::try_get(collection_id)?;559560 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(561 collection_id,562 new_admin_id.clone()563 ));564565 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)566 }567568 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.569 ///570 /// # Permissions571 ///572 /// * Collection Owner.573 /// * Collection Admin.574 ///575 /// # Arguments576 ///577 /// * collection_id: ID of the Collection to remove admin for.578 ///579 /// * account_id: Address of admin to remove.580 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]581 #[transactional]582 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {583 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);584 let collection = <CollectionHandle<T>>::try_get(collection_id)?;585586 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(587 collection_id,588 account_id.clone()589 ));590591 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)592 }593594 /// # Permissions595 ///596 /// * Collection Owner597 ///598 /// # Arguments599 ///600 /// * collection_id.601 ///602 /// * new_sponsor.603 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]604 #[transactional]605 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {606 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607608 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;609 target_collection.check_is_owner(&sender)?;610611 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());612613 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(614 collection_id,615 new_sponsor616 ));617618 target_collection.save()619 }620621 /// # Permissions622 ///623 /// * Sponsor.624 ///625 /// # Arguments626 ///627 /// * collection_id.628 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]629 #[transactional]630 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {631 let sender = ensure_signed(origin)?;632633 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;634 ensure!(635 target_collection.sponsorship.pending_sponsor() == Some(&sender),636 Error::<T>::ConfirmUnsetSponsorFail637 );638639 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());640641 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(642 collection_id,643 sender644 ));645646 target_collection.save()647 }648649 /// Switch back to pay-per-own-transaction model.650 ///651 /// # Permissions652 ///653 /// * Collection owner.654 ///655 /// # Arguments656 ///657 /// * collection_id.658 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]659 #[transactional]660 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {661 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;664 target_collection.check_is_owner(&sender)?;665666 target_collection.sponsorship = SponsorshipState::Disabled;667668 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(669 collection_id670 ));671 target_collection.save()672 }673674 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.675 ///676 /// # Permissions677 ///678 /// * Collection Owner.679 /// * Collection Admin.680 /// * Anyone if681 /// * Allow List is enabled, and682 /// * Address is added to allow list, and683 /// * MintPermission is enabled (see SetMintPermission method)684 ///685 /// # Arguments686 ///687 /// * collection_id: ID of the collection.688 ///689 /// * owner: Address, initial owner of the NFT.690 ///691 /// * data: Token data to store on chain.692 #[weight = <CommonWeights<T>>::create_item()]693 #[transactional]694 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {695 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);696 let budget = budget::Value::new(2);697698 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))699 }700701 /// This method creates multiple items in a collection created with CreateCollection method.702 ///703 /// # Permissions704 ///705 /// * Collection Owner.706 /// * Collection Admin.707 /// * Anyone if708 /// * Allow List is enabled, and709 /// * Address is added to allow list, and710 /// * MintPermission is enabled (see SetMintPermission method)711 ///712 /// # Arguments713 ///714 /// * collection_id: ID of the collection.715 ///716 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].717 ///718 /// * owner: Address, initial owner of the NFT.719 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]720 #[transactional]721 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {722 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);723 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724 let budget = budget::Value::new(2);725726 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))727 }728729 #[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]730 #[transactional]731 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {732 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);733 let budget = budget::Value::new(2);734735 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))736 }737738 // TODO! transaction weight739740 /// Set transfers_enabled value for particular collection741 ///742 /// # Permissions743 ///744 /// * Collection Owner.745 ///746 /// # Arguments747 ///748 /// * collection_id: ID of the collection.749 ///750 /// * value: New flag value.751 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]752 #[transactional]753 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {754 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756 target_collection.check_is_owner(&sender)?;757758 // =========759760 target_collection.limits.transfers_enabled = Some(value);761 target_collection.save()762 }763764 /// Destroys a concrete instance of NFT.765 ///766 /// # Permissions767 ///768 /// * Collection Owner.769 /// * Collection Admin.770 /// * Current NFT Owner.771 ///772 /// # Arguments773 ///774 /// * collection_id: ID of the collection.775 ///776 /// * item_id: ID of NFT to burn.777 #[weight = <CommonWeights<T>>::burn_item()]778 #[transactional]779 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781782 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;783 if value == 1 {784 <NftTransferBasket<T>>::remove(collection_id, item_id);785 <NftApproveBasket<T>>::remove(collection_id, item_id);786 }787 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?788 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());789 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));790 Ok(post_info)791 }792793 /// Destroys a concrete instance of NFT on behalf of the owner794 /// See also: [`approve`]795 ///796 /// # Permissions797 ///798 /// * Collection Owner.799 /// * Collection Admin.800 /// * Current NFT Owner.801 ///802 /// # Arguments803 ///804 /// * collection_id: ID of the collection.805 ///806 /// * item_id: ID of NFT to burn.807 ///808 /// * from: owner of item809 #[weight = <CommonWeights<T>>::burn_from()]810 #[transactional]811 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813 let budget = budget::Value::new(2);814815 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))816 }817818 /// Change ownership of the token.819 ///820 /// # Permissions821 ///822 /// * Collection Owner823 /// * Collection Admin824 /// * Current NFT owner825 ///826 /// # Arguments827 ///828 /// * recipient: Address of token recipient.829 ///830 /// * collection_id.831 ///832 /// * item_id: ID of the item833 /// * Non-Fungible Mode: Required.834 /// * Fungible Mode: Ignored.835 /// * Re-Fungible Mode: Required.836 ///837 /// * value: Amount to transfer.838 /// * Non-Fungible Mode: Ignored839 /// * Fungible Mode: Must specify transferred amount840 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)841 #[weight = <CommonWeights<T>>::transfer()]842 #[transactional]843 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {844 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);845 let budget = budget::Value::new(2);846847 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))848 }849850 /// Set, change, or remove approved address to transfer the ownership of the NFT.851 ///852 /// # Permissions853 ///854 /// * Collection Owner855 /// * Collection Admin856 /// * Current NFT owner857 ///858 /// # Arguments859 ///860 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).861 ///862 /// * collection_id.863 ///864 /// * item_id: ID of the item.865 #[weight = <CommonWeights<T>>::approve()]866 #[transactional]867 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {868 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869870 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))871 }872873 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.874 ///875 /// # Permissions876 /// * Collection Owner877 /// * Collection Admin878 /// * Current NFT owner879 /// * Address approved by current NFT owner880 ///881 /// # Arguments882 ///883 /// * from: Address that owns token.884 ///885 /// * recipient: Address of token recipient.886 ///887 /// * collection_id.888 ///889 /// * item_id: ID of the item.890 ///891 /// * value: Amount to transfer.892 #[weight = <CommonWeights<T>>::transfer_from()]893 #[transactional]894 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {895 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896 let budget = budget::Value::new(2);897898 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))899 }900901 /// Set off-chain data schema.902 ///903 /// # Permissions904 ///905 /// * Collection Owner906 /// * Collection Admin907 ///908 /// # Arguments909 ///910 /// * collection_id.911 ///912 /// * schema: String representing the offchain data schema.913 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]914 #[transactional]915 pub fn set_variable_meta_data (916 origin,917 collection_id: CollectionId,918 item_id: TokenId,919 data: BoundedVec<u8, CustomDataLimit>,920 ) -> DispatchResultWithPostInfo {921 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);922923 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))924 }925926 /// Set meta_update_permission value for particular collection927 ///928 /// # Permissions929 ///930 /// * Collection Owner.931 ///932 /// # Arguments933 ///934 /// * collection_id: ID of the collection.935 ///936 /// * value: New flag value.937 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]938 #[transactional]939 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {940 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;942943 ensure!(944 target_collection.meta_update_permission != MetaUpdatePermission::None,945 <CommonError<T>>::MetadataFlagFrozen,946 );947 target_collection.check_is_owner(&sender)?;948949 target_collection.meta_update_permission = value;950951 target_collection.save()952 }953954 /// Set schema standard955 /// ImageURL956 /// Unique957 ///958 /// # Permissions959 ///960 /// * Collection Owner961 /// * Collection Admin962 ///963 /// # Arguments964 ///965 /// * collection_id.966 ///967 /// * schema: SchemaVersion: enum968 #[weight = <SelfWeightOf<T>>::set_schema_version()]969 #[transactional]970 pub fn set_schema_version(971 origin,972 collection_id: CollectionId,973 version: SchemaVersion974 ) -> DispatchResult {975 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);976 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;977 target_collection.check_is_owner_or_admin(&sender)?;978 target_collection.schema_version = version;979980 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(981 collection_id982 ));983984 target_collection.save()985 }986987 /// Set off-chain data schema.988 ///989 /// # Permissions990 ///991 /// * Collection Owner992 /// * Collection Admin993 ///994 /// # Arguments995 ///996 /// * collection_id.997 ///998 /// * schema: String representing the offchain data schema.999 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1000 #[transactional]1001 pub fn set_offchain_schema(1002 origin,1003 collection_id: CollectionId,1004 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1005 ) -> DispatchResult {1006 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10081009 // =========10101011 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;10121013 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1014 collection_id1015 ));1016 Ok(())1017 }10181019 /// Set const on-chain data schema.1020 ///1021 /// # Permissions1022 ///1023 /// * Collection Owner1024 /// * Collection Admin1025 ///1026 /// # Arguments1027 ///1028 /// * collection_id.1029 ///1030 /// * schema: String representing the const on-chain data schema.1031 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1032 #[transactional]1033 pub fn set_const_on_chain_schema (1034 origin,1035 collection_id: CollectionId,1036 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1037 ) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10401041 // =========10421043 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10441045 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1046 collection_id1047 ));1048 Ok(())1049 }10501051 /// Set variable on-chain data schema.1052 ///1053 /// # Permissions1054 ///1055 /// * Collection Owner1056 /// * Collection Admin1057 ///1058 /// # Arguments1059 ///1060 /// * collection_id.1061 ///1062 /// * schema: String representing the variable on-chain data schema.1063 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1064 #[transactional]1065 pub fn set_variable_on_chain_schema (1066 origin,1067 collection_id: CollectionId,1068 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1069 ) -> DispatchResult {1070 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1071 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10721073 // =========10741075 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;10761077 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1078 collection_id1079 ));1080 Ok(())1081 }10821083 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1084 #[transactional]1085 pub fn set_collection_limits(1086 origin,1087 collection_id: CollectionId,1088 new_limit: CollectionLimits,1089 ) -> DispatchResult {1090 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1091 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1092 target_collection.check_is_owner(&sender)?;1093 let old_limit = &target_collection.limits;10941095 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10961097 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1098 collection_id1099 ));11001101 target_collection.save()1102 }1103 }1104}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425use frame_support::{26 decl_module, decl_storage, decl_error, decl_event,27 dispatch::DispatchResult,28 ensure,29 weights::{Weight},30 transactional,31 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32 BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,39 MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,40 AccessMode, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId,41 SchemaVersion, SponsorshipState, MetaUpdatePermission, CreateCollectionData, CustomDataLimit,42 CreateItemExData, budget, CollectionField,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, Error as CommonError, CommonWeightInfo,47 dispatch::dispatch_call, dispatch::CollectionDispatch,48};4950#[cfg(feature = "runtime-benchmarks")]51mod benchmarking;52pub mod weights;53use weights::WeightInfo;5455pub trait SponsorshipPredict<T: Config> {56 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>57 where58 u64: From<<T as frame_system::Config>::BlockNumber>;59}6061decl_error! {62 /// Error for non-fungible-token module.63 pub enum Error for Module<T: Config> {64 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.65 CollectionDecimalPointLimitExceeded,66 /// This address is not set as sponsor, use setCollectionSponsor first.67 ConfirmUnsetSponsorFail,68 /// Length of items properties must be greater than 0.69 EmptyArgument,70 }71}7273pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {74 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7576 /// Weight information for extrinsics in this pallet.77 type WeightInfo: WeightInfo;78 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;79}8081decl_event! {82 pub enum Event<T>83 where84 <T as frame_system::Config>::AccountId,85 <T as pallet_evm::account::Config>::CrossAccountId,86 {87 /// Collection sponsor was removed88 ///89 /// # Arguments90 ///91 /// * collection_id: Globally unique collection identifier.92 CollectionSponsorRemoved(CollectionId),9394 /// Collection admin was added95 ///96 /// # Arguments97 ///98 /// * collection_id: Globally unique collection identifier.99 ///100 /// * admin: Admin address.101 CollectionAdminAdded(CollectionId, CrossAccountId),102103 /// Collection owned was change104 ///105 /// # Arguments106 ///107 /// * collection_id: Globally unique collection identifier.108 ///109 /// * owner: New owner address.110 CollectionOwnedChanged(CollectionId, AccountId),111112 /// Collection sponsor was set113 ///114 /// # Arguments115 ///116 /// * collection_id: Globally unique collection identifier.117 ///118 /// * owner: New sponsor address.119 CollectionSponsorSet(CollectionId, AccountId),120121 /// const on chain schema was set122 ///123 /// # Arguments124 ///125 /// * collection_id: Globally unique collection identifier.126 ConstOnChainSchemaSet(CollectionId),127128 /// New sponsor was confirm129 ///130 /// # Arguments131 ///132 /// * collection_id: Globally unique collection identifier.133 ///134 /// * sponsor: New sponsor address.135 SponsorshipConfirmed(CollectionId, AccountId),136137 /// Collection admin was removed138 ///139 /// # Arguments140 ///141 /// * collection_id: Globally unique collection identifier.142 ///143 /// * admin: Admin address.144 CollectionAdminRemoved(CollectionId, CrossAccountId),145146 /// Address was remove from allow list147 ///148 /// # Arguments149 ///150 /// * collection_id: Globally unique collection identifier.151 ///152 /// * user: Address.153 AllowListAddressRemoved(CollectionId, CrossAccountId),154155 /// Address was add to allow list156 ///157 /// # Arguments158 ///159 /// * collection_id: Globally unique collection identifier.160 ///161 /// * user: Address.162 AllowListAddressAdded(CollectionId, CrossAccountId),163164 /// Collection limits was set165 ///166 /// # Arguments167 ///168 /// * collection_id: Globally unique collection identifier.169 CollectionLimitSet(CollectionId),170171 /// Mint permission was set172 ///173 /// # Arguments174 ///175 /// * collection_id: Globally unique collection identifier.176 MintPermissionSet(CollectionId),177178 /// Offchain schema was set179 ///180 /// # Arguments181 ///182 /// * collection_id: Globally unique collection identifier.183 OffchainSchemaSet(CollectionId),184185 /// Public access mode was set186 ///187 /// # Arguments188 ///189 /// * collection_id: Globally unique collection identifier.190 ///191 /// * mode: New access state.192 PublicAccessModeSet(CollectionId, AccessMode),193194 /// Schema version was set195 ///196 /// # Arguments197 ///198 /// * collection_id: Globally unique collection identifier.199 SchemaVersionSet(CollectionId),200201 /// Variable on chain schema was set202 ///203 /// # Arguments204 ///205 /// * collection_id: Globally unique collection identifier.206 VariableOnChainSchemaSet(CollectionId),207 }208}209210type SelfWeightOf<T> = <T as Config>::WeightInfo;211212// # Used definitions213//214// ## User control levels215//216// chain-controlled - key is uncontrolled by user217// i.e autoincrementing index218// can use non-cryptographic hash219// real - key is controlled by user220// but it is hard to generate enough colliding values, i.e owner of signed txs221// can use non-cryptographic hash222// controlled - key is completly controlled by users223// i.e maps with mutable keys224// should use cryptographic hash225//226// ## User control level downgrade reasons227//228// ?1 - chain-controlled -> controlled229// collections/tokens can be destroyed, resulting in massive holes230// ?2 - chain-controlled -> controlled231// same as ?1, but can be only added, resulting in easier exploitation232// ?3 - real -> controlled233// no confirmation required, so addresses can be easily generated234decl_storage! {235 trait Store for Module<T: Config> as Unique {236237 //#region Private members238 /// Used for migrations239 ChainVersion: u64;240 //#endregion241242 //#region Tokens transfer rate limit baskets243 /// (Collection id (controlled?2), who created (real))244 /// TODO: Off chain worker should remove from this map when collection gets removed245 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;246 /// Collection id (controlled?2), token id (controlled?2)247 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248 /// Collection id (controlled?2), owning user (real)249 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;250 /// Collection id (controlled?2), token id (controlled?2)251 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252 //#endregion253254 /// Variable metadata sponsoring255 /// Collection id (controlled?2), token id (controlled?2)256 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;257 /// Approval sponsoring258 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;259 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;260 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;261 }262}263264decl_module! {265 pub struct Module<T: Config> for enum Call266 where267 origin: T::Origin268 {269 type Error = Error<T>;270271 fn deposit_event() = default;272273 fn on_initialize(_now: T::BlockNumber) -> Weight {274 0275 }276277 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.278 ///279 /// # Permissions280 ///281 /// * Anyone.282 ///283 /// # Arguments284 ///285 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.286 ///287 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.288 ///289 /// * token_prefix: UTF-8 string with token prefix.290 ///291 /// * mode: [CollectionMode] collection type and type dependent data.292 // returns collection ID293 #[weight = <SelfWeightOf<T>>::create_collection()]294 #[transactional]295 #[deprecated]296 pub fn create_collection(origin,297 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,298 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,299 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,300 mode: CollectionMode) -> DispatchResult {301 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {302 name: collection_name,303 description: collection_description,304 token_prefix,305 mode,306 ..Default::default()307 };308 Self::create_collection_ex(origin, data)309 }310311 /// This method creates a collection312 ///313 /// Prefer it to deprecated [`created_collection`] method314 #[weight = <SelfWeightOf<T>>::create_collection()]315 #[transactional]316 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {317 let sender = ensure_signed(origin)?;318319 // =========320321 T::CollectionDispatch::create(sender, data)?;322323 Ok(())324 }325326 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.327 ///328 /// # Permissions329 ///330 /// * Collection Owner.331 ///332 /// # Arguments333 ///334 /// * collection_id: collection to destroy.335 #[weight = <SelfWeightOf<T>>::destroy_collection()]336 #[transactional]337 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {338 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339 let collection = <CollectionHandle<T>>::try_get(collection_id)?;340341 // =========342343 T::CollectionDispatch::destroy(sender, collection)?;344345 <NftTransferBasket<T>>::remove_prefix(collection_id, None);346 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);347 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);348349 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);350 <NftApproveBasket<T>>::remove_prefix(collection_id, None);351 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);352 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);353354 Ok(())355 }356357 /// Add an address to allow list.358 ///359 /// # Permissions360 ///361 /// * Collection Owner362 /// * Collection Admin363 ///364 /// # Arguments365 ///366 /// * collection_id.367 ///368 /// * address.369 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]370 #[transactional]371 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{372373 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);374 let collection = <CollectionHandle<T>>::try_get(collection_id)?;375376 <PalletCommon<T>>::toggle_allowlist(377 &collection,378 &sender,379 &address,380 true,381 )?;382383 Self::deposit_event(Event::<T>::AllowListAddressAdded(384 collection_id,385 address386 ));387388 Ok(())389 }390391 /// Remove an address from allow list.392 ///393 /// # Permissions394 ///395 /// * Collection Owner396 /// * Collection Admin397 ///398 /// # Arguments399 ///400 /// * collection_id.401 ///402 /// * address.403 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]404 #[transactional]405 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{406407 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408 let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 false,415 )?;416417 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Toggle between normal and allow list access for the methods with access for `Anyone`.426 ///427 /// # Permissions428 ///429 /// * Collection Owner.430 ///431 /// # Arguments432 ///433 /// * collection_id.434 ///435 /// * mode: [AccessMode]436 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]437 #[transactional]438 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult439 {440 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441442 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;443 target_collection.check_is_owner(&sender)?;444445 target_collection.access = mode.clone();446447 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(448 collection_id,449 mode450 ));451452 target_collection.save()453 }454455 /// Allows Anyone to create tokens if:456 /// * Allow List is enabled, and457 /// * Address is added to allow list, and458 /// * This method was called with True parameter459 ///460 /// # Permissions461 /// * Collection Owner462 ///463 /// # Arguments464 ///465 /// * collection_id.466 ///467 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.468 #[weight = <SelfWeightOf<T>>::set_mint_permission()]469 #[transactional]470 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult471 {472 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);473474 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475 target_collection.check_is_owner(&sender)?;476477 target_collection.mint_mode = mint_permission;478479 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(480 collection_id481 ));482483 target_collection.save()484 }485486 /// Change the owner of the collection.487 ///488 /// # Permissions489 ///490 /// * Collection Owner.491 ///492 /// # Arguments493 ///494 /// * collection_id.495 ///496 /// * new_owner.497 #[weight = <SelfWeightOf<T>>::change_collection_owner()]498 #[transactional]499 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {500501 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);502503 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;504 target_collection.check_is_owner(&sender)?;505506 target_collection.owner = new_owner.clone();507 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(508 collection_id,509 new_owner510 ));511512 target_collection.save()513 }514515 /// Adds an admin of the Collection.516 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.517 ///518 /// # Permissions519 ///520 /// * Collection Owner.521 /// * Collection Admin.522 ///523 /// # Arguments524 ///525 /// * collection_id: ID of the Collection to add admin for.526 ///527 /// * new_admin_id: Address of new admin to add.528 #[weight = <SelfWeightOf<T>>::add_collection_admin()]529 #[transactional]530 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {531 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532 let collection = <CollectionHandle<T>>::try_get(collection_id)?;533534 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(535 collection_id,536 new_admin_id.clone()537 ));538539 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)540 }541542 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.543 ///544 /// # Permissions545 ///546 /// * Collection Owner.547 /// * Collection Admin.548 ///549 /// # Arguments550 ///551 /// * collection_id: ID of the Collection to remove admin for.552 ///553 /// * account_id: Address of admin to remove.554 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]555 #[transactional]556 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {557 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);558 let collection = <CollectionHandle<T>>::try_get(collection_id)?;559560 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(561 collection_id,562 account_id.clone()563 ));564565 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)566 }567568 /// # Permissions569 ///570 /// * Collection Owner571 ///572 /// # Arguments573 ///574 /// * collection_id.575 ///576 /// * new_sponsor.577 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]578 #[transactional]579 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {580 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);581582 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;583 target_collection.check_is_owner(&sender)?;584585 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());586587 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(588 collection_id,589 new_sponsor590 ));591592 target_collection.save()593 }594595 /// # Permissions596 ///597 /// * Sponsor.598 ///599 /// # Arguments600 ///601 /// * collection_id.602 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]603 #[transactional]604 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {605 let sender = ensure_signed(origin)?;606607 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;608 ensure!(609 target_collection.sponsorship.pending_sponsor() == Some(&sender),610 Error::<T>::ConfirmUnsetSponsorFail611 );612613 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());614615 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(616 collection_id,617 sender618 ));619620 target_collection.save()621 }622623 /// Switch back to pay-per-own-transaction model.624 ///625 /// # Permissions626 ///627 /// * Collection owner.628 ///629 /// # Arguments630 ///631 /// * collection_id.632 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]633 #[transactional]634 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {635 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);636637 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;638 target_collection.check_is_owner(&sender)?;639640 target_collection.sponsorship = SponsorshipState::Disabled;641642 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(643 collection_id644 ));645 target_collection.save()646 }647648 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.649 ///650 /// # Permissions651 ///652 /// * Collection Owner.653 /// * Collection Admin.654 /// * Anyone if655 /// * Allow List is enabled, and656 /// * Address is added to allow list, and657 /// * MintPermission is enabled (see SetMintPermission method)658 ///659 /// # Arguments660 ///661 /// * collection_id: ID of the collection.662 ///663 /// * owner: Address, initial owner of the NFT.664 ///665 /// * data: Token data to store on chain.666 #[weight = T::CommonWeightInfo::create_item()]667 #[transactional]668 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {669 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);670 let budget = budget::Value::new(2);671672 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))673 }674675 /// This method creates multiple items in a collection created with CreateCollection method.676 ///677 /// # Permissions678 ///679 /// * Collection Owner.680 /// * Collection Admin.681 /// * Anyone if682 /// * Allow List is enabled, and683 /// * Address is added to allow list, and684 /// * MintPermission is enabled (see SetMintPermission method)685 ///686 /// # Arguments687 ///688 /// * collection_id: ID of the collection.689 ///690 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].691 ///692 /// * owner: Address, initial owner of the NFT.693 #[weight = T::CommonWeightInfo::create_multiple_items(items_data.len() as u32)]694 #[transactional]695 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {696 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);697 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);698 let budget = budget::Value::new(2);699700 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))701 }702703 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]704 #[transactional]705 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {706 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707 let budget = budget::Value::new(2);708709 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))710 }711712 // TODO! transaction weight713714 /// Set transfers_enabled value for particular collection715 ///716 /// # Permissions717 ///718 /// * Collection Owner.719 ///720 /// # Arguments721 ///722 /// * collection_id: ID of the collection.723 ///724 /// * value: New flag value.725 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]726 #[transactional]727 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {728 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);729 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;730 target_collection.check_is_owner(&sender)?;731732 // =========733734 target_collection.limits.transfers_enabled = Some(value);735 target_collection.save()736 }737738 /// Destroys a concrete instance of NFT.739 ///740 /// # Permissions741 ///742 /// * Collection Owner.743 /// * Collection Admin.744 /// * Current NFT Owner.745 ///746 /// # Arguments747 ///748 /// * collection_id: ID of the collection.749 ///750 /// * item_id: ID of NFT to burn.751 #[weight = T::CommonWeightInfo::burn_item()]752 #[transactional]753 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {754 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755756 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;757 if value == 1 {758 <NftTransferBasket<T>>::remove(collection_id, item_id);759 <NftApproveBasket<T>>::remove(collection_id, item_id);760 }761 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?762 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());763 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));764 Ok(post_info)765 }766767 /// Destroys a concrete instance of NFT on behalf of the owner768 /// See also: [`approve`]769 ///770 /// # Permissions771 ///772 /// * Collection Owner.773 /// * Collection Admin.774 /// * Current NFT Owner.775 ///776 /// # Arguments777 ///778 /// * collection_id: ID of the collection.779 ///780 /// * item_id: ID of NFT to burn.781 ///782 /// * from: owner of item783 #[weight = T::CommonWeightInfo::burn_from()]784 #[transactional]785 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {786 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);787 let budget = budget::Value::new(2);788789 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))790 }791792 /// Change ownership of the token.793 ///794 /// # Permissions795 ///796 /// * Collection Owner797 /// * Collection Admin798 /// * Current NFT owner799 ///800 /// # Arguments801 ///802 /// * recipient: Address of token recipient.803 ///804 /// * collection_id.805 ///806 /// * item_id: ID of the item807 /// * Non-Fungible Mode: Required.808 /// * Fungible Mode: Ignored.809 /// * Re-Fungible Mode: Required.810 ///811 /// * value: Amount to transfer.812 /// * Non-Fungible Mode: Ignored813 /// * Fungible Mode: Must specify transferred amount814 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)815 #[weight = T::CommonWeightInfo::transfer()]816 #[transactional]817 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {818 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819 let budget = budget::Value::new(2);820821 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))822 }823824 /// Set, change, or remove approved address to transfer the ownership of the NFT.825 ///826 /// # Permissions827 ///828 /// * Collection Owner829 /// * Collection Admin830 /// * Current NFT owner831 ///832 /// # Arguments833 ///834 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).835 ///836 /// * collection_id.837 ///838 /// * item_id: ID of the item.839 #[weight = T::CommonWeightInfo::approve()]840 #[transactional]841 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {842 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);843844 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))845 }846847 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.848 ///849 /// # Permissions850 /// * Collection Owner851 /// * Collection Admin852 /// * Current NFT owner853 /// * Address approved by current NFT owner854 ///855 /// # Arguments856 ///857 /// * from: Address that owns token.858 ///859 /// * recipient: Address of token recipient.860 ///861 /// * collection_id.862 ///863 /// * item_id: ID of the item.864 ///865 /// * value: Amount to transfer.866 #[weight = T::CommonWeightInfo::transfer_from()]867 #[transactional]868 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {869 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);870 let budget = budget::Value::new(2);871872 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))873 }874875 /// Set off-chain data schema.876 ///877 /// # Permissions878 ///879 /// * Collection Owner880 /// * Collection Admin881 ///882 /// # Arguments883 ///884 /// * collection_id.885 ///886 /// * schema: String representing the offchain data schema.887 #[weight = T::CommonWeightInfo::set_variable_metadata(data.len() as u32)]888 #[transactional]889 pub fn set_variable_meta_data (890 origin,891 collection_id: CollectionId,892 item_id: TokenId,893 data: BoundedVec<u8, CustomDataLimit>,894 ) -> DispatchResultWithPostInfo {895 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896897 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))898 }899900 /// Set meta_update_permission value for particular collection901 ///902 /// # Permissions903 ///904 /// * Collection Owner.905 ///906 /// # Arguments907 ///908 /// * collection_id: ID of the collection.909 ///910 /// * value: New flag value.911 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]912 #[transactional]913 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {914 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;916917 ensure!(918 target_collection.meta_update_permission != MetaUpdatePermission::None,919 <CommonError<T>>::MetadataFlagFrozen,920 );921 target_collection.check_is_owner(&sender)?;922923 target_collection.meta_update_permission = value;924925 target_collection.save()926 }927928 /// Set schema standard929 /// ImageURL930 /// Unique931 ///932 /// # Permissions933 ///934 /// * Collection Owner935 /// * Collection Admin936 ///937 /// # Arguments938 ///939 /// * collection_id.940 ///941 /// * schema: SchemaVersion: enum942 #[weight = <SelfWeightOf<T>>::set_schema_version()]943 #[transactional]944 pub fn set_schema_version(945 origin,946 collection_id: CollectionId,947 version: SchemaVersion948 ) -> DispatchResult {949 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);950 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;951 target_collection.check_is_owner_or_admin(&sender)?;952 target_collection.schema_version = version;953954 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(955 collection_id956 ));957958 target_collection.save()959 }960961 /// Set off-chain data schema.962 ///963 /// # Permissions964 ///965 /// * Collection Owner966 /// * Collection Admin967 ///968 /// # Arguments969 ///970 /// * collection_id.971 ///972 /// * schema: String representing the offchain data schema.973 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]974 #[transactional]975 pub fn set_offchain_schema(976 origin,977 collection_id: CollectionId,978 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,979 ) -> DispatchResult {980 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);981 let collection = <CollectionHandle<T>>::try_get(collection_id)?;982983 // =========984985 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::OffchainSchema, schema.into_inner())?;986987 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(988 collection_id989 ));990 Ok(())991 }992993 /// Set const on-chain data schema.994 ///995 /// # Permissions996 ///997 /// * Collection Owner998 /// * Collection Admin999 ///1000 /// # Arguments1001 ///1002 /// * collection_id.1003 ///1004 /// * schema: String representing the const on-chain data schema.1005 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1006 #[transactional]1007 pub fn set_const_on_chain_schema (1008 origin,1009 collection_id: CollectionId,1010 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1011 ) -> DispatchResult {1012 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10141015 // =========10161017 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::ConstOnChainSchema, schema.into_inner())?;10181019 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1020 collection_id1021 ));1022 Ok(())1023 }10241025 /// Set variable on-chain data schema.1026 ///1027 /// # Permissions1028 ///1029 /// * Collection Owner1030 /// * Collection Admin1031 ///1032 /// # Arguments1033 ///1034 /// * collection_id.1035 ///1036 /// * schema: String representing the variable on-chain data schema.1037 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1038 #[transactional]1039 pub fn set_variable_on_chain_schema (1040 origin,1041 collection_id: CollectionId,1042 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1043 ) -> DispatchResult {1044 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045 let collection = <CollectionHandle<T>>::try_get(collection_id)?;10461047 // =========10481049 <PalletCommon<T>>::set_field(&collection, &sender, CollectionField::VariableOnChainSchema, schema.into_inner())?;10501051 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1052 collection_id1053 ));1054 Ok(())1055 }10561057 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1058 #[transactional]1059 pub fn set_collection_limits(1060 origin,1061 collection_id: CollectionId,1062 new_limit: CollectionLimits,1063 ) -> DispatchResult {1064 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1065 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1066 target_collection.check_is_owner(&sender)?;1067 let old_limit = &target_collection.limits;10681069 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10701071 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1072 collection_id1073 ));10741075 target_collection.save()1076 }1077 }1078}pallets/unique/src/mock.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-#![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<Test>;
-type Block = frame_system::mocking::MockBlock<Test>;
-
-// 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<T>},
- TemplateModule: pallet_template::{Pallet, Call, Storage},
- Balances: pallet_balances::{Pallet, Call, Storage},
- Common: pallet_common::{Pallet, Storage, Event<T>},
- 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<Self::AccountId>;
- type Header = Header;
- type Event = ();
- type BlockHashCount = BlockHashCount;
- type Version = ();
- type PalletInfo = PalletInfo;
- type AccountData = pallet_balances::AccountData<u64>;
- 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<Test>;
-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<pallet_balances::Pallet<Test>, ()>;
- type LengthToFee = IdentityFee<u64>;
- type WeightToFee = IdentityFee<u64>;
- 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<u64> for TestEvmAddressMapping {
- fn into_account_id(_addr: sp_core::H160) -> u64 {
- unimplemented!()
- }
-}
-
-pub struct TestEvmBackwardsAddressMapping;
-impl EvmBackwardsAddressMapping<u64> 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<u64> 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<MaybeMirroredLog>) {}
-}
-
-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::<Test>()
- .unwrap()
- .into()
-}
pallets/unique/src/sponsorship.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::CrossAccountId,
- item_id: &TokenId,
-) -> Option<()> {
- // preliminary sponsoring correctness check
- match collection.mode {
- CollectionMode::NFT => {
- let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
- if !owner.conv_eq(who) {
- return None;
- }
- }
- CollectionMode::Fungible(_) => {
- if item_id != &TokenId::default() {
- return None;
- }
- if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
- return None;
- }
- }
- CollectionMode::ReFungible => {
- if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
- return None;
- }
- }
- }
-
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, item_id),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
- }
- CollectionMode::ReFungible => {
- <ReFungibleTransferBasket<T>>::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 => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
- }
- CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
- (collection.id, item_id, who.as_sub()),
- block_number,
- ),
- };
-
- Some(())
-}
-
-pub fn withdraw_create_item<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::CrossAccountId,
- _properties: &CreateItemData,
-) -> Option<()> {
- if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {
- return None;
- }
-
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
- let timeout = last_tx_block + limit.into();
- if block_number < timeout {
- return None;
- }
- }
-
- CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
-
- Some(())
-}
-
-pub fn withdraw_set_variable_meta_data<T: Config>(
- who: &T::CrossAccountId,
- collection: &CollectionHandle<T>,
- 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::<T>::get((collection.id, item_id))?.owner;
- if !owner.conv_eq(who) {
- return None;
- }
- }
- CollectionMode::Fungible(_) => {
- if item_id != &TokenId::default() {
- return None;
- }
- if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
- return None;
- }
- }
- CollectionMode::ReFungible => {
- if !<pallet_refungible::Owned<T>>::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 = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let limit = collection.limits.sponsored_data_rate_limit()?;
-
- if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {
- let timeout = last_tx_block + limit.into();
- if block_number < timeout {
- return None;
- }
- }
-
- <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);
-
- Some(())
-}
-
-pub fn withdraw_approve<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::AccountId,
- item_id: &TokenId,
-) -> Option<()> {
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let limit = collection.limits.sponsor_approve_timeout();
-
- let last_tx_block = match collection.mode {
- CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
- CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
- CollectionMode::ReFungible => {
- <RefungibleApproveBasket<T>>::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 => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
- CollectionMode::Fungible(_) => {
- <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
- }
- CollectionMode::ReFungible => {
- <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
- }
- };
-
- Some(())
-}
-
-fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
- let collection = CollectionHandle::new(id)?;
- let sponsor = collection.sponsorship.sponsor().cloned()?;
- Some((sponsor, collection))
-}
-
-pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
-impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
-where
- T: Config,
- C: IsSubType<Call<T>>,
-{
- fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
- match IsSubType::<Call<T>>::is_sub_type(call)? {
- Call::create_item {
- collection_id,
- data,
- ..
- } => {
- let (sponsor, collection) = load(*collection_id)?;
- withdraw_create_item::<T>(
- &collection,
- &T::CrossAccountId::from_sub(who.clone()),
- data,
- )
- .map(|()| sponsor)
- }
- Call::transfer {
- collection_id,
- item_id,
- ..
- } => {
- let (sponsor, collection) = load(*collection_id)?;
- withdraw_transfer::<T>(
- &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::<T>(&collection, from, item_id).map(|()| sponsor)
- }
- Call::approve {
- collection_id,
- item_id,
- ..
- } => {
- let (sponsor, collection) = load(*collection_id)?;
- withdraw_approve::<T>(&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>(
- &T::CrossAccountId::from_sub(who.clone()),
- &collection,
- item_id,
- data,
- )
- .map(|()| sponsor)
- }
- _ => None,
- }
- }
-}
-
-use crate::SponsorshipPredict;
-pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
-
-impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>
-where
- T: Config,
-{
- fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
- where
- u64: From<<T as frame_system::Config>::BlockNumber>,
- {
- let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
- let _ = collection.sponsorship.sponsor()?;
-
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, token),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
- }
- CollectionMode::ReFungible => {
- <ReFungibleTransferBasket<T>>::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 => {
- <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
- }
- CollectionMode::Fungible(_) => true,
- CollectionMode::ReFungible => {
- <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
- }
- };
-
- if token_exists {
- Some(0)
- } else {
- None
- }
- }
-}
pallets/unique/src/tests.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-// 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!(<pallet_balances::Pallet<Test>>::set_balance(
- Origin::root(),
- DONOR_USER,
- value,
- 0
- ));
- assert_ok!(<pallet_balances::Pallet<Test>>::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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
-
- let data: CreateCollectionData<u64> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
- assert_eq!(
- <pallet_common::CollectionById<Test>>::get(id)
- .unwrap()
- .owner,
- owner
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::get(id).unwrap().name,
- saved_col_name
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::get(id).unwrap().mode,
- *mode
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::get(id)
- .unwrap()
- .description,
- saved_description
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::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!(
- <pallet_common::CollectionById<Test>>::get(collection_id)
- .unwrap()
- .schema_version,
- SchemaVersion::Unique
- );
- });
-}
-
-#[test]
-fn check_not_sufficient_founds() {
- new_test_ext().execute_with(|| {
- let acc: u64 = 1;
- <pallet_balances::Pallet<Test>>::set_balance(Origin::root(), acc, 0, 0).unwrap();
-
- let name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let token_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
-
- let data: CreateCollectionData<<Test as system::Config>::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, <CommonError<Test>>::NotSufficientFounds);
- });
-}
-
-#[test]
-fn create_fungible_collection_fails_with_large_decimal_numbers() {
- new_test_ext().execute_with(|| {
- let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
-
- let data: CreateCollectionData<u64> = 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::<Test>::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 = <pallet_nonfungible::TokenData<Test>>::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 = <pallet_nonfungible::TokenData<Test>>::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 = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));
- let balance =
- <pallet_refungible::Balance<Test>>::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 = <pallet_refungible::TokenData<Test>>::get((
- CollectionId(1),
- TokenId((index + 1) as u32),
- ));
- let balance =
- <pallet_refungible::Balance<Test>>::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!(
- <pallet_fungible::Balance<Test>>::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!(
- <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),
- 5
- );
-
- // change owner scenario
- assert_ok!(TemplateModule::transfer(
- origin1,
- account(2),
- CollectionId(1),
- TokenId(0),
- 5
- ));
- assert_eq!(
- <pallet_fungible::Balance<Test>>::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!(
- <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),
- 1
- );
- assert_eq!(
- <pallet_fungible::Balance<Test>>::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 = <pallet_refungible::TokenData<Test>>::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!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
- 1023
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
- 1023
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 0
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- false
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
- 523
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
- 500
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
- 1
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
- 323
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
- 700
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
- 1
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 0
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- false
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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),
- <pallet_nonfungible::Error::<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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::<Test>::ApprovedValueTooLow
- );
-
- // do approve
- assert_ok!(TemplateModule::approve(
- origin1,
- account(2),
- CollectionId(1),
- TokenId(1),
- 1
- ));
- assert_eq!(
- <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),
- account(2)
- );
-
- assert_ok!(TemplateModule::transfer_from(
- origin2,
- account(1),
- account(3),
- CollectionId(1),
- TokenId(1),
- 1
- ));
- assert!(
- <pallet_nonfungible::Allowance<Test>>::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!(
- &<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))
- .unwrap()
- .const_data,
- &data.const_data.into_inner()
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::Allowance<Test>>::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!(
- <pallet_nonfungible::Allowance<Test>>::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!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
- 1023
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_refungible::Allowance<Test>>::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!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
- 923
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
- 100
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_refungible::Allowance<Test>>::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!(
- <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),
- 5
- );
- assert_ok!(TemplateModule::approve(
- origin1,
- account(3),
- CollectionId(1),
- TokenId(0),
- 5
- ));
- assert_eq!(
- <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),
- 5
- );
- assert_eq!(
- <pallet_fungible::Allowance<Test>>::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!(
- <pallet_fungible::Allowance<Test>>::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::<Test>::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!(
- <pallet_common::CollectionById<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
-
- // burn item
- assert_ok!(TemplateModule::burn_item(
- origin1.clone(),
- collection_id,
- TokenId(1),
- 1
- ));
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::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::<Test>::TokenNotFound
- );
-
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::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!(
- <pallet_fungible::Balance<Test>>::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::<Test>::TokenValueTooLow
- );
-
- assert_eq!(
- <pallet_fungible::Balance<Test>>::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!(
- <pallet_fungible::Balance<Test>>::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),
- <pallet_fungible::Error::<Test>>::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!(
- <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_refungible::Balance<Test>>::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::<Test>::TokenValueTooLow
- );
-
- assert_eq!(
- <pallet_refungible::Balance<Test>>::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!(
- <pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),
- false
- );
- assert!(<pallet_common::IsAdmin<Test>>::get((
- CollectionId(1),
- account(2)
- )));
- assert!(<pallet_common::IsAdmin<Test>>::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!(<pallet_common::IsAdmin<Test>>::get((
- CollectionId(1),
- account(2)
- )));
- assert!(<pallet_common::IsAdmin<Test>>::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!(<pallet_common::IsAdmin<Test>>::get((
- CollectionId(1),
- account(2)
- )));
- assert_eq!(
- <pallet_common::IsAdmin<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),
- 0
- );
- assert_eq!(
- <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),
- 0
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),
- 5
- );
- assert_eq!(
- <pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),
- 1
- );
-
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_refungible::Owned<Test>>::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!(
- <pallet_nonfungible::Allowance<Test>>::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!(
- <pallet_nonfungible::Allowance<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),
- 0
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::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!(<pallet_common::Allowlist<Test>>::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!(<pallet_common::Allowlist<Test>>::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::<Test>::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::<Test>::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::<Test>::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!(<pallet_common::Allowlist<Test>>::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!(
- <pallet_common::Allowlist<Test>>::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!(
- <pallet_common::Allowlist<Test>>::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::<Test>::NoPermission
- );
- assert!(<pallet_common::Allowlist<Test>>::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::<Test>::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!(<pallet_common::Allowlist<Test>>::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::<Test>::CollectionNotFound
- );
-
- // Account 2 is not found in collection allow-list anyway
- assert_eq!(
- <pallet_common::Allowlist<Test>>::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!(
- <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),
- false
- );
- assert_ok!(TemplateModule::remove_from_allow_list(
- origin1,
- collection_id,
- account(2)
- ));
- assert_eq!(
- <pallet_common::Allowlist<Test>>::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::<Test>::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!(
- <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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!(
- <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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::<Test>::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!(
- <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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::<Test>::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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
-
- let data: CreateCollectionData<u64> = 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::<Test>::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::<Test>::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::<Test>::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!(
- <pallet_common::CollectionById<Test>>::get(collection_id)
- .unwrap()
- .const_on_chain_schema,
- b"test const on chain schema".to_vec()
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::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!(
- <pallet_common::CollectionById<Test>>::get(collection_id)
- .unwrap()
- .const_on_chain_schema,
- b"".to_vec()
- );
- assert_eq!(
- <pallet_common::CollectionById<Test>>::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!(
- <pallet_nonfungible::TokenData<Test>>::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!(
- <pallet_refungible::TokenData<Test>>::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),
- <pallet_fungible::Error<Test>>::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!(
- <pallet_nonfungible::TokenData<Test>>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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!(
- <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- false
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 0
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::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!(
- <pallet_nonfungible::TokenData<Test>>::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::<Test>::NoPermission
- );
- });
-}
-
-#[test]
-fn set_variable_meta_flag_after_freeze() {
- new_test_ext().execute_with(|| {
- // default_limits();
-
- let collection_id =
- create_test_collection_for_owner(&CollectionMode::NFT, 2, 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::<Test>::MetadataFlagFrozen
- );
- });
-}
-
-#[test]
-fn set_variable_meta_data_on_nft_with_none_flag_neg() {
- new_test_ext().execute_with(|| {
- // default_limits();
-
- let collection_id =
- create_test_collection_for_owner(&CollectionMode::NFT, 1, 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::<Test>::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!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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::<Test>::TransferNotAllowed
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
- 1
- );
- assert_eq!(
- <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),
- 0
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
- true
- );
- assert_eq!(
- <pallet_nonfungible::Owned<Test>>::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()
- ));
- });
-}
runtime/common/Cargo.tomldiffbeforeafterboth--- 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' }
runtime/common/src/eth_sponsoring.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+//! 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<T: UniqueConfig>(PhantomData<*const T>);
+impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
+ SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T>
+{
+ fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
+ let collection_id = map_eth_to_id(&call.0)?;
+ let collection = <CollectionHandle<T>>::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 = <UniqueNFTCall<T>>::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::<T>(&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::<T>(
+ &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::<T>(&collection, &from, &token_id).map(|()| sponsor)
+ }
+ UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+ .map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+ CollectionMode::Fungible(_) => {
+ let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
+ #[allow(clippy::single_match)]
+ match call {
+ UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
+ withdraw_transfer::<T>(&collection, who, &TokenId::default())
+ .map(|()| sponsor)
+ }
+ UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
+ let from = T::CrossAccountId::from_eth(from);
+ withdraw_transfer::<T>(&collection, &from, &TokenId::default())
+ .map(|()| sponsor)
+ }
+ UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+ withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
+ .map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ }?))
+ }
+}
runtime/common/src/lib.rsdiffbeforeafterboth--- 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;
runtime/common/src/sponsoring.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<T>,
+ who: &T::CrossAccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // preliminary sponsoring correctness check
+ match collection.mode {
+ CollectionMode::NFT => {
+ let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+ if !owner.conv_eq(who) {
+ return None;
+ }
+ }
+ CollectionMode::Fungible(_) => {
+ if item_id != &TokenId::default() {
+ return None;
+ }
+ if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+ return None;
+ }
+ }
+ CollectionMode::ReFungible => {
+ if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+ return None;
+ }
+ }
+ }
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::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 => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
+ }
+ CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
+ (collection.id, item_id, who.as_sub()),
+ block_number,
+ ),
+ };
+
+ Some(())
+}
+
+pub fn withdraw_create_item<
+ T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+>(
+ collection: &CollectionHandle<T>,
+ who: &T::CrossAccountId,
+ _properties: &CreateItemData,
+) -> Option<()> {
+ if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {
+ return None;
+ }
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
+ }
+ }
+
+ CreateItemBasket::<T>::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<T>,
+ 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::<T>::get((collection.id, item_id))?.owner;
+ if !owner.conv_eq(who) {
+ return None;
+ }
+ }
+ CollectionMode::Fungible(_) => {
+ if item_id != &TokenId::default() {
+ return None;
+ }
+ if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+ return None;
+ }
+ }
+ CollectionMode::ReFungible => {
+ if !<pallet_refungible::Owned<T>>::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 = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsored_data_rate_limit()?;
+
+ if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
+ }
+ }
+
+ <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);
+
+ Some(())
+}
+
+pub fn withdraw_approve<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>(
+ collection: &CollectionHandle<T>,
+ who: &T::AccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsor_approve_timeout();
+
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::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 => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
+ }
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
+ }
+ };
+
+ Some(())
+}
+
+fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
+ let collection = CollectionHandle::new(id)?;
+ let sponsor = collection.sponsorship.sponsor().cloned()?;
+ Some((sponsor, collection))
+}
+
+pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
+impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
+where
+ T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig,
+ C: IsSubType<UniqueCall<T>>,
+{
+ fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+ match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {
+ UniqueCall::create_item {
+ collection_id,
+ data,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_create_item::<T>(
+ &collection,
+ &T::CrossAccountId::from_sub(who.clone()),
+ data,
+ )
+ .map(|()| sponsor)
+ }
+ UniqueCall::transfer {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_transfer::<T>(
+ &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::<T>(&collection, from, item_id).map(|()| sponsor)
+ }
+ UniqueCall::approve {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_approve::<T>(&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>(
+ &T::CrossAccountId::from_sub(who.clone()),
+ &collection,
+ item_id,
+ data,
+ )
+ .map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+}
+
+use crate::SponsorshipPredict;
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>
+where
+ T: Config,
+{
+ fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>,
+ {
+ let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+ let _ = collection.sponsorship.sponsor()?;
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, token),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::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 => {
+ <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+ }
+ CollectionMode::Fungible(_) => true,
+ CollectionMode::ReFungible => {
+ <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+ }
+ };
+
+ if token_exists {
+ Some(0)
+ } else {
+ None
+ }
+ }
+}
runtime/common/src/weights.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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)* )) => {
+ <FungibleWeights<T>>::$method($($args)*)
+ .max(<NonfungibleWeights<T>>::$method($($args)*))
+ .max(<RefungibleWeights<T>>::$method($($args)*))
+ };
+}
+
+pub struct CommonWeights<T>(PhantomData<T>)
+where
+ T: FungibleConfig + NonfungibleConfig + RefungibleConfig;
+impl<T> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T>
+where
+ T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+ fn create_item() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_item())
+ }
+
+ fn create_multiple_items(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_multiple_items(amount))
+ }
+
+ fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
+ }
+
+ fn burn_item() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(burn_item())
+ }
+
+ fn transfer() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(transfer())
+ }
+
+ fn approve() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(approve())
+ }
+
+ fn transfer_from() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(transfer_from())
+ }
+
+ fn set_variable_metadata(bytes: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(set_variable_metadata(bytes))
+ }
+
+ fn burn_from() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(burn_from())
+ }
+}
runtime/opal/src/lib.rsdiffbeforeafterboth--- 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<Self>;
+ type CommonWeightInfo = CommonWeights<Self>;
}
parameter_types! {
@@ -923,11 +927,11 @@
// }
type EvmSponsorshipHandler = (
- pallet_unique::UniqueEthSponsorshipHandler<Runtime>,
+ UniqueEthSponsorshipHandler<Runtime>,
pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
);
type SponsorshipHandler = (
- pallet_unique::UniqueSponsorshipHandler<Runtime>,
+ UniqueSponsorshipHandler<Runtime>,
//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
);
runtime/tests/Cargo.tomldiffbeforeafterboth--- /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 = "*"
runtime/tests/src/lib.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+#![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<Test>;
+type Block = frame_system::mocking::MockBlock<Test>;
+
+#[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<T>},
+ Unique: pallet_unique::{Pallet, Call, Storage},
+ Balances: pallet_balances::{Pallet, Call, Storage},
+ Common: pallet_common::{Pallet, Storage, Event<T>},
+ 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<Self::AccountId>;
+ type Header = Header;
+ type Event = ();
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData<u64>;
+ 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<Test>;
+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<pallet_balances::Pallet<Test>, ()>;
+ type LengthToFee = IdentityFee<u64>;
+ type WeightToFee = IdentityFee<u64>;
+ 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<u64> for TestEvmAddressMapping {
+ fn into_account_id(_addr: sp_core::H160) -> u64 {
+ unimplemented!()
+ }
+}
+
+pub struct TestEvmBackwardsAddressMapping;
+impl EvmBackwardsAddressMapping<u64> 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<u64> 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<MaybeMirroredLog>) {}
+}
+
+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<Self>;
+ type EvmTokenAddressMapping = EvmTokenAddressMapping;
+ type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
+}
+
+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<Self>;
+}
+
+// Build genesis storage according to the mock runtime.
+pub fn new_test_ext() -> sp_io::TestExternalities {
+ system::GenesisConfig::default()
+ .build_storage::<Test>()
+ .unwrap()
+ .into()
+}
runtime/tests/src/tests.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+// 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!(<pallet_balances::Pallet<Test>>::set_balance(
+ Origin::root(),
+ DONOR_USER,
+ value,
+ 0
+ ));
+ assert_ok!(<pallet_balances::Pallet<Test>>::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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let data: CreateCollectionData<u64> = 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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
+ assert_eq!(
+ <pallet_common::CollectionById<Test>>::get(id)
+ .unwrap()
+ .owner,
+ owner
+ );
+ assert_eq!(
+ <pallet_common::CollectionById<Test>>::get(id).unwrap().name,
+ saved_col_name
+ );
+ assert_eq!(
+ <pallet_common::CollectionById<Test>>::get(id).unwrap().mode,
+ *mode
+ );
+ assert_eq!(
+ <pallet_common::CollectionById<Test>>::get(id)
+ .unwrap()
+ .description,
+ saved_description
+ );
+ assert_eq!(
+ <pallet_common::CollectionById<Test>>::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!(
+ <pallet_common::CollectionById<Test>>::get(collection_id)
+ .unwrap()
+ .schema_version,
+ SchemaVersion::Unique
+ );
+ });
+}
+
+#[test]
+fn check_not_sufficient_founds() {
+ new_test_ext().execute_with(|| {
+ let acc: u64 = 1;
+ <pallet_balances::Pallet<Test>>::set_balance(Origin::root(), acc, 0, 0).unwrap();
+
+ let name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let data: CreateCollectionData<<Test as frame_system::Config>::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, <CommonError<Test>>::NotSufficientFounds);
+ });
+}
+
+#[test]
+fn create_fungible_collection_fails_with_large_decimal_numbers() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let data: CreateCollectionData<u64> = 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::<Test>::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 = <pallet_nonfungible::TokenData<Test>>::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 = <pallet_nonfungible::TokenData<Test>>::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 = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));
+ let balance =
+ <pallet_refungible::Balance<Test>>::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 = <pallet_refungible::TokenData<Test>>::get((
+ CollectionId(1),
+ TokenId((index + 1) as u32),
+ ));
+ let balance =
+ <pallet_refungible::Balance<Test>>::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!(
+ <pallet_fungible::Balance<Test>>::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!(
+ <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),
+ 5
+ );
+
+ // change owner scenario
+ assert_ok!(Unique::transfer(
+ origin1,
+ account(2),
+ CollectionId(1),
+ TokenId(0),
+ 5
+ ));
+ assert_eq!(
+ <pallet_fungible::Balance<Test>>::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!(
+ <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),
+ 1
+ );
+ assert_eq!(
+ <pallet_fungible::Balance<Test>>::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 = <pallet_refungible::TokenData<Test>>::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!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
+ 1023
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
+ 1023
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ false
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
+ 523
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
+ 500
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),
+ 323
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
+ 700
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ false
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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),
+ <pallet_nonfungible::Error::<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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::<Test>::ApprovedValueTooLow
+ );
+
+ // do approve
+ assert_ok!(Unique::approve(
+ origin1,
+ account(2),
+ CollectionId(1),
+ TokenId(1),
+ 1
+ ));
+ assert_eq!(
+ <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),
+ account(2)
+ );
+
+ assert_ok!(Unique::transfer_from(
+ origin2,
+ account(1),
+ account(3),
+ CollectionId(1),
+ TokenId(1),
+ 1
+ ));
+ assert!(
+ <pallet_nonfungible::Allowance<Test>>::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!(
+ &<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))
+ .unwrap()
+ .const_data,
+ &data.const_data.into_inner()
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
+ 1023
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_refungible::Allowance<Test>>::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!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),
+ 923
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),
+ 100
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_refungible::Allowance<Test>>::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!(
+ <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),
+ 5
+ );
+ assert_ok!(Unique::approve(
+ origin1,
+ account(3),
+ CollectionId(1),
+ TokenId(0),
+ 5
+ ));
+ assert_eq!(
+ <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),
+ 5
+ );
+ assert_eq!(
+ <pallet_fungible::Allowance<Test>>::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!(
+ <pallet_fungible::Allowance<Test>>::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::<Test>::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!(
+ <pallet_common::CollectionById<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+
+ // burn item
+ assert_ok!(Unique::burn_item(
+ origin1.clone(),
+ collection_id,
+ TokenId(1),
+ 1
+ ));
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::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::<Test>::TokenNotFound
+ );
+
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::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!(
+ <pallet_fungible::Balance<Test>>::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::<Test>::TokenValueTooLow
+ );
+
+ assert_eq!(
+ <pallet_fungible::Balance<Test>>::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!(
+ <pallet_fungible::Balance<Test>>::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),
+ <pallet_fungible::Error::<Test>>::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!(
+ <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::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::<Test>::TokenValueTooLow
+ );
+
+ assert_eq!(
+ <pallet_refungible::Balance<Test>>::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!(
+ <pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),
+ false
+ );
+ assert!(<pallet_common::IsAdmin<Test>>::get((
+ CollectionId(1),
+ account(2)
+ )));
+ assert!(<pallet_common::IsAdmin<Test>>::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!(<pallet_common::IsAdmin<Test>>::get((
+ CollectionId(1),
+ account(2)
+ )));
+ assert!(<pallet_common::IsAdmin<Test>>::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!(<pallet_common::IsAdmin<Test>>::get((
+ CollectionId(1),
+ account(2)
+ )));
+ assert_eq!(
+ <pallet_common::IsAdmin<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),
+ 5
+ );
+ assert_eq!(
+ <pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),
+ 1
+ );
+
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_refungible::Owned<Test>>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::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!(<pallet_common::Allowlist<Test>>::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!(<pallet_common::Allowlist<Test>>::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::<Test>::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::<Test>::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::<Test>::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!(<pallet_common::Allowlist<Test>>::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!(
+ <pallet_common::Allowlist<Test>>::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!(
+ <pallet_common::Allowlist<Test>>::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::<Test>::NoPermission
+ );
+ assert!(<pallet_common::Allowlist<Test>>::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::<Test>::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!(<pallet_common::Allowlist<Test>>::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::<Test>::CollectionNotFound
+ );
+
+ // Account 2 is not found in collection allow-list anyway
+ assert_eq!(
+ <pallet_common::Allowlist<Test>>::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!(
+ <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),
+ false
+ );
+ assert_ok!(Unique::remove_from_allow_list(
+ origin1,
+ collection_id,
+ account(2)
+ ));
+ assert_eq!(
+ <pallet_common::Allowlist<Test>>::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::<Test>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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::<Test>::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!(
+ <pallet_nonfungible::Allowance<Test>>::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::<Test>::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::<Test>::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::<Test>::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<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let data: CreateCollectionData<u64> = 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::<Test>::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::<Test>::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::<Test>::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!(
+ <pallet_common::CollectionData<Test>>::get((
+ collection_id,
+ CollectionField::ConstOnChainSchema
+ )),
+ b"test const on chain schema".to_vec()
+ );
+ assert_eq!(
+ <pallet_common::CollectionData<Test>>::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!(
+ <pallet_common::CollectionData<Test>>::get((
+ collection_id,
+ CollectionField::ConstOnChainSchema
+ )),
+ b"".to_vec()
+ );
+ assert_eq!(
+ <pallet_common::CollectionData<Test>>::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!(
+ <pallet_nonfungible::TokenData<Test>>::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!(
+ <pallet_refungible::TokenData<Test>>::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),
+ <pallet_fungible::Error<Test>>::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!(
+ <pallet_nonfungible::TokenData<Test>>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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!(
+ <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ false
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 0
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::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!(
+ <pallet_nonfungible::TokenData<Test>>::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::<Test>::NoPermission
+ );
+ });
+}
+
+#[test]
+fn set_variable_meta_flag_after_freeze() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id =
+ create_test_collection_for_owner(&CollectionMode::NFT, 2, 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::<Test>::MetadataFlagFrozen
+ );
+ });
+}
+
+#[test]
+fn set_variable_meta_data_on_nft_with_none_flag_neg() {
+ new_test_ext().execute_with(|| {
+ // default_limits();
+
+ let collection_id =
+ create_test_collection_for_owner(&CollectionMode::NFT, 1, 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::<Test>::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!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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::<Test>::TransferNotAllowed
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),
+ 1
+ );
+ assert_eq!(
+ <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),
+ 0
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),
+ true
+ );
+ assert_eq!(
+ <pallet_nonfungible::Owned<Test>>::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()
+ ));
+ });
+}