difftreelog
feat merge native erc20 adapter
in: master
78 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5871,6 +5871,7 @@
"pallet-aura",
"pallet-authorship",
"pallet-balances",
+ "pallet-balances-adapter",
"pallet-base-fee",
"pallet-collator-selection",
"pallet-common",
@@ -6252,6 +6253,27 @@
]
[[package]]
+name = "pallet-balances-adapter"
+version = "0.1.0"
+dependencies = [
+ "evm-coder",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "pallet-common",
+ "pallet-evm",
+ "pallet-evm-coder-substrate",
+ "pallet-evm-transaction-payment",
+ "pallet-structure",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-runtime",
+ "sp-std",
+ "up-data-structs",
+]
+
+[[package]]
name = "pallet-base-fee"
version = "1.0.0"
source = "git+https://github.com/uniquenetwork/unique-frontier?branch=unique-polkadot-v0.9.42#75ad5b9a49e1942553c4068228f1546ffb1f7246"
@@ -6947,6 +6969,7 @@
"frame-benchmarking",
"frame-support",
"frame-system",
+ "pallet-balances",
"pallet-common",
"pallet-evm",
"pallet-evm-coder-substrate",
@@ -7411,6 +7434,7 @@
"frame-benchmarking",
"frame-support",
"frame-system",
+ "pallet-balances-adapter",
"pallet-common",
"pallet-evm",
"pallet-evm-coder-substrate",
@@ -9350,6 +9374,7 @@
"pallet-aura",
"pallet-authorship",
"pallet-balances",
+ "pallet-balances-adapter",
"pallet-base-fee",
"pallet-collator-selection",
"pallet-common",
@@ -12902,8 +12927,8 @@
"frame-support",
"frame-system",
"pallet-balances",
+ "pallet-balances-adapter",
"pallet-common",
- "pallet-configuration",
"pallet-ethereum",
"pallet-evm",
"pallet-evm-coder-substrate",
@@ -12914,7 +12939,6 @@
"pallet-timestamp",
"pallet-transaction-payment",
"pallet-unique",
- "pallet-xcm",
"parity-scale-codec",
"scale-info",
"sp-core",
@@ -13767,6 +13791,7 @@
"pallet-aura",
"pallet-authorship",
"pallet-balances",
+ "pallet-balances-adapter",
"pallet-base-fee",
"pallet-collator-selection",
"pallet-common",
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,9 +19,9 @@
panic = "unwind"
[profile.production]
+codegen-units = 1
inherits = "release"
lto = true
-codegen-units = 1
opt-level = 3
[workspace.dependencies]
@@ -29,6 +29,7 @@
app-promotion-rpc = { path = "primitives/app_promotion_rpc", default-features = false }
evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.1", default-features = false }
pallet-app-promotion = { path = "pallets/app-promotion", default-features = false }
+pallet-balances-adapter = { default-features = false, path = "pallets/balances-adapter" }
pallet-charge-transaction = { package = "pallet-template-transaction-payment", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.42" }
pallet-collator-selection = { default-features = false, path = "pallets/collator-selection" }
pallet-common = { default-features = false, path = "pallets/common" }
@@ -85,15 +86,15 @@
cumulus-client-consensus-common = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-client-network = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-client-service = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-aura-ext = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-dmp-queue = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-parachain-system = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-xcm = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-pallet-xcmp-queue = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-core = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-aura-ext = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-dmp-queue = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-parachain-system = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-xcm = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-pallet-xcmp-queue = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-core = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-timestamp = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
-cumulus-primitives-utility = { default-features = false , git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-timestamp = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
+cumulus-primitives-utility = { default-features = false, git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-relay-chain-inprocess-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-relay-chain-interface = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
cumulus-relay-chain-minimal-node = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.42" }
@@ -123,9 +124,9 @@
sc-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
+sc-consensus-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-consensus-manual-seal = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
-sc-consensus-grandpa = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-network-sync = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
@@ -177,11 +178,11 @@
try-runtime-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.42" }
# ORML
-orml-tokens = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-traits = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-vesting = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-xtokens = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
-orml-xcm-support = { default-features = false , git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-tokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-traits = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-vesting = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-xcm-support = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
+orml-xtokens = { default-features = false, git = "https://github.com/uniquenetwork/open-runtime-module-library", branch = "polkadot-v0.9.42" }
# Other
derivative = { version = "2.2.0", features = ["use_core"] }
@@ -194,4 +195,3 @@
num_enum = { version = "0.5.3", default-features = false }
serde = { default-features = false, features = ['derive'], version = "1.0.136" }
smallvec = "1.6.1"
-
Makefilediffbeforeafterboth--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@
@echo " bench-evm-migration"
@echo " bench-unique"
+NATIVE_FUNGIBLE_EVM_STUBS=./pallets/balances-adapter/src/stubs
+NATIVE_FUNGIBLE_EVM_ABI=./tests/src/eth/abi/nativeFungible.json
+
FUNGIBLE_EVM_STUBS=./pallets/fungible/src/stubs
FUNGIBLE_EVM_ABI=./tests/src/eth/abi/fungible.json
@@ -27,6 +30,10 @@
.PHONY: regenerate_solidity
regenerate_solidity: UniqueFungible.sol UniqueNFT.sol UniqueRefungible.sol UniqueRefungibleToken.sol ContractHelpers.sol CollectionHelpers.sol
+UniqueNativeFungible.sol:
+ PACKAGE=pallet-balances-adapter NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+ PACKAGE=pallet-balances-adapter NAME=erc::gen_impl OUTPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
UniqueFungible.sol:
PACKAGE=pallet-fungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
PACKAGE=pallet-fungible NAME=erc::gen_impl OUTPUT=$(FUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
@@ -51,6 +58,10 @@
PACKAGE=pallet-unique NAME=eth::collection_helper_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
PACKAGE=pallet-unique NAME=eth::collection_helper_impl OUTPUT=$(COLLECTION_HELPER_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+UniqueNativeFungible: UniqueNativeFungible.sol
+ INPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/UniqueNativeFungible.raw ./.maintain/scripts/compile_stub.sh
+ INPUT=$(NATIVE_FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NATIVE_FUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
+
UniqueFungible: UniqueFungible.sol
INPUT=$(FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(FUNGIBLE_EVM_STUBS)/UniqueFungible.raw ./.maintain/scripts/compile_stub.sh
INPUT=$(FUNGIBLE_EVM_STUBS)/$< OUTPUT=$(FUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -267,7 +267,7 @@
pub fn development_config() -> DefaultChainSpec {
let mut properties = Map::new();
properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
- properties.insert("tokenDecimals".into(), 18.into());
+ properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
properties.insert(
"ss58Format".into(),
default_runtime::SS58Prefix::get().into(),
@@ -341,7 +341,7 @@
pub fn local_testnet_config() -> DefaultChainSpec {
let mut properties = Map::new();
properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());
- properties.insert("tokenDecimals".into(), 18.into());
+ properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());
properties.insert(
"ss58Format".into(),
default_runtime::SS58Prefix::get().into(),
pallets/balances-adapter/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/balances-adapter/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+edition = "2021"
+license = "GPLv3"
+name = "pallet-balances-adapter"
+version = "0.1.0"
+
+[dependencies]
+# Substrate
+frame-support = { workspace = true }
+frame-system = { workspace = true }
+pallet-balances = { workspace = true }
+pallet-structure = { workspace = true }
+sp-core = { workspace = true }
+sp-runtime = { workspace = true }
+sp-std = { workspace = true }
+
+#Parity
+codec = { workspace = true, package = "parity-scale-codec" }
+scale-info = { workspace = true }
+
+# Unique
+evm-coder = { workspace = true }
+pallet-evm = { workspace = true }
+
+#Local
+pallet-common = { workspace = true }
+pallet-evm-coder-substrate = { workspace = true }
+pallet-evm-transaction-payment = { workspace = true }
+up-data-structs = { workspace = true, features = ['serde1'] }
+
+[features]
+default = ["std"]
+std = [
+ "evm-coder/std",
+ "frame-support/std",
+ "frame-system/std",
+ "pallet-balances/std",
+ "pallet-common/std",
+ "pallet-evm-coder-substrate/std",
+ "pallet-evm/std",
+ "sp-core/std",
+ "sp-std/std",
+]
+stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"]
+try-runtime = ["frame-support/try-runtime"]
pallets/balances-adapter/src/common.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/balances-adapter/src/common.rs
@@ -0,0 +1,359 @@
+use alloc::{vec, vec::Vec};
+use core::marker::PhantomData;
+use crate::{Config, NativeFungibleHandle, Pallet};
+use frame_support::{fail, weights::Weight};
+use pallet_balances::{weights::SubstrateWeight as BalancesWeight, WeightInfo};
+use pallet_common::{CommonCollectionOperations, CommonWeightInfo};
+use up_data_structs::TokenId;
+
+pub struct CommonWeights<T: Config>(PhantomData<T>);
+
+// All implementations with `Weight::default` used in methods that return error `UnsupportedOperation`.
+impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
+ fn create_multiple_items(_amount: &[up_data_structs::CreateItemData]) -> Weight {
+ Weight::default()
+ }
+
+ fn create_multiple_items_ex(
+ _cost: &up_data_structs::CreateItemExData<T::CrossAccountId>,
+ ) -> Weight {
+ Weight::default()
+ }
+
+ fn burn_item() -> Weight {
+ Weight::default()
+ }
+
+ fn set_collection_properties(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn delete_collection_properties(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn set_token_properties(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn delete_token_properties(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn set_token_property_permissions(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn transfer() -> Weight {
+ <BalancesWeight<T> as WeightInfo>::transfer_allow_death()
+ }
+
+ fn approve() -> Weight {
+ Weight::default()
+ }
+
+ fn approve_from() -> Weight {
+ Weight::default()
+ }
+
+ fn transfer_from() -> Weight {
+ <BalancesWeight<T> as WeightInfo>::transfer_allow_death()
+ }
+
+ fn burn_from() -> Weight {
+ Weight::default()
+ }
+
+ fn burn_recursively_self_raw() -> Weight {
+ Weight::default()
+ }
+
+ fn burn_recursively_breadth_raw(_amount: u32) -> Weight {
+ Weight::default()
+ }
+
+ fn token_owner() -> Weight {
+ Weight::default()
+ }
+
+ fn set_allowance_for_all() -> Weight {
+ Weight::default()
+ }
+
+ fn force_repair_item() -> Weight {
+ Weight::default()
+ }
+}
+
+/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallet
+/// methods and adds weight info.
+impl<T: Config> CommonCollectionOperations<T> for NativeFungibleHandle<T> {
+ fn create_item(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _to: <T>::CrossAccountId,
+ _data: up_data_structs::CreateItemData,
+ _nesting_budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn create_multiple_items(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _to: <T>::CrossAccountId,
+ _data: Vec<up_data_structs::CreateItemData>,
+ _nesting_budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn create_multiple_items_ex(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _data: up_data_structs::CreateItemExData<<T>::CrossAccountId>,
+ _nesting_budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn burn_item(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _token: TokenId,
+ _amount: u128,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn burn_item_recursively(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _token: TokenId,
+ _self_budget: &dyn up_data_structs::budget::Budget,
+ _breadth_budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn set_collection_properties(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _properties: Vec<up_data_structs::Property>,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn delete_collection_properties(
+ &self,
+ _sender: &<T>::CrossAccountId,
+ _property_keys: Vec<up_data_structs::PropertyKey>,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn set_token_properties(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _token_id: TokenId,
+ _properties: Vec<up_data_structs::Property>,
+ _budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn delete_token_properties(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _token_id: TokenId,
+ _property_keys: Vec<up_data_structs::PropertyKey>,
+ _budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn set_token_property_permissions(
+ &self,
+ _sender: &<T>::CrossAccountId,
+ _property_permissions: Vec<up_data_structs::PropertyKeyPermission>,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn transfer(
+ &self,
+ sender: <T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ _token: TokenId,
+ amount: u128,
+ budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ <Pallet<T>>::transfer(self, &sender, &to, amount, budget)
+ }
+
+ fn approve(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _spender: <T>::CrossAccountId,
+ _token: TokenId,
+ _amount: u128,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn approve_from(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _from: <T>::CrossAccountId,
+ _to: <T>::CrossAccountId,
+ _token: TokenId,
+ _amount: u128,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn transfer_from(
+ &self,
+ sender: <T>::CrossAccountId,
+ from: <T>::CrossAccountId,
+ to: <T>::CrossAccountId,
+ _token: TokenId,
+ amount: u128,
+ budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ <Pallet<T>>::transfer_from(self, &sender, &from, &to, amount, budget)
+ }
+
+ fn burn_from(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _from: <T>::CrossAccountId,
+ _token: TokenId,
+ _amount: u128,
+ _budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn check_nesting(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _from: (up_data_structs::CollectionId, TokenId),
+ _under: TokenId,
+ _budget: &dyn up_data_structs::budget::Budget,
+ ) -> frame_support::sp_runtime::DispatchResult {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn nest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
+
+ fn unnest(&self, _under: TokenId, _to_nest: (up_data_structs::CollectionId, TokenId)) {}
+
+ fn account_tokens(&self, account: <T>::CrossAccountId) -> Vec<TokenId> {
+ let balance = <Pallet<T>>::total_balance(&account);
+ if balance != 0 {
+ vec![TokenId::default()]
+ } else {
+ vec![]
+ }
+ }
+
+ fn collection_tokens(&self) -> Vec<TokenId> {
+ vec![TokenId::default()]
+ }
+
+ fn token_exists(&self, token: TokenId) -> bool {
+ token == TokenId::default()
+ }
+
+ fn last_token_id(&self) -> TokenId {
+ TokenId::default()
+ }
+
+ fn token_owner(
+ &self,
+ _token: TokenId,
+ ) -> Result<<T>::CrossAccountId, up_data_structs::TokenOwnerError> {
+ Err(up_data_structs::TokenOwnerError::MultipleOwners)
+ }
+
+ fn token_owners(&self, _token: TokenId) -> Vec<<T>::CrossAccountId> {
+ vec![]
+ }
+
+ fn token_property(
+ &self,
+ _token_id: TokenId,
+ _key: &up_data_structs::PropertyKey,
+ ) -> Option<up_data_structs::PropertyValue> {
+ None
+ }
+
+ fn token_properties(
+ &self,
+ _token: TokenId,
+ _keys: Option<Vec<up_data_structs::PropertyKey>>,
+ ) -> Vec<up_data_structs::Property> {
+ vec![]
+ }
+
+ fn total_supply(&self) -> u32 {
+ 1
+ }
+
+ fn account_balance(&self, account: T::CrossAccountId) -> u32 {
+ let balance = <Pallet<T>>::balance_of(&account);
+ (balance != 0).into()
+ }
+
+ fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
+ if token != TokenId::default() {
+ return 0;
+ }
+ <Pallet<T>>::balance_of(&account)
+ }
+
+ fn total_pieces(&self, token: TokenId) -> Option<u128> {
+ if token != TokenId::default() {
+ return None;
+ }
+ Some(<Pallet<T>>::total_issuance())
+ }
+
+ fn allowance(
+ &self,
+ _sender: <T>::CrossAccountId,
+ _spender: <T>::CrossAccountId,
+ _token: TokenId,
+ ) -> u128 {
+ 0
+ }
+
+ fn refungible_extensions(&self) -> Option<&dyn pallet_common::RefungibleExtensions<T>> {
+ None
+ }
+
+ fn set_allowance_for_all(
+ &self,
+ _owner: <T>::CrossAccountId,
+ _operator: <T>::CrossAccountId,
+ _approve: bool,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ fn allowance_for_all(
+ &self,
+ _owner: <T>::CrossAccountId,
+ _operator: <T>::CrossAccountId,
+ ) -> bool {
+ false
+ }
+
+ fn repair_item(
+ &self,
+ _token: TokenId,
+ ) -> frame_support::pallet_prelude::DispatchResultWithPostInfo {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+}
pallets/balances-adapter/src/erc.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/balances-adapter/src/erc.rs
@@ -0,0 +1,165 @@
+use crate::{Config, NativeFungibleHandle, Pallet, SelfWeightOf};
+use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*};
+use pallet_balances::WeightInfo;
+use pallet_common::{
+ erc::{CommonEvmHandler, CrossAccountId, PrecompileHandle, PrecompileResult},
+ eth::CrossAddress,
+};
+use pallet_evm_coder_substrate::{
+ call, dispatch_to_evm,
+ execution::{PreDispatch, Result},
+ frontier_contract, WithRecorder,
+};
+use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_core::{U256, Get};
+
+frontier_contract! {
+ macro_rules! NativeFungibleHandle_result {...}
+ impl<T: Config> Contract for NativeFungibleHandle<T> {...}
+}
+
+#[solidity_interface(name = ERC20, enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x942e8b22)]
+impl<T: Config> NativeFungibleHandle<T> {
+ fn allowance(&self, _owner: Address, _spender: Address) -> Result<U256> {
+ Ok(U256::zero())
+ }
+
+ fn approve(&mut self, _caller: Caller, _spender: Address, _amount: U256) -> Result<bool> {
+ Err("Approve not supported".into())
+ }
+
+ fn balance_of(&self, owner: Address) -> Result<U256> {
+ self.consume_store_reads(1)?;
+ let owner = T::CrossAccountId::from_eth(owner);
+ let balance = <Pallet<T>>::balance_of(&owner);
+ Ok(balance.into())
+ }
+
+ fn decimals(&self) -> Result<u8> {
+ Ok(T::Decimals::get())
+ }
+
+ fn name(&self) -> Result<String> {
+ Ok(T::Name::get())
+ }
+
+ fn symbol(&self) -> Result<String> {
+ Ok(T::Symbol::get())
+ }
+
+ fn total_supply(&self) -> Result<U256> {
+ self.consume_store_reads(1)?;
+ Ok(<Pallet<T>>::total_issuance().into())
+ }
+
+ #[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+ fn transfer(&mut self, caller: Caller, to: Address, amount: U256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = T::CrossAccountId::from_eth(to);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder()
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+ Ok(true)
+ }
+
+ #[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+ fn transfer_from(
+ &mut self,
+ caller: Caller,
+ from: Address,
+ to: Address,
+ amount: U256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = T::CrossAccountId::from_eth(from);
+ let to = T::CrossAccountId::from_eth(to);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder()
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+ Ok(true)
+ }
+}
+
+#[solidity_interface(name = ERC20UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]
+impl<T: Config> NativeFungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
+ fn balance_of_cross(&self, owner: CrossAddress) -> Result<U256> {
+ self.consume_store_reads(1)?;
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let balance = <Pallet<T>>::balance_of(&owner);
+ Ok(balance.into())
+ }
+
+ #[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+ fn transfer_cross(&mut self, caller: Caller, to: CrossAddress, amount: U256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = to.into_sub_cross_account::<T>()?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder()
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+
+ Ok(true)
+ }
+
+ #[weight(<SelfWeightOf<T>>::transfer_allow_death())]
+ fn transfer_from_cross(
+ &mut self,
+ caller: Caller,
+ from: CrossAddress,
+ to: CrossAddress,
+ amount: U256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = from.into_sub_cross_account::<T>()?;
+ let to = to.into_sub_cross_account::<T>()?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ if from != caller {
+ return Err("no permission".into());
+ }
+
+ let budget = self
+ .recorder()
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
+
+ Ok(true)
+ }
+}
+
+#[solidity_interface(
+ name = UniqueNativeFungible,
+ is(ERC20, ERC20UniqueExtensions),
+ enum(derive(PreDispatch))
+)]
+impl<T: Config> NativeFungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}
+
+generate_stubgen!(gen_impl, UniqueNativeFungibleCall<()>, true);
+generate_stubgen!(gen_iface, UniqueNativeFungibleCall<()>, false);
+
+impl<T: Config> CommonEvmHandler for NativeFungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,
+{
+ const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNativeFungible.raw");
+
+ fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+ call::<T, UniqueNativeFungibleCall<T>, _, _>(handle, self)
+ }
+}
pallets/balances-adapter/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/balances-adapter/src/lib.rs
@@ -0,0 +1,206 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+extern crate alloc;
+use core::ops::Deref;
+
+use frame_support::sp_runtime::DispatchResult;
+use pallet_evm_coder_substrate::{WithRecorder, SubstrateRecorder};
+pub use pallet::*;
+
+pub mod common;
+pub mod erc;
+
+pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+
+/// Handle for native fungible collection
+pub struct NativeFungibleHandle<T: Config>(SubstrateRecorder<T>);
+impl<T: Config> NativeFungibleHandle<T> {
+ /// Creates a handle
+ pub fn new() -> NativeFungibleHandle<T> {
+ Self(SubstrateRecorder::new(u64::MAX))
+ }
+
+ /// Creates a handle
+ pub fn new_with_gas_limit(gas_limit: u64) -> NativeFungibleHandle<T> {
+ Self(SubstrateRecorder::new(gas_limit))
+ }
+
+ /// Check if the collection is internal
+ pub fn check_is_internal(&self) -> DispatchResult {
+ Ok(())
+ }
+}
+
+impl<T: Config> WithRecorder<T> for NativeFungibleHandle<T> {
+ fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ &self.0
+ }
+ fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ self.0
+ }
+}
+
+impl<T: Config> Deref for NativeFungibleHandle<T> {
+ type Target = SubstrateRecorder<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+#[frame_support::pallet]
+pub mod pallet {
+ use super::*;
+ use alloc::string::String;
+ use frame_support::{
+ dispatch::PostDispatchInfo,
+ ensure,
+ pallet_prelude::{DispatchResultWithPostInfo, Pays},
+ traits::{
+ Get,
+ fungible::{Inspect, Mutate},
+ tokens::Preservation,
+ },
+ };
+ use pallet_balances::WeightInfo;
+ use pallet_common::{erc::CrossAccountId, Error as CommonError, Pallet as PalletCommon};
+ use pallet_structure::Pallet as PalletStructure;
+ use sp_core::U256;
+ use sp_runtime::DispatchError;
+ use up_data_structs::{budget::Budget, mapping::TokenAddressMapping};
+
+ #[pallet::config]
+ pub trait Config:
+ frame_system::Config
+ + pallet_evm_coder_substrate::Config
+ + pallet_common::Config
+ + pallet_structure::Config
+ {
+ /// Inspect from `pallet_balances`
+ type Inspect: frame_support::traits::tokens::fungible::Inspect<
+ Self::AccountId,
+ Balance = Self::CurrencyBalance,
+ >;
+
+ /// Mutate from `pallet_balances`
+ type Mutate: frame_support::traits::tokens::fungible::Mutate<
+ Self::AccountId,
+ Balance = Self::CurrencyBalance,
+ >;
+
+ /// Balance type of chain
+ type CurrencyBalance: Into<U256> + TryFrom<U256> + TryFrom<u128> + Into<u128>;
+
+ /// Decimals of balance
+ type Decimals: Get<u8>;
+ /// Collection name
+ type Name: Get<String>;
+ /// Collection symbol
+ type Symbol: Get<String>;
+
+ /// Weight information
+ type WeightInfo: WeightInfo;
+ }
+ #[pallet::pallet]
+ pub struct Pallet<T>(_);
+
+ impl<T: Config> Pallet<T> {
+ pub fn balance_of(account: &T::CrossAccountId) -> u128 {
+ T::Inspect::balance(account.as_sub()).into()
+ }
+
+ pub fn total_balance(account: &T::CrossAccountId) -> u128 {
+ T::Inspect::total_balance(account.as_sub()).into()
+ }
+
+ pub fn total_issuance() -> u128 {
+ T::Inspect::total_issuance().into()
+ }
+
+ /// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.
+ /// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.
+ ///
+ /// - `spender`: CrossAccountId who has the allowance rights.
+ /// - `from`: The owner of the tokens who sets the allowance.
+ /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+ fn check_allowed(
+ spender: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ nesting_budget: &dyn Budget,
+ ) -> Result<u128, DispatchError> {
+ if let Some((collection_id, token_id)) =
+ T::CrossTokenAddressMapping::address_to_token(from)
+ {
+ ensure!(
+ <PalletStructure<T>>::check_indirectly_owned(
+ spender.clone(),
+ collection_id,
+ token_id,
+ None,
+ nesting_budget
+ )?,
+ <CommonError<T>>::ApprovedValueTooLow,
+ );
+ } else if !spender.conv_eq(from) {
+ return Ok(0);
+ }
+
+ Ok(Self::balance_of(from))
+ }
+
+ /// Transfers the specified amount of tokens. Will check that
+ /// the transfer is allowed for the token.
+ ///
+ /// - `collection`: Collection that contains the token.
+ /// - `from`: Owner of tokens to transfer.
+ /// - `to`: Recepient of transfered tokens.
+ /// - `amount`: Amount of tokens to transfer.
+ /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+ pub fn transfer(
+ _collection: &NativeFungibleHandle<T>,
+ from: &T::CrossAccountId,
+ to: &T::CrossAccountId,
+ amount: u128,
+ _nesting_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ <PalletCommon<T>>::ensure_correct_receiver(to)?;
+
+ if from != to && amount != 0 {
+ let amount = amount
+ .try_into()
+ .map_err(|_| sp_runtime::ArithmeticError::Overflow)?;
+ T::Mutate::transfer(from.as_sub(), to.as_sub(), amount, Preservation::Expendable)?;
+ };
+
+ Ok(PostDispatchInfo {
+ actual_weight: Some(<SelfWeightOf<T>>::transfer_allow_death()),
+ pays_fee: Pays::Yes,
+ })
+ }
+
+ /// Transfer NFT token from one account to another.
+ ///
+ /// Same as the [`Self::transfer`] but spender doesn't needs to be the owner of the token.
+ /// The owner should set allowance for the spender to transfer token.
+ ///
+ /// - `collection`: Collection that contains the token.
+ /// - `spender`: Account that spend the money.
+ /// - `from`: Owner of tokens to transfer.
+ /// - `to`: Recepient of transfered tokens.
+ /// - `amount`: Amount of tokens to transfer.
+ /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+ pub fn transfer_from(
+ collection: &NativeFungibleHandle<T>,
+ spender: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ to: &T::CrossAccountId,
+ amount: u128,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResultWithPostInfo {
+ let allowance = Self::check_allowed(spender, from, nesting_budget)?;
+ if allowance < amount {
+ return Err(<CommonError<T>>::ApprovedValueTooLow.into());
+ }
+ Self::transfer(collection, from, to, amount, nesting_budget)
+ }
+ }
+}
pallets/balances-adapter/src/stubs/UniqueNativeFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/balances-adapter/src/stubs/UniqueNativeFungible.soldiffbeforeafterboth--- /dev/null
+++ b/pallets/balances-adapter/src/stubs/UniqueNativeFungible.sol
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev common stubs holder
+contract Dummy {
+ uint8 dummy;
+ string stub_error = "this contract is implemented in native";
+}
+
+contract ERC165 is Dummy {
+ function supportsInterface(bytes4 interfaceID) external view returns (bool) {
+ require(false, stub_error);
+ interfaceID;
+ return true;
+ }
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x1313556c
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev EVM selector for this function is: 0xec069398,
+ /// or in textual repr: balanceOfCross((address,uint256))
+ function balanceOfCross(CrossAddress memory owner) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ dummy;
+ return 0;
+ }
+
+ /// @dev EVM selector for this function is: 0x2ada85ff,
+ /// or in textual repr: transferCross((address,uint256),uint256)
+ function transferCross(CrossAddress memory to, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ CrossAddress memory from,
+ CrossAddress memory to,
+ uint256 amount
+ ) public returns (bool) {
+ require(false, stub_error);
+ from;
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+}
+
+/// Cross account struct
+struct CrossAddress {
+ address eth;
+ uint256 sub;
+}
+
+/// @dev inlined interface
+contract ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+contract ERC20 is Dummy, ERC165, ERC20Events {
+ /// @dev EVM selector for this function is: 0xdd62ed3e,
+ /// or in textual repr: allowance(address,address)
+ function allowance(address owner, address spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+
+ /// @dev EVM selector for this function is: 0x095ea7b3,
+ /// or in textual repr: approve(address,uint256)
+ function approve(address spender, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ spender;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ /// @dev EVM selector for this function is: 0x70a08231,
+ /// or in textual repr: balanceOf(address)
+ function balanceOf(address owner) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ dummy;
+ return 0;
+ }
+
+ /// @dev EVM selector for this function is: 0x313ce567,
+ /// or in textual repr: decimals()
+ function decimals() public view returns (uint8) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @dev EVM selector for this function is: 0x18160ddd,
+ /// or in textual repr: totalSupply()
+ function totalSupply() public view returns (uint256) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+
+ /// @dev EVM selector for this function is: 0xa9059cbb,
+ /// or in textual repr: transfer(address,uint256)
+ function transfer(address to, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ /// @dev EVM selector for this function is: 0x23b872dd,
+ /// or in textual repr: transferFrom(address,address,uint256)
+ function transferFrom(
+ address from,
+ address to,
+ uint256 amount
+ ) public returns (bool) {
+ require(false, stub_error);
+ from;
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+}
+
+contract UniqueNativeFungible is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -11,7 +11,7 @@
use sp_runtime::DispatchError;
use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags};
-use crate::{pallet::Config, CommonCollectionOperations, CollectionHandle};
+use crate::{pallet::Config, CommonCollectionOperations};
// TODO: move to benchmarking
/// Price of [`dispatch_tx`] call with noop `call` argument
@@ -34,16 +34,11 @@
collection: CollectionId,
call: C,
) -> DispatchResultWithPostInfo {
- let handle =
- CollectionHandle::try_get(collection).map_err(|error| DispatchErrorWithPostInfo {
- post_info: PostDispatchInfo {
- actual_weight: Some(dispatch_weight::<T>()),
- pays_fee: Pays::Yes,
- },
- error,
- })?;
- handle
- .check_is_internal()
+ let dispatched = T::CollectionDispatch::dispatch(collection)
+ .and_then(|dispatched| {
+ dispatched.check_is_internal()?;
+ Ok(dispatched)
+ })
.map_err(|error| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(dispatch_weight::<T>()),
@@ -51,7 +46,6 @@
},
error,
})?;
- let dispatched = T::CollectionDispatch::dispatch(handle);
let mut result = call(dispatched.as_dyn());
match &mut result {
Ok(PostDispatchInfo {
@@ -72,6 +66,9 @@
/// Interface for working with different collections through the dispatcher.
pub trait CollectionDispatch<T: Config> {
+ /// Check if the collection is internal.
+ fn check_is_internal(&self) -> DispatchResult;
+
/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
///
/// * `sender` - The user who will become the owner of the collection.
@@ -87,15 +84,14 @@
///
/// * `sender` - The owner of the collection.
/// * `handle` - Collection handle.
- fn destroy(sender: T::CrossAccountId, handle: CollectionHandle<T>) -> DispatchResult;
+ fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult;
/// Get a specialized collection from the handle.
///
/// * `handle` - Collection handle.
- fn dispatch(handle: CollectionHandle<T>) -> Self;
-
- /// Get the collection handle for the corresponding implementation.
- fn into_inner(self) -> CollectionHandle<T>;
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>
+ where
+ Self: Sized;
/// Get the implementation of [`CommonCollectionOperations`].
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -72,7 +72,6 @@
dispatch::Pays,
transactional, fail,
};
-use pallet_evm::GasWeightMapping;
use up_data_structs::{
AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,
RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,
@@ -105,7 +104,7 @@
/// Collection handle contains information about collection data and id.
/// Also provides functionality to count consumed gas.
///
-/// CollectionHandle is used as a generic wrapper for collections of all types.
+/// CollectionHandle is used as a generic wrapper for collections of all types (except native fungible).
/// It allows to perform common operations and queries on any collection type,
/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].
#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
@@ -129,11 +128,7 @@
impl<T: Config> CollectionHandle<T> {
/// Same as [CollectionHandle::new] but with an explicit gas limit.
pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
- <CollectionById<T>>::get(id).map(|collection| Self {
- id,
- collection,
- recorder: SubstrateRecorder::new(gas_limit),
- })
+ Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))
}
/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].
@@ -161,14 +156,7 @@
&self,
reads: u64,
) -> pallet_evm_coder_substrate::execution::Result<()> {
- self.recorder
- .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
- <T as frame_system::Config>::DbWeight::get()
- .read
- .saturating_mul(reads),
- // TODO: measure proof
- 0,
- )))
+ self.recorder().consume_store_reads(reads)
}
/// Consume gas for writing.
@@ -176,14 +164,7 @@
&self,
writes: u64,
) -> pallet_evm_coder_substrate::execution::Result<()> {
- self.recorder
- .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
- <T as frame_system::Config>::DbWeight::get()
- .write
- .saturating_mul(writes),
- // TODO: measure proof
- 0,
- )))
+ self.recorder().consume_store_writes(writes)
}
/// Consume gas for reading and writing.
@@ -192,15 +173,8 @@
reads: u64,
writes: u64,
) -> pallet_evm_coder_substrate::execution::Result<()> {
- let weight = <T as frame_system::Config>::DbWeight::get();
- let reads = weight.read.saturating_mul(reads);
- let writes = weight.read.saturating_mul(writes);
- self.recorder
- .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
- reads.saturating_add(writes),
- // TODO: measure proof
- 0,
- )))
+ self.recorder()
+ .consume_store_reads_and_writes(reads, writes)
}
/// Save collection to storage.
@@ -469,6 +443,8 @@
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
+ /// Collection id for native fungible collction.
+ pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -37,7 +37,7 @@
ExitError, ExitRevert, ExitSucceed, GasWeightMapping, PrecompileFailure, PrecompileOutput,
PrecompileResult, PrecompileHandle,
};
-use sp_core::H160;
+use sp_core::{Get, H160};
// #[cfg(feature = "runtime-benchmarks")]
// pub mod benchmarking;
pub mod execution;
@@ -204,6 +204,40 @@
Err(Error::Error(e)) => Err(e.into()),
})
}
+
+ /// Consume gas for reading.
+ pub fn consume_store_reads(&self, reads: u64) -> execution::Result<()> {
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ <T as frame_system::Config>::DbWeight::get()
+ .read
+ .saturating_mul(reads),
+ // TODO: measure proof
+ 0,
+ )))
+ }
+
+ /// Consume gas for writing.
+ pub fn consume_store_writes(&self, writes: u64) -> execution::Result<()> {
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ <T as frame_system::Config>::DbWeight::get()
+ .write
+ .saturating_mul(writes),
+ // TODO: measure proof
+ 0,
+ )))
+ }
+
+ /// Consume gas for reading and writing.
+ pub fn consume_store_reads_and_writes(&self, reads: u64, writes: u64) -> execution::Result<()> {
+ let weight = <T as frame_system::Config>::DbWeight::get();
+ let reads = weight.read.saturating_mul(reads);
+ let writes = weight.read.saturating_mul(writes);
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ reads.saturating_add(writes),
+ // TODO: measure proof
+ 0,
+ )))
+ }
}
pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> execution::Error {
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -109,7 +109,8 @@
.recorder
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+ <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
}
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -12,6 +12,7 @@
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
+pallet-balances = { workspace = true }
pallet-common = { workspace = true }
pallet-evm = { workspace = true }
pallet-evm-coder-substrate = { workspace = true }
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -166,7 +166,11 @@
#[pallet::config]
pub trait Config:
- frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config
+ frame_system::Config
+ + pallet_common::Config
+ + pallet_structure::Config
+ + pallet_evm::Config
+ + pallet_balances::Config
{
type WeightInfo: WeightInfo;
}
@@ -1325,11 +1329,15 @@
}
fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {
- <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);
+ if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <TokenChildren<T>>::insert((under.0, under.1, to_nest), true);
+ }
}
fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {
- <TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+ if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+ }
}
fn collection_has_tokens(collection_id: CollectionId) -> bool {
pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -42,7 +42,7 @@
},
CollectionFlags::default(),
)?;
- let dispatch = T::CollectionDispatch::dispatch(CollectionHandle::try_get(CollectionId(1))?);
+ let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?;
let dispatch = dispatch.as_dyn();
dispatch.create_item(caller_cross.clone(), caller_cross.clone(), CreateItemData::NFT(CreateNftData::default()), &Unlimited)?;
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -60,7 +60,7 @@
use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
use frame_support::fail;
pub use pallet::*;
-use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use pallet_common::{dispatch::CollectionDispatch};
use up_data_structs::{
CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,
};
@@ -155,11 +155,10 @@
token: TokenId,
) -> Result<Parent<T::CrossAccountId>, DispatchError> {
// TODO: Reduce cost by not reading collection config
- let handle = match CollectionHandle::try_get(collection) {
+ let handle = match T::CollectionDispatch::dispatch(collection) {
Ok(v) => v,
Err(_) => return Ok(Parent::TokenNotFound),
};
- let handle = T::CollectionDispatch::dispatch(handle);
let handle = handle.as_dyn();
Ok(match handle.token_owner(token) {
@@ -279,8 +278,7 @@
self_budget: &dyn Budget,
breadth_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
- let handle = <CollectionHandle<T>>::try_get(collection)?;
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
}
@@ -405,9 +403,7 @@
return Ok(())
};
- let handle = <CollectionHandle<T>>::try_get(collection)?;
-
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
action(dispatch, token)
pallets/unique/Cargo.tomldiffbeforeafterboth--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -24,6 +24,7 @@
'frame-benchmarking/std',
'frame-support/std',
'frame-system/std',
+ 'pallet-balances-adapter/std',
'pallet-common/std',
'pallet-evm-coder-substrate/std',
'pallet-evm/std',
@@ -46,6 +47,7 @@
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-common = { workspace = true }
pallet-evm = { workspace = true }
pallet-evm-coder-substrate = { workspace = true }
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -440,6 +440,10 @@
collection_id: CollectionId,
address: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
@@ -467,6 +471,10 @@
collection_id: CollectionId,
address: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
@@ -493,6 +501,9 @@
collection_id: CollectionId,
new_owner: T::AccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let new_owner = T::CrossAccountId::from_sub(new_owner);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -522,6 +533,9 @@
collection_id: CollectionId,
new_admin_id: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
@@ -548,6 +562,9 @@
collection_id: CollectionId,
account_id: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
@@ -573,6 +590,9 @@
collection_id: CollectionId,
new_sponsor: T::AccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.set_sponsor(&sender, new_sponsor.clone())
@@ -597,6 +617,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = ensure_signed(origin)?;
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.confirm_sponsorship(&sender)
@@ -617,6 +640,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.remove_sponsor(&sender)
@@ -894,6 +920,9 @@
collection_id: CollectionId,
value: bool,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_internal()?;
@@ -1146,6 +1175,9 @@
collection_id: CollectionId,
new_limit: CollectionLimits,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)
@@ -1170,6 +1202,9 @@
collection_id: CollectionId,
new_permission: CollectionPermissions,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)
@@ -1238,6 +1273,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
ensure_root(origin)?;
<PalletCommon<T>>::repair_collection(collection_id)
}
@@ -1296,10 +1334,7 @@
sender: T::CrossAccountId,
collection_id: CollectionId,
) -> DispatchResult {
- let collection = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_internal()?;
-
- T::CollectionDispatch::destroy(sender, collection)?;
+ T::CollectionDispatch::destroy(sender, collection_id)?;
// TODO: basket cleanup should be moved elsewhere
// Maybe runtime dispatch.rs should perform it?
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+use alloc::string::{String, ToString};
use frame_support::parameter_types;
use sp_runtime::traits::AccountIdConversion;
use crate::{
@@ -23,9 +24,10 @@
weights::CommonWeights,
RelayChainBlockNumberProvider,
},
- Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, Balances,
+ Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, DECIMALS,
+ Balances,
};
-use frame_support::traits::{ConstU32, ConstU64};
+use frame_support::traits::{ConstU32, ConstU64, Currency};
use up_common::{
types::{AccountId, Balance, BlockNumber},
constants::*,
@@ -51,8 +53,8 @@
pub mod preimage;
parameter_types! {
+ pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
- pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
}
impl pallet_common::Config for Runtime {
@@ -85,6 +87,21 @@
}
parameter_types! {
+ pub const Decimals: u8 = DECIMALS;
+ pub Name: String = RUNTIME_NAME.to_string();
+ pub Symbol: String = TOKEN_SYMBOL.to_string();
+}
+impl pallet_balances_adapter::Config for Runtime {
+ type Inspect = Balances;
+ type Mutate = Balances;
+ type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+ type Decimals = Decimals;
+ type Name = Name;
+ type Symbol = Symbol;
+ type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
+}
+
+parameter_types! {
pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
}
runtime/common/construct_runtime.rsdiffbeforeafterboth--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -106,6 +106,8 @@
Maintenance: pallet_maintenance = 154,
+ BalancesAdapter: pallet_balances_adapter = 155,
+
#[cfg(feature = "pallet-test-utils")]
TestUtils: pallet_test_utils = 255,
}
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use frame_support::{dispatch::DispatchResult, ensure};
+use frame_support::{dispatch::DispatchResult, ensure, fail};
use pallet_evm::{PrecompileHandle, PrecompileResult};
use sp_core::H160;
use sp_runtime::DispatchError;
@@ -25,6 +25,7 @@
};
pub use pallet_common::dispatch::CollectionDispatch;
use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
+use pallet_balances_adapter::{NativeFungibleHandle};
use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
use pallet_refungible::{
Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
@@ -39,20 +40,35 @@
pub enum CollectionDispatchT<T>
where
- T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
+ T: pallet_fungible::Config
+ + pallet_nonfungible::Config
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
{
Fungible(FungibleHandle<T>),
Nonfungible(NonfungibleHandle<T>),
Refungible(RefungibleHandle<T>),
+ NativeFungible(NativeFungibleHandle<T>),
}
+
impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
where
T: pallet_common::Config
+ pallet_unique::Config
+ pallet_fungible::Config
+ pallet_nonfungible::Config
- + pallet_refungible::Config,
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
{
+ fn check_is_internal(&self) -> DispatchResult {
+ match self {
+ Self::Fungible(h) => h.check_is_internal(),
+ Self::Nonfungible(h) => h.check_is_internal(),
+ Self::Refungible(h) => h.check_is_internal(),
+ Self::NativeFungible(h) => h.check_is_internal(),
+ }
+ }
+
fn create(
sender: T::CrossAccountId,
payer: T::CrossAccountId,
@@ -83,7 +99,13 @@
Ok(id)
}
- fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+ fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+
match collection.mode {
CollectionMode::ReFungible => {
PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
@@ -98,20 +120,19 @@
Ok(())
}
- fn dispatch(handle: CollectionHandle<T>) -> Self {
- match handle.mode {
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError> {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ return Ok(Self::NativeFungible(
+ NativeFungibleHandle::new_with_gas_limit(u64::MAX),
+ ));
+ }
+
+ let handle = <CollectionHandle<T>>::try_get(collection_id)?;
+ Ok(match handle.mode {
CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
- }
- }
-
- fn into_inner(self) -> CollectionHandle<T> {
- match self {
- Self::Fungible(f) => f.into_inner(),
- Self::Nonfungible(f) => f.into_inner(),
- Self::Refungible(f) => f.into_inner(),
- }
+ })
}
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
@@ -119,6 +140,7 @@
Self::Fungible(h) => h,
Self::Nonfungible(h) => h,
Self::Refungible(h) => h,
+ Self::NativeFungible(h) => h,
}
}
}
@@ -129,7 +151,8 @@
+ pallet_unique::Config
+ pallet_fungible::Config
+ pallet_nonfungible::Config
- + pallet_refungible::Config,
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,
{
fn is_reserved(target: &H160) -> bool {
@@ -166,14 +189,19 @@
}
fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
- let collection =
- <CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
- let dispatched = Self::dispatch(collection);
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <NativeFungibleHandle<T>>::new_with_gas_limit(handle.remaining_gas()).call(handle)
+ } else {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(
+ collection_id,
+ handle.remaining_gas(),
+ )?;
- match dispatched {
- Self::Fungible(h) => h.call(handle),
- Self::Nonfungible(h) => h.call(handle),
- Self::Refungible(h) => h.call(handle),
+ match collection.mode {
+ CollectionMode::Fungible(_) => FungibleHandle::cast(collection).call(handle),
+ CollectionMode::NFT => NonfungibleHandle::cast(collection).call(handle),
+ CollectionMode::ReFungible => RefungibleHandle::cast(collection).call(handle),
+ }
}
} else if let Some((collection_id, token_id)) =
<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -17,7 +17,7 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
- let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+ let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch($collection)?;
let dispatch = collection.as_dyn();
Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
runtime/common/weights/mod.rsdiffbeforeafterboth--- a/runtime/common/weights/mod.rs
+++ b/runtime/common/weights/mod.rs
@@ -18,6 +18,9 @@
use frame_support::{weights::Weight};
use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo};
+use pallet_balances_adapter::{
+ Config as NativeFungibleConfig, common::CommonWeights as NativeFungibleWeights,
+};
use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights};
use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights};
@@ -32,6 +35,7 @@
macro_rules! max_weight_of {
($method:ident ( $($args:tt)* )) => {{
let max_weight = <FungibleWeights<T>>::$method($($args)*)
+ .max(<NativeFungibleWeights<T>>::$method($($args)*))
.max(<NonfungibleWeights<T>>::$method($($args)*));
#[cfg(feature = "refungible")]
@@ -42,16 +46,22 @@
}
#[cfg(not(feature = "refungible"))]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {}
+pub trait CommonWeightConfigs: FungibleConfig + NativeFungibleConfig + NonfungibleConfig {}
#[cfg(not(feature = "refungible"))]
-impl<T: FungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
#[cfg(feature = "refungible")]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+pub trait CommonWeightConfigs:
+ FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig
+{
+}
#[cfg(feature = "refungible")]
-impl<T: FungibleConfig + NonfungibleConfig + RefungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig>
+ CommonWeightConfigs for T
+{
+}
pub struct CommonWeights<T>(PhantomData<T>);
runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -18,9 +18,10 @@
[features]
default = ['opal-runtime', 'std']
limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
-opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'pallet-test-utils', 'refungible']
+opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'pallet-test-utils', 'preimage', 'refungible']
pov-estimate = []
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'cumulus-pallet-parachain-system/runtime-benchmarks',
'frame-benchmarking',
'frame-support/runtime-benchmarks',
@@ -41,7 +42,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -70,8 +70,8 @@
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -79,6 +79,7 @@
'fp-rpc/std',
'fp-self-contained/std',
'pallet-app-promotion/std',
+ 'pallet-balances-adapter/std',
'pallet-base-fee/std',
'pallet-charge-transaction/std',
'pallet-collator-selection/std',
@@ -128,8 +129,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
- "orml-xtokens/std",
"orml-xcm-support/std",
+ "orml-xtokens/std",
"pallet-foreign-assets/std",
'pallet-maintenance/std',
@@ -139,8 +140,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -158,6 +159,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-base-fee/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
@@ -189,8 +191,8 @@
app-promotion = []
collator-selection = []
foreign-assets = []
-preimage = []
pallet-test-utils = []
+preimage = []
refungible = []
scheduler = []
@@ -216,8 +218,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -258,6 +260,7 @@
log = { workspace = true }
num_enum = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-base-fee = { workspace = true }
pallet-charge-transaction = { workspace = true }
pallet-collator-selection = { workspace = true }
@@ -291,11 +294,11 @@
################################################################################
# Optional dependencies
-frame-system-benchmarking = { workspace = true, optional = true}
frame-benchmarking = { workspace = true, optional = true }
+frame-system-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
# Test dependencies
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "opal";
pub const TOKEN_SYMBOL: &str = "OPL";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/quartz/Cargo.tomldiffbeforeafterboth--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -22,6 +22,7 @@
pov-estimate = []
quartz-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'refungible']
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'cumulus-pallet-parachain-system/runtime-benchmarks',
'frame-benchmarking',
'frame-support/runtime-benchmarks',
@@ -42,7 +43,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -66,13 +66,14 @@
'frame-try-runtime/std',
'pallet-aura/std',
'pallet-balances/std',
+ 'pallet-balances-adapter/std',
# 'pallet-contracts/std',
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
"pallet-identity/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -127,8 +128,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
+ "orml-xcm-support/std",
"orml-xtokens/std",
- "orml-xcm-support/std",
"pallet-foreign-assets/std",
"pallet-maintenance/std",
]
@@ -136,8 +137,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -154,6 +155,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
'pallet-configuration/try-runtime',
@@ -208,8 +210,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -246,6 +248,7 @@
fp-evm = { workspace = true }
log = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-collator-selection = { workspace = true }
pallet-common = { workspace = true }
pallet-configuration = { workspace = true }
@@ -283,11 +286,11 @@
################################################################################
# Optional dependencies
+frame-benchmarking = { workspace = true, optional = true }
frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -48,6 +48,7 @@
#[cfg(not(feature = "become-sapphire"))]
pub const RUNTIME_NAME: &str = "quartz";
pub const TOKEN_SYMBOL: &str = "QTZ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/tests/Cargo.tomldiffbeforeafterboth--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -26,6 +26,7 @@
pallet-ethereum = { workspace = true }
pallet-evm = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-common = { workspace = true }
pallet-fungible = { workspace = true }
pallet-nonfungible = { workspace = true }
@@ -41,5 +42,3 @@
evm-coder = { workspace = true }
up-sponsorship = { workspace = true }
xcm = { workspace = true }
-pallet-xcm = { workspace = true }
-pallet-configuration = { workspace = true }
runtime/tests/src/lib.rsdiffbeforeafterboth--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -19,7 +19,7 @@
use sp_core::{H160, H256, U256};
use frame_support::{
parameter_types,
- traits::{Everything, ConstU32, ConstU64},
+ traits::{Everything, ConstU32, ConstU64, Currency},
weights::IdentityFee,
pallet_prelude::Weight,
};
@@ -266,6 +266,19 @@
impl pallet_nonfungible::Config for Test {
type WeightInfo = ();
}
+parameter_types! {
+ pub const Decimals: u8 = 18;
+ pub Name: String = "Test".to_string();
+ pub Symbol: String = "TST".to_string();
+}
+impl pallet_balances_adapter::Config for Test {
+ type Currency = Balances;
+ type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+ type Decimals = Decimals;
+ type Name = Name;
+ type Symbol = Symbol;
+ type WeightInfo = ();
+}
parameter_types! {
// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
runtime/unique/Cargo.tomldiffbeforeafterboth--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -20,6 +20,7 @@
limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
pov-estimate = []
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'frame-benchmarking',
'frame-support/runtime-benchmarks',
'frame-system-benchmarking',
@@ -39,7 +40,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -68,9 +68,9 @@
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
"pallet-identity/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -78,6 +78,7 @@
'fp-rpc/std',
'fp-self-contained/std',
'pallet-app-promotion/std',
+ 'pallet-balances-adapter/std',
'pallet-base-fee/std',
'pallet-charge-transaction/std',
'pallet-collator-selection/std',
@@ -124,8 +125,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
+ "orml-xcm-support/std",
"orml-xtokens/std",
- "orml-xcm-support/std",
"pallet-foreign-assets/std",
"pallet-maintenance/std",
]
@@ -134,8 +135,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -152,6 +153,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
'pallet-configuration/try-runtime',
@@ -207,8 +209,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -244,6 +246,7 @@
derivative = { workspace = true }
log = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-collator-selection = { workspace = true }
pallet-common = { workspace = true }
pallet-configuration = { workspace = true }
@@ -282,11 +285,11 @@
################################################################################
# Optional dependencies
+frame-benchmarking = { workspace = true, optional = true }
frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
# Test dependencies
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "unique";
pub const TOKEN_SYMBOL: &str = "UNQ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
tests/.eslintrc.jsondiffbeforeafterboth--- a/tests/.eslintrc.json
+++ b/tests/.eslintrc.json
@@ -86,6 +86,14 @@
"array-bracket-spacing": "warn",
"template-curly-spacing": "warn",
"space-in-parens": "warn",
+ "arrow-body-style": ["warn", "as-needed"],
+ "no-restricted-syntax": [
+ "warn",
+ {
+ "message": "toHuman results in horrible, hard to debug conversions with no stability guarantees, use Codec/at least .toJson instead",
+ "selector": "MemberExpression > Identifier[name=\"toHuman\"]"
+ }
+ ],
"@typescript-eslint/naming-convention": [
"warn",
{
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -39,12 +39,14 @@
"testParallelFull": "yarn testParallel && yarn testSequential",
"testParallel": "yarn _testParallel './src/**/*.test.ts'",
"testSequential": "yarn _test './src/**/*.seqtest.ts'",
- "testStructure": "yarn setup && yarn _test ./**/nesting/*.*test.ts",
- "testEth": "yarn setup && yarn _test './**/eth/**/*.*test.ts'",
- "testEthNesting": "yarn setup && yarn _test './**/eth/nesting/**/*.*test.ts'",
- "testEthFractionalizer": "yarn setup && yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
- "testEthMarketplace": "yarn setup && yarn _test './**/eth/marketplace/**/*.*test.ts'",
- "testEvent": "yarn setup && yarn _test ./src/check-event/*.*test.ts",
+ "testStructure": "yarn _test ./**/nesting/*.*test.ts",
+ "testEth": "yarn _test './**/eth/**/*.*test.ts'",
+ "testEthNesting": "yarn _test './**/eth/nesting/**/*.*test.ts'",
+ "testEthFractionalizer": "yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
+ "testEthMarketplace": "yarn _test './**/eth/marketplace/**/*.*test.ts'",
+ "testSub": "yarn _test './**/sub/**/*.*test.ts'",
+ "testSubNesting": "yarn _test './**/sub/nesting/**/*.*test.ts'",
+ "testEvent": "yarn _test ./src/check-event/*.*test.ts",
"testEthPayable": "yarn _test './**/eth/payable.test.ts'",
"testEvmCoder": "yarn _test './**/eth/evmCoder.test.ts'",
"testNesting": "yarn _test ./**/nest.test.ts",
tests/src/addCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
let donor: IKeyringPair;
@@ -82,7 +83,7 @@
itSub("Can't add collection admin of not existing collection.", async ({helper}) => {
const [alice, bob] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.addAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejectedWith(/common\.CollectionNotFound/);
tests/src/allowLists.test.tsdiffbeforeafterboth--- a/tests/src/allowLists.test.ts
+++ b/tests/src/allowLists.test.ts
@@ -16,7 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub} from './util';
-import {ICollectionPermissions} from './util/playgrounds/types';
+import {ICollectionPermissions, NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test ext. Allow list tests', () => {
let alice: IKeyringPair;
@@ -60,7 +60,7 @@
describe('Negative', () => {
itSub('Nobody can add address to allow list of non-existing collection', async ({helper}) => {
- const collectionId = (1<<32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.nft.addToAllowList(bob, collectionId, {Substrate: charlie.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
@@ -140,7 +140,7 @@
});
itSub('Nobody can remove address from allow list of non-existing collection', async ({helper}) => {
- const collectionId = (1<<32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeFromAllowList(bob, collectionId, {Substrate: charlie.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/benchmarks/utils/common.tsdiffbeforeafterboth--- a/tests/src/benchmarks/utils/common.ts
+++ b/tests/src/benchmarks/utils/common.ts
@@ -91,4 +91,4 @@
await collection.setTokenPropertyPermissions(donor, permissions);
return collection;
-}
\ No newline at end of file
+}
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
let alice: IKeyringPair;
@@ -127,7 +128,7 @@
});
itSub('Can\'t change owner of a non-existing collection.', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
const changeOwnerTx = () => helper.collection.changeOwner(bob, collectionId, bob.address);
await expect(changeOwnerTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
async function setSponsorHelper(collection: any, signer: IKeyringPair, sponsorAddress: string) {
await collection.setSponsor(signer, sponsorAddress);
@@ -198,7 +199,7 @@
});
itSub('(!negative test!) Confirm sponsorship for a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
const confirmSponsorshipTx = () => helper.collection.confirmSponsorship(bob, collectionId);
await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/creditFeesToTreasury.seqtest.tsdiffbeforeafterboth--- a/tests/src/creditFeesToTreasury.seqtest.ts
+++ b/tests/src/creditFeesToTreasury.seqtest.ts
@@ -88,7 +88,7 @@
expect(treasuryIncrease).to.be.equal(fee);
});
- itSub.only('Treasury balance increased by failed tx fee', async ({helper}) => {
+ itSub('Treasury balance increased by failed tx fee', async ({helper}) => {
const api = helper.getApi();
await helper.wait.newBlocks(1);
tests/src/eth/abi/nativeFungible.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/abi/nativeFungible.json
@@ -0,0 +1,201 @@
+[
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" },
+ { "internalType": "address", "name": "spender", "type": "address" }
+ ],
+ "name": "allowance",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "spender", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "approve",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" }
+ ],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ }
+ ],
+ "name": "balanceOfCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+ ],
+ "name": "supportsInterface",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transfer",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from", "type": "address" },
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "from",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFromCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
tests/src/eth/api/UniqueNativeFungible.soldiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/api/UniqueNativeFungible.sol
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+ function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x1313556c
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev EVM selector for this function is: 0xec069398,
+ /// or in textual repr: balanceOfCross((address,uint256))
+ function balanceOfCross(CrossAddress memory owner) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x2ada85ff,
+ /// or in textual repr: transferCross((address,uint256),uint256)
+ function transferCross(CrossAddress memory to, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ CrossAddress memory from,
+ CrossAddress memory to,
+ uint256 amount
+ ) external returns (bool);
+}
+
+/// Cross account struct
+struct CrossAddress {
+ address eth;
+ uint256 sub;
+}
+
+/// @dev inlined interface
+interface ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+interface ERC20 is Dummy, ERC165, ERC20Events {
+ /// @dev EVM selector for this function is: 0xdd62ed3e,
+ /// or in textual repr: allowance(address,address)
+ function allowance(address owner, address spender) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x095ea7b3,
+ /// or in textual repr: approve(address,uint256)
+ function approve(address spender, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0x70a08231,
+ /// or in textual repr: balanceOf(address)
+ function balanceOf(address owner) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x313ce567,
+ /// or in textual repr: decimals()
+ function decimals() external view returns (uint8);
+
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() external view returns (string memory);
+
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() external view returns (string memory);
+
+ /// @dev EVM selector for this function is: 0x18160ddd,
+ /// or in textual repr: totalSupply()
+ function totalSupply() external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0xa9059cbb,
+ /// or in textual repr: transfer(address,uint256)
+ function transfer(address to, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0x23b872dd,
+ /// or in textual repr: transferFrom(address,address,uint256)
+ function transferFrom(
+ address from,
+ address to,
+ uint256 amount
+ ) external returns (bool);
+}
+
+interface UniqueNativeFungible is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
tests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -18,7 +18,7 @@
import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
import {itEth, expect} from './util';
-describe.only('evm nft collection sponsoring', () => {
+describe('evm nft collection sponsoring', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
let nominal: bigint;
@@ -319,7 +319,7 @@
});
});
-describe.only('evm RFT collection sponsoring', () => {
+describe('evm RFT collection sponsoring', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
let nominal: bigint;
tests/src/eth/createFTCollection.seqtest.tsdiffbeforeafterboth--- a/tests/src/eth/createFTCollection.seqtest.ts
+++ b/tests/src/eth/createFTCollection.seqtest.ts
@@ -20,7 +20,7 @@
const DECIMALS = 18;
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
tests/src/eth/createFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createFTCollection.test.ts
+++ b/tests/src/eth/createFTCollection.test.ts
@@ -22,7 +22,7 @@
const DECIMALS = 18;
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
@@ -129,7 +129,7 @@
});
});
-describe.only('(!negative tests!) Create FT collection from EVM', () => {
+describe('(!negative tests!) Create FT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -21,7 +21,7 @@
import {COLLECTION_HELPER} from '../util';
-describe.only('Create NFT collection from EVM', () => {
+describe('Create NFT collection from EVM', () => {
let donor: IKeyringPair;
before(async function () {
@@ -143,7 +143,7 @@
});
});
-describe.only('(!negative tests!) Create NFT collection from EVM', () => {
+describe('(!negative tests!) Create NFT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -21,7 +21,7 @@
import {CollectionLimitField} from './util/playgrounds/types';
-describe.only('Create RFT collection from EVM', () => {
+describe('Create RFT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
@@ -154,7 +154,7 @@
});
});
-describe.only('(!negative tests!) Create RFT collection from EVM', () => {
+describe('(!negative tests!) Create RFT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -80,7 +80,7 @@
};
-describe.only('Fractionalizer contract usage', () => {
+describe('Fractionalizer contract usage', () => {
let donor: IKeyringPair;
before(async function() {
tests/src/eth/nativeFungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -0,0 +1,173 @@
+// Copyright 2019-2023 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {UniqueHelper} from '../util/playgrounds/unique';
+
+describe('NativeFungible: ERC20 calls', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth('approve()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const spender = helper.eth.createAccount();
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ await expect(contract.methods.approve(spender, 100).call({from: owner})).to.be.rejectedWith('Approve not supported');
+ });
+
+ itEth('balanceOf()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor, 123n);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balance = await contract.methods.balanceOf(owner).call({from: owner});
+ expect(balance).to.be.eq('123000000000000000000');
+ });
+
+ itEth('decimals()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realDecimals = (await helper.chain.getChainProperties().tokenDecimals)[0].toString();
+ const decimals = await contract.methods.decimals().call({from: owner});
+ expect(decimals).to.be.eq(realDecimals);
+ });
+
+ itEth('name()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realName = await UniqueHelper.detectNetwork(helper.getApi());
+ const name = await contract.methods.name().call({from: owner});
+ expect(name).to.be.eq(realName);
+ });
+
+ itEth('symbol()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realName = (await helper.chain.getChainProperties().tokenSymbol)[0];
+ const name = await contract.methods.symbol().call({from: owner});
+ expect(name).to.be.eq(realName);
+ });
+
+ itEth('totalSupply()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const totalSupplyEth = BigInt(await contract.methods.totalSupply().call({from: owner}));
+ const totalSupplySub = await helper.balance.getTotalIssuance();
+ expect(totalSupplyEth).to.be.eq(totalSupplySub);
+ });
+
+ itEth('transfer()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transfer(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore + 50n).to.be.equal(balanceReceiverAfter);
+ });
+
+ itEth('transferFrom()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transferFrom(owner, receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFrom(receiver, receiver, 50).call({from: owner})).to.be.rejectedWith('ApprovedValueTooLow');
+ });
+});
+
+describe('NativeFungible: ERC20UniqueExtensions calls', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth('transferCross()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferCross(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+ });
+
+ itEth('transferFromCross()', async ({helper}) => {
+ const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner.eth);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferFromCross(owner, receiver, 50).send({from: owner.eth});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFromCross(receiver, receiver, 50).call({from: owner.eth})).to.be.rejectedWith('no permission');
+ });
+});
\ No newline at end of file
tests/src/eth/nativeRpc/estimateGas.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/nativeRpc/estimateGas.test.ts
@@ -0,0 +1,62 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+
+describe('Ethereum native RPC calls', () => {
+ let donor: IKeyringPair;
+ const NATIVE_TOKEN_ADDRESS = '0x17c4e6453cc49aaaaeaca894e6d9683e00000000';
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth.skip('estimate gas', async ({helper}) => {
+ const BALANCE = 100n;
+ const BALANCE_TO_TRANSFER = 90n;
+
+ const owner = await helper.eth.createAccountWithBalance(donor, BALANCE);
+ const recepient = helper.eth.createAccount();
+
+ const web3 = helper.getWeb3();
+ // data: transfer(recepient, 90);
+ const data = web3.eth.abi.encodeFunctionCall({
+ name: 'transfer',
+ type: 'function',
+ inputs: [{
+ type: 'address',
+ name: 'to',
+ },{
+ type: 'uint256',
+ name: 'amount',
+ }],
+ }, [recepient, (BALANCE_TO_TRANSFER * (10n ** 18n)).toString()]);
+
+ const estimateGas = await web3.eth.estimateGas({
+ to: NATIVE_TOKEN_ADDRESS,
+ value: '0x0',
+ data,
+ from: owner,
+ maxFeePerGas: '0x14c9338c61d',
+ });
+
+ expect(estimateGas).to.be.greaterThan(40000).and.to.be.lessThan(60000);
+ });
+});
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -187,4 +187,84 @@
.call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
});
});
+
+ describe('Fungible', () => {
+ async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {
+ if (mode === 'ft') {
+ const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+ await contract.methods.mint(owner, 100n).send({from: owner});
+ return {collectionAddress, contract};
+ }
+
+ // native ft
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+ return {collectionAddress, contract};
+ }
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');
+ });
+ });
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+
+ await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');
+
+ await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');
+ });
+ });
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ await targetContract.methods.setCollectionNesting(false).send({from: owner});
+
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ if (testCase.mode === 'ft') {
+ await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
+ } else {
+ await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;
+ }
+ });
+ });
+ });
});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -929,7 +929,7 @@
});
});
- itEth.only('Returns collection name', async ({helper}) => {
+ itEth('Returns collection name', async ({helper}) => {
// FIXME: should not have balance to use .call()
const caller = await helper.eth.createAccountWithBalance(donor);
const tokenPropertyPermissions = [{
tests/src/eth/payable.test.tsdiffbeforeafterboth--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -41,7 +41,7 @@
expect(await contract.methods.getCollected().call()).to.be.equal('10000');
});
- itEth.only('Evm contract can receive wei from substrate account', async ({helper}) => {
+ itEth('Evm contract can receive wei from substrate account', async ({helper}) => {
const deployer = await helper.eth.createAccountWithBalance(donor);
const contract = await helper.eth.deployCollectorContract(deployer);
const [alice] = await helper.arrange.createAccounts([40n], donor);
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -679,7 +679,7 @@
});
});
- itEth.only('Returns collection name', async ({helper}) => {
+ itEth('Returns collection name', async ({helper}) => {
// FIXME: should not have balance to use .call()
const caller = await helper.eth.createAccountWithBalance(alice);
const tokenPropertyPermissions = [{
tests/src/eth/tokenProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -314,7 +314,7 @@
expect(result.length).to.equal(0);
}));
- itEth.only('Can be read', async({helper}) => {
+ itEth('Can be read', async({helper}) => {
// FIXME: User with no balance should be able to call
const caller = await helper.eth.createAccountWithBalance(alice);
const collection = await helper.nft.mintCollection(alice, {
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -22,6 +22,7 @@
// Native contracts ABI
import collectionHelpersAbi from '../../abi/collectionHelpers.json' assert {type: 'json'};
+import nativeFungibleAbi from '../../abi/nativeFungible.json' assert {type: 'json'};
import fungibleAbi from '../../abi/fungible.json' assert {type: 'json'};
import fungibleDeprecatedAbi from '../../abi/fungibleDeprecated.json' assert {type: 'json'};
import nonFungibleAbi from '../../abi/nonFungible.json' assert {type: 'json'};
@@ -112,7 +113,6 @@
data: object,
from: signer,
gas: gas ?? this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
return await contract.deploy({data: object}).send({from: signer});
}
@@ -121,30 +121,33 @@
class NativeContractGroup extends EthGroupBase {
- async contractHelpers(caller: string): Promise<Contract> {
+ contractHelpers(caller: string) {
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {
from: caller,
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
}
- async collectionHelpers(caller: string) {
+ collectionHelpers(caller: string) {
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {
from: caller,
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
}
- async collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
- let abi = {
- 'nft': nonFungibleAbi,
- 'rft': refungibleAbi,
- 'ft': fungibleAbi,
- }[mode];
+ collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
+ let abi;
+ if (address === this.helper.ethAddress.fromCollectionId(0)) {
+ abi = nativeFungibleAbi;
+ } else {
+ abi ={
+ 'nft': nonFungibleAbi,
+ 'rft': refungibleAbi,
+ 'ft': fungibleAbi,
+ }[mode];
+ }
if (mergeDeprecated) {
const deprecated = {
'nft': nonFungibleDeprecatedAbi,
@@ -156,7 +159,6 @@
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(abi as any, address, {
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
...(caller ? {from: caller} : {}),
});
}
@@ -165,12 +167,11 @@
return this.collection(this.helper.ethAddress.fromCollectionId(collectionId), mode, caller, mergeDeprecated);
}
- async rftToken(address: string, caller?: string, mergeDeprecated = false) {
+ rftToken(address: string, caller?: string, mergeDeprecated = false) {
const web3 = this.helper.getWeb3();
const abi = mergeDeprecated ? [...refungibleTokenAbi, ...refungibleTokenDeprecatedAbi] : refungibleTokenAbi;
return new web3.eth.Contract(abi as any, address, {
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
...(caller ? {from: caller} : {}),
});
}
@@ -210,6 +211,7 @@
async sendEVM(signer: IKeyringPair, contractAddress: string, abi: string, value: string, gasLimit?: number) {
if (!gasLimit) gasLimit = this.DEFAULT_GAS;
const web3 = this.helper.getWeb3();
+ // FIXME: can't send legacy transaction using tx.evm.call
const gasPrice = await web3.eth.getGasPrice();
// TODO: check execution status
await this.helper.executeExtrinsic(
@@ -377,7 +379,7 @@
class EthAddressGroup extends EthGroupBase {
extractCollectionId(address: string): number {
if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
- return parseInt(address.substr(address.length - 8), 16);
+ return parseInt(address.slice(address.length - 8), 16);
}
fromCollectionId(collectionId: number): string {
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -325,7 +325,7 @@
expect(await helper.preimage.getPreimageInfo(preimageHashes[0])).to.have.property('unrequested');
});
- itSub.only('Does not allow execution of a preimage that would fail', async ({helper}) => {
+ itSub('Does not allow execution of a preimage that would fail', async ({helper}) => {
const [zeroAccount] = await helper.arrange.createAccounts([0n], superuser);
const preimage = helper.constructApiCall('api.tx.balances.forceTransfer', [
tests/src/nativeFungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/nativeFungible.test.ts
@@ -0,0 +1,221 @@
+// Copyright 2019-2023 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, usingPlaygrounds} from './util';
+
+describe('Native fungible', () => {
+ let root: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ root = await privateKey('//Alice');
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
+ });
+ });
+
+ itSub('destroy_collection()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.burn(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ await expect(helper.executeExtrinsic(alice, 'api.tx.unique.destroyCollection', [0])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('add_to_allow_list()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.addToAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_from_allow_list()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeFromAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('change_collection_owner()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.changeOwner(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('add_collection_admin()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.addAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_collection_admin()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_sponsor()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setSponsor(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('confirm_sponsorship()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.confirmSponsorship(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_collection_sponsor()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeSponsor(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('create_item()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.mint(alice, 100n, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_properties()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setProperties(alice, [{key: 'value'}])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('delete_collection_properties()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.deleteProperties(alice, ['key'])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_token_properties()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.setTokenProperties',
+ [0, 0, [{key: 'value'}]],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('delete_token_properties()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.deleteTokenProperties',
+ [0, 0, ['key']],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_transfers_enabled_flag()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.setTransfersEnabledFlag',
+ [0, true],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('burn_item()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.burnItem',
+ [0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('burn_from()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.burnTokens(alice, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('transfer()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+ const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+ await collection.transfer(alice, {Substrate: bob.address}, 100n);
+ const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+ const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+ expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+ expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+ });
+
+ itSub('transfer_from()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+ const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+
+ await collection.transferFrom(alice, {Substrate: alice.address}, {Substrate: bob.address}, 100n);
+
+ const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+ const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+ expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+ expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+
+ await expect(collection.transferFrom(alice, {Substrate: bob.address}, {Substrate: alice.address}, 100n)).to.be.rejectedWith('common.ApprovedValueTooLow');
+ });
+
+ itSub('approve()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.approveTokens(alice, {Substrate: bob.address}, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('approve_from()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.approveFrom',
+ [{Substrate: alice.address}, {Substrate: bob.address}, 0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_limits()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setLimits(alice, {accountTokenOwnershipLimit: 1})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_permissions()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setPermissions(alice, {access: 'AllowList'})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('repartition()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.repartition',
+ [0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('unique.RepartitionCalledOnNonRefungibleCollection');
+ });
+
+ itSub('force_repair_collection()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.forceRepairCollection',
+ [0],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('force_repair_item()', async ({helper}) => {
+ await expect(helper.getSudo().executeExtrinsic(
+ root,
+ 'api.tx.unique.forceRepairItem',
+ [0, 0],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('Nest into NFT token()', async ({helper}) => {
+ const nftCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await nftCollection.mintToken(alice);
+
+ const collection = helper.ft.getCollectionObject(0);
+ await collection.transfer(alice, targetToken.nestingAccount(), 100n);
+
+ await collection.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 100n);
+ });
+});
\ No newline at end of file
tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -47,6 +47,20 @@
await expect(nestedToken.getOwner()).to.be.rejected;
});
+ itSub('NativeFungible: allows the owner to successfully unnest a token', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await collection.mintToken(alice);
+
+ const collectionFT = helper.ft.getCollectionObject(0);
+
+ // Nest
+ await collectionFT.transfer(alice, targetToken.nestingAccount(), 10n);
+ // Unnest
+ await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;
+
+ expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
+ });
+
itSub('Fungible: allows the owner to successfully unnest a token', async ({helper}) => {
const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
const targetToken = await collection.mintToken(alice);
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -19,6 +19,7 @@
// Pallets that must always be present
const requiredPallets = [
'balances',
+ 'balancesadapter',
'common',
'timestamp',
'transactionpayment',
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
let alice: IKeyringPair;
@@ -68,7 +69,7 @@
});
itSub('Can\'t remove collection admin from not existing collection', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
tests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionSponsor.test.ts
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('integration test: ext. removeCollectionSponsor():', () => {
let donor: IKeyringPair;
@@ -91,7 +92,7 @@
});
itSub('(!negative test!) Remove sponsor for a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeSponsor(alice, collectionId)).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/setCollectionLimits.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -17,6 +17,7 @@
// https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
const accountTokenOwnershipLimit = 0;
const sponsoredDataSize = 0;
@@ -110,7 +111,7 @@
});
itSub('execute setCollectionLimits for not exists collection', async ({helper}) => {
- const nonExistentCollectionId = (1 << 32) - 1;
+ const nonExistentCollectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setLimits(
alice,
nonExistentCollectionId,
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('integration test: ext. setCollectionSponsor():', () => {
let alice: IKeyringPair;
@@ -105,7 +106,7 @@
});
itSub('(!negative test!) Add sponsor to a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setSponsor(alice, collectionId, bob.address))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/setPermissions.test.tsdiffbeforeafterboth--- a/tests/src/setPermissions.test.ts
+++ b/tests/src/setPermissions.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test: Set Permissions', () => {
let alice: IKeyringPair;
@@ -85,7 +86,7 @@
});
itSub('fails on not existing collection', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setPermissions(alice, collectionId, {access: 'AllowList', mintMode: true}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -157,7 +157,8 @@
await helper.staking.stake(staker, 1000n * nominal);
await helper.staking.stake(staker, 199n * nominal);
// check balances
- expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}, {id: 'appstake', amount: 1199n * nominal, reasons: 'All'}]);
+ expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}]);
+ expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([{id: 'appstakeappstake', amount: 1199n * nominal}]);
expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 1199n * nominal});
expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(1199n);
expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);
@@ -453,7 +454,7 @@
const [_unstake1, unstake2] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
await helper.wait.forParachainBlockNumber(unstake2.block);
- expect(await helper.balance.getLocked(staker.address)).to.deep.eq([]);
+ expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([]);
expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 0n});
expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(999n);
expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(0n);
tests/src/sub/nesting/admin.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/admin.test.ts
+++ b/tests/src/sub/nesting/admin.test.ts
@@ -17,7 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, usingPlaygrounds} from '../../util';
-describe('Collection admin', () => {
+describe('Nesting by collection admin', () => {
let alice: IKeyringPair;
let bob: IKeyringPair;
let charlie: IKeyringPair;
@@ -84,4 +84,3 @@
expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
});
});
-
tests/src/sub/nesting/common.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/common.test.ts
+++ b/tests/src/sub/nesting/common.test.ts
@@ -21,102 +21,144 @@
let alice: IKeyringPair;
let bob: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+describe('Common nesting tests', () => {
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+ });
});
-});
-[
- {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
- {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
- {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
- {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
- itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
- // Only NFT can be target for nesting in
- const targetNFTCollection = await helper.nft.mintCollection(alice);
- const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ [
+ {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
+ {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
+ {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase => {
+ itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
+ // Only NFT can be target for nesting in
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+ const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
- const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
- // permissions should be set:
- await targetNFTCollection.setPermissions(alice, {
- nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ // 1. Bob can immediately create nested token:
+ const nestedToken1 = testCase.mode === 'nft'
+ ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
+ : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
+ expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+ expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+
+ // 2. Bob can mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(bob);
+ await nestedToken2.nest(bob, targetTokenBob);
+ expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+ expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
});
+ });
- // 1. Bob can immediately create nested token:
- const nestedToken1 = testCase.mode === 'nft'
- ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
- : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
- expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
- // 2. Bob can mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(bob);
- await nestedToken2.nest(bob, targetTokenBob);
- expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+ // Only NFT allows nesting, permissions should be set:
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+ const collectionForNesting = await helper.ft.mintCollection(bob);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
+
+ // 1. Alice can immediately create nested tokens:
+ await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
+ expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+
+ // 2. Alice can mint and nest token:
+ await collectionForNesting.mint(bob, 100n);
+ await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
+ expect(await targetTokenBob.getChildren()).to.be.deep.equal([{collectionId: collectionForNesting.collectionId, tokenId: 0}]);
+ });
});
-});
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`Token owner can nest Native FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+ // Only NFT allows nesting, permissions should be set:
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ expect(await targetTokenBob.getChildren()).to.be.empty;
-[
- {restrictedMode: true},
- {restrictedMode: false},
-].map(testCase => {
- itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
- // Only NFT allows nesting, permissions should be set:
- const targetNFTCollection = await helper.nft.mintCollection(alice);
- const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ const collectionForNesting = helper.ft.getCollectionObject(0);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
- const collectionForNesting = await helper.ft.mintCollection(bob);
- // permissions should be set:
- await targetNFTCollection.setPermissions(alice, {
- nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ // Bob can nest Native FT into their NFT:
+ await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(50n);
+ // Native FT should't be visible in NFT children:
+ expect(await targetTokenBob.getChildren()).to.be.deep.equal([]);
});
+ });
+
- // 1. Alice can immediately create nested tokens:
- await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
- expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
- expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+ itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
+ const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const tokenA = await collectionToNest.mintToken(alice);
+ const tokenB = await collectionToNest.mintToken(alice);
- // 2. Alice can mint and nest token:
- await collectionForNesting.mint(bob, 100n);
- await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
- expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
- });
-});
+ // Create a nested token
+ const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const nativeFtCollectionToBeNested = helper.ft.getCollectionObject(0);
+ const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
+ const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
+ const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
+ await nativeFtCollectionToBeNested.transfer(alice, tokenA.nestingAccount(), 100n);
+ expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+ expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+ expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
-itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
- const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const tokenA = await collectionToNest.mintToken(alice);
- const tokenB = await collectionToNest.mintToken(alice);
+ expect(await tokenA.getChildren()).to.be.length(3);
+ expect(await tokenB.getChildren()).to.be.length(0);
- // Create a nested token
- const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ // Transfer the nested token to another token
+ await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
+ await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ await nativeFtCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
- const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
- const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
- const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
- expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
- expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
- expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+ expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+ expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
- // Transfer the nested token to another token
- await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
- await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
- await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
+ expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
- expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
- expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
+ // RFT, FT, and without native FT
+ expect(await tokenA.getChildren()).to.be.length(2);
+ // NFT, RFT, FT, and without native FT
+ expect(await tokenB.getChildren()).to.be.length(3);
+ });
});
tests/src/sub/nesting/e2e.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/e2e.test.ts
+++ b/tests/src/sub/nesting/e2e.test.ts
@@ -32,6 +32,7 @@
const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
const collectionB = await helper.ft.mintCollection(alice);
const collectionC = await helper.rft.mintCollection(alice);
+ const collectionNative = helper.ft.getCollectionObject(0);
const targetToken = await collectionA.mintToken(alice);
expect((await targetToken.getChildren()).length).to.be.equal(0, 'Children length check at creation');
@@ -73,6 +74,14 @@
{tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
]).and.has.length(3);
+ // Nest native fungible token into another collection
+ await collectionNative.transfer(alice, targetToken.nestingAccount(), 2n);
+ expect(await targetToken.getChildren()).to.have.deep.members([
+ {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},
+ {tokenId: 0, collectionId: collectionB.collectionId},
+ {tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
+ ]).and.has.length(3);
+
// Burn all nested pieces
await tokenC.burnFrom(alice, targetToken.nestingAccount(), 2n);
expect(await targetToken.getChildren()).to.have.deep.members([
tests/src/sub/nesting/nesting.negative.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/nesting.negative.test.ts
+++ b/tests/src/sub/nesting/nesting.negative.test.ts
@@ -23,238 +23,272 @@
let bob: IKeyringPair;
let charlie: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+describe('Negative Test: Nesting', () => {
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+ });
});
-});
-[
- {mode: 'nft' as const, requiredPallets: []},
- {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
- itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
- // Create default collection, permissions are not set:
- const aliceNFTCollection = await helper.nft.mintCollection(alice);
- const targetToken = await aliceNFTCollection.mintToken(alice);
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase => {
+ itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const aliceNFTCollection = await helper.nft.mintCollection(alice);
+ const targetToken = await aliceNFTCollection.mintToken(alice);
- const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
+ const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
- // 1. Alice cannot create immediately nested tokens:
- const nestingTx = testCase.mode === 'nft'
- ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
- : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
- await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // 1. Alice cannot create immediately nested tokens:
+ const nestingTx = testCase.mode === 'nft'
+ ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
+ : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
+ await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
- // 2. Alice cannot mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(alice);
- await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // 2. Alice cannot mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(alice);
+ await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ });
});
-});
-itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => {
- // Create default collection, permissions are not set:
- const aliceNFTCollection = await helper.nft.mintCollection(alice);
- const targetToken = await aliceNFTCollection.mintToken(alice);
+ [
+ {mode: 'ft'},
+ {mode: 'nativeFt'},
+ ].map(testCase => {
+ itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled (except for native fungible collection)`, async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const aliceNFTCollection = await helper.nft.mintCollection(alice);
+ const targetToken = await aliceNFTCollection.mintToken(alice);
- const collectionForNesting = await helper.ft.mintCollection(alice);
+ const collectionForNesting = testCase.mode === 'ft' ? await helper.ft.mintCollection(alice) : helper.ft.getCollectionObject(0);
- // 1. Alice cannot create immediately nested tokens:
- await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // Alice cannot create immediately nested tokens:
+ if (testCase.mode === 'ft') {
+ await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ } else {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.not.rejected;
+ }
- // 2. Alice can mint and nest token:
- await collectionForNesting.mint(alice, 100n);
- await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
-});
+ // Alice can't mint and nest tokens:
+ if (testCase.mode === 'ft') {
+ await collectionForNesting.mint(alice, 100n);
+ }
-[
- {mode: 'nft' as const},
- {mode: 'rft' as const},
- {mode: 'ft' as const},
-].map(testCase => {
- itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => {
- const targetCollection = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, collectionAdmin: true}},
+ if (testCase.mode === 'ft') {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ } else {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.not.rejected;
+ }
});
- const targetToken = await targetCollection.mintToken(alice);
+ });
+
+ [
+ {mode: 'nft' as const},
+ {mode: 'rft' as const},
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens (except for native fungible collection)`, async ({helper}) => {
+ const targetCollection = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, collectionAdmin: true}},
+ });
+ const targetToken = await targetCollection.mintToken(alice);
- const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+ const nestedCollectionBob = await (
+ testCase.mode === 'native ft'
+ ? helper.ft.getCollectionObject(0)
+ : helper[testCase.mode].mintCollection(bob)
+ );
- let nestedTokenBob: UniqueNFToken | UniqueRFToken;
- switch (testCase.mode) {
- case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
- case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
- case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
- }
+ let nestedTokenBob: UniqueNFToken | UniqueRFToken;
+ switch (testCase.mode) {
+ case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
+ case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
+ case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n)).to.be.rejectedWith('common.UnsupportedOperation'); break;
+ }
- // Bob non-owner of targetToken and non admin of targetCollection, so
- // 1. cannot mint nested token:
- switch (testCase.mode) {
- case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- }
+ // Bob non-owner of targetToken and non admin of targetCollection, so
+ // 1. cannot mint nested token:
+ switch (testCase.mode) {
+ case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UnsupportedOperation'); break;
+ }
- // 2. cannot nest existing token:
- switch (testCase.mode) {
- case 'nft':
- case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- }
+ // 2. cannot nest existing token:
+ switch (testCase.mode) {
+ case 'nft':
+ case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.not.rejected; break;
+ }
+ });
});
-});
-[
- {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},
- {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
-].map(testCase => {
- itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
- // Create collection with tokenOwner or create collection with collectionAdmin permission:
- const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
- const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
- await targetCollection.addAdmin(alice, {Substrate: bob.address});
+ [
+ {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},
+ {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
+ ].map(testCase => {
+ itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
+ // Create collection with tokenOwner or create collection with collectionAdmin permission:
+ const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
+ const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
+ await targetCollection.addAdmin(alice, {Substrate: bob.address});
- const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
- const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
- // if nesting permissions restricted for token owner – minter is bob (admin),
- // if collectionAdmin – charlie (owner)
- testCase.nesting.tokenOwner
- ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
- : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+ const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
+ const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+ // if nesting permissions restricted for token owner – minter is bob (admin),
+ // if collectionAdmin – charlie (owner)
+ testCase.nesting.tokenOwner
+ ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
+ : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+ });
});
-});
-itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice);
- // To avoid UserIsNotAllowedToNest error
- await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
+ itSub.ifWithPallets('Cannot nest in non existing token (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice);
+ // To avoid UserIsNotAllowedToNest error
+ await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
- // The list of non-existing tokens:
- const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
- const tokenBurnt = await collection.mintToken(alice);
- await tokenBurnt.burn(alice);
- const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
+ // The list of non-existing tokens:
+ const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
+ const tokenBurnt = await collection.mintToken(alice);
+ await tokenBurnt.burn(alice);
+ const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
- // The list of collections to nest tokens from:
- const nftCollectionForNesting = await helper.nft.mintCollection(alice);
- const rftCollectionForNesting = await helper.rft.mintCollection(alice);
- const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+ // The list of collections to nest tokens from:
+ const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+ const rftCollectionForNesting = await helper.rft.mintCollection(alice);
+ const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+ const nativeFtCollectionForNesting = helper.ft.getCollectionObject(0);
- const testCases = [
- {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
- {token: tokenBurnt, error: 'TokenNotFound'},
- {token: tokenNotMintedYet, error: 'TokenNotFound'},
- ];
+ const testCases = [
+ {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
+ {token: tokenBurnt, error: 'TokenNotFound'},
+ {token: tokenNotMintedYet, error: 'TokenNotFound'},
+ ];
- for(const testCase of testCases) {
- // 1. Alice cannot create nested token to non-existing token
- await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ for(const testCase of testCases) {
+ // 1. Alice cannot create nested token to non-existing token
+ await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- // 2. Alice cannot mint and nest token:
- const nft = await nftCollectionForNesting.mintToken(alice);
- const rft = await rftCollectionForNesting.mintToken(alice, 100n);
- const _ft = await ftCollectionForNesting.mint(alice, 100n);
- await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
- }
-});
+ // 2. Alice cannot mint and nest token:
+ const nft = await nftCollectionForNesting.mintToken(alice);
+ const rft = await rftCollectionForNesting.mintToken(alice, 100n);
+ const _ft = await ftCollectionForNesting.mint(alice, 100n);
+ await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
+ await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.not.rejected;
+ }
+ });
-itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
- const existingCollection = await helper.nft.mintCollection(alice);
- const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
- const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
+ itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
+ const existingCollection = await helper.nft.mintCollection(alice);
+ const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
+ const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
- const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+ const nftCollectionForNesting = await helper.nft.mintCollection(alice);
- // 1. Alice cannot create nested token to collection address
- await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ // 1. Alice cannot create nested token to collection address
+ await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- // 2. Alice cannot mint and nest token to collection address:
- const nft = await nftCollectionForNesting.mintToken(alice);
- await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
-});
+ // 2. Alice cannot mint and nest token to collection address:
+ const nft = await nftCollectionForNesting.mintToken(alice);
+ await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ });
-itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => {
- // Create default collection, permissions are not set:
- const rftCollection = await helper.rft.mintCollection(alice);
- const ftCollection = await helper.ft.mintCollection(alice);
+ itEth.ifWithPallets('Cannot nest in RFT or FT (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const rftCollection = await helper.rft.mintCollection(alice);
+ const ftCollection = await helper.ft.mintCollection(alice);
+ const nativeFtCollection = helper.ft.getCollectionObject(0);
- const rftToken = await rftCollection.mintToken(alice);
- const _ftToken = await ftCollection.mint(alice, 100n);
+ const rftToken = await rftCollection.mintToken(alice);
+ const _ftToken = await ftCollection.mint(alice, 100n);
- const collectionForNesting = await helper.nft.mintCollection(alice);
+ const collectionForNesting = await helper.nft.mintCollection(alice);
- // 1. Alice cannot create immediately nested tokens:
- await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
- await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ // 1. Alice cannot create immediately nested tokens:
+ await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+ await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.rejectedWith('common.UnsupportedOperation');
- // 2. Alice cannot mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(alice);
- await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
- await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
-});
+ // 2. Alice cannot mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(alice);
+ await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+ await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.not.rejected;
+ });
-itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => {
- const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
- const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
- const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
- const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+ itSub('Cannot nest in restricted collection if collection is not in the list (except native fungible collection)', async ({helper}) => {
+ const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
+ const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
+ const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
+ const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+ const allowedCollectionNativeFT = helper.ft.getCollectionObject(0);
- // Collection restricted to allowedCollectionId
- const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
- });
- // Create collection with restricted nesting -- even self is not allowed
- const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, restricted: []}},
- });
+ // Collection restricted to allowedCollectionId
+ const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
+ });
+ // Create collection with restricted nesting -- even self is not allowed
+ const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, restricted: []}},
+ });
- const targetTokenA = await restrictedCollectionA.mintToken(alice);
- const targetTokenB = await restrictedCollectionB.mintToken(alice);
+ const targetTokenA = await restrictedCollectionA.mintToken(alice);
+ const targetTokenB = await restrictedCollectionB.mintToken(alice);
- // 1. Cannot mint in own collection after allowlisting the accounts:
- await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ // 1. Cannot mint in own collection after allowlisting the accounts:
+ await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- // 2. Cannot mint from notAllowedCollection:
- await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-});
+ // 2. Cannot mint from notAllowedCollection:
+ await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(allowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.not.rejected;
+ await expect(allowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.not.rejected;
+ });
-itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- let token = await collection.mintToken(alice);
+ itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ let token = await collection.mintToken(alice);
- const maxNestingLevel = 5;
+ const maxNestingLevel = 5;
- // Create a nested-token matryoshka
- for (let i = 0; i < maxNestingLevel; i++) {
- token = await collection.mintToken(alice, token.nestingAccount());
- }
+ // Create a nested-token matryoshka
+ for (let i = 0; i < maxNestingLevel; i++) {
+ token = await collection.mintToken(alice, token.nestingAccount());
+ }
- // The nesting depth is limited by `maxNestingLevel`
- // 1. Cannot mint:
- await expect(collection.mintToken(alice, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
- // 2. Cannot transfer:
- const anotherToken = await collection.mintToken(alice);
- await expect(anotherToken.transfer(alice, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
- // 3. Cannot nest FT pieces:
- const ftCollection = await helper.ft.mintCollection(alice);
- await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
+ // The nesting depth is limited by `maxNestingLevel`
+ // 1. Cannot mint:
+ await expect(collection.mintToken(alice, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
+ // 2. Cannot transfer:
+ const anotherToken = await collection.mintToken(alice);
+ await expect(anotherToken.transfer(alice, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
+ // 3. Cannot nest FT pieces:
+ const ftCollection = await helper.ft.mintCollection(alice);
+ await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
- expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await token.getChildren()).to.has.length(0);
+ expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+ expect(await token.getChildren()).to.has.length(0);
+ });
});
tests/src/sub/nesting/refungible.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/refungible.test.ts
+++ b/tests/src/sub/nesting/refungible.test.ts
@@ -17,44 +17,45 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, Pallets, usingPlaygrounds} from '../../util';
-// ReFungible specific nesting tests
-let alice: IKeyringPair;
+describe('ReFungible-specific nesting tests', () => {
+ let alice: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice] = await helper.arrange.createAccounts([200n], donor);
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice] = await helper.arrange.createAccounts([200n], donor);
+ });
});
-});
-itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice, {
- permissions: {
- nesting: {
- tokenOwner: true,
+ itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice, {
+ permissions: {
+ nesting: {
+ tokenOwner: true,
+ },
},
- },
- });
- const collectionRFT = await helper.rft.mintCollection(alice);
+ });
+ const collectionRFT = await helper.rft.mintCollection(alice);
- const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
- const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
+ const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
+ const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
- await rft.transfer(alice, nft.nestingAccount(), 40n);
+ await rft.transfer(alice, nft.nestingAccount(), 40n);
- expect(await rft.getTopmostOwner()).deep.equal(null);
+ expect(await rft.getTopmostOwner()).deep.equal(null);
- await rft.transfer(alice, nft.nestingAccount(), 60n);
+ await rft.transfer(alice, nft.nestingAccount(), 60n);
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
- await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
- expect(await rft.getTopmostOwner()).deep.equal(null);
+ expect(await rft.getTopmostOwner()).deep.equal(null);
- await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ });
});
tests/src/sub/nesting/unnesting.negative.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/unnesting.negative.test.ts
+++ b/tests/src/sub/nesting/unnesting.negative.test.ts
@@ -52,12 +52,19 @@
});
[
- {restrictedMode: true},
- {restrictedMode: false},
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(md => [
+ {mode: md.mode, restrictedMode: true},
+ {mode: md.mode, restrictedMode: false},
].map(testCase => {
- itSub(`Fungible: disallows a non-Owner to unnest someone else's token ${testCase.restrictedMode ? '(Restricted nesting)' : ''}`, async ({helper}) => {
+ itSub(`Fungible: disallows a non-Owner to unnest someone else's token [${testCase.mode}${testCase.restrictedMode ? ' (Restricted nesting)' : ''}]`, async ({helper}) => {
const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionFT = await helper.ft.mintCollection(alice);
+ const collectionFT = await (
+ testCase.mode === 'ft'
+ ? helper.ft.mintCollection(alice)
+ : helper.ft.getCollectionObject(0)
+ );
const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address});
await collectionNFT.setPermissions(alice, {nesting: {
@@ -65,12 +72,16 @@
}});
// Nest some tokens as Alice into Bob's token
- await collectionFT.mint(alice, 5n, targetToken.nestingAccount());
+ await (
+ testCase.mode === 'ft'
+ ? collectionFT.mint(alice, 5n, targetToken.nestingAccount())
+ : collectionFT.transfer(alice, targetToken.nestingAccount(), 5n)
+ );
// Try to pull it out as Alice still
await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.ApprovedValueTooLow/);
expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
});
- });
+ }));
});
tests/src/transfer.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';2021describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {22 let donor: IKeyringPair;23 let alice: IKeyringPair;24 let bob: IKeyringPair;2526 before(async () => {27 await usingPlaygrounds(async (helper, privateKey) => {28 donor = await privateKey({url: import.meta.url});29 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);30 });31 });3233 itSub('Balance transfers and check balance', async ({helper}) => {34 const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);35 const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3637 expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3839 const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);40 const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4142 expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;43 expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;44 });4546 itSub('Inability to pay fees error message is correct', async ({helper}) => {47 const [zero] = await helper.arrange.createAccounts([0n], donor);4849 // console.error = () => {};50 // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.51 await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))52 .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');53 });5455 itSub('[nft] User can transfer owned token', async ({helper}) => {56 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});57 const nft = await collection.mintToken(alice);5859 await nft.transfer(alice, {Substrate: bob.address});60 expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});61 });6263 itSub('[fungible] User can transfer owned token', async ({helper}) => {64 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});65 await collection.mint(alice, 10n);6667 await collection.transfer(alice, {Substrate: bob.address}, 9n);68 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);69 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);70 });7172 itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {73 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});74 const rft = await collection.mintToken(alice, 10n);7576 await rft.transfer(alice, {Substrate: bob.address}, 9n);77 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);78 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);79 });8081 itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {82 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});83 await collection.addAdmin(alice, {Substrate: bob.address});8485 const nft = await collection.mintToken(bob, {Substrate: bob.address});86 await nft.transfer(bob, {Substrate: alice.address});8788 expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});89 });9091 itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {92 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});93 await collection.addAdmin(alice, {Substrate: bob.address});9495 await collection.mint(bob, 10n, {Substrate: bob.address});96 await collection.transfer(bob, {Substrate: alice.address}, 1n);9798 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);99 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);100 });101102 itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {103 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});104 await collection.addAdmin(alice, {Substrate: bob.address});105106 const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});107 await rft.transfer(bob, {Substrate: alice.address}, 1n);108109 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);110 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);111 });112});113114describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {115 let alice: IKeyringPair;116 let bob: IKeyringPair;117118 before(async () => {119 await usingPlaygrounds(async (helper, privateKey) => {120 const donor = await privateKey({url: import.meta.url});121 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);122 });123 });124125126 itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {127 const collectionId = (1 << 32) - 1;128 await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))129 .to.be.rejectedWith(/common\.CollectionNotFound/);130 });131132 itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133 const collectionId = (1 << 32) - 1;134 await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))135 .to.be.rejectedWith(/common\.CollectionNotFound/);136 });137138 itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {139 const collectionId = (1 << 32) - 1;140 await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))141 .to.be.rejectedWith(/common\.CollectionNotFound/);142 });143144 itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {145 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});146 const nft = await collection.mintToken(alice);147148 await nft.burn(alice);149 await collection.burn(alice);150151 await expect(nft.transfer(alice, {Substrate: bob.address}))152 .to.be.rejectedWith(/common\.CollectionNotFound/);153 });154155 itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {156 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});157 await collection.mint(alice, 10n);158159 await collection.burnTokens(alice, 10n);160 await collection.burn(alice);161162 await expect(collection.transfer(alice, {Substrate: bob.address}))163 .to.be.rejectedWith(/common\.CollectionNotFound/);164 });165166 itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {167 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});168 const rft = await collection.mintToken(alice, 10n);169170 await rft.burn(alice, 10n);171 await collection.burn(alice);172173 await expect(rft.transfer(alice, {Substrate: bob.address}))174 .to.be.rejectedWith(/common\.CollectionNotFound/);175 });176177 itSub('[nft] Transfer with not existed item_id', async ({helper}) => {178 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});179 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))180 .to.be.rejectedWith(/common\.TokenNotFound/);181 });182183 itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {184 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});185 await expect(collection.transfer(alice, {Substrate: bob.address}))186 .to.be.rejectedWith(/common\.TokenValueTooLow/);187 });188189 itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {190 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});191 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))192 .to.be.rejectedWith(/common\.TokenValueTooLow/);193 });194195 itSub('Zero transfer NFT', async ({helper}) => {196 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});197 const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});198 const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});199 // 1. Zero transfer of own tokens allowed:200 await helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0]);201 // 2. Zero transfer of non-owned tokens not allowed:202 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');203 // 3. Zero transfer of non-existing tokens not allowed:204 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, 10, 0])).to.be.rejectedWith('common.TokenNotFound');205 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});206 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});207 // 4. Storage is not corrupted:208 await tokenAlice.transfer(alice, {Substrate: bob.address});209 await tokenBob.transfer(bob, {Substrate: alice.address});210 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});211 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});212 });213214 itSub('[nft] Transfer with deleted item_id', async ({helper}) => {215 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});216 const nft = await collection.mintToken(alice);217218 await nft.burn(alice);219220 await expect(nft.transfer(alice, {Substrate: bob.address}))221 .to.be.rejectedWith(/common\.TokenNotFound/);222 });223224 itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {225 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});226 await collection.mint(alice, 10n);227228 await collection.burnTokens(alice, 10n);229230 await expect(collection.transfer(alice, {Substrate: bob.address}))231 .to.be.rejectedWith(/common\.TokenValueTooLow/);232 });233234 itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {235 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});236 const rft = await collection.mintToken(alice, 10n);237238 await rft.burn(alice, 10n);239240 await expect(rft.transfer(alice, {Substrate: bob.address}))241 .to.be.rejectedWith(/common\.TokenValueTooLow/);242 });243244 itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {245 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});246 const nft = await collection.mintToken(alice);247248 await expect(nft.transfer(bob, {Substrate: bob.address}))249 .to.be.rejectedWith(/common\.NoPermission/);250 expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});251 });252253 itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {254 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});255 await collection.mint(alice, 10n);256257 await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))258 .to.be.rejectedWith(/common\.TokenValueTooLow/);259 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);260 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);261 });262263 itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {264 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});265 const rft = await collection.mintToken(alice, 10n);266267 await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))268 .to.be.rejectedWith(/common\.TokenValueTooLow/);269 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);270 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);271 });272});273274describe('Transfers to self (potentially over substrate-evm boundary)', () => {275 let donor: IKeyringPair;276277 before(async function() {278 await usingEthPlaygrounds(async (_, privateKey) => {279 donor = await privateKey({url: import.meta.url});280 });281 });282283 itEth('Transfers to self. In case of same frontend', async ({helper}) => {284 const [owner] = await helper.arrange.createAccounts([10n], donor);285 const collection = await helper.ft.mintCollection(owner, {});286 await collection.mint(owner, 100n);287288 const ownerProxy = helper.address.substrateToEth(owner.address);289290 // transfer to own proxy291 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);292 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);293 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);294295 // transfer-from own proxy to own proxy again296 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);297 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);298 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);299 });300301 itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {302 const [owner] = await helper.arrange.createAccounts([10n], donor);303 const collection = await helper.ft.mintCollection(owner, {});304 await collection.mint(owner, 100n);305306 const ownerProxy = helper.address.substrateToEth(owner.address);307308 // transfer to own proxy309 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);310 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);311 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);312313 // transfer-from own proxy to self314 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);315 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);316 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);317 });318319 itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {320 const [owner] = await helper.arrange.createAccounts([10n], donor);321 const collection = await helper.ft.mintCollection(owner, {});322 await collection.mint(owner, 100n);323324 // transfer to self again325 await collection.transfer(owner, {Substrate: owner.address}, 10n);326 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);327328 // transfer-from self to self again329 await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);330 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);331 });332333 itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {334 const [owner] = await helper.arrange.createAccounts([10n], donor);335 const collection = await helper.ft.mintCollection(owner, {});336 await collection.mint(owner, 10n);337338 // transfer to self again339 await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))340 .to.be.rejectedWith(/common\.TokenValueTooLow/);341342 // transfer-from self to self again343 await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))344 .to.be.rejectedWith(/common\.TokenValueTooLow/);345 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);346 });347});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';20import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';2122describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {23 let donor: IKeyringPair;24 let alice: IKeyringPair;25 let bob: IKeyringPair;2627 before(async () => {28 await usingPlaygrounds(async (helper, privateKey) => {29 donor = await privateKey({url: import.meta.url});30 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);31 });32 });3334 itSub('Balance transfers and check balance', async ({helper}) => {35 const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);36 const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3738 expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3940 const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);41 const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4243 expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;44 expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;45 });4647 itSub('Inability to pay fees error message is correct', async ({helper}) => {48 const [zero] = await helper.arrange.createAccounts([0n], donor);4950 // console.error = () => {};51 // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.52 await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))53 .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');54 });5556 itSub('[nft] User can transfer owned token', async ({helper}) => {57 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});58 const nft = await collection.mintToken(alice);5960 await nft.transfer(alice, {Substrate: bob.address});61 expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});62 });6364 itSub('[fungible] User can transfer owned token', async ({helper}) => {65 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});66 await collection.mint(alice, 10n);6768 await collection.transfer(alice, {Substrate: bob.address}, 9n);69 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);70 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);71 });7273 itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {74 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});75 const rft = await collection.mintToken(alice, 10n);7677 await rft.transfer(alice, {Substrate: bob.address}, 9n);78 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);79 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);80 });8182 itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {83 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});84 await collection.addAdmin(alice, {Substrate: bob.address});8586 const nft = await collection.mintToken(bob, {Substrate: bob.address});87 await nft.transfer(bob, {Substrate: alice.address});8889 expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});90 });9192 itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {93 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});94 await collection.addAdmin(alice, {Substrate: bob.address});9596 await collection.mint(bob, 10n, {Substrate: bob.address});97 await collection.transfer(bob, {Substrate: alice.address}, 1n);9899 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);100 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);101 });102103 itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {104 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});105 await collection.addAdmin(alice, {Substrate: bob.address});106107 const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});108 await rft.transfer(bob, {Substrate: alice.address}, 1n);109110 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);111 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);112 });113});114115describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {116 let alice: IKeyringPair;117 let bob: IKeyringPair;118119 before(async () => {120 await usingPlaygrounds(async (helper, privateKey) => {121 const donor = await privateKey({url: import.meta.url});122 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);123 });124 });125126127 itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {128 await expect(helper.nft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))129 .to.be.rejectedWith(/common\.CollectionNotFound/);130 });131132 itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133 await expect(helper.ft.transfer(alice, NON_EXISTENT_COLLECTION_ID, {Substrate: bob.address}))134 .to.be.rejectedWith(/common\.CollectionNotFound/);135 });136137 itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {138 await expect(helper.rft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))139 .to.be.rejectedWith(/common\.CollectionNotFound/);140 });141142 itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {143 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});144 const nft = await collection.mintToken(alice);145146 await nft.burn(alice);147 await collection.burn(alice);148149 await expect(nft.transfer(alice, {Substrate: bob.address}))150 .to.be.rejectedWith(/common\.CollectionNotFound/);151 });152153 itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {154 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});155 await collection.mint(alice, 10n);156157 await collection.burnTokens(alice, 10n);158 await collection.burn(alice);159160 await expect(collection.transfer(alice, {Substrate: bob.address}))161 .to.be.rejectedWith(/common\.CollectionNotFound/);162 });163164 itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {165 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});166 const rft = await collection.mintToken(alice, 10n);167168 await rft.burn(alice, 10n);169 await collection.burn(alice);170171 await expect(rft.transfer(alice, {Substrate: bob.address}))172 .to.be.rejectedWith(/common\.CollectionNotFound/);173 });174175 itSub('[nft] Transfer with not existed item_id', async ({helper}) => {176 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});177 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))178 .to.be.rejectedWith(/common\.TokenNotFound/);179 });180181 itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {182 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});183 await expect(collection.transfer(alice, {Substrate: bob.address}))184 .to.be.rejectedWith(/common\.TokenValueTooLow/);185 });186187 itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {188 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});189 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))190 .to.be.rejectedWith(/common\.TokenValueTooLow/);191 });192193 itSub('Zero transfer NFT', async ({helper}) => {194 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});195 const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});196 const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});197 // 1. Zero transfer of own tokens allowed:198 await helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0]);199 // 2. Zero transfer of non-owned tokens not allowed:200 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');201 // 3. Zero transfer of non-existing tokens not allowed:202 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, 10, 0])).to.be.rejectedWith('common.TokenNotFound');203 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});204 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});205 // 4. Storage is not corrupted:206 await tokenAlice.transfer(alice, {Substrate: bob.address});207 await tokenBob.transfer(bob, {Substrate: alice.address});208 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});209 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});210 });211212 itSub('[nft] Transfer with deleted item_id', async ({helper}) => {213 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});214 const nft = await collection.mintToken(alice);215216 await nft.burn(alice);217218 await expect(nft.transfer(alice, {Substrate: bob.address}))219 .to.be.rejectedWith(/common\.TokenNotFound/);220 });221222 itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {223 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});224 await collection.mint(alice, 10n);225226 await collection.burnTokens(alice, 10n);227228 await expect(collection.transfer(alice, {Substrate: bob.address}))229 .to.be.rejectedWith(/common\.TokenValueTooLow/);230 });231232 itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {233 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});234 const rft = await collection.mintToken(alice, 10n);235236 await rft.burn(alice, 10n);237238 await expect(rft.transfer(alice, {Substrate: bob.address}))239 .to.be.rejectedWith(/common\.TokenValueTooLow/);240 });241242 itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {243 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});244 const nft = await collection.mintToken(alice);245246 await expect(nft.transfer(bob, {Substrate: bob.address}))247 .to.be.rejectedWith(/common\.NoPermission/);248 expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});249 });250251 itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {252 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});253 await collection.mint(alice, 10n);254255 await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))256 .to.be.rejectedWith(/common\.TokenValueTooLow/);257 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);258 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);259 });260261 itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {262 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});263 const rft = await collection.mintToken(alice, 10n);264265 await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))266 .to.be.rejectedWith(/common\.TokenValueTooLow/);267 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);268 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);269 });270});271272describe('Transfers to self (potentially over substrate-evm boundary)', () => {273 let donor: IKeyringPair;274275 before(async function() {276 await usingEthPlaygrounds(async (_, privateKey) => {277 donor = await privateKey({url: import.meta.url});278 });279 });280281 itEth('Transfers to self. In case of same frontend', async ({helper}) => {282 const [owner] = await helper.arrange.createAccounts([10n], donor);283 const collection = await helper.ft.mintCollection(owner, {});284 await collection.mint(owner, 100n);285286 const ownerProxy = helper.address.substrateToEth(owner.address);287288 // transfer to own proxy289 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);290 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);291 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);292293 // transfer-from own proxy to own proxy again294 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);295 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);296 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);297 });298299 itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {300 const [owner] = await helper.arrange.createAccounts([10n], donor);301 const collection = await helper.ft.mintCollection(owner, {});302 await collection.mint(owner, 100n);303304 const ownerProxy = helper.address.substrateToEth(owner.address);305306 // transfer to own proxy307 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);308 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);309 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);310311 // transfer-from own proxy to self312 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);313 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);314 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);315 });316317 itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {318 const [owner] = await helper.arrange.createAccounts([10n], donor);319 const collection = await helper.ft.mintCollection(owner, {});320 await collection.mint(owner, 100n);321322 // transfer to self again323 await collection.transfer(owner, {Substrate: owner.address}, 10n);324 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);325326 // transfer-from self to self again327 await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);328 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);329 });330331 itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {332 const [owner] = await helper.arrange.createAccounts([10n], donor);333 const collection = await helper.ft.mintCollection(owner, {});334 await collection.mint(owner, 10n);335336 // transfer to self again337 await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))338 .to.be.rejectedWith(/common\.TokenValueTooLow/);339340 // transfer-from self to self again341 await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))342 .to.be.rejectedWith(/common\.TokenValueTooLow/);343 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);344 });345});tests/src/transferFrom.test.tsdiffbeforeafterboth--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
let alice: IKeyringPair;
@@ -97,10 +98,9 @@
});
itSub('transferFrom for a collection that does not exist', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.collection.approveToken(alice, collectionId, 0, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.approveToken(alice, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
- await expect(helper.collection.transferTokenFrom(bob, collectionId, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.transferTokenFrom(bob, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -3,6 +3,8 @@
import {IKeyringPair} from '@polkadot/types/types';
+export const NON_EXISTENT_COLLECTION_ID = 4_294_967_295;
+
export interface IEvent {
section: string;
method: string;
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -7,11 +7,8 @@
import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
import {SignerOptions} from '@polkadot/api/types/submittable';
-import '../../interfaces/augment-api-tx';
+import '../../interfaces/augment-api';
import {AugmentedSubmittables} from '@polkadot/api-base/types/submittable';
-import {RpcInterface} from '@polkadot/rpc-core/types';
-import {QueryableStorage} from '@polkadot/api-base/types/storage';
-import {DecoratedRpc} from '@polkadot/api-base/types/rpc';
import {ApiInterfaceEvents} from '@polkadot/api/types';
import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';
import {IKeyringPair} from '@polkadot/types/types';
@@ -50,7 +47,7 @@
} from './types';
import {RuntimeDispatchInfo} from '@polkadot/types/interfaces';
import type {Vec} from '@polkadot/types-codec';
-import {FrameSystemEventRecord} from '@polkadot/types/lookup';
+import {FrameSystemEventRecord, PalletBalancesIdAmount} from '@polkadot/types/lookup';
export class CrossAccountId {
Substrate!: TSubstrateAccount;
@@ -379,15 +376,6 @@
type Get2<T, P extends string, E> =
P extends `${infer Key}.${infer Key2}` ? Key extends keyof T ? Key2 extends keyof T[Key] ? T[Key][Key2] : E : E : E;
type ForceFunction<T> = T extends (...args: any) => any ? T : (...args: any) => Invalid<'not a function'>;
-type ReturnTypeWithArgs<T extends (...args: any[]) => any, ARGS_T> =
- Extract<
- T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; (...args: infer A4): infer R4; } ? [A1, R1] | [A2, R2] | [A3, R3] | [A4, R4] :
- T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; (...args: infer A3): infer R3; } ? [A1, R1] | [A2, R2] | [A3, R3] :
- T extends { (...args: infer A1): infer R1; (...args: infer A2): infer R2; } ? [A1, R1] | [A2, R2] :
- T extends { (...args: infer A1): infer R1; } ? [A1, R1] :
- never,
- [ARGS_T, any]
- >[1]
export class ChainHelperBase {
helperBase: any;
@@ -498,7 +486,7 @@
if (xcmChains.indexOf(spec.specName) > -1) return spec.specName;
- if (['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName;
+ if (['quartz', 'unique', 'sapphire'].indexOf(spec.specName) > -1) return spec.specName;
return 'opal';
}
@@ -677,12 +665,12 @@
async executeExtrinsic<
E extends string,
V extends (
-...args: any) => any = ForceFunction<
- Get2<
- AugmentedSubmittables<'promise'>,
- E, (...args: any) => Invalid<'not found'>
+ ...args: any) => any = ForceFunction<
+ Get2<
+ AugmentedSubmittables<'promise'>,
+ E, (...args: any) => Invalid<'not found'>
+ >
>
- >
>(
sender: TSigner,
extrinsic: `api.tx.${E}`,
@@ -1756,9 +1744,7 @@
children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]);
}
- return children.toJSON().map((x: any) => {
- return {collectionId: x.collection, tokenId: x.token};
- });
+ return children.toJSON().map((x: any) => ({collectionId: x.collection, tokenId: x.token}));
}
/**
@@ -2400,14 +2386,22 @@
return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
}
+ /**
+ * Get total issuance
+ * @returns
+ */
+ async getTotalIssuance(): Promise<bigint> {
+ const total = (await this.helper.callRpc('api.query.balances.totalIssuance', []));
+ return total.toBigInt();
+ }
+
async getLocked(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint, reason: string }]> {
const locks = (await this.helper.callRpc('api.query.balances.locks', [address])).toHuman();
- return locks.map((lock: any) => { return {id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}; });
+ return locks.map((lock: any) => ({id: lock.id, amount: BigInt(lock.amount.replace(/,/g, '')), reasons: lock.reasons}));
}
-
- async getFrozen(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint}]> {
- const locks = (await this.helper.callRpc('api.query.balances.freezes', [address])).toHuman();
- return locks.map((lock: any) => { return {id: lock.id, amount: BigInt(lock.amount.replace(/,/g, ''))}; });
+ async getFrozen(address: TSubstrateAccount): Promise<{ id: string, amount: bigint }[]> {
+ const locks = await this.helper.api!.query.balances.freezes(address);
+ return locks.map(lock => ({id: lock.id.toString(), amount: lock.amount.toBigInt()}));
}
}
@@ -2493,9 +2487,18 @@
}
/**
+ * Get total issuance
+ * @returns
+ */
+ getTotalIssuance(): Promise<bigint> {
+ return this.subBalanceGroup.getTotalIssuance();
+ }
+
+ /**
* Get locked balances
* @param address substrate address
* @returns locked balances with reason via api.query.balances.locks
+ * @deprecated all the methods should switch to getFrozen
*/
getLocked(address: TSubstrateAccount) {
return this.subBalanceGroup.getLocked(address);
tests/src/vesting.test.tsdiffbeforeafterboth--- a/tests/src/vesting.test.ts
+++ b/tests/src/vesting.test.ts
@@ -103,7 +103,7 @@
expect(balanceSender.reserved).to.eq(0n);
});
- itSub.only('cannot send more tokens than have', async ({helper}) => {
+ itSub('cannot send more tokens than have', async ({helper}) => {
const [sender, receiver] = await helper.arrange.createAccounts([1000n, 1n], donor);
const schedule = {start: 0n, period: 1n, periodCount: 1n, perPeriod: 100n * nominal};
const manyPeriodsSchedule = {start: 0n, period: 1n, periodCount: 100n, perPeriod: 10n * nominal};