git.delta.rocks / unique-network / refs/commits / 97d7053c9b0e

difftreelog

Merge branch 'develop' into feature/contract-sponsoring-modes

Igor Kozyrev2022-02-17parents: #e7ebf1d #71137c9.patch.diff
in: master

34 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -382,9 +382,9 @@
 
 [[package]]
 name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "backtrace"
@@ -634,9 +634,9 @@
 
 [[package]]
 name = "block-buffer"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -1097,9 +1097,9 @@
 
 [[package]]
 name = "core-foundation"
-version = "0.9.2"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -1229,9 +1229,9 @@
 
 [[package]]
 name = "crc32fast"
-version = "1.3.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
  "cfg-if 1.0.0",
 ]
@@ -1288,9 +1288,9 @@
 
 [[package]]
 name = "crypto-common"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
 dependencies = [
  "generic-array 0.14.5",
 ]
@@ -1898,13 +1898,12 @@
 
 [[package]]
 name = "digest"
-version = "0.10.1"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
 dependencies = [
- "block-buffer 0.10.1",
+ "block-buffer 0.10.2",
  "crypto-common",
- "generic-array 0.14.5",
 ]
 
 [[package]]
@@ -3241,9 +3240,9 @@
 
 [[package]]
 name = "httparse"
-version = "1.5.1"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
 
 [[package]]
 name = "httpdate"
@@ -4664,9 +4663,9 @@
 
 [[package]]
 name = "memmap2"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d"
+checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
 dependencies = [
  "libc",
 ]
@@ -8805,7 +8804,7 @@
 source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#4aeb95f7f38fcd519e2628f32f79044a8fef99d5"
 dependencies = [
  "impl-trait-for-tuples",
- "memmap2 0.5.2",
+ "memmap2 0.5.3",
  "parity-scale-codec",
  "sc-chain-spec-derive",
  "sc-network",
@@ -9912,7 +9911,7 @@
 dependencies = [
  "cfg-if 1.0.0",
  "cpufeatures 0.2.1",
- "digest 0.10.1",
+ "digest 0.10.2",
 ]
 
 [[package]]
@@ -11644,7 +11643,7 @@
 
 [[package]]
 name = "unique-node"
-version = "0.9.14"
+version = "0.9.16"
 dependencies = [
  "cumulus-client-cli",
  "cumulus-client-collator",
modifiedDockerfile-parachaindiffbeforeafterboth
--- a/Dockerfile-parachain
+++ b/Dockerfile-parachain
@@ -90,7 +90,7 @@
       nvm install v15.5.0 && \
       nvm use v15.5.0
 
-RUN git clone https://github.com/PureStake/polkadot-launch -b jlm-use-para-id-in-specs
+RUN git clone https://github.com/paritytech/polkadot-launch
 
 RUN export NVM_DIR="$HOME/.nvm" && \
     [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
modifiedREADME.mddiffbeforeafterboth
--- a/README.md
+++ b/README.md
@@ -29,26 +29,11 @@
 
 Please see our [walk-thorugh instructions](doc/hackusama_walk_through.md) to try everything out!
 
-## Hackusama Update
-
-During the Kusama Hackaphon the following changes were made:
-
--   Enabled Smart Contracts Pallet
--   Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
--   Fixed misc. bugs in Unique Pallet
--   Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
--   New Features:
-    -   Re-Fungible Token Mode
-    -   Off-Chain Schema to store token image URLs
-    -   Alternative economic model
-    -   Allow Lists and Public Mint Permission
--   Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
-    Blockchain.
-
 ## Application Development
 
 If you are building an application that operates NFT tokens, use [this document](doc/application_development.md).
 
+
 ## Building
 
 Building Unique chain requires special versions of Rust and toolchain. We don't use the most recent versions of everything
@@ -57,8 +42,8 @@
 1. Install Rust:
 
 ```bash
+sudo apt-get install git curl libssl-dev llvm pkg-config libclang-dev clang
 curl https://sh.rustup.rs -sSf | sh
-sudo apt-get install libssl-dev pkg-config libclang-dev clang
 ```
 
 2. Remove all installed toolchains with `rustup toolchain list` and `rustup toolchain uninstall <toolchain>`.
@@ -86,111 +71,52 @@
 cargo build --release
 ```
 
-## Run
-
-You can start a development chain with:
-
-```bash
-cargo run -- --dev
-```
-
-Detailed logs may be shown by running the node with the following environment variables set:
-`RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- --dev`.
+## Building as Parachain locally
 
-If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two
-validator nodes for Alice and Bob, who are the initial authorities of the genesis chain that have been endowed with
-testnet units. Give each node a name and expose them so they are listed on the Polkadot
-[telemetry site](https://telemetry.polkadot.io/#/Local%20Testnet). You'll need two terminal windows open.
+Note: checkout this project and all related projects (see below) in the sibling folders (both under the same folder)
 
-We'll start Alice's substrate node first on default TCP port 30333 with her chain database stored locally at
-`/tmp/alice`. The bootnode ID of her node is `QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN`, which is generated from
-the `--node-key` value that we specify below:
+### Polkadot launch utility
 
-```bash
-cargo run -- \
-  --base-path /tmp/alice \
-  --chain=local \
-  --alice \
-  --node-key 0000000000000000000000000000000000000000000000000000000000000001 \
-  --telemetry-url ws://telemetry.polkadot.io:1024 \
-  --validator
 ```
-
-In the second terminal, we'll start Bob's substrate node on a different TCP port of 30334, and with his chain database
-stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's
-bootnode ID on TCP port 30333:
-
-```bash
-cargo run -- \
-  --base-path /tmp/bob \
-  --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN \
-  --chain=local \
-  --bob \
-  --port 30334 \
-  --telemetry-url ws://telemetry.polkadot.io:1024 \
-  --validator
+git clone https://github.com/paritytech/polkadot-launch
 ```
-
-Additional CLI usage options are available and may be shown by running `cargo run -- --help`.
-
-## Building and Running as Parachain locally
-
-Rust toolchain: nightly-2021-11-11
-Note: checkout this project and polkadot project (see below) in the sibling folders (both under the same folder)
 
 ### Build relay
 
 ```
 git clone https://github.com/paritytech/polkadot.git
 cd polkadot
-git checkout release-v0.9.9
+git checkout release-v0.9.16
 cargo build --release
 ```
 
-### Build parachain
+### Build Unique parachain
 
 Run in the root of this project:
 ```
-cargo --build
+cargo build --release
 ```
 
-### Run 4-node Relay
-
-1. Download `rococo-custom-4.json` chain spec here: https://substrate.dev/cumulus-workshop/shared/chainspecs/rococo-custom-4.json
-2. Use these instructions to launch 4 nodes: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/1-launch
+### Build Acala parachain (optional, full config only)
 
-Example (Run in polkadot folder. Replace `12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on` with output from Alice node):
 ```
-./target/release/polkadot --alice --validator --base-path ./cumulus_relay0 --chain rococo-custom-4.json --port 50555 --ws-port 9944
-./target/release/polkadot --bob --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50556 --ws-port 9945
-./target/release/polkadot --charlie --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50557 --ws-port 9946
-./target/release/polkadot --dave --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50558 --ws-port 9947
-
+git clone https://github.com/AcalaNetwork/Acala
+cd Acala
+git checkout 54db3acd409a0b787f116f20e163a3b24101ce38
+make build-release
 ```
 
-3. Export genesis state and runtime wasm from Unique parachain:
+## Running as Parachain locally
 
-Run from this project root:
 ```
-./target/release/unique-collator export-genesis-state --parachain-id 2000 > ./resources/para-2000-genesis
-./target/release/unique-collator export-genesis-wasm > ./resources/para-2000-wasm
+./launch-testnet.sh
 ```
 
-4. Run two parachain nodes:
-
-Replace `12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg` with Alice or Bob relay ID
-
-Run from this project root:
+Optional, full setup with Acala and Statemint
 ```
-./target/release/unique-collator --alice --collator --force-authoring --base-path ./tmp/parachain-alice --parachain-id 2000 --port 40333 --ws-port 9844  -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30343 --ws-port 9977
-./target/release/unique-collator --bob --collator --force-authoring --parachain-id 2000 --base-path ./tmp/parachain/bob --port 40334 --ws-port 9845 -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30344 --ws-port 9978 --bootnodes /ip4/127.0.0.1/tcp/50556/p2p/12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg
+./launch-testnet-full.sh
 ```
-
-4. Reserve parachain ID as described here: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/2-reserve
 
-5. Register parachain in relay as described here: https://substrate.dev/cumulus-workshop/#/en/3-parachains/2-register
-
-
 ## Run Integration Tests
 
 1. Install all needed dependecies
@@ -202,40 +128,11 @@
 2. Run tests
 ```
 yarn test
-```
-
-## Benchmarks
-
-First of all, add rust toolchain and make it default.
-```bash
-rustup target add wasm32-unknown-unknown --toolchain nightly-2021-11-11
-```
-
-Then in "/node/src" run build command below
-```bash
-cargo +nightly-2021-11-11 build --release --features runtime-benchmarks
-```
-
-Run benchmark
-```bash
-target/release/unique-collator benchmark --chain dev --pallet "pallet_unique" --extrinsic "*" --repeat 1
 ```
-
-## UI custom types
-
-Moved to [runtime_types.json](./runtime_types.json).
-
-## Running Integration Tests
 
-See [tests/README.md](./tests/README.md).
 
 ## Code Formatting
 
-### Get formatter and linter settings into your branch (if you forked before they were introduced)
-```bash
-git cherry-pick -n 8ff77c21b0d30b2a4648fa35dbf61dfa9d3948a7
-```
-
 ### Apply formatting and clippy fixes
 ```bash
 cargo clippy
@@ -252,84 +149,57 @@
 cd tests && yarn eslint --ext .ts,.js src/
 ```
 
-## Re-Enabling Ink! Contracts
 
-Uncomment following lies:
-1. In node/rpc/Cargo.toml
-```
-# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
-```
+## Karura token transfer
 
-2. In node/rpc/src/lib.rs
-```
-// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
-...
-// use pallet_contracts_rpc::{Contracts, ContractsApi};
-...
-// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+To get started, you need to open inbound and outbound hrmp channels.
 
+### Next, we need to register our asset at Karura.
 ```
+assetRegistry -> registerForeignAsset(location, metadata)
+location:
+	V0(X2(Parent, Parachain(PARA_ID))) 
+metadata:
+	name         OPL
+	symbol       OPL
+	decimals     18
+minimalBalance	 1
+```
 
-3. In runtime/Cargo.toml
+### Next, we can send tokens from Opal to Karura:
 ```
-    # 'pallet-contracts/std',
-    # 'pallet-contracts-primitives/std',
-    # 'pallet-contracts-rpc-runtime-api/std',
-    # 'pallet-contract-helpers/std',
-...
-    # [dependencies.pallet-contracts]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
+polkadotXcm -> reserveTransferAssets
+dest:
+	V0(X2(Parent, Parachain(<KARURA_PARA_ID>))) 
+beneficiary:
+	X1(AccountId(Any, <ACCOUNT>))
+assets:
+	V1(Concrete(0,Here), Fungible(<AMOUNT>))
+feeAssetItem: 
+	0	
+weightLimit:
+	<LIMIT>
+```	
 
-    # [dependencies.pallet-contracts-primitives]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
+The result will be displayed in ChainState   
+tokens -> accounts	
 
-    # [dependencies.pallet-contracts-rpc-runtime-api]
-    # git = 'https://github.com/paritytech/substrate.git'
-    # default-features = false
-    # branch = 'polkadot-v0.9.9'
-    # version = '3.0.0'
-...
-    # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+### To send tokens from Karura to Opal:
 ```
+xtokens -> transfer
 
-4. runtime/src/lib.rs
-```
-// use pallet_contracts::weights::WeightInfo;
-...
-// pub use pallet_timestamp::Call as TimestampCall;
-...
-// mod chain_extension;
-// use crate::chain_extension::{NFTExtension, Imbalance};
-...
-/*
-parameter_types! {
-	pub TombstoneDeposit: Balance = deposit(
-  ...
-}
-*/
-...
-//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-...
-// impl pallet_contract_helpers::Config for Runtime {}
-...
-// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
-...
-// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
-...
-//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-...
-/*
-	impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
-		for Runtime
-	{
-    ...
-	}
-*/
+currencyId:
+	ForeingAsset
+		<TOKEN_ID>
 
-```
+amount:
+		<AMOUNT>
+dest:
+	V1
+	(
+		Parents:1, 
+		X2(Parachain(<KARURA_PARA_ID>), AccountId(Any, <ACCOUNT>)
+	)
+destWeight:
+	<WEIGHT>
+```
\ No newline at end of file
modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -4,7 +4,7 @@
 use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
 use jsonrpc_derive::rpc;
 use up_data_structs::{Collection, CollectionId, CollectionStats, TokenId};
-use sp_api::{BlockId, BlockT, ProvideRuntimeApi};
+use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
 use sp_blockchain::HeaderBackend;
 use up_rpc::UniqueApi as UniqueRuntimeApi;
 
@@ -132,7 +132,10 @@
 }
 
 macro_rules! pass_method {
-	($method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?) => {
+	(
+		$method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?
+		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*
+	) => {
 		fn $method_name(
 			&self,
 			$(
@@ -142,8 +145,25 @@
 		) -> Result<$result> {
 			let api = self.client.runtime_api();
 			let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
+			let _api_version = if let Ok(Some(api_version)) =
+				api.api_version::<dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>>(&at)
+			{
+				api_version
+			} else {
+				// unreachable for our runtime
+				return Err(RpcError {
+					code: ErrorCode::InvalidParams,
+					message: "Api is not available".into(),
+					data: None,
+				})
+			};
 
-			let result = api.$method_name(&at, $($name),*).map_err(|e| RpcError {
+			let result = $(if _api_version < $ver {
+				api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))
+			} else)*
+			{ api.$method_name(&at, $($name),*) };
+
+			let result = result.map_err(|e| RpcError {
 				code: ErrorCode::ServerError(Error::RuntimeError.into()),
 				message: "Unable to query".into(),
 				data: Some(format!("{:?}", e).into()),
@@ -168,7 +188,10 @@
 {
 	pass_method!(account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>);
 	pass_method!(token_exists(collection: CollectionId, token: TokenId) -> bool);
-	pass_method!(token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>);
+	pass_method!(
+		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>;
+		changed_in 2, token_owner_before_version_2(collection, token) => |u| Some(u)
+	);
 	pass_method!(const_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
 	pass_method!(variable_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
 	pass_method!(collection_tokens(collection: CollectionId) -> u32);
addeddoc/hackusama_update.mddiffbeforeafterboth
--- /dev/null
+++ b/doc/hackusama_update.md
@@ -0,0 +1,15 @@
+## Hackusama Update
+
+During the Kusama Hackaphon the following changes were made:
+
+-   Enabled Smart Contracts Pallet
+-   Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
+-   Fixed misc. bugs in Unique Pallet
+-   Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
+-   New Features:
+    -   Re-Fungible Token Mode
+    -   Off-Chain Schema to store token image URLs
+    -   Alternative economic model
+    -   Allow Lists and Public Mint Permission
+-   Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
+    Blockchain.
\ No newline at end of file
addeddoc/re-enable_ink_contracts.mddiffbeforeafterboth
--- /dev/null
+++ b/doc/re-enable_ink_contracts.md
@@ -0,0 +1,81 @@
+## Re-Enabling Ink! Contracts
+
+Uncomment following lies:
+1. In node/rpc/Cargo.toml
+```
+# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
+```
+
+2. In node/rpc/src/lib.rs
+```
+// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
+...
+// use pallet_contracts_rpc::{Contracts, ContractsApi};
+...
+// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+
+```
+
+3. In runtime/Cargo.toml
+```
+    # 'pallet-contracts/std',
+    # 'pallet-contracts-primitives/std',
+    # 'pallet-contracts-rpc-runtime-api/std',
+    # 'pallet-contract-helpers/std',
+...
+    # [dependencies.pallet-contracts]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+
+    # [dependencies.pallet-contracts-primitives]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+
+    # [dependencies.pallet-contracts-rpc-runtime-api]
+    # git = 'https://github.com/paritytech/substrate.git'
+    # default-features = false
+    # branch = 'polkadot-v0.9.9'
+    # version = '3.0.0'
+...
+    # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+```
+
+4. runtime/src/lib.rs
+```
+// use pallet_contracts::weights::WeightInfo;
+...
+// pub use pallet_timestamp::Call as TimestampCall;
+...
+// mod chain_extension;
+// use crate::chain_extension::{NFTExtension, Imbalance};
+...
+/*
+parameter_types! {
+	pub TombstoneDeposit: Balance = deposit(
+  ...
+}
+*/
+...
+//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
+...
+// impl pallet_contract_helpers::Config for Runtime {}
+...
+// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
+...
+// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
+...
+//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
+...
+/*
+	impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
+		for Runtime
+	{
+    ...
+	}
+*/
+
+```
\ No newline at end of file
addedlaunch-config-full.jsondiffbeforeafterboth
--- /dev/null
+++ b/launch-config-full.json
@@ -0,0 +1,135 @@
+{
+    "relaychain": {
+        "bin": "../polkadot/target/release/polkadot",
+        "chain": "rococo-local",
+        "nodes": [
+            {
+                "name": "alice",
+                "wsPort": 9844,
+                "rpcPort": 9843,
+                "port": 30444,
+                "flags": [
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
+                ]
+            },
+            {
+                "name": "bob",
+                "wsPort": 9855,
+                "rpcPort": 9854,
+                "port": 30555,
+                "flags": [
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
+                ]
+            },
+            {
+                "name": "charlie",
+                "wsPort": 9866,
+                "rpcPort": 9865,
+                "port": 30666,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            },
+            {
+                "name": "dave",
+                "wsPort": 9877,
+                "rpcPort": 9876,
+                "port": 30777,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            },
+            {
+                "name": "eve",
+                "wsPort": 9888,
+                "rpcPort": 9887,
+                "port": 30888,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
+            }
+        ],
+        "genesis": {
+            "runtime": {
+                "runtime_genesis_config": {
+                    "parachainsConfiguration": {
+                        "config": {
+                            "validation_upgrade_frequency": 1,
+                            "validation_upgrade_delay": 1
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "parachains": [
+        {
+            "bin": "../unique-chain/target/release/unique-collator",
+            "id": "1000",
+            "balance": "1000000000000000000000000",
+            "nodes": [
+                {
+                    "port": 31200,
+                    "wsPort": 9944,
+                    "rpcPort": 9933,
+                    "name": "alice",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                },
+                {
+                    "port": 31201,
+                    "wsPort": 9945,
+                    "rpcPort": 9934,
+                    "name": "bob",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+                }
+            ]
+        },
+        {
+            "bin": "../Acala/target/release/acala",
+            "id": "2000",
+            "chain":  "karura-dev",
+			"balance": "1000000000000000000000",
+			"nodes": [
+				{
+					"wsPort": 9946,
+					"port": 31202,
+					"name": "alice",
+                    "flags": [
+                        "--rpc-cors=all",
+                        "--unsafe-rpc-external",
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
+                    ]
+				}
+			]
+		}
+    ],
+    "simpleParachains": [],
+    "hrmpChannels": [
+        {
+            "sender": 2000,
+            "recipient": 1000,
+            "maxCapacity": 8,
+            "maxMessageSize": 512
+        },
+        {
+            "sender": 1000,
+            "recipient": 2000,
+            "maxCapacity": 8,
+            "maxMessageSize": 512
+        }
+    ],
+    "finalization": false
+}
deletedlaunch-config-westend.jsondiffbeforeafterboth
--- a/launch-config-westend.json
+++ /dev/null
@@ -1,141 +0,0 @@
-{
-    "relaychain": {
-        "bin": "../polkadot/target/release/polkadot",
-        "chain": "westend-local",
-        "nodes": [
-            {
-                "name": "alice",
-                "wsPort": 9844,
-                "rpcPort": 9843,
-                "port": 30444,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "bob",
-                "wsPort": 9855,
-                "rpcPort": 9854,
-                "port": 30555,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "charlie",
-                "wsPort": 9866,
-                "rpcPort": 9865,
-                "port": 30666,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "dave",
-                "wsPort": 9877,
-                "rpcPort": 9876,
-                "port": 30777,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "eve",
-                "wsPort": 9888,
-                "rpcPort": 9886,
-                "port": 30888,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            },
-            {
-                "name": "ferdie",
-                "wsPort": 9897,
-                "rpcPort": 9896,
-                "port": 30999,
-                "flags": [
-                    "-lparachain::candidate_validation=debug"
-                ]
-            }
-        ],
-        "genesis": {
-            "runtime": {
-                "runtime_genesis_config": {
-                    "parachainsConfiguration": {
-                        "config": {
-                            "validation_upgrade_frequency": 1,
-                            "validation_upgrade_delay": 1
-                        }
-                    }
-                }
-            }
-        }
-    },
-    "parachains": [
-        {
-            "bin": "../unique-chain/target/release/unique-collator",
-            "chain": "westend-local",
-            "balance": "1000000000000000000000000",
-            "nodes": [
-                {
-                    "port": 31200,
-                    "wsPort": 9944,
-                    "rpcPort": 9933,
-                    "name": "alice",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31201,
-                    "wsPort": 9945,
-                    "rpcPort": 9934,
-                    "name": "bob",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31202,
-                    "wsPort": 9946,
-                    "rpcPort": 9935,
-                    "name": "charlie",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31203,
-                    "wsPort": 9947,
-                    "rpcPort": 9936,
-                    "name": "dave",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                },
-                {
-                    "port": 31204,
-                    "wsPort": 9948,
-                    "rpcPort": 9937,
-                    "name": "eve",
-                    "flags": [
-                        "--rpc-cors=all",
-                        "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
-                    ]
-                }
-            ]
-        }
-    ],
-    "simpleParachains": [],
-    "hrmpChannels": [],
-    "finalization": false
-}
modifiedlaunch-config.jsondiffbeforeafterboth
--- a/launch-config.json
+++ b/launch-config.json
@@ -9,7 +9,8 @@
                 "rpcPort": 9843,
                 "port": 30444,
                 "flags": [
-                    "-lparachain::candidate_validation=debug"
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
                 ]
             },
             {
@@ -18,7 +19,8 @@
                 "rpcPort": 9854,
                 "port": 30555,
                 "flags": [
-                    "-lparachain::candidate_validation=debug"
+                    "-lparachain::candidate_validation=debug",
+                    "-lxcm=trace"
                 ]
             },
             {
@@ -38,6 +40,15 @@
                 "flags": [
                     "-lparachain::candidate_validation=debug"
                 ]
+            },
+            {
+                "name": "eve",
+                "wsPort": 9888,
+                "rpcPort": 9887,
+                "port": 30888,
+                "flags": [
+                    "-lparachain::candidate_validation=debug"
+                ]
             }
         ],
         "genesis": {
@@ -67,7 +78,8 @@
                     "flags": [
                         "--rpc-cors=all",
                         "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
                     ]
                 },
                 {
@@ -78,7 +90,8 @@
                     "flags": [
                         "--rpc-cors=all",
                         "--unsafe-rpc-external",
-                        "--unsafe-ws-external"
+                        "--unsafe-ws-external",
+                        "-lxcm=trace"
                     ]
                 }
             ]
deletedlaunch-test-env.shdiffbeforeafterboth
--- a/launch-test-env.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-cp launch-config.json ../polkadot-launch/launch-config.json
-
-cd ../polkadot-launch
-yarn install
-yarn build
-yarn start launch-config.json
addedlaunch-testnet-full.shdiffbeforeafterboth
--- /dev/null
+++ b/launch-testnet-full.sh
@@ -0,0 +1,6 @@
+cp launch-config-full.json ../polkadot-launch/launch-config-full.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config-full.json
addedlaunch-testnet.shdiffbeforeafterboth
--- /dev/null
+++ b/launch-testnet.sh
@@ -0,0 +1,6 @@
+cp launch-config.json ../polkadot-launch/launch-config.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config.json
modifiednode/cli/Cargo.tomldiffbeforeafterboth
--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -257,7 +257,7 @@
 license = 'All Rights Reserved'
 name = 'unique-node'
 repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.9.14'
+version = '0.9.16'
 
 [[bin]]
 name = 'unique-collator'
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
 	MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,
-	TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit,
+	TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+	CustomDataLimit, CreateCollectionData, SponsorshipState,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -285,6 +288,10 @@
 		TokenVariableDataLimitExceeded,
 		/// Exceeded max admin count
 		CollectionAdminCountExceeded,
+		/// Collection limit bounds per collection exceeded
+		CollectionLimitBoundsExceeded,
+		/// Tried to enable permissions which are only permitted to be disabled
+		OwnerPermissionsCantBeReverted,
 
 		/// Collection settings not allowing items transferring
 		TransferNotAllowed,
@@ -300,7 +307,7 @@
 		/// Item balance not enough.
 		TokenValueTooLow,
 		/// Requested value more than approved.
-		TokenValueNotEnough,
+		ApprovedValueTooLow,
 		/// Tried to approve more than owned
 		CantApproveMoreThanOwned,
 
@@ -395,7 +402,10 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -418,6 +428,29 @@
 
 		// =========
 
+		let collection = Collection {
+			owner: owner.clone(),
+			name: data.name,
+			mode: data.mode.clone(),
+			mint_mode: false,
+			access: data.access.unwrap_or_default(),
+			description: data.description,
+			token_prefix: data.token_prefix,
+			offchain_schema: data.offchain_schema,
+			schema_version: data.schema_version.unwrap_or_default(),
+			sponsorship: data
+				.pending_sponsor
+				.map(SponsorshipState::Unconfirmed)
+				.unwrap_or_default(),
+			variable_on_chain_schema: data.variable_on_chain_schema,
+			const_on_chain_schema: data.const_on_chain_schema,
+			limits: data
+				.limits
+				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+			meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+		};
+
 		// Take a (non-refundable) deposit of collection creation
 		{
 			let mut imbalance =
@@ -429,7 +462,7 @@
 				),
 			);
 			<T as Config>::Currency::settle(
-				&data.owner,
+				&owner,
 				imbalance,
 				WithdrawReasons::TRANSFER,
 				ExistenceRequirement::KeepAlive,
@@ -438,12 +471,8 @@
 		}
 
 		<CreatedCollectionCount<T>>::put(created_count);
-		<Pallet<T>>::deposit_event(Event::CollectionCreated(
-			id,
-			data.mode.id(),
-			data.owner.clone(),
-		));
-		<CollectionById<T>>::insert(id, data);
+		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+		<CollectionById<T>>::insert(id, collection);
 		Ok(id)
 	}
 
@@ -527,6 +556,61 @@
 
 		Ok(())
 	}
+
+	pub fn clamp_limits(
+		mode: CollectionMode,
+		old_limit: &CollectionLimits,
+		mut new_limit: CollectionLimits,
+	) -> Result<CollectionLimits, DispatchError> {
+		macro_rules! limit_default {
+				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+					$(
+						if let Some($new) = $new.$field {
+							let $old = $old.$field($($arg)?);
+							let _ = $new;
+							let _ = $old;
+							$check
+						} else {
+							$new.$field = $old.$field
+						}
+					)*
+				}};
+			}
+
+		limit_default!(old_limit, new_limit,
+			account_token_ownership_limit => ensure!(
+				new_limit <= MAX_TOKEN_OWNERSHIP,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsor_transfer_timeout(match mode {
+				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			}) => ensure!(
+				new_limit <= MAX_SPONSOR_TIMEOUT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsored_data_size => ensure!(
+				new_limit <= CUSTOM_DATA_LIMIT,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			token_limit => ensure!(
+				old_limit >= new_limit && new_limit > 0,
+				<Error<T>>::CollectionTokenLimitExceeded
+			),
+			owner_can_transfer => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			owner_can_destroy => ensure!(
+				old_limit || !new_limit,
+				<Error<T>>::OwnerPermissionsCantBeReverted,
+			),
+			sponsored_data_rate_limit => {},
+			transfers_enabled => {},
+		);
+		Ok(new_limit)
+	}
 }
 
 #[macro_export]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use core::ops::Deref;
 use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
 };
@@ -100,8 +100,11 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: FungibleHandle<T>,
@@ -343,7 +346,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -374,7 +377,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use erc::ERC721Events;
 use frame_support::{BoundedVec, ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, CustomDataLimit, TokenId};
+use up_data_structs::{AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
 };
@@ -140,8 +140,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
@@ -492,7 +495,7 @@
 		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -520,7 +523,7 @@
 		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -2,7 +2,7 @@
 
 use frame_support::{ensure, BoundedVec};
 use up_data_structs::{
-	AccessMode, Collection, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
+	AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
 };
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -155,8 +155,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
@@ -528,7 +531,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
@@ -561,7 +564,7 @@
 		if allowance.is_none() {
 			ensure!(
 				collection.ignores_allowance(spender),
-				<CommonError<T>>::TokenValueNotEnough
+				<CommonError<T>>::ApprovedValueTooLow
 			);
 		}
 
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -36,13 +36,11 @@
 use frame_system::{self as system, ensure_signed};
 use sp_runtime::{sp_std::prelude::Vec};
 use up_data_structs::{
-	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
-	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-	NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
-	MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,
-	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
-	CustomDataLimit,
+	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+	OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,
+	MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,
+	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+	CreateCollectionData, CustomDataLimit,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -84,10 +82,6 @@
 		ConfirmUnsetSponsorFail,
 		/// Length of items properties must be greater than 0.
 		EmptyArgument,
-		/// Collection limit bounds per collection exceeded
-		CollectionLimitBoundsExceeded,
-		/// Tried to enable permissions which are only permitted to be disabled
-		OwnerPermissionsCantBeReverted,
 	}
 }
 
@@ -321,42 +315,39 @@
 		// returns collection ID
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
+		#[deprecated]
 		pub fn create_collection(origin,
 								 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
 								 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
 								 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
-								 mode: CollectionMode) -> DispatchResult {
-
-			// Anyone can create a collection
-			let who = ensure_signed(origin)?;
-
-			// Create new collection
-			let new_collection = Collection {
-				owner: who,
+								 mode: CollectionMode) -> DispatchResult  {
+			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {
 				name: collection_name,
-				mode: mode.clone(),
-				mint_mode: false,
-				access: AccessMode::Normal,
 				description: collection_description,
 				token_prefix,
-				offchain_schema: BoundedVec::default(),
-				schema_version: SchemaVersion::ImageURL,
-				sponsorship: SponsorshipState::Disabled,
-				variable_on_chain_schema: BoundedVec::default(),
-				const_on_chain_schema: BoundedVec::default(),
-				limits: Default::default(),
-				meta_update_permission: Default::default(),
+				mode,
+				..Default::default()
 			};
+			Self::create_collection_ex(origin, data)
+		}
 
-			let _id = match mode {
-				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},
+		/// This method creates a collection
+		///
+		/// Prefer it to deprecated [`created_collection`] method
+		#[weight = <SelfWeightOf<T>>::create_collection()]
+		#[transactional]
+		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
+			let owner = ensure_signed(origin)?;
+
+			let _id = match data.mode {
+				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},
 				CollectionMode::Fungible(decimal_points) => {
 					// check params
 					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);
-					<PalletFungible<T>>::init_collection(new_collection)?
+					<PalletFungible<T>>::init_collection(owner, data)?
 				}
 				CollectionMode::ReFungible => {
-					<PalletRefungible<T>>::init_collection(new_collection)?
+					<PalletRefungible<T>>::init_collection(owner, data)?
 				}
 			};
 
@@ -1093,61 +1084,12 @@
 			collection_id: CollectionId,
 			new_limit: CollectionLimits,
 		) -> DispatchResult {
-			let mut new_limit = new_limit;
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
 			let old_limit = &target_collection.limits;
 
-			macro_rules! limit_default {
-				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
-					$(
-						if let Some($new) = $new.$field {
-							let $old = $old.$field($($arg)?);
-							let _ = $new;
-							let _ = $old;
-							$check
-						} else {
-							$new.$field = $old.$field
-						}
-					)*
-				}};
-			}
-
-			limit_default!(old_limit, new_limit,
-				account_token_ownership_limit => ensure!(
-					new_limit <= MAX_TOKEN_OWNERSHIP,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsor_transfer_timeout(match target_collection.mode {
-					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-				}) => ensure!(
-					new_limit <= MAX_SPONSOR_TIMEOUT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				sponsored_data_size => ensure!(
-					new_limit <= CUSTOM_DATA_LIMIT,
-					<Error<T>>::CollectionLimitBoundsExceeded,
-				),
-				token_limit => ensure!(
-					old_limit >= new_limit && new_limit > 0,
-					<CommonError<T>>::CollectionTokenLimitExceeded
-				),
-				owner_can_transfer => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				owner_can_destroy => ensure!(
-					old_limit || !new_limit,
-					<Error<T>>::OwnerPermissionsCantBeReverted,
-				),
-				sponsored_data_rate_limit => {},
-				transfers_enabled => {},
-			);
-
-			target_collection.limits = new_limit;
+			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
 				collection_id
modifiedpallets/unique/src/tests.rsdiffbeforeafterboth
before · pallets/unique/src/tests.rs
1// Tests to be written here2use super::*;3use crate::mock::*;4use crate::{AccessMode, CollectionMode};5use up_data_structs::{6	COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,7	CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,8	TokenId,9};10use frame_support::{assert_noop, assert_ok};11use sp_std::convert::TryInto;1213fn default_nft_data() -> CreateNftData {14	CreateNftData {15		const_data: vec![1, 2, 3].try_into().unwrap(),16		variable_data: vec![3, 2, 1].try_into().unwrap(),17	}18}1920fn default_fungible_data() -> CreateFungibleData {21	CreateFungibleData { value: 5 }22}2324fn default_re_fungible_data() -> CreateReFungibleData {25	CreateReFungibleData {26		const_data: vec![1, 2, 3].try_into().unwrap(),27		variable_data: vec![3, 2, 1].try_into().unwrap(),28		pieces: 1023,29	}30}3132fn create_test_collection_for_owner(33	mode: &CollectionMode,34	owner: u64,35	id: CollectionId,36) -> CollectionId {37	let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();38	let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();39	let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();4041	let origin1 = Origin::signed(owner);42	assert_ok!(TemplateModule::create_collection(43		origin1,44		col_name1.try_into().unwrap(),45		col_desc1.try_into().unwrap(),46		token_prefix1.try_into().unwrap(),47		mode.clone()48	));4950	let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();51	let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();52	let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();53	assert_eq!(54		<pallet_common::CollectionById<Test>>::get(id)55			.unwrap()56			.owner,57		owner58	);59	assert_eq!(60		<pallet_common::CollectionById<Test>>::get(id).unwrap().name,61		saved_col_name62	);63	assert_eq!(64		<pallet_common::CollectionById<Test>>::get(id).unwrap().mode,65		*mode66	);67	assert_eq!(68		<pallet_common::CollectionById<Test>>::get(id)69			.unwrap()70			.description,71		saved_description72	);73	assert_eq!(74		<pallet_common::CollectionById<Test>>::get(id)75			.unwrap()76			.token_prefix,77		saved_prefix78	);79	id80}8182fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId {83	create_test_collection_for_owner(&mode, 1, id)84}8586fn create_test_item(collection_id: CollectionId, data: &CreateItemData) {87	let origin1 = Origin::signed(1);88	assert_ok!(TemplateModule::create_item(89		origin1,90		collection_id,91		account(1),92		data.clone()93	));94}9596fn account(sub: u64) -> TestCrossAccountId {97	TestCrossAccountId::from_sub(sub)98}99100// Use cases tests region101// #region102103#[test]104fn set_version_schema() {105	new_test_ext().execute_with(|| {106		let origin1 = Origin::signed(1);107		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));108109		assert_ok!(TemplateModule::set_schema_version(110			origin1,111			collection_id,112			SchemaVersion::Unique113		));114		assert_eq!(115			<pallet_common::CollectionById<Test>>::get(collection_id)116				.unwrap()117				.schema_version,118			SchemaVersion::Unique119		);120	});121}122123#[test]124fn create_fungible_collection_fails_with_large_decimal_numbers() {125	new_test_ext().execute_with(|| {126		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();127		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();128		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();129130		let origin1 = Origin::signed(1);131		assert_noop!(132			TemplateModule::create_collection(133				origin1,134				col_name1.try_into().unwrap(),135				col_desc1.try_into().unwrap(),136				token_prefix1.try_into().unwrap(),137				CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1)138			),139			Error::<Test>::CollectionDecimalPointLimitExceeded140		);141	});142}143144#[test]145fn create_nft_item() {146	new_test_ext().execute_with(|| {147		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));148149		let data = default_nft_data();150		create_test_item(collection_id, &data.clone().into());151152		let item = <pallet_nonfungible::TokenData<Test>>::get((collection_id, 1)).unwrap();153		assert_eq!(item.const_data, data.const_data.into_inner());154		assert_eq!(item.variable_data, data.variable_data.into_inner());155	});156}157158// Use cases tests region159// #region160#[test]161fn create_nft_multiple_items() {162	new_test_ext().execute_with(|| {163		create_test_collection(&CollectionMode::NFT, CollectionId(1));164165		let origin1 = Origin::signed(1);166167		let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()];168169		assert_ok!(TemplateModule::create_multiple_items(170			origin1,171			CollectionId(1),172			account(1),173			items_data174				.clone()175				.into_iter()176				.map(|d| { d.into() })177				.collect()178		));179		for (index, data) in items_data.into_iter().enumerate() {180			let item = <pallet_nonfungible::TokenData<Test>>::get((181				CollectionId(1),182				TokenId((index + 1) as u32),183			))184			.unwrap();185			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());186			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());187		}188	});189}190191#[test]192fn create_refungible_item() {193	new_test_ext().execute_with(|| {194		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));195196		let data = default_re_fungible_data();197		create_test_item(collection_id, &data.clone().into());198		let item = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));199		let balance =200			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1)));201		assert_eq!(item.const_data, data.const_data.into_inner());202		assert_eq!(item.variable_data, data.variable_data.into_inner());203		assert_eq!(balance, 1023);204	});205}206207#[test]208fn create_multiple_refungible_items() {209	new_test_ext().execute_with(|| {210		create_test_collection(&CollectionMode::ReFungible, CollectionId(1));211212		let origin1 = Origin::signed(1);213214		let items_data = vec![215			default_re_fungible_data(),216			default_re_fungible_data(),217			default_re_fungible_data(),218		];219220		assert_ok!(TemplateModule::create_multiple_items(221			origin1,222			CollectionId(1),223			account(1),224			items_data225				.clone()226				.into_iter()227				.map(|d| { d.into() })228				.collect()229		));230		for (index, data) in items_data.into_iter().enumerate() {231			let item = <pallet_refungible::TokenData<Test>>::get((232				CollectionId(1),233				TokenId((index + 1) as u32),234			));235			let balance =236				<pallet_refungible::Balance<Test>>::get((CollectionId(1), TokenId(1), account(1)));237			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());238			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());239			assert_eq!(balance, 1023);240		}241	});242}243244#[test]245fn create_fungible_item() {246	new_test_ext().execute_with(|| {247		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));248249		let data = default_fungible_data();250		create_test_item(collection_id, &data.into());251252		assert_eq!(253			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),254			5255		);256	});257}258259//#[test]260// fn create_multiple_fungible_items() {261//     new_test_ext().execute_with(|| {262//         default_limits();263264//         create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));265266//         let origin1 = Origin::signed(1);267268//         let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()];269270//         assert_ok!(TemplateModule::create_multiple_items(271//             origin1.clone(),272//             1,273//             1,274//             items_data.clone().into_iter().map(|d| { d.into() }).collect()275//         ));276277//         for (index, _) in items_data.iter().enumerate() {278//             assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as TokenId).value, 5);279//         }280//         assert_eq!(TemplateModule::balance_count(1, 1), 3000);281//         assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]);282//     });283// }284285#[test]286fn transfer_fungible_item() {287	new_test_ext().execute_with(|| {288		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));289290		let origin1 = Origin::signed(1);291		let origin2 = Origin::signed(2);292293		let data = default_fungible_data();294		create_test_item(collection_id, &data.into());295296		assert_eq!(297			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),298			5299		);300301		// change owner scenario302		assert_ok!(TemplateModule::transfer(303			origin1,304			account(2),305			CollectionId(1),306			TokenId(0),307			5308		));309		assert_eq!(310			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),311			0312		);313314		// split item scenario315		assert_ok!(TemplateModule::transfer(316			origin2.clone(),317			account(3),318			CollectionId(1),319			TokenId(0),320			3321		));322323		// split item and new owner has account scenario324		assert_ok!(TemplateModule::transfer(325			origin2,326			account(3),327			CollectionId(1),328			TokenId(0),329			1330		));331		assert_eq!(332			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),333			1334		);335		assert_eq!(336			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(3))),337			4338		);339	});340}341342#[test]343fn transfer_refungible_item() {344	new_test_ext().execute_with(|| {345		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));346347		// Create RFT 1 in 1023 pieces for account 1348		let data = default_re_fungible_data();349		create_test_item(collection_id, &data.clone().into());350		let item = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));351		assert_eq!(item.const_data, data.const_data.into_inner());352		assert_eq!(item.variable_data, data.variable_data.into_inner());353		assert_eq!(354			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),355			1356		);357		assert_eq!(358			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),359			1023360		);361		assert_eq!(362			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),363			true364		);365366		// Account 1 transfers all 1023 pieces of RFT 1 to account 2367		let origin1 = Origin::signed(1);368		let origin2 = Origin::signed(2);369		assert_ok!(TemplateModule::transfer(370			origin1,371			account(2),372			CollectionId(1),373			TokenId(1),374			1023375		));376		assert_eq!(377			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),378			1023379		);380		assert_eq!(381			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),382			0383		);384		assert_eq!(385			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),386			1387		);388		assert_eq!(389			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),390			false391		);392		assert_eq!(393			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),394			true395		);396397		// Account 2 transfers 500 pieces of RFT 1 to account 3398		assert_ok!(TemplateModule::transfer(399			origin2.clone(),400			account(3),401			CollectionId(1),402			TokenId(1),403			500404		));405		assert_eq!(406			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),407			523408		);409		assert_eq!(410			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),411			500412		);413		assert_eq!(414			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),415			1416		);417		assert_eq!(418			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),419			1420		);421		assert_eq!(422			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),423			true424		);425		assert_eq!(426			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),427			true428		);429430		// Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance431		assert_ok!(TemplateModule::transfer(432			origin2,433			account(3),434			CollectionId(1),435			TokenId(1),436			200437		));438		assert_eq!(439			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),440			323441		);442		assert_eq!(443			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),444			700445		);446		assert_eq!(447			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),448			1449		);450		assert_eq!(451			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),452			1453		);454		assert_eq!(455			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),456			true457		);458		assert_eq!(459			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),460			true461		);462	});463}464465#[test]466fn transfer_nft_item() {467	new_test_ext().execute_with(|| {468		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));469470		let data = default_nft_data();471		create_test_item(collection_id, &data.into());472		assert_eq!(473			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),474			1475		);476		assert_eq!(477			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),478			true479		);480481		let origin1 = Origin::signed(1);482		// default scenario483		assert_ok!(TemplateModule::transfer(484			origin1,485			account(2),486			CollectionId(1),487			TokenId(1),488			1489		));490		assert_eq!(491			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),492			0493		);494		assert_eq!(495			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),496			1497		);498		assert_eq!(499			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),500			false501		);502		assert_eq!(503			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),504			true505		);506	});507}508509#[test]510fn transfer_nft_item_wrong_value() {511	new_test_ext().execute_with(|| {512		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));513514		let data = default_nft_data();515		create_test_item(collection_id, &data.into());516		assert_eq!(517			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),518			1519		);520		assert_eq!(521			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),522			true523		);524525		let origin1 = Origin::signed(1);526527		assert_noop!(528			TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2)529				.map_err(|e| e.error),530			<pallet_nonfungible::Error::<Test>>::NonfungibleItemsHaveNoAmount531		);532	});533}534535#[test]536fn transfer_nft_item_zero_value() {537	new_test_ext().execute_with(|| {538		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));539540		let data = default_nft_data();541		create_test_item(collection_id, &data.into());542		assert_eq!(543			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),544			1545		);546		assert_eq!(547			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),548			true549		);550551		let origin1 = Origin::signed(1);552553		// Transferring 0 amount works on NFT...554		assert_ok!(TemplateModule::transfer(555			origin1,556			account(2),557			CollectionId(1),558			TokenId(1),559			0560		));561		// ... and results in no transfer562		assert_eq!(563			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),564			1565		);566		assert_eq!(567			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),568			true569		);570	});571}572573#[test]574fn nft_approve_and_transfer_from() {575	new_test_ext().execute_with(|| {576		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));577578		let data = default_nft_data();579		create_test_item(collection_id, &data.into());580581		let origin1 = Origin::signed(1);582		let origin2 = Origin::signed(2);583584		assert_eq!(585			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),586			1587		);588		assert_eq!(589			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),590			true591		);592593		// neg transfer_from594		assert_noop!(595			TemplateModule::transfer_from(596				origin2.clone(),597				account(1),598				account(2),599				CollectionId(1),600				TokenId(1),601				1602			)603			.map_err(|e| e.error),604			CommonError::<Test>::TokenValueNotEnough605		);606607		// do approve608		assert_ok!(TemplateModule::approve(609			origin1,610			account(2),611			CollectionId(1),612			TokenId(1),613			1614		));615		assert_eq!(616			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),617			account(2)618		);619620		assert_ok!(TemplateModule::transfer_from(621			origin2,622			account(1),623			account(3),624			CollectionId(1),625			TokenId(1),626			1627		));628		assert!(629			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()630		);631	});632}633634#[test]635fn nft_approve_and_transfer_from_allow_list() {636	new_test_ext().execute_with(|| {637		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));638639		let origin1 = Origin::signed(1);640		let origin2 = Origin::signed(2);641642		// Create NFT 1 for account 1643		let data = default_nft_data();644		create_test_item(collection_id, &data.clone().into());645		assert_eq!(646			&<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))647				.unwrap()648				.const_data,649			&data.const_data.into_inner()650		);651		assert_eq!(652			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),653			1654		);655		assert_eq!(656			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),657			true658		);659660		// Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list661		assert_ok!(TemplateModule::set_mint_permission(662			origin1.clone(),663			CollectionId(1),664			true665		));666		assert_ok!(TemplateModule::set_public_access_mode(667			origin1.clone(),668			CollectionId(1),669			AccessMode::AllowList670		));671		assert_ok!(TemplateModule::add_to_allow_list(672			origin1.clone(),673			CollectionId(1),674			account(1)675		));676		assert_ok!(TemplateModule::add_to_allow_list(677			origin1.clone(),678			CollectionId(1),679			account(2)680		));681		assert_ok!(TemplateModule::add_to_allow_list(682			origin1.clone(),683			CollectionId(1),684			account(3)685		));686687		// Account 1 approves account 2 for NFT 1688		assert_ok!(TemplateModule::approve(689			origin1.clone(),690			account(2),691			CollectionId(1),692			TokenId(1),693			1694		));695		assert_eq!(696			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),697			account(2)698		);699700		// Account 2 transfers NFT 1 from account 1 to account 3701		assert_ok!(TemplateModule::transfer_from(702			origin2,703			account(1),704			account(3),705			CollectionId(1),706			TokenId(1),707			1708		));709		assert!(710			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()711		);712	});713}714715#[test]716fn refungible_approve_and_transfer_from() {717	new_test_ext().execute_with(|| {718		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));719720		let origin1 = Origin::signed(1);721		let origin2 = Origin::signed(2);722723		// Create RFT 1 in 1023 pieces for account 1724		let data = default_re_fungible_data();725		create_test_item(collection_id, &data.into());726727		assert_eq!(728			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),729			1730		);731		assert_eq!(732			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),733			1023734		);735		assert_eq!(736			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),737			true738		);739740		// Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list741		assert_ok!(TemplateModule::set_mint_permission(742			origin1.clone(),743			CollectionId(1),744			true745		));746		assert_ok!(TemplateModule::set_public_access_mode(747			origin1.clone(),748			CollectionId(1),749			AccessMode::AllowList750		));751		assert_ok!(TemplateModule::add_to_allow_list(752			origin1.clone(),753			CollectionId(1),754			account(1)755		));756		assert_ok!(TemplateModule::add_to_allow_list(757			origin1.clone(),758			CollectionId(1),759			account(2)760		));761		assert_ok!(TemplateModule::add_to_allow_list(762			origin1.clone(),763			CollectionId(1),764			account(3)765		));766767		// Account 1 approves account 2 for 1023 pieces of RFT 1768		assert_ok!(TemplateModule::approve(769			origin1,770			account(2),771			CollectionId(1),772			TokenId(1),773			1023774		));775		assert_eq!(776			<pallet_refungible::Allowance<Test>>::get((777				CollectionId(1),778				TokenId(1),779				account(1),780				account(2)781			)),782			1023783		);784785		// Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3786		assert_ok!(TemplateModule::transfer_from(787			origin2,788			account(1),789			account(3),790			CollectionId(1),791			TokenId(1),792			100793		));794		assert_eq!(795			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),796			1797		);798		assert_eq!(799			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),800			1801		);802		assert_eq!(803			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),804			923805		);806		assert_eq!(807			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),808			100809		);810		assert_eq!(811			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),812			true813		);814		assert_eq!(815			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),816			true817		);818		assert_eq!(819			<pallet_refungible::Allowance<Test>>::get((820				CollectionId(1),821				TokenId(1),822				account(1),823				account(2)824			)),825			923826		);827	});828}829830#[test]831fn fungible_approve_and_transfer_from() {832	new_test_ext().execute_with(|| {833		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));834835		let data = default_fungible_data();836		create_test_item(collection_id, &data.into());837838		let origin1 = Origin::signed(1);839		let origin2 = Origin::signed(2);840841		assert_ok!(TemplateModule::set_mint_permission(842			origin1.clone(),843			CollectionId(1),844			true845		));846		assert_ok!(TemplateModule::set_public_access_mode(847			origin1.clone(),848			CollectionId(1),849			AccessMode::AllowList850		));851		assert_ok!(TemplateModule::add_to_allow_list(852			origin1.clone(),853			CollectionId(1),854			account(1)855		));856		assert_ok!(TemplateModule::add_to_allow_list(857			origin1.clone(),858			CollectionId(1),859			account(2)860		));861		assert_ok!(TemplateModule::add_to_allow_list(862			origin1.clone(),863			CollectionId(1),864			account(3)865		));866867		// do approve868		assert_ok!(TemplateModule::approve(869			origin1.clone(),870			account(2),871			CollectionId(1),872			TokenId(0),873			5874		));875		assert_eq!(876			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),877			5878		);879		assert_ok!(TemplateModule::approve(880			origin1,881			account(3),882			CollectionId(1),883			TokenId(0),884			5885		));886		assert_eq!(887			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),888			5889		);890		assert_eq!(891			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(3))),892			5893		);894895		assert_ok!(TemplateModule::transfer_from(896			origin2.clone(),897			account(1),898			account(3),899			CollectionId(1),900			TokenId(0),901			4902		));903904		assert_eq!(905			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),906			1907		);908909		assert_noop!(910			TemplateModule::transfer_from(911				origin2,912				account(1),913				account(3),914				CollectionId(1),915				TokenId(0),916				4917			)918			.map_err(|e| e.error),919			CommonError::<Test>::TokenValueNotEnough920		);921	});922}923924#[test]925fn change_collection_owner() {926	new_test_ext().execute_with(|| {927		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));928929		let origin1 = Origin::signed(1);930		assert_ok!(TemplateModule::change_collection_owner(931			origin1,932			collection_id,933			2934		));935		assert_eq!(936			<pallet_common::CollectionById<Test>>::get(collection_id)937				.unwrap()938				.owner,939			2940		);941	});942}943944#[test]945fn destroy_collection() {946	new_test_ext().execute_with(|| {947		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));948949		let origin1 = Origin::signed(1);950		assert_ok!(TemplateModule::destroy_collection(origin1, collection_id));951	});952}953954#[test]955fn burn_nft_item() {956	new_test_ext().execute_with(|| {957		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));958959		let origin1 = Origin::signed(1);960961		let data = default_nft_data();962		create_test_item(collection_id, &data.into());963964		// check balance (collection with id = 1, user id = 1)965		assert_eq!(966			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),967			1968		);969970		// burn item971		assert_ok!(TemplateModule::burn_item(972			origin1.clone(),973			collection_id,974			TokenId(1),975			1976		));977		assert_eq!(978			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),979			0980		);981	});982}983984#[test]985fn burn_same_nft_item_twice() {986	new_test_ext().execute_with(|| {987		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));988989		let origin1 = Origin::signed(1);990991		let data = default_nft_data();992		create_test_item(collection_id, &data.into());993994		// check balance (collection with id = 1, user id = 1)995		assert_eq!(996			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),997			1998		);9991000		// burn item1001		assert_ok!(TemplateModule::burn_item(1002			origin1.clone(),1003			collection_id,1004			TokenId(1),1005			11006		));10071008		// burn item again1009		assert_noop!(1010			TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error),1011			CommonError::<Test>::TokenNotFound1012		);10131014		assert_eq!(1015			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1016			01017		);1018	});1019}10201021#[test]1022fn burn_fungible_item() {1023	new_test_ext().execute_with(|| {1024		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));10251026		let origin1 = Origin::signed(1);1027		assert_ok!(TemplateModule::add_collection_admin(1028			origin1.clone(),1029			collection_id,1030			account(2)1031		));10321033		let data = default_fungible_data();1034		create_test_item(collection_id, &data.into());10351036		// check balance (collection with id = 1, user id = 1)1037		assert_eq!(1038			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1039			51040		);10411042		// burn item1043		assert_ok!(TemplateModule::burn_item(1044			origin1.clone(),1045			CollectionId(1),1046			TokenId(0),1047			51048		));1049		assert_noop!(1050			TemplateModule::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error),1051			CommonError::<Test>::TokenValueTooLow1052		);10531054		assert_eq!(1055			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1056			01057		);1058	});1059}10601061#[test]1062fn burn_fungible_item_with_token_id() {1063	new_test_ext().execute_with(|| {1064		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));10651066		let origin1 = Origin::signed(1);1067		assert_ok!(TemplateModule::add_collection_admin(1068			origin1.clone(),1069			collection_id,1070			account(2)1071		));10721073		let data = default_fungible_data();1074		create_test_item(collection_id, &data.into());10751076		// check balance (collection with id = 1, user id = 1)1077		assert_eq!(1078			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1079			51080		);10811082		// Try to burn item using Token ID1083		assert_noop!(1084			TemplateModule::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error),1085			<pallet_fungible::Error::<Test>>::FungibleItemsHaveNoId1086		);1087	});1088}1089#[test]1090fn burn_refungible_item() {1091	new_test_ext().execute_with(|| {1092		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));1093		let origin1 = Origin::signed(1);10941095		assert_ok!(TemplateModule::set_mint_permission(1096			origin1.clone(),1097			collection_id,1098			true1099		));1100		assert_ok!(TemplateModule::set_public_access_mode(1101			origin1.clone(),1102			collection_id,1103			AccessMode::AllowList1104		));1105		assert_ok!(TemplateModule::add_to_allow_list(1106			origin1.clone(),1107			collection_id,1108			account(1)1109		));11101111		assert_ok!(TemplateModule::add_collection_admin(1112			origin1.clone(),1113			collection_id,1114			account(2)1115		));11161117		let data = default_re_fungible_data();1118		create_test_item(collection_id, &data.into());11191120		// check balance (collection with id = 1, user id = 2)1121		assert_eq!(1122			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),1123			11124		);1125		assert_eq!(1126			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1127			10231128		);11291130		// burn item1131		assert_ok!(TemplateModule::burn_item(1132			origin1.clone(),1133			collection_id,1134			TokenId(1),1135			10231136		));1137		assert_noop!(1138			TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1023)1139				.map_err(|e| e.error),1140			CommonError::<Test>::TokenValueTooLow1141		);11421143		assert_eq!(1144			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1145			01146		);1147	});1148}11491150#[test]1151fn add_collection_admin() {1152	new_test_ext().execute_with(|| {1153		let collection1_id =1154			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1155		let origin1 = Origin::signed(1);11561157		// Add collection admins1158		assert_ok!(TemplateModule::add_collection_admin(1159			origin1.clone(),1160			collection1_id,1161			account(2)1162		));1163		assert_ok!(TemplateModule::add_collection_admin(1164			origin1,1165			collection1_id,1166			account(3)1167		));11681169		// Owner is not an admin by default1170		assert_eq!(1171			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),1172			false1173		);1174		assert!(<pallet_common::IsAdmin<Test>>::get((1175			CollectionId(1),1176			account(2)1177		)));1178		assert!(<pallet_common::IsAdmin<Test>>::get((1179			CollectionId(1),1180			account(3)1181		)));1182	});1183}11841185#[test]1186fn remove_collection_admin() {1187	new_test_ext().execute_with(|| {1188		let collection1_id =1189			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1190		let origin1 = Origin::signed(1);1191		let origin2 = Origin::signed(2);11921193		// Add collection admins 2 and 31194		assert_ok!(TemplateModule::add_collection_admin(1195			origin1.clone(),1196			collection1_id,1197			account(2)1198		));1199		assert_ok!(TemplateModule::add_collection_admin(1200			origin1,1201			collection1_id,1202			account(3)1203		));12041205		assert!(<pallet_common::IsAdmin<Test>>::get((1206			CollectionId(1),1207			account(2)1208		)));1209		assert!(<pallet_common::IsAdmin<Test>>::get((1210			CollectionId(1),1211			account(3)1212		)));12131214		// remove admin 31215		assert_ok!(TemplateModule::remove_collection_admin(1216			origin2,1217			CollectionId(1),1218			account(3)1219		));12201221		// 2 is still admin, 3 is not an admin anymore1222		assert!(<pallet_common::IsAdmin<Test>>::get((1223			CollectionId(1),1224			account(2)1225		)));1226		assert_eq!(1227			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(3))),1228			false1229		);1230	});1231}12321233#[test]1234fn balance_of() {1235	new_test_ext().execute_with(|| {1236		let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1237		let fungible_collection_id =1238			create_test_collection(&CollectionMode::Fungible(3), CollectionId(2));1239		let re_fungible_collection_id =1240			create_test_collection(&CollectionMode::ReFungible, CollectionId(3));12411242		// check balance before1243		assert_eq!(1244			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1245			01246		);1247		assert_eq!(1248			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1249			01250		);1251		assert_eq!(1252			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1253			01254		);12551256		let nft_data = default_nft_data();1257		create_test_item(nft_collection_id, &nft_data.into());12581259		let fungible_data = default_fungible_data();1260		create_test_item(fungible_collection_id, &fungible_data.into());12611262		let re_fungible_data = default_re_fungible_data();1263		create_test_item(re_fungible_collection_id, &re_fungible_data.into());12641265		// check balance (collection with id = 1, user id = 1)1266		assert_eq!(1267			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1268			11269		);1270		assert_eq!(1271			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1272			51273		);1274		assert_eq!(1275			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1276			11277		);12781279		assert_eq!(1280			<pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),1281			true1282		);1283		assert_eq!(1284			<pallet_refungible::Owned<Test>>::get((1285				re_fungible_collection_id,1286				account(1),1287				TokenId(1)1288			)),1289			true1290		);1291	});1292}12931294#[test]1295fn approve() {1296	new_test_ext().execute_with(|| {1297		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));12981299		let data = default_nft_data();1300		create_test_item(collection_id, &data.into());13011302		let origin1 = Origin::signed(1);13031304		// approve1305		assert_ok!(TemplateModule::approve(1306			origin1,1307			account(2),1308			CollectionId(1),1309			TokenId(1),1310			11311		));1312		assert_eq!(1313			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1314			account(2)1315		);1316	});1317}13181319#[test]1320fn transfer_from() {1321	new_test_ext().execute_with(|| {1322		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1323		let origin1 = Origin::signed(1);1324		let origin2 = Origin::signed(2);13251326		let data = default_nft_data();1327		create_test_item(collection_id, &data.into());13281329		// approve1330		assert_ok!(TemplateModule::approve(1331			origin1.clone(),1332			account(2),1333			CollectionId(1),1334			TokenId(1),1335			11336		));1337		assert_eq!(1338			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1339			account(2)1340		);13411342		assert_ok!(TemplateModule::set_mint_permission(1343			origin1.clone(),1344			CollectionId(1),1345			true1346		));1347		assert_ok!(TemplateModule::set_public_access_mode(1348			origin1.clone(),1349			CollectionId(1),1350			AccessMode::AllowList1351		));1352		assert_ok!(TemplateModule::add_to_allow_list(1353			origin1.clone(),1354			CollectionId(1),1355			account(1)1356		));1357		assert_ok!(TemplateModule::add_to_allow_list(1358			origin1.clone(),1359			CollectionId(1),1360			account(2)1361		));1362		assert_ok!(TemplateModule::add_to_allow_list(1363			origin1,1364			CollectionId(1),1365			account(3)1366		));13671368		assert_ok!(TemplateModule::transfer_from(1369			origin2,1370			account(1),1371			account(2),1372			CollectionId(1),1373			TokenId(1),1374			11375		));13761377		// after transfer1378		assert_eq!(1379			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),1380			01381		);1382		assert_eq!(1383			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(2))),1384			11385		);1386	});1387}13881389// #endregion13901391// Coverage tests region1392// #region13931394#[test]1395fn owner_can_add_address_to_allow_list() {1396	new_test_ext().execute_with(|| {1397		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));13981399		let origin1 = Origin::signed(1);1400		assert_ok!(TemplateModule::add_to_allow_list(1401			origin1,1402			collection_id,1403			account(2)1404		));1405		assert!(<pallet_common::Allowlist<Test>>::get((1406			collection_id,1407			account(2)1408		)));1409	});1410}14111412#[test]1413fn admin_can_add_address_to_allow_list() {1414	new_test_ext().execute_with(|| {1415		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1416		let origin1 = Origin::signed(1);1417		let origin2 = Origin::signed(2);14181419		assert_ok!(TemplateModule::add_collection_admin(1420			origin1,1421			collection_id,1422			account(2)1423		));1424		assert_ok!(TemplateModule::add_to_allow_list(1425			origin2,1426			collection_id,1427			account(3)1428		));1429		assert!(<pallet_common::Allowlist<Test>>::get((1430			collection_id,1431			account(3)1432		)));1433	});1434}14351436#[test]1437fn nonprivileged_user_cannot_add_address_to_allow_list() {1438	new_test_ext().execute_with(|| {1439		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14401441		let origin2 = Origin::signed(2);1442		assert_noop!(1443			TemplateModule::add_to_allow_list(origin2, collection_id, account(3)),1444			CommonError::<Test>::NoPermission1445		);1446	});1447}14481449#[test]1450fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() {1451	new_test_ext().execute_with(|| {1452		let origin1 = Origin::signed(1);14531454		assert_noop!(1455			TemplateModule::add_to_allow_list(origin1, CollectionId(1), account(2)),1456			CommonError::<Test>::CollectionNotFound1457		);1458	});1459}14601461#[test]1462fn nobody_can_add_address_to_allow_list_of_deleted_collection() {1463	new_test_ext().execute_with(|| {1464		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14651466		let origin1 = Origin::signed(1);1467		assert_ok!(TemplateModule::destroy_collection(1468			origin1.clone(),1469			collection_id1470		));1471		assert_noop!(1472			TemplateModule::add_to_allow_list(origin1, collection_id, account(2)),1473			CommonError::<Test>::CollectionNotFound1474		);1475	});1476}14771478// If address is already added to allow list, nothing happens1479#[test]1480fn address_is_already_added_to_allow_list() {1481	new_test_ext().execute_with(|| {1482		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1483		let origin1 = Origin::signed(1);14841485		assert_ok!(TemplateModule::add_to_allow_list(1486			origin1.clone(),1487			collection_id,1488			account(2)1489		));1490		assert_ok!(TemplateModule::add_to_allow_list(1491			origin1,1492			collection_id,1493			account(2)1494		));1495		assert!(<pallet_common::Allowlist<Test>>::get((1496			collection_id,1497			account(2)1498		)));1499	});1500}15011502#[test]1503fn owner_can_remove_address_from_allow_list() {1504	new_test_ext().execute_with(|| {1505		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15061507		let origin1 = Origin::signed(1);1508		assert_ok!(TemplateModule::add_to_allow_list(1509			origin1.clone(),1510			collection_id,1511			account(2)1512		));1513		assert_ok!(TemplateModule::remove_from_allow_list(1514			origin1,1515			collection_id,1516			account(2)1517		));1518		assert_eq!(1519			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1520			false1521		);1522	});1523}15241525#[test]1526fn admin_can_remove_address_from_allow_list() {1527	new_test_ext().execute_with(|| {1528		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1529		let origin1 = Origin::signed(1);1530		let origin2 = Origin::signed(2);15311532		// Owner adds admin1533		assert_ok!(TemplateModule::add_collection_admin(1534			origin1.clone(),1535			collection_id,1536			account(2)1537		));15381539		// Owner adds address 3 to allow list1540		assert_ok!(TemplateModule::add_to_allow_list(1541			origin1,1542			collection_id,1543			account(3)1544		));15451546		// Admin removes address 3 from allow list1547		assert_ok!(TemplateModule::remove_from_allow_list(1548			origin2,1549			collection_id,1550			account(3)1551		));1552		assert_eq!(1553			<pallet_common::Allowlist<Test>>::get((collection_id, account(3))),1554			false1555		);1556	});1557}15581559#[test]1560fn nonprivileged_user_cannot_remove_address_from_allow_list() {1561	new_test_ext().execute_with(|| {1562		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1563		let origin1 = Origin::signed(1);1564		let origin2 = Origin::signed(2);15651566		assert_ok!(TemplateModule::add_to_allow_list(1567			origin1,1568			collection_id,1569			account(2)1570		));1571		assert_noop!(1572			TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)),1573			CommonError::<Test>::NoPermission1574		);1575		assert!(<pallet_common::Allowlist<Test>>::get((1576			collection_id,1577			account(2)1578		)));1579	});1580}15811582#[test]1583fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() {1584	new_test_ext().execute_with(|| {1585		let origin1 = Origin::signed(1);15861587		assert_noop!(1588			TemplateModule::remove_from_allow_list(origin1, CollectionId(1), account(2)),1589			CommonError::<Test>::CollectionNotFound1590		);1591	});1592}15931594#[test]1595fn nobody_can_remove_address_from_allow_list_of_deleted_collection() {1596	new_test_ext().execute_with(|| {1597		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1598		let origin1 = Origin::signed(1);1599		let origin2 = Origin::signed(2);16001601		// Add account 2 to allow list1602		assert_ok!(TemplateModule::add_to_allow_list(1603			origin1.clone(),1604			collection_id,1605			account(2)1606		));16071608		// Account 2 is in collection allow-list1609		assert!(<pallet_common::Allowlist<Test>>::get((1610			collection_id,1611			account(2)1612		)));16131614		// Destroy collection1615		assert_ok!(TemplateModule::destroy_collection(origin1, collection_id));16161617		// Attempt to remove account 2 from collection allow-list => error1618		assert_noop!(1619			TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)),1620			CommonError::<Test>::CollectionNotFound1621		);16221623		// Account 2 is not found in collection allow-list anyway1624		assert_eq!(1625			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1626			false1627		);1628	});1629}16301631// If address is already removed from allow list, nothing happens1632#[test]1633fn address_is_already_removed_from_allow_list() {1634	new_test_ext().execute_with(|| {1635		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1636		let origin1 = Origin::signed(1);16371638		assert_ok!(TemplateModule::add_to_allow_list(1639			origin1.clone(),1640			collection_id,1641			account(2)1642		));1643		assert_ok!(TemplateModule::remove_from_allow_list(1644			origin1.clone(),1645			collection_id,1646			account(2)1647		));1648		assert_eq!(1649			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1650			false1651		);1652		assert_ok!(TemplateModule::remove_from_allow_list(1653			origin1,1654			collection_id,1655			account(2)1656		));1657		assert_eq!(1658			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1659			false1660		);1661	});1662}16631664// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests)1665#[test]1666fn allow_list_test_1() {1667	new_test_ext().execute_with(|| {1668		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));16691670		let origin1 = Origin::signed(1);16711672		let data = default_nft_data();1673		create_test_item(collection_id, &data.into());16741675		assert_ok!(TemplateModule::set_public_access_mode(1676			origin1.clone(),1677			collection_id,1678			AccessMode::AllowList1679		));1680		assert_ok!(TemplateModule::add_to_allow_list(1681			origin1.clone(),1682			collection_id,1683			account(2)1684		));16851686		assert_noop!(1687			TemplateModule::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1)1688				.map_err(|e| e.error),1689			CommonError::<Test>::AddressNotInAllowlist1690		);1691	});1692}16931694#[test]1695fn allow_list_test_2() {1696	new_test_ext().execute_with(|| {1697		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1698		let origin1 = Origin::signed(1);16991700		let data = default_nft_data();1701		create_test_item(collection_id, &data.into());17021703		assert_ok!(TemplateModule::set_public_access_mode(1704			origin1.clone(),1705			collection_id,1706			AccessMode::AllowList1707		));1708		assert_ok!(TemplateModule::add_to_allow_list(1709			origin1.clone(),1710			collection_id,1711			account(1)1712		));1713		assert_ok!(TemplateModule::add_to_allow_list(1714			origin1.clone(),1715			collection_id,1716			account(2)1717		));17181719		// do approve1720		assert_ok!(TemplateModule::approve(1721			origin1.clone(),1722			account(1),1723			collection_id,1724			TokenId(1),1725			11726		));1727		assert_eq!(1728			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1729			account(1)1730		);17311732		assert_ok!(TemplateModule::remove_from_allow_list(1733			origin1.clone(),1734			collection_id,1735			account(1)1736		));17371738		assert_noop!(1739			TemplateModule::transfer_from(1740				origin1,1741				account(1),1742				account(3),1743				CollectionId(1),1744				TokenId(1),1745				11746			)1747			.map_err(|e| e.error),1748			CommonError::<Test>::AddressNotInAllowlist1749		);1750	});1751}17521753// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests)1754#[test]1755fn allow_list_test_3() {1756	new_test_ext().execute_with(|| {1757		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17581759		let origin1 = Origin::signed(1);17601761		let data = default_nft_data();1762		create_test_item(collection_id, &data.into());17631764		assert_ok!(TemplateModule::set_public_access_mode(1765			origin1.clone(),1766			collection_id,1767			AccessMode::AllowList1768		));1769		assert_ok!(TemplateModule::add_to_allow_list(1770			origin1.clone(),1771			collection_id,1772			account(1)1773		));17741775		assert_noop!(1776			TemplateModule::transfer(origin1, account(3), collection_id, TokenId(1), 1)1777				.map_err(|e| e.error),1778			CommonError::<Test>::AddressNotInAllowlist1779		);1780	});1781}17821783#[test]1784fn allow_list_test_4() {1785	new_test_ext().execute_with(|| {1786		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17871788		let origin1 = Origin::signed(1);17891790		let data = default_nft_data();1791		create_test_item(collection_id, &data.into());17921793		assert_ok!(TemplateModule::set_public_access_mode(1794			origin1.clone(),1795			collection_id,1796			AccessMode::AllowList1797		));1798		assert_ok!(TemplateModule::add_to_allow_list(1799			origin1.clone(),1800			collection_id,1801			account(1)1802		));1803		assert_ok!(TemplateModule::add_to_allow_list(1804			origin1.clone(),1805			collection_id,1806			account(2)1807		));18081809		// do approve1810		assert_ok!(TemplateModule::approve(1811			origin1.clone(),1812			account(1),1813			collection_id,1814			TokenId(1),1815			11816		));1817		assert_eq!(1818			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1819			account(1)1820		);18211822		assert_ok!(TemplateModule::remove_from_allow_list(1823			origin1.clone(),1824			collection_id,1825			account(2)1826		));18271828		assert_noop!(1829			TemplateModule::transfer_from(1830				origin1,1831				account(1),1832				account(3),1833				collection_id,1834				TokenId(1),1835				11836			)1837			.map_err(|e| e.error),1838			CommonError::<Test>::AddressNotInAllowlist1839		);1840	});1841}18421843// 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)1844#[test]1845fn allow_list_test_5() {1846	new_test_ext().execute_with(|| {1847		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18481849		let origin1 = Origin::signed(1);18501851		let data = default_nft_data();1852		create_test_item(collection_id, &data.into());18531854		assert_ok!(TemplateModule::set_public_access_mode(1855			origin1.clone(),1856			collection_id,1857			AccessMode::AllowList1858		));1859		assert_noop!(1860			TemplateModule::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1)1861				.map_err(|e| e.error),1862			CommonError::<Test>::AddressNotInAllowlist1863		);1864	});1865}18661867// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method).1868#[test]1869fn allow_list_test_6() {1870	new_test_ext().execute_with(|| {1871		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18721873		let origin1 = Origin::signed(1);18741875		let data = default_nft_data();1876		create_test_item(collection_id, &data.into());18771878		assert_ok!(TemplateModule::set_public_access_mode(1879			origin1.clone(),1880			collection_id,1881			AccessMode::AllowList1882		));18831884		// do approve1885		assert_noop!(1886			TemplateModule::approve(origin1, account(1), CollectionId(1), TokenId(1), 1)1887				.map_err(|e| e.error),1888			CommonError::<Test>::AddressNotInAllowlist1889		);1890	});1891}18921893// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and1894//          tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests)1895#[test]1896fn allow_list_test_7() {1897	new_test_ext().execute_with(|| {1898		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18991900		let data = default_nft_data();1901		create_test_item(collection_id, &data.into());19021903		let origin1 = Origin::signed(1);19041905		assert_ok!(TemplateModule::set_public_access_mode(1906			origin1.clone(),1907			collection_id,1908			AccessMode::AllowList1909		));1910		assert_ok!(TemplateModule::add_to_allow_list(1911			origin1.clone(),1912			collection_id,1913			account(1)1914		));1915		assert_ok!(TemplateModule::add_to_allow_list(1916			origin1.clone(),1917			collection_id,1918			account(2)1919		));19201921		assert_ok!(TemplateModule::transfer(1922			origin1,1923			account(2),1924			CollectionId(1),1925			TokenId(1),1926			11927		));1928	});1929}19301931#[test]1932fn allow_list_test_8() {1933	new_test_ext().execute_with(|| {1934		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19351936		// Create NFT for account 11937		let data = default_nft_data();1938		create_test_item(collection_id, &data.into());19391940		let origin1 = Origin::signed(1);19411942		// Toggle Allow List mode and add accounts 1 and 21943		assert_ok!(TemplateModule::set_public_access_mode(1944			origin1.clone(),1945			collection_id,1946			AccessMode::AllowList1947		));1948		assert_ok!(TemplateModule::add_to_allow_list(1949			origin1.clone(),1950			collection_id,1951			account(1)1952		));1953		assert_ok!(TemplateModule::add_to_allow_list(1954			origin1.clone(),1955			collection_id,1956			account(2)1957		));19581959		// Sself-approve account 1 for NFT 11960		assert_ok!(TemplateModule::approve(1961			origin1.clone(),1962			account(1),1963			CollectionId(1),1964			TokenId(1),1965			11966		));1967		assert_eq!(1968			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1969			account(1)1970		);19711972		// Transfer from 1 to 21973		assert_ok!(TemplateModule::transfer_from(1974			origin1,1975			account(1),1976			account(2),1977			CollectionId(1),1978			TokenId(1),1979			11980		));1981	});1982}19831984// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner.1985#[test]1986fn allow_list_test_9() {1987	new_test_ext().execute_with(|| {1988		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1989		let origin1 = Origin::signed(1);19901991		assert_ok!(TemplateModule::set_public_access_mode(1992			origin1.clone(),1993			collection_id,1994			AccessMode::AllowList1995		));1996		assert_ok!(TemplateModule::set_mint_permission(1997			origin1,1998			collection_id,1999			false2000		));20012002		let data = default_nft_data();2003		create_test_item(collection_id, &data.into());2004	});2005}20062007// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin.2008#[test]2009fn allow_list_test_10() {2010	new_test_ext().execute_with(|| {2011		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20122013		let origin1 = Origin::signed(1);2014		let origin2 = Origin::signed(2);20152016		assert_ok!(TemplateModule::set_public_access_mode(2017			origin1.clone(),2018			collection_id,2019			AccessMode::AllowList2020		));2021		assert_ok!(TemplateModule::set_mint_permission(2022			origin1.clone(),2023			collection_id,2024			false2025		));20262027		assert_ok!(TemplateModule::add_collection_admin(2028			origin1,2029			collection_id,2030			account(2)2031		));20322033		assert_ok!(TemplateModule::create_item(2034			origin2,2035			collection_id,2036			account(2),2037			default_nft_data().into()2038		));2039	});2040}20412042// 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.2043#[test]2044fn allow_list_test_11() {2045	new_test_ext().execute_with(|| {2046		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20472048		let origin1 = Origin::signed(1);2049		let origin2 = Origin::signed(2);20502051		assert_ok!(TemplateModule::set_public_access_mode(2052			origin1.clone(),2053			collection_id,2054			AccessMode::AllowList2055		));2056		assert_ok!(TemplateModule::set_mint_permission(2057			origin1.clone(),2058			collection_id,2059			false2060		));2061		assert_ok!(TemplateModule::add_to_allow_list(2062			origin1,2063			collection_id,2064			account(2)2065		));20662067		assert_noop!(2068			TemplateModule::create_item(2069				origin2,2070				CollectionId(1),2071				account(2),2072				default_nft_data().into()2073			)2074			.map_err(|e| e.error),2075			CommonError::<Test>::PublicMintingNotAllowed2076		);2077	});2078}20792080// 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.2081#[test]2082fn allow_list_test_12() {2083	new_test_ext().execute_with(|| {2084		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20852086		let origin1 = Origin::signed(1);2087		let origin2 = Origin::signed(2);20882089		assert_ok!(TemplateModule::set_public_access_mode(2090			origin1.clone(),2091			collection_id,2092			AccessMode::AllowList2093		));2094		assert_ok!(TemplateModule::set_mint_permission(2095			origin1,2096			collection_id,2097			false2098		));20992100		assert_noop!(2101			TemplateModule::create_item(2102				origin2,2103				CollectionId(1),2104				account(2),2105				default_nft_data().into()2106			)2107			.map_err(|e| e.error),2108			CommonError::<Test>::PublicMintingNotAllowed2109		);2110	});2111}21122113// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner.2114#[test]2115fn allow_list_test_13() {2116	new_test_ext().execute_with(|| {2117		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21182119		let origin1 = Origin::signed(1);21202121		assert_ok!(TemplateModule::set_public_access_mode(2122			origin1.clone(),2123			collection_id,2124			AccessMode::AllowList2125		));2126		assert_ok!(TemplateModule::set_mint_permission(2127			origin1,2128			collection_id,2129			true2130		));21312132		let data = default_nft_data();2133		create_test_item(collection_id, &data.into());2134	});2135}21362137// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin.2138#[test]2139fn allow_list_test_14() {2140	new_test_ext().execute_with(|| {2141		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21422143		let origin1 = Origin::signed(1);2144		let origin2 = Origin::signed(2);21452146		assert_ok!(TemplateModule::set_public_access_mode(2147			origin1.clone(),2148			collection_id,2149			AccessMode::AllowList2150		));2151		assert_ok!(TemplateModule::set_mint_permission(2152			origin1.clone(),2153			collection_id,2154			true2155		));21562157		assert_ok!(TemplateModule::add_collection_admin(2158			origin1,2159			collection_id,2160			account(2)2161		));21622163		assert_ok!(TemplateModule::create_item(2164			origin2,2165			collection_id,2166			account(2),2167			default_nft_data().into()2168		));2169	});2170}21712172// 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.2173#[test]2174fn allow_list_test_15() {2175	new_test_ext().execute_with(|| {2176		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21772178		let origin1 = Origin::signed(1);2179		let origin2 = Origin::signed(2);21802181		assert_ok!(TemplateModule::set_public_access_mode(2182			origin1.clone(),2183			collection_id,2184			AccessMode::AllowList2185		));2186		assert_ok!(TemplateModule::set_mint_permission(2187			origin1,2188			collection_id,2189			true2190		));21912192		assert_noop!(2193			TemplateModule::create_item(2194				origin2,2195				collection_id,2196				account(2),2197				default_nft_data().into()2198			)2199			.map_err(|e| e.error),2200			CommonError::<Test>::AddressNotInAllowlist2201		);2202	});2203}22042205// 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.2206#[test]2207fn allow_list_test_16() {2208	new_test_ext().execute_with(|| {2209		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22102211		let origin1 = Origin::signed(1);2212		let origin2 = Origin::signed(2);22132214		assert_ok!(TemplateModule::set_public_access_mode(2215			origin1.clone(),2216			collection_id,2217			AccessMode::AllowList2218		));2219		assert_ok!(TemplateModule::set_mint_permission(2220			origin1.clone(),2221			collection_id,2222			true2223		));2224		assert_ok!(TemplateModule::add_to_allow_list(2225			origin1,2226			collection_id,2227			account(2)2228		));22292230		assert_ok!(TemplateModule::create_item(2231			origin2,2232			collection_id,2233			account(2),2234			default_nft_data().into()2235		));2236	});2237}22382239// Total number of collections. Positive test2240#[test]2241fn total_number_collections_bound() {2242	new_test_ext().execute_with(|| {2243		create_test_collection(&CollectionMode::NFT, CollectionId(1));2244	});2245}22462247#[test]2248fn create_max_collections() {2249	new_test_ext().execute_with(|| {2250		for i in 1..COLLECTION_NUMBER_LIMIT {2251			create_test_collection(&CollectionMode::NFT, CollectionId(i));2252		}2253	});2254}22552256// Total number of collections. Negative test2257#[test]2258fn total_number_collections_bound_neg() {2259	new_test_ext().execute_with(|| {2260		let origin1 = Origin::signed(1);22612262		for i in 1..=COLLECTION_NUMBER_LIMIT {2263			create_test_collection(&CollectionMode::NFT, CollectionId(i));2264		}22652266		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();2267		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();2268		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();22692270		// 11-th collection in chain. Expects error2271		assert_noop!(2272			TemplateModule::create_collection(2273				origin1,2274				col_name1.try_into().unwrap(),2275				col_desc1.try_into().unwrap(),2276				token_prefix1.try_into().unwrap(),2277				CollectionMode::NFT2278			),2279			CommonError::<Test>::TotalCollectionsLimitExceeded2280		);2281	});2282}22832284// Owned tokens by a single address. Positive test2285#[test]2286fn owned_tokens_bound() {2287	new_test_ext().execute_with(|| {2288		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22892290		let data = default_nft_data();2291		create_test_item(collection_id, &data.clone().into());2292		create_test_item(collection_id, &data.into());2293	});2294}22952296// Owned tokens by a single address. Negotive test2297#[test]2298fn owned_tokens_bound_neg() {2299	new_test_ext().execute_with(|| {2300		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23012302		let origin1 = Origin::signed(1);23032304		for _ in 1..=MAX_TOKEN_OWNERSHIP {2305			let data = default_nft_data();2306			create_test_item(collection_id, &data.clone().into());2307		}23082309		let data = default_nft_data();2310		assert_noop!(2311			TemplateModule::create_item(origin1, CollectionId(1), account(1), data.into())2312				.map_err(|e| e.error),2313			CommonError::<Test>::AccountTokenLimitExceeded2314		);2315	});2316}23172318// Number of collection admins. Positive test2319#[test]2320fn collection_admins_bound() {2321	new_test_ext().execute_with(|| {2322		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23232324		let origin1 = Origin::signed(1);23252326		assert_ok!(TemplateModule::add_collection_admin(2327			origin1.clone(),2328			collection_id,2329			account(2)2330		));2331		assert_ok!(TemplateModule::add_collection_admin(2332			origin1,2333			collection_id,2334			account(3)2335		));2336	});2337}23382339// Number of collection admins. Negotive test2340#[test]2341fn collection_admins_bound_neg() {2342	new_test_ext().execute_with(|| {2343		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23442345		let origin1 = Origin::signed(1);23462347		for i in 0..COLLECTION_ADMINS_LIMIT {2348			assert_ok!(TemplateModule::add_collection_admin(2349				origin1.clone(),2350				collection_id,2351				account((2 + i).into())2352			));2353		}2354		assert_noop!(2355			TemplateModule::add_collection_admin(2356				origin1,2357				collection_id,2358				account((3 + COLLECTION_ADMINS_LIMIT).into())2359			),2360			CommonError::<Test>::CollectionAdminCountExceeded2361		);2362	});2363}2364// #endregion23652366#[test]2367fn set_const_on_chain_schema() {2368	new_test_ext().execute_with(|| {2369		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23702371		let origin1 = Origin::signed(1);2372		assert_ok!(TemplateModule::set_const_on_chain_schema(2373			origin1,2374			collection_id,2375			b"test const on chain schema".to_vec().try_into().unwrap()2376		));23772378		assert_eq!(2379			<pallet_common::CollectionById<Test>>::get(collection_id)2380				.unwrap()2381				.const_on_chain_schema,2382			b"test const on chain schema".to_vec()2383		);2384		assert_eq!(2385			<pallet_common::CollectionById<Test>>::get(collection_id)2386				.unwrap()2387				.variable_on_chain_schema,2388			b"".to_vec()2389		);2390	});2391}23922393#[test]2394fn set_variable_on_chain_schema() {2395	new_test_ext().execute_with(|| {2396		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23972398		let origin1 = Origin::signed(1);2399		assert_ok!(TemplateModule::set_variable_on_chain_schema(2400			origin1,2401			collection_id,2402			b"test variable on chain schema"2403				.to_vec()2404				.try_into()2405				.unwrap()2406		));24072408		assert_eq!(2409			<pallet_common::CollectionById<Test>>::get(collection_id)2410				.unwrap()2411				.const_on_chain_schema,2412			b"".to_vec()2413		);2414		assert_eq!(2415			<pallet_common::CollectionById<Test>>::get(collection_id)2416				.unwrap()2417				.variable_on_chain_schema,2418			b"test variable on chain schema".to_vec()2419		);2420	});2421}24222423#[test]2424fn set_variable_meta_data_on_nft_token_stores_variable_meta_data() {2425	new_test_ext().execute_with(|| {2426		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24272428		let origin1 = Origin::signed(1);24292430		let data = default_nft_data();2431		create_test_item(CollectionId(1), &data.into());24322433		let variable_data = b"test data".to_vec();2434		assert_ok!(TemplateModule::set_variable_meta_data(2435			origin1,2436			collection_id,2437			TokenId(1),2438			variable_data.clone().try_into().unwrap()2439		));24402441		assert_eq!(2442			<pallet_nonfungible::TokenData<Test>>::get((collection_id, 1))2443				.unwrap()2444				.variable_data,2445			variable_data2446		);2447	});2448}24492450#[test]2451fn set_variable_meta_data_on_re_fungible_token_stores_variable_meta_data() {2452	new_test_ext().execute_with(|| {2453		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));24542455		let origin1 = Origin::signed(1);24562457		let data = default_re_fungible_data();2458		create_test_item(collection_id, &data.into());24592460		let variable_data = b"test data".to_vec();2461		assert_ok!(TemplateModule::set_variable_meta_data(2462			origin1,2463			collection_id,2464			TokenId(1),2465			variable_data.clone().try_into().unwrap()2466		));24672468		assert_eq!(2469			<pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1))).variable_data,2470			variable_data2471		);2472	});2473}24742475#[test]2476fn set_variable_meta_data_on_fungible_token_fails() {2477	new_test_ext().execute_with(|| {2478		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));24792480		let origin1 = Origin::signed(1);24812482		let data = default_fungible_data();2483		create_test_item(collection_id, &data.into());24842485		let variable_data = b"test data".to_vec();2486		assert_noop!(2487			TemplateModule::set_variable_meta_data(2488				origin1,2489				collection_id,2490				TokenId(0),2491				variable_data.try_into().unwrap()2492			)2493			.map_err(|e| e.error),2494			<pallet_fungible::Error<Test>>::FungibleItemsDontHaveData2495		);2496	});2497}24982499#[test]2500fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() {2501	new_test_ext().execute_with(|| {2502		//default_limits();25032504		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));25052506		let origin1 = Origin::signed(1);25072508		let data = default_nft_data();2509		create_test_item(collection_id, &data.into());25102511		assert_ok!(TemplateModule::set_meta_update_permission_flag(2512			origin1.clone(),2513			collection_id,2514			MetaUpdatePermission::ItemOwner,2515		));25162517		let variable_data = b"ten chars.".to_vec();2518		assert_ok!(TemplateModule::set_variable_meta_data(2519			origin1,2520			collection_id,2521			TokenId(1),2522			variable_data.clone().try_into().unwrap()2523		));25242525		assert_eq!(2526			<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))2527				.unwrap()2528				.variable_data,2529			variable_data2530		);2531	});2532}25332534#[test]2535fn collection_transfer_flag_works() {2536	new_test_ext().execute_with(|| {2537		let origin1 = Origin::signed(1);25382539		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2540		assert_ok!(TemplateModule::set_transfers_enabled_flag(2541			origin1,2542			collection_id,2543			true2544		));25452546		let data = default_nft_data();2547		create_test_item(collection_id, &data.into());2548		assert_eq!(2549			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2550			12551		);2552		assert_eq!(2553			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2554			true2555		);25562557		let origin1 = Origin::signed(1);25582559		// default scenario2560		assert_ok!(TemplateModule::transfer(2561			origin1,2562			account(2),2563			collection_id,2564			TokenId(1),2565			12566		));2567		assert_eq!(2568			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2569			false2570		);2571		assert_eq!(2572			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2573			true2574		);2575		assert_eq!(2576			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2577			02578		);2579		assert_eq!(2580			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2581			12582		);2583	});2584}25852586#[test]2587fn set_variable_meta_data_on_nft_with_admin_flag() {2588	new_test_ext().execute_with(|| {2589		// default_limits();25902591		let collection_id =2592			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));25932594		let origin1 = Origin::signed(1);2595		let origin2 = Origin::signed(2);25962597		assert_ok!(TemplateModule::set_mint_permission(2598			origin2.clone(),2599			collection_id,2600			true2601		));2602		assert_ok!(TemplateModule::add_to_allow_list(2603			origin2.clone(),2604			collection_id,2605			account(1)2606		));26072608		assert_ok!(TemplateModule::add_collection_admin(2609			origin2.clone(),2610			collection_id,2611			account(1)2612		));26132614		let data = default_nft_data();2615		create_test_item(collection_id, &data.into());26162617		assert_ok!(TemplateModule::set_meta_update_permission_flag(2618			origin2.clone(),2619			collection_id,2620			MetaUpdatePermission::Admin,2621		));26222623		let variable_data = b"test.".to_vec();2624		assert_ok!(TemplateModule::set_variable_meta_data(2625			origin1,2626			collection_id,2627			TokenId(1),2628			variable_data.clone().try_into().unwrap()2629		));26302631		assert_eq!(2632			<pallet_nonfungible::TokenData<Test>>::get((collection_id, 1))2633				.unwrap()2634				.variable_data,2635			variable_data2636		);2637	});2638}26392640#[test]2641fn set_variable_meta_data_on_nft_with_admin_flag_neg() {2642	new_test_ext().execute_with(|| {2643		// default_limits();26442645		let collection_id =2646			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));26472648		let origin1 = Origin::signed(1);2649		let origin2 = Origin::signed(2);26502651		assert_ok!(TemplateModule::set_mint_permission(2652			origin2.clone(),2653			collection_id,2654			true2655		));2656		assert_ok!(TemplateModule::add_to_allow_list(2657			origin2.clone(),2658			collection_id,2659			account(1)2660		));26612662		let data = default_nft_data();2663		create_test_item(collection_id, &data.into());26642665		assert_ok!(TemplateModule::set_meta_update_permission_flag(2666			origin2.clone(),2667			collection_id,2668			MetaUpdatePermission::Admin,2669		));26702671		let variable_data = b"test.".to_vec();2672		assert_noop!(2673			TemplateModule::set_variable_meta_data(2674				origin1,2675				collection_id,2676				TokenId(1),2677				variable_data.try_into().unwrap()2678			)2679			.map_err(|e| e.error),2680			CommonError::<Test>::NoPermission2681		);2682	});2683}26842685#[test]2686fn set_variable_meta_flag_after_freeze() {2687	new_test_ext().execute_with(|| {2688		// default_limits();26892690		let collection_id =2691			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));26922693		let origin2 = Origin::signed(2);26942695		assert_ok!(TemplateModule::set_meta_update_permission_flag(2696			origin2.clone(),2697			collection_id,2698			MetaUpdatePermission::None,2699		));2700		assert_noop!(2701			TemplateModule::set_meta_update_permission_flag(2702				origin2.clone(),2703				collection_id,2704				MetaUpdatePermission::Admin2705			),2706			CommonError::<Test>::MetadataFlagFrozen2707		);2708	});2709}27102711#[test]2712fn set_variable_meta_data_on_nft_with_none_flag_neg() {2713	new_test_ext().execute_with(|| {2714		// default_limits();27152716		let collection_id =2717			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));2718		let origin1 = Origin::signed(1);27192720		let data = default_nft_data();2721		create_test_item(collection_id, &data.into());27222723		assert_ok!(TemplateModule::set_meta_update_permission_flag(2724			origin1.clone(),2725			collection_id,2726			MetaUpdatePermission::None,2727		));27282729		let variable_data = b"test.".to_vec();2730		assert_noop!(2731			TemplateModule::set_variable_meta_data(2732				origin1.clone(),2733				collection_id,2734				TokenId(1),2735				variable_data.try_into().unwrap()2736			)2737			.map_err(|e| e.error),2738			CommonError::<Test>::NoPermission2739		);2740	});2741}27422743#[test]2744fn collection_transfer_flag_works_neg() {2745	new_test_ext().execute_with(|| {2746		let origin1 = Origin::signed(1);27472748		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2749		assert_ok!(TemplateModule::set_transfers_enabled_flag(2750			origin1,2751			collection_id,2752			false2753		));27542755		let data = default_nft_data();2756		create_test_item(collection_id, &data.into());2757		assert_eq!(2758			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2759			12760		);2761		assert_eq!(2762			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2763			true2764		);27652766		let origin1 = Origin::signed(1);27672768		// default scenario2769		assert_noop!(2770			TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1)2771				.map_err(|e| e.error),2772			CommonError::<Test>::TransferNotAllowed2773		);2774		assert_eq!(2775			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2776			12777		);2778		assert_eq!(2779			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2780			02781		);2782		assert_eq!(2783			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2784			true2785		);2786		assert_eq!(2787			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2788			false2789		);2790	});2791}
after · pallets/unique/src/tests.rs
1// Tests to be written here2use super::*;3use crate::mock::*;4use crate::{AccessMode, CollectionMode};5use up_data_structs::{6	COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,7	CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,8	TokenId, MAX_TOKEN_OWNERSHIP,9};10use frame_support::{assert_noop, assert_ok};11use sp_std::convert::TryInto;1213fn default_nft_data() -> CreateNftData {14	CreateNftData {15		const_data: vec![1, 2, 3].try_into().unwrap(),16		variable_data: vec![3, 2, 1].try_into().unwrap(),17	}18}1920fn default_fungible_data() -> CreateFungibleData {21	CreateFungibleData { value: 5 }22}2324fn default_re_fungible_data() -> CreateReFungibleData {25	CreateReFungibleData {26		const_data: vec![1, 2, 3].try_into().unwrap(),27		variable_data: vec![3, 2, 1].try_into().unwrap(),28		pieces: 1023,29	}30}3132fn create_test_collection_for_owner(33	mode: &CollectionMode,34	owner: u64,35	id: CollectionId,36) -> CollectionId {37	let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();38	let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();39	let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();4041	let origin1 = Origin::signed(owner);42	assert_ok!(TemplateModule::create_collection(43		origin1,44		col_name1.try_into().unwrap(),45		col_desc1.try_into().unwrap(),46		token_prefix1.try_into().unwrap(),47		mode.clone()48	));4950	let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();51	let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();52	let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();53	assert_eq!(54		<pallet_common::CollectionById<Test>>::get(id)55			.unwrap()56			.owner,57		owner58	);59	assert_eq!(60		<pallet_common::CollectionById<Test>>::get(id).unwrap().name,61		saved_col_name62	);63	assert_eq!(64		<pallet_common::CollectionById<Test>>::get(id).unwrap().mode,65		*mode66	);67	assert_eq!(68		<pallet_common::CollectionById<Test>>::get(id)69			.unwrap()70			.description,71		saved_description72	);73	assert_eq!(74		<pallet_common::CollectionById<Test>>::get(id)75			.unwrap()76			.token_prefix,77		saved_prefix78	);79	id80}8182fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId {83	create_test_collection_for_owner(&mode, 1, id)84}8586fn create_test_item(collection_id: CollectionId, data: &CreateItemData) {87	let origin1 = Origin::signed(1);88	assert_ok!(TemplateModule::create_item(89		origin1,90		collection_id,91		account(1),92		data.clone()93	));94}9596fn account(sub: u64) -> TestCrossAccountId {97	TestCrossAccountId::from_sub(sub)98}99100// Use cases tests region101// #region102103#[test]104fn set_version_schema() {105	new_test_ext().execute_with(|| {106		let origin1 = Origin::signed(1);107		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));108109		assert_ok!(TemplateModule::set_schema_version(110			origin1,111			collection_id,112			SchemaVersion::Unique113		));114		assert_eq!(115			<pallet_common::CollectionById<Test>>::get(collection_id)116				.unwrap()117				.schema_version,118			SchemaVersion::Unique119		);120	});121}122123#[test]124fn create_fungible_collection_fails_with_large_decimal_numbers() {125	new_test_ext().execute_with(|| {126		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();127		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();128		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();129130		let origin1 = Origin::signed(1);131		assert_noop!(132			TemplateModule::create_collection(133				origin1,134				col_name1.try_into().unwrap(),135				col_desc1.try_into().unwrap(),136				token_prefix1.try_into().unwrap(),137				CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1)138			),139			Error::<Test>::CollectionDecimalPointLimitExceeded140		);141	});142}143144#[test]145fn create_nft_item() {146	new_test_ext().execute_with(|| {147		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));148149		let data = default_nft_data();150		create_test_item(collection_id, &data.clone().into());151152		let item = <pallet_nonfungible::TokenData<Test>>::get((collection_id, 1)).unwrap();153		assert_eq!(item.const_data, data.const_data.into_inner());154		assert_eq!(item.variable_data, data.variable_data.into_inner());155	});156}157158// Use cases tests region159// #region160#[test]161fn create_nft_multiple_items() {162	new_test_ext().execute_with(|| {163		create_test_collection(&CollectionMode::NFT, CollectionId(1));164165		let origin1 = Origin::signed(1);166167		let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()];168169		assert_ok!(TemplateModule::create_multiple_items(170			origin1,171			CollectionId(1),172			account(1),173			items_data174				.clone()175				.into_iter()176				.map(|d| { d.into() })177				.collect()178		));179		for (index, data) in items_data.into_iter().enumerate() {180			let item = <pallet_nonfungible::TokenData<Test>>::get((181				CollectionId(1),182				TokenId((index + 1) as u32),183			))184			.unwrap();185			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());186			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());187		}188	});189}190191#[test]192fn create_refungible_item() {193	new_test_ext().execute_with(|| {194		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));195196		let data = default_re_fungible_data();197		create_test_item(collection_id, &data.clone().into());198		let item = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));199		let balance =200			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1)));201		assert_eq!(item.const_data, data.const_data.into_inner());202		assert_eq!(item.variable_data, data.variable_data.into_inner());203		assert_eq!(balance, 1023);204	});205}206207#[test]208fn create_multiple_refungible_items() {209	new_test_ext().execute_with(|| {210		create_test_collection(&CollectionMode::ReFungible, CollectionId(1));211212		let origin1 = Origin::signed(1);213214		let items_data = vec![215			default_re_fungible_data(),216			default_re_fungible_data(),217			default_re_fungible_data(),218		];219220		assert_ok!(TemplateModule::create_multiple_items(221			origin1,222			CollectionId(1),223			account(1),224			items_data225				.clone()226				.into_iter()227				.map(|d| { d.into() })228				.collect()229		));230		for (index, data) in items_data.into_iter().enumerate() {231			let item = <pallet_refungible::TokenData<Test>>::get((232				CollectionId(1),233				TokenId((index + 1) as u32),234			));235			let balance =236				<pallet_refungible::Balance<Test>>::get((CollectionId(1), TokenId(1), account(1)));237			assert_eq!(item.const_data.to_vec(), data.const_data.into_inner());238			assert_eq!(item.variable_data.to_vec(), data.variable_data.into_inner());239			assert_eq!(balance, 1023);240		}241	});242}243244#[test]245fn create_fungible_item() {246	new_test_ext().execute_with(|| {247		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));248249		let data = default_fungible_data();250		create_test_item(collection_id, &data.into());251252		assert_eq!(253			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),254			5255		);256	});257}258259//#[test]260// fn create_multiple_fungible_items() {261//     new_test_ext().execute_with(|| {262//         default_limits();263264//         create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));265266//         let origin1 = Origin::signed(1);267268//         let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()];269270//         assert_ok!(TemplateModule::create_multiple_items(271//             origin1.clone(),272//             1,273//             1,274//             items_data.clone().into_iter().map(|d| { d.into() }).collect()275//         ));276277//         for (index, _) in items_data.iter().enumerate() {278//             assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as TokenId).value, 5);279//         }280//         assert_eq!(TemplateModule::balance_count(1, 1), 3000);281//         assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]);282//     });283// }284285#[test]286fn transfer_fungible_item() {287	new_test_ext().execute_with(|| {288		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));289290		let origin1 = Origin::signed(1);291		let origin2 = Origin::signed(2);292293		let data = default_fungible_data();294		create_test_item(collection_id, &data.into());295296		assert_eq!(297			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),298			5299		);300301		// change owner scenario302		assert_ok!(TemplateModule::transfer(303			origin1,304			account(2),305			CollectionId(1),306			TokenId(0),307			5308		));309		assert_eq!(310			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),311			0312		);313314		// split item scenario315		assert_ok!(TemplateModule::transfer(316			origin2.clone(),317			account(3),318			CollectionId(1),319			TokenId(0),320			3321		));322323		// split item and new owner has account scenario324		assert_ok!(TemplateModule::transfer(325			origin2,326			account(3),327			CollectionId(1),328			TokenId(0),329			1330		));331		assert_eq!(332			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),333			1334		);335		assert_eq!(336			<pallet_fungible::Balance<Test>>::get((CollectionId(1), account(3))),337			4338		);339	});340}341342#[test]343fn transfer_refungible_item() {344	new_test_ext().execute_with(|| {345		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));346347		// Create RFT 1 in 1023 pieces for account 1348		let data = default_re_fungible_data();349		create_test_item(collection_id, &data.clone().into());350		let item = <pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1)));351		assert_eq!(item.const_data, data.const_data.into_inner());352		assert_eq!(item.variable_data, data.variable_data.into_inner());353		assert_eq!(354			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),355			1356		);357		assert_eq!(358			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),359			1023360		);361		assert_eq!(362			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),363			true364		);365366		// Account 1 transfers all 1023 pieces of RFT 1 to account 2367		let origin1 = Origin::signed(1);368		let origin2 = Origin::signed(2);369		assert_ok!(TemplateModule::transfer(370			origin1,371			account(2),372			CollectionId(1),373			TokenId(1),374			1023375		));376		assert_eq!(377			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),378			1023379		);380		assert_eq!(381			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),382			0383		);384		assert_eq!(385			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),386			1387		);388		assert_eq!(389			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),390			false391		);392		assert_eq!(393			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),394			true395		);396397		// Account 2 transfers 500 pieces of RFT 1 to account 3398		assert_ok!(TemplateModule::transfer(399			origin2.clone(),400			account(3),401			CollectionId(1),402			TokenId(1),403			500404		));405		assert_eq!(406			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),407			523408		);409		assert_eq!(410			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),411			500412		);413		assert_eq!(414			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),415			1416		);417		assert_eq!(418			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),419			1420		);421		assert_eq!(422			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),423			true424		);425		assert_eq!(426			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),427			true428		);429430		// Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance431		assert_ok!(TemplateModule::transfer(432			origin2,433			account(3),434			CollectionId(1),435			TokenId(1),436			200437		));438		assert_eq!(439			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),440			323441		);442		assert_eq!(443			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),444			700445		);446		assert_eq!(447			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),448			1449		);450		assert_eq!(451			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),452			1453		);454		assert_eq!(455			<pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),456			true457		);458		assert_eq!(459			<pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),460			true461		);462	});463}464465#[test]466fn transfer_nft_item() {467	new_test_ext().execute_with(|| {468		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));469470		let data = default_nft_data();471		create_test_item(collection_id, &data.into());472		assert_eq!(473			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),474			1475		);476		assert_eq!(477			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),478			true479		);480481		let origin1 = Origin::signed(1);482		// default scenario483		assert_ok!(TemplateModule::transfer(484			origin1,485			account(2),486			CollectionId(1),487			TokenId(1),488			1489		));490		assert_eq!(491			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),492			0493		);494		assert_eq!(495			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),496			1497		);498		assert_eq!(499			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),500			false501		);502		assert_eq!(503			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),504			true505		);506	});507}508509#[test]510fn transfer_nft_item_wrong_value() {511	new_test_ext().execute_with(|| {512		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));513514		let data = default_nft_data();515		create_test_item(collection_id, &data.into());516		assert_eq!(517			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),518			1519		);520		assert_eq!(521			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),522			true523		);524525		let origin1 = Origin::signed(1);526527		assert_noop!(528			TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2)529				.map_err(|e| e.error),530			<pallet_nonfungible::Error::<Test>>::NonfungibleItemsHaveNoAmount531		);532	});533}534535#[test]536fn transfer_nft_item_zero_value() {537	new_test_ext().execute_with(|| {538		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));539540		let data = default_nft_data();541		create_test_item(collection_id, &data.into());542		assert_eq!(543			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),544			1545		);546		assert_eq!(547			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),548			true549		);550551		let origin1 = Origin::signed(1);552553		// Transferring 0 amount works on NFT...554		assert_ok!(TemplateModule::transfer(555			origin1,556			account(2),557			CollectionId(1),558			TokenId(1),559			0560		));561		// ... and results in no transfer562		assert_eq!(563			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),564			1565		);566		assert_eq!(567			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),568			true569		);570	});571}572573#[test]574fn nft_approve_and_transfer_from() {575	new_test_ext().execute_with(|| {576		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));577578		let data = default_nft_data();579		create_test_item(collection_id, &data.into());580581		let origin1 = Origin::signed(1);582		let origin2 = Origin::signed(2);583584		assert_eq!(585			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),586			1587		);588		assert_eq!(589			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),590			true591		);592593		// neg transfer_from594		assert_noop!(595			TemplateModule::transfer_from(596				origin2.clone(),597				account(1),598				account(2),599				CollectionId(1),600				TokenId(1),601				1602			)603			.map_err(|e| e.error),604			CommonError::<Test>::ApprovedValueTooLow605		);606607		// do approve608		assert_ok!(TemplateModule::approve(609			origin1,610			account(2),611			CollectionId(1),612			TokenId(1),613			1614		));615		assert_eq!(616			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),617			account(2)618		);619620		assert_ok!(TemplateModule::transfer_from(621			origin2,622			account(1),623			account(3),624			CollectionId(1),625			TokenId(1),626			1627		));628		assert!(629			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()630		);631	});632}633634#[test]635fn nft_approve_and_transfer_from_allow_list() {636	new_test_ext().execute_with(|| {637		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));638639		let origin1 = Origin::signed(1);640		let origin2 = Origin::signed(2);641642		// Create NFT 1 for account 1643		let data = default_nft_data();644		create_test_item(collection_id, &data.clone().into());645		assert_eq!(646			&<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))647				.unwrap()648				.const_data,649			&data.const_data.into_inner()650		);651		assert_eq!(652			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),653			1654		);655		assert_eq!(656			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),657			true658		);659660		// Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list661		assert_ok!(TemplateModule::set_mint_permission(662			origin1.clone(),663			CollectionId(1),664			true665		));666		assert_ok!(TemplateModule::set_public_access_mode(667			origin1.clone(),668			CollectionId(1),669			AccessMode::AllowList670		));671		assert_ok!(TemplateModule::add_to_allow_list(672			origin1.clone(),673			CollectionId(1),674			account(1)675		));676		assert_ok!(TemplateModule::add_to_allow_list(677			origin1.clone(),678			CollectionId(1),679			account(2)680		));681		assert_ok!(TemplateModule::add_to_allow_list(682			origin1.clone(),683			CollectionId(1),684			account(3)685		));686687		// Account 1 approves account 2 for NFT 1688		assert_ok!(TemplateModule::approve(689			origin1.clone(),690			account(2),691			CollectionId(1),692			TokenId(1),693			1694		));695		assert_eq!(696			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),697			account(2)698		);699700		// Account 2 transfers NFT 1 from account 1 to account 3701		assert_ok!(TemplateModule::transfer_from(702			origin2,703			account(1),704			account(3),705			CollectionId(1),706			TokenId(1),707			1708		));709		assert!(710			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()711		);712	});713}714715#[test]716fn refungible_approve_and_transfer_from() {717	new_test_ext().execute_with(|| {718		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));719720		let origin1 = Origin::signed(1);721		let origin2 = Origin::signed(2);722723		// Create RFT 1 in 1023 pieces for account 1724		let data = default_re_fungible_data();725		create_test_item(collection_id, &data.into());726727		assert_eq!(728			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),729			1730		);731		assert_eq!(732			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),733			1023734		);735		assert_eq!(736			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),737			true738		);739740		// Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list741		assert_ok!(TemplateModule::set_mint_permission(742			origin1.clone(),743			CollectionId(1),744			true745		));746		assert_ok!(TemplateModule::set_public_access_mode(747			origin1.clone(),748			CollectionId(1),749			AccessMode::AllowList750		));751		assert_ok!(TemplateModule::add_to_allow_list(752			origin1.clone(),753			CollectionId(1),754			account(1)755		));756		assert_ok!(TemplateModule::add_to_allow_list(757			origin1.clone(),758			CollectionId(1),759			account(2)760		));761		assert_ok!(TemplateModule::add_to_allow_list(762			origin1.clone(),763			CollectionId(1),764			account(3)765		));766767		// Account 1 approves account 2 for 1023 pieces of RFT 1768		assert_ok!(TemplateModule::approve(769			origin1,770			account(2),771			CollectionId(1),772			TokenId(1),773			1023774		));775		assert_eq!(776			<pallet_refungible::Allowance<Test>>::get((777				CollectionId(1),778				TokenId(1),779				account(1),780				account(2)781			)),782			1023783		);784785		// Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3786		assert_ok!(TemplateModule::transfer_from(787			origin2,788			account(1),789			account(3),790			CollectionId(1),791			TokenId(1),792			100793		));794		assert_eq!(795			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),796			1797		);798		assert_eq!(799			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),800			1801		);802		assert_eq!(803			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),804			923805		);806		assert_eq!(807			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),808			100809		);810		assert_eq!(811			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),812			true813		);814		assert_eq!(815			<pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),816			true817		);818		assert_eq!(819			<pallet_refungible::Allowance<Test>>::get((820				CollectionId(1),821				TokenId(1),822				account(1),823				account(2)824			)),825			923826		);827	});828}829830#[test]831fn fungible_approve_and_transfer_from() {832	new_test_ext().execute_with(|| {833		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));834835		let data = default_fungible_data();836		create_test_item(collection_id, &data.into());837838		let origin1 = Origin::signed(1);839		let origin2 = Origin::signed(2);840841		assert_ok!(TemplateModule::set_mint_permission(842			origin1.clone(),843			CollectionId(1),844			true845		));846		assert_ok!(TemplateModule::set_public_access_mode(847			origin1.clone(),848			CollectionId(1),849			AccessMode::AllowList850		));851		assert_ok!(TemplateModule::add_to_allow_list(852			origin1.clone(),853			CollectionId(1),854			account(1)855		));856		assert_ok!(TemplateModule::add_to_allow_list(857			origin1.clone(),858			CollectionId(1),859			account(2)860		));861		assert_ok!(TemplateModule::add_to_allow_list(862			origin1.clone(),863			CollectionId(1),864			account(3)865		));866867		// do approve868		assert_ok!(TemplateModule::approve(869			origin1.clone(),870			account(2),871			CollectionId(1),872			TokenId(0),873			5874		));875		assert_eq!(876			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),877			5878		);879		assert_ok!(TemplateModule::approve(880			origin1,881			account(3),882			CollectionId(1),883			TokenId(0),884			5885		));886		assert_eq!(887			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),888			5889		);890		assert_eq!(891			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(3))),892			5893		);894895		assert_ok!(TemplateModule::transfer_from(896			origin2.clone(),897			account(1),898			account(3),899			CollectionId(1),900			TokenId(0),901			4902		));903904		assert_eq!(905			<pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),906			1907		);908909		assert_noop!(910			TemplateModule::transfer_from(911				origin2,912				account(1),913				account(3),914				CollectionId(1),915				TokenId(0),916				4917			)918			.map_err(|e| e.error),919			CommonError::<Test>::ApprovedValueTooLow920		);921	});922}923924#[test]925fn change_collection_owner() {926	new_test_ext().execute_with(|| {927		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));928929		let origin1 = Origin::signed(1);930		assert_ok!(TemplateModule::change_collection_owner(931			origin1,932			collection_id,933			2934		));935		assert_eq!(936			<pallet_common::CollectionById<Test>>::get(collection_id)937				.unwrap()938				.owner,939			2940		);941	});942}943944#[test]945fn destroy_collection() {946	new_test_ext().execute_with(|| {947		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));948949		let origin1 = Origin::signed(1);950		assert_ok!(TemplateModule::destroy_collection(origin1, collection_id));951	});952}953954#[test]955fn burn_nft_item() {956	new_test_ext().execute_with(|| {957		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));958959		let origin1 = Origin::signed(1);960961		let data = default_nft_data();962		create_test_item(collection_id, &data.into());963964		// check balance (collection with id = 1, user id = 1)965		assert_eq!(966			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),967			1968		);969970		// burn item971		assert_ok!(TemplateModule::burn_item(972			origin1.clone(),973			collection_id,974			TokenId(1),975			1976		));977		assert_eq!(978			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),979			0980		);981	});982}983984#[test]985fn burn_same_nft_item_twice() {986	new_test_ext().execute_with(|| {987		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));988989		let origin1 = Origin::signed(1);990991		let data = default_nft_data();992		create_test_item(collection_id, &data.into());993994		// check balance (collection with id = 1, user id = 1)995		assert_eq!(996			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),997			1998		);9991000		// burn item1001		assert_ok!(TemplateModule::burn_item(1002			origin1.clone(),1003			collection_id,1004			TokenId(1),1005			11006		));10071008		// burn item again1009		assert_noop!(1010			TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error),1011			CommonError::<Test>::TokenNotFound1012		);10131014		assert_eq!(1015			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1016			01017		);1018	});1019}10201021#[test]1022fn burn_fungible_item() {1023	new_test_ext().execute_with(|| {1024		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));10251026		let origin1 = Origin::signed(1);1027		assert_ok!(TemplateModule::add_collection_admin(1028			origin1.clone(),1029			collection_id,1030			account(2)1031		));10321033		let data = default_fungible_data();1034		create_test_item(collection_id, &data.into());10351036		// check balance (collection with id = 1, user id = 1)1037		assert_eq!(1038			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1039			51040		);10411042		// burn item1043		assert_ok!(TemplateModule::burn_item(1044			origin1.clone(),1045			CollectionId(1),1046			TokenId(0),1047			51048		));1049		assert_noop!(1050			TemplateModule::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error),1051			CommonError::<Test>::TokenValueTooLow1052		);10531054		assert_eq!(1055			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1056			01057		);1058	});1059}10601061#[test]1062fn burn_fungible_item_with_token_id() {1063	new_test_ext().execute_with(|| {1064		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));10651066		let origin1 = Origin::signed(1);1067		assert_ok!(TemplateModule::add_collection_admin(1068			origin1.clone(),1069			collection_id,1070			account(2)1071		));10721073		let data = default_fungible_data();1074		create_test_item(collection_id, &data.into());10751076		// check balance (collection with id = 1, user id = 1)1077		assert_eq!(1078			<pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1079			51080		);10811082		// Try to burn item using Token ID1083		assert_noop!(1084			TemplateModule::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error),1085			<pallet_fungible::Error::<Test>>::FungibleItemsHaveNoId1086		);1087	});1088}1089#[test]1090fn burn_refungible_item() {1091	new_test_ext().execute_with(|| {1092		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));1093		let origin1 = Origin::signed(1);10941095		assert_ok!(TemplateModule::set_mint_permission(1096			origin1.clone(),1097			collection_id,1098			true1099		));1100		assert_ok!(TemplateModule::set_public_access_mode(1101			origin1.clone(),1102			collection_id,1103			AccessMode::AllowList1104		));1105		assert_ok!(TemplateModule::add_to_allow_list(1106			origin1.clone(),1107			collection_id,1108			account(1)1109		));11101111		assert_ok!(TemplateModule::add_collection_admin(1112			origin1.clone(),1113			collection_id,1114			account(2)1115		));11161117		let data = default_re_fungible_data();1118		create_test_item(collection_id, &data.into());11191120		// check balance (collection with id = 1, user id = 2)1121		assert_eq!(1122			<pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),1123			11124		);1125		assert_eq!(1126			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1127			10231128		);11291130		// burn item1131		assert_ok!(TemplateModule::burn_item(1132			origin1.clone(),1133			collection_id,1134			TokenId(1),1135			10231136		));1137		assert_noop!(1138			TemplateModule::burn_item(origin1, collection_id, TokenId(1), 1023)1139				.map_err(|e| e.error),1140			CommonError::<Test>::TokenValueTooLow1141		);11421143		assert_eq!(1144			<pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1145			01146		);1147	});1148}11491150#[test]1151fn add_collection_admin() {1152	new_test_ext().execute_with(|| {1153		let collection1_id =1154			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1155		let origin1 = Origin::signed(1);11561157		// Add collection admins1158		assert_ok!(TemplateModule::add_collection_admin(1159			origin1.clone(),1160			collection1_id,1161			account(2)1162		));1163		assert_ok!(TemplateModule::add_collection_admin(1164			origin1,1165			collection1_id,1166			account(3)1167		));11681169		// Owner is not an admin by default1170		assert_eq!(1171			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),1172			false1173		);1174		assert!(<pallet_common::IsAdmin<Test>>::get((1175			CollectionId(1),1176			account(2)1177		)));1178		assert!(<pallet_common::IsAdmin<Test>>::get((1179			CollectionId(1),1180			account(3)1181		)));1182	});1183}11841185#[test]1186fn remove_collection_admin() {1187	new_test_ext().execute_with(|| {1188		let collection1_id =1189			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1190		let origin1 = Origin::signed(1);1191		let origin2 = Origin::signed(2);11921193		// Add collection admins 2 and 31194		assert_ok!(TemplateModule::add_collection_admin(1195			origin1.clone(),1196			collection1_id,1197			account(2)1198		));1199		assert_ok!(TemplateModule::add_collection_admin(1200			origin1,1201			collection1_id,1202			account(3)1203		));12041205		assert!(<pallet_common::IsAdmin<Test>>::get((1206			CollectionId(1),1207			account(2)1208		)));1209		assert!(<pallet_common::IsAdmin<Test>>::get((1210			CollectionId(1),1211			account(3)1212		)));12131214		// remove admin 31215		assert_ok!(TemplateModule::remove_collection_admin(1216			origin2,1217			CollectionId(1),1218			account(3)1219		));12201221		// 2 is still admin, 3 is not an admin anymore1222		assert!(<pallet_common::IsAdmin<Test>>::get((1223			CollectionId(1),1224			account(2)1225		)));1226		assert_eq!(1227			<pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(3))),1228			false1229		);1230	});1231}12321233#[test]1234fn balance_of() {1235	new_test_ext().execute_with(|| {1236		let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1237		let fungible_collection_id =1238			create_test_collection(&CollectionMode::Fungible(3), CollectionId(2));1239		let re_fungible_collection_id =1240			create_test_collection(&CollectionMode::ReFungible, CollectionId(3));12411242		// check balance before1243		assert_eq!(1244			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1245			01246		);1247		assert_eq!(1248			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1249			01250		);1251		assert_eq!(1252			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1253			01254		);12551256		let nft_data = default_nft_data();1257		create_test_item(nft_collection_id, &nft_data.into());12581259		let fungible_data = default_fungible_data();1260		create_test_item(fungible_collection_id, &fungible_data.into());12611262		let re_fungible_data = default_re_fungible_data();1263		create_test_item(re_fungible_collection_id, &re_fungible_data.into());12641265		// check balance (collection with id = 1, user id = 1)1266		assert_eq!(1267			<pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1268			11269		);1270		assert_eq!(1271			<pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1272			51273		);1274		assert_eq!(1275			<pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1276			11277		);12781279		assert_eq!(1280			<pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),1281			true1282		);1283		assert_eq!(1284			<pallet_refungible::Owned<Test>>::get((1285				re_fungible_collection_id,1286				account(1),1287				TokenId(1)1288			)),1289			true1290		);1291	});1292}12931294#[test]1295fn approve() {1296	new_test_ext().execute_with(|| {1297		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));12981299		let data = default_nft_data();1300		create_test_item(collection_id, &data.into());13011302		let origin1 = Origin::signed(1);13031304		// approve1305		assert_ok!(TemplateModule::approve(1306			origin1,1307			account(2),1308			CollectionId(1),1309			TokenId(1),1310			11311		));1312		assert_eq!(1313			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1314			account(2)1315		);1316	});1317}13181319#[test]1320fn transfer_from() {1321	new_test_ext().execute_with(|| {1322		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1323		let origin1 = Origin::signed(1);1324		let origin2 = Origin::signed(2);13251326		let data = default_nft_data();1327		create_test_item(collection_id, &data.into());13281329		// approve1330		assert_ok!(TemplateModule::approve(1331			origin1.clone(),1332			account(2),1333			CollectionId(1),1334			TokenId(1),1335			11336		));1337		assert_eq!(1338			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1339			account(2)1340		);13411342		assert_ok!(TemplateModule::set_mint_permission(1343			origin1.clone(),1344			CollectionId(1),1345			true1346		));1347		assert_ok!(TemplateModule::set_public_access_mode(1348			origin1.clone(),1349			CollectionId(1),1350			AccessMode::AllowList1351		));1352		assert_ok!(TemplateModule::add_to_allow_list(1353			origin1.clone(),1354			CollectionId(1),1355			account(1)1356		));1357		assert_ok!(TemplateModule::add_to_allow_list(1358			origin1.clone(),1359			CollectionId(1),1360			account(2)1361		));1362		assert_ok!(TemplateModule::add_to_allow_list(1363			origin1,1364			CollectionId(1),1365			account(3)1366		));13671368		assert_ok!(TemplateModule::transfer_from(1369			origin2,1370			account(1),1371			account(2),1372			CollectionId(1),1373			TokenId(1),1374			11375		));13761377		// after transfer1378		assert_eq!(1379			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),1380			01381		);1382		assert_eq!(1383			<pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(2))),1384			11385		);1386	});1387}13881389// #endregion13901391// Coverage tests region1392// #region13931394#[test]1395fn owner_can_add_address_to_allow_list() {1396	new_test_ext().execute_with(|| {1397		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));13981399		let origin1 = Origin::signed(1);1400		assert_ok!(TemplateModule::add_to_allow_list(1401			origin1,1402			collection_id,1403			account(2)1404		));1405		assert!(<pallet_common::Allowlist<Test>>::get((1406			collection_id,1407			account(2)1408		)));1409	});1410}14111412#[test]1413fn admin_can_add_address_to_allow_list() {1414	new_test_ext().execute_with(|| {1415		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1416		let origin1 = Origin::signed(1);1417		let origin2 = Origin::signed(2);14181419		assert_ok!(TemplateModule::add_collection_admin(1420			origin1,1421			collection_id,1422			account(2)1423		));1424		assert_ok!(TemplateModule::add_to_allow_list(1425			origin2,1426			collection_id,1427			account(3)1428		));1429		assert!(<pallet_common::Allowlist<Test>>::get((1430			collection_id,1431			account(3)1432		)));1433	});1434}14351436#[test]1437fn nonprivileged_user_cannot_add_address_to_allow_list() {1438	new_test_ext().execute_with(|| {1439		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14401441		let origin2 = Origin::signed(2);1442		assert_noop!(1443			TemplateModule::add_to_allow_list(origin2, collection_id, account(3)),1444			CommonError::<Test>::NoPermission1445		);1446	});1447}14481449#[test]1450fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() {1451	new_test_ext().execute_with(|| {1452		let origin1 = Origin::signed(1);14531454		assert_noop!(1455			TemplateModule::add_to_allow_list(origin1, CollectionId(1), account(2)),1456			CommonError::<Test>::CollectionNotFound1457		);1458	});1459}14601461#[test]1462fn nobody_can_add_address_to_allow_list_of_deleted_collection() {1463	new_test_ext().execute_with(|| {1464		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14651466		let origin1 = Origin::signed(1);1467		assert_ok!(TemplateModule::destroy_collection(1468			origin1.clone(),1469			collection_id1470		));1471		assert_noop!(1472			TemplateModule::add_to_allow_list(origin1, collection_id, account(2)),1473			CommonError::<Test>::CollectionNotFound1474		);1475	});1476}14771478// If address is already added to allow list, nothing happens1479#[test]1480fn address_is_already_added_to_allow_list() {1481	new_test_ext().execute_with(|| {1482		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1483		let origin1 = Origin::signed(1);14841485		assert_ok!(TemplateModule::add_to_allow_list(1486			origin1.clone(),1487			collection_id,1488			account(2)1489		));1490		assert_ok!(TemplateModule::add_to_allow_list(1491			origin1,1492			collection_id,1493			account(2)1494		));1495		assert!(<pallet_common::Allowlist<Test>>::get((1496			collection_id,1497			account(2)1498		)));1499	});1500}15011502#[test]1503fn owner_can_remove_address_from_allow_list() {1504	new_test_ext().execute_with(|| {1505		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15061507		let origin1 = Origin::signed(1);1508		assert_ok!(TemplateModule::add_to_allow_list(1509			origin1.clone(),1510			collection_id,1511			account(2)1512		));1513		assert_ok!(TemplateModule::remove_from_allow_list(1514			origin1,1515			collection_id,1516			account(2)1517		));1518		assert_eq!(1519			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1520			false1521		);1522	});1523}15241525#[test]1526fn admin_can_remove_address_from_allow_list() {1527	new_test_ext().execute_with(|| {1528		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1529		let origin1 = Origin::signed(1);1530		let origin2 = Origin::signed(2);15311532		// Owner adds admin1533		assert_ok!(TemplateModule::add_collection_admin(1534			origin1.clone(),1535			collection_id,1536			account(2)1537		));15381539		// Owner adds address 3 to allow list1540		assert_ok!(TemplateModule::add_to_allow_list(1541			origin1,1542			collection_id,1543			account(3)1544		));15451546		// Admin removes address 3 from allow list1547		assert_ok!(TemplateModule::remove_from_allow_list(1548			origin2,1549			collection_id,1550			account(3)1551		));1552		assert_eq!(1553			<pallet_common::Allowlist<Test>>::get((collection_id, account(3))),1554			false1555		);1556	});1557}15581559#[test]1560fn nonprivileged_user_cannot_remove_address_from_allow_list() {1561	new_test_ext().execute_with(|| {1562		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1563		let origin1 = Origin::signed(1);1564		let origin2 = Origin::signed(2);15651566		assert_ok!(TemplateModule::add_to_allow_list(1567			origin1,1568			collection_id,1569			account(2)1570		));1571		assert_noop!(1572			TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)),1573			CommonError::<Test>::NoPermission1574		);1575		assert!(<pallet_common::Allowlist<Test>>::get((1576			collection_id,1577			account(2)1578		)));1579	});1580}15811582#[test]1583fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() {1584	new_test_ext().execute_with(|| {1585		let origin1 = Origin::signed(1);15861587		assert_noop!(1588			TemplateModule::remove_from_allow_list(origin1, CollectionId(1), account(2)),1589			CommonError::<Test>::CollectionNotFound1590		);1591	});1592}15931594#[test]1595fn nobody_can_remove_address_from_allow_list_of_deleted_collection() {1596	new_test_ext().execute_with(|| {1597		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1598		let origin1 = Origin::signed(1);1599		let origin2 = Origin::signed(2);16001601		// Add account 2 to allow list1602		assert_ok!(TemplateModule::add_to_allow_list(1603			origin1.clone(),1604			collection_id,1605			account(2)1606		));16071608		// Account 2 is in collection allow-list1609		assert!(<pallet_common::Allowlist<Test>>::get((1610			collection_id,1611			account(2)1612		)));16131614		// Destroy collection1615		assert_ok!(TemplateModule::destroy_collection(origin1, collection_id));16161617		// Attempt to remove account 2 from collection allow-list => error1618		assert_noop!(1619			TemplateModule::remove_from_allow_list(origin2, collection_id, account(2)),1620			CommonError::<Test>::CollectionNotFound1621		);16221623		// Account 2 is not found in collection allow-list anyway1624		assert_eq!(1625			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1626			false1627		);1628	});1629}16301631// If address is already removed from allow list, nothing happens1632#[test]1633fn address_is_already_removed_from_allow_list() {1634	new_test_ext().execute_with(|| {1635		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1636		let origin1 = Origin::signed(1);16371638		assert_ok!(TemplateModule::add_to_allow_list(1639			origin1.clone(),1640			collection_id,1641			account(2)1642		));1643		assert_ok!(TemplateModule::remove_from_allow_list(1644			origin1.clone(),1645			collection_id,1646			account(2)1647		));1648		assert_eq!(1649			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1650			false1651		);1652		assert_ok!(TemplateModule::remove_from_allow_list(1653			origin1,1654			collection_id,1655			account(2)1656		));1657		assert_eq!(1658			<pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1659			false1660		);1661	});1662}16631664// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests)1665#[test]1666fn allow_list_test_1() {1667	new_test_ext().execute_with(|| {1668		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));16691670		let origin1 = Origin::signed(1);16711672		let data = default_nft_data();1673		create_test_item(collection_id, &data.into());16741675		assert_ok!(TemplateModule::set_public_access_mode(1676			origin1.clone(),1677			collection_id,1678			AccessMode::AllowList1679		));1680		assert_ok!(TemplateModule::add_to_allow_list(1681			origin1.clone(),1682			collection_id,1683			account(2)1684		));16851686		assert_noop!(1687			TemplateModule::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1)1688				.map_err(|e| e.error),1689			CommonError::<Test>::AddressNotInAllowlist1690		);1691	});1692}16931694#[test]1695fn allow_list_test_2() {1696	new_test_ext().execute_with(|| {1697		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1698		let origin1 = Origin::signed(1);16991700		let data = default_nft_data();1701		create_test_item(collection_id, &data.into());17021703		assert_ok!(TemplateModule::set_public_access_mode(1704			origin1.clone(),1705			collection_id,1706			AccessMode::AllowList1707		));1708		assert_ok!(TemplateModule::add_to_allow_list(1709			origin1.clone(),1710			collection_id,1711			account(1)1712		));1713		assert_ok!(TemplateModule::add_to_allow_list(1714			origin1.clone(),1715			collection_id,1716			account(2)1717		));17181719		// do approve1720		assert_ok!(TemplateModule::approve(1721			origin1.clone(),1722			account(1),1723			collection_id,1724			TokenId(1),1725			11726		));1727		assert_eq!(1728			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1729			account(1)1730		);17311732		assert_ok!(TemplateModule::remove_from_allow_list(1733			origin1.clone(),1734			collection_id,1735			account(1)1736		));17371738		assert_noop!(1739			TemplateModule::transfer_from(1740				origin1,1741				account(1),1742				account(3),1743				CollectionId(1),1744				TokenId(1),1745				11746			)1747			.map_err(|e| e.error),1748			CommonError::<Test>::AddressNotInAllowlist1749		);1750	});1751}17521753// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests)1754#[test]1755fn allow_list_test_3() {1756	new_test_ext().execute_with(|| {1757		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17581759		let origin1 = Origin::signed(1);17601761		let data = default_nft_data();1762		create_test_item(collection_id, &data.into());17631764		assert_ok!(TemplateModule::set_public_access_mode(1765			origin1.clone(),1766			collection_id,1767			AccessMode::AllowList1768		));1769		assert_ok!(TemplateModule::add_to_allow_list(1770			origin1.clone(),1771			collection_id,1772			account(1)1773		));17741775		assert_noop!(1776			TemplateModule::transfer(origin1, account(3), collection_id, TokenId(1), 1)1777				.map_err(|e| e.error),1778			CommonError::<Test>::AddressNotInAllowlist1779		);1780	});1781}17821783#[test]1784fn allow_list_test_4() {1785	new_test_ext().execute_with(|| {1786		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17871788		let origin1 = Origin::signed(1);17891790		let data = default_nft_data();1791		create_test_item(collection_id, &data.into());17921793		assert_ok!(TemplateModule::set_public_access_mode(1794			origin1.clone(),1795			collection_id,1796			AccessMode::AllowList1797		));1798		assert_ok!(TemplateModule::add_to_allow_list(1799			origin1.clone(),1800			collection_id,1801			account(1)1802		));1803		assert_ok!(TemplateModule::add_to_allow_list(1804			origin1.clone(),1805			collection_id,1806			account(2)1807		));18081809		// do approve1810		assert_ok!(TemplateModule::approve(1811			origin1.clone(),1812			account(1),1813			collection_id,1814			TokenId(1),1815			11816		));1817		assert_eq!(1818			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1819			account(1)1820		);18211822		assert_ok!(TemplateModule::remove_from_allow_list(1823			origin1.clone(),1824			collection_id,1825			account(2)1826		));18271828		assert_noop!(1829			TemplateModule::transfer_from(1830				origin1,1831				account(1),1832				account(3),1833				collection_id,1834				TokenId(1),1835				11836			)1837			.map_err(|e| e.error),1838			CommonError::<Test>::AddressNotInAllowlist1839		);1840	});1841}18421843// 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)1844#[test]1845fn allow_list_test_5() {1846	new_test_ext().execute_with(|| {1847		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18481849		let origin1 = Origin::signed(1);18501851		let data = default_nft_data();1852		create_test_item(collection_id, &data.into());18531854		assert_ok!(TemplateModule::set_public_access_mode(1855			origin1.clone(),1856			collection_id,1857			AccessMode::AllowList1858		));1859		assert_noop!(1860			TemplateModule::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1)1861				.map_err(|e| e.error),1862			CommonError::<Test>::AddressNotInAllowlist1863		);1864	});1865}18661867// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method).1868#[test]1869fn allow_list_test_6() {1870	new_test_ext().execute_with(|| {1871		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18721873		let origin1 = Origin::signed(1);18741875		let data = default_nft_data();1876		create_test_item(collection_id, &data.into());18771878		assert_ok!(TemplateModule::set_public_access_mode(1879			origin1.clone(),1880			collection_id,1881			AccessMode::AllowList1882		));18831884		// do approve1885		assert_noop!(1886			TemplateModule::approve(origin1, account(1), CollectionId(1), TokenId(1), 1)1887				.map_err(|e| e.error),1888			CommonError::<Test>::AddressNotInAllowlist1889		);1890	});1891}18921893// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and1894//          tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests)1895#[test]1896fn allow_list_test_7() {1897	new_test_ext().execute_with(|| {1898		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18991900		let data = default_nft_data();1901		create_test_item(collection_id, &data.into());19021903		let origin1 = Origin::signed(1);19041905		assert_ok!(TemplateModule::set_public_access_mode(1906			origin1.clone(),1907			collection_id,1908			AccessMode::AllowList1909		));1910		assert_ok!(TemplateModule::add_to_allow_list(1911			origin1.clone(),1912			collection_id,1913			account(1)1914		));1915		assert_ok!(TemplateModule::add_to_allow_list(1916			origin1.clone(),1917			collection_id,1918			account(2)1919		));19201921		assert_ok!(TemplateModule::transfer(1922			origin1,1923			account(2),1924			CollectionId(1),1925			TokenId(1),1926			11927		));1928	});1929}19301931#[test]1932fn allow_list_test_8() {1933	new_test_ext().execute_with(|| {1934		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19351936		// Create NFT for account 11937		let data = default_nft_data();1938		create_test_item(collection_id, &data.into());19391940		let origin1 = Origin::signed(1);19411942		// Toggle Allow List mode and add accounts 1 and 21943		assert_ok!(TemplateModule::set_public_access_mode(1944			origin1.clone(),1945			collection_id,1946			AccessMode::AllowList1947		));1948		assert_ok!(TemplateModule::add_to_allow_list(1949			origin1.clone(),1950			collection_id,1951			account(1)1952		));1953		assert_ok!(TemplateModule::add_to_allow_list(1954			origin1.clone(),1955			collection_id,1956			account(2)1957		));19581959		// Sself-approve account 1 for NFT 11960		assert_ok!(TemplateModule::approve(1961			origin1.clone(),1962			account(1),1963			CollectionId(1),1964			TokenId(1),1965			11966		));1967		assert_eq!(1968			<pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1969			account(1)1970		);19711972		// Transfer from 1 to 21973		assert_ok!(TemplateModule::transfer_from(1974			origin1,1975			account(1),1976			account(2),1977			CollectionId(1),1978			TokenId(1),1979			11980		));1981	});1982}19831984// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner.1985#[test]1986fn allow_list_test_9() {1987	new_test_ext().execute_with(|| {1988		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1989		let origin1 = Origin::signed(1);19901991		assert_ok!(TemplateModule::set_public_access_mode(1992			origin1.clone(),1993			collection_id,1994			AccessMode::AllowList1995		));1996		assert_ok!(TemplateModule::set_mint_permission(1997			origin1,1998			collection_id,1999			false2000		));20012002		let data = default_nft_data();2003		create_test_item(collection_id, &data.into());2004	});2005}20062007// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin.2008#[test]2009fn allow_list_test_10() {2010	new_test_ext().execute_with(|| {2011		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20122013		let origin1 = Origin::signed(1);2014		let origin2 = Origin::signed(2);20152016		assert_ok!(TemplateModule::set_public_access_mode(2017			origin1.clone(),2018			collection_id,2019			AccessMode::AllowList2020		));2021		assert_ok!(TemplateModule::set_mint_permission(2022			origin1.clone(),2023			collection_id,2024			false2025		));20262027		assert_ok!(TemplateModule::add_collection_admin(2028			origin1,2029			collection_id,2030			account(2)2031		));20322033		assert_ok!(TemplateModule::create_item(2034			origin2,2035			collection_id,2036			account(2),2037			default_nft_data().into()2038		));2039	});2040}20412042// 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.2043#[test]2044fn allow_list_test_11() {2045	new_test_ext().execute_with(|| {2046		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20472048		let origin1 = Origin::signed(1);2049		let origin2 = Origin::signed(2);20502051		assert_ok!(TemplateModule::set_public_access_mode(2052			origin1.clone(),2053			collection_id,2054			AccessMode::AllowList2055		));2056		assert_ok!(TemplateModule::set_mint_permission(2057			origin1.clone(),2058			collection_id,2059			false2060		));2061		assert_ok!(TemplateModule::add_to_allow_list(2062			origin1,2063			collection_id,2064			account(2)2065		));20662067		assert_noop!(2068			TemplateModule::create_item(2069				origin2,2070				CollectionId(1),2071				account(2),2072				default_nft_data().into()2073			)2074			.map_err(|e| e.error),2075			CommonError::<Test>::PublicMintingNotAllowed2076		);2077	});2078}20792080// 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.2081#[test]2082fn allow_list_test_12() {2083	new_test_ext().execute_with(|| {2084		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20852086		let origin1 = Origin::signed(1);2087		let origin2 = Origin::signed(2);20882089		assert_ok!(TemplateModule::set_public_access_mode(2090			origin1.clone(),2091			collection_id,2092			AccessMode::AllowList2093		));2094		assert_ok!(TemplateModule::set_mint_permission(2095			origin1,2096			collection_id,2097			false2098		));20992100		assert_noop!(2101			TemplateModule::create_item(2102				origin2,2103				CollectionId(1),2104				account(2),2105				default_nft_data().into()2106			)2107			.map_err(|e| e.error),2108			CommonError::<Test>::PublicMintingNotAllowed2109		);2110	});2111}21122113// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner.2114#[test]2115fn allow_list_test_13() {2116	new_test_ext().execute_with(|| {2117		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21182119		let origin1 = Origin::signed(1);21202121		assert_ok!(TemplateModule::set_public_access_mode(2122			origin1.clone(),2123			collection_id,2124			AccessMode::AllowList2125		));2126		assert_ok!(TemplateModule::set_mint_permission(2127			origin1,2128			collection_id,2129			true2130		));21312132		let data = default_nft_data();2133		create_test_item(collection_id, &data.into());2134	});2135}21362137// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin.2138#[test]2139fn allow_list_test_14() {2140	new_test_ext().execute_with(|| {2141		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21422143		let origin1 = Origin::signed(1);2144		let origin2 = Origin::signed(2);21452146		assert_ok!(TemplateModule::set_public_access_mode(2147			origin1.clone(),2148			collection_id,2149			AccessMode::AllowList2150		));2151		assert_ok!(TemplateModule::set_mint_permission(2152			origin1.clone(),2153			collection_id,2154			true2155		));21562157		assert_ok!(TemplateModule::add_collection_admin(2158			origin1,2159			collection_id,2160			account(2)2161		));21622163		assert_ok!(TemplateModule::create_item(2164			origin2,2165			collection_id,2166			account(2),2167			default_nft_data().into()2168		));2169	});2170}21712172// 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.2173#[test]2174fn allow_list_test_15() {2175	new_test_ext().execute_with(|| {2176		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21772178		let origin1 = Origin::signed(1);2179		let origin2 = Origin::signed(2);21802181		assert_ok!(TemplateModule::set_public_access_mode(2182			origin1.clone(),2183			collection_id,2184			AccessMode::AllowList2185		));2186		assert_ok!(TemplateModule::set_mint_permission(2187			origin1,2188			collection_id,2189			true2190		));21912192		assert_noop!(2193			TemplateModule::create_item(2194				origin2,2195				collection_id,2196				account(2),2197				default_nft_data().into()2198			)2199			.map_err(|e| e.error),2200			CommonError::<Test>::AddressNotInAllowlist2201		);2202	});2203}22042205// 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.2206#[test]2207fn allow_list_test_16() {2208	new_test_ext().execute_with(|| {2209		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22102211		let origin1 = Origin::signed(1);2212		let origin2 = Origin::signed(2);22132214		assert_ok!(TemplateModule::set_public_access_mode(2215			origin1.clone(),2216			collection_id,2217			AccessMode::AllowList2218		));2219		assert_ok!(TemplateModule::set_mint_permission(2220			origin1.clone(),2221			collection_id,2222			true2223		));2224		assert_ok!(TemplateModule::add_to_allow_list(2225			origin1,2226			collection_id,2227			account(2)2228		));22292230		assert_ok!(TemplateModule::create_item(2231			origin2,2232			collection_id,2233			account(2),2234			default_nft_data().into()2235		));2236	});2237}22382239// Total number of collections. Positive test2240#[test]2241fn total_number_collections_bound() {2242	new_test_ext().execute_with(|| {2243		create_test_collection(&CollectionMode::NFT, CollectionId(1));2244	});2245}22462247#[test]2248fn create_max_collections() {2249	new_test_ext().execute_with(|| {2250		for i in 1..COLLECTION_NUMBER_LIMIT {2251			create_test_collection(&CollectionMode::NFT, CollectionId(i));2252		}2253	});2254}22552256// Total number of collections. Negative test2257#[test]2258fn total_number_collections_bound_neg() {2259	new_test_ext().execute_with(|| {2260		let origin1 = Origin::signed(1);22612262		for i in 1..=COLLECTION_NUMBER_LIMIT {2263			create_test_collection(&CollectionMode::NFT, CollectionId(i));2264		}22652266		let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();2267		let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();2268		let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();22692270		// 11-th collection in chain. Expects error2271		assert_noop!(2272			TemplateModule::create_collection(2273				origin1,2274				col_name1.try_into().unwrap(),2275				col_desc1.try_into().unwrap(),2276				token_prefix1.try_into().unwrap(),2277				CollectionMode::NFT2278			),2279			CommonError::<Test>::TotalCollectionsLimitExceeded2280		);2281	});2282}22832284// Owned tokens by a single address. Positive test2285#[test]2286fn owned_tokens_bound() {2287	new_test_ext().execute_with(|| {2288		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22892290		let data = default_nft_data();2291		create_test_item(collection_id, &data.clone().into());2292		create_test_item(collection_id, &data.into());2293	});2294}22952296// Owned tokens by a single address. Negotive test2297#[test]2298fn owned_tokens_bound_neg() {2299	new_test_ext().execute_with(|| {2300		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23012302		let origin1 = Origin::signed(1);23032304		for _ in 1..=MAX_TOKEN_OWNERSHIP {2305			let data = default_nft_data();2306			create_test_item(collection_id, &data.clone().into());2307		}23082309		let data = default_nft_data();2310		assert_noop!(2311			TemplateModule::create_item(origin1, CollectionId(1), account(1), data.into())2312				.map_err(|e| e.error),2313			CommonError::<Test>::AccountTokenLimitExceeded2314		);2315	});2316}23172318// Number of collection admins. Positive test2319#[test]2320fn collection_admins_bound() {2321	new_test_ext().execute_with(|| {2322		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23232324		let origin1 = Origin::signed(1);23252326		assert_ok!(TemplateModule::add_collection_admin(2327			origin1.clone(),2328			collection_id,2329			account(2)2330		));2331		assert_ok!(TemplateModule::add_collection_admin(2332			origin1,2333			collection_id,2334			account(3)2335		));2336	});2337}23382339// Number of collection admins. Negotive test2340#[test]2341fn collection_admins_bound_neg() {2342	new_test_ext().execute_with(|| {2343		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23442345		let origin1 = Origin::signed(1);23462347		for i in 0..COLLECTION_ADMINS_LIMIT {2348			assert_ok!(TemplateModule::add_collection_admin(2349				origin1.clone(),2350				collection_id,2351				account((2 + i).into())2352			));2353		}2354		assert_noop!(2355			TemplateModule::add_collection_admin(2356				origin1,2357				collection_id,2358				account((3 + COLLECTION_ADMINS_LIMIT).into())2359			),2360			CommonError::<Test>::CollectionAdminCountExceeded2361		);2362	});2363}2364// #endregion23652366#[test]2367fn set_const_on_chain_schema() {2368	new_test_ext().execute_with(|| {2369		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23702371		let origin1 = Origin::signed(1);2372		assert_ok!(TemplateModule::set_const_on_chain_schema(2373			origin1,2374			collection_id,2375			b"test const on chain schema".to_vec().try_into().unwrap()2376		));23772378		assert_eq!(2379			<pallet_common::CollectionById<Test>>::get(collection_id)2380				.unwrap()2381				.const_on_chain_schema,2382			b"test const on chain schema".to_vec()2383		);2384		assert_eq!(2385			<pallet_common::CollectionById<Test>>::get(collection_id)2386				.unwrap()2387				.variable_on_chain_schema,2388			b"".to_vec()2389		);2390	});2391}23922393#[test]2394fn set_variable_on_chain_schema() {2395	new_test_ext().execute_with(|| {2396		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23972398		let origin1 = Origin::signed(1);2399		assert_ok!(TemplateModule::set_variable_on_chain_schema(2400			origin1,2401			collection_id,2402			b"test variable on chain schema"2403				.to_vec()2404				.try_into()2405				.unwrap()2406		));24072408		assert_eq!(2409			<pallet_common::CollectionById<Test>>::get(collection_id)2410				.unwrap()2411				.const_on_chain_schema,2412			b"".to_vec()2413		);2414		assert_eq!(2415			<pallet_common::CollectionById<Test>>::get(collection_id)2416				.unwrap()2417				.variable_on_chain_schema,2418			b"test variable on chain schema".to_vec()2419		);2420	});2421}24222423#[test]2424fn set_variable_meta_data_on_nft_token_stores_variable_meta_data() {2425	new_test_ext().execute_with(|| {2426		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24272428		let origin1 = Origin::signed(1);24292430		let data = default_nft_data();2431		create_test_item(CollectionId(1), &data.into());24322433		let variable_data = b"test data".to_vec();2434		assert_ok!(TemplateModule::set_variable_meta_data(2435			origin1,2436			collection_id,2437			TokenId(1),2438			variable_data.clone().try_into().unwrap()2439		));24402441		assert_eq!(2442			<pallet_nonfungible::TokenData<Test>>::get((collection_id, 1))2443				.unwrap()2444				.variable_data,2445			variable_data2446		);2447	});2448}24492450#[test]2451fn set_variable_meta_data_on_re_fungible_token_stores_variable_meta_data() {2452	new_test_ext().execute_with(|| {2453		let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));24542455		let origin1 = Origin::signed(1);24562457		let data = default_re_fungible_data();2458		create_test_item(collection_id, &data.into());24592460		let variable_data = b"test data".to_vec();2461		assert_ok!(TemplateModule::set_variable_meta_data(2462			origin1,2463			collection_id,2464			TokenId(1),2465			variable_data.clone().try_into().unwrap()2466		));24672468		assert_eq!(2469			<pallet_refungible::TokenData<Test>>::get((collection_id, TokenId(1))).variable_data,2470			variable_data2471		);2472	});2473}24742475#[test]2476fn set_variable_meta_data_on_fungible_token_fails() {2477	new_test_ext().execute_with(|| {2478		let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));24792480		let origin1 = Origin::signed(1);24812482		let data = default_fungible_data();2483		create_test_item(collection_id, &data.into());24842485		let variable_data = b"test data".to_vec();2486		assert_noop!(2487			TemplateModule::set_variable_meta_data(2488				origin1,2489				collection_id,2490				TokenId(0),2491				variable_data.try_into().unwrap()2492			)2493			.map_err(|e| e.error),2494			<pallet_fungible::Error<Test>>::FungibleItemsDontHaveData2495		);2496	});2497}24982499#[test]2500fn set_variable_meta_data_on_nft_with_item_owner_permission_flag() {2501	new_test_ext().execute_with(|| {2502		//default_limits();25032504		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));25052506		let origin1 = Origin::signed(1);25072508		let data = default_nft_data();2509		create_test_item(collection_id, &data.into());25102511		assert_ok!(TemplateModule::set_meta_update_permission_flag(2512			origin1.clone(),2513			collection_id,2514			MetaUpdatePermission::ItemOwner,2515		));25162517		let variable_data = b"ten chars.".to_vec();2518		assert_ok!(TemplateModule::set_variable_meta_data(2519			origin1,2520			collection_id,2521			TokenId(1),2522			variable_data.clone().try_into().unwrap()2523		));25242525		assert_eq!(2526			<pallet_nonfungible::TokenData<Test>>::get((collection_id, TokenId(1)))2527				.unwrap()2528				.variable_data,2529			variable_data2530		);2531	});2532}25332534#[test]2535fn collection_transfer_flag_works() {2536	new_test_ext().execute_with(|| {2537		let origin1 = Origin::signed(1);25382539		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2540		assert_ok!(TemplateModule::set_transfers_enabled_flag(2541			origin1,2542			collection_id,2543			true2544		));25452546		let data = default_nft_data();2547		create_test_item(collection_id, &data.into());2548		assert_eq!(2549			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2550			12551		);2552		assert_eq!(2553			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2554			true2555		);25562557		let origin1 = Origin::signed(1);25582559		// default scenario2560		assert_ok!(TemplateModule::transfer(2561			origin1,2562			account(2),2563			collection_id,2564			TokenId(1),2565			12566		));2567		assert_eq!(2568			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2569			false2570		);2571		assert_eq!(2572			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2573			true2574		);2575		assert_eq!(2576			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2577			02578		);2579		assert_eq!(2580			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2581			12582		);2583	});2584}25852586#[test]2587fn set_variable_meta_data_on_nft_with_admin_flag() {2588	new_test_ext().execute_with(|| {2589		// default_limits();25902591		let collection_id =2592			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));25932594		let origin1 = Origin::signed(1);2595		let origin2 = Origin::signed(2);25962597		assert_ok!(TemplateModule::set_mint_permission(2598			origin2.clone(),2599			collection_id,2600			true2601		));2602		assert_ok!(TemplateModule::add_to_allow_list(2603			origin2.clone(),2604			collection_id,2605			account(1)2606		));26072608		assert_ok!(TemplateModule::add_collection_admin(2609			origin2.clone(),2610			collection_id,2611			account(1)2612		));26132614		let data = default_nft_data();2615		create_test_item(collection_id, &data.into());26162617		assert_ok!(TemplateModule::set_meta_update_permission_flag(2618			origin2.clone(),2619			collection_id,2620			MetaUpdatePermission::Admin,2621		));26222623		let variable_data = b"test.".to_vec();2624		assert_ok!(TemplateModule::set_variable_meta_data(2625			origin1,2626			collection_id,2627			TokenId(1),2628			variable_data.clone().try_into().unwrap()2629		));26302631		assert_eq!(2632			<pallet_nonfungible::TokenData<Test>>::get((collection_id, 1))2633				.unwrap()2634				.variable_data,2635			variable_data2636		);2637	});2638}26392640#[test]2641fn set_variable_meta_data_on_nft_with_admin_flag_neg() {2642	new_test_ext().execute_with(|| {2643		// default_limits();26442645		let collection_id =2646			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));26472648		let origin1 = Origin::signed(1);2649		let origin2 = Origin::signed(2);26502651		assert_ok!(TemplateModule::set_mint_permission(2652			origin2.clone(),2653			collection_id,2654			true2655		));2656		assert_ok!(TemplateModule::add_to_allow_list(2657			origin2.clone(),2658			collection_id,2659			account(1)2660		));26612662		let data = default_nft_data();2663		create_test_item(collection_id, &data.into());26642665		assert_ok!(TemplateModule::set_meta_update_permission_flag(2666			origin2.clone(),2667			collection_id,2668			MetaUpdatePermission::Admin,2669		));26702671		let variable_data = b"test.".to_vec();2672		assert_noop!(2673			TemplateModule::set_variable_meta_data(2674				origin1,2675				collection_id,2676				TokenId(1),2677				variable_data.try_into().unwrap()2678			)2679			.map_err(|e| e.error),2680			CommonError::<Test>::NoPermission2681		);2682	});2683}26842685#[test]2686fn set_variable_meta_flag_after_freeze() {2687	new_test_ext().execute_with(|| {2688		// default_limits();26892690		let collection_id =2691			create_test_collection_for_owner(&CollectionMode::NFT, 2, CollectionId(1));26922693		let origin2 = Origin::signed(2);26942695		assert_ok!(TemplateModule::set_meta_update_permission_flag(2696			origin2.clone(),2697			collection_id,2698			MetaUpdatePermission::None,2699		));2700		assert_noop!(2701			TemplateModule::set_meta_update_permission_flag(2702				origin2.clone(),2703				collection_id,2704				MetaUpdatePermission::Admin2705			),2706			CommonError::<Test>::MetadataFlagFrozen2707		);2708	});2709}27102711#[test]2712fn set_variable_meta_data_on_nft_with_none_flag_neg() {2713	new_test_ext().execute_with(|| {2714		// default_limits();27152716		let collection_id =2717			create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));2718		let origin1 = Origin::signed(1);27192720		let data = default_nft_data();2721		create_test_item(collection_id, &data.into());27222723		assert_ok!(TemplateModule::set_meta_update_permission_flag(2724			origin1.clone(),2725			collection_id,2726			MetaUpdatePermission::None,2727		));27282729		let variable_data = b"test.".to_vec();2730		assert_noop!(2731			TemplateModule::set_variable_meta_data(2732				origin1.clone(),2733				collection_id,2734				TokenId(1),2735				variable_data.try_into().unwrap()2736			)2737			.map_err(|e| e.error),2738			CommonError::<Test>::NoPermission2739		);2740	});2741}27422743#[test]2744fn collection_transfer_flag_works_neg() {2745	new_test_ext().execute_with(|| {2746		let origin1 = Origin::signed(1);27472748		let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2749		assert_ok!(TemplateModule::set_transfers_enabled_flag(2750			origin1,2751			collection_id,2752			false2753		));27542755		let data = default_nft_data();2756		create_test_item(collection_id, &data.into());2757		assert_eq!(2758			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2759			12760		);2761		assert_eq!(2762			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2763			true2764		);27652766		let origin1 = Origin::signed(1);27672768		// default scenario2769		assert_noop!(2770			TemplateModule::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1)2771				.map_err(|e| e.error),2772			CommonError::<Test>::TransferNotAllowed2773		);2774		assert_eq!(2775			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2776			12777		);2778		assert_eq!(2779			<pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2780			02781		);2782		assert_eq!(2783			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2784			true2785		);2786		assert_eq!(2787			<pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2788			false2789		);2790	});2791}
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -263,6 +263,31 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default(bound = ""))]
+pub struct CreateCollectionData<AccountId> {
+	#[derivative(Default(value = "CollectionMode::NFT"))]
+	pub mode: CollectionMode,
+	pub access: Option<AccessMode>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
+	pub schema_version: Option<SchemaVersion>,
+	pub pending_sponsor: Option<AccountId>,
+	pub limits: Option<CollectionLimits>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
+	#[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+	pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
+	pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct NftItemType<AccountId> {
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -9,10 +9,14 @@
 type Result<T> = core::result::Result<T, DispatchError>;
 
 sp_api::decl_runtime_apis! {
+	#[api_version(2)]
 	pub trait UniqueApi<CrossAccountId, AccountId> where
 		AccountId: Decode,
 		CrossAccountId: pallet_common::account::CrossAccountId<AccountId>,
 	{
+		#[changed_in(2)]
+		fn token_owner(collection: CollectionId, token: TokenId) -> Result<CrossAccountId>;
+
 		fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>>;
 		fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool>;
 
modifiedruntime/src/lib.rsdiffbeforeafterboth
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -23,7 +23,8 @@
 use sp_runtime::{
 	Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
 	traits::{
-		AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify, AccountIdConversion,
+		AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify,
+		AccountIdConversion, Zero,
 	},
 	transaction_validity::{TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, MultiSignature, RuntimeAppPublic,
@@ -45,8 +46,9 @@
 	dispatch::DispatchResult,
 	PalletId, parameter_types, StorageValue, ConsensusEngineId,
 	traits::{
-		Everything, Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem,
-		LockIdentifier, OnUnbalanced, Randomness, FindAuthor,
+		tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
+		Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
+		OnUnbalanced, Randomness, FindAuthor,
 	},
 	weights::{
 		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
@@ -69,8 +71,9 @@
 use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
 use fp_rpc::TransactionStatus;
 use sp_runtime::{
-	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf},
+	traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
 	transaction_validity::TransactionValidityError,
+	SaturatedConversion,
 };
 
 // pub use pallet_timestamp::Call as TimestampCall;
@@ -82,13 +85,24 @@
 use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
 use xcm_builder::{
 	AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
-	EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset,
-	ParentAsSuperuser, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative,
-	SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
-	SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
+	EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
+	ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
+	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
 };
-use xcm_executor::{Config, XcmExecutor};
+use xcm_executor::{Config, XcmExecutor, Assets};
+use sp_std::{marker::PhantomData};
 
+use xcm::latest::{
+	//	Xcm,
+	AssetId::{Concrete},
+	Fungibility::Fungible as XcmFungible,
+	MultiAsset,
+	Error as XcmError,
+};
+use xcm_executor::traits::{MatchesFungible, WeightTrader};
+//use xcm_executor::traits::MatchesFungible;
+use sp_runtime::traits::CheckedConversion;
+
 // mod chain_extension;
 // use crate::chain_extension::{NFTExtension, Imbalance};
 
@@ -146,10 +160,10 @@
 	spec_name: create_runtime_str!("opal"),
 	impl_name: create_runtime_str!("opal"),
 	authoring_version: 1,
-	spec_version: 914000,
-	impl_version: 1,
+	spec_version: 916001,
+	impl_version: 0,
 	apis: RUNTIME_API_VERSIONS,
-	transaction_version: 0,
+	transaction_version: 1,
 	state_version: 0,
 };
 
@@ -616,12 +630,22 @@
 	AccountId32Aliases<RelayNetwork, AccountId>,
 );
 
+pub struct OnlySelfCurrency;
+impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
+	fn matches_fungible(a: &MultiAsset) -> Option<B> {
+		match (&a.id, &a.fun) {
+			(Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
+			_ => None,
+		}
+	}
+}
+
 /// Means for transacting assets on this chain.
 pub type LocalAssetTransactor = CurrencyAdapter<
 	// Use this currency:
 	Balances,
 	// Use this currency when it is a fungible asset matching the given location or name:
-	IsConcrete<RelayLocation>,
+	OnlySelfCurrency,
 	// Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID:
 	LocationToAccountId,
 	// Our chain's account ID type (we can't get away without mentioning it explicitly):
@@ -677,6 +701,88 @@
 	// ^^^ Parent & its unit plurality gets free execution
 );
 
+pub struct UsingOnlySelfCurrencyComponents<
+	WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+	AssetId: Get<MultiLocation>,
+	AccountId,
+	Currency: CurrencyT<AccountId>,
+	OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+>(
+	Weight,
+	Currency::Balance,
+	PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
+);
+impl<
+		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+		AssetId: Get<MultiLocation>,
+		AccountId,
+		Currency: CurrencyT<AccountId>,
+		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+	> WeightTrader
+	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+	fn new() -> Self {
+		Self(0, Zero::zero(), PhantomData)
+	}
+
+	fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
+		let amount = WeightToFee::calc(&weight);
+		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
+
+		// location to this parachain through relay chain
+		let option1: xcm::v1::AssetId = Concrete(MultiLocation {
+			parents: 1,
+			interior: X1(Parachain(ParachainInfo::parachain_id().into())),
+		});
+		// direct location
+		let option2: xcm::v1::AssetId = Concrete(MultiLocation {
+			parents: 0,
+			interior: Here,
+		});
+
+		let required = if payment.fungible.contains_key(&option1) {
+			(option1, u128_amount).into()
+		} else if payment.fungible.contains_key(&option2) {
+			(option2, u128_amount).into()
+		} else {
+			(Concrete(MultiLocation::default()), u128_amount).into()
+		};
+
+		let unused = payment
+			.checked_sub(required)
+			.map_err(|_| XcmError::TooExpensive)?;
+		self.0 = self.0.saturating_add(weight);
+		self.1 = self.1.saturating_add(amount);
+		Ok(unused)
+	}
+
+	fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
+		let weight = weight.min(self.0);
+		let amount = WeightToFee::calc(&weight);
+		self.0 -= weight;
+		self.1 = self.1.saturating_sub(amount);
+		let amount: u128 = amount.saturated_into();
+		if amount > 0 {
+			Some((AssetId::get(), amount).into())
+		} else {
+			None
+		}
+	}
+}
+impl<
+		WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+		AssetId: Get<MultiLocation>,
+		AccountId,
+		Currency: CurrencyT<AccountId>,
+		OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+	> Drop
+	for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+	fn drop(&mut self) {
+		OnUnbalanced::on_unbalanced(Currency::issue(self.1));
+	}
+}
+
 pub struct XcmConfig;
 impl Config for XcmConfig {
 	type Call = Call;
@@ -689,7 +795,13 @@
 	type LocationInverter = LocationInverter<Ancestry>;
 	type Barrier = Barrier;
 	type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
-	type Trader = UsingComponents<IdentityFee<Balance>, RelayLocation, AccountId, Balances, ()>;
+	type Trader = UsingOnlySelfCurrencyComponents<
+		IdentityFee<Balance>,
+		RelayLocation,
+		AccountId,
+		Balances,
+		(),
+	>;
 	type ResponseHandler = (); // Don't handle responses for now.
 	type SubscriptionService = PolkadotXcm;
 
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -64,12 +64,14 @@
     "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
     "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
     "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
+    "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",
     "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
     "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
     "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
+    "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --input src/interfaces/ --package .",
-    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint ws://localhost:9944 --output src/interfaces/ --package .",
-    "polkadot-types": "yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
+    "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
+    "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
   },
   "author": "",
   "license": "SEE LICENSE IN ../LICENSE",
modifiedtests/src/check-event/createCollectionEvent.test.tsdiffbeforeafterboth
--- a/tests/src/check-event/createCollectionEvent.test.ts
+++ b/tests/src/check-event/createCollectionEvent.test.ts
@@ -27,7 +27,7 @@
   });
   it('Check event from createCollection(): ', async () => {
     await usingApi(async (api: ApiPromise) => {
-      const tx = api.tx.unique.createCollection([0x31], [0x32], '0x33', 'NFT');
+      const tx = api.tx.unique.createCollectionEx({name: [0x31], description: [0x32], tokenPrefix: '0x33', mode: 'NFT'});
       const events = await submitTransactionAsync(alice, tx);
       const msg = JSON.stringify(uniqueEventMessage(events));
       expect(msg).to.be.contain(checkSection);
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -3,12 +3,11 @@
 // file 'LICENSE', which is part of this source code package.
 //
 
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import {createCollectionExpectFailure, createCollectionExpectSuccess} from './util/helpers';
+import {expect} from 'chai';
+import privateKey from './substrate/privateKey';
+import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';
+import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers';
 
-chai.use(chaiAsPromised);
-
 describe('integration test: ext. createCollection():', () => {
   it('Create new NFT collection', async () => {
     await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
@@ -28,6 +27,45 @@
   it('Create new ReFungible collection', async () => {
     await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
   });
+  it('Create new collection with extra fields', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+      const tx = api.tx.unique.createCollectionEx({
+        mode: {Fungible: 8},
+        access: 'AllowList',
+        name: [1],
+        description: [2],
+        tokenPrefix: '0x000000',
+        offchainSchema: '0x111111',
+        schemaVersion: 'Unique',
+        pendingSponsor: bob.address,
+        limits: {
+          accountTokenOwnershipLimit: 3,
+        },
+        variableOnChainSchema: '0x222222',
+        constOnChainSchema: '0x333333',
+        metaUpdatePermission: 'Admin',
+      });
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getCreateCollectionResult(events);
+
+      const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;
+      expect(collection.owner.toString()).to.equal(alice.address);
+      expect(collection.mode.asFungible.toNumber()).to.equal(8);
+      expect(collection.access.isAllowList).to.be.true;
+      expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);
+      expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);
+      expect(collection.tokenPrefix.toString()).to.equal('0x000000');
+      expect(collection.offchainSchema.toString()).to.equal('0x111111');
+      expect(collection.schemaVersion.isUnique).to.be.true;
+      expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);
+      expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);
+      expect(collection.variableOnChainSchema.toString()).to.equal('0x222222');
+      expect(collection.constOnChainSchema.toString()).to.equal('0x333333');
+      expect(collection.metaUpdatePermission.isAdmin).to.be.true;
+    });
+  });
 });
 
 describe('(!negative test!) integration test: ext. createCollection():', () => {
@@ -40,4 +78,11 @@
   it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
     await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});
   });
+  it('fails when bad limits are set', async () => {
+    await usingApi(async api => {
+      const alice = privateKey('//Alice');
+      const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});
+      await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);
+    });
+  });
 });
addedtests/src/interfaces/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/tests/src/interfaces/.gitignore
@@ -0,0 +1 @@
+metadata.json
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -69,6 +69,10 @@
        **/
       CollectionDescriptionLimitExceeded: AugmentedError<ApiType>;
       /**
+       * Collection limit bounds per collection exceeded
+       **/
+      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
+      /**
        * Collection name can not be longer than 63 char.
        **/
       CollectionNameLimitExceeded: AugmentedError<ApiType>;
@@ -97,6 +101,10 @@
        **/
       NoPermission: AugmentedError<ApiType>;
       /**
+       * Tried to enable permissions which are only permitted to be disabled
+       **/
+      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
+      /**
        * Collection is not in mint mode.
        **/
       PublicMintingNotAllowed: AugmentedError<ApiType>;
@@ -107,7 +115,7 @@
       /**
        * Requested value more than approved.
        **/
-      TokenValueNotEnough: AugmentedError<ApiType>;
+      ApprovedValueTooLow: AugmentedError<ApiType>;
       /**
        * Item balance not enough.
        **/
@@ -436,10 +444,6 @@
        * Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.
        **/
       CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;
-      /**
-       * Collection limit bounds per collection exceeded
-       **/
-      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
       /**
        * This address is not set as sponsor, use setCollectionSponsor first.
        **/
@@ -448,10 +452,6 @@
        * Length of items properties must be greater than 0.
        **/
       EmptyArgument: AugmentedError<ApiType>;
-      /**
-       * Tried to enable permissions which are only permitted to be disabled
-       **/
-      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
       /**
        * Generic error
        **/
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -2,7 +2,7 @@
 /* eslint-disable */
 
 import type { CumulusPrimitivesParachainInherentParachainInherentData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
 import type { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types';
 import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types';
 import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics';
@@ -671,6 +671,12 @@
        **/
       createCollection: AugmentedSubmittable<(collectionName: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Vec<u16>, Vec<u16>, Bytes, UpDataStructsCollectionMode]>;
       /**
+       * This method creates a collection
+       * 
+       * Prefer it to deprecated [`created_collection`] method
+       **/
+      createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; offchainSchema?: any; schemaVersion?: any; pendingSponsor?: any; limits?: any; variableOnChainSchema?: any; constOnChainSchema?: any; metaUpdatePermission?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [UpDataStructsCreateCollectionData]>;
+      /**
        * This method creates a concrete instance of NFT Collection created with CreateCollection method.
        * 
        * # Permissions
modifiedtests/src/interfaces/augment-types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -3,7 +3,7 @@
 
 import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum';
 import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
 import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types';
 import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
 import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
@@ -1021,6 +1021,7 @@
     UpDataStructsCollectionLimits: UpDataStructsCollectionLimits;
     UpDataStructsCollectionMode: UpDataStructsCollectionMode;
     UpDataStructsCollectionStats: UpDataStructsCollectionStats;
+    UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
     UpDataStructsCreateItemData: UpDataStructsCreateItemData;
     UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission;
     UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -67,6 +67,20 @@
       constOnChainSchema: 'Vec<u8>',
       metaUpdatePermission: 'UpDataStructsMetaUpdatePermission',
     },
+    UpDataStructsCreateCollectionData: {
+      mode: 'UpDataStructsCollectionMode',
+      access: 'Option<UpDataStructsAccessMode>',
+      name: 'Vec<u16>',
+      description: 'Vec<u16>',
+      tokenPrefix: 'Vec<u8>',
+      offchainSchema: 'Vec<u8>',
+      schemaVersion: 'Option<UpDataStructsSchemaVersion>',
+      pendingSponsor: 'Option<AccountId>',
+      limits: 'Option<UpDataStructsCollectionLimits>',
+      variableOnChainSchema: 'Vec<u8>',
+      constOnChainSchema: 'Vec<u8>',
+      metaUpdatePermission: 'Option<UpDataStructsMetaUpdatePermission>',
+    },
     UpDataStructsCollectionStats: {
       created: 'u32',
       destroyed: 'u32',
@@ -76,7 +90,13 @@
     UpDataStructsTokenId: 'u32',
     PalletNonfungibleItemData: mkDummy('NftItemData'),
     PalletRefungibleItemData: mkDummy('RftItemData'),
-    UpDataStructsCollectionMode: mkDummy('CollectionMode'),
+    UpDataStructsCollectionMode: {
+      _enum: {
+        NFT: null,
+        Fungible: 'u32',
+        ReFungible: null,
+      },
+    },
     UpDataStructsCreateItemData: mkDummy('CreateItemData'),
     UpDataStructsCollectionLimits: {
       accountTokenOwnershipLimit: 'Option<u32>',
@@ -101,7 +121,9 @@
     UpDataStructsAccessMode: {
       _enum: ['Normal', 'AllowList'],
     },
-    UpDataStructsSchemaVersion: mkDummy('SchemaVersion'),
+    UpDataStructsSchemaVersion: {
+      _enum: ['ImageURL', 'Unique'],
+    },
 
     PalletUnqSchedulerScheduledV2: mkDummy('ScheduledV2'),
     PalletUnqSchedulerCallSpec: mkDummy('CallSpec'),
modifiedtests/src/interfaces/unique/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -77,8 +77,11 @@
 }
 
 /** @name UpDataStructsCollectionMode */
-export interface UpDataStructsCollectionMode extends Struct {
-  readonly dummyCollectionMode: u32;
+export interface UpDataStructsCollectionMode extends Enum {
+  readonly isNft: boolean;
+  readonly isFungible: boolean;
+  readonly asFungible: u32;
+  readonly isReFungible: boolean;
 }
 
 /** @name UpDataStructsCollectionStats */
@@ -88,6 +91,22 @@
   readonly alive: u32;
 }
 
+/** @name UpDataStructsCreateCollectionData */
+export interface UpDataStructsCreateCollectionData extends Struct {
+  readonly mode: UpDataStructsCollectionMode;
+  readonly access: Option<UpDataStructsAccessMode>;
+  readonly name: Vec<u16>;
+  readonly description: Vec<u16>;
+  readonly tokenPrefix: Bytes;
+  readonly offchainSchema: Bytes;
+  readonly schemaVersion: Option<UpDataStructsSchemaVersion>;
+  readonly pendingSponsor: Option<AccountId>;
+  readonly limits: Option<UpDataStructsCollectionLimits>;
+  readonly variableOnChainSchema: Bytes;
+  readonly constOnChainSchema: Bytes;
+  readonly metaUpdatePermission: Option<UpDataStructsMetaUpdatePermission>;
+}
+
 /** @name UpDataStructsCreateItemData */
 export interface UpDataStructsCreateItemData extends Struct {
   readonly dummyCreateItemData: u32;
@@ -101,8 +120,9 @@
 }
 
 /** @name UpDataStructsSchemaVersion */
-export interface UpDataStructsSchemaVersion extends Struct {
-  readonly dummySchemaVersion: u32;
+export interface UpDataStructsSchemaVersion extends Enum {
+  readonly isImageUrl: boolean;
+  readonly isUnique: boolean;
 }
 
 /** @name UpDataStructsSponsorshipState */
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -95,6 +95,32 @@
   return TransactionStatus.Fail;
 }
 
+export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise<EventRecord[]> {
+  return new Promise(async (res, rej) => {
+    try {
+      await transaction.signAndSend(sender, ({events, status}) => {
+        if (!status.isInBlock && !status.isFinalized) return;
+        for (const {event} of events) {
+          if (api.events.system.ExtrinsicSuccess.is(event)) {
+            res(events);
+          } else if (api.events.system.ExtrinsicFailed.is(event)) {
+            const {data: [error]} = event;
+            if (error.isModule) {
+              const decoded = api.registry.findMetaError(error.asModule);
+              const {method, section} = decoded;
+              rej(new Error(`${section}.${method}`));
+            } else {
+              rej(new Error(error.toString()));
+            }
+          }
+        }
+      });
+    } catch (e) {
+      rej(e);
+    }
+  });
+}
+
 export function
 submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
   /* eslint no-async-promise-executor: "off" */
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -49,7 +49,7 @@
     };
   } else if ('Substrate' in input) {
     return input;
-  }else if ('substrate' in input) {
+  } else if ('substrate' in input) {
     return {
       Substrate: (input as any).substrate,
     };
@@ -116,15 +116,15 @@
 
 export interface IChainLimits {
   collectionNumbersLimit: number;
-	accountTokenOwnershipLimit: number;
-	collectionsAdminsLimit: number;
-	customDataLimit: number;
-	nftSponsorTransferTimeout: number;
-	fungibleSponsorTransferTimeout: number;
-	refungibleSponsorTransferTimeout: number;
-	offchainSchemaLimit: number;
-	variableOnChainSchemaLimit: number;
-	constOnChainSchemaLimit: number;
+  accountTokenOwnershipLimit: number;
+  collectionsAdminsLimit: number;
+  customDataLimit: number;
+  nftSponsorTransferTimeout: number;
+  fungibleSponsorTransferTimeout: number;
+  refungibleSponsorTransferTimeout: number;
+  offchainSchemaLimit: number;
+  variableOnChainSchemaLimit: number;
+  constOnChainSchemaLimit: number;
 }
 
 export interface IReFungibleTokenDataType {
@@ -283,7 +283,7 @@
       modeprm = {refungible: null};
     }
 
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await submitTransactionAsync(alicePrivateKey, tx);
     const result = getCreateCollectionResult(events);
 
@@ -329,7 +329,7 @@
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
-    const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+    const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
     const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
     const result = getCreateCollectionResult(events);
 
@@ -557,7 +557,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
 
@@ -569,7 +569,7 @@
 
   await usingApi(async (api) => {
 
-    const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+    const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
     const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
     const result = getGenericResult(events);
 
@@ -811,8 +811,7 @@
 }
 
 export async function
-getFreeBalance(account: IKeyringPair) : Promise<bigint>
-{
+getFreeBalance(account: IKeyringPair): Promise<bigint> {
   let balance = 0n;
   await usingApi(async (api) => {
     balance = BigInt((await api.query.system.account(account.address)).data.free.toString());
addedtests/src/xcmTransfer.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/xcmTransfer.test.ts
@@ -0,0 +1,178 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+
+import {WsProvider} from '@polkadot/api';
+import {ApiOptions} from '@polkadot/api/types';
+import {IKeyringPair} from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi, {submitTransactionAsync} from './substrate/substrate-api';
+import {getGenericResult} from './util/helpers';
+import waitNewBlocks from './substrate/wait-new-blocks';
+import getBalance from './substrate/get-balance';
+import {alicesPublicKey} from './accounts';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const UNIQUE_CHAIN = 1000;
+const KARURA_CHAIN = 2000;
+const KARURA_PORT = '9946';
+
+describe('Integration test: Exchanging OPL with Karura', () => {
+  let alice: IKeyringPair;
+  
+  before(async () => {
+    await usingApi(async () => {
+      alice = privateKey('//Alice');
+    });
+
+    const karuraApiOptions: ApiOptions = {
+      provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT),
+    };
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X2: [
+            'Parent',
+            {
+              Parachain: UNIQUE_CHAIN,
+            },
+          ],
+        },
+      };
+
+      const metadata =
+      {
+        name: 'OPL',
+        symbol: 'OPL',
+        decimals: 18,
+        minimalBalance: 1,
+      };
+
+      const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
+      const sudoTx = api.tx.sudo.sudo(tx as any);
+      const events = await submitTransactionAsync(alice, sudoTx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    }, karuraApiOptions);
+  });
+
+  it('Should connect and send OPL to Karura', async () => {
+    let balanceOnKaruraBefore: bigint;
+    
+    await usingApi(async (api) => {
+      const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+      balanceOnKaruraBefore = free;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X2: [
+            'Parent',
+            {
+              Parachain: KARURA_CHAIN,
+            },
+          ],
+        },
+      };
+
+      const beneficiary = {
+        V0: {
+          X1: {
+            AccountId32: {
+              network: 'Any',
+              id: alice.addressRaw,
+            },
+          },
+        },
+      };
+
+      const assets = {
+        V1: [
+          {
+            id: {
+              Concrete: {
+                parents: 0,
+                interior: 'Here',
+              },
+            },
+            fun: {
+              Fungible: 5000000000,
+            },
+          },
+        ],
+      };
+
+      const feeAssetItem = 0;
+
+      const weightLimit = {
+        Limited: 5000000000,
+      };
+
+      const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    });
+
+    await usingApi(async (api) => {
+      // todo do something about instant sealing, where there might not be any new blocks
+      await waitNewBlocks(api, 3);
+      const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+      expect(free > balanceOnKaruraBefore).to.be.true;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+  });
+
+  it('Should connect to Karura and send OPL back', async () => {
+    let balanceBefore: bigint;
+    
+    await usingApi(async (api) => {
+      [balanceBefore] = await getBalance(api, [alicesPublicKey]);
+    });
+
+    await usingApi(async (api) => {
+      const destination = {
+        V0: {
+          X3: [
+            'Parent',
+            {
+              Parachain: UNIQUE_CHAIN,
+            },
+            {
+              AccountId32: {
+                network: 'Any',
+                id: alice.addressRaw,
+              },
+            },
+          ],
+        },
+      };
+
+      const id = {
+        ForeignAsset: 0,
+      };
+
+      const amount = 5000000000;
+      const destWeight = 50000000;
+
+      const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight);
+      const events = await submitTransactionAsync(alice, tx);
+      const result = getGenericResult(events);
+      expect(result.success).to.be.true;
+    }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+    await usingApi(async (api) => {
+      // todo do something about instant sealing, where there might not be any new blocks
+      await waitNewBlocks(api, 3);
+      const [balanceAfter] = await getBalance(api, [alicesPublicKey]);
+      expect(balanceAfter > balanceBefore).to.be.true;
+    });
+  });
+});
\ No newline at end of file