git.delta.rocks / unique-network / refs/commits / 82643c5daa1a

difftreelog

feat add tests

Trubnikov Sergey2023-04-25parent: #f0ad1b0.patch.diff
in: master

12 files changed

modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
before · node/cli/src/chain_spec.rs
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/>.1617use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};18use sc_service::ChainType;19use sp_core::{sr25519, Pair, Public};20use sp_runtime::traits::{IdentifyAccount, Verify};21use std::collections::BTreeMap;2223use serde::{Deserialize, Serialize};24use serde_json::map::Map;2526use up_common::types::opaque::*;2728#[cfg(feature = "unique-runtime")]29pub use unique_runtime as default_runtime;3031#[cfg(all(not(feature = "unique-runtime"), feature = "quartz-runtime"))]32pub use quartz_runtime as default_runtime;3334#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]35pub use opal_runtime as default_runtime;3637/// The `ChainSpec` parameterized for the unique runtime.38#[cfg(feature = "unique-runtime")]39pub type UniqueChainSpec = sc_service::GenericChainSpec<unique_runtime::GenesisConfig, Extensions>;4041/// The `ChainSpec` parameterized for the quartz runtime.42#[cfg(feature = "quartz-runtime")]43pub type QuartzChainSpec = sc_service::GenericChainSpec<quartz_runtime::GenesisConfig, Extensions>;4445/// The `ChainSpec` parameterized for the opal runtime.46pub type OpalChainSpec = sc_service::GenericChainSpec<opal_runtime::GenesisConfig, Extensions>;4748#[cfg(feature = "unique-runtime")]49pub type DefaultChainSpec = UniqueChainSpec;5051#[cfg(all(not(feature = "unique-runtime"), feature = "quartz-runtime"))]52pub type DefaultChainSpec = QuartzChainSpec;5354#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]55pub type DefaultChainSpec = OpalChainSpec;5657#[cfg(not(feature = "unique-runtime"))]58/// PARA_ID for Opal/Sapphire/Quartz59const PARA_ID: u32 = 2095;6061#[cfg(feature = "unique-runtime")]62/// PARA_ID for Unique63const PARA_ID: u32 = 2037;6465pub trait RuntimeIdentification {66	fn runtime_id(&self) -> RuntimeId;67}6869impl RuntimeIdentification for Box<dyn sc_service::ChainSpec> {70	fn runtime_id(&self) -> RuntimeId {71		#[cfg(feature = "unique-runtime")]72		if self.id().starts_with("unique") || self.id().starts_with("unq") {73			return RuntimeId::Unique;74		}7576		#[cfg(feature = "quartz-runtime")]77		if self.id().starts_with("quartz")78			|| self.id().starts_with("qtz")79			|| self.id().starts_with("sapphire")80		{81			return RuntimeId::Quartz;82		}8384		if self.id().starts_with("opal") || self.id() == "dev" || self.id() == "local_testnet" {85			return RuntimeId::Opal;86		}8788		RuntimeId::Unknown(self.id().into())89	}90}9192pub enum ServiceId {93	Prod,94	Dev,95}9697pub trait ServiceIdentification {98	fn service_id(&self) -> ServiceId;99}100101impl ServiceIdentification for Box<dyn sc_service::ChainSpec> {102	fn service_id(&self) -> ServiceId {103		if self.id().ends_with("dev") {104			ServiceId::Dev105		} else {106			ServiceId::Prod107		}108	}109}110111/// Helper function to generate a crypto pair from seed112pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {113	TPublic::Pair::from_string(&format!("//{}", seed), None)114		.expect("static values are valid; qed")115		.public()116}117118/// The extensions for the [`DefaultChainSpec`].119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)]120#[serde(deny_unknown_fields)]121pub struct Extensions {122	/// The relay chain of the Parachain.123	pub relay_chain: String,124	/// The id of the Parachain.125	pub para_id: u32,126}127128impl Extensions {129	/// Try to get the extension from the given `ChainSpec`.130	pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> {131		sc_chain_spec::get_extension(chain_spec.extensions())132	}133}134135type AccountPublic = <Signature as Verify>::Signer;136137/// Helper function to generate an account ID from seed138pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId139where140	AccountPublic: From<<TPublic::Pair as Pair>::Public>,141{142	AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()143}144145#[cfg(not(feature = "unique-runtime"))]146macro_rules! testnet_genesis {147	(148		$runtime:path,149		$root_key:expr,150		$initial_invulnerables:expr,151		$endowed_accounts:expr,152		$id:expr153	) => {{154		use $runtime::*;155156		GenesisConfig {157			system: SystemConfig {158				code: WASM_BINARY159					.expect("WASM binary was not build, please build it!")160					.to_vec(),161			},162			balances: BalancesConfig {163				balances: $endowed_accounts164					.iter()165					.cloned()166					// 1e13 UNQ167					.map(|k| (k, 1 << 100))168					.collect(),169			},170			common: Default::default(),171			nonfungible: Default::default(),172			treasury: Default::default(),173			tokens: TokensConfig { balances: vec![] },174			sudo: SudoConfig {175				key: Some($root_key),176			},177			vesting: VestingConfig { vesting: vec![] },178			parachain_info: ParachainInfoConfig {179				parachain_id: $id.into(),180			},181			parachain_system: Default::default(),182			collator_selection: CollatorSelectionConfig {183				invulnerables: $initial_invulnerables184					.iter()185					.cloned()186					.map(|(acc, _)| acc)187					.collect(),188			},189			session: SessionConfig {190				keys: $initial_invulnerables191					.into_iter()192					.map(|(acc, aura)| {193						(194							acc.clone(),          // account id195							acc,                  // validator id196							SessionKeys { aura }, // session keys197						)198					})199					.collect(),200			},201			aura: Default::default(),202			aura_ext: Default::default(),203			evm: EVMConfig {204				accounts: BTreeMap::new(),205			},206			ethereum: EthereumConfig {},207			polkadot_xcm: Default::default(),208			transaction_payment: Default::default(),209		}210	}};211}212213#[cfg(feature = "unique-runtime")]214macro_rules! testnet_genesis {215	(216		$runtime:path,217		$root_key:expr,218		$initial_invulnerables:expr,219		$endowed_accounts:expr,220		$id:expr221	) => {{222		use $runtime::*;223224		GenesisConfig {225			system: SystemConfig {226				code: WASM_BINARY227					.expect("WASM binary was not build, please build it!")228					.to_vec(),229			},230			common: Default::default(),231			nonfungible: Default::default(),232			balances: BalancesConfig {233				balances: $endowed_accounts234					.iter()235					.cloned()236					// 1e13 UNQ237					.map(|k| (k, 1 << 100))238					.collect(),239			},240			treasury: Default::default(),241			tokens: TokensConfig { balances: vec![] },242			sudo: SudoConfig {243				key: Some($root_key),244			},245			vesting: VestingConfig { vesting: vec![] },246			parachain_info: ParachainInfoConfig {247				parachain_id: $id.into(),248			},249			parachain_system: Default::default(),250			aura: AuraConfig {251				authorities: $initial_invulnerables252					.into_iter()253					.map(|(_, aura)| aura)254					.collect(),255			},256			aura_ext: Default::default(),257			evm: EVMConfig {258				accounts: BTreeMap::new(),259			},260			ethereum: EthereumConfig {},261			polkadot_xcm: Default::default(),262			transaction_payment: Default::default(),263		}264	}};265}266267pub fn development_config() -> DefaultChainSpec {268	let mut properties = Map::new();269	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());270	properties.insert("tokenDecimals".into(), 18.into());271	properties.insert(272		"ss58Format".into(),273		default_runtime::SS58Prefix::get().into(),274	);275276	DefaultChainSpec::from_genesis(277		// Name278		format!(279			"{}{}",280			default_runtime::RUNTIME_NAME.to_uppercase(),281			if cfg!(feature = "unique-runtime") {282				""283			} else {284				" by UNIQUE"285			}286		)287		.as_str(),288		// ID289		format!("{}_dev", default_runtime::RUNTIME_NAME).as_str(),290		ChainType::Local,291		move || {292			testnet_genesis!(293				default_runtime,294				// Sudo account295				get_account_id_from_seed::<sr25519::Public>("Alice"),296				vec![297					(298						get_account_id_from_seed::<sr25519::Public>("Alice"),299						get_from_seed::<AuraId>("Alice"),300					),301					(302						get_account_id_from_seed::<sr25519::Public>("Bob"),303						get_from_seed::<AuraId>("Bob"),304					),305				],306				// Pre-funded accounts307				vec![308					get_account_id_from_seed::<sr25519::Public>("Alice"),309					get_account_id_from_seed::<sr25519::Public>("Bob"),310					get_account_id_from_seed::<sr25519::Public>("Charlie"),311					get_account_id_from_seed::<sr25519::Public>("Dave"),312					get_account_id_from_seed::<sr25519::Public>("Eve"),313					get_account_id_from_seed::<sr25519::Public>("Ferdie"),314					get_account_id_from_seed::<sr25519::Public>("Alice//stash"),315					get_account_id_from_seed::<sr25519::Public>("Bob//stash"),316					get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),317					get_account_id_from_seed::<sr25519::Public>("Dave//stash"),318					get_account_id_from_seed::<sr25519::Public>("Eve//stash"),319					get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),320				],321				PARA_ID322			)323		},324		// Bootnodes325		vec![],326		// Telemetry327		None,328		// Protocol ID329		None,330		None,331		// Properties332		Some(properties),333		// Extensions334		Extensions {335			relay_chain: "rococo-dev".into(),336			para_id: PARA_ID,337		},338	)339}340341pub fn local_testnet_config() -> DefaultChainSpec {342	let mut properties = Map::new();343	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());344	properties.insert("tokenDecimals".into(), 18.into());345	properties.insert(346		"ss58Format".into(),347		default_runtime::SS58Prefix::get().into(),348	);349350	DefaultChainSpec::from_genesis(351		// Name352		format!(353			"{}{}",354			default_runtime::RUNTIME_NAME.to_uppercase(),355			if cfg!(feature = "unique-runtime") {356				""357			} else {358				" by UNIQUE"359			}360		)361		.as_str(),362		// ID363		format!("{}_local", default_runtime::RUNTIME_NAME).as_str(),364		ChainType::Local,365		move || {366			testnet_genesis!(367				default_runtime,368				// Sudo account369				get_account_id_from_seed::<sr25519::Public>("Alice"),370				vec![371					(372						get_account_id_from_seed::<sr25519::Public>("Alice"),373						get_from_seed::<AuraId>("Alice"),374					),375					(376						get_account_id_from_seed::<sr25519::Public>("Bob"),377						get_from_seed::<AuraId>("Bob"),378					),379				],380				// Pre-funded accounts381				vec![382					get_account_id_from_seed::<sr25519::Public>("Alice"),383					get_account_id_from_seed::<sr25519::Public>("Bob"),384					get_account_id_from_seed::<sr25519::Public>("Charlie"),385					get_account_id_from_seed::<sr25519::Public>("Dave"),386					get_account_id_from_seed::<sr25519::Public>("Eve"),387					get_account_id_from_seed::<sr25519::Public>("Ferdie"),388					get_account_id_from_seed::<sr25519::Public>("Alice//stash"),389					get_account_id_from_seed::<sr25519::Public>("Bob//stash"),390					get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),391					get_account_id_from_seed::<sr25519::Public>("Dave//stash"),392					get_account_id_from_seed::<sr25519::Public>("Eve//stash"),393					get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),394				],395				PARA_ID396			)397		},398		// Bootnodes399		vec![],400		// Telemetry401		None,402		// Protocol ID403		None,404		None,405		// Properties406		Some(properties),407		// Extensions408		Extensions {409			relay_chain: "westend-local".into(),410			para_id: PARA_ID,411		},412	)413}
after · node/cli/src/chain_spec.rs
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/>.1617use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};18use sc_service::ChainType;19use sp_core::{sr25519, Pair, Public};20use sp_runtime::traits::{IdentifyAccount, Verify};21use std::collections::BTreeMap;2223use serde::{Deserialize, Serialize};24use serde_json::map::Map;2526use up_common::types::opaque::*;2728#[cfg(feature = "unique-runtime")]29pub use unique_runtime as default_runtime;3031#[cfg(all(not(feature = "unique-runtime"), feature = "quartz-runtime"))]32pub use quartz_runtime as default_runtime;3334#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]35pub use opal_runtime as default_runtime;3637/// The `ChainSpec` parameterized for the unique runtime.38#[cfg(feature = "unique-runtime")]39pub type UniqueChainSpec = sc_service::GenericChainSpec<unique_runtime::GenesisConfig, Extensions>;4041/// The `ChainSpec` parameterized for the quartz runtime.42#[cfg(feature = "quartz-runtime")]43pub type QuartzChainSpec = sc_service::GenericChainSpec<quartz_runtime::GenesisConfig, Extensions>;4445/// The `ChainSpec` parameterized for the opal runtime.46pub type OpalChainSpec = sc_service::GenericChainSpec<opal_runtime::GenesisConfig, Extensions>;4748#[cfg(feature = "unique-runtime")]49pub type DefaultChainSpec = UniqueChainSpec;5051#[cfg(all(not(feature = "unique-runtime"), feature = "quartz-runtime"))]52pub type DefaultChainSpec = QuartzChainSpec;5354#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]55pub type DefaultChainSpec = OpalChainSpec;5657#[cfg(not(feature = "unique-runtime"))]58/// PARA_ID for Opal/Sapphire/Quartz59const PARA_ID: u32 = 2095;6061#[cfg(feature = "unique-runtime")]62/// PARA_ID for Unique63const PARA_ID: u32 = 2037;6465pub trait RuntimeIdentification {66	fn runtime_id(&self) -> RuntimeId;67}6869impl RuntimeIdentification for Box<dyn sc_service::ChainSpec> {70	fn runtime_id(&self) -> RuntimeId {71		#[cfg(feature = "unique-runtime")]72		if self.id().starts_with("unique") || self.id().starts_with("unq") {73			return RuntimeId::Unique;74		}7576		#[cfg(feature = "quartz-runtime")]77		if self.id().starts_with("quartz")78			|| self.id().starts_with("qtz")79			|| self.id().starts_with("sapphire")80		{81			return RuntimeId::Quartz;82		}8384		if self.id().starts_with("opal") || self.id() == "dev" || self.id() == "local_testnet" {85			return RuntimeId::Opal;86		}8788		RuntimeId::Unknown(self.id().into())89	}90}9192pub enum ServiceId {93	Prod,94	Dev,95}9697pub trait ServiceIdentification {98	fn service_id(&self) -> ServiceId;99}100101impl ServiceIdentification for Box<dyn sc_service::ChainSpec> {102	fn service_id(&self) -> ServiceId {103		if self.id().ends_with("dev") {104			ServiceId::Dev105		} else {106			ServiceId::Prod107		}108	}109}110111/// Helper function to generate a crypto pair from seed112pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {113	TPublic::Pair::from_string(&format!("//{}", seed), None)114		.expect("static values are valid; qed")115		.public()116}117118/// The extensions for the [`DefaultChainSpec`].119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)]120#[serde(deny_unknown_fields)]121pub struct Extensions {122	/// The relay chain of the Parachain.123	pub relay_chain: String,124	/// The id of the Parachain.125	pub para_id: u32,126}127128impl Extensions {129	/// Try to get the extension from the given `ChainSpec`.130	pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> {131		sc_chain_spec::get_extension(chain_spec.extensions())132	}133}134135type AccountPublic = <Signature as Verify>::Signer;136137/// Helper function to generate an account ID from seed138pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId139where140	AccountPublic: From<<TPublic::Pair as Pair>::Public>,141{142	AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()143}144145#[cfg(not(feature = "unique-runtime"))]146macro_rules! testnet_genesis {147	(148		$runtime:path,149		$root_key:expr,150		$initial_invulnerables:expr,151		$endowed_accounts:expr,152		$id:expr153	) => {{154		use $runtime::*;155156		GenesisConfig {157			system: SystemConfig {158				code: WASM_BINARY159					.expect("WASM binary was not build, please build it!")160					.to_vec(),161			},162			balances: BalancesConfig {163				balances: $endowed_accounts164					.iter()165					.cloned()166					// 1e13 UNQ167					.map(|k| (k, 1 << 100))168					.collect(),169			},170			common: Default::default(),171			nonfungible: Default::default(),172			treasury: Default::default(),173			tokens: TokensConfig { balances: vec![] },174			sudo: SudoConfig {175				key: Some($root_key),176			},177			vesting: VestingConfig { vesting: vec![] },178			parachain_info: ParachainInfoConfig {179				parachain_id: $id.into(),180			},181			parachain_system: Default::default(),182			collator_selection: CollatorSelectionConfig {183				invulnerables: $initial_invulnerables184					.iter()185					.cloned()186					.map(|(acc, _)| acc)187					.collect(),188			},189			session: SessionConfig {190				keys: $initial_invulnerables191					.into_iter()192					.map(|(acc, aura)| {193						(194							acc.clone(),          // account id195							acc,                  // validator id196							SessionKeys { aura }, // session keys197						)198					})199					.collect(),200			},201			aura: Default::default(),202			aura_ext: Default::default(),203			evm: EVMConfig {204				accounts: BTreeMap::new(),205			},206			ethereum: EthereumConfig {},207			polkadot_xcm: Default::default(),208			transaction_payment: Default::default(),209		}210	}};211}212213#[cfg(feature = "unique-runtime")]214macro_rules! testnet_genesis {215	(216		$runtime:path,217		$root_key:expr,218		$initial_invulnerables:expr,219		$endowed_accounts:expr,220		$id:expr221	) => {{222		use $runtime::*;223224		GenesisConfig {225			system: SystemConfig {226				code: WASM_BINARY227					.expect("WASM binary was not build, please build it!")228					.to_vec(),229			},230			common: Default::default(),231			nonfungible: Default::default(),232			balances: BalancesConfig {233				balances: $endowed_accounts234					.iter()235					.cloned()236					// 1e13 UNQ237					.map(|k| (k, 1 << 100))238					.collect(),239			},240			treasury: Default::default(),241			tokens: TokensConfig { balances: vec![] },242			sudo: SudoConfig {243				key: Some($root_key),244			},245			vesting: VestingConfig { vesting: vec![] },246			parachain_info: ParachainInfoConfig {247				parachain_id: $id.into(),248			},249			parachain_system: Default::default(),250			aura: AuraConfig {251				authorities: $initial_invulnerables252					.into_iter()253					.map(|(_, aura)| aura)254					.collect(),255			},256			aura_ext: Default::default(),257			evm: EVMConfig {258				accounts: BTreeMap::new(),259			},260			ethereum: EthereumConfig {},261			polkadot_xcm: Default::default(),262			transaction_payment: Default::default(),263		}264	}};265}266267pub fn development_config() -> DefaultChainSpec {268	let mut properties = Map::new();269	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());270	properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());271	properties.insert(272		"ss58Format".into(),273		default_runtime::SS58Prefix::get().into(),274	);275276	DefaultChainSpec::from_genesis(277		// Name278		format!(279			"{}{}",280			default_runtime::RUNTIME_NAME.to_uppercase(),281			if cfg!(feature = "unique-runtime") {282				""283			} else {284				" by UNIQUE"285			}286		)287		.as_str(),288		// ID289		format!("{}_dev", default_runtime::RUNTIME_NAME).as_str(),290		ChainType::Local,291		move || {292			testnet_genesis!(293				default_runtime,294				// Sudo account295				get_account_id_from_seed::<sr25519::Public>("Alice"),296				vec![297					(298						get_account_id_from_seed::<sr25519::Public>("Alice"),299						get_from_seed::<AuraId>("Alice"),300					),301					(302						get_account_id_from_seed::<sr25519::Public>("Bob"),303						get_from_seed::<AuraId>("Bob"),304					),305				],306				// Pre-funded accounts307				vec![308					get_account_id_from_seed::<sr25519::Public>("Alice"),309					get_account_id_from_seed::<sr25519::Public>("Bob"),310					get_account_id_from_seed::<sr25519::Public>("Charlie"),311					get_account_id_from_seed::<sr25519::Public>("Dave"),312					get_account_id_from_seed::<sr25519::Public>("Eve"),313					get_account_id_from_seed::<sr25519::Public>("Ferdie"),314					get_account_id_from_seed::<sr25519::Public>("Alice//stash"),315					get_account_id_from_seed::<sr25519::Public>("Bob//stash"),316					get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),317					get_account_id_from_seed::<sr25519::Public>("Dave//stash"),318					get_account_id_from_seed::<sr25519::Public>("Eve//stash"),319					get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),320				],321				PARA_ID322			)323		},324		// Bootnodes325		vec![],326		// Telemetry327		None,328		// Protocol ID329		None,330		None,331		// Properties332		Some(properties),333		// Extensions334		Extensions {335			relay_chain: "rococo-dev".into(),336			para_id: PARA_ID,337		},338	)339}340341pub fn local_testnet_config() -> DefaultChainSpec {342	let mut properties = Map::new();343	properties.insert("tokenSymbol".into(), default_runtime::TOKEN_SYMBOL.into());344	properties.insert("tokenDecimals".into(), default_runtime::DECIMALS.into());345	properties.insert(346		"ss58Format".into(),347		default_runtime::SS58Prefix::get().into(),348	);349350	DefaultChainSpec::from_genesis(351		// Name352		format!(353			"{}{}",354			default_runtime::RUNTIME_NAME.to_uppercase(),355			if cfg!(feature = "unique-runtime") {356				""357			} else {358				" by UNIQUE"359			}360		)361		.as_str(),362		// ID363		format!("{}_local", default_runtime::RUNTIME_NAME).as_str(),364		ChainType::Local,365		move || {366			testnet_genesis!(367				default_runtime,368				// Sudo account369				get_account_id_from_seed::<sr25519::Public>("Alice"),370				vec![371					(372						get_account_id_from_seed::<sr25519::Public>("Alice"),373						get_from_seed::<AuraId>("Alice"),374					),375					(376						get_account_id_from_seed::<sr25519::Public>("Bob"),377						get_from_seed::<AuraId>("Bob"),378					),379				],380				// Pre-funded accounts381				vec![382					get_account_id_from_seed::<sr25519::Public>("Alice"),383					get_account_id_from_seed::<sr25519::Public>("Bob"),384					get_account_id_from_seed::<sr25519::Public>("Charlie"),385					get_account_id_from_seed::<sr25519::Public>("Dave"),386					get_account_id_from_seed::<sr25519::Public>("Eve"),387					get_account_id_from_seed::<sr25519::Public>("Ferdie"),388					get_account_id_from_seed::<sr25519::Public>("Alice//stash"),389					get_account_id_from_seed::<sr25519::Public>("Bob//stash"),390					get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),391					get_account_id_from_seed::<sr25519::Public>("Dave//stash"),392					get_account_id_from_seed::<sr25519::Public>("Eve//stash"),393					get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),394				],395				PARA_ID396			)397		},398		// Bootnodes399		vec![],400		// Telemetry401		None,402		// Protocol ID403		None,404		None,405		// Properties406		Some(properties),407		// Extensions408		Extensions {409			relay_chain: "westend-local".into(),410			para_id: PARA_ID,411		},412	)413}
modifiedpallets/balances-adapter/src/erc.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/erc.rs
+++ b/pallets/balances-adapter/src/erc.rs
@@ -38,14 +38,14 @@
 
 #[solidity_interface(name = ERC20, events(ERC20Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x942e8b22)]
 impl<T: Config> NativeFungibleHandle<T> {
-	fn allowance(&self, owner: Address, spender: Address) -> Result<U256> {
+	fn allowance(&self, _owner: Address, _spender: Address) -> Result<U256> {
 		Ok(U256::zero())
 	}
 
 	// #[weight(<SelfWeightOf<T>>::approve())]
-	fn approve(&mut self, caller: Caller, spender: Address, amount: U256) -> Result<bool> {
+	fn approve(&mut self, _caller: Caller, _spender: Address, _amount: U256) -> Result<bool> {
 		// self.consume_store_reads(1)?;
-		Err("Approve not supported now".into())
+		Err("Approve not supported".into())
 	}
 
 	fn balance_of(&self, owner: Address) -> Result<U256> {
@@ -106,7 +106,7 @@
 		let to = T::CrossAccountId::from_eth(to);
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
 
-		if from != to {
+		if from != caller {
 			return Err("no permission".into());
 		}
 		// let budget = self
@@ -171,7 +171,7 @@
 		let to = to.into_sub_cross_account::<T>()?;
 		let amount = amount.try_into().map_err(|_| "amount overflow")?;
 
-		if from != to {
+		if from != caller {
 			return Err("no permission".into());
 		}
 
modifiedpallets/common/src/dispatch.rsdiffbeforeafterboth
--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -83,7 +83,7 @@
 	///
 	/// * `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.
 	///
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -1296,10 +1296,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?
modifiedruntime/common/config/pallets/mod.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -24,7 +24,8 @@
 		weights::CommonWeights,
 		RelayChainBlockNumberProvider,
 	},
-	Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, Balances,
+	Runtime, RuntimeEvent, RuntimeCall, RuntimeOrigin, RUNTIME_NAME, TOKEN_SYMBOL, DECIMALS,
+	Balances,
 };
 use frame_support::traits::{ConstU32, ConstU64, Currency};
 use up_common::{
@@ -53,7 +54,7 @@
 
 parameter_types! {
 	pub const CollectionCreationPrice: Balance = 2 * UNIQUE;
-	pub const Decimals: u8 = 32;
+	pub const Decimals: u8 = DECIMALS;
 	pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating();
 	pub Name: String = RUNTIME_NAME.to_string();
 	pub Symbol: String = TOKEN_SYMBOL.to_string();
modifiedruntime/common/dispatch.rsdiffbeforeafterboth
--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -99,7 +99,10 @@
 		Ok(id)
 	}
 
-	fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> DispatchResult {
+	fn destroy(sender: T::CrossAccountId, collection_id: CollectionId) -> DispatchResult {
+		let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+		collection.check_is_internal()?;
+
 		match collection.mode {
 			CollectionMode::ReFungible => {
 				PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?
modifiedruntime/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 {
modifiedruntime/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 {
modifiedruntime/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 {
modifiedtests/src/eth/nativeFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nativeFungible.test.ts
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -15,29 +15,157 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import {itEth, usingEthPlaygrounds} from './util';
+import {expect, itEth, usingEthPlaygrounds} from './util';
 
-describe('NativeFungible: Plain calls', () => {
+describe('NativeFungible: ERC20 calls', () => {
   let donor: IKeyringPair;
-  let alice: IKeyringPair;
-  let owner: IKeyringPair;
 
   before(async function() {
     await usingEthPlaygrounds(async (helper, privateKey) => {
       donor = await privateKey({url: import.meta.url});
-      [alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);
+      // [alice] = await helper.arrange.createAccounts([30n], donor);
     });
   });
 
-  itEth.skip('Can perform approve()', async ({helper}) => {
+  itEth('approve()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const spender = helper.eth.createAccount();
-    const collection = await helper.ft.mintCollection(alice);
-    await collection.mint(alice, 200n, {Ethereum: owner});
+    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 decimals = await contract.methods.decimals().call({from: owner});
+    expect(decimals).to.be.eq('18');
+  });
+
+  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 name = await contract.methods.name().call({from: owner});
+    expect(name).to.be.eq('opal');
+  });
+
+  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 name = await contract.methods.symbol().call({from: owner});
+    expect(name).to.be.eq('OPL');
+  });
+
+  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 === balanceReceiverAfter - 50n).to.be.true;
+  });
 
+  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);
 
-    await contract.methods.approve(spender, 100).send({from: 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('no permission');
+  });
+});
+
+describe('NativeFungible: ERC20UniqueExtensions calls', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({url: import.meta.url});
+      // [alice] = await helper.arrange.createAccounts([30n], donor);
+    });
+  });
+
+  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
modifiedtests/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
@@ -142,7 +142,7 @@
 
   async collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false) {
     let abi;
-    if (address === '0x17C4e6453cC49aaAAEaCA894E6d9683e00000000' && mode === 'ft') {
+    if (address === '0x17C4e6453cC49aaAAEaCA894E6d9683e00000000') {
       abi = nativeFungibleAbi;
     } else {
       abi ={
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -2400,7 +2400,16 @@
     return {free: accountInfo.free.toBigInt(), frozen: accountInfo.frozen.toBigInt(), reserved: accountInfo.reserved.toBigInt()};
   }
 
-  async getLocked(address: TSubstrateAccount): Promise<[{ id: string, amount: bigint, reason: string }]> {
+  /**
+   * 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}; });
   }
@@ -2488,6 +2497,14 @@
   }
 
   /**
+   * 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