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.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 {expect, itSub, Pallets, usingPlaygrounds} from '../../util';19import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../../util/playgrounds/unique';20import {itEth} from '../../eth/util';2122let alice: IKeyringPair;23let bob: IKeyringPair;24let charlie: IKeyringPair;2526before(async () => {27 await usingPlaygrounds(async (helper, privateKey) => {28 const donor = await privateKey({url: import.meta.url});29 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);30 });31});3233[34 {mode: 'nft' as const, requiredPallets: []},35 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},36].map(testCase => {37 itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {38 // Create default collection, permissions are not set:39 const aliceNFTCollection = await helper.nft.mintCollection(alice);40 const targetToken = await aliceNFTCollection.mintToken(alice);4142 const collectionForNesting = await helper[testCase.mode].mintCollection(alice);4344 // 1. Alice cannot create immediately nested tokens:45 const nestingTx = testCase.mode === 'nft'46 ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())47 : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());48 await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');4950 // 2. Alice cannot mint and nest token:51 const nestedToken2 = await collectionForNesting.mintToken(alice);52 await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');53 });54});5556itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => {57 // Create default collection, permissions are not set:58 const aliceNFTCollection = await helper.nft.mintCollection(alice);59 const targetToken = await aliceNFTCollection.mintToken(alice);6061 const collectionForNesting = await helper.ft.mintCollection(alice);6263 // 1. Alice cannot create immediately nested tokens:64 await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');6566 // 2. Alice can mint and nest token:67 await collectionForNesting.mint(alice, 100n);68 await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');69});7071[72 {mode: 'nft' as const},73 {mode: 'rft' as const},74 {mode: 'ft' as const},75].map(testCase => {76 itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => {77 const targetCollection = await helper.nft.mintCollection(alice, {permissions:78 {nesting: {tokenOwner: true, collectionAdmin: true}},79 });80 const targetToken = await targetCollection.mintToken(alice);8182 const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);8384 let nestedTokenBob: UniqueNFToken | UniqueRFToken;85 switch (testCase.mode) {86 case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;87 case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;88 case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;89 }9091 // Bob non-owner of targetToken and non admin of targetCollection, so92 // 1. cannot mint nested token:93 switch (testCase.mode) {94 case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;95 case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;96 case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;97 }9899 // 2. cannot nest existing token:100 switch (testCase.mode) {101 case 'nft':102 case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;103 case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;104 }105 });106});107108[109 {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},110 {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},111].map(testCase => {112 itSub(`${testCase.nesting.tokenOwner'Admin''Token owner'} cannot nest when only ${testCase.nesting.tokenOwner'tokenOwner''collectionAdmin'} is allowed`, async ({helper}) => {113 // Create collection with tokenOwner or create collection with collectionAdmin permission:114 const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});115 const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});116 await targetCollection.addAdmin(alice, {Substrate: bob.address});117118 const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);119 const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);120 // if nesting permissions restricted for token owner – minter is bob (admin),121 // if collectionAdmin – charlie (owner)122 testCase.nesting.tokenOwner123 ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)124 : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);125 });126});127128itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => {129 const collection = await helper.nft.mintCollection(alice);130 // To avoid UserIsNotAllowedToNest error131 await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});132133 // The list of non-existing tokens:134 const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);135 const tokenBurnt = await collection.mintToken(alice);136 await tokenBurnt.burn(alice);137 const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);138139 // The list of collections to nest tokens from:140 const nftCollectionForNesting = await helper.nft.mintCollection(alice);141 const rftCollectionForNesting = await helper.rft.mintCollection(alice);142 const ftCollectionForNesting = await helper.ft.mintCollection(alice);143144 const testCases = [145 {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},146 {token: tokenBurnt, error: 'TokenNotFound'},147 {token: tokenNotMintedYet, error: 'TokenNotFound'},148 ];149150 for(const testCase of testCases) {151 // 1. Alice cannot create nested token to non-existing token152 await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);153 await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);154 await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);155156 // 2. Alice cannot mint and nest token:157 const nft = await nftCollectionForNesting.mintToken(alice);158 const rft = await rftCollectionForNesting.mintToken(alice, 100n);159 const _ft = await ftCollectionForNesting.mint(alice, 100n);160 await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);161 await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);162 await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);163 }164});165166itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {167 const existingCollection = await helper.nft.mintCollection(alice);168 const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);169 const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);170171 const nftCollectionForNesting = await helper.nft.mintCollection(alice);172173 // 1. Alice cannot create nested token to collection address174 await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');175 await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');176177 // 2. Alice cannot mint and nest token to collection address:178 const nft = await nftCollectionForNesting.mintToken(alice);179 await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');180 await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');181});182183itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => {184 // Create default collection, permissions are not set:185 const rftCollection = await helper.rft.mintCollection(alice);186 const ftCollection = await helper.ft.mintCollection(alice);187188 const rftToken = await rftCollection.mintToken(alice);189 const _ftToken = await ftCollection.mint(alice, 100n);190191 const collectionForNesting = await helper.nft.mintCollection(alice);192193 // 1. Alice cannot create immediately nested tokens:194 await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');195 await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');196197 // 2. Alice cannot mint and nest token:198 const nestedToken2 = await collectionForNesting.mintToken(alice);199 await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');200 await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');201});202203itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => {204 const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);205 const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);206 const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);207 const notAllowedCollectionFT = await helper.ft.mintCollection(alice);208209 // Collection restricted to allowedCollectionId210 const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:211 {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},212 });213 // Create collection with restricted nesting -- even self is not allowed214 const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:215 {nesting: {tokenOwner: true, restricted: []}},216 });217218 const targetTokenA = await restrictedCollectionA.mintToken(alice);219 const targetTokenB = await restrictedCollectionB.mintToken(alice);220221 // 1. Cannot mint in own collection after allowlisting the accounts:222 await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);223 await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);224225 // 2. Cannot mint from notAllowedCollection:226 await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);227 await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);228 await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);229 await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);230 await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);231 await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);232});233234itSub('Cannot create nesting chains greater than 5', async ({helper}) => {235 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});236 let token = await collection.mintToken(alice);237238 const maxNestingLevel = 5;239240 // Create a nested-token matryoshka241 for (let i = 0; i < maxNestingLevel; i++) {242 token = await collection.mintToken(alice, token.nestingAccount());243 }244245 // The nesting depth is limited by `maxNestingLevel`246 // 1. Cannot mint:247 await expect(collection.mintToken(alice, token.nestingAccount()))248 .to.be.rejectedWith(/structure\.DepthLimit/);249 // 2. Cannot transfer:250 const anotherToken = await collection.mintToken(alice);251 await expect(anotherToken.transfer(alice, token.nestingAccount()))252 .to.be.rejectedWith(/structure\.DepthLimit/);253 // 3. Cannot nest FT pieces:254 const ftCollection = await helper.ft.mintCollection(alice);255 await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))256 .to.be.rejectedWith(/structure\.DepthLimit/);257258 expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});259 expect(await token.getChildren()).to.has.length(0);260});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 {expect, itSub, Pallets, usingPlaygrounds} from '../../util';19import {UniqueFTCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../../util/playgrounds/unique';20import {itEth} from '../../eth/util';2122let alice: IKeyringPair;23let bob: IKeyringPair;24let charlie: IKeyringPair;2526describe('Negative Test: Nesting', () => {27 before(async () => {28 await usingPlaygrounds(async (helper, privateKey) => {29 const donor = await privateKey({url: import.meta.url});30 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);31 });32 });3334 [35 {mode: 'nft' as const, requiredPallets: []},36 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},37 ].map(testCase => {38 itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {39 // Create default collection, permissions are not set:40 const aliceNFTCollection = await helper.nft.mintCollection(alice);41 const targetToken = await aliceNFTCollection.mintToken(alice);4243 const collectionForNesting = await helper[testCase.mode].mintCollection(alice);4445 // 1. Alice cannot create immediately nested tokens:46 const nestingTx = testCase.mode === 'nft'47 ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())48 : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());49 await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');5051 // 2. Alice cannot mint and nest token:52 const nestedToken2 = await collectionForNesting.mintToken(alice);53 await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');54 });55 });5657 [58 {mode: 'ft'},59 {mode: 'nativeFt'},60 ].map(testCase => {61 itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled (except for native fungible collection)`, async ({helper}) => {62 // Create default collection, permissions are not set:63 const aliceNFTCollection = await helper.nft.mintCollection(alice);64 const targetToken = await aliceNFTCollection.mintToken(alice);6566 const collectionForNesting = testCase.mode === 'ft' ? await helper.ft.mintCollection(alice) : helper.ft.getCollectionObject(0);6768 // Alice cannot create immediately nested tokens:69 if (testCase.mode === 'ft') {70 await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');71 } else {72 await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.not.rejected;73 }7475 // Alice can't mint and nest tokens:76 if (testCase.mode === 'ft') {77 await collectionForNesting.mint(alice, 100n);78 }7980 if (testCase.mode === 'ft') {81 await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');82 } else {83 await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.not.rejected;84 }85 });86 });8788 [89 {mode: 'nft' as const},90 {mode: 'rft' as const},91 {mode: 'ft' as const},92 {mode: 'native ft' as const},93 ].map(testCase => {94 itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens (except for native fungible collection)`, async ({helper}) => {95 const targetCollection = await helper.nft.mintCollection(alice, {permissions:96 {nesting: {tokenOwner: true, collectionAdmin: true}},97 });98 const targetToken = await targetCollection.mintToken(alice);99100 const nestedCollectionBob = await (101 testCase.mode === 'native ft'102 ? helper.ft.getCollectionObject(0)103 : helper[testCase.mode].mintCollection(bob)104 );105106 let nestedTokenBob: UniqueNFToken | UniqueRFToken;107 switch (testCase.mode) {108 case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;109 case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;110 case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;111 case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n)).to.be.rejectedWith('common.UnsupportedOperation'); break;112 }113114 // Bob non-owner of targetToken and non admin of targetCollection, so115 // 1. cannot mint nested token:116 switch (testCase.mode) {117 case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;118 case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;119 case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;120 case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UnsupportedOperation'); break;121 }122123 // 2. cannot nest existing token:124 switch (testCase.mode) {125 case 'nft':126 case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;127 case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;128 case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.not.rejected; break;129 }130 });131 });132133 [134 {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},135 {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},136 ].map(testCase => {137 itSub(`${testCase.nesting.tokenOwner'Admin''Token owner'} cannot nest when only ${testCase.nesting.tokenOwner'tokenOwner''collectionAdmin'} is allowed`, async ({helper}) => {138 // Create collection with tokenOwner or create collection with collectionAdmin permission:139 const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});140 const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});141 await targetCollection.addAdmin(alice, {Substrate: bob.address});142143 const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);144 const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);145 // if nesting permissions restricted for token owner – minter is bob (admin),146 // if collectionAdmin – charlie (owner)147 testCase.nesting.tokenOwner148 ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)149 : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);150 });151 });152153 itSub.ifWithPallets('Cannot nest in non existing token (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {154 const collection = await helper.nft.mintCollection(alice);155 // To avoid UserIsNotAllowedToNest error156 await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});157158 // The list of non-existing tokens:159 const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);160 const tokenBurnt = await collection.mintToken(alice);161 await tokenBurnt.burn(alice);162 const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);163164 // The list of collections to nest tokens from:165 const nftCollectionForNesting = await helper.nft.mintCollection(alice);166 const rftCollectionForNesting = await helper.rft.mintCollection(alice);167 const ftCollectionForNesting = await helper.ft.mintCollection(alice);168 const nativeFtCollectionForNesting = helper.ft.getCollectionObject(0);169170 const testCases = [171 {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},172 {token: tokenBurnt, error: 'TokenNotFound'},173 {token: tokenNotMintedYet, error: 'TokenNotFound'},174 ];175176 for(const testCase of testCases) {177 // 1. Alice cannot create nested token to non-existing token178 await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);179 await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);180 await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);181182 // 2. Alice cannot mint and nest token:183 const nft = await nftCollectionForNesting.mintToken(alice);184 const rft = await rftCollectionForNesting.mintToken(alice, 100n);185 const _ft = await ftCollectionForNesting.mint(alice, 100n);186 await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);187 await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);188 await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);189 await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.not.rejected;190 }191 });192193 itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {194 const existingCollection = await helper.nft.mintCollection(alice);195 const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);196 const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);197198 const nftCollectionForNesting = await helper.nft.mintCollection(alice);199200 // 1. Alice cannot create nested token to collection address201 await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');202 await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');203204 // 2. Alice cannot mint and nest token to collection address:205 const nft = await nftCollectionForNesting.mintToken(alice);206 await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');207 await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');208 });209210 itEth.ifWithPallets('Cannot nest in RFT or FT (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {211 // Create default collection, permissions are not set:212 const rftCollection = await helper.rft.mintCollection(alice);213 const ftCollection = await helper.ft.mintCollection(alice);214 const nativeFtCollection = helper.ft.getCollectionObject(0);215216 const rftToken = await rftCollection.mintToken(alice);217 const _ftToken = await ftCollection.mint(alice, 100n);218219 const collectionForNesting = await helper.nft.mintCollection(alice);220221 // 1. Alice cannot create immediately nested tokens:222 await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');223 await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');224 await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.rejectedWith('common.UnsupportedOperation');225226 // 2. Alice cannot mint and nest token:227 const nestedToken2 = await collectionForNesting.mintToken(alice);228 await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');229 await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');230 await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.not.rejected;231 });232233 itSub('Cannot nest in restricted collection if collection is not in the list (except native fungible collection)', async ({helper}) => {234 const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);235 const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);236 const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);237 const notAllowedCollectionFT = await helper.ft.mintCollection(alice);238 const allowedCollectionNativeFT = helper.ft.getCollectionObject(0);239240 // Collection restricted to allowedCollectionId241 const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:242 {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},243 });244 // Create collection with restricted nesting -- even self is not allowed245 const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:246 {nesting: {tokenOwner: true, restricted: []}},247 });248249 const targetTokenA = await restrictedCollectionA.mintToken(alice);250 const targetTokenB = await restrictedCollectionB.mintToken(alice);251252 // 1. Cannot mint in own collection after allowlisting the accounts:253 await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);254 await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);255256 // 2. Cannot mint from notAllowedCollection:257 await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);258 await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);259 await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);260 await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);261 await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);262 await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);263 await expect(allowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.not.rejected;264 await expect(allowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.not.rejected;265 });266267 itSub('Cannot create nesting chains greater than 5', async ({helper}) => {268 const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});269 let token = await collection.mintToken(alice);270271 const maxNestingLevel = 5;272273 // Create a nested-token matryoshka274 for (let i = 0; i < maxNestingLevel; i++) {275 token = await collection.mintToken(alice, token.nestingAccount());276 }277278 // The nesting depth is limited by `maxNestingLevel`279 // 1. Cannot mint:280 await expect(collection.mintToken(alice, token.nestingAccount()))281 .to.be.rejectedWith(/structure\.DepthLimit/);282 // 2. Cannot transfer:283 const anotherToken = await collection.mintToken(alice);284 await expect(anotherToken.transfer(alice, token.nestingAccount()))285 .to.be.rejectedWith(/structure\.DepthLimit/);286 // 3. Cannot nest FT pieces:287 const ftCollection = await helper.ft.mintCollection(alice);288 await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))289 .to.be.rejectedWith(/structure\.DepthLimit/);290291 expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});292 expect(await token.getChildren()).to.has.length(0);293 });294});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.tsdiffbeforeafterboth--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -17,6 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itEth, usingEthPlaygrounds} from './eth/util';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {
let donor: IKeyringPair;
@@ -124,20 +125,17 @@
itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.nft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))
+ await expect(helper.ft.transfer(alice, NON_EXISTENT_COLLECTION_ID, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.rft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
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};