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.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{68 Get,69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 },72 dispatch::Pays,73 transactional, fail,74};75use pallet_evm::GasWeightMapping;76use up_data_structs::{77 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,78 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,79 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,80 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,81 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,82 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,83 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,84 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,85 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,86 CollectionPermissions,87};88use up_pov_estimate_rpc::PovInfo;8990pub use pallet::*;91use sp_core::H160;92use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9394#[cfg(feature = "runtime-benchmarks")]95pub mod benchmarking;96pub mod dispatch;97pub mod erc;98pub mod eth;99pub mod helpers;100#[allow(missing_docs)]101pub mod weights;102/// Weight info.103pub type SelfWeightOf<T> = <T as Config>::WeightInfo;104105/// Collection handle contains information about collection data and id.106/// Also provides functionality to count consumed gas.107///108/// CollectionHandle is used as a generic wrapper for collections of all types.109/// It allows to perform common operations and queries on any collection type,110/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].111#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]112pub struct CollectionHandle<T: Config> {113 /// Collection id114 pub id: CollectionId,115 collection: Collection<T::AccountId>,116 /// Substrate recorder for counting consumed gas117 pub recorder: SubstrateRecorder<T>,118}119120impl<T: Config> WithRecorder<T> for CollectionHandle<T> {121 fn recorder(&self) -> &SubstrateRecorder<T> {122 &self.recorder123 }124 fn into_recorder(self) -> SubstrateRecorder<T> {125 self.recorder126 }127}128129impl<T: Config> CollectionHandle<T> {130 /// Same as [CollectionHandle::new] but with an explicit gas limit.131 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {132 <CollectionById<T>>::get(id).map(|collection| Self {133 id,134 collection,135 recorder: SubstrateRecorder::new(gas_limit),136 })137 }138139 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].140 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {141 <CollectionById<T>>::get(id).map(|collection| Self {142 id,143 collection,144 recorder,145 })146 }147148 /// Retrives collection data from storage and creates collection handle with default parameters.149 /// If collection not found return `None`150 pub fn new(id: CollectionId) -> Option<Self> {151 Self::new_with_gas_limit(id, u64::MAX)152 }153154 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.155 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {156 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)157 }158159 /// Consume gas for reading.160 pub fn consume_store_reads(161 &self,162 reads: u64,163 ) -> pallet_evm_coder_substrate::execution::Result<()> {164 self.recorder165 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(166 <T as frame_system::Config>::DbWeight::get()167 .read168 .saturating_mul(reads),169 // TODO: measure proof170 0,171 )))172 }173174 /// Consume gas for writing.175 pub fn consume_store_writes(176 &self,177 writes: u64,178 ) -> pallet_evm_coder_substrate::execution::Result<()> {179 self.recorder180 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(181 <T as frame_system::Config>::DbWeight::get()182 .write183 .saturating_mul(writes),184 // TODO: measure proof185 0,186 )))187 }188189 /// Consume gas for reading and writing.190 pub fn consume_store_reads_and_writes(191 &self,192 reads: u64,193 writes: u64,194 ) -> pallet_evm_coder_substrate::execution::Result<()> {195 let weight = <T as frame_system::Config>::DbWeight::get();196 let reads = weight.read.saturating_mul(reads);197 let writes = weight.read.saturating_mul(writes);198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(200 reads.saturating_add(writes),201 // TODO: measure proof202 0,203 )))204 }205206 /// Save collection to storage.207 pub fn save(&self) -> DispatchResult {208 <CollectionById<T>>::insert(self.id, &self.collection);209 Ok(())210 }211212 /// Set collection sponsor.213 ///214 /// Unique collections allows sponsoring for certain actions.215 /// This method allows you to set the sponsor of the collection.216 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].217 pub fn set_sponsor(218 &mut self,219 sender: &T::CrossAccountId,220 sponsor: T::AccountId,221 ) -> DispatchResult {222 self.check_is_internal()?;223 self.check_is_owner_or_admin(sender)?;224225 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());226227 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));228 <PalletEvm<T>>::deposit_log(229 erc::CollectionHelpersEvents::CollectionChanged {230 collection_id: eth::collection_id_to_address(self.id),231 }232 .to_log(T::ContractAddress::get()),233 );234235 self.save()236 }237238 /// Force set `sponsor`.239 ///240 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation241 /// from the `sponsor` is not required.242 ///243 /// # Arguments244 ///245 /// * `sender`: Caller's account.246 /// * `sponsor`: ID of the account of the sponsor-to-be.247 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {248 self.check_is_internal()?;249250 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());251252 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));253 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));254 <PalletEvm<T>>::deposit_log(255 erc::CollectionHelpersEvents::CollectionChanged {256 collection_id: eth::collection_id_to_address(self.id),257 }258 .to_log(T::ContractAddress::get()),259 );260261 self.save()262 }263264 /// Confirm sponsorship265 ///266 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.267 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].268 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {269 self.check_is_internal()?;270 ensure!(271 self.collection.sponsorship.pending_sponsor() == Some(sender),272 Error::<T>::ConfirmSponsorshipFail273 );274275 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());276277 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));278 <PalletEvm<T>>::deposit_log(279 erc::CollectionHelpersEvents::CollectionChanged {280 collection_id: eth::collection_id_to_address(self.id),281 }282 .to_log(T::ContractAddress::get()),283 );284285 self.save()286 }287288 /// Remove collection sponsor.289 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {290 self.check_is_internal()?;291 self.check_is_owner_or_admin(sender)?;292293 self.collection.sponsorship = SponsorshipState::Disabled;294295 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));296 <PalletEvm<T>>::deposit_log(297 erc::CollectionHelpersEvents::CollectionChanged {298 collection_id: eth::collection_id_to_address(self.id),299 }300 .to_log(T::ContractAddress::get()),301 );302 self.save()303 }304305 /// Force remove `sponsor`.306 ///307 /// Differs from `remove_sponsor` in that308 /// it doesn't require consent from the `owner` of the collection.309 pub fn force_remove_sponsor(&mut self) -> DispatchResult {310 self.check_is_internal()?;311312 self.collection.sponsorship = SponsorshipState::Disabled;313314 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));315 <PalletEvm<T>>::deposit_log(316 erc::CollectionHelpersEvents::CollectionChanged {317 collection_id: eth::collection_id_to_address(self.id),318 }319 .to_log(T::ContractAddress::get()),320 );321 self.save()322 }323324 /// Checks that the collection was created with, and must be operated upon through **Unique API**.325 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.326 pub fn check_is_internal(&self) -> DispatchResult {327 if self.flags.external {328 return Err(<Error<T>>::CollectionIsExternal)?;329 }330331 Ok(())332 }333334 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.335 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.336 pub fn check_is_external(&self) -> DispatchResult {337 if !self.flags.external {338 return Err(<Error<T>>::CollectionIsInternal)?;339 }340341 Ok(())342 }343}344345impl<T: Config> Deref for CollectionHandle<T> {346 type Target = Collection<T::AccountId>;347348 fn deref(&self) -> &Self::Target {349 &self.collection350 }351}352353impl<T: Config> DerefMut for CollectionHandle<T> {354 fn deref_mut(&mut self) -> &mut Self::Target {355 &mut self.collection356 }357}358359impl<T: Config> CollectionHandle<T> {360 /// Checks if the `user` is the owner of the collection.361 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {362 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);363 Ok(())364 }365366 /// Returns **true** if the `user` is the owner or administrator of the collection.367 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {368 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))369 }370371 /// Checks if the `user` is the owner or administrator of the collection.372 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {373 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);374 Ok(())375 }376377 /// Returns **true** if378 /// * the `user`is a collection owner or admin379 /// * the collection limits allow the owner/admins to transfer/burn any collection token380 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {381 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)382 }383384 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.385 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {386 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)387 }388389 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.390 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {391 ensure!(392 <Allowlist<T>>::get((self.id, user)),393 <Error<T>>::AddressNotInAllowlist394 );395 Ok(())396 }397398 /// Changes collection owner to another account399 /// #### Store read/writes400 /// 1 writes401 pub fn change_owner(402 &mut self,403 caller: T::CrossAccountId,404 new_owner: T::CrossAccountId,405 ) -> DispatchResult {406 self.check_is_internal()?;407 self.check_is_owner(&caller)?;408 self.collection.owner = new_owner.as_sub().clone();409410 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(411 self.id,412 new_owner.as_sub().clone(),413 ));414 <PalletEvm<T>>::deposit_log(415 erc::CollectionHelpersEvents::CollectionChanged {416 collection_id: eth::collection_id_to_address(self.id),417 }418 .to_log(T::ContractAddress::get()),419 );420421 self.save()422 }423}424425#[frame_support::pallet]426pub mod pallet {427428 use super::*;429 use dispatch::CollectionDispatch;430 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};431 use up_data_structs::{TokenId, mapping::TokenAddressMapping};432 use scale_info::TypeInfo;433 use weights::WeightInfo;434435 #[pallet::config]436 pub trait Config:437 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo438 {439 /// Weight information for functions of this pallet.440 type WeightInfo: WeightInfo;441442 /// Events compatible with [`frame_system::Config::Event`].443 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;444445 /// Handler of accounts and payment.446 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;447448 /// Set price to create a collection.449 #[pallet::constant]450 type CollectionCreationPrice: Get<451 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,452 >;453454 /// Dispatcher of operations on collections.455 type CollectionDispatch: CollectionDispatch<Self>;456457 /// Account which holds the chain's treasury.458 type TreasuryAccountId: Get<Self::AccountId>;459460 /// Address under which the CollectionHelper contract would be available.461 #[pallet::constant]462 type ContractAddress: Get<H160>;463464 /// Mapper for token addresses to Ethereum addresses.465 type EvmTokenAddressMapping: TokenAddressMapping<H160>;466467 /// Mapper for token addresses to [`CrossAccountId`].468 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;469 }470471 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);472473 #[pallet::pallet]474 #[pallet::storage_version(STORAGE_VERSION)]475 pub struct Pallet<T>(_);476477 #[pallet::extra_constants]478 impl<T: Config> Pallet<T> {479 /// Maximum admins per collection.480 pub fn collection_admins_limit() -> u32 {481 COLLECTION_ADMINS_LIMIT482 }483 }484485 #[pallet::genesis_config]486 pub struct GenesisConfig<T>(PhantomData<T>);487488 #[cfg(feature = "std")]489 impl<T: Config> Default for GenesisConfig<T> {490 fn default() -> Self {491 Self(Default::default())492 }493 }494495 #[pallet::genesis_build]496 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {497 fn build(&self) {498 StorageVersion::new(1).put::<Pallet<T>>();499 }500 }501502 impl<T: Config> Pallet<T> {503 /// Helper function that handles deposit events504 pub fn deposit_event(event: Event<T>) {505 let event = <T as Config>::RuntimeEvent::from(event);506 let event = event.into();507 <frame_system::Pallet<T>>::deposit_event(event)508 }509 }510511 #[pallet::event]512 pub enum Event<T: Config> {513 /// New collection was created514 CollectionCreated(515 /// Globally unique identifier of newly created collection.516 CollectionId,517 /// [`CollectionMode`] converted into _u8_.518 u8,519 /// Collection owner.520 T::AccountId,521 ),522523 /// New collection was destroyed524 CollectionDestroyed(525 /// Globally unique identifier of collection.526 CollectionId,527 ),528529 /// New item was created.530 ItemCreated(531 /// Id of the collection where item was created.532 CollectionId,533 /// Id of an item. Unique within the collection.534 TokenId,535 /// Owner of newly created item536 T::CrossAccountId,537 /// Always 1 for NFT538 u128,539 ),540541 /// Collection item was burned.542 ItemDestroyed(543 /// Id of the collection where item was destroyed.544 CollectionId,545 /// Identifier of burned NFT.546 TokenId,547 /// Which user has destroyed its tokens.548 T::CrossAccountId,549 /// Amount of token pieces destroed. Always 1 for NFT.550 u128,551 ),552553 /// Item was transferred554 Transfer(555 /// Id of collection to which item is belong.556 CollectionId,557 /// Id of an item.558 TokenId,559 /// Original owner of item.560 T::CrossAccountId,561 /// New owner of item.562 T::CrossAccountId,563 /// Amount of token pieces transfered. Always 1 for NFT.564 u128,565 ),566567 /// Amount pieces of token owned by `sender` was approved for `spender`.568 Approved(569 /// Id of collection to which item is belong.570 CollectionId,571 /// Id of an item.572 TokenId,573 /// Original owner of item.574 T::CrossAccountId,575 /// Id for which the approval was granted.576 T::CrossAccountId,577 /// Amount of token pieces transfered. Always 1 for NFT.578 u128,579 ),580581 /// A `sender` approves operations on all owned tokens for `spender`.582 ApprovedForAll(583 /// Id of collection to which item is belong.584 CollectionId,585 /// Owner of a wallet.586 T::CrossAccountId,587 /// Id for which operator status was granted or rewoked.588 T::CrossAccountId,589 /// Is operator status granted or revoked?590 bool,591 ),592593 /// The colletion property has been added or edited.594 CollectionPropertySet(595 /// Id of collection to which property has been set.596 CollectionId,597 /// The property that was set.598 PropertyKey,599 ),600601 /// The property has been deleted.602 CollectionPropertyDeleted(603 /// Id of collection to which property has been deleted.604 CollectionId,605 /// The property that was deleted.606 PropertyKey,607 ),608609 /// The token property has been added or edited.610 TokenPropertySet(611 /// Identifier of the collection whose token has the property set.612 CollectionId,613 /// The token for which the property was set.614 TokenId,615 /// The property that was set.616 PropertyKey,617 ),618619 /// The token property has been deleted.620 TokenPropertyDeleted(621 /// Identifier of the collection whose token has the property deleted.622 CollectionId,623 /// The token for which the property was deleted.624 TokenId,625 /// The property that was deleted.626 PropertyKey,627 ),628629 /// The token property permission of a collection has been set.630 PropertyPermissionSet(631 /// ID of collection to which property permission has been set.632 CollectionId,633 /// The property permission that was set.634 PropertyKey,635 ),636637 /// Address was added to the allow list.638 AllowListAddressAdded(639 /// ID of the affected collection.640 CollectionId,641 /// Address of the added account.642 T::CrossAccountId,643 ),644645 /// Address was removed from the allow list.646 AllowListAddressRemoved(647 /// ID of the affected collection.648 CollectionId,649 /// Address of the removed account.650 T::CrossAccountId,651 ),652653 /// Collection admin was added.654 CollectionAdminAdded(655 /// ID of the affected collection.656 CollectionId,657 /// Admin address.658 T::CrossAccountId,659 ),660661 /// Collection admin was removed.662 CollectionAdminRemoved(663 /// ID of the affected collection.664 CollectionId,665 /// Removed admin address.666 T::CrossAccountId,667 ),668669 /// Collection limits were set.670 CollectionLimitSet(671 /// ID of the affected collection.672 CollectionId,673 ),674675 /// Collection owned was changed.676 CollectionOwnerChanged(677 /// ID of the affected collection.678 CollectionId,679 /// New owner address.680 T::AccountId,681 ),682683 /// Collection permissions were set.684 CollectionPermissionSet(685 /// ID of the affected collection.686 CollectionId,687 ),688689 /// Collection sponsor was set.690 CollectionSponsorSet(691 /// ID of the affected collection.692 CollectionId,693 /// New sponsor address.694 T::AccountId,695 ),696697 /// New sponsor was confirm.698 SponsorshipConfirmed(699 /// ID of the affected collection.700 CollectionId,701 /// New sponsor address.702 T::AccountId,703 ),704705 /// Collection sponsor was removed.706 CollectionSponsorRemoved(707 /// ID of the affected collection.708 CollectionId,709 ),710 }711712 #[pallet::error]713 pub enum Error<T> {714 /// This collection does not exist.715 CollectionNotFound,716 /// Sender parameter and item owner must be equal.717 MustBeTokenOwner,718 /// No permission to perform action719 NoPermission,720 /// Destroying only empty collections is allowed721 CantDestroyNotEmptyCollection,722 /// Collection is not in mint mode.723 PublicMintingNotAllowed,724 /// Address is not in allow list.725 AddressNotInAllowlist,726727 /// Collection name can not be longer than 63 char.728 CollectionNameLimitExceeded,729 /// Collection description can not be longer than 255 char.730 CollectionDescriptionLimitExceeded,731 /// Token prefix can not be longer than 15 char.732 CollectionTokenPrefixLimitExceeded,733 /// Total collections bound exceeded.734 TotalCollectionsLimitExceeded,735 /// Exceeded max admin count736 CollectionAdminCountExceeded,737 /// Collection limit bounds per collection exceeded738 CollectionLimitBoundsExceeded,739 /// Tried to enable permissions which are only permitted to be disabled740 OwnerPermissionsCantBeReverted,741 /// Collection settings not allowing items transferring742 TransferNotAllowed,743 /// Account token limit exceeded per collection744 AccountTokenLimitExceeded,745 /// Collection token limit exceeded746 CollectionTokenLimitExceeded,747 /// Metadata flag frozen748 MetadataFlagFrozen,749750 /// Item does not exist751 TokenNotFound,752 /// Item is balance not enough753 TokenValueTooLow,754 /// Requested value is more than the approved755 ApprovedValueTooLow,756 /// Tried to approve more than owned757 CantApproveMoreThanOwned,758 /// Only spending from eth mirror could be approved759 AddressIsNotEthMirror,760761 /// Can't transfer tokens to ethereum zero address762 AddressIsZero,763764 /// The operation is not supported765 UnsupportedOperation,766767 /// Insufficient funds to perform an action768 NotSufficientFounds,769770 /// User does not satisfy the nesting rule771 UserIsNotAllowedToNest,772 /// Only tokens from specific collections may nest tokens under this one773 SourceCollectionIsNotAllowedToNest,774775 /// Tried to store more data than allowed in collection field776 CollectionFieldSizeExceeded,777778 /// Tried to store more property data than allowed779 NoSpaceForProperty,780781 /// Tried to store more property keys than allowed782 PropertyLimitReached,783784 /// Property key is too long785 PropertyKeyIsTooLong,786787 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed788 InvalidCharacterInPropertyKey,789790 /// Empty property keys are forbidden791 EmptyPropertyKey,792793 /// Tried to access an external collection with an internal API794 CollectionIsExternal,795796 /// Tried to access an internal collection with an external API797 CollectionIsInternal,798799 /// This address is not set as sponsor, use setCollectionSponsor first.800 ConfirmSponsorshipFail,801802 /// The user is not an administrator.803 UserIsNotCollectionAdmin,804 }805806 /// Storage of the count of created collections. Essentially contains the last collection ID.807 #[pallet::storage]808 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;809810 /// Storage of the count of deleted collections.811 #[pallet::storage]812 pub type DestroyedCollectionCount<T> =813 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;814815 /// Storage of collection info.816 #[pallet::storage]817 pub type CollectionById<T> = StorageMap<818 Hasher = Blake2_128Concat,819 Key = CollectionId,820 Value = Collection<<T as frame_system::Config>::AccountId>,821 QueryKind = OptionQuery,822 >;823824 /// Storage of collection properties.825 #[pallet::storage]826 #[pallet::getter(fn collection_properties)]827 pub type CollectionProperties<T> = StorageMap<828 Hasher = Blake2_128Concat,829 Key = CollectionId,830 Value = CollectionPropertiesT,831 QueryKind = ValueQuery,832 >;833834 /// Storage of token property permissions of a collection.835 #[pallet::storage]836 #[pallet::getter(fn property_permissions)]837 pub type CollectionPropertyPermissions<T> = StorageMap<838 Hasher = Blake2_128Concat,839 Key = CollectionId,840 Value = PropertiesPermissionMap,841 QueryKind = ValueQuery,842 >;843844 /// Storage of the amount of collection admins.845 #[pallet::storage]846 pub type AdminAmount<T> = StorageMap<847 Hasher = Blake2_128Concat,848 Key = CollectionId,849 Value = u32,850 QueryKind = ValueQuery,851 >;852853 /// List of collection admins.854 #[pallet::storage]855 pub type IsAdmin<T: Config> = StorageNMap<856 Key = (857 Key<Blake2_128Concat, CollectionId>,858 Key<Blake2_128Concat, T::CrossAccountId>,859 ),860 Value = bool,861 QueryKind = ValueQuery,862 >;863864 /// Allowlisted collection users.865 #[pallet::storage]866 pub type Allowlist<T: Config> = StorageNMap<867 Key = (868 Key<Blake2_128Concat, CollectionId>,869 Key<Blake2_128Concat, T::CrossAccountId>,870 ),871 Value = bool,872 QueryKind = ValueQuery,873 >;874875 /// Not used by code, exists only to provide some types to metadata.876 #[pallet::storage]877 pub type DummyStorageValue<T: Config> = StorageValue<878 Value = (879 CollectionStats,880 CollectionId,881 TokenId,882 TokenChild,883 PhantomType<(884 TokenData<T::CrossAccountId>,885 RpcCollection<T::AccountId>,886 // PoV Estimate Info887 PovInfo,888 )>,889 ),890 QueryKind = OptionQuery,891 >;892}893894impl<T: Config> Pallet<T> {895 /// Enshure that receiver address is correct.896 ///897 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.898 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {899 ensure!(900 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,901 <Error<T>>::AddressIsZero902 );903 Ok(())904 }905906 /// Get a vector of collection admins.907 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {908 <IsAdmin<T>>::iter_prefix((collection,))909 .map(|(a, _)| a)910 .collect()911 }912913 /// Get a vector of users allowed to mint tokens.914 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {915 <Allowlist<T>>::iter_prefix((collection,))916 .map(|(a, _)| a)917 .collect()918 }919920 /// Is `user` allowed to mint token in `collection`.921 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {922 <Allowlist<T>>::get((collection, user))923 }924925 /// Get statistics of collections.926 pub fn collection_stats() -> CollectionStats {927 let created = <CreatedCollectionCount<T>>::get();928 let destroyed = <DestroyedCollectionCount<T>>::get();929 CollectionStats {930 created: created.0,931 destroyed: destroyed.0,932 alive: created.0 - destroyed.0,933 }934 }935936 /// Get the effective limits for the collection.937 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {938 let collection = <CollectionById<T>>::get(collection)?;939 let limits = collection.limits;940 let effective_limits = CollectionLimits {941 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),942 sponsored_data_size: Some(limits.sponsored_data_size()),943 sponsored_data_rate_limit: Some(944 limits945 .sponsored_data_rate_limit946 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),947 ),948 token_limit: Some(limits.token_limit()),949 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(950 match collection.mode {951 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,952 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,953 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,954 },955 )),956 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),957 owner_can_transfer: Some(limits.owner_can_transfer()),958 owner_can_destroy: Some(limits.owner_can_destroy()),959 transfers_enabled: Some(limits.transfers_enabled()),960 };961962 Some(effective_limits)963 }964965 /// Returns information about the `collection` adapted for rpc.966 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {967 let Collection {968 name,969 description,970 owner,971 mode,972 token_prefix,973 sponsorship,974 limits,975 permissions,976 flags,977 } = <CollectionById<T>>::get(collection)?;978979 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)980 .into_iter()981 .map(|(key, permission)| PropertyKeyPermission { key, permission })982 .collect();983984 let properties = <CollectionProperties<T>>::get(collection)985 .into_iter()986 .map(|(key, value)| Property { key, value })987 .collect();988989 let permissions = CollectionPermissions {990 access: Some(permissions.access()),991 mint_mode: Some(permissions.mint_mode()),992 nesting: Some(permissions.nesting().clone()),993 };994995 Some(RpcCollection {996 name: name.into_inner(),997 description: description.into_inner(),998 owner,999 mode,1000 token_prefix: token_prefix.into_inner(),1001 sponsorship,1002 limits,1003 permissions,1004 token_property_permissions,1005 properties,1006 read_only: flags.external,10071008 flags: RpcCollectionFlags {1009 foreign: flags.foreign,1010 erc721metadata: flags.erc721metadata,1011 },1012 })1013 }1014}10151016macro_rules! limit_default {1017 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1018 $(1019 if let Some($new) = $new.$field {1020 let $old = $old.$field($($arg)?);1021 let _ = $new;1022 let _ = $old;1023 $check1024 } else {1025 $new.$field = $old.$field1026 }1027 )*1028 }};1029}1030macro_rules! limit_default_clone {1031 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1032 $(1033 if let Some($new) = $new.$field.clone() {1034 let $old = $old.$field($($arg)?);1035 let _ = $new;1036 let _ = $old;1037 $check1038 } else {1039 $new.$field = $old.$field.clone()1040 }1041 )*1042 }};1043}10441045impl<T: Config> Pallet<T> {1046 /// Create new collection.1047 ///1048 /// * `owner` - The owner of the collection.1049 /// * `data` - Description of the created collection.1050 /// * `flags` - Extra flags to store.1051 pub fn init_collection(1052 owner: T::CrossAccountId,1053 payer: T::CrossAccountId,1054 data: CreateCollectionData<T::AccountId>,1055 flags: CollectionFlags,1056 ) -> Result<CollectionId, DispatchError> {1057 {1058 ensure!(1059 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1060 Error::<T>::CollectionTokenPrefixLimitExceeded1061 );1062 }10631064 let created_count = <CreatedCollectionCount<T>>::get()1065 .01066 .checked_add(1)1067 .ok_or(ArithmeticError::Overflow)?;1068 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1069 let id = CollectionId(created_count);10701071 // bound Total number of collections1072 ensure!(1073 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1074 <Error<T>>::TotalCollectionsLimitExceeded1075 );10761077 // =========10781079 let collection = Collection {1080 owner: owner.as_sub().clone(),1081 name: data.name,1082 mode: data.mode.clone(),1083 description: data.description,1084 token_prefix: data.token_prefix,1085 sponsorship: data1086 .pending_sponsor1087 .map(SponsorshipState::Unconfirmed)1088 .unwrap_or_default(),1089 limits: data1090 .limits1091 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1092 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1093 permissions: data1094 .permissions1095 .map(|permissions| {1096 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1097 })1098 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1099 flags,1100 };11011102 let mut collection_properties = CollectionPropertiesT::new();1103 collection_properties1104 .try_set_from_iter(data.properties.into_iter())1105 .map_err(<Error<T>>::from)?;11061107 CollectionProperties::<T>::insert(id, collection_properties);11081109 let mut token_props_permissions = PropertiesPermissionMap::new();1110 token_props_permissions1111 .try_set_from_iter(data.token_property_permissions.into_iter())1112 .map_err(<Error<T>>::from)?;11131114 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11151116 // Take a (non-refundable) deposit of collection creation1117 {1118 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1119 imbalance.subsume(<T as Config>::Currency::deposit(1120 &T::TreasuryAccountId::get(),1121 T::CollectionCreationPrice::get(),1122 Precision::Exact,1123 )?);1124 let credit =1125 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1126 .map_err(|_| Error::<T>::NotSufficientFounds)?;11271128 debug_assert!(credit.peek().is_zero())1129 }11301131 <CreatedCollectionCount<T>>::put(created_count);1132 <Pallet<T>>::deposit_event(Event::CollectionCreated(1133 id,1134 data.mode.id(),1135 owner.as_sub().clone(),1136 ));1137 <PalletEvm<T>>::deposit_log(1138 erc::CollectionHelpersEvents::CollectionCreated {1139 owner: *owner.as_eth(),1140 collection_id: eth::collection_id_to_address(id),1141 }1142 .to_log(T::ContractAddress::get()),1143 );1144 <CollectionById<T>>::insert(id, collection);1145 Ok(id)1146 }11471148 /// Destroy collection.1149 ///1150 /// * `collection` - Collection handler.1151 /// * `sender` - The owner or administrator of the collection.1152 pub fn destroy_collection(1153 collection: CollectionHandle<T>,1154 sender: &T::CrossAccountId,1155 ) -> DispatchResult {1156 ensure!(1157 collection.limits.owner_can_destroy(),1158 <Error<T>>::NoPermission,1159 );1160 collection.check_is_owner(sender)?;11611162 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1163 .01164 .checked_add(1)1165 .ok_or(ArithmeticError::Overflow)?;11661167 // =========11681169 <DestroyedCollectionCount<T>>::put(destroyed_collections);1170 <CollectionById<T>>::remove(collection.id);1171 <AdminAmount<T>>::remove(collection.id);1172 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1173 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1174 <CollectionProperties<T>>::remove(collection.id);11751176 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11771178 <PalletEvm<T>>::deposit_log(1179 erc::CollectionHelpersEvents::CollectionDestroyed {1180 collection_id: eth::collection_id_to_address(collection.id),1181 }1182 .to_log(T::ContractAddress::get()),1183 );1184 Ok(())1185 }11861187 /// This function sets or removes a collection properties according to1188 /// `properties_updates` contents:1189 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1190 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1191 ///1192 /// This function fires an event for each property change.1193 /// In case of an error, all the changes (including the events) will be reverted1194 /// since the function is transactional.1195 #[transactional]1196 fn modify_collection_properties(1197 collection: &CollectionHandle<T>,1198 sender: &T::CrossAccountId,1199 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1200 ) -> DispatchResult {1201 collection.check_is_owner_or_admin(sender)?;12021203 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12041205 for (key, value) in properties_updates {1206 match value {1207 Some(value) => {1208 stored_properties1209 .try_set(key.clone(), value)1210 .map_err(<Error<T>>::from)?;12111212 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1213 <PalletEvm<T>>::deposit_log(1214 erc::CollectionHelpersEvents::CollectionChanged {1215 collection_id: eth::collection_id_to_address(collection.id),1216 }1217 .to_log(T::ContractAddress::get()),1218 );1219 }1220 None => {1221 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12221223 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1224 <PalletEvm<T>>::deposit_log(1225 erc::CollectionHelpersEvents::CollectionChanged {1226 collection_id: eth::collection_id_to_address(collection.id),1227 }1228 .to_log(T::ContractAddress::get()),1229 );1230 }1231 }1232 }12331234 <CollectionProperties<T>>::set(collection.id, stored_properties);12351236 Ok(())1237 }12381239 /// A batch operation to add, edit or remove properties for a token.1240 /// It sets or removes a token's properties according to1241 /// `properties_updates` contents:1242 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1243 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1244 ///1245 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1246 /// - `is_token_create`: Indicates that method is called during token initialization.1247 /// Allows to bypass ownership check.1248 ///1249 /// All affected properties should have `mutable` permission1250 /// to be **deleted** or to be **set more than once**,1251 /// and the sender should have permission to edit those properties.1252 ///1253 /// This function fires an event for each property change.1254 /// In case of an error, all the changes (including the events) will be reverted1255 /// since the function is transactional.1256 pub fn modify_token_properties(1257 collection: &CollectionHandle<T>,1258 sender: &T::CrossAccountId,1259 token_id: TokenId,1260 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1261 is_token_create: bool,1262 mut stored_properties: TokenProperties,1263 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1264 set_token_properties: impl FnOnce(TokenProperties),1265 log: evm_coder::ethereum::Log,1266 ) -> DispatchResult {1267 let is_collection_admin = collection.is_owner_or_admin(sender);1268 let permissions = Self::property_permissions(collection.id);12691270 let mut token_owner_result = None;1271 let mut is_token_owner = || -> Result<bool, DispatchError> {1272 *token_owner_result.get_or_insert_with(&is_token_owner)1273 };12741275 for (key, value) in properties_updates {1276 let permission = permissions1277 .get(&key)1278 .cloned()1279 .unwrap_or_else(PropertyPermission::none);12801281 let is_property_exists = stored_properties.get(&key).is_some();12821283 match permission {1284 PropertyPermission { mutable: false, .. } if is_property_exists => {1285 return Err(<Error<T>>::NoPermission.into());1286 }12871288 PropertyPermission {1289 collection_admin,1290 token_owner,1291 ..1292 } => {1293 //TODO: investigate threats during public minting.1294 let is_token_create =1295 is_token_create && (collection_admin || token_owner) && value.is_some();1296 if !(is_token_create1297 || (collection_admin && is_collection_admin)1298 || (token_owner && is_token_owner()?))1299 {1300 fail!(<Error<T>>::NoPermission);1301 }1302 }1303 }13041305 match value {1306 Some(value) => {1307 stored_properties1308 .try_set(key.clone(), value)1309 .map_err(<Error<T>>::from)?;13101311 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1312 }1313 None => {1314 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13151316 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1317 }1318 }13191320 <PalletEvm<T>>::deposit_log(log.clone());1321 }13221323 set_token_properties(stored_properties);13241325 Ok(())1326 }13271328 /// Sets or unsets the approval of a given operator.1329 ///1330 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1331 /// - `owner`: Token owner1332 /// - `operator`: Operator1333 /// - `approve`: Should operator status be granted or revoked?1334 pub fn set_allowance_for_all(1335 collection: &CollectionHandle<T>,1336 owner: &T::CrossAccountId,1337 operator: &T::CrossAccountId,1338 approve: bool,1339 set_allowance: impl FnOnce(),1340 log: evm_coder::ethereum::Log,1341 ) -> DispatchResult {1342 if collection.permissions.access() == AccessMode::AllowList {1343 collection.check_allowlist(owner)?;1344 collection.check_allowlist(operator)?;1345 }13461347 Self::ensure_correct_receiver(operator)?;13481349 set_allowance();13501351 <PalletEvm<T>>::deposit_log(log);1352 Self::deposit_event(Event::ApprovedForAll(1353 collection.id,1354 owner.clone(),1355 operator.clone(),1356 approve,1357 ));1358 Ok(())1359 }13601361 /// Set collection property.1362 ///1363 /// * `collection` - Collection handler.1364 /// * `sender` - The owner or administrator of the collection.1365 /// * `property` - The property to set.1366 pub fn set_collection_property(1367 collection: &CollectionHandle<T>,1368 sender: &T::CrossAccountId,1369 property: Property,1370 ) -> DispatchResult {1371 Self::set_collection_properties(collection, sender, [property].into_iter())1372 }13731374 /// Set a scoped collection property, where the scope is a special prefix1375 /// prohibiting a user access to change the property directly.1376 ///1377 /// * `collection_id` - ID of the collection for which the property is being set.1378 /// * `scope` - Property scope.1379 /// * `property` - The property to set.1380 pub fn set_scoped_collection_property(1381 collection_id: CollectionId,1382 scope: PropertyScope,1383 property: Property,1384 ) -> DispatchResult {1385 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1386 properties.try_scoped_set(scope, property.key, property.value)1387 })1388 .map_err(<Error<T>>::from)?;13891390 Ok(())1391 }13921393 /// Set scoped collection properties, where the scope is a special prefix1394 /// prohibiting a user access to change the properties directly.1395 ///1396 /// * `collection_id` - ID of the collection for which the properties is being set.1397 /// * `scope` - Property scope.1398 /// * `properties` - The properties to set.1399 pub fn set_scoped_collection_properties(1400 collection_id: CollectionId,1401 scope: PropertyScope,1402 properties: impl Iterator<Item = Property>,1403 ) -> DispatchResult {1404 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1405 stored_properties.try_scoped_set_from_iter(scope, properties)1406 })1407 .map_err(<Error<T>>::from)?;14081409 Ok(())1410 }14111412 /// Set collection properties.1413 ///1414 /// * `collection` - Collection handler.1415 /// * `sender` - The owner or administrator of the collection.1416 /// * `properties` - The properties to set.1417 pub fn set_collection_properties(1418 collection: &CollectionHandle<T>,1419 sender: &T::CrossAccountId,1420 properties: impl Iterator<Item = Property>,1421 ) -> DispatchResult {1422 Self::modify_collection_properties(1423 collection,1424 sender,1425 properties.map(|property| (property.key, Some(property.value))),1426 )1427 }14281429 /// Delete collection property.1430 ///1431 /// * `collection` - Collection handler.1432 /// * `sender` - The owner or administrator of the collection.1433 /// * `property` - The property to delete.1434 pub fn delete_collection_property(1435 collection: &CollectionHandle<T>,1436 sender: &T::CrossAccountId,1437 property_key: PropertyKey,1438 ) -> DispatchResult {1439 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1440 }14411442 /// Delete collection properties.1443 ///1444 /// * `collection` - Collection handler.1445 /// * `sender` - The owner or administrator of the collection.1446 /// * `properties` - The properties to delete.1447 pub fn delete_collection_properties(1448 collection: &CollectionHandle<T>,1449 sender: &T::CrossAccountId,1450 property_keys: impl Iterator<Item = PropertyKey>,1451 ) -> DispatchResult {1452 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1453 }14541455 /// Set collection propetry permission without any checks.1456 ///1457 /// Used for migrations.1458 ///1459 /// * `collection` - Collection handler.1460 /// * `property_permissions` - Property permissions.1461 pub fn set_property_permission_unchecked(1462 collection: CollectionId,1463 property_permission: PropertyKeyPermission,1464 ) -> DispatchResult {1465 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1466 permissions.try_set(property_permission.key, property_permission.permission)1467 })1468 .map_err(<Error<T>>::from)?;1469 Ok(())1470 }14711472 /// Set collection property permission.1473 ///1474 /// * `collection` - Collection handler.1475 /// * `sender` - The owner or administrator of the collection.1476 /// * `property_permission` - Property permission.1477 pub fn set_property_permission(1478 collection: &CollectionHandle<T>,1479 sender: &T::CrossAccountId,1480 property_permission: PropertyKeyPermission,1481 ) -> DispatchResult {1482 Self::set_scoped_property_permission(1483 collection,1484 sender,1485 PropertyScope::None,1486 property_permission,1487 )1488 }14891490 /// Set collection property permission with scope.1491 ///1492 /// * `collection` - Collection handler.1493 /// * `sender` - The owner or administrator of the collection.1494 /// * `scope` - Property scope.1495 /// * `property_permission` - Property permission.1496 pub fn set_scoped_property_permission(1497 collection: &CollectionHandle<T>,1498 sender: &T::CrossAccountId,1499 scope: PropertyScope,1500 property_permission: PropertyKeyPermission,1501 ) -> DispatchResult {1502 collection.check_is_owner_or_admin(sender)?;15031504 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1505 let current_permission = all_permissions.get(&property_permission.key);1506 if matches![1507 current_permission,1508 Some(PropertyPermission { mutable: false, .. })1509 ] {1510 return Err(<Error<T>>::NoPermission.into());1511 }15121513 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1514 let property_permission = property_permission.clone();1515 permissions.try_scoped_set(1516 scope,1517 property_permission.key,1518 property_permission.permission,1519 )1520 })1521 .map_err(<Error<T>>::from)?;15221523 Self::deposit_event(Event::PropertyPermissionSet(1524 collection.id,1525 property_permission.key,1526 ));1527 <PalletEvm<T>>::deposit_log(1528 erc::CollectionHelpersEvents::CollectionChanged {1529 collection_id: eth::collection_id_to_address(collection.id),1530 }1531 .to_log(T::ContractAddress::get()),1532 );15331534 Ok(())1535 }15361537 /// Set token property permission.1538 ///1539 /// * `collection` - Collection handler.1540 /// * `sender` - The owner or administrator of the collection.1541 /// * `property_permissions` - Property permissions.1542 #[transactional]1543 pub fn set_token_property_permissions(1544 collection: &CollectionHandle<T>,1545 sender: &T::CrossAccountId,1546 property_permissions: Vec<PropertyKeyPermission>,1547 ) -> DispatchResult {1548 Self::set_scoped_token_property_permissions(1549 collection,1550 sender,1551 PropertyScope::None,1552 property_permissions,1553 )1554 }15551556 /// Set token property permission with scope.1557 ///1558 /// * `collection` - Collection handler.1559 /// * `sender` - The owner or administrator of the collection.1560 /// * `scope` - Property scope.1561 /// * `property_permissions` - Property permissions.1562 #[transactional]1563 pub fn set_scoped_token_property_permissions(1564 collection: &CollectionHandle<T>,1565 sender: &T::CrossAccountId,1566 scope: PropertyScope,1567 property_permissions: Vec<PropertyKeyPermission>,1568 ) -> DispatchResult {1569 for prop_pemission in property_permissions {1570 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1571 }15721573 Ok(())1574 }15751576 /// Get collection property.1577 pub fn get_collection_property(1578 collection_id: CollectionId,1579 key: &PropertyKey,1580 ) -> Option<PropertyValue> {1581 Self::collection_properties(collection_id).get(key).cloned()1582 }15831584 /// Convert byte vector to property key vector.1585 pub fn bytes_keys_to_property_keys(1586 keys: Vec<Vec<u8>>,1587 ) -> Result<Vec<PropertyKey>, DispatchError> {1588 keys.into_iter()1589 .map(|key| -> Result<PropertyKey, DispatchError> {1590 key.try_into()1591 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1592 })1593 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1594 }15951596 /// Get properties according to given keys.1597 pub fn filter_collection_properties(1598 collection_id: CollectionId,1599 keys: Option<Vec<PropertyKey>>,1600 ) -> Result<Vec<Property>, DispatchError> {1601 let properties = Self::collection_properties(collection_id);16021603 let properties = keys1604 .map(|keys| {1605 keys.into_iter()1606 .filter_map(|key| {1607 properties.get(&key).map(|value| Property {1608 key,1609 value: value.clone(),1610 })1611 })1612 .collect()1613 })1614 .unwrap_or_else(|| {1615 properties1616 .into_iter()1617 .map(|(key, value)| Property { key, value })1618 .collect()1619 });16201621 Ok(properties)1622 }16231624 /// Get property permissions according to given keys.1625 pub fn filter_property_permissions(1626 collection_id: CollectionId,1627 keys: Option<Vec<PropertyKey>>,1628 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1629 let permissions = Self::property_permissions(collection_id);16301631 let key_permissions = keys1632 .map(|keys| {1633 keys.into_iter()1634 .filter_map(|key| {1635 permissions1636 .get(&key)1637 .map(|permission| PropertyKeyPermission {1638 key,1639 permission: permission.clone(),1640 })1641 })1642 .collect()1643 })1644 .unwrap_or_else(|| {1645 permissions1646 .into_iter()1647 .map(|(key, permission)| PropertyKeyPermission { key, permission })1648 .collect()1649 });16501651 Ok(key_permissions)1652 }16531654 /// Toggle `user` participation in the `collection`'s allow list.1655 /// #### Store read/writes1656 /// 1 writes1657 pub fn toggle_allowlist(1658 collection: &CollectionHandle<T>,1659 sender: &T::CrossAccountId,1660 user: &T::CrossAccountId,1661 allowed: bool,1662 ) -> DispatchResult {1663 collection.check_is_owner_or_admin(sender)?;16641665 // =========16661667 if allowed {1668 <Allowlist<T>>::insert((collection.id, user), true);1669 Self::deposit_event(Event::<T>::AllowListAddressAdded(1670 collection.id,1671 user.clone(),1672 ));1673 } else {1674 <Allowlist<T>>::remove((collection.id, user));1675 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1676 collection.id,1677 user.clone(),1678 ));1679 }16801681 <PalletEvm<T>>::deposit_log(1682 erc::CollectionHelpersEvents::CollectionChanged {1683 collection_id: eth::collection_id_to_address(collection.id),1684 }1685 .to_log(T::ContractAddress::get()),1686 );16871688 Ok(())1689 }16901691 /// Toggle `user` participation in the `collection`'s admin list.1692 /// #### Store read/writes1693 /// 2 reads, 2 writes1694 pub fn toggle_admin(1695 collection: &CollectionHandle<T>,1696 sender: &T::CrossAccountId,1697 user: &T::CrossAccountId,1698 admin: bool,1699 ) -> DispatchResult {1700 collection.check_is_internal()?;1701 collection.check_is_owner(sender)?;17021703 let is_admin = <IsAdmin<T>>::get((collection.id, user));1704 if is_admin == admin {1705 if admin {1706 return Ok(());1707 } else {1708 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1709 }1710 }1711 let amount = <AdminAmount<T>>::get(collection.id);17121713 // =========17141715 if admin {1716 let amount = amount1717 .checked_add(1)1718 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1719 ensure!(1720 amount <= Self::collection_admins_limit(),1721 <Error<T>>::CollectionAdminCountExceeded,1722 );17231724 <AdminAmount<T>>::insert(collection.id, amount);1725 <IsAdmin<T>>::insert((collection.id, user), true);17261727 Self::deposit_event(Event::<T>::CollectionAdminAdded(1728 collection.id,1729 user.clone(),1730 ));1731 } else {1732 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1733 <IsAdmin<T>>::remove((collection.id, user));17341735 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1736 collection.id,1737 user.clone(),1738 ));1739 }17401741 <PalletEvm<T>>::deposit_log(1742 erc::CollectionHelpersEvents::CollectionChanged {1743 collection_id: eth::collection_id_to_address(collection.id),1744 }1745 .to_log(T::ContractAddress::get()),1746 );17471748 Ok(())1749 }17501751 /// Update collection limits.1752 pub fn update_limits(1753 user: &T::CrossAccountId,1754 collection: &mut CollectionHandle<T>,1755 new_limit: CollectionLimits,1756 ) -> DispatchResult {1757 collection.check_is_internal()?;1758 collection.check_is_owner_or_admin(user)?;17591760 collection.limits =1761 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17621763 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1764 <PalletEvm<T>>::deposit_log(1765 erc::CollectionHelpersEvents::CollectionChanged {1766 collection_id: eth::collection_id_to_address(collection.id),1767 }1768 .to_log(T::ContractAddress::get()),1769 );17701771 collection.save()1772 }17731774 /// Merge set fields from `new_limit` to `old_limit`.1775 fn clamp_limits(1776 mode: CollectionMode,1777 old_limit: &CollectionLimits,1778 mut new_limit: CollectionLimits,1779 ) -> Result<CollectionLimits, DispatchError> {1780 let limits = old_limit;1781 limit_default!(old_limit, new_limit,1782 account_token_ownership_limit => ensure!(1783 new_limit <= MAX_TOKEN_OWNERSHIP,1784 <Error<T>>::CollectionLimitBoundsExceeded,1785 ),1786 sponsored_data_size => ensure!(1787 new_limit <= CUSTOM_DATA_LIMIT,1788 <Error<T>>::CollectionLimitBoundsExceeded,1789 ),17901791 sponsored_data_rate_limit => {},1792 token_limit => ensure!(1793 old_limit >= new_limit && new_limit > 0,1794 <Error<T>>::CollectionTokenLimitExceeded1795 ),17961797 sponsor_transfer_timeout(match mode {1798 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1799 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1800 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1801 }) => ensure!(1802 new_limit <= MAX_SPONSOR_TIMEOUT,1803 <Error<T>>::CollectionLimitBoundsExceeded,1804 ),1805 sponsor_approve_timeout => {},1806 owner_can_transfer => ensure!(1807 !limits.owner_can_transfer_instaled() ||1808 old_limit || !new_limit,1809 <Error<T>>::OwnerPermissionsCantBeReverted,1810 ),1811 owner_can_destroy => ensure!(1812 old_limit || !new_limit,1813 <Error<T>>::OwnerPermissionsCantBeReverted,1814 ),1815 transfers_enabled => {},1816 );1817 Ok(new_limit)1818 }18191820 /// Update collection permissions.1821 pub fn update_permissions(1822 user: &T::CrossAccountId,1823 collection: &mut CollectionHandle<T>,1824 new_permission: CollectionPermissions,1825 ) -> DispatchResult {1826 collection.check_is_internal()?;1827 collection.check_is_owner_or_admin(user)?;1828 collection.permissions = Self::clamp_permissions(1829 collection.mode.clone(),1830 &collection.permissions,1831 new_permission,1832 )?;18331834 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1835 <PalletEvm<T>>::deposit_log(1836 erc::CollectionHelpersEvents::CollectionChanged {1837 collection_id: eth::collection_id_to_address(collection.id),1838 }1839 .to_log(T::ContractAddress::get()),1840 );18411842 collection.save()1843 }18441845 /// Merge set fields from `new_permission` to `old_permission`.1846 fn clamp_permissions(1847 _mode: CollectionMode,1848 old_permission: &CollectionPermissions,1849 mut new_permission: CollectionPermissions,1850 ) -> Result<CollectionPermissions, DispatchError> {1851 limit_default_clone!(old_permission, new_permission,1852 access => {},1853 mint_mode => {},1854 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1855 );1856 Ok(new_permission)1857 }18581859 /// Repair possibly broken properties of a collection.1860 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1861 CollectionProperties::<T>::mutate(collection_id, |properties| {1862 properties.recompute_consumed_space();1863 });18641865 Ok(())1866 }1867}18681869/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1870#[macro_export]1871macro_rules! unsupported {1872 ($runtime:path) => {1873 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1874 };1875}18761877/// Return weights for various worst-case operations.1878pub trait CommonWeightInfo<CrossAccountId> {1879 /// Weight of item creation.1880 fn create_item(data: &CreateItemData) -> Weight {1881 Self::create_multiple_items(from_ref(data))1882 }18831884 /// Weight of items creation.1885 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18861887 /// Weight of items creation.1888 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18891890 /// The weight of the burning item.1891 fn burn_item() -> Weight;18921893 /// Property setting weight.1894 ///1895 /// * `amount`- The number of properties to set.1896 fn set_collection_properties(amount: u32) -> Weight;18971898 /// Collection property deletion weight.1899 ///1900 /// * `amount`- The number of properties to set.1901 fn delete_collection_properties(amount: u32) -> Weight;19021903 /// Token property setting weight.1904 ///1905 /// * `amount`- The number of properties to set.1906 fn set_token_properties(amount: u32) -> Weight;19071908 /// Token property deletion weight.1909 ///1910 /// * `amount`- The number of properties to delete.1911 fn delete_token_properties(amount: u32) -> Weight;19121913 /// Token property permissions set weight.1914 ///1915 /// * `amount`- The number of property permissions to set.1916 fn set_token_property_permissions(amount: u32) -> Weight;19171918 /// Transfer price of the token or its parts.1919 fn transfer() -> Weight;19201921 /// The price of setting the permission of the operation from another user.1922 fn approve() -> Weight;19231924 /// The price of setting the permission of the operation from another user for eth mirror.1925 fn approve_from() -> Weight;19261927 /// Transfer price from another user.1928 fn transfer_from() -> Weight;19291930 /// The price of burning a token from another user.1931 fn burn_from() -> Weight;19321933 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1934 /// whole users's balance.1935 ///1936 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1937 fn burn_recursively_self_raw() -> Weight;19381939 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1940 ///1941 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1942 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19431944 /// The price of recursive burning a token.1945 ///1946 /// `max_selfs` - The maximum burning weight of the token itself.1947 /// `max_breadth` - The maximum number of nested tokens to burn.1948 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1949 Self::burn_recursively_self_raw()1950 .saturating_mul(max_selfs.max(1) as u64)1951 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1952 }19531954 /// The price of retrieving token owner1955 fn token_owner() -> Weight;19561957 /// The price of setting approval for all1958 fn set_allowance_for_all() -> Weight;19591960 /// The price of repairing an item.1961 fn force_repair_item() -> Weight;1962}19631964/// Weight info extension trait for refungible pallet.1965pub trait RefungibleExtensionsWeightInfo {1966 /// Weight of token repartition.1967 fn repartition() -> Weight;1968}19691970/// Common collection operations.1971///1972/// It wraps methods in Fungible, Nonfungible and Refungible pallets1973/// and adds weight info.1974pub trait CommonCollectionOperations<T: Config> {1975 /// Create token.1976 ///1977 /// * `sender` - The user who mint the token and pays for the transaction.1978 /// * `to` - The user who will own the token.1979 /// * `data` - Token data.1980 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1981 fn create_item(1982 &self,1983 sender: T::CrossAccountId,1984 to: T::CrossAccountId,1985 data: CreateItemData,1986 nesting_budget: &dyn Budget,1987 ) -> DispatchResultWithPostInfo;19881989 /// Create multiple tokens.1990 ///1991 /// * `sender` - The user who mint the token and pays for the transaction.1992 /// * `to` - The user who will own the token.1993 /// * `data` - Token data.1994 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1995 fn create_multiple_items(1996 &self,1997 sender: T::CrossAccountId,1998 to: T::CrossAccountId,1999 data: Vec<CreateItemData>,2000 nesting_budget: &dyn Budget,2001 ) -> DispatchResultWithPostInfo;20022003 /// Create multiple tokens.2004 ///2005 /// * `sender` - The user who mint the token and pays for the transaction.2006 /// * `to` - The user who will own the token.2007 /// * `data` - Token data.2008 /// * `nesting_budget` - A budget that can be spent on nesting tokens.2009 fn create_multiple_items_ex(2010 &self,2011 sender: T::CrossAccountId,2012 data: CreateItemExData<T::CrossAccountId>,2013 nesting_budget: &dyn Budget,2014 ) -> DispatchResultWithPostInfo;20152016 /// Burn token.2017 ///2018 /// * `sender` - The user who owns the token.2019 /// * `token` - Token id that will burned.2020 /// * `amount` - The number of parts of the token that will be burned.2021 fn burn_item(2022 &self,2023 sender: T::CrossAccountId,2024 token: TokenId,2025 amount: u128,2026 ) -> DispatchResultWithPostInfo;20272028 /// Burn token and all nested tokens recursievly.2029 ///2030 /// * `sender` - The user who owns the token.2031 /// * `token` - Token id that will burned.2032 /// * `self_budget` - The budget that can be spent on burning tokens.2033 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2034 fn burn_item_recursively(2035 &self,2036 sender: T::CrossAccountId,2037 token: TokenId,2038 self_budget: &dyn Budget,2039 breadth_budget: &dyn Budget,2040 ) -> DispatchResultWithPostInfo;20412042 /// Set collection properties.2043 ///2044 /// * `sender` - Must be either the owner of the collection or its admin.2045 /// * `properties` - Properties to be set.2046 fn set_collection_properties(2047 &self,2048 sender: T::CrossAccountId,2049 properties: Vec<Property>,2050 ) -> DispatchResultWithPostInfo;20512052 /// Delete collection properties.2053 ///2054 /// * `sender` - Must be either the owner of the collection or its admin.2055 /// * `properties` - The properties to be removed.2056 fn delete_collection_properties(2057 &self,2058 sender: &T::CrossAccountId,2059 property_keys: Vec<PropertyKey>,2060 ) -> DispatchResultWithPostInfo;20612062 /// Set token properties.2063 ///2064 /// The appropriate [`PropertyPermission`] for the token property2065 /// must be set with [`Self::set_token_property_permissions`].2066 ///2067 /// * `sender` - Must be either the owner of the token or its admin.2068 /// * `token_id` - The token for which the properties are being set.2069 /// * `properties` - Properties to be set.2070 /// * `budget` - Budget for setting properties.2071 fn set_token_properties(2072 &self,2073 sender: T::CrossAccountId,2074 token_id: TokenId,2075 properties: Vec<Property>,2076 budget: &dyn Budget,2077 ) -> DispatchResultWithPostInfo;20782079 /// Remove token properties.2080 ///2081 /// The appropriate [`PropertyPermission`] for the token property2082 /// must be set with [`Self::set_token_property_permissions`].2083 ///2084 /// * `sender` - Must be either the owner of the token or its admin.2085 /// * `token_id` - The token for which the properties are being remove.2086 /// * `property_keys` - Keys to remove corresponding properties.2087 /// * `budget` - Budget for removing properties.2088 fn delete_token_properties(2089 &self,2090 sender: T::CrossAccountId,2091 token_id: TokenId,2092 property_keys: Vec<PropertyKey>,2093 budget: &dyn Budget,2094 ) -> DispatchResultWithPostInfo;20952096 /// Set token property permissions.2097 ///2098 /// * `sender` - Must be either the owner of the token or its admin.2099 /// * `token_id` - The token for which the properties are being set.2100 /// * `property_permissions` - Property permissions to be set.2101 /// * `budget` - Budget for setting properties.2102 fn set_token_property_permissions(2103 &self,2104 sender: &T::CrossAccountId,2105 property_permissions: Vec<PropertyKeyPermission>,2106 ) -> DispatchResultWithPostInfo;21072108 /// Transfer amount of token pieces.2109 ///2110 /// * `sender` - Donor user.2111 /// * `to` - Recepient user.2112 /// * `token` - The token of which parts are being sent.2113 /// * `amount` - The number of parts of the token that will be transferred.2114 /// * `budget` - The maximum budget that can be spent on the transfer.2115 fn transfer(2116 &self,2117 sender: T::CrossAccountId,2118 to: T::CrossAccountId,2119 token: TokenId,2120 amount: u128,2121 budget: &dyn Budget,2122 ) -> DispatchResultWithPostInfo;21232124 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2125 ///2126 /// * `sender` - The user who grants access to the token.2127 /// * `spender` - The user to whom the rights are granted.2128 /// * `token` - The token to which access is granted.2129 /// * `amount` - The amount of pieces that another user can dispose of.2130 fn approve(2131 &self,2132 sender: T::CrossAccountId,2133 spender: T::CrossAccountId,2134 token: TokenId,2135 amount: u128,2136 ) -> DispatchResultWithPostInfo;21372138 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2139 ///2140 /// * `sender` - The user who grants access to the token.2141 /// * `from` - Spender's eth mirror.2142 /// * `to` - The user to whom the rights are granted.2143 /// * `token` - The token to which access is granted.2144 /// * `amount` - The amount of pieces that another user can dispose of.2145 fn approve_from(2146 &self,2147 sender: T::CrossAccountId,2148 from: T::CrossAccountId,2149 to: T::CrossAccountId,2150 token: TokenId,2151 amount: u128,2152 ) -> DispatchResultWithPostInfo;21532154 /// Send parts of a token owned by another user.2155 ///2156 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2157 ///2158 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2159 /// * `from` - The user who owns the token.2160 /// * `to` - Recepient user.2161 /// * `token` - The token of which parts are being sent.2162 /// * `amount` - The number of parts of the token that will be transferred.2163 /// * `budget` - The maximum budget that can be spent on the transfer.2164 fn transfer_from(2165 &self,2166 sender: T::CrossAccountId,2167 from: T::CrossAccountId,2168 to: T::CrossAccountId,2169 token: TokenId,2170 amount: u128,2171 budget: &dyn Budget,2172 ) -> DispatchResultWithPostInfo;21732174 /// Burn parts of a token owned by another user.2175 ///2176 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2177 ///2178 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2179 /// * `from` - The user who owns the token.2180 /// * `token` - The token of which parts are being sent.2181 /// * `amount` - The number of parts of the token that will be transferred.2182 /// * `budget` - The maximum budget that can be spent on the burn.2183 fn burn_from(2184 &self,2185 sender: T::CrossAccountId,2186 from: T::CrossAccountId,2187 token: TokenId,2188 amount: u128,2189 budget: &dyn Budget,2190 ) -> DispatchResultWithPostInfo;21912192 /// Check permission to nest token.2193 ///2194 /// * `sender` - The user who initiated the check.2195 /// * `from` - The token that is checked for embedding.2196 /// * `under` - Token under which to check.2197 /// * `budget` - The maximum budget that can be spent on the check.2198 fn check_nesting(2199 &self,2200 sender: T::CrossAccountId,2201 from: (CollectionId, TokenId),2202 under: TokenId,2203 budget: &dyn Budget,2204 ) -> DispatchResult;22052206 /// Nest one token into another.2207 ///2208 /// * `under` - Token holder.2209 /// * `to_nest` - Nested token.2210 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22112212 /// Unnest token.2213 ///2214 /// * `under` - Token holder.2215 /// * `to_nest` - Token to unnest.2216 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22172218 /// Get all user tokens.2219 ///2220 /// * `account` - Account for which you need to get tokens.2221 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22222223 /// Get all the tokens in the collection.2224 fn collection_tokens(&self) -> Vec<TokenId>;22252226 /// Check if the token exists.2227 ///2228 /// * `token` - Id token to check.2229 fn token_exists(&self, token: TokenId) -> bool;22302231 /// Get the id of the last minted token.2232 fn last_token_id(&self) -> TokenId;22332234 /// Get the owner of the token.2235 ///2236 /// * `token` - The token for which you need to find out the owner.2237 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22382239 /// Returns 10 tokens owners in no particular order.2240 ///2241 /// * `token` - The token for which you need to find out the owners.2242 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22432244 /// Get the value of the token property by key.2245 ///2246 /// * `token` - Token with the property to get.2247 /// * `key` - Property name.2248 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22492250 /// Get a set of token properties by key vector.2251 ///2252 /// * `token` - Token with the property to get.2253 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2254 /// then all properties are returned.2255 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22562257 /// Amount of unique collection tokens2258 fn total_supply(&self) -> u32;22592260 /// Amount of different tokens account has.2261 ///2262 /// * `account` - The account for which need to get the balance.2263 fn account_balance(&self, account: T::CrossAccountId) -> u32;22642265 /// Amount of specific token account have.2266 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22672268 /// Amount of token pieces2269 fn total_pieces(&self, token: TokenId) -> Option<u128>;22702271 /// Get the number of parts of the token that a trusted user can manage.2272 ///2273 /// * `sender` - Trusted user.2274 /// * `spender` - Owner of the token.2275 /// * `token` - The token for which to get the value.2276 fn allowance(2277 &self,2278 sender: T::CrossAccountId,2279 spender: T::CrossAccountId,2280 token: TokenId,2281 ) -> u128;22822283 /// Get extension for RFT collection.2284 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22852286 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2287 /// * `owner` - Token owner2288 /// * `operator` - Operator2289 /// * `approve` - Should operator status be granted or revoked?2290 fn set_allowance_for_all(2291 &self,2292 owner: T::CrossAccountId,2293 operator: T::CrossAccountId,2294 approve: bool,2295 ) -> DispatchResultWithPostInfo;22962297 /// Tells whether the given `owner` approves the `operator`.2298 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22992300 /// Repairs a possibly broken item.2301 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2302}23032304/// Extension for RFT collection.2305pub trait RefungibleExtensions<T>2306where2307 T: Config,2308{2309 /// Change the number of parts of the token.2310 ///2311 /// When the value changes down, this function is equivalent to burning parts of the token.2312 ///2313 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2314 /// * `token` - The token for which you want to change the number of parts.2315 /// * `amount` - The new value of the parts of the token.2316 fn repartition(2317 &self,2318 sender: &T::CrossAccountId,2319 token: TokenId,2320 amount: u128,2321 ) -> DispatchResultWithPostInfo;2322}23232324/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2325///2326/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2327pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2328 let post_info = PostDispatchInfo {2329 actual_weight: Some(weight),2330 pays_fee: Pays::Yes,2331 };2332 match res {2333 Ok(()) => Ok(post_info),2334 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2335 }2336}23372338impl<T: Config> From<PropertiesError> for Error<T> {2339 fn from(error: PropertiesError) -> Self {2340 match error {2341 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2342 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2343 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2344 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2345 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2346 }2347 }2348}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{68 Get,69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 },72 dispatch::Pays,73 transactional, fail,74};75use up_data_structs::{76 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,77 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,79 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,80 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,81 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,82 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,83 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,84 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,85 CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101/// Weight info.102pub type SelfWeightOf<T> = <T as Config>::WeightInfo;103104/// Collection handle contains information about collection data and id.105/// Also provides functionality to count consumed gas.106///107/// CollectionHandle is used as a generic wrapper for collections of all types (except native fungible).108/// It allows to perform common operations and queries on any collection type,109/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].110#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]111pub struct CollectionHandle<T: Config> {112 /// Collection id113 pub id: CollectionId,114 collection: Collection<T::AccountId>,115 /// Substrate recorder for counting consumed gas116 pub recorder: SubstrateRecorder<T>,117}118119impl<T: Config> WithRecorder<T> for CollectionHandle<T> {120 fn recorder(&self) -> &SubstrateRecorder<T> {121 &self.recorder122 }123 fn into_recorder(self) -> SubstrateRecorder<T> {124 self.recorder125 }126}127128impl<T: Config> CollectionHandle<T> {129 /// Same as [CollectionHandle::new] but with an explicit gas limit.130 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {131 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))132 }133134 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].135 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {136 <CollectionById<T>>::get(id).map(|collection| Self {137 id,138 collection,139 recorder,140 })141 }142143 /// Retrives collection data from storage and creates collection handle with default parameters.144 /// If collection not found return `None`145 pub fn new(id: CollectionId) -> Option<Self> {146 Self::new_with_gas_limit(id, u64::MAX)147 }148149 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.150 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {151 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)152 }153154 /// Consume gas for reading.155 pub fn consume_store_reads(156 &self,157 reads: u64,158 ) -> pallet_evm_coder_substrate::execution::Result<()> {159 self.recorder().consume_store_reads(reads)160 }161162 /// Consume gas for writing.163 pub fn consume_store_writes(164 &self,165 writes: u64,166 ) -> pallet_evm_coder_substrate::execution::Result<()> {167 self.recorder().consume_store_writes(writes)168 }169170 /// Consume gas for reading and writing.171 pub fn consume_store_reads_and_writes(172 &self,173 reads: u64,174 writes: u64,175 ) -> pallet_evm_coder_substrate::execution::Result<()> {176 self.recorder()177 .consume_store_reads_and_writes(reads, writes)178 }179180 /// Save collection to storage.181 pub fn save(&self) -> DispatchResult {182 <CollectionById<T>>::insert(self.id, &self.collection);183 Ok(())184 }185186 /// Set collection sponsor.187 ///188 /// Unique collections allows sponsoring for certain actions.189 /// This method allows you to set the sponsor of the collection.190 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].191 pub fn set_sponsor(192 &mut self,193 sender: &T::CrossAccountId,194 sponsor: T::AccountId,195 ) -> DispatchResult {196 self.check_is_internal()?;197 self.check_is_owner_or_admin(sender)?;198199 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());200201 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));202 <PalletEvm<T>>::deposit_log(203 erc::CollectionHelpersEvents::CollectionChanged {204 collection_id: eth::collection_id_to_address(self.id),205 }206 .to_log(T::ContractAddress::get()),207 );208209 self.save()210 }211212 /// Force set `sponsor`.213 ///214 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation215 /// from the `sponsor` is not required.216 ///217 /// # Arguments218 ///219 /// * `sender`: Caller's account.220 /// * `sponsor`: ID of the account of the sponsor-to-be.221 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {222 self.check_is_internal()?;223224 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());225226 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));227 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));228 <PalletEvm<T>>::deposit_log(229 erc::CollectionHelpersEvents::CollectionChanged {230 collection_id: eth::collection_id_to_address(self.id),231 }232 .to_log(T::ContractAddress::get()),233 );234235 self.save()236 }237238 /// Confirm sponsorship239 ///240 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.241 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].242 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {243 self.check_is_internal()?;244 ensure!(245 self.collection.sponsorship.pending_sponsor() == Some(sender),246 Error::<T>::ConfirmSponsorshipFail247 );248249 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());250251 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));252 <PalletEvm<T>>::deposit_log(253 erc::CollectionHelpersEvents::CollectionChanged {254 collection_id: eth::collection_id_to_address(self.id),255 }256 .to_log(T::ContractAddress::get()),257 );258259 self.save()260 }261262 /// Remove collection sponsor.263 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {264 self.check_is_internal()?;265 self.check_is_owner_or_admin(sender)?;266267 self.collection.sponsorship = SponsorshipState::Disabled;268269 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));270 <PalletEvm<T>>::deposit_log(271 erc::CollectionHelpersEvents::CollectionChanged {272 collection_id: eth::collection_id_to_address(self.id),273 }274 .to_log(T::ContractAddress::get()),275 );276 self.save()277 }278279 /// Force remove `sponsor`.280 ///281 /// Differs from `remove_sponsor` in that282 /// it doesn't require consent from the `owner` of the collection.283 pub fn force_remove_sponsor(&mut self) -> DispatchResult {284 self.check_is_internal()?;285286 self.collection.sponsorship = SponsorshipState::Disabled;287288 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));289 <PalletEvm<T>>::deposit_log(290 erc::CollectionHelpersEvents::CollectionChanged {291 collection_id: eth::collection_id_to_address(self.id),292 }293 .to_log(T::ContractAddress::get()),294 );295 self.save()296 }297298 /// Checks that the collection was created with, and must be operated upon through **Unique API**.299 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.300 pub fn check_is_internal(&self) -> DispatchResult {301 if self.flags.external {302 return Err(<Error<T>>::CollectionIsExternal)?;303 }304305 Ok(())306 }307308 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.309 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.310 pub fn check_is_external(&self) -> DispatchResult {311 if !self.flags.external {312 return Err(<Error<T>>::CollectionIsInternal)?;313 }314315 Ok(())316 }317}318319impl<T: Config> Deref for CollectionHandle<T> {320 type Target = Collection<T::AccountId>;321322 fn deref(&self) -> &Self::Target {323 &self.collection324 }325}326327impl<T: Config> DerefMut for CollectionHandle<T> {328 fn deref_mut(&mut self) -> &mut Self::Target {329 &mut self.collection330 }331}332333impl<T: Config> CollectionHandle<T> {334 /// Checks if the `user` is the owner of the collection.335 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {336 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);337 Ok(())338 }339340 /// Returns **true** if the `user` is the owner or administrator of the collection.341 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {342 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))343 }344345 /// Checks if the `user` is the owner or administrator of the collection.346 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {347 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);348 Ok(())349 }350351 /// Returns **true** if352 /// * the `user`is a collection owner or admin353 /// * the collection limits allow the owner/admins to transfer/burn any collection token354 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {355 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)356 }357358 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.359 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {360 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)361 }362363 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.364 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {365 ensure!(366 <Allowlist<T>>::get((self.id, user)),367 <Error<T>>::AddressNotInAllowlist368 );369 Ok(())370 }371372 /// Changes collection owner to another account373 /// #### Store read/writes374 /// 1 writes375 pub fn change_owner(376 &mut self,377 caller: T::CrossAccountId,378 new_owner: T::CrossAccountId,379 ) -> DispatchResult {380 self.check_is_internal()?;381 self.check_is_owner(&caller)?;382 self.collection.owner = new_owner.as_sub().clone();383384 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(385 self.id,386 new_owner.as_sub().clone(),387 ));388 <PalletEvm<T>>::deposit_log(389 erc::CollectionHelpersEvents::CollectionChanged {390 collection_id: eth::collection_id_to_address(self.id),391 }392 .to_log(T::ContractAddress::get()),393 );394395 self.save()396 }397}398399#[frame_support::pallet]400pub mod pallet {401402 use super::*;403 use dispatch::CollectionDispatch;404 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};405 use up_data_structs::{TokenId, mapping::TokenAddressMapping};406 use scale_info::TypeInfo;407 use weights::WeightInfo;408409 #[pallet::config]410 pub trait Config:411 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo412 {413 /// Weight information for functions of this pallet.414 type WeightInfo: WeightInfo;415416 /// Events compatible with [`frame_system::Config::Event`].417 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;418419 /// Handler of accounts and payment.420 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;421422 /// Set price to create a collection.423 #[pallet::constant]424 type CollectionCreationPrice: Get<425 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,426 >;427428 /// Dispatcher of operations on collections.429 type CollectionDispatch: CollectionDispatch<Self>;430431 /// Account which holds the chain's treasury.432 type TreasuryAccountId: Get<Self::AccountId>;433434 /// Address under which the CollectionHelper contract would be available.435 #[pallet::constant]436 type ContractAddress: Get<H160>;437438 /// Mapper for token addresses to Ethereum addresses.439 type EvmTokenAddressMapping: TokenAddressMapping<H160>;440441 /// Mapper for token addresses to [`CrossAccountId`].442 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;443 }444445 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);446 /// Collection id for native fungible collction.447 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);448449 #[pallet::pallet]450 #[pallet::storage_version(STORAGE_VERSION)]451 pub struct Pallet<T>(_);452453 #[pallet::extra_constants]454 impl<T: Config> Pallet<T> {455 /// Maximum admins per collection.456 pub fn collection_admins_limit() -> u32 {457 COLLECTION_ADMINS_LIMIT458 }459 }460461 #[pallet::genesis_config]462 pub struct GenesisConfig<T>(PhantomData<T>);463464 #[cfg(feature = "std")]465 impl<T: Config> Default for GenesisConfig<T> {466 fn default() -> Self {467 Self(Default::default())468 }469 }470471 #[pallet::genesis_build]472 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {473 fn build(&self) {474 StorageVersion::new(1).put::<Pallet<T>>();475 }476 }477478 impl<T: Config> Pallet<T> {479 /// Helper function that handles deposit events480 pub fn deposit_event(event: Event<T>) {481 let event = <T as Config>::RuntimeEvent::from(event);482 let event = event.into();483 <frame_system::Pallet<T>>::deposit_event(event)484 }485 }486487 #[pallet::event]488 pub enum Event<T: Config> {489 /// New collection was created490 CollectionCreated(491 /// Globally unique identifier of newly created collection.492 CollectionId,493 /// [`CollectionMode`] converted into _u8_.494 u8,495 /// Collection owner.496 T::AccountId,497 ),498499 /// New collection was destroyed500 CollectionDestroyed(501 /// Globally unique identifier of collection.502 CollectionId,503 ),504505 /// New item was created.506 ItemCreated(507 /// Id of the collection where item was created.508 CollectionId,509 /// Id of an item. Unique within the collection.510 TokenId,511 /// Owner of newly created item512 T::CrossAccountId,513 /// Always 1 for NFT514 u128,515 ),516517 /// Collection item was burned.518 ItemDestroyed(519 /// Id of the collection where item was destroyed.520 CollectionId,521 /// Identifier of burned NFT.522 TokenId,523 /// Which user has destroyed its tokens.524 T::CrossAccountId,525 /// Amount of token pieces destroed. Always 1 for NFT.526 u128,527 ),528529 /// Item was transferred530 Transfer(531 /// Id of collection to which item is belong.532 CollectionId,533 /// Id of an item.534 TokenId,535 /// Original owner of item.536 T::CrossAccountId,537 /// New owner of item.538 T::CrossAccountId,539 /// Amount of token pieces transfered. Always 1 for NFT.540 u128,541 ),542543 /// Amount pieces of token owned by `sender` was approved for `spender`.544 Approved(545 /// Id of collection to which item is belong.546 CollectionId,547 /// Id of an item.548 TokenId,549 /// Original owner of item.550 T::CrossAccountId,551 /// Id for which the approval was granted.552 T::CrossAccountId,553 /// Amount of token pieces transfered. Always 1 for NFT.554 u128,555 ),556557 /// A `sender` approves operations on all owned tokens for `spender`.558 ApprovedForAll(559 /// Id of collection to which item is belong.560 CollectionId,561 /// Owner of a wallet.562 T::CrossAccountId,563 /// Id for which operator status was granted or rewoked.564 T::CrossAccountId,565 /// Is operator status granted or revoked?566 bool,567 ),568569 /// The colletion property has been added or edited.570 CollectionPropertySet(571 /// Id of collection to which property has been set.572 CollectionId,573 /// The property that was set.574 PropertyKey,575 ),576577 /// The property has been deleted.578 CollectionPropertyDeleted(579 /// Id of collection to which property has been deleted.580 CollectionId,581 /// The property that was deleted.582 PropertyKey,583 ),584585 /// The token property has been added or edited.586 TokenPropertySet(587 /// Identifier of the collection whose token has the property set.588 CollectionId,589 /// The token for which the property was set.590 TokenId,591 /// The property that was set.592 PropertyKey,593 ),594595 /// The token property has been deleted.596 TokenPropertyDeleted(597 /// Identifier of the collection whose token has the property deleted.598 CollectionId,599 /// The token for which the property was deleted.600 TokenId,601 /// The property that was deleted.602 PropertyKey,603 ),604605 /// The token property permission of a collection has been set.606 PropertyPermissionSet(607 /// ID of collection to which property permission has been set.608 CollectionId,609 /// The property permission that was set.610 PropertyKey,611 ),612613 /// Address was added to the allow list.614 AllowListAddressAdded(615 /// ID of the affected collection.616 CollectionId,617 /// Address of the added account.618 T::CrossAccountId,619 ),620621 /// Address was removed from the allow list.622 AllowListAddressRemoved(623 /// ID of the affected collection.624 CollectionId,625 /// Address of the removed account.626 T::CrossAccountId,627 ),628629 /// Collection admin was added.630 CollectionAdminAdded(631 /// ID of the affected collection.632 CollectionId,633 /// Admin address.634 T::CrossAccountId,635 ),636637 /// Collection admin was removed.638 CollectionAdminRemoved(639 /// ID of the affected collection.640 CollectionId,641 /// Removed admin address.642 T::CrossAccountId,643 ),644645 /// Collection limits were set.646 CollectionLimitSet(647 /// ID of the affected collection.648 CollectionId,649 ),650651 /// Collection owned was changed.652 CollectionOwnerChanged(653 /// ID of the affected collection.654 CollectionId,655 /// New owner address.656 T::AccountId,657 ),658659 /// Collection permissions were set.660 CollectionPermissionSet(661 /// ID of the affected collection.662 CollectionId,663 ),664665 /// Collection sponsor was set.666 CollectionSponsorSet(667 /// ID of the affected collection.668 CollectionId,669 /// New sponsor address.670 T::AccountId,671 ),672673 /// New sponsor was confirm.674 SponsorshipConfirmed(675 /// ID of the affected collection.676 CollectionId,677 /// New sponsor address.678 T::AccountId,679 ),680681 /// Collection sponsor was removed.682 CollectionSponsorRemoved(683 /// ID of the affected collection.684 CollectionId,685 ),686 }687688 #[pallet::error]689 pub enum Error<T> {690 /// This collection does not exist.691 CollectionNotFound,692 /// Sender parameter and item owner must be equal.693 MustBeTokenOwner,694 /// No permission to perform action695 NoPermission,696 /// Destroying only empty collections is allowed697 CantDestroyNotEmptyCollection,698 /// Collection is not in mint mode.699 PublicMintingNotAllowed,700 /// Address is not in allow list.701 AddressNotInAllowlist,702703 /// Collection name can not be longer than 63 char.704 CollectionNameLimitExceeded,705 /// Collection description can not be longer than 255 char.706 CollectionDescriptionLimitExceeded,707 /// Token prefix can not be longer than 15 char.708 CollectionTokenPrefixLimitExceeded,709 /// Total collections bound exceeded.710 TotalCollectionsLimitExceeded,711 /// Exceeded max admin count712 CollectionAdminCountExceeded,713 /// Collection limit bounds per collection exceeded714 CollectionLimitBoundsExceeded,715 /// Tried to enable permissions which are only permitted to be disabled716 OwnerPermissionsCantBeReverted,717 /// Collection settings not allowing items transferring718 TransferNotAllowed,719 /// Account token limit exceeded per collection720 AccountTokenLimitExceeded,721 /// Collection token limit exceeded722 CollectionTokenLimitExceeded,723 /// Metadata flag frozen724 MetadataFlagFrozen,725726 /// Item does not exist727 TokenNotFound,728 /// Item is balance not enough729 TokenValueTooLow,730 /// Requested value is more than the approved731 ApprovedValueTooLow,732 /// Tried to approve more than owned733 CantApproveMoreThanOwned,734 /// Only spending from eth mirror could be approved735 AddressIsNotEthMirror,736737 /// Can't transfer tokens to ethereum zero address738 AddressIsZero,739740 /// The operation is not supported741 UnsupportedOperation,742743 /// Insufficient funds to perform an action744 NotSufficientFounds,745746 /// User does not satisfy the nesting rule747 UserIsNotAllowedToNest,748 /// Only tokens from specific collections may nest tokens under this one749 SourceCollectionIsNotAllowedToNest,750751 /// Tried to store more data than allowed in collection field752 CollectionFieldSizeExceeded,753754 /// Tried to store more property data than allowed755 NoSpaceForProperty,756757 /// Tried to store more property keys than allowed758 PropertyLimitReached,759760 /// Property key is too long761 PropertyKeyIsTooLong,762763 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed764 InvalidCharacterInPropertyKey,765766 /// Empty property keys are forbidden767 EmptyPropertyKey,768769 /// Tried to access an external collection with an internal API770 CollectionIsExternal,771772 /// Tried to access an internal collection with an external API773 CollectionIsInternal,774775 /// This address is not set as sponsor, use setCollectionSponsor first.776 ConfirmSponsorshipFail,777778 /// The user is not an administrator.779 UserIsNotCollectionAdmin,780 }781782 /// Storage of the count of created collections. Essentially contains the last collection ID.783 #[pallet::storage]784 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;785786 /// Storage of the count of deleted collections.787 #[pallet::storage]788 pub type DestroyedCollectionCount<T> =789 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;790791 /// Storage of collection info.792 #[pallet::storage]793 pub type CollectionById<T> = StorageMap<794 Hasher = Blake2_128Concat,795 Key = CollectionId,796 Value = Collection<<T as frame_system::Config>::AccountId>,797 QueryKind = OptionQuery,798 >;799800 /// Storage of collection properties.801 #[pallet::storage]802 #[pallet::getter(fn collection_properties)]803 pub type CollectionProperties<T> = StorageMap<804 Hasher = Blake2_128Concat,805 Key = CollectionId,806 Value = CollectionPropertiesT,807 QueryKind = ValueQuery,808 >;809810 /// Storage of token property permissions of a collection.811 #[pallet::storage]812 #[pallet::getter(fn property_permissions)]813 pub type CollectionPropertyPermissions<T> = StorageMap<814 Hasher = Blake2_128Concat,815 Key = CollectionId,816 Value = PropertiesPermissionMap,817 QueryKind = ValueQuery,818 >;819820 /// Storage of the amount of collection admins.821 #[pallet::storage]822 pub type AdminAmount<T> = StorageMap<823 Hasher = Blake2_128Concat,824 Key = CollectionId,825 Value = u32,826 QueryKind = ValueQuery,827 >;828829 /// List of collection admins.830 #[pallet::storage]831 pub type IsAdmin<T: Config> = StorageNMap<832 Key = (833 Key<Blake2_128Concat, CollectionId>,834 Key<Blake2_128Concat, T::CrossAccountId>,835 ),836 Value = bool,837 QueryKind = ValueQuery,838 >;839840 /// Allowlisted collection users.841 #[pallet::storage]842 pub type Allowlist<T: Config> = StorageNMap<843 Key = (844 Key<Blake2_128Concat, CollectionId>,845 Key<Blake2_128Concat, T::CrossAccountId>,846 ),847 Value = bool,848 QueryKind = ValueQuery,849 >;850851 /// Not used by code, exists only to provide some types to metadata.852 #[pallet::storage]853 pub type DummyStorageValue<T: Config> = StorageValue<854 Value = (855 CollectionStats,856 CollectionId,857 TokenId,858 TokenChild,859 PhantomType<(860 TokenData<T::CrossAccountId>,861 RpcCollection<T::AccountId>,862 // PoV Estimate Info863 PovInfo,864 )>,865 ),866 QueryKind = OptionQuery,867 >;868}869870impl<T: Config> Pallet<T> {871 /// Enshure that receiver address is correct.872 ///873 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.874 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {875 ensure!(876 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,877 <Error<T>>::AddressIsZero878 );879 Ok(())880 }881882 /// Get a vector of collection admins.883 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {884 <IsAdmin<T>>::iter_prefix((collection,))885 .map(|(a, _)| a)886 .collect()887 }888889 /// Get a vector of users allowed to mint tokens.890 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {891 <Allowlist<T>>::iter_prefix((collection,))892 .map(|(a, _)| a)893 .collect()894 }895896 /// Is `user` allowed to mint token in `collection`.897 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {898 <Allowlist<T>>::get((collection, user))899 }900901 /// Get statistics of collections.902 pub fn collection_stats() -> CollectionStats {903 let created = <CreatedCollectionCount<T>>::get();904 let destroyed = <DestroyedCollectionCount<T>>::get();905 CollectionStats {906 created: created.0,907 destroyed: destroyed.0,908 alive: created.0 - destroyed.0,909 }910 }911912 /// Get the effective limits for the collection.913 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {914 let collection = <CollectionById<T>>::get(collection)?;915 let limits = collection.limits;916 let effective_limits = CollectionLimits {917 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),918 sponsored_data_size: Some(limits.sponsored_data_size()),919 sponsored_data_rate_limit: Some(920 limits921 .sponsored_data_rate_limit922 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),923 ),924 token_limit: Some(limits.token_limit()),925 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(926 match collection.mode {927 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,928 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,929 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,930 },931 )),932 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),933 owner_can_transfer: Some(limits.owner_can_transfer()),934 owner_can_destroy: Some(limits.owner_can_destroy()),935 transfers_enabled: Some(limits.transfers_enabled()),936 };937938 Some(effective_limits)939 }940941 /// Returns information about the `collection` adapted for rpc.942 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {943 let Collection {944 name,945 description,946 owner,947 mode,948 token_prefix,949 sponsorship,950 limits,951 permissions,952 flags,953 } = <CollectionById<T>>::get(collection)?;954955 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)956 .into_iter()957 .map(|(key, permission)| PropertyKeyPermission { key, permission })958 .collect();959960 let properties = <CollectionProperties<T>>::get(collection)961 .into_iter()962 .map(|(key, value)| Property { key, value })963 .collect();964965 let permissions = CollectionPermissions {966 access: Some(permissions.access()),967 mint_mode: Some(permissions.mint_mode()),968 nesting: Some(permissions.nesting().clone()),969 };970971 Some(RpcCollection {972 name: name.into_inner(),973 description: description.into_inner(),974 owner,975 mode,976 token_prefix: token_prefix.into_inner(),977 sponsorship,978 limits,979 permissions,980 token_property_permissions,981 properties,982 read_only: flags.external,983984 flags: RpcCollectionFlags {985 foreign: flags.foreign,986 erc721metadata: flags.erc721metadata,987 },988 })989 }990}991992macro_rules! limit_default {993 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{994 $(995 if let Some($new) = $new.$field {996 let $old = $old.$field($($arg)?);997 let _ = $new;998 let _ = $old;999 $check1000 } else {1001 $new.$field = $old.$field1002 }1003 )*1004 }};1005}1006macro_rules! limit_default_clone {1007 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1008 $(1009 if let Some($new) = $new.$field.clone() {1010 let $old = $old.$field($($arg)?);1011 let _ = $new;1012 let _ = $old;1013 $check1014 } else {1015 $new.$field = $old.$field.clone()1016 }1017 )*1018 }};1019}10201021impl<T: Config> Pallet<T> {1022 /// Create new collection.1023 ///1024 /// * `owner` - The owner of the collection.1025 /// * `data` - Description of the created collection.1026 /// * `flags` - Extra flags to store.1027 pub fn init_collection(1028 owner: T::CrossAccountId,1029 payer: T::CrossAccountId,1030 data: CreateCollectionData<T::AccountId>,1031 flags: CollectionFlags,1032 ) -> Result<CollectionId, DispatchError> {1033 {1034 ensure!(1035 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1036 Error::<T>::CollectionTokenPrefixLimitExceeded1037 );1038 }10391040 let created_count = <CreatedCollectionCount<T>>::get()1041 .01042 .checked_add(1)1043 .ok_or(ArithmeticError::Overflow)?;1044 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1045 let id = CollectionId(created_count);10461047 // bound Total number of collections1048 ensure!(1049 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1050 <Error<T>>::TotalCollectionsLimitExceeded1051 );10521053 // =========10541055 let collection = Collection {1056 owner: owner.as_sub().clone(),1057 name: data.name,1058 mode: data.mode.clone(),1059 description: data.description,1060 token_prefix: data.token_prefix,1061 sponsorship: data1062 .pending_sponsor1063 .map(SponsorshipState::Unconfirmed)1064 .unwrap_or_default(),1065 limits: data1066 .limits1067 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1068 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1069 permissions: data1070 .permissions1071 .map(|permissions| {1072 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1073 })1074 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1075 flags,1076 };10771078 let mut collection_properties = CollectionPropertiesT::new();1079 collection_properties1080 .try_set_from_iter(data.properties.into_iter())1081 .map_err(<Error<T>>::from)?;10821083 CollectionProperties::<T>::insert(id, collection_properties);10841085 let mut token_props_permissions = PropertiesPermissionMap::new();1086 token_props_permissions1087 .try_set_from_iter(data.token_property_permissions.into_iter())1088 .map_err(<Error<T>>::from)?;10891090 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);10911092 // Take a (non-refundable) deposit of collection creation1093 {1094 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1095 imbalance.subsume(<T as Config>::Currency::deposit(1096 &T::TreasuryAccountId::get(),1097 T::CollectionCreationPrice::get(),1098 Precision::Exact,1099 )?);1100 let credit =1101 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1102 .map_err(|_| Error::<T>::NotSufficientFounds)?;11031104 debug_assert!(credit.peek().is_zero())1105 }11061107 <CreatedCollectionCount<T>>::put(created_count);1108 <Pallet<T>>::deposit_event(Event::CollectionCreated(1109 id,1110 data.mode.id(),1111 owner.as_sub().clone(),1112 ));1113 <PalletEvm<T>>::deposit_log(1114 erc::CollectionHelpersEvents::CollectionCreated {1115 owner: *owner.as_eth(),1116 collection_id: eth::collection_id_to_address(id),1117 }1118 .to_log(T::ContractAddress::get()),1119 );1120 <CollectionById<T>>::insert(id, collection);1121 Ok(id)1122 }11231124 /// Destroy collection.1125 ///1126 /// * `collection` - Collection handler.1127 /// * `sender` - The owner or administrator of the collection.1128 pub fn destroy_collection(1129 collection: CollectionHandle<T>,1130 sender: &T::CrossAccountId,1131 ) -> DispatchResult {1132 ensure!(1133 collection.limits.owner_can_destroy(),1134 <Error<T>>::NoPermission,1135 );1136 collection.check_is_owner(sender)?;11371138 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1139 .01140 .checked_add(1)1141 .ok_or(ArithmeticError::Overflow)?;11421143 // =========11441145 <DestroyedCollectionCount<T>>::put(destroyed_collections);1146 <CollectionById<T>>::remove(collection.id);1147 <AdminAmount<T>>::remove(collection.id);1148 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1149 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1150 <CollectionProperties<T>>::remove(collection.id);11511152 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11531154 <PalletEvm<T>>::deposit_log(1155 erc::CollectionHelpersEvents::CollectionDestroyed {1156 collection_id: eth::collection_id_to_address(collection.id),1157 }1158 .to_log(T::ContractAddress::get()),1159 );1160 Ok(())1161 }11621163 /// This function sets or removes a collection properties according to1164 /// `properties_updates` contents:1165 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1166 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1167 ///1168 /// This function fires an event for each property change.1169 /// In case of an error, all the changes (including the events) will be reverted1170 /// since the function is transactional.1171 #[transactional]1172 fn modify_collection_properties(1173 collection: &CollectionHandle<T>,1174 sender: &T::CrossAccountId,1175 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1176 ) -> DispatchResult {1177 collection.check_is_owner_or_admin(sender)?;11781179 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);11801181 for (key, value) in properties_updates {1182 match value {1183 Some(value) => {1184 stored_properties1185 .try_set(key.clone(), value)1186 .map_err(<Error<T>>::from)?;11871188 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1189 <PalletEvm<T>>::deposit_log(1190 erc::CollectionHelpersEvents::CollectionChanged {1191 collection_id: eth::collection_id_to_address(collection.id),1192 }1193 .to_log(T::ContractAddress::get()),1194 );1195 }1196 None => {1197 stored_properties.remove(&key).map_err(<Error<T>>::from)?;11981199 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1200 <PalletEvm<T>>::deposit_log(1201 erc::CollectionHelpersEvents::CollectionChanged {1202 collection_id: eth::collection_id_to_address(collection.id),1203 }1204 .to_log(T::ContractAddress::get()),1205 );1206 }1207 }1208 }12091210 <CollectionProperties<T>>::set(collection.id, stored_properties);12111212 Ok(())1213 }12141215 /// A batch operation to add, edit or remove properties for a token.1216 /// It sets or removes a token's properties according to1217 /// `properties_updates` contents:1218 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1219 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1220 ///1221 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1222 /// - `is_token_create`: Indicates that method is called during token initialization.1223 /// Allows to bypass ownership check.1224 ///1225 /// All affected properties should have `mutable` permission1226 /// to be **deleted** or to be **set more than once**,1227 /// and the sender should have permission to edit those properties.1228 ///1229 /// This function fires an event for each property change.1230 /// In case of an error, all the changes (including the events) will be reverted1231 /// since the function is transactional.1232 pub fn modify_token_properties(1233 collection: &CollectionHandle<T>,1234 sender: &T::CrossAccountId,1235 token_id: TokenId,1236 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1237 is_token_create: bool,1238 mut stored_properties: TokenProperties,1239 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1240 set_token_properties: impl FnOnce(TokenProperties),1241 log: evm_coder::ethereum::Log,1242 ) -> DispatchResult {1243 let is_collection_admin = collection.is_owner_or_admin(sender);1244 let permissions = Self::property_permissions(collection.id);12451246 let mut token_owner_result = None;1247 let mut is_token_owner = || -> Result<bool, DispatchError> {1248 *token_owner_result.get_or_insert_with(&is_token_owner)1249 };12501251 for (key, value) in properties_updates {1252 let permission = permissions1253 .get(&key)1254 .cloned()1255 .unwrap_or_else(PropertyPermission::none);12561257 let is_property_exists = stored_properties.get(&key).is_some();12581259 match permission {1260 PropertyPermission { mutable: false, .. } if is_property_exists => {1261 return Err(<Error<T>>::NoPermission.into());1262 }12631264 PropertyPermission {1265 collection_admin,1266 token_owner,1267 ..1268 } => {1269 //TODO: investigate threats during public minting.1270 let is_token_create =1271 is_token_create && (collection_admin || token_owner) && value.is_some();1272 if !(is_token_create1273 || (collection_admin && is_collection_admin)1274 || (token_owner && is_token_owner()?))1275 {1276 fail!(<Error<T>>::NoPermission);1277 }1278 }1279 }12801281 match value {1282 Some(value) => {1283 stored_properties1284 .try_set(key.clone(), value)1285 .map_err(<Error<T>>::from)?;12861287 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1288 }1289 None => {1290 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12911292 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1293 }1294 }12951296 <PalletEvm<T>>::deposit_log(log.clone());1297 }12981299 set_token_properties(stored_properties);13001301 Ok(())1302 }13031304 /// Sets or unsets the approval of a given operator.1305 ///1306 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1307 /// - `owner`: Token owner1308 /// - `operator`: Operator1309 /// - `approve`: Should operator status be granted or revoked?1310 pub fn set_allowance_for_all(1311 collection: &CollectionHandle<T>,1312 owner: &T::CrossAccountId,1313 operator: &T::CrossAccountId,1314 approve: bool,1315 set_allowance: impl FnOnce(),1316 log: evm_coder::ethereum::Log,1317 ) -> DispatchResult {1318 if collection.permissions.access() == AccessMode::AllowList {1319 collection.check_allowlist(owner)?;1320 collection.check_allowlist(operator)?;1321 }13221323 Self::ensure_correct_receiver(operator)?;13241325 set_allowance();13261327 <PalletEvm<T>>::deposit_log(log);1328 Self::deposit_event(Event::ApprovedForAll(1329 collection.id,1330 owner.clone(),1331 operator.clone(),1332 approve,1333 ));1334 Ok(())1335 }13361337 /// Set collection property.1338 ///1339 /// * `collection` - Collection handler.1340 /// * `sender` - The owner or administrator of the collection.1341 /// * `property` - The property to set.1342 pub fn set_collection_property(1343 collection: &CollectionHandle<T>,1344 sender: &T::CrossAccountId,1345 property: Property,1346 ) -> DispatchResult {1347 Self::set_collection_properties(collection, sender, [property].into_iter())1348 }13491350 /// Set a scoped collection property, where the scope is a special prefix1351 /// prohibiting a user access to change the property directly.1352 ///1353 /// * `collection_id` - ID of the collection for which the property is being set.1354 /// * `scope` - Property scope.1355 /// * `property` - The property to set.1356 pub fn set_scoped_collection_property(1357 collection_id: CollectionId,1358 scope: PropertyScope,1359 property: Property,1360 ) -> DispatchResult {1361 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1362 properties.try_scoped_set(scope, property.key, property.value)1363 })1364 .map_err(<Error<T>>::from)?;13651366 Ok(())1367 }13681369 /// Set scoped collection properties, where the scope is a special prefix1370 /// prohibiting a user access to change the properties directly.1371 ///1372 /// * `collection_id` - ID of the collection for which the properties is being set.1373 /// * `scope` - Property scope.1374 /// * `properties` - The properties to set.1375 pub fn set_scoped_collection_properties(1376 collection_id: CollectionId,1377 scope: PropertyScope,1378 properties: impl Iterator<Item = Property>,1379 ) -> DispatchResult {1380 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1381 stored_properties.try_scoped_set_from_iter(scope, properties)1382 })1383 .map_err(<Error<T>>::from)?;13841385 Ok(())1386 }13871388 /// Set collection properties.1389 ///1390 /// * `collection` - Collection handler.1391 /// * `sender` - The owner or administrator of the collection.1392 /// * `properties` - The properties to set.1393 pub fn set_collection_properties(1394 collection: &CollectionHandle<T>,1395 sender: &T::CrossAccountId,1396 properties: impl Iterator<Item = Property>,1397 ) -> DispatchResult {1398 Self::modify_collection_properties(1399 collection,1400 sender,1401 properties.map(|property| (property.key, Some(property.value))),1402 )1403 }14041405 /// Delete collection property.1406 ///1407 /// * `collection` - Collection handler.1408 /// * `sender` - The owner or administrator of the collection.1409 /// * `property` - The property to delete.1410 pub fn delete_collection_property(1411 collection: &CollectionHandle<T>,1412 sender: &T::CrossAccountId,1413 property_key: PropertyKey,1414 ) -> DispatchResult {1415 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1416 }14171418 /// Delete collection properties.1419 ///1420 /// * `collection` - Collection handler.1421 /// * `sender` - The owner or administrator of the collection.1422 /// * `properties` - The properties to delete.1423 pub fn delete_collection_properties(1424 collection: &CollectionHandle<T>,1425 sender: &T::CrossAccountId,1426 property_keys: impl Iterator<Item = PropertyKey>,1427 ) -> DispatchResult {1428 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1429 }14301431 /// Set collection propetry permission without any checks.1432 ///1433 /// Used for migrations.1434 ///1435 /// * `collection` - Collection handler.1436 /// * `property_permissions` - Property permissions.1437 pub fn set_property_permission_unchecked(1438 collection: CollectionId,1439 property_permission: PropertyKeyPermission,1440 ) -> DispatchResult {1441 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1442 permissions.try_set(property_permission.key, property_permission.permission)1443 })1444 .map_err(<Error<T>>::from)?;1445 Ok(())1446 }14471448 /// Set collection property permission.1449 ///1450 /// * `collection` - Collection handler.1451 /// * `sender` - The owner or administrator of the collection.1452 /// * `property_permission` - Property permission.1453 pub fn set_property_permission(1454 collection: &CollectionHandle<T>,1455 sender: &T::CrossAccountId,1456 property_permission: PropertyKeyPermission,1457 ) -> DispatchResult {1458 Self::set_scoped_property_permission(1459 collection,1460 sender,1461 PropertyScope::None,1462 property_permission,1463 )1464 }14651466 /// Set collection property permission with scope.1467 ///1468 /// * `collection` - Collection handler.1469 /// * `sender` - The owner or administrator of the collection.1470 /// * `scope` - Property scope.1471 /// * `property_permission` - Property permission.1472 pub fn set_scoped_property_permission(1473 collection: &CollectionHandle<T>,1474 sender: &T::CrossAccountId,1475 scope: PropertyScope,1476 property_permission: PropertyKeyPermission,1477 ) -> DispatchResult {1478 collection.check_is_owner_or_admin(sender)?;14791480 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1481 let current_permission = all_permissions.get(&property_permission.key);1482 if matches![1483 current_permission,1484 Some(PropertyPermission { mutable: false, .. })1485 ] {1486 return Err(<Error<T>>::NoPermission.into());1487 }14881489 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1490 let property_permission = property_permission.clone();1491 permissions.try_scoped_set(1492 scope,1493 property_permission.key,1494 property_permission.permission,1495 )1496 })1497 .map_err(<Error<T>>::from)?;14981499 Self::deposit_event(Event::PropertyPermissionSet(1500 collection.id,1501 property_permission.key,1502 ));1503 <PalletEvm<T>>::deposit_log(1504 erc::CollectionHelpersEvents::CollectionChanged {1505 collection_id: eth::collection_id_to_address(collection.id),1506 }1507 .to_log(T::ContractAddress::get()),1508 );15091510 Ok(())1511 }15121513 /// Set token property permission.1514 ///1515 /// * `collection` - Collection handler.1516 /// * `sender` - The owner or administrator of the collection.1517 /// * `property_permissions` - Property permissions.1518 #[transactional]1519 pub fn set_token_property_permissions(1520 collection: &CollectionHandle<T>,1521 sender: &T::CrossAccountId,1522 property_permissions: Vec<PropertyKeyPermission>,1523 ) -> DispatchResult {1524 Self::set_scoped_token_property_permissions(1525 collection,1526 sender,1527 PropertyScope::None,1528 property_permissions,1529 )1530 }15311532 /// Set token property permission with scope.1533 ///1534 /// * `collection` - Collection handler.1535 /// * `sender` - The owner or administrator of the collection.1536 /// * `scope` - Property scope.1537 /// * `property_permissions` - Property permissions.1538 #[transactional]1539 pub fn set_scoped_token_property_permissions(1540 collection: &CollectionHandle<T>,1541 sender: &T::CrossAccountId,1542 scope: PropertyScope,1543 property_permissions: Vec<PropertyKeyPermission>,1544 ) -> DispatchResult {1545 for prop_pemission in property_permissions {1546 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1547 }15481549 Ok(())1550 }15511552 /// Get collection property.1553 pub fn get_collection_property(1554 collection_id: CollectionId,1555 key: &PropertyKey,1556 ) -> Option<PropertyValue> {1557 Self::collection_properties(collection_id).get(key).cloned()1558 }15591560 /// Convert byte vector to property key vector.1561 pub fn bytes_keys_to_property_keys(1562 keys: Vec<Vec<u8>>,1563 ) -> Result<Vec<PropertyKey>, DispatchError> {1564 keys.into_iter()1565 .map(|key| -> Result<PropertyKey, DispatchError> {1566 key.try_into()1567 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1568 })1569 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1570 }15711572 /// Get properties according to given keys.1573 pub fn filter_collection_properties(1574 collection_id: CollectionId,1575 keys: Option<Vec<PropertyKey>>,1576 ) -> Result<Vec<Property>, DispatchError> {1577 let properties = Self::collection_properties(collection_id);15781579 let properties = keys1580 .map(|keys| {1581 keys.into_iter()1582 .filter_map(|key| {1583 properties.get(&key).map(|value| Property {1584 key,1585 value: value.clone(),1586 })1587 })1588 .collect()1589 })1590 .unwrap_or_else(|| {1591 properties1592 .into_iter()1593 .map(|(key, value)| Property { key, value })1594 .collect()1595 });15961597 Ok(properties)1598 }15991600 /// Get property permissions according to given keys.1601 pub fn filter_property_permissions(1602 collection_id: CollectionId,1603 keys: Option<Vec<PropertyKey>>,1604 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1605 let permissions = Self::property_permissions(collection_id);16061607 let key_permissions = keys1608 .map(|keys| {1609 keys.into_iter()1610 .filter_map(|key| {1611 permissions1612 .get(&key)1613 .map(|permission| PropertyKeyPermission {1614 key,1615 permission: permission.clone(),1616 })1617 })1618 .collect()1619 })1620 .unwrap_or_else(|| {1621 permissions1622 .into_iter()1623 .map(|(key, permission)| PropertyKeyPermission { key, permission })1624 .collect()1625 });16261627 Ok(key_permissions)1628 }16291630 /// Toggle `user` participation in the `collection`'s allow list.1631 /// #### Store read/writes1632 /// 1 writes1633 pub fn toggle_allowlist(1634 collection: &CollectionHandle<T>,1635 sender: &T::CrossAccountId,1636 user: &T::CrossAccountId,1637 allowed: bool,1638 ) -> DispatchResult {1639 collection.check_is_owner_or_admin(sender)?;16401641 // =========16421643 if allowed {1644 <Allowlist<T>>::insert((collection.id, user), true);1645 Self::deposit_event(Event::<T>::AllowListAddressAdded(1646 collection.id,1647 user.clone(),1648 ));1649 } else {1650 <Allowlist<T>>::remove((collection.id, user));1651 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1652 collection.id,1653 user.clone(),1654 ));1655 }16561657 <PalletEvm<T>>::deposit_log(1658 erc::CollectionHelpersEvents::CollectionChanged {1659 collection_id: eth::collection_id_to_address(collection.id),1660 }1661 .to_log(T::ContractAddress::get()),1662 );16631664 Ok(())1665 }16661667 /// Toggle `user` participation in the `collection`'s admin list.1668 /// #### Store read/writes1669 /// 2 reads, 2 writes1670 pub fn toggle_admin(1671 collection: &CollectionHandle<T>,1672 sender: &T::CrossAccountId,1673 user: &T::CrossAccountId,1674 admin: bool,1675 ) -> DispatchResult {1676 collection.check_is_internal()?;1677 collection.check_is_owner(sender)?;16781679 let is_admin = <IsAdmin<T>>::get((collection.id, user));1680 if is_admin == admin {1681 if admin {1682 return Ok(());1683 } else {1684 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1685 }1686 }1687 let amount = <AdminAmount<T>>::get(collection.id);16881689 // =========16901691 if admin {1692 let amount = amount1693 .checked_add(1)1694 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1695 ensure!(1696 amount <= Self::collection_admins_limit(),1697 <Error<T>>::CollectionAdminCountExceeded,1698 );16991700 <AdminAmount<T>>::insert(collection.id, amount);1701 <IsAdmin<T>>::insert((collection.id, user), true);17021703 Self::deposit_event(Event::<T>::CollectionAdminAdded(1704 collection.id,1705 user.clone(),1706 ));1707 } else {1708 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1709 <IsAdmin<T>>::remove((collection.id, user));17101711 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1712 collection.id,1713 user.clone(),1714 ));1715 }17161717 <PalletEvm<T>>::deposit_log(1718 erc::CollectionHelpersEvents::CollectionChanged {1719 collection_id: eth::collection_id_to_address(collection.id),1720 }1721 .to_log(T::ContractAddress::get()),1722 );17231724 Ok(())1725 }17261727 /// Update collection limits.1728 pub fn update_limits(1729 user: &T::CrossAccountId,1730 collection: &mut CollectionHandle<T>,1731 new_limit: CollectionLimits,1732 ) -> DispatchResult {1733 collection.check_is_internal()?;1734 collection.check_is_owner_or_admin(user)?;17351736 collection.limits =1737 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17381739 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1740 <PalletEvm<T>>::deposit_log(1741 erc::CollectionHelpersEvents::CollectionChanged {1742 collection_id: eth::collection_id_to_address(collection.id),1743 }1744 .to_log(T::ContractAddress::get()),1745 );17461747 collection.save()1748 }17491750 /// Merge set fields from `new_limit` to `old_limit`.1751 fn clamp_limits(1752 mode: CollectionMode,1753 old_limit: &CollectionLimits,1754 mut new_limit: CollectionLimits,1755 ) -> Result<CollectionLimits, DispatchError> {1756 let limits = old_limit;1757 limit_default!(old_limit, new_limit,1758 account_token_ownership_limit => ensure!(1759 new_limit <= MAX_TOKEN_OWNERSHIP,1760 <Error<T>>::CollectionLimitBoundsExceeded,1761 ),1762 sponsored_data_size => ensure!(1763 new_limit <= CUSTOM_DATA_LIMIT,1764 <Error<T>>::CollectionLimitBoundsExceeded,1765 ),17661767 sponsored_data_rate_limit => {},1768 token_limit => ensure!(1769 old_limit >= new_limit && new_limit > 0,1770 <Error<T>>::CollectionTokenLimitExceeded1771 ),17721773 sponsor_transfer_timeout(match mode {1774 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1775 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1776 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1777 }) => ensure!(1778 new_limit <= MAX_SPONSOR_TIMEOUT,1779 <Error<T>>::CollectionLimitBoundsExceeded,1780 ),1781 sponsor_approve_timeout => {},1782 owner_can_transfer => ensure!(1783 !limits.owner_can_transfer_instaled() ||1784 old_limit || !new_limit,1785 <Error<T>>::OwnerPermissionsCantBeReverted,1786 ),1787 owner_can_destroy => ensure!(1788 old_limit || !new_limit,1789 <Error<T>>::OwnerPermissionsCantBeReverted,1790 ),1791 transfers_enabled => {},1792 );1793 Ok(new_limit)1794 }17951796 /// Update collection permissions.1797 pub fn update_permissions(1798 user: &T::CrossAccountId,1799 collection: &mut CollectionHandle<T>,1800 new_permission: CollectionPermissions,1801 ) -> DispatchResult {1802 collection.check_is_internal()?;1803 collection.check_is_owner_or_admin(user)?;1804 collection.permissions = Self::clamp_permissions(1805 collection.mode.clone(),1806 &collection.permissions,1807 new_permission,1808 )?;18091810 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1811 <PalletEvm<T>>::deposit_log(1812 erc::CollectionHelpersEvents::CollectionChanged {1813 collection_id: eth::collection_id_to_address(collection.id),1814 }1815 .to_log(T::ContractAddress::get()),1816 );18171818 collection.save()1819 }18201821 /// Merge set fields from `new_permission` to `old_permission`.1822 fn clamp_permissions(1823 _mode: CollectionMode,1824 old_permission: &CollectionPermissions,1825 mut new_permission: CollectionPermissions,1826 ) -> Result<CollectionPermissions, DispatchError> {1827 limit_default_clone!(old_permission, new_permission,1828 access => {},1829 mint_mode => {},1830 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1831 );1832 Ok(new_permission)1833 }18341835 /// Repair possibly broken properties of a collection.1836 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1837 CollectionProperties::<T>::mutate(collection_id, |properties| {1838 properties.recompute_consumed_space();1839 });18401841 Ok(())1842 }1843}18441845/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1846#[macro_export]1847macro_rules! unsupported {1848 ($runtime:path) => {1849 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1850 };1851}18521853/// Return weights for various worst-case operations.1854pub trait CommonWeightInfo<CrossAccountId> {1855 /// Weight of item creation.1856 fn create_item(data: &CreateItemData) -> Weight {1857 Self::create_multiple_items(from_ref(data))1858 }18591860 /// Weight of items creation.1861 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18621863 /// Weight of items creation.1864 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18651866 /// The weight of the burning item.1867 fn burn_item() -> Weight;18681869 /// Property setting weight.1870 ///1871 /// * `amount`- The number of properties to set.1872 fn set_collection_properties(amount: u32) -> Weight;18731874 /// Collection property deletion weight.1875 ///1876 /// * `amount`- The number of properties to set.1877 fn delete_collection_properties(amount: u32) -> Weight;18781879 /// Token property setting weight.1880 ///1881 /// * `amount`- The number of properties to set.1882 fn set_token_properties(amount: u32) -> Weight;18831884 /// Token property deletion weight.1885 ///1886 /// * `amount`- The number of properties to delete.1887 fn delete_token_properties(amount: u32) -> Weight;18881889 /// Token property permissions set weight.1890 ///1891 /// * `amount`- The number of property permissions to set.1892 fn set_token_property_permissions(amount: u32) -> Weight;18931894 /// Transfer price of the token or its parts.1895 fn transfer() -> Weight;18961897 /// The price of setting the permission of the operation from another user.1898 fn approve() -> Weight;18991900 /// The price of setting the permission of the operation from another user for eth mirror.1901 fn approve_from() -> Weight;19021903 /// Transfer price from another user.1904 fn transfer_from() -> Weight;19051906 /// The price of burning a token from another user.1907 fn burn_from() -> Weight;19081909 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1910 /// whole users's balance.1911 ///1912 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1913 fn burn_recursively_self_raw() -> Weight;19141915 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1916 ///1917 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1918 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19191920 /// The price of recursive burning a token.1921 ///1922 /// `max_selfs` - The maximum burning weight of the token itself.1923 /// `max_breadth` - The maximum number of nested tokens to burn.1924 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1925 Self::burn_recursively_self_raw()1926 .saturating_mul(max_selfs.max(1) as u64)1927 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1928 }19291930 /// The price of retrieving token owner1931 fn token_owner() -> Weight;19321933 /// The price of setting approval for all1934 fn set_allowance_for_all() -> Weight;19351936 /// The price of repairing an item.1937 fn force_repair_item() -> Weight;1938}19391940/// Weight info extension trait for refungible pallet.1941pub trait RefungibleExtensionsWeightInfo {1942 /// Weight of token repartition.1943 fn repartition() -> Weight;1944}19451946/// Common collection operations.1947///1948/// It wraps methods in Fungible, Nonfungible and Refungible pallets1949/// and adds weight info.1950pub trait CommonCollectionOperations<T: Config> {1951 /// Create token.1952 ///1953 /// * `sender` - The user who mint the token and pays for the transaction.1954 /// * `to` - The user who will own the token.1955 /// * `data` - Token data.1956 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1957 fn create_item(1958 &self,1959 sender: T::CrossAccountId,1960 to: T::CrossAccountId,1961 data: CreateItemData,1962 nesting_budget: &dyn Budget,1963 ) -> DispatchResultWithPostInfo;19641965 /// Create multiple tokens.1966 ///1967 /// * `sender` - The user who mint the token and pays for the transaction.1968 /// * `to` - The user who will own the token.1969 /// * `data` - Token data.1970 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1971 fn create_multiple_items(1972 &self,1973 sender: T::CrossAccountId,1974 to: T::CrossAccountId,1975 data: Vec<CreateItemData>,1976 nesting_budget: &dyn Budget,1977 ) -> DispatchResultWithPostInfo;19781979 /// Create multiple tokens.1980 ///1981 /// * `sender` - The user who mint the token and pays for the transaction.1982 /// * `to` - The user who will own the token.1983 /// * `data` - Token data.1984 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1985 fn create_multiple_items_ex(1986 &self,1987 sender: T::CrossAccountId,1988 data: CreateItemExData<T::CrossAccountId>,1989 nesting_budget: &dyn Budget,1990 ) -> DispatchResultWithPostInfo;19911992 /// Burn token.1993 ///1994 /// * `sender` - The user who owns the token.1995 /// * `token` - Token id that will burned.1996 /// * `amount` - The number of parts of the token that will be burned.1997 fn burn_item(1998 &self,1999 sender: T::CrossAccountId,2000 token: TokenId,2001 amount: u128,2002 ) -> DispatchResultWithPostInfo;20032004 /// Burn token and all nested tokens recursievly.2005 ///2006 /// * `sender` - The user who owns the token.2007 /// * `token` - Token id that will burned.2008 /// * `self_budget` - The budget that can be spent on burning tokens.2009 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2010 fn burn_item_recursively(2011 &self,2012 sender: T::CrossAccountId,2013 token: TokenId,2014 self_budget: &dyn Budget,2015 breadth_budget: &dyn Budget,2016 ) -> DispatchResultWithPostInfo;20172018 /// Set collection properties.2019 ///2020 /// * `sender` - Must be either the owner of the collection or its admin.2021 /// * `properties` - Properties to be set.2022 fn set_collection_properties(2023 &self,2024 sender: T::CrossAccountId,2025 properties: Vec<Property>,2026 ) -> DispatchResultWithPostInfo;20272028 /// Delete collection properties.2029 ///2030 /// * `sender` - Must be either the owner of the collection or its admin.2031 /// * `properties` - The properties to be removed.2032 fn delete_collection_properties(2033 &self,2034 sender: &T::CrossAccountId,2035 property_keys: Vec<PropertyKey>,2036 ) -> DispatchResultWithPostInfo;20372038 /// Set token properties.2039 ///2040 /// The appropriate [`PropertyPermission`] for the token property2041 /// must be set with [`Self::set_token_property_permissions`].2042 ///2043 /// * `sender` - Must be either the owner of the token or its admin.2044 /// * `token_id` - The token for which the properties are being set.2045 /// * `properties` - Properties to be set.2046 /// * `budget` - Budget for setting properties.2047 fn set_token_properties(2048 &self,2049 sender: T::CrossAccountId,2050 token_id: TokenId,2051 properties: Vec<Property>,2052 budget: &dyn Budget,2053 ) -> DispatchResultWithPostInfo;20542055 /// Remove token properties.2056 ///2057 /// The appropriate [`PropertyPermission`] for the token property2058 /// must be set with [`Self::set_token_property_permissions`].2059 ///2060 /// * `sender` - Must be either the owner of the token or its admin.2061 /// * `token_id` - The token for which the properties are being remove.2062 /// * `property_keys` - Keys to remove corresponding properties.2063 /// * `budget` - Budget for removing properties.2064 fn delete_token_properties(2065 &self,2066 sender: T::CrossAccountId,2067 token_id: TokenId,2068 property_keys: Vec<PropertyKey>,2069 budget: &dyn Budget,2070 ) -> DispatchResultWithPostInfo;20712072 /// Set token property permissions.2073 ///2074 /// * `sender` - Must be either the owner of the token or its admin.2075 /// * `token_id` - The token for which the properties are being set.2076 /// * `property_permissions` - Property permissions to be set.2077 /// * `budget` - Budget for setting properties.2078 fn set_token_property_permissions(2079 &self,2080 sender: &T::CrossAccountId,2081 property_permissions: Vec<PropertyKeyPermission>,2082 ) -> DispatchResultWithPostInfo;20832084 /// Transfer amount of token pieces.2085 ///2086 /// * `sender` - Donor user.2087 /// * `to` - Recepient user.2088 /// * `token` - The token of which parts are being sent.2089 /// * `amount` - The number of parts of the token that will be transferred.2090 /// * `budget` - The maximum budget that can be spent on the transfer.2091 fn transfer(2092 &self,2093 sender: T::CrossAccountId,2094 to: T::CrossAccountId,2095 token: TokenId,2096 amount: u128,2097 budget: &dyn Budget,2098 ) -> DispatchResultWithPostInfo;20992100 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2101 ///2102 /// * `sender` - The user who grants access to the token.2103 /// * `spender` - The user to whom the rights are granted.2104 /// * `token` - The token to which access is granted.2105 /// * `amount` - The amount of pieces that another user can dispose of.2106 fn approve(2107 &self,2108 sender: T::CrossAccountId,2109 spender: T::CrossAccountId,2110 token: TokenId,2111 amount: u128,2112 ) -> DispatchResultWithPostInfo;21132114 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2115 ///2116 /// * `sender` - The user who grants access to the token.2117 /// * `from` - Spender's eth mirror.2118 /// * `to` - The user to whom the rights are granted.2119 /// * `token` - The token to which access is granted.2120 /// * `amount` - The amount of pieces that another user can dispose of.2121 fn approve_from(2122 &self,2123 sender: T::CrossAccountId,2124 from: T::CrossAccountId,2125 to: T::CrossAccountId,2126 token: TokenId,2127 amount: u128,2128 ) -> DispatchResultWithPostInfo;21292130 /// Send parts of a token owned by another user.2131 ///2132 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2133 ///2134 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2135 /// * `from` - The user who owns the token.2136 /// * `to` - Recepient user.2137 /// * `token` - The token of which parts are being sent.2138 /// * `amount` - The number of parts of the token that will be transferred.2139 /// * `budget` - The maximum budget that can be spent on the transfer.2140 fn transfer_from(2141 &self,2142 sender: T::CrossAccountId,2143 from: T::CrossAccountId,2144 to: T::CrossAccountId,2145 token: TokenId,2146 amount: u128,2147 budget: &dyn Budget,2148 ) -> DispatchResultWithPostInfo;21492150 /// Burn parts of a token owned by another user.2151 ///2152 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2153 ///2154 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2155 /// * `from` - The user who owns the token.2156 /// * `token` - The token of which parts are being sent.2157 /// * `amount` - The number of parts of the token that will be transferred.2158 /// * `budget` - The maximum budget that can be spent on the burn.2159 fn burn_from(2160 &self,2161 sender: T::CrossAccountId,2162 from: T::CrossAccountId,2163 token: TokenId,2164 amount: u128,2165 budget: &dyn Budget,2166 ) -> DispatchResultWithPostInfo;21672168 /// Check permission to nest token.2169 ///2170 /// * `sender` - The user who initiated the check.2171 /// * `from` - The token that is checked for embedding.2172 /// * `under` - Token under which to check.2173 /// * `budget` - The maximum budget that can be spent on the check.2174 fn check_nesting(2175 &self,2176 sender: T::CrossAccountId,2177 from: (CollectionId, TokenId),2178 under: TokenId,2179 budget: &dyn Budget,2180 ) -> DispatchResult;21812182 /// Nest one token into another.2183 ///2184 /// * `under` - Token holder.2185 /// * `to_nest` - Nested token.2186 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21872188 /// Unnest token.2189 ///2190 /// * `under` - Token holder.2191 /// * `to_nest` - Token to unnest.2192 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21932194 /// Get all user tokens.2195 ///2196 /// * `account` - Account for which you need to get tokens.2197 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21982199 /// Get all the tokens in the collection.2200 fn collection_tokens(&self) -> Vec<TokenId>;22012202 /// Check if the token exists.2203 ///2204 /// * `token` - Id token to check.2205 fn token_exists(&self, token: TokenId) -> bool;22062207 /// Get the id of the last minted token.2208 fn last_token_id(&self) -> TokenId;22092210 /// Get the owner of the token.2211 ///2212 /// * `token` - The token for which you need to find out the owner.2213 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22142215 /// Returns 10 tokens owners in no particular order.2216 ///2217 /// * `token` - The token for which you need to find out the owners.2218 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22192220 /// Get the value of the token property by key.2221 ///2222 /// * `token` - Token with the property to get.2223 /// * `key` - Property name.2224 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22252226 /// Get a set of token properties by key vector.2227 ///2228 /// * `token` - Token with the property to get.2229 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2230 /// then all properties are returned.2231 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22322233 /// Amount of unique collection tokens2234 fn total_supply(&self) -> u32;22352236 /// Amount of different tokens account has.2237 ///2238 /// * `account` - The account for which need to get the balance.2239 fn account_balance(&self, account: T::CrossAccountId) -> u32;22402241 /// Amount of specific token account have.2242 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22432244 /// Amount of token pieces2245 fn total_pieces(&self, token: TokenId) -> Option<u128>;22462247 /// Get the number of parts of the token that a trusted user can manage.2248 ///2249 /// * `sender` - Trusted user.2250 /// * `spender` - Owner of the token.2251 /// * `token` - The token for which to get the value.2252 fn allowance(2253 &self,2254 sender: T::CrossAccountId,2255 spender: T::CrossAccountId,2256 token: TokenId,2257 ) -> u128;22582259 /// Get extension for RFT collection.2260 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22612262 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2263 /// * `owner` - Token owner2264 /// * `operator` - Operator2265 /// * `approve` - Should operator status be granted or revoked?2266 fn set_allowance_for_all(2267 &self,2268 owner: T::CrossAccountId,2269 operator: T::CrossAccountId,2270 approve: bool,2271 ) -> DispatchResultWithPostInfo;22722273 /// Tells whether the given `owner` approves the `operator`.2274 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22752276 /// Repairs a possibly broken item.2277 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2278}22792280/// Extension for RFT collection.2281pub trait RefungibleExtensions<T>2282where2283 T: Config,2284{2285 /// Change the number of parts of the token.2286 ///2287 /// When the value changes down, this function is equivalent to burning parts of the token.2288 ///2289 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2290 /// * `token` - The token for which you want to change the number of parts.2291 /// * `amount` - The new value of the parts of the token.2292 fn repartition(2293 &self,2294 sender: &T::CrossAccountId,2295 token: TokenId,2296 amount: u128,2297 ) -> DispatchResultWithPostInfo;2298}22992300/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2301///2302/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2303pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2304 let post_info = PostDispatchInfo {2305 actual_weight: Some(weight),2306 pays_fee: Pays::Yes,2307 };2308 match res {2309 Ok(()) => Ok(post_info),2310 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2311 }2312}23132314impl<T: Config> From<PropertiesError> for Error<T> {2315 fn from(error: PropertiesError) -> Self {2316 match error {2317 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2318 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2319 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2320 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2321 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2322 }2323 }2324}pallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -37,7 +37,7 @@
ExitError, ExitRevert, ExitSucceed, GasWeightMapping, PrecompileFailure, PrecompileOutput,
PrecompileResult, PrecompileHandle,
};
-use sp_core::H160;
+use sp_core::{Get, H160};
// #[cfg(feature = "runtime-benchmarks")]
// pub mod benchmarking;
pub mod execution;
@@ -204,6 +204,40 @@
Err(Error::Error(e)) => Err(e.into()),
})
}
+
+ /// Consume gas for reading.
+ pub fn consume_store_reads(&self, reads: u64) -> execution::Result<()> {
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ <T as frame_system::Config>::DbWeight::get()
+ .read
+ .saturating_mul(reads),
+ // TODO: measure proof
+ 0,
+ )))
+ }
+
+ /// Consume gas for writing.
+ pub fn consume_store_writes(&self, writes: u64) -> execution::Result<()> {
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ <T as frame_system::Config>::DbWeight::get()
+ .write
+ .saturating_mul(writes),
+ // TODO: measure proof
+ 0,
+ )))
+ }
+
+ /// Consume gas for reading and writing.
+ pub fn consume_store_reads_and_writes(&self, reads: u64, writes: u64) -> execution::Result<()> {
+ let weight = <T as frame_system::Config>::DbWeight::get();
+ let reads = weight.read.saturating_mul(reads);
+ let writes = weight.read.saturating_mul(writes);
+ self.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(
+ reads.saturating_add(writes),
+ // TODO: measure proof
+ 0,
+ )))
+ }
}
pub fn dispatch_to_evm<T: Config>(err: DispatchError) -> execution::Error {
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -109,7 +109,8 @@
.recorder
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- <Pallet<T>>::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?;
+ <Pallet<T>>::transfer(self, &caller, &to, amount, &budget)
+ .map_err(|e| dispatch_to_evm::<T>(e.error))?;
Ok(true)
}
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -12,6 +12,7 @@
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
+pallet-balances = { workspace = true }
pallet-common = { workspace = true }
pallet-evm = { workspace = true }
pallet-evm-coder-substrate = { workspace = true }
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -166,7 +166,11 @@
#[pallet::config]
pub trait Config:
- frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config
+ frame_system::Config
+ + pallet_common::Config
+ + pallet_structure::Config
+ + pallet_evm::Config
+ + pallet_balances::Config
{
type WeightInfo: WeightInfo;
}
@@ -1325,11 +1329,15 @@
}
fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {
- <TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);
+ if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <TokenChildren<T>>::insert((under.0, under.1, to_nest), true);
+ }
}
fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {
- <TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+ if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+ }
}
fn collection_has_tokens(collection_id: CollectionId) -> bool {
pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -42,7 +42,7 @@
},
CollectionFlags::default(),
)?;
- let dispatch = T::CollectionDispatch::dispatch(CollectionHandle::try_get(CollectionId(1))?);
+ let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?;
let dispatch = dispatch.as_dyn();
dispatch.create_item(caller_cross.clone(), caller_cross.clone(), CreateItemData::NFT(CreateNftData::default()), &Unlimited)?;
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -60,7 +60,7 @@
use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
use frame_support::fail;
pub use pallet::*;
-use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use pallet_common::{dispatch::CollectionDispatch};
use up_data_structs::{
CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,
};
@@ -155,11 +155,10 @@
token: TokenId,
) -> Result<Parent<T::CrossAccountId>, DispatchError> {
// TODO: Reduce cost by not reading collection config
- let handle = match CollectionHandle::try_get(collection) {
+ let handle = match T::CollectionDispatch::dispatch(collection) {
Ok(v) => v,
Err(_) => return Ok(Parent::TokenNotFound),
};
- let handle = T::CollectionDispatch::dispatch(handle);
let handle = handle.as_dyn();
Ok(match handle.token_owner(token) {
@@ -279,8 +278,7 @@
self_budget: &dyn Budget,
breadth_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
- let handle = <CollectionHandle<T>>::try_get(collection)?;
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
}
@@ -405,9 +403,7 @@
return Ok(())
};
- let handle = <CollectionHandle<T>>::try_get(collection)?;
-
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
action(dispatch, token)
pallets/unique/Cargo.tomldiffbeforeafterboth--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -24,6 +24,7 @@
'frame-benchmarking/std',
'frame-support/std',
'frame-system/std',
+ 'pallet-balances-adapter/std',
'pallet-common/std',
'pallet-evm-coder-substrate/std',
'pallet-evm/std',
@@ -46,6 +47,7 @@
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-common = { workspace = true }
pallet-evm = { workspace = true }
pallet-evm-coder-substrate = { workspace = true }
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -440,6 +440,10 @@
collection_id: CollectionId,
address: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
@@ -467,6 +471,10 @@
collection_id: CollectionId,
address: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
collection.check_is_internal()?;
@@ -493,6 +501,9 @@
collection_id: CollectionId,
new_owner: T::AccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let new_owner = T::CrossAccountId::from_sub(new_owner);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
@@ -522,6 +533,9 @@
collection_id: CollectionId,
new_admin_id: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
@@ -548,6 +562,9 @@
collection_id: CollectionId,
account_id: T::CrossAccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
@@ -573,6 +590,9 @@
collection_id: CollectionId,
new_sponsor: T::AccountId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.set_sponsor(&sender, new_sponsor.clone())
@@ -597,6 +617,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = ensure_signed(origin)?;
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.confirm_sponsorship(&sender)
@@ -617,6 +640,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.remove_sponsor(&sender)
@@ -894,6 +920,9 @@
collection_id: CollectionId,
value: bool,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_internal()?;
@@ -1146,6 +1175,9 @@
collection_id: CollectionId,
new_limit: CollectionLimits,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::update_limits(&sender, &mut target_collection, new_limit)
@@ -1170,6 +1202,9 @@
collection_id: CollectionId,
new_permission: CollectionPermissions,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
<PalletCommon<T>>::update_permissions(&sender, &mut target_collection, new_permission)
@@ -1238,6 +1273,9 @@
origin: OriginFor<T>,
collection_id: CollectionId,
) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
ensure_root(origin)?;
<PalletCommon<T>>::repair_collection(collection_id)
}
@@ -1296,10 +1334,7 @@
sender: T::CrossAccountId,
collection_id: CollectionId,
) -> DispatchResult {
- let collection = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_internal()?;
-
- T::CollectionDispatch::destroy(sender, collection)?;
+ T::CollectionDispatch::destroy(sender, collection_id)?;
// TODO: basket cleanup should be moved elsewhere
// Maybe runtime dispatch.rs should perform it?
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+use alloc::string::{String, ToString};
use frame_support::parameter_types;
use sp_runtime::traits::AccountIdConversion;
use crate::{
@@ -23,9 +24,10 @@
weights::CommonWeights,
RelayChainBlockNumberProvider,
},
- Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, Balances,
+ Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, DECIMALS,
+ Balances,
};
-use frame_support::traits::{ConstU32, ConstU64};
+use frame_support::traits::{ConstU32, ConstU64, Currency};
use up_common::{
types::{AccountId, Balance, BlockNumber},
constants::*,
@@ -51,8 +53,8 @@
pub mod preimage;
parameter_types! {
+ pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
- pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
}
impl pallet_common::Config for Runtime {
@@ -85,6 +87,21 @@
}
parameter_types! {
+ pub const Decimals: u8 = DECIMALS;
+ pub Name: String = RUNTIME_NAME.to_string();
+ pub Symbol: String = TOKEN_SYMBOL.to_string();
+}
+impl pallet_balances_adapter::Config for Runtime {
+ type Inspect = Balances;
+ type Mutate = Balances;
+ type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+ type Decimals = Decimals;
+ type Name = Name;
+ type Symbol = Symbol;
+ type WeightInfo = pallet_balances::weights::SubstrateWeight<Self>;
+}
+
+parameter_types! {
pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied
}
runtime/common/construct_runtime.rsdiffbeforeafterboth--- a/runtime/common/construct_runtime.rs
+++ b/runtime/common/construct_runtime.rs
@@ -106,6 +106,8 @@
Maintenance: pallet_maintenance = 154,
+ BalancesAdapter: pallet_balances_adapter = 155,
+
#[cfg(feature = "pallet-test-utils")]
TestUtils: pallet_test_utils = 255,
}
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use frame_support::{dispatch::DispatchResult, ensure};
+use frame_support::{dispatch::DispatchResult, ensure, fail};
use pallet_evm::{PrecompileHandle, PrecompileResult};
use sp_core::H160;
use sp_runtime::DispatchError;
@@ -25,6 +25,7 @@
};
pub use pallet_common::dispatch::CollectionDispatch;
use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
+use pallet_balances_adapter::{NativeFungibleHandle};
use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
use pallet_refungible::{
Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
@@ -39,20 +40,35 @@
pub enum CollectionDispatchT<T>
where
- T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
+ T: pallet_fungible::Config
+ + pallet_nonfungible::Config
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
{
Fungible(FungibleHandle<T>),
Nonfungible(NonfungibleHandle<T>),
Refungible(RefungibleHandle<T>),
+ NativeFungible(NativeFungibleHandle<T>),
}
+
impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
where
T: pallet_common::Config
+ pallet_unique::Config
+ pallet_fungible::Config
+ pallet_nonfungible::Config
- + pallet_refungible::Config,
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
{
+ fn check_is_internal(&self) -> DispatchResult {
+ match self {
+ Self::Fungible(h) => h.check_is_internal(),
+ Self::Nonfungible(h) => h.check_is_internal(),
+ Self::Refungible(h) => h.check_is_internal(),
+ Self::NativeFungible(h) => h.check_is_internal(),
+ }
+ }
+
fn create(
sender: T::CrossAccountId,
payer: T::CrossAccountId,
@@ -83,7 +99,13 @@
Ok(id)
}
- fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+ fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ fail!(<pallet_common::Error<T>>::UnsupportedOperation);
+ }
+
+ let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+
match collection.mode {
CollectionMode::ReFungible => {
PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
@@ -98,20 +120,19 @@
Ok(())
}
- fn dispatch(handle: CollectionHandle<T>) -> Self {
- match handle.mode {
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError> {
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ return Ok(Self::NativeFungible(
+ NativeFungibleHandle::new_with_gas_limit(u64::MAX),
+ ));
+ }
+
+ let handle = <CollectionHandle<T>>::try_get(collection_id)?;
+ Ok(match handle.mode {
CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
- }
- }
-
- fn into_inner(self) -> CollectionHandle<T> {
- match self {
- Self::Fungible(f) => f.into_inner(),
- Self::Nonfungible(f) => f.into_inner(),
- Self::Refungible(f) => f.into_inner(),
- }
+ })
}
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
@@ -119,6 +140,7 @@
Self::Fungible(h) => h,
Self::Nonfungible(h) => h,
Self::Refungible(h) => h,
+ Self::NativeFungible(h) => h,
}
}
}
@@ -129,7 +151,8 @@
+ pallet_unique::Config
+ pallet_fungible::Config
+ pallet_nonfungible::Config
- + pallet_refungible::Config,
+ + pallet_refungible::Config
+ + pallet_balances_adapter::Config,
T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,
{
fn is_reserved(target: &H160) -> bool {
@@ -166,14 +189,19 @@
}
fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
- let collection =
- <CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
- let dispatched = Self::dispatch(collection);
+ if collection_id == pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+ <NativeFungibleHandle<T>>::new_with_gas_limit(handle.remaining_gas()).call(handle)
+ } else {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(
+ collection_id,
+ handle.remaining_gas(),
+ )?;
- match dispatched {
- Self::Fungible(h) => h.call(handle),
- Self::Nonfungible(h) => h.call(handle),
- Self::Refungible(h) => h.call(handle),
+ match collection.mode {
+ CollectionMode::Fungible(_) => FungibleHandle::cast(collection).call(handle),
+ CollectionMode::NFT => NonfungibleHandle::cast(collection).call(handle),
+ CollectionMode::ReFungible => RefungibleHandle::cast(collection).call(handle),
+ }
}
} else if let Some((collection_id, token_id)) =
<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -17,7 +17,7 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
- let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+ let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch($collection)?;
let dispatch = collection.as_dyn();
Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
runtime/common/weights/mod.rsdiffbeforeafterboth--- a/runtime/common/weights/mod.rs
+++ b/runtime/common/weights/mod.rs
@@ -18,6 +18,9 @@
use frame_support::{weights::Weight};
use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo};
+use pallet_balances_adapter::{
+ Config as NativeFungibleConfig, common::CommonWeights as NativeFungibleWeights,
+};
use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights};
use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights};
@@ -32,6 +35,7 @@
macro_rules! max_weight_of {
($method:ident ( $($args:tt)* )) => {{
let max_weight = <FungibleWeights<T>>::$method($($args)*)
+ .max(<NativeFungibleWeights<T>>::$method($($args)*))
.max(<NonfungibleWeights<T>>::$method($($args)*));
#[cfg(feature = "refungible")]
@@ -42,16 +46,22 @@
}
#[cfg(not(feature = "refungible"))]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {}
+pub trait CommonWeightConfigs: FungibleConfig + NativeFungibleConfig + NonfungibleConfig {}
#[cfg(not(feature = "refungible"))]
-impl<T: FungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
#[cfg(feature = "refungible")]
-pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+pub trait CommonWeightConfigs:
+ FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig
+{
+}
#[cfg(feature = "refungible")]
-impl<T: FungibleConfig + NonfungibleConfig + RefungibleConfig> CommonWeightConfigs for T {}
+impl<T: FungibleConfig + NativeFungibleConfig + NonfungibleConfig + RefungibleConfig>
+ CommonWeightConfigs for T
+{
+}
pub struct CommonWeights<T>(PhantomData<T>);
runtime/opal/Cargo.tomldiffbeforeafterboth--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -18,9 +18,10 @@
[features]
default = ['opal-runtime', 'std']
limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
-opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'pallet-test-utils', 'refungible']
+opal-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'pallet-test-utils', 'preimage', 'refungible']
pov-estimate = []
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'cumulus-pallet-parachain-system/runtime-benchmarks',
'frame-benchmarking',
'frame-support/runtime-benchmarks',
@@ -41,7 +42,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -70,8 +70,8 @@
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -79,6 +79,7 @@
'fp-rpc/std',
'fp-self-contained/std',
'pallet-app-promotion/std',
+ 'pallet-balances-adapter/std',
'pallet-base-fee/std',
'pallet-charge-transaction/std',
'pallet-collator-selection/std',
@@ -128,8 +129,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
- "orml-xtokens/std",
"orml-xcm-support/std",
+ "orml-xtokens/std",
"pallet-foreign-assets/std",
'pallet-maintenance/std',
@@ -139,8 +140,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -158,6 +159,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-base-fee/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
@@ -189,8 +191,8 @@
app-promotion = []
collator-selection = []
foreign-assets = []
-preimage = []
pallet-test-utils = []
+preimage = []
refungible = []
scheduler = []
@@ -216,8 +218,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -258,6 +260,7 @@
log = { workspace = true }
num_enum = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-base-fee = { workspace = true }
pallet-charge-transaction = { workspace = true }
pallet-collator-selection = { workspace = true }
@@ -291,11 +294,11 @@
################################################################################
# Optional dependencies
-frame-system-benchmarking = { workspace = true, optional = true}
frame-benchmarking = { workspace = true, optional = true }
+frame-system-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
# Test dependencies
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "opal";
pub const TOKEN_SYMBOL: &str = "OPL";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/quartz/Cargo.tomldiffbeforeafterboth--- a/runtime/quartz/Cargo.toml
+++ b/runtime/quartz/Cargo.toml
@@ -22,6 +22,7 @@
pov-estimate = []
quartz-runtime = ['app-promotion', 'collator-selection', 'foreign-assets', 'preimage', 'refungible']
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'cumulus-pallet-parachain-system/runtime-benchmarks',
'frame-benchmarking',
'frame-support/runtime-benchmarks',
@@ -42,7 +43,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -66,13 +66,14 @@
'frame-try-runtime/std',
'pallet-aura/std',
'pallet-balances/std',
+ 'pallet-balances-adapter/std',
# 'pallet-contracts/std',
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
"pallet-identity/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -127,8 +128,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
+ "orml-xcm-support/std",
"orml-xtokens/std",
- "orml-xcm-support/std",
"pallet-foreign-assets/std",
"pallet-maintenance/std",
]
@@ -136,8 +137,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -154,6 +155,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
'pallet-configuration/try-runtime',
@@ -208,8 +210,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -246,6 +248,7 @@
fp-evm = { workspace = true }
log = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-collator-selection = { workspace = true }
pallet-common = { workspace = true }
pallet-configuration = { workspace = true }
@@ -283,11 +286,11 @@
################################################################################
# Optional dependencies
+frame-benchmarking = { workspace = true, optional = true }
frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -48,6 +48,7 @@
#[cfg(not(feature = "become-sapphire"))]
pub const RUNTIME_NAME: &str = "quartz";
pub const TOKEN_SYMBOL: &str = "QTZ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
runtime/tests/Cargo.tomldiffbeforeafterboth--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -26,6 +26,7 @@
pallet-ethereum = { workspace = true }
pallet-evm = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-common = { workspace = true }
pallet-fungible = { workspace = true }
pallet-nonfungible = { workspace = true }
@@ -41,5 +42,3 @@
evm-coder = { workspace = true }
up-sponsorship = { workspace = true }
xcm = { workspace = true }
-pallet-xcm = { workspace = true }
-pallet-configuration = { workspace = true }
runtime/tests/src/lib.rsdiffbeforeafterboth--- a/runtime/tests/src/lib.rs
+++ b/runtime/tests/src/lib.rs
@@ -19,7 +19,7 @@
use sp_core::{H160, H256, U256};
use frame_support::{
parameter_types,
- traits::{Everything, ConstU32, ConstU64},
+ traits::{Everything, ConstU32, ConstU64, Currency},
weights::IdentityFee,
pallet_prelude::Weight,
};
@@ -266,6 +266,19 @@
impl pallet_nonfungible::Config for Test {
type WeightInfo = ();
}
+parameter_types! {
+ pub const Decimals: u8 = 18;
+ pub Name: String = "Test".to_string();
+ pub Symbol: String = "TST".to_string();
+}
+impl pallet_balances_adapter::Config for Test {
+ type Currency = Balances;
+ type CurrencyBalance = <Balances as Currency<Self::AccountId>>::Balance;
+ type Decimals = Decimals;
+ type Name = Name;
+ type Symbol = Symbol;
+ type WeightInfo = ();
+}
parameter_types! {
// 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f
runtime/unique/Cargo.tomldiffbeforeafterboth--- a/runtime/unique/Cargo.toml
+++ b/runtime/unique/Cargo.toml
@@ -20,6 +20,7 @@
limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing']
pov-estimate = []
runtime-benchmarks = [
+ "pallet-preimage/runtime-benchmarks",
'frame-benchmarking',
'frame-support/runtime-benchmarks',
'frame-system-benchmarking',
@@ -39,7 +40,6 @@
'pallet-inflation/runtime-benchmarks',
'pallet-maintenance/runtime-benchmarks',
'pallet-nonfungible/runtime-benchmarks',
- "pallet-preimage/runtime-benchmarks",
'pallet-refungible/runtime-benchmarks',
'pallet-structure/runtime-benchmarks',
'pallet-timestamp/runtime-benchmarks',
@@ -68,9 +68,9 @@
# 'pallet-contracts-primitives/std',
# 'pallet-contracts-rpc-runtime-api/std',
# 'pallet-contract-helpers/std',
- "pallet-preimage/std",
"pallet-authorship/std",
"pallet-identity/std",
+ "pallet-preimage/std",
"pallet-session/std",
"sp-consensus-aura/std",
'app-promotion-rpc/std',
@@ -78,6 +78,7 @@
'fp-rpc/std',
'fp-self-contained/std',
'pallet-app-promotion/std',
+ 'pallet-balances-adapter/std',
'pallet-base-fee/std',
'pallet-charge-transaction/std',
'pallet-collator-selection/std',
@@ -124,8 +125,8 @@
"orml-tokens/std",
"orml-traits/std",
"orml-vesting/std",
+ "orml-xcm-support/std",
"orml-xtokens/std",
- "orml-xcm-support/std",
"pallet-foreign-assets/std",
"pallet-maintenance/std",
]
@@ -134,8 +135,8 @@
"pallet-authorship/try-runtime",
"pallet-collator-selection/try-runtime",
"pallet-identity/try-runtime",
+ "pallet-preimage/try-runtime",
"pallet-session/try-runtime",
- "pallet-preimage/try-runtime",
'cumulus-pallet-aura-ext/try-runtime',
'cumulus-pallet-dmp-queue/try-runtime',
'cumulus-pallet-parachain-system/try-runtime',
@@ -152,6 +153,7 @@
'pallet-app-promotion/try-runtime',
'pallet-aura/try-runtime',
'pallet-balances/try-runtime',
+ 'pallet-balances-adapter/try-runtime',
'pallet-charge-transaction/try-runtime',
'pallet-common/try-runtime',
'pallet-configuration/try-runtime',
@@ -207,8 +209,8 @@
orml-tokens = { workspace = true }
orml-traits = { workspace = true }
orml-vesting = { workspace = true }
+orml-xcm-support = { workspace = true }
orml-xtokens = { workspace = true }
-orml-xcm-support = { workspace = true }
pallet-aura = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { features = ["insecure_zero_ed"], workspace = true }
@@ -244,6 +246,7 @@
derivative = { workspace = true }
log = { workspace = true }
pallet-app-promotion = { workspace = true }
+pallet-balances-adapter = { workspace = true }
pallet-collator-selection = { workspace = true }
pallet-common = { workspace = true }
pallet-configuration = { workspace = true }
@@ -282,11 +285,11 @@
################################################################################
# Optional dependencies
+frame-benchmarking = { workspace = true, optional = true }
frame-system-benchmarking = { workspace = true, optional = true }
-frame-benchmarking = { workspace = true, optional = true }
frame-try-runtime = { workspace = true, optional = true }
+hex-literal = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
-hex-literal = { workspace = true, optional = true }
################################################################################
# Test dependencies
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -45,6 +45,7 @@
pub const RUNTIME_NAME: &str = "unique";
pub const TOKEN_SYMBOL: &str = "UNQ";
+pub const DECIMALS: u8 = 18;
/// This runtime version.
pub const VERSION: RuntimeVersion = RuntimeVersion {
tests/.eslintrc.jsondiffbeforeafterboth--- a/tests/.eslintrc.json
+++ b/tests/.eslintrc.json
@@ -86,6 +86,14 @@
"array-bracket-spacing": "warn",
"template-curly-spacing": "warn",
"space-in-parens": "warn",
+ "arrow-body-style": ["warn", "as-needed"],
+ "no-restricted-syntax": [
+ "warn",
+ {
+ "message": "toHuman results in horrible, hard to debug conversions with no stability guarantees, use Codec/at least .toJson instead",
+ "selector": "MemberExpression > Identifier[name=\"toHuman\"]"
+ }
+ ],
"@typescript-eslint/naming-convention": [
"warn",
{
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -39,12 +39,14 @@
"testParallelFull": "yarn testParallel && yarn testSequential",
"testParallel": "yarn _testParallel './src/**/*.test.ts'",
"testSequential": "yarn _test './src/**/*.seqtest.ts'",
- "testStructure": "yarn setup && yarn _test ./**/nesting/*.*test.ts",
- "testEth": "yarn setup && yarn _test './**/eth/**/*.*test.ts'",
- "testEthNesting": "yarn setup && yarn _test './**/eth/nesting/**/*.*test.ts'",
- "testEthFractionalizer": "yarn setup && yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
- "testEthMarketplace": "yarn setup && yarn _test './**/eth/marketplace/**/*.*test.ts'",
- "testEvent": "yarn setup && yarn _test ./src/check-event/*.*test.ts",
+ "testStructure": "yarn _test ./**/nesting/*.*test.ts",
+ "testEth": "yarn _test './**/eth/**/*.*test.ts'",
+ "testEthNesting": "yarn _test './**/eth/nesting/**/*.*test.ts'",
+ "testEthFractionalizer": "yarn _test './**/eth/fractionalizer/**/*.*test.ts'",
+ "testEthMarketplace": "yarn _test './**/eth/marketplace/**/*.*test.ts'",
+ "testSub": "yarn _test './**/sub/**/*.*test.ts'",
+ "testSubNesting": "yarn _test './**/sub/nesting/**/*.*test.ts'",
+ "testEvent": "yarn _test ./src/check-event/*.*test.ts",
"testEthPayable": "yarn _test './**/eth/payable.test.ts'",
"testEvmCoder": "yarn _test './**/eth/evmCoder.test.ts'",
"testNesting": "yarn _test ./**/nest.test.ts",
tests/src/addCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
let donor: IKeyringPair;
@@ -82,7 +83,7 @@
itSub("Can't add collection admin of not existing collection.", async ({helper}) => {
const [alice, bob] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.addAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejectedWith(/common\.CollectionNotFound/);
tests/src/allowLists.test.tsdiffbeforeafterboth--- a/tests/src/allowLists.test.ts
+++ b/tests/src/allowLists.test.ts
@@ -16,7 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub} from './util';
-import {ICollectionPermissions} from './util/playgrounds/types';
+import {ICollectionPermissions, NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test ext. Allow list tests', () => {
let alice: IKeyringPair;
@@ -60,7 +60,7 @@
describe('Negative', () => {
itSub('Nobody can add address to allow list of non-existing collection', async ({helper}) => {
- const collectionId = (1<<32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.nft.addToAllowList(bob, collectionId, {Substrate: charlie.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
@@ -140,7 +140,7 @@
});
itSub('Nobody can remove address from allow list of non-existing collection', async ({helper}) => {
- const collectionId = (1<<32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeFromAllowList(bob, collectionId, {Substrate: charlie.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/benchmarks/utils/common.tsdiffbeforeafterboth--- a/tests/src/benchmarks/utils/common.ts
+++ b/tests/src/benchmarks/utils/common.ts
@@ -91,4 +91,4 @@
await collection.setTokenPropertyPermissions(donor, permissions);
return collection;
-}
\ No newline at end of file
+}
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
let alice: IKeyringPair;
@@ -127,7 +128,7 @@
});
itSub('Can\'t change owner of a non-existing collection.', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
const changeOwnerTx = () => helper.collection.changeOwner(bob, collectionId, bob.address);
await expect(changeOwnerTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {usingPlaygrounds, expect, itSub, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
async function setSponsorHelper(collection: any, signer: IKeyringPair, sponsorAddress: string) {
await collection.setSponsor(signer, sponsorAddress);
@@ -198,7 +199,7 @@
});
itSub('(!negative test!) Confirm sponsorship for a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
const confirmSponsorshipTx = () => helper.collection.confirmSponsorship(bob, collectionId);
await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/creditFeesToTreasury.seqtest.tsdiffbeforeafterboth--- a/tests/src/creditFeesToTreasury.seqtest.ts
+++ b/tests/src/creditFeesToTreasury.seqtest.ts
@@ -88,7 +88,7 @@
expect(treasuryIncrease).to.be.equal(fee);
});
- itSub.only('Treasury balance increased by failed tx fee', async ({helper}) => {
+ itSub('Treasury balance increased by failed tx fee', async ({helper}) => {
const api = helper.getApi();
await helper.wait.newBlocks(1);
tests/src/eth/abi/nativeFungible.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/abi/nativeFungible.json
@@ -0,0 +1,201 @@
+[
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" },
+ { "internalType": "address", "name": "spender", "type": "address" }
+ ],
+ "name": "allowance",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "spender", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "approve",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" }
+ ],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ }
+ ],
+ "name": "balanceOfCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+ ],
+ "name": "supportsInterface",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transfer",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from", "type": "address" },
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "from",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFromCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
tests/src/eth/api/UniqueNativeFungible.soldiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/api/UniqueNativeFungible.sol
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+ function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x1313556c
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev EVM selector for this function is: 0xec069398,
+ /// or in textual repr: balanceOfCross((address,uint256))
+ function balanceOfCross(CrossAddress memory owner) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x2ada85ff,
+ /// or in textual repr: transferCross((address,uint256),uint256)
+ function transferCross(CrossAddress memory to, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ CrossAddress memory from,
+ CrossAddress memory to,
+ uint256 amount
+ ) external returns (bool);
+}
+
+/// Cross account struct
+struct CrossAddress {
+ address eth;
+ uint256 sub;
+}
+
+/// @dev inlined interface
+interface ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+interface ERC20 is Dummy, ERC165, ERC20Events {
+ /// @dev EVM selector for this function is: 0xdd62ed3e,
+ /// or in textual repr: allowance(address,address)
+ function allowance(address owner, address spender) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x095ea7b3,
+ /// or in textual repr: approve(address,uint256)
+ function approve(address spender, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0x70a08231,
+ /// or in textual repr: balanceOf(address)
+ function balanceOf(address owner) external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0x313ce567,
+ /// or in textual repr: decimals()
+ function decimals() external view returns (uint8);
+
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() external view returns (string memory);
+
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() external view returns (string memory);
+
+ /// @dev EVM selector for this function is: 0x18160ddd,
+ /// or in textual repr: totalSupply()
+ function totalSupply() external view returns (uint256);
+
+ /// @dev EVM selector for this function is: 0xa9059cbb,
+ /// or in textual repr: transfer(address,uint256)
+ function transfer(address to, uint256 amount) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0x23b872dd,
+ /// or in textual repr: transferFrom(address,address,uint256)
+ function transferFrom(
+ address from,
+ address to,
+ uint256 amount
+ ) external returns (bool);
+}
+
+interface UniqueNativeFungible is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
tests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -18,7 +18,7 @@
import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
import {itEth, expect} from './util';
-describe.only('evm nft collection sponsoring', () => {
+describe('evm nft collection sponsoring', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
let nominal: bigint;
@@ -319,7 +319,7 @@
});
});
-describe.only('evm RFT collection sponsoring', () => {
+describe('evm RFT collection sponsoring', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
let nominal: bigint;
tests/src/eth/createFTCollection.seqtest.tsdiffbeforeafterboth--- a/tests/src/eth/createFTCollection.seqtest.ts
+++ b/tests/src/eth/createFTCollection.seqtest.ts
@@ -20,7 +20,7 @@
const DECIMALS = 18;
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
tests/src/eth/createFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createFTCollection.test.ts
+++ b/tests/src/eth/createFTCollection.test.ts
@@ -22,7 +22,7 @@
const DECIMALS = 18;
-describe.only('Create FT collection from EVM', () => {
+describe('Create FT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
@@ -129,7 +129,7 @@
});
});
-describe.only('(!negative tests!) Create FT collection from EVM', () => {
+describe('(!negative tests!) Create FT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -21,7 +21,7 @@
import {COLLECTION_HELPER} from '../util';
-describe.only('Create NFT collection from EVM', () => {
+describe('Create NFT collection from EVM', () => {
let donor: IKeyringPair;
before(async function () {
@@ -143,7 +143,7 @@
});
});
-describe.only('(!negative tests!) Create NFT collection from EVM', () => {
+describe('(!negative tests!) Create NFT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -21,7 +21,7 @@
import {CollectionLimitField} from './util/playgrounds/types';
-describe.only('Create RFT collection from EVM', () => {
+describe('Create RFT collection from EVM', () => {
let donor: IKeyringPair;
before(async function() {
@@ -154,7 +154,7 @@
});
});
-describe.only('(!negative tests!) Create RFT collection from EVM', () => {
+describe('(!negative tests!) Create RFT collection from EVM', () => {
let donor: IKeyringPair;
let nominal: bigint;
tests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth--- a/tests/src/eth/fractionalizer/fractionalizer.test.ts
+++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts
@@ -80,7 +80,7 @@
};
-describe.only('Fractionalizer contract usage', () => {
+describe('Fractionalizer contract usage', () => {
let donor: IKeyringPair;
before(async function() {
tests/src/eth/nativeFungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -0,0 +1,173 @@
+// Copyright 2019-2023 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {UniqueHelper} from '../util/playgrounds/unique';
+
+describe('NativeFungible: ERC20 calls', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth('approve()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const spender = helper.eth.createAccount();
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ await expect(contract.methods.approve(spender, 100).call({from: owner})).to.be.rejectedWith('Approve not supported');
+ });
+
+ itEth('balanceOf()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor, 123n);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balance = await contract.methods.balanceOf(owner).call({from: owner});
+ expect(balance).to.be.eq('123000000000000000000');
+ });
+
+ itEth('decimals()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realDecimals = (await helper.chain.getChainProperties().tokenDecimals)[0].toString();
+ const decimals = await contract.methods.decimals().call({from: owner});
+ expect(decimals).to.be.eq(realDecimals);
+ });
+
+ itEth('name()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realName = await UniqueHelper.detectNetwork(helper.getApi());
+ const name = await contract.methods.name().call({from: owner});
+ expect(name).to.be.eq(realName);
+ });
+
+ itEth('symbol()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const realName = (await helper.chain.getChainProperties().tokenSymbol)[0];
+ const name = await contract.methods.symbol().call({from: owner});
+ expect(name).to.be.eq(realName);
+ });
+
+ itEth('totalSupply()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const totalSupplyEth = BigInt(await contract.methods.totalSupply().call({from: owner}));
+ const totalSupplySub = await helper.balance.getTotalIssuance();
+ expect(totalSupplyEth).to.be.eq(totalSupplySub);
+ });
+
+ itEth('transfer()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transfer(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore + 50n).to.be.equal(balanceReceiverAfter);
+ });
+
+ itEth('transferFrom()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.eth.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver);
+
+ await contract.methods.transferFrom(owner, receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFrom(receiver, receiver, 50).call({from: owner})).to.be.rejectedWith('ApprovedValueTooLow');
+ });
+});
+
+describe('NativeFungible: ERC20UniqueExtensions calls', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth('transferCross()', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferCross(receiver, 50).send({from: owner});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+ });
+
+ itEth('transferFromCross()', async ({helper}) => {
+ const owner = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor);
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner.eth);
+
+ const balanceOwnerBefore = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverBefore = await helper.balance.getEthereum(receiver.eth);
+
+ await contract.methods.transferFromCross(owner, receiver, 50).send({from: owner.eth});
+
+ const balanceOwnerAfter = await helper.balance.getEthereum(owner.eth);
+ const balanceReceiverAfter = await helper.balance.getEthereum(receiver.eth);
+
+ expect(balanceOwnerBefore - 50n > balanceOwnerAfter).to.be.true;
+ expect(balanceReceiverBefore === balanceReceiverAfter - 50n).to.be.true;
+
+ await expect(contract.methods.transferFromCross(receiver, receiver, 50).call({from: owner.eth})).to.be.rejectedWith('no permission');
+ });
+});
\ No newline at end of file
tests/src/eth/nativeRpc/estimateGas.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/nativeRpc/estimateGas.test.ts
@@ -0,0 +1,62 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+
+describe('Ethereum native RPC calls', () => {
+ let donor: IKeyringPair;
+ const NATIVE_TOKEN_ADDRESS = '0x17c4e6453cc49aaaaeaca894e6d9683e00000000';
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itEth.skip('estimate gas', async ({helper}) => {
+ const BALANCE = 100n;
+ const BALANCE_TO_TRANSFER = 90n;
+
+ const owner = await helper.eth.createAccountWithBalance(donor, BALANCE);
+ const recepient = helper.eth.createAccount();
+
+ const web3 = helper.getWeb3();
+ // data: transfer(recepient, 90);
+ const data = web3.eth.abi.encodeFunctionCall({
+ name: 'transfer',
+ type: 'function',
+ inputs: [{
+ type: 'address',
+ name: 'to',
+ },{
+ type: 'uint256',
+ name: 'amount',
+ }],
+ }, [recepient, (BALANCE_TO_TRANSFER * (10n ** 18n)).toString()]);
+
+ const estimateGas = await web3.eth.estimateGas({
+ to: NATIVE_TOKEN_ADDRESS,
+ value: '0x0',
+ data,
+ from: owner,
+ maxFeePerGas: '0x14c9338c61d',
+ });
+
+ expect(estimateGas).to.be.greaterThan(40000).and.to.be.lessThan(60000);
+ });
+});
tests/src/eth/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/eth/nesting/nest.test.ts
+++ b/tests/src/eth/nesting/nest.test.ts
@@ -187,4 +187,84 @@
.call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');
});
});
+
+ describe('Fungible', () => {
+ async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {
+ if (mode === 'ft') {
+ const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+ await contract.methods.mint(owner, 100n).send({from: owner});
+ return {collectionAddress, contract};
+ }
+
+ // native ft
+ const collectionAddress = helper.ethAddress.fromCollectionId(0);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+ return {collectionAddress, contract};
+ }
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');
+ });
+ });
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});
+
+ await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');
+
+ await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});
+ expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');
+ });
+ });
+
+ [
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);
+ await targetContract.methods.setCollectionNesting(false).send({from: owner});
+
+ const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);
+
+ const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});
+ const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;
+ const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);
+
+ if (testCase.mode === 'ft') {
+ await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');
+ } else {
+ await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;
+ }
+ });
+ });
+ });
});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -929,7 +929,7 @@
});
});
- itEth.only('Returns collection name', async ({helper}) => {
+ itEth('Returns collection name', async ({helper}) => {
// FIXME: should not have balance to use .call()
const caller = await helper.eth.createAccountWithBalance(donor);
const tokenPropertyPermissions = [{
tests/src/eth/payable.test.tsdiffbeforeafterboth--- a/tests/src/eth/payable.test.ts
+++ b/tests/src/eth/payable.test.ts
@@ -41,7 +41,7 @@
expect(await contract.methods.getCollected().call()).to.be.equal('10000');
});
- itEth.only('Evm contract can receive wei from substrate account', async ({helper}) => {
+ itEth('Evm contract can receive wei from substrate account', async ({helper}) => {
const deployer = await helper.eth.createAccountWithBalance(donor);
const contract = await helper.eth.deployCollectorContract(deployer);
const [alice] = await helper.arrange.createAccounts([40n], donor);
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -679,7 +679,7 @@
});
});
- itEth.only('Returns collection name', async ({helper}) => {
+ itEth('Returns collection name', async ({helper}) => {
// FIXME: should not have balance to use .call()
const caller = await helper.eth.createAccountWithBalance(alice);
const tokenPropertyPermissions = [{
tests/src/eth/tokenProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -314,7 +314,7 @@
expect(result.length).to.equal(0);
}));
- itEth.only('Can be read', async({helper}) => {
+ itEth('Can be read', async({helper}) => {
// FIXME: User with no balance should be able to call
const caller = await helper.eth.createAccountWithBalance(alice);
const collection = await helper.nft.mintCollection(alice, {
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -22,6 +22,7 @@
// Native contracts ABI
import collectionHelpersAbi from '../../abi/collectionHelpers.json' assert {type: 'json'};
+import nativeFungibleAbi from '../../abi/nativeFungible.json' assert {type: 'json'};
import fungibleAbi from '../../abi/fungible.json' assert {type: 'json'};
import fungibleDeprecatedAbi from '../../abi/fungibleDeprecated.json' assert {type: 'json'};
import nonFungibleAbi from '../../abi/nonFungible.json' assert {type: 'json'};
@@ -112,7 +113,6 @@
data: object,
from: signer,
gas: gas ?? this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
return await contract.deploy({data: object}).send({from: signer});
}
@@ -121,30 +121,33 @@
class NativeContractGroup extends EthGroupBase {
- async contractHelpers(caller: string): Promise<Contract> {
+ contractHelpers(caller: string) {
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {
from: caller,
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
}
- async collectionHelpers(caller: string) {
+ collectionHelpers(caller: string) {
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {
from: caller,
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
});
}
- async collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
- let abi = {
- 'nft': nonFungibleAbi,
- 'rft': refungibleAbi,
- 'ft': fungibleAbi,
- }[mode];
+ collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
+ let abi;
+ if (address === this.helper.ethAddress.fromCollectionId(0)) {
+ abi = nativeFungibleAbi;
+ } else {
+ abi ={
+ 'nft': nonFungibleAbi,
+ 'rft': refungibleAbi,
+ 'ft': fungibleAbi,
+ }[mode];
+ }
if (mergeDeprecated) {
const deprecated = {
'nft': nonFungibleDeprecatedAbi,
@@ -156,7 +159,6 @@
const web3 = this.helper.getWeb3();
return new web3.eth.Contract(abi as any, address, {
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
...(caller ? {from: caller} : {}),
});
}
@@ -165,12 +167,11 @@
return this.collection(this.helper.ethAddress.fromCollectionId(collectionId), mode, caller, mergeDeprecated);
}
- async rftToken(address: string, caller?: string, mergeDeprecated = false) {
+ rftToken(address: string, caller?: string, mergeDeprecated = false) {
const web3 = this.helper.getWeb3();
const abi = mergeDeprecated ? [...refungibleTokenAbi, ...refungibleTokenDeprecatedAbi] : refungibleTokenAbi;
return new web3.eth.Contract(abi as any, address, {
gas: this.helper.eth.DEFAULT_GAS,
- gasPrice: await this.getGasPrice(),
...(caller ? {from: caller} : {}),
});
}
@@ -210,6 +211,7 @@
async sendEVM(signer: IKeyringPair, contractAddress: string, abi: string, value: string, gasLimit?: number) {
if (!gasLimit) gasLimit = this.DEFAULT_GAS;
const web3 = this.helper.getWeb3();
+ // FIXME: can't send legacy transaction using tx.evm.call
const gasPrice = await web3.eth.getGasPrice();
// TODO: check execution status
await this.helper.executeExtrinsic(
@@ -377,7 +379,7 @@
class EthAddressGroup extends EthGroupBase {
extractCollectionId(address: string): number {
if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format');
- return parseInt(address.substr(address.length - 8), 16);
+ return parseInt(address.slice(address.length - 8), 16);
}
fromCollectionId(collectionId: number): string {
tests/src/maintenance.seqtest.tsdiffbeforeafterboth--- a/tests/src/maintenance.seqtest.ts
+++ b/tests/src/maintenance.seqtest.ts
@@ -325,7 +325,7 @@
expect(await helper.preimage.getPreimageInfo(preimageHashes[0])).to.have.property('unrequested');
});
- itSub.only('Does not allow execution of a preimage that would fail', async ({helper}) => {
+ itSub('Does not allow execution of a preimage that would fail', async ({helper}) => {
const [zeroAccount] = await helper.arrange.createAccounts([0n], superuser);
const preimage = helper.constructApiCall('api.tx.balances.forceTransfer', [
tests/src/nativeFungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/nativeFungible.test.ts
@@ -0,0 +1,221 @@
+// Copyright 2019-2023 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, usingPlaygrounds} from './util';
+
+describe('Native fungible', () => {
+ let root: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ root = await privateKey('//Alice');
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob] = await helper.arrange.createAccounts([10n, 10n], donor);
+ });
+ });
+
+ itSub('destroy_collection()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.burn(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ await expect(helper.executeExtrinsic(alice, 'api.tx.unique.destroyCollection', [0])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('add_to_allow_list()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.addToAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_from_allow_list()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeFromAllowList(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('change_collection_owner()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.changeOwner(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('add_collection_admin()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.addAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_collection_admin()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeAdmin(alice, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_sponsor()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setSponsor(alice, bob.address)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('confirm_sponsorship()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.confirmSponsorship(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('remove_collection_sponsor()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.removeSponsor(alice)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('create_item()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.mint(alice, 100n, {Substrate: bob.address})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_properties()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setProperties(alice, [{key: 'value'}])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('delete_collection_properties()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.deleteProperties(alice, ['key'])).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_token_properties()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.setTokenProperties',
+ [0, 0, [{key: 'value'}]],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('delete_token_properties()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.deleteTokenProperties',
+ [0, 0, ['key']],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_transfers_enabled_flag()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.setTransfersEnabledFlag',
+ [0, true],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('burn_item()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.burnItem',
+ [0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('burn_from()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.burnTokens(alice, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('transfer()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+ const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+ await collection.transfer(alice, {Substrate: bob.address}, 100n);
+ const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+ const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+ expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+ expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+ });
+
+ itSub('transfer_from()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ const balanceAliceBefore = await helper.balance.getSubstrate(alice.address);
+ const balanceBobBefore = await helper.balance.getSubstrate(bob.address);
+
+ await collection.transferFrom(alice, {Substrate: alice.address}, {Substrate: bob.address}, 100n);
+
+ const balanceAliceAfter = await helper.balance.getSubstrate(alice.address);
+ const balanceBobAfter = await helper.balance.getSubstrate(bob.address);
+ expect(balanceAliceBefore - balanceAliceAfter > 100n).to.be.true;
+ expect(balanceBobAfter - balanceBobBefore === 100n).to.be.true;
+
+ await expect(collection.transferFrom(alice, {Substrate: bob.address}, {Substrate: alice.address}, 100n)).to.be.rejectedWith('common.ApprovedValueTooLow');
+ });
+
+ itSub('approve()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.approveTokens(alice, {Substrate: bob.address}, 100n)).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('approve_from()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.approveFrom',
+ [{Substrate: alice.address}, {Substrate: bob.address}, 0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_limits()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setLimits(alice, {accountTokenOwnershipLimit: 1})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('set_collection_permissions()', async ({helper}) => {
+ const collection = helper.ft.getCollectionObject(0);
+ await expect(collection.setPermissions(alice, {access: 'AllowList'})).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('repartition()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.repartition',
+ [0, 0, 100n],
+ true,
+ )).to.be.rejectedWith('unique.RepartitionCalledOnNonRefungibleCollection');
+ });
+
+ itSub('force_repair_collection()', async ({helper}) => {
+ await expect(helper.executeExtrinsic(
+ alice,
+ 'api.tx.unique.forceRepairCollection',
+ [0],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('force_repair_item()', async ({helper}) => {
+ await expect(helper.getSudo().executeExtrinsic(
+ root,
+ 'api.tx.unique.forceRepairItem',
+ [0, 0],
+ true,
+ )).to.be.rejectedWith('common.UnsupportedOperation');
+ });
+
+ itSub('Nest into NFT token()', async ({helper}) => {
+ const nftCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await nftCollection.mintToken(alice);
+
+ const collection = helper.ft.getCollectionObject(0);
+ await collection.transfer(alice, targetToken.nestingAccount(), 100n);
+
+ await collection.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 100n);
+ });
+});
\ No newline at end of file
tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/unnest.test.ts
+++ b/tests/src/nesting/unnest.test.ts
@@ -47,6 +47,20 @@
await expect(nestedToken.getOwner()).to.be.rejected;
});
+ itSub('NativeFungible: allows the owner to successfully unnest a token', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await collection.mintToken(alice);
+
+ const collectionFT = helper.ft.getCollectionObject(0);
+
+ // Nest
+ await collectionFT.transfer(alice, targetToken.nestingAccount(), 10n);
+ // Unnest
+ await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled;
+
+ expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
+ });
+
itSub('Fungible: allows the owner to successfully unnest a token', async ({helper}) => {
const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
const targetToken = await collection.mintToken(alice);
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -19,6 +19,7 @@
// Pallets that must always be present
const requiredPallets = [
'balances',
+ 'balancesadapter',
'common',
'timestamp',
'transactionpayment',
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => {
let alice: IKeyringPair;
@@ -68,7 +69,7 @@
});
itSub('Can\'t remove collection admin from not existing collection', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
tests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionSponsor.test.ts
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('integration test: ext. removeCollectionSponsor():', () => {
let donor: IKeyringPair;
@@ -91,7 +92,7 @@
});
itSub('(!negative test!) Remove sponsor for a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.removeSponsor(alice, collectionId)).to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/setCollectionLimits.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -17,6 +17,7 @@
// https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
const accountTokenOwnershipLimit = 0;
const sponsoredDataSize = 0;
@@ -110,7 +111,7 @@
});
itSub('execute setCollectionLimits for not exists collection', async ({helper}) => {
- const nonExistentCollectionId = (1 << 32) - 1;
+ const nonExistentCollectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setLimits(
alice,
nonExistentCollectionId,
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect, Pallets} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('integration test: ext. setCollectionSponsor():', () => {
let alice: IKeyringPair;
@@ -105,7 +106,7 @@
});
itSub('(!negative test!) Add sponsor to a collection that never existed', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setSponsor(alice, collectionId, bob.address))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/setPermissions.test.tsdiffbeforeafterboth--- a/tests/src/setPermissions.test.ts
+++ b/tests/src/setPermissions.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './util/playgrounds/types';
describe('Integration Test: Set Permissions', () => {
let alice: IKeyringPair;
@@ -85,7 +86,7 @@
});
itSub('fails on not existing collection', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
+ const collectionId = NON_EXISTENT_COLLECTION_ID;
await expect(helper.collection.setPermissions(alice, collectionId, {access: 'AllowList', mintMode: true}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth--- a/tests/src/sub/appPromotion/appPromotion.test.ts
+++ b/tests/src/sub/appPromotion/appPromotion.test.ts
@@ -157,7 +157,8 @@
await helper.staking.stake(staker, 1000n * nominal);
await helper.staking.stake(staker, 199n * nominal);
// check balances
- expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}, {id: 'appstake', amount: 1199n * nominal, reasons: 'All'}]);
+ expect(await helper.balance.getLocked(staker.address)).to.deep.eq([{id: 'ormlvest', amount: 200n * nominal, reasons: 'All'}]);
+ expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([{id: 'appstakeappstake', amount: 1199n * nominal}]);
expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 1199n * nominal});
expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(1199n);
expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(1199n * nominal);
@@ -453,7 +454,7 @@
const [_unstake1, unstake2] = await helper.staking.getPendingUnstakePerBlock({Substrate: staker.address});
await helper.wait.forParachainBlockNumber(unstake2.block);
- expect(await helper.balance.getLocked(staker.address)).to.deep.eq([]);
+ expect(await helper.balance.getFrozen(staker.address)).to.deep.eq([]);
expect(await helper.balance.getSubstrateFull(staker.address)).to.deep.contain({reserved: 0n, frozen: 0n});
expect(await helper.balance.getSubstrate(staker.address) / nominal).to.eq(999n);
expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.eq(0n);
tests/src/sub/nesting/admin.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/admin.test.ts
+++ b/tests/src/sub/nesting/admin.test.ts
@@ -17,7 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, usingPlaygrounds} from '../../util';
-describe('Collection admin', () => {
+describe('Nesting by collection admin', () => {
let alice: IKeyringPair;
let bob: IKeyringPair;
let charlie: IKeyringPair;
@@ -84,4 +84,3 @@
expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase());
});
});
-
tests/src/sub/nesting/common.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/common.test.ts
+++ b/tests/src/sub/nesting/common.test.ts
@@ -21,102 +21,144 @@
let alice: IKeyringPair;
let bob: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+describe('Common nesting tests', () => {
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+ });
});
-});
-[
- {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
- {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
- {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
- {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
- itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
- // Only NFT can be target for nesting in
- const targetNFTCollection = await helper.nft.mintCollection(alice);
- const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ [
+ {mode: 'nft' as const, restrictedMode: true, requiredPallets: []},
+ {mode: 'nft' as const, restrictedMode: false, requiredPallets: []},
+ {mode: 'rft' as const, restrictedMode: true, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'rft' as const, restrictedMode: false, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase => {
+ itSub.ifWithPallets(`Token owner can nest ${testCase.mode.toUpperCase()} in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, testCase.requiredPallets, async ({helper}) => {
+ // Only NFT can be target for nesting in
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+ const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
- const collectionForNesting = await helper[testCase.mode].mintCollection(bob);
- // permissions should be set:
- await targetNFTCollection.setPermissions(alice, {
- nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ // 1. Bob can immediately create nested token:
+ const nestedToken1 = testCase.mode === 'nft'
+ ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
+ : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
+ expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+ expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+
+ // 2. Bob can mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(bob);
+ await nestedToken2.nest(bob, targetTokenBob);
+ expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
+ expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
});
+ });
- // 1. Bob can immediately create nested token:
- const nestedToken1 = testCase.mode === 'nft'
- ? await (collectionForNesting as UniqueNFTCollection).mintToken(bob, targetTokenBob.nestingAccount())
- : await (collectionForNesting as UniqueRFTCollection).mintToken(bob, 10n, targetTokenBob.nestingAccount());
- expect(await nestedToken1.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken1.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
- // 2. Bob can mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(bob);
- await nestedToken2.nest(bob, targetTokenBob);
- expect(await nestedToken2.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address});
- expect(await nestedToken2.getOwner()).to.be.deep.equal(targetTokenBob.nestingAccount().toLowerCase());
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+ // Only NFT allows nesting, permissions should be set:
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+
+ const collectionForNesting = await helper.ft.mintCollection(bob);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
+
+ // 1. Alice can immediately create nested tokens:
+ await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
+ expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+
+ // 2. Alice can mint and nest token:
+ await collectionForNesting.mint(bob, 100n);
+ await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
+ expect(await targetTokenBob.getChildren()).to.be.deep.equal([{collectionId: collectionForNesting.collectionId, tokenId: 0}]);
+ });
});
-});
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`Token owner can nest Native FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
+ // Only NFT allows nesting, permissions should be set:
+ const targetNFTCollection = await helper.nft.mintCollection(alice);
+ const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ expect(await targetTokenBob.getChildren()).to.be.empty;
-[
- {restrictedMode: true},
- {restrictedMode: false},
-].map(testCase => {
- itSub(`Token owner can nest FT in NFT if "tokenOwner" permission set ${testCase.restrictedMode ? 'in restricted mode': ''}`, async ({helper}) => {
- // Only NFT allows nesting, permissions should be set:
- const targetNFTCollection = await helper.nft.mintCollection(alice);
- const targetTokenBob = await targetNFTCollection.mintToken(alice, {Substrate: bob.address});
+ const collectionForNesting = helper.ft.getCollectionObject(0);
+ // permissions should be set:
+ await targetNFTCollection.setPermissions(alice, {
+ nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ });
- const collectionForNesting = await helper.ft.mintCollection(bob);
- // permissions should be set:
- await targetNFTCollection.setPermissions(alice, {
- nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionForNesting.collectionId] : null},
+ // Bob can nest Native FT into their NFT:
+ await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
+ expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(50n);
+ // Native FT should't be visible in NFT children:
+ expect(await targetTokenBob.getChildren()).to.be.deep.equal([]);
});
+ });
+
- // 1. Alice can immediately create nested tokens:
- await collectionForNesting.mint(bob, 100n, targetTokenBob.nestingAccount());
- expect(await collectionForNesting.getTop10Owners()).deep.eq([targetTokenBob.nestingAccount().toLowerCase()]);
- expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(100n);
+ itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
+ const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const tokenA = await collectionToNest.mintToken(alice);
+ const tokenB = await collectionToNest.mintToken(alice);
- // 2. Alice can mint and nest token:
- await collectionForNesting.mint(bob, 100n);
- await collectionForNesting.transfer(bob, targetTokenBob.nestingAccount(), 50n);
- expect(await collectionForNesting.getBalance(targetTokenBob.nestingAccount())).eq(150n);
- });
-});
+ // Create a nested token
+ const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const nativeFtCollectionToBeNested = helper.ft.getCollectionObject(0);
+ const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
+ const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
+ const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
+ await nativeFtCollectionToBeNested.transfer(alice, tokenA.nestingAccount(), 100n);
+ expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+ expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
+ expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
-itSub.ifWithPallets('Owner can unnest tokens using transferFrom', [Pallets.ReFungible], async ({helper}) => {
- const collectionToNest = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const tokenA = await collectionToNest.mintToken(alice);
- const tokenB = await collectionToNest.mintToken(alice);
+ expect(await tokenA.getChildren()).to.be.length(3);
+ expect(await tokenB.getChildren()).to.be.length(0);
- // Create a nested token
- const nftCollectionToBeNested = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const rftCollectionToBeNested = await helper.rft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- const ftCollectionToBeNested = await helper.ft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ // Transfer the nested token to another token
+ await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
+ await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ await nativeFtCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
- const nestedNFT = await nftCollectionToBeNested.mintToken(alice, tokenA.nestingAccount());
- const nestedRFT = await rftCollectionToBeNested.mintToken(alice, 100n, tokenA.nestingAccount());
- const _nestedFT = await ftCollectionToBeNested.mint(alice, 100n, tokenA.nestingAccount());
- expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
- expect(await nestedRFT.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase());
- expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(100n);
+ expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+ expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
- // Transfer the nested token to another token
- await nestedNFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount());
- await nestedRFT.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
- await ftCollectionToBeNested.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount(), 25n);
+ expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await nestedNFT.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await nestedNFT.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase());
+ expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await nestedRFT.getBalance(tokenB.nestingAccount())).to.equal(25n);
- expect(await nestedRFT.getBalance(tokenA.nestingAccount())).to.equal(75n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
+ expect(await nativeFtCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
- expect(await ftCollectionToBeNested.getBalance(tokenB.nestingAccount())).to.equal(25n);
- expect(await ftCollectionToBeNested.getBalance(tokenA.nestingAccount())).to.equal(75n);
+ // RFT, FT, and without native FT
+ expect(await tokenA.getChildren()).to.be.length(2);
+ // NFT, RFT, FT, and without native FT
+ expect(await tokenB.getChildren()).to.be.length(3);
+ });
});
tests/src/sub/nesting/e2e.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/e2e.test.ts
+++ b/tests/src/sub/nesting/e2e.test.ts
@@ -32,6 +32,7 @@
const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
const collectionB = await helper.ft.mintCollection(alice);
const collectionC = await helper.rft.mintCollection(alice);
+ const collectionNative = helper.ft.getCollectionObject(0);
const targetToken = await collectionA.mintToken(alice);
expect((await targetToken.getChildren()).length).to.be.equal(0, 'Children length check at creation');
@@ -73,6 +74,14 @@
{tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
]).and.has.length(3);
+ // Nest native fungible token into another collection
+ await collectionNative.transfer(alice, targetToken.nestingAccount(), 2n);
+ expect(await targetToken.getChildren()).to.have.deep.members([
+ {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId},
+ {tokenId: 0, collectionId: collectionB.collectionId},
+ {tokenId: tokenC.tokenId, collectionId: collectionC.collectionId},
+ ]).and.has.length(3);
+
// Burn all nested pieces
await tokenC.burnFrom(alice, targetToken.nestingAccount(), 2n);
expect(await targetToken.getChildren()).to.have.deep.members([
tests/src/sub/nesting/nesting.negative.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/nesting.negative.test.ts
+++ b/tests/src/sub/nesting/nesting.negative.test.ts
@@ -23,238 +23,272 @@
let bob: IKeyringPair;
let charlie: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+describe('Negative Test: Nesting', () => {
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+ });
});
-});
-[
- {mode: 'nft' as const, requiredPallets: []},
- {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
-].map(testCase => {
- itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
- // Create default collection, permissions are not set:
- const aliceNFTCollection = await helper.nft.mintCollection(alice);
- const targetToken = await aliceNFTCollection.mintToken(alice);
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase => {
+ itSub.ifWithPallets(`Owner cannot nest ${testCase.mode.toUpperCase()} if nesting is disabled`, testCase.requiredPallets, async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const aliceNFTCollection = await helper.nft.mintCollection(alice);
+ const targetToken = await aliceNFTCollection.mintToken(alice);
- const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
+ const collectionForNesting = await helper[testCase.mode].mintCollection(alice);
- // 1. Alice cannot create immediately nested tokens:
- const nestingTx = testCase.mode === 'nft'
- ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
- : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
- await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // 1. Alice cannot create immediately nested tokens:
+ const nestingTx = testCase.mode === 'nft'
+ ? (collectionForNesting as UniqueNFTCollection).mintToken(alice, targetToken.nestingAccount())
+ : (collectionForNesting as UniqueRFTCollection).mintToken(alice, 10n, targetToken.nestingAccount());
+ await expect(nestingTx).to.be.rejectedWith('common.UserIsNotAllowedToNest');
- // 2. Alice cannot mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(alice);
- await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // 2. Alice cannot mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(alice);
+ await expect(nestedToken2.nest(alice, targetToken)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ });
});
-});
-itSub('Owner cannot nest FT if nesting is disabled', async ({helper}) => {
- // Create default collection, permissions are not set:
- const aliceNFTCollection = await helper.nft.mintCollection(alice);
- const targetToken = await aliceNFTCollection.mintToken(alice);
+ [
+ {mode: 'ft'},
+ {mode: 'nativeFt'},
+ ].map(testCase => {
+ itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled (except for native fungible collection)`, async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const aliceNFTCollection = await helper.nft.mintCollection(alice);
+ const targetToken = await aliceNFTCollection.mintToken(alice);
- const collectionForNesting = await helper.ft.mintCollection(alice);
+ const collectionForNesting = testCase.mode === 'ft' ? await helper.ft.mintCollection(alice) : helper.ft.getCollectionObject(0);
- // 1. Alice cannot create immediately nested tokens:
- await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ // Alice cannot create immediately nested tokens:
+ if (testCase.mode === 'ft') {
+ await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ } else {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.not.rejected;
+ }
- // 2. Alice can mint and nest token:
- await collectionForNesting.mint(alice, 100n);
- await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
-});
+ // Alice can't mint and nest tokens:
+ if (testCase.mode === 'ft') {
+ await collectionForNesting.mint(alice, 100n);
+ }
-[
- {mode: 'nft' as const},
- {mode: 'rft' as const},
- {mode: 'ft' as const},
-].map(testCase => {
- itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => {
- const targetCollection = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, collectionAdmin: true}},
+ if (testCase.mode === 'ft') {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest');
+ } else {
+ await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.not.rejected;
+ }
});
- const targetToken = await targetCollection.mintToken(alice);
+ });
+
+ [
+ {mode: 'nft' as const},
+ {mode: 'rft' as const},
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(testCase => {
+ itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens (except for native fungible collection)`, async ({helper}) => {
+ const targetCollection = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, collectionAdmin: true}},
+ });
+ const targetToken = await targetCollection.mintToken(alice);
- const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+ const nestedCollectionBob = await (
+ testCase.mode === 'native ft'
+ ? helper.ft.getCollectionObject(0)
+ : helper[testCase.mode].mintCollection(bob)
+ );
- let nestedTokenBob: UniqueNFToken | UniqueRFToken;
- switch (testCase.mode) {
- case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
- case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
- case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
- }
+ let nestedTokenBob: UniqueNFToken | UniqueRFToken;
+ switch (testCase.mode) {
+ case 'nft': nestedTokenBob = await (nestedCollectionBob as UniqueNFTCollection).mintToken(bob); break;
+ case 'rft': nestedTokenBob = await (nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n); break;
+ case 'ft': await (nestedCollectionBob as UniqueFTCollection).mint(bob, 100n); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n)).to.be.rejectedWith('common.UnsupportedOperation'); break;
+ }
- // Bob non-owner of targetToken and non admin of targetCollection, so
- // 1. cannot mint nested token:
- switch (testCase.mode) {
- case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- }
+ // Bob non-owner of targetToken and non admin of targetCollection, so
+ // 1. cannot mint nested token:
+ switch (testCase.mode) {
+ case 'nft': await expect((nestedCollectionBob as UniqueNFTCollection).mintToken(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'rft': await expect((nestedCollectionBob as UniqueRFTCollection).mintToken(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).mint(bob, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UnsupportedOperation'); break;
+ }
- // 2. cannot nest existing token:
- switch (testCase.mode) {
- case 'nft':
- case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
- }
+ // 2. cannot nest existing token:
+ switch (testCase.mode) {
+ case 'nft':
+ case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break;
+ case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.not.rejected; break;
+ }
+ });
});
-});
-[
- {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},
- {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
-].map(testCase => {
- itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
- // Create collection with tokenOwner or create collection with collectionAdmin permission:
- const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
- const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
- await targetCollection.addAdmin(alice, {Substrate: bob.address});
+ [
+ {mode: 'nft' as const, nesting: {tokenOwner: true, collectionAdmin: false}},
+ {mode: 'nft' as const, nesting: {tokenOwner: false, collectionAdmin: true}},
+ ].map(testCase => {
+ itSub(`${testCase.nesting.tokenOwner ? 'Admin' : 'Token owner'} cannot nest when only ${testCase.nesting.tokenOwner ? 'tokenOwner' : 'collectionAdmin'} is allowed`, async ({helper}) => {
+ // Create collection with tokenOwner or create collection with collectionAdmin permission:
+ const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: testCase.nesting}});
+ const targetTokenCharlie = await targetCollection.mintToken(alice, {Substrate: charlie.address});
+ await targetCollection.addAdmin(alice, {Substrate: bob.address});
- const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
- const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
- // if nesting permissions restricted for token owner – minter is bob (admin),
- // if collectionAdmin – charlie (owner)
- testCase.nesting.tokenOwner
- ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
- : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+ const nestedCollectionCharlie = await helper[testCase.mode].mintCollection(charlie);
+ const nestedCollectionBob = await helper[testCase.mode].mintCollection(bob);
+ // if nesting permissions restricted for token owner – minter is bob (admin),
+ // if collectionAdmin – charlie (owner)
+ testCase.nesting.tokenOwner
+ ? await expect(nestedCollectionBob.mintToken(bob, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/)
+ : await expect(nestedCollectionCharlie.mintToken(charlie, targetTokenCharlie.nestingAccount())).to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+ });
});
-});
-itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice);
- // To avoid UserIsNotAllowedToNest error
- await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
+ itSub.ifWithPallets('Cannot nest in non existing token (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice);
+ // To avoid UserIsNotAllowedToNest error
+ await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}});
- // The list of non-existing tokens:
- const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
- const tokenBurnt = await collection.mintToken(alice);
- await tokenBurnt.burn(alice);
- const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
+ // The list of non-existing tokens:
+ const tokenFromNonExistingCollection = helper.nft.getTokenObject(9999999, 1);
+ const tokenBurnt = await collection.mintToken(alice);
+ await tokenBurnt.burn(alice);
+ const tokenNotMintedYet = helper.nft.getTokenObject(collection.collectionId, 2);
- // The list of collections to nest tokens from:
- const nftCollectionForNesting = await helper.nft.mintCollection(alice);
- const rftCollectionForNesting = await helper.rft.mintCollection(alice);
- const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+ // The list of collections to nest tokens from:
+ const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+ const rftCollectionForNesting = await helper.rft.mintCollection(alice);
+ const ftCollectionForNesting = await helper.ft.mintCollection(alice);
+ const nativeFtCollectionForNesting = helper.ft.getCollectionObject(0);
- const testCases = [
- {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
- {token: tokenBurnt, error: 'TokenNotFound'},
- {token: tokenNotMintedYet, error: 'TokenNotFound'},
- ];
+ const testCases = [
+ {token: tokenFromNonExistingCollection, error: 'CollectionNotFound'},
+ {token: tokenBurnt, error: 'TokenNotFound'},
+ {token: tokenNotMintedYet, error: 'TokenNotFound'},
+ ];
- for(const testCase of testCases) {
- // 1. Alice cannot create nested token to non-existing token
- await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ for(const testCase of testCases) {
+ // 1. Alice cannot create nested token to non-existing token
+ await expect(nftCollectionForNesting.mintToken(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(rftCollectionForNesting.mintToken(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(ftCollectionForNesting.mint(alice, 10n, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- // 2. Alice cannot mint and nest token:
- const nft = await nftCollectionForNesting.mintToken(alice);
- const rft = await rftCollectionForNesting.mintToken(alice, 100n);
- const _ft = await ftCollectionForNesting.mint(alice, 100n);
- await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
- await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
- }
-});
+ // 2. Alice cannot mint and nest token:
+ const nft = await nftCollectionForNesting.mintToken(alice);
+ const rft = await rftCollectionForNesting.mintToken(alice, 100n);
+ const _ft = await ftCollectionForNesting.mint(alice, 100n);
+ await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error);
+ await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error);
+ await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.not.rejected;
+ }
+ });
-itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
- const existingCollection = await helper.nft.mintCollection(alice);
- const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
- const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
+ itEth.ifWithPallets('Cannot nest in collection address', [Pallets.ReFungible], async({helper}) => {
+ const existingCollection = await helper.nft.mintCollection(alice);
+ const existingCollectionAddress = helper.ethAddress.fromCollectionId(existingCollection.collectionId);
+ const futureCollectionAddress = helper.ethAddress.fromCollectionId(99999999);
- const nftCollectionForNesting = await helper.nft.mintCollection(alice);
+ const nftCollectionForNesting = await helper.nft.mintCollection(alice);
- // 1. Alice cannot create nested token to collection address
- await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ // 1. Alice cannot create nested token to collection address
+ await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ await expect(nftCollectionForNesting.mintToken(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- // 2. Alice cannot mint and nest token to collection address:
- const nft = await nftCollectionForNesting.mintToken(alice);
- await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
- await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
-});
+ // 2. Alice cannot mint and nest token to collection address:
+ const nft = await nftCollectionForNesting.mintToken(alice);
+ await expect(nft.transfer(alice, {Ethereum: existingCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection');
+ });
-itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => {
- // Create default collection, permissions are not set:
- const rftCollection = await helper.rft.mintCollection(alice);
- const ftCollection = await helper.ft.mintCollection(alice);
+ itEth.ifWithPallets('Cannot nest in RFT or FT (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => {
+ // Create default collection, permissions are not set:
+ const rftCollection = await helper.rft.mintCollection(alice);
+ const ftCollection = await helper.ft.mintCollection(alice);
+ const nativeFtCollection = helper.ft.getCollectionObject(0);
- const rftToken = await rftCollection.mintToken(alice);
- const _ftToken = await ftCollection.mint(alice, 100n);
+ const rftToken = await rftCollection.mintToken(alice);
+ const _ftToken = await ftCollection.mint(alice, 100n);
- const collectionForNesting = await helper.nft.mintCollection(alice);
+ const collectionForNesting = await helper.nft.mintCollection(alice);
- // 1. Alice cannot create immediately nested tokens:
- await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
- await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ // 1. Alice cannot create immediately nested tokens:
+ await expect(collectionForNesting.mintToken(alice, rftToken.nestingAccount())).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+ await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ await expect(collectionForNesting.mintToken(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.rejectedWith('common.UnsupportedOperation');
- // 2. Alice cannot mint and nest token:
- const nestedToken2 = await collectionForNesting.mintToken(alice);
- await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
- await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
-});
+ // 2. Alice cannot mint and nest token:
+ const nestedToken2 = await collectionForNesting.mintToken(alice);
+ await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting');
+ await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting');
+ await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.not.rejected;
+ });
-itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => {
- const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
- const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
- const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
- const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+ itSub('Cannot nest in restricted collection if collection is not in the list (except native fungible collection)', async ({helper}) => {
+ const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice);
+ const notAllowedCollectionNFT = await helper.nft.mintCollection(alice);
+ const notAllowedCollectionRFT = await helper.rft.mintCollection(alice);
+ const notAllowedCollectionFT = await helper.ft.mintCollection(alice);
+ const allowedCollectionNativeFT = helper.ft.getCollectionObject(0);
- // Collection restricted to allowedCollectionId
- const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
- });
- // Create collection with restricted nesting -- even self is not allowed
- const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
- {nesting: {tokenOwner: true, restricted: []}},
- });
+ // Collection restricted to allowedCollectionId
+ const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, restricted: [allowedCollectionId]}},
+ });
+ // Create collection with restricted nesting -- even self is not allowed
+ const restrictedCollectionB = await helper.nft.mintCollection(alice, {permissions:
+ {nesting: {tokenOwner: true, restricted: []}},
+ });
- const targetTokenA = await restrictedCollectionA.mintToken(alice);
- const targetTokenB = await restrictedCollectionB.mintToken(alice);
+ const targetTokenA = await restrictedCollectionA.mintToken(alice);
+ const targetTokenB = await restrictedCollectionB.mintToken(alice);
- // 1. Cannot mint in own collection after allowlisting the accounts:
- await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ // 1. Cannot mint in own collection after allowlisting the accounts:
+ await expect(restrictedCollectionA.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(restrictedCollectionB.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- // 2. Cannot mint from notAllowedCollection:
- await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-});
+ // 2. Cannot mint from notAllowedCollection:
+ await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionNFT.mintToken(alice, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ await expect(allowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.not.rejected;
+ await expect(allowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.not.rejected;
+ });
-itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
- let token = await collection.mintToken(alice);
+ itSub('Cannot create nesting chains greater than 5', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ let token = await collection.mintToken(alice);
- const maxNestingLevel = 5;
+ const maxNestingLevel = 5;
- // Create a nested-token matryoshka
- for (let i = 0; i < maxNestingLevel; i++) {
- token = await collection.mintToken(alice, token.nestingAccount());
- }
+ // Create a nested-token matryoshka
+ for (let i = 0; i < maxNestingLevel; i++) {
+ token = await collection.mintToken(alice, token.nestingAccount());
+ }
- // The nesting depth is limited by `maxNestingLevel`
- // 1. Cannot mint:
- await expect(collection.mintToken(alice, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
- // 2. Cannot transfer:
- const anotherToken = await collection.mintToken(alice);
- await expect(anotherToken.transfer(alice, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
- // 3. Cannot nest FT pieces:
- const ftCollection = await helper.ft.mintCollection(alice);
- await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
- .to.be.rejectedWith(/structure\.DepthLimit/);
+ // The nesting depth is limited by `maxNestingLevel`
+ // 1. Cannot mint:
+ await expect(collection.mintToken(alice, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
+ // 2. Cannot transfer:
+ const anotherToken = await collection.mintToken(alice);
+ await expect(anotherToken.transfer(alice, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
+ // 3. Cannot nest FT pieces:
+ const ftCollection = await helper.ft.mintCollection(alice);
+ await expect(ftCollection.mint(alice, 100n, token.nestingAccount()))
+ .to.be.rejectedWith(/structure\.DepthLimit/);
- expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
- expect(await token.getChildren()).to.has.length(0);
+ expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address});
+ expect(await token.getChildren()).to.has.length(0);
+ });
});
tests/src/sub/nesting/refungible.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/refungible.test.ts
+++ b/tests/src/sub/nesting/refungible.test.ts
@@ -17,44 +17,45 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itSub, Pallets, usingPlaygrounds} from '../../util';
-// ReFungible specific nesting tests
-let alice: IKeyringPair;
+describe('ReFungible-specific nesting tests', () => {
+ let alice: IKeyringPair;
-before(async () => {
- await usingPlaygrounds(async (helper, privateKey) => {
- const donor = await privateKey({url: import.meta.url});
- [alice] = await helper.arrange.createAccounts([200n], donor);
+ before(async () => {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ const donor = await privateKey({url: import.meta.url});
+ [alice] = await helper.arrange.createAccounts([200n], donor);
+ });
});
-});
-itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice, {
- permissions: {
- nesting: {
- tokenOwner: true,
+ itSub.ifWithPallets('ReFungible: getTopmostOwner works correctly with Nesting', [Pallets.ReFungible], async({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice, {
+ permissions: {
+ nesting: {
+ tokenOwner: true,
+ },
},
- },
- });
- const collectionRFT = await helper.rft.mintCollection(alice);
+ });
+ const collectionRFT = await helper.rft.mintCollection(alice);
- const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
- const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
+ const nft = await collectionNFT.mintToken(alice, {Substrate: alice.address});
+ const rft = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
- await rft.transfer(alice, nft.nestingAccount(), 40n);
+ await rft.transfer(alice, nft.nestingAccount(), 40n);
- expect(await rft.getTopmostOwner()).deep.equal(null);
+ expect(await rft.getTopmostOwner()).deep.equal(null);
- await rft.transfer(alice, nft.nestingAccount(), 60n);
+ await rft.transfer(alice, nft.nestingAccount(), 60n);
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
- await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 30n);
- expect(await rft.getTopmostOwner()).deep.equal(null);
+ expect(await rft.getTopmostOwner()).deep.equal(null);
- await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
+ await rft.transferFrom(alice, nft.nestingAccount(), {Substrate: alice.address}, 70n);
- expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ expect(await rft.getTopmostOwner()).deep.equal({Substrate: alice.address});
+ });
});
tests/src/sub/nesting/unnesting.negative.test.tsdiffbeforeafterboth--- a/tests/src/sub/nesting/unnesting.negative.test.ts
+++ b/tests/src/sub/nesting/unnesting.negative.test.ts
@@ -52,12 +52,19 @@
});
[
- {restrictedMode: true},
- {restrictedMode: false},
+ {mode: 'ft' as const},
+ {mode: 'native ft' as const},
+ ].map(md => [
+ {mode: md.mode, restrictedMode: true},
+ {mode: md.mode, restrictedMode: false},
].map(testCase => {
- itSub(`Fungible: disallows a non-Owner to unnest someone else's token ${testCase.restrictedMode ? '(Restricted nesting)' : ''}`, async ({helper}) => {
+ itSub(`Fungible: disallows a non-Owner to unnest someone else's token [${testCase.mode}${testCase.restrictedMode ? ' (Restricted nesting)' : ''}]`, async ({helper}) => {
const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionFT = await helper.ft.mintCollection(alice);
+ const collectionFT = await (
+ testCase.mode === 'ft'
+ ? helper.ft.mintCollection(alice)
+ : helper.ft.getCollectionObject(0)
+ );
const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address});
await collectionNFT.setPermissions(alice, {nesting: {
@@ -65,12 +72,16 @@
}});
// Nest some tokens as Alice into Bob's token
- await collectionFT.mint(alice, 5n, targetToken.nestingAccount());
+ await (
+ testCase.mode === 'ft'
+ ? collectionFT.mint(alice, 5n, targetToken.nestingAccount())
+ : collectionFT.transfer(alice, targetToken.nestingAccount(), 5n)
+ );
// Try to pull it out as Alice still
await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.ApprovedValueTooLow/);
expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
});
- });
+ }));
});
tests/src/transfer.test.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};