git.delta.rocks / unique-network / refs/commits / a156ee0040e7

difftreelog

refactor Make implementations of Abi* for EthCrossAccount via AbiCoder macro

Trubnikov Sergey2022-11-17parent: #0a8a6b0.patch.diff
in: master

9 files changed

modified.maintain/scripts/generate_abi.shdiffbeforeafterboth
--- a/.maintain/scripts/generate_abi.sh
+++ b/.maintain/scripts/generate_abi.sh
@@ -4,6 +4,7 @@
 dir=$PWD
 
 tmp=$(mktemp -d)
+echo "Tmp file: $tmp/input.sol"
 cd $tmp
 cp $dir/$INPUT input.sol
 solcjs --abi -p input.sol
modifiedcrates/evm-coder/src/abi/impls.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi/impls.rs
+++ b/crates/evm-coder/src/abi/impls.rs
@@ -120,42 +120,6 @@
 	}
 }
 
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
-
-impl AbiType for EthCrossAccount {
-	const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)"));
-
-	fn is_dynamic() -> bool {
-		address::is_dynamic() || uint256::is_dynamic()
-	}
-
-	fn size() -> usize {
-		<address as AbiType>::size() + <uint256 as AbiType>::size()
-	}
-}
-
-impl AbiRead for EthCrossAccount {
-	fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
-		let size = if !EthCrossAccount::is_dynamic() {
-			Some(<EthCrossAccount as AbiType>::size())
-		} else {
-			None
-		};
-		let mut subresult = reader.subresult(size)?;
-		let eth = <address>::abi_read(&mut subresult)?;
-		let sub = <uint256>::abi_read(&mut subresult)?;
-
-		Ok(EthCrossAccount { eth, sub })
-	}
-}
-
-impl AbiWrite for EthCrossAccount {
-	fn abi_write(&self, writer: &mut AbiWriter) {
-		self.eth.abi_write(writer);
-		self.sub.abi_write(writer);
-	}
-}
-
 impl sealed::CanBePlacedInVec for Property {}
 
 impl AbiType for Property {
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -93,6 +93,7 @@
 pub use evm_coder_procedural::solidity;
 /// See [`solidity_interface`]
 pub use evm_coder_procedural::weight;
+pub use evm_coder_procedural::AbiCoder;
 pub use sha3_const;
 
 /// Derives [`ToLog`] for enum
@@ -119,7 +120,6 @@
 
 	#[cfg(not(feature = "std"))]
 	use alloc::{vec::Vec};
-	use pallet_evm::account::CrossAccountId;
 	use primitive_types::{U256, H160, H256};
 
 	pub type address = H160;
@@ -185,73 +185,7 @@
 		#[must_use]
 		pub fn is_empty(&self) -> bool {
 			self.len() == 0
-		}
-	}
-
-	#[derive(Debug, Default)]
-	pub struct EthCrossAccount {
-		pub(crate) eth: address,
-		pub(crate) sub: uint256,
-	}
-
-	impl EthCrossAccount {
-		pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
-		where
-			T: pallet_evm::Config,
-			T::AccountId: AsRef<[u8; 32]>,
-		{
-			if cross_account_id.is_canonical_substrate() {
-				Self {
-					eth: Default::default(),
-					sub: convert_cross_account_to_uint256::<T>(cross_account_id),
-				}
-			} else {
-				Self {
-					eth: *cross_account_id.as_eth(),
-					sub: Default::default(),
-				}
-			}
-		}
-
-		pub fn into_sub_cross_account<T>(&self) -> crate::execution::Result<T::CrossAccountId>
-		where
-			T: pallet_evm::Config,
-			T::AccountId: From<[u8; 32]>,
-		{
-			if self.eth == Default::default() && self.sub == Default::default() {
-				Err("All fields of cross account is zeroed".into())
-			} else if self.eth == Default::default() {
-				Ok(convert_uint256_to_cross_account::<T>(self.sub))
-			} else if self.sub == Default::default() {
-				Ok(T::CrossAccountId::from_eth(self.eth))
-			} else {
-				Err("All fields of cross account is non zeroed".into())
-			}
 		}
-	}
-
-	/// Convert `CrossAccountId` to `uint256`.
-	pub fn convert_cross_account_to_uint256<T: pallet_evm::Config>(
-		from: &T::CrossAccountId,
-	) -> uint256
-	where
-		T::AccountId: AsRef<[u8; 32]>,
-	{
-		let slice = from.as_sub().as_ref();
-		uint256::from_big_endian(slice)
-	}
-
-	/// Convert `uint256` to `CrossAccountId`.
-	pub fn convert_uint256_to_cross_account<T: pallet_evm::Config>(
-		from: uint256,
-	) -> T::CrossAccountId
-	where
-		T::AccountId: From<[u8; 32]>,
-	{
-		let mut new_admin_arr = [0_u8; 32];
-		from.to_big_endian(&mut new_admin_arr);
-		let account_id = T::AccountId::from(new_admin_arr);
-		T::CrossAccountId::from_sub(account_id)
 	}
 
 	#[derive(Debug, Default)]
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -145,7 +145,7 @@
 	}
 }
 
-mod sealed {
+pub mod sealed {
 	/// Not every type should be directly placed in vec.
 	/// Vec encoding is not memory efficient, as every item will be padded
 	/// to 32 bytes.
@@ -156,7 +156,6 @@
 impl sealed::CanBePlacedInVec for uint256 {}
 impl sealed::CanBePlacedInVec for string {}
 impl sealed::CanBePlacedInVec for address {}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
 impl sealed::CanBePlacedInVec for Property {}
 
 impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
@@ -171,61 +170,6 @@
 		write!(writer, "new ")?;
 		T::solidity_name(writer, tc)?;
 		write!(writer, "[](0)")
-	}
-}
-
-impl SolidityTupleType for EthCrossAccount {
-	fn names(tc: &TypeCollector) -> Vec<string> {
-		let mut collected = Vec::with_capacity(Self::len());
-		{
-			let mut out = string::new();
-			address::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		{
-			let mut out = string::new();
-			uint256::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		collected
-	}
-
-	fn len() -> usize {
-		2
-	}
-}
-
-impl SolidityTypeName for EthCrossAccount {
-	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}", tc.collect_struct::<Self>())
-	}
-
-	fn is_simple() -> bool {
-		false
-	}
-
-	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}(", tc.collect_struct::<Self>())?;
-		address::solidity_default(writer, tc)?;
-		write!(writer, ",")?;
-		uint256::solidity_default(writer, tc)?;
-		write!(writer, ")")
-	}
-}
-
-impl StructCollect for EthCrossAccount {
-	fn name() -> String {
-		"EthCrossAccount".into()
-	}
-
-	fn declaration() -> String {
-		let mut str = String::new();
-		writeln!(str, "/// @dev Cross account struct").unwrap();
-		writeln!(str, "struct {} {{", Self::name()).unwrap();
-		writeln!(str, "\taddress eth;").unwrap();
-		writeln!(str, "\tuint256 sub;").unwrap();
-		writeln!(str, "}}").unwrap();
-		str
 	}
 }
 
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
before · pallets/common/src/erc.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/>.1617//! This module contains the implementation of pallet methods for evm.1819use evm_coder::{20	abi::AbiType,21	solidity_interface, solidity, ToLog,22	types::*,23	types::Property as PropertyStruct,24	execution::{Result, Error},25	weight,26};27pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use up_data_structs::{31	AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,32	SponsoringRateLimit, SponsorshipState,33};34use alloc::format;3536use crate::{37	Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,38	eth::convert_cross_account_to_uint256, weights::WeightInfo,39};4041/// Events for ethereum collection helper.42#[derive(ToLog)]43pub enum CollectionHelpersEvents {44	/// The collection has been created.45	CollectionCreated {46		/// Collection owner.47		#[indexed]48		owner: address,4950		/// Collection ID.51		#[indexed]52		collection_id: address,53	},54	/// The collection has been destroyed.55	CollectionDestroyed {56		/// Collection ID.57		#[indexed]58		collection_id: address,59	},60}6162/// Does not always represent a full collection, for RFT it is either63/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).64pub trait CommonEvmHandler {65	/// Raw compiled binary code of the contract stub66	const CODE: &'static [u8];6768	/// Call precompiled handle.69	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;70}7172/// @title A contract that allows you to work with collections.73#[solidity_interface(name = Collection)]74impl<T: Config> CollectionHandle<T>75where76	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,77{78	/// Set collection property.79	///80	/// @param key Property key.81	/// @param value Propery value.82	#[solidity(hide)]83	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]84	fn set_collection_property(85		&mut self,86		caller: caller,87		key: string,88		value: bytes,89	) -> Result<void> {90		let caller = T::CrossAccountId::from_eth(caller);91		let key = <Vec<u8>>::from(key)92			.try_into()93			.map_err(|_| "key too large")?;94		let value = value.0.try_into().map_err(|_| "value too large")?;9596		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })97			.map_err(dispatch_to_evm::<T>)98	}99100	/// Set collection properties.101	///102	/// @param properties Vector of properties key/value pair.103	#[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]104	fn set_collection_properties(105		&mut self,106		caller: caller,107		properties: Vec<PropertyStruct>,108	) -> Result<void> {109		let caller = T::CrossAccountId::from_eth(caller);110111		let properties = properties112			.into_iter()113			.map(|PropertyStruct { key, value }| {114				let key = <Vec<u8>>::from(key)115					.try_into()116					.map_err(|_| "key too large")?;117118				let value = value.0.try_into().map_err(|_| "value too large")?;119120				Ok(Property { key, value })121			})122			.collect::<Result<Vec<_>>>()?;123124		<Pallet<T>>::set_collection_properties(self, &caller, properties)125			.map_err(dispatch_to_evm::<T>)126	}127128	/// Delete collection property.129	///130	/// @param key Property key.131	#[solidity(hide)]132	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]133	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {134		let caller = T::CrossAccountId::from_eth(caller);135		let key = <Vec<u8>>::from(key)136			.try_into()137			.map_err(|_| "key too large")?;138139		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)140	}141142	/// Delete collection properties.143	///144	/// @param keys Properties keys.145	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]146	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {147		let caller = T::CrossAccountId::from_eth(caller);148		let keys = keys149			.into_iter()150			.map(|key| {151				<Vec<u8>>::from(key)152					.try_into()153					.map_err(|_| Error::Revert("key too large".into()))154			})155			.collect::<Result<Vec<_>>>()?;156157		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)158	}159160	/// Get collection property.161	///162	/// @dev Throws error if key not found.163	///164	/// @param key Property key.165	/// @return bytes The property corresponding to the key.166	fn collection_property(&self, key: string) -> Result<bytes> {167		let key = <Vec<u8>>::from(key)168			.try_into()169			.map_err(|_| "key too large")?;170171		let props = CollectionProperties::<T>::get(self.id);172		let prop = props.get(&key).ok_or("key not found")?;173174		Ok(bytes(prop.to_vec()))175	}176177	/// Get collection properties.178	///179	/// @param keys Properties keys. Empty keys for all propertyes.180	/// @return Vector of properties key/value pairs.181	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {182		let keys = keys183			.into_iter()184			.map(|key| {185				<Vec<u8>>::from(key)186					.try_into()187					.map_err(|_| Error::Revert("key too large".into()))188			})189			.collect::<Result<Vec<_>>>()?;190191		let properties = Pallet::<T>::filter_collection_properties(192			self.id,193			if keys.is_empty() { None } else { Some(keys) },194		)195		.map_err(dispatch_to_evm::<T>)?;196197		let properties = properties198			.into_iter()199			.map(|p| {200				let key =201					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;202				let value = bytes(p.value.to_vec());203				Ok(PropertyStruct { key, value })204			})205			.collect::<Result<Vec<_>>>()?;206		Ok(properties)207	}208209	/// Set the sponsor of the collection.210	///211	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.212	///213	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.214	#[solidity(hide)]215	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {216		self.consume_store_reads_and_writes(1, 1)?;217218		check_is_owner_or_admin(caller, self)?;219220		let sponsor = T::CrossAccountId::from_eth(sponsor);221		self.set_sponsor(sponsor.as_sub().clone())222			.map_err(dispatch_to_evm::<T>)?;223		save(self)224	}225226	/// Set the sponsor of the collection.227	///228	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.229	///230	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.231	fn set_collection_sponsor_cross(232		&mut self,233		caller: caller,234		sponsor: EthCrossAccount,235	) -> Result<void> {236		self.consume_store_reads_and_writes(1, 1)?;237238		check_is_owner_or_admin(caller, self)?;239240		let sponsor = sponsor.into_sub_cross_account::<T>()?;241		self.set_sponsor(sponsor.as_sub().clone())242			.map_err(dispatch_to_evm::<T>)?;243		save(self)244	}245246	/// Whether there is a pending sponsor.247	fn has_collection_pending_sponsor(&self) -> Result<bool> {248		Ok(matches!(249			self.collection.sponsorship,250			SponsorshipState::Unconfirmed(_)251		))252	}253254	/// Collection sponsorship confirmation.255	///256	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.257	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {258		self.consume_store_writes(1)?;259260		let caller = T::CrossAccountId::from_eth(caller);261		if !self262			.confirm_sponsorship(caller.as_sub())263			.map_err(dispatch_to_evm::<T>)?264		{265			return Err("caller is not set as sponsor".into());266		}267		save(self)268	}269270	/// Remove collection sponsor.271	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272		self.consume_store_reads_and_writes(1, 1)?;273		check_is_owner_or_admin(caller, self)?;274		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;275		save(self)276	}277278	/// Get current sponsor.279	///280	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.281	fn collection_sponsor(&self) -> Result<(address, uint256)> {282		let sponsor = match self.collection.sponsorship.sponsor() {283			Some(sponsor) => sponsor,284			None => return Ok(Default::default()),285		};286		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());287		let result: (address, uint256) = if sponsor.is_canonical_substrate() {288			let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);289			(Default::default(), sponsor)290		} else {291			let sponsor = *sponsor.as_eth();292			(sponsor, Default::default())293		};294		Ok(result)295	}296297	/// Set limits for the collection.298	/// @dev Throws error if limit not found.299	/// @param limit Name of the limit. Valid names:300	/// 	"accountTokenOwnershipLimit",301	/// 	"sponsoredDataSize",302	/// 	"sponsoredDataRateLimit",303	/// 	"tokenLimit",304	/// 	"sponsorTransferTimeout",305	/// 	"sponsorApproveTimeout"306	///  	"ownerCanTransfer",307	/// 	"ownerCanDestroy",308	/// 	"transfersEnabled"309	/// @param value Value of the limit.310	#[solidity(rename_selector = "setCollectionLimit")]311	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {312		self.consume_store_reads_and_writes(1, 1)?;313314		let value = value315			.try_into()316			.map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;317318		let convert_value_to_bool = || match value {319			0 => Ok(false),320			1 => Ok(true),321			_ => {322				return Err(Error::Revert(format!(323					"can't convert value to boolean \"{}\"",324					value325				)))326			}327		};328329		check_is_owner_or_admin(caller, self)?;330		let mut limits = self.limits.clone();331332		match limit.as_str() {333			"accountTokenOwnershipLimit" => {334				limits.account_token_ownership_limit = Some(value);335			}336			"sponsoredDataSize" => {337				limits.sponsored_data_size = Some(value);338			}339			"sponsoredDataRateLimit" => {340				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));341			}342			"tokenLimit" => {343				limits.token_limit = Some(value);344			}345			"sponsorTransferTimeout" => {346				limits.sponsor_transfer_timeout = Some(value);347			}348			"sponsorApproveTimeout" => {349				limits.sponsor_approve_timeout = Some(value);350			}351			"ownerCanTransfer" => {352				limits.owner_can_transfer = Some(convert_value_to_bool()?);353			}354			"ownerCanDestroy" => {355				limits.owner_can_destroy = Some(convert_value_to_bool()?);356			}357			"transfersEnabled" => {358				limits.transfers_enabled = Some(convert_value_to_bool()?);359			}360			_ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),361		}362		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)363			.map_err(dispatch_to_evm::<T>)?;364		save(self)365	}366367	/// Get contract address.368	fn contract_address(&self) -> Result<address> {369		Ok(crate::eth::collection_id_to_address(self.id))370	}371372	/// Add collection admin.373	/// @param newAdmin Cross account administrator address.374	fn add_collection_admin_cross(375		&mut self,376		caller: caller,377		new_admin: EthCrossAccount,378	) -> Result<void> {379		self.consume_store_writes(2)?;380381		let caller = T::CrossAccountId::from_eth(caller);382		let new_admin = new_admin.into_sub_cross_account::<T>()?;383		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;384		Ok(())385	}386387	/// Remove collection admin.388	/// @param admin Cross account administrator address.389	fn remove_collection_admin_cross(390		&mut self,391		caller: caller,392		admin: EthCrossAccount,393	) -> Result<void> {394		self.consume_store_writes(2)?;395396		let caller = T::CrossAccountId::from_eth(caller);397		let admin = admin.into_sub_cross_account::<T>()?;398		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;399		Ok(())400	}401402	/// Add collection admin.403	/// @param newAdmin Address of the added administrator.404	#[solidity(hide)]405	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {406		self.consume_store_writes(2)?;407408		let caller = T::CrossAccountId::from_eth(caller);409		let new_admin = T::CrossAccountId::from_eth(new_admin);410		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;411		Ok(())412	}413414	/// Remove collection admin.415	///416	/// @param admin Address of the removed administrator.417	#[solidity(hide)]418	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {419		self.consume_store_writes(2)?;420421		let caller = T::CrossAccountId::from_eth(caller);422		let admin = T::CrossAccountId::from_eth(admin);423		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;424		Ok(())425	}426427	/// Toggle accessibility of collection nesting.428	///429	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'430	#[solidity(rename_selector = "setCollectionNesting")]431	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {432		self.consume_store_reads_and_writes(1, 1)?;433434		check_is_owner_or_admin(caller, self)?;435436		let mut permissions = self.collection.permissions.clone();437		let mut nesting = permissions.nesting().clone();438		nesting.token_owner = enable;439		nesting.restricted = None;440		permissions.nesting = Some(nesting);441442		self.collection.permissions = <Pallet<T>>::clamp_permissions(443			self.collection.mode.clone(),444			&self.collection.permissions,445			permissions,446		)447		.map_err(dispatch_to_evm::<T>)?;448449		save(self)450	}451452	/// Toggle accessibility of collection nesting.453	///454	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'455	/// @param collections Addresses of collections that will be available for nesting.456	#[solidity(rename_selector = "setCollectionNesting")]457	fn set_nesting(458		&mut self,459		caller: caller,460		enable: bool,461		collections: Vec<address>,462	) -> Result<void> {463		self.consume_store_reads_and_writes(1, 1)?;464465		if collections.is_empty() {466			return Err("no addresses provided".into());467		}468		check_is_owner_or_admin(caller, self)?;469470		let mut permissions = self.collection.permissions.clone();471		match enable {472			false => {473				let mut nesting = permissions.nesting().clone();474				nesting.token_owner = false;475				nesting.restricted = None;476				permissions.nesting = Some(nesting);477			}478			true => {479				let mut bv = OwnerRestrictedSet::new();480				for i in collections {481					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {482						Error::Revert("Can't convert address into collection id".into())483					})?)484					.map_err(|_| "too many collections")?;485				}486				let mut nesting = permissions.nesting().clone();487				nesting.token_owner = true;488				nesting.restricted = Some(bv);489				permissions.nesting = Some(nesting);490			}491		};492493		self.collection.permissions = <Pallet<T>>::clamp_permissions(494			self.collection.mode.clone(),495			&self.collection.permissions,496			permissions,497		)498		.map_err(dispatch_to_evm::<T>)?;499500		save(self)501	}502503	/// Set the collection access method.504	/// @param mode Access mode505	/// 	0 for Normal506	/// 	1 for AllowList507	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {508		self.consume_store_reads_and_writes(1, 1)?;509510		check_is_owner_or_admin(caller, self)?;511		let permissions = CollectionPermissions {512			access: Some(match mode {513				0 => AccessMode::Normal,514				1 => AccessMode::AllowList,515				_ => return Err("not supported access mode".into()),516			}),517			..Default::default()518		};519		self.collection.permissions = <Pallet<T>>::clamp_permissions(520			self.collection.mode.clone(),521			&self.collection.permissions,522			permissions,523		)524		.map_err(dispatch_to_evm::<T>)?;525526		save(self)527	}528529	/// Checks that user allowed to operate with collection.530	///531	/// @param user User address to check.532	fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {533		let user = user.into_sub_cross_account::<T>()?;534		Ok(Pallet::<T>::allowed(self.id, user))535	}536537	/// Add the user to the allowed list.538	///539	/// @param user Address of a trusted user.540	#[solidity(hide)]541	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {542		self.consume_store_writes(1)?;543544		let caller = T::CrossAccountId::from_eth(caller);545		let user = T::CrossAccountId::from_eth(user);546		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;547		Ok(())548	}549550	/// Add user to allowed list.551	///552	/// @param user User cross account address.553	fn add_to_collection_allow_list_cross(554		&mut self,555		caller: caller,556		user: EthCrossAccount,557	) -> Result<void> {558		self.consume_store_writes(1)?;559560		let caller = T::CrossAccountId::from_eth(caller);561		let user = user.into_sub_cross_account::<T>()?;562		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;563		Ok(())564	}565566	/// Remove the user from the allowed list.567	///568	/// @param user Address of a removed user.569	#[solidity(hide)]570	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {571		self.consume_store_writes(1)?;572573		let caller = T::CrossAccountId::from_eth(caller);574		let user = T::CrossAccountId::from_eth(user);575		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;576		Ok(())577	}578579	/// Remove user from allowed list.580	///581	/// @param user User cross account address.582	fn remove_from_collection_allow_list_cross(583		&mut self,584		caller: caller,585		user: EthCrossAccount,586	) -> Result<void> {587		self.consume_store_writes(1)?;588589		let caller = T::CrossAccountId::from_eth(caller);590		let user = user.into_sub_cross_account::<T>()?;591		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;592		Ok(())593	}594595	/// Switch permission for minting.596	///597	/// @param mode Enable if "true".598	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {599		self.consume_store_reads_and_writes(1, 1)?;600601		check_is_owner_or_admin(caller, self)?;602		let permissions = CollectionPermissions {603			mint_mode: Some(mode),604			..Default::default()605		};606		self.collection.permissions = <Pallet<T>>::clamp_permissions(607			self.collection.mode.clone(),608			&self.collection.permissions,609			permissions,610		)611		.map_err(dispatch_to_evm::<T>)?;612613		save(self)614	}615616	/// Check that account is the owner or admin of the collection617	///618	/// @param user account to verify619	/// @return "true" if account is the owner or admin620	#[solidity(hide, rename_selector = "isOwnerOrAdmin")]621	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {622		let user = T::CrossAccountId::from_eth(user);623		Ok(self.is_owner_or_admin(&user))624	}625626	/// Check that account is the owner or admin of the collection627	///628	/// @param user User cross account to verify629	/// @return "true" if account is the owner or admin630	fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {631		let user = user.into_sub_cross_account::<T>()?;632		Ok(self.is_owner_or_admin(&user))633	}634635	/// Returns collection type636	///637	/// @return `Fungible` or `NFT` or `ReFungible`638	fn unique_collection_type(&self) -> Result<string> {639		let mode = match self.collection.mode {640			CollectionMode::Fungible(_) => "Fungible",641			CollectionMode::NFT => "NFT",642			CollectionMode::ReFungible => "ReFungible",643		};644		Ok(mode.into())645	}646647	/// Get collection owner.648	///649	/// @return Tuble with sponsor address and his substrate mirror.650	/// If address is canonical then substrate mirror is zero and vice versa.651	fn collection_owner(&self) -> Result<EthCrossAccount> {652		Ok(EthCrossAccount::from_sub_cross_account::<T>(653			&T::CrossAccountId::from_sub(self.owner.clone()),654		))655	}656657	/// Changes collection owner to another account658	///659	/// @dev Owner can be changed only by current owner660	/// @param newOwner new owner account661	#[solidity(hide, rename_selector = "changeCollectionOwner")]662	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {663		self.consume_store_writes(1)?;664665		let caller = T::CrossAccountId::from_eth(caller);666		let new_owner = T::CrossAccountId::from_eth(new_owner);667		self.set_owner_internal(caller, new_owner)668			.map_err(dispatch_to_evm::<T>)669	}670671	/// Get collection administrators672	///673	/// @return Vector of tuples with admins address and his substrate mirror.674	/// If address is canonical then substrate mirror is zero and vice versa.675	fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {676		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))677			.map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))678			.collect();679		Ok(result)680	}681682	/// Changes collection owner to another account683	///684	/// @dev Owner can be changed only by current owner685	/// @param newOwner new owner cross account686	fn change_collection_owner_cross(687		&mut self,688		caller: caller,689		new_owner: EthCrossAccount,690	) -> Result<void> {691		self.consume_store_writes(1)?;692693		let caller = T::CrossAccountId::from_eth(caller);694		let new_owner = new_owner.into_sub_cross_account::<T>()?;695		self.set_owner_internal(caller, new_owner)696			.map_err(dispatch_to_evm::<T>)697	}698}699700/// ### Note701/// Do not forget to add: `self.consume_store_reads(1)?;`702fn check_is_owner_or_admin<T: Config>(703	caller: caller,704	collection: &CollectionHandle<T>,705) -> Result<T::CrossAccountId> {706	let caller = T::CrossAccountId::from_eth(caller);707	collection708		.check_is_owner_or_admin(&caller)709		.map_err(dispatch_to_evm::<T>)?;710	Ok(caller)711}712713/// ### Note714/// Do not forget to add: `self.consume_store_writes(1)?;`715fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {716	collection717		.check_is_internal()718		.map_err(dispatch_to_evm::<T>)?;719	collection.save().map_err(dispatch_to_evm::<T>)?;720	Ok(())721}722723/// Contains static property keys and values.724pub mod static_property {725	use evm_coder::{726		execution::{Result, Error},727	};728	use alloc::format;729730	const EXPECT_CONVERT_ERROR: &str = "length < limit";731732	/// Keys.733	pub mod key {734		use super::*;735736		/// Key "baseURI".737		pub fn base_uri() -> up_data_structs::PropertyKey {738			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)739		}740741		/// Key "url".742		pub fn url() -> up_data_structs::PropertyKey {743			property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)744		}745746		/// Key "suffix".747		pub fn suffix() -> up_data_structs::PropertyKey {748			property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)749		}750751		/// Key "parentNft".752		pub fn parent_nft() -> up_data_structs::PropertyKey {753			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)754		}755	}756757	/// Convert `byte` to [`PropertyKey`].758	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {759		bytes.to_vec().try_into().map_err(|_| {760			Error::Revert(format!(761				"Property key is too long. Max length is {}.",762				up_data_structs::PropertyKey::bound()763			))764		})765	}766767	/// Convert `bytes` to [`PropertyValue`].768	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {769		bytes.to_vec().try_into().map_err(|_| {770			Error::Revert(format!(771				"Property key is too long. Max length is {}.",772				up_data_structs::PropertyKey::bound()773			))774		})775	}776}
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,7 +16,10 @@
 
 //! The module contains a number of functions for converting and checking ethereum identifiers.
 
-use evm_coder::types::{uint256, address};
+use evm_coder::{
+	AbiCoder,
+	types::{uint256, address},
+};
 pub use pallet_evm::{Config, account::CrossAccountId};
 use sp_core::H160;
 use up_data_structs::CollectionId;
@@ -109,3 +112,111 @@
 		Err("All fields of cross account is non zeroed".into())
 	}
 }
+
+#[derive(Debug, Default, AbiCoder)]
+pub struct EthCrossAccount {
+	pub(crate) eth: address,
+	pub(crate) sub: uint256,
+}
+
+impl EthCrossAccount {
+	pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
+	where
+		T: pallet_evm::account::Config,
+		T::AccountId: AsRef<[u8; 32]>,
+	{
+		if cross_account_id.is_canonical_substrate() {
+			Self {
+				eth: Default::default(),
+				sub: convert_cross_account_to_uint256::<T>(cross_account_id),
+			}
+		} else {
+			Self {
+				eth: *cross_account_id.as_eth(),
+				sub: Default::default(),
+			}
+		}
+	}
+
+	pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
+	where
+		T: pallet_evm::account::Config,
+		T::AccountId: From<[u8; 32]>,
+	{
+		if self.eth == Default::default() && self.sub == Default::default() {
+			Err("All fields of cross account is zeroed".into())
+		} else if self.eth == Default::default() {
+			Ok(convert_uint256_to_cross_account::<T>(self.sub))
+		} else if self.sub == Default::default() {
+			Ok(T::CrossAccountId::from_eth(self.eth))
+		} else {
+			Err("All fields of cross account is non zeroed".into())
+		}
+	}
+}
+
+impl ::evm_coder::solidity::sealed::CanBePlacedInVec for EthCrossAccount {}
+impl ::evm_coder::solidity::SolidityTupleType for EthCrossAccount {
+	fn names(tc: &::evm_coder::solidity::TypeCollector) -> Vec<String> {
+		let mut collected =
+			Vec::with_capacity(<Self as ::evm_coder::solidity::SolidityTupleType>::len());
+		{
+			let mut out = String::new();
+			<address as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+				.expect("no fmt error");
+			collected.push(out);
+		}
+		{
+			let mut out = String::new();
+			<uint256 as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+				.expect("no fmt error");
+			collected.push(out);
+		}
+		collected
+	}
+
+	fn len() -> usize {
+		2
+	}
+}
+impl ::evm_coder::solidity::SolidityTypeName for EthCrossAccount {
+	fn solidity_name(
+		writer: &mut impl ::core::fmt::Write,
+		tc: &::evm_coder::solidity::TypeCollector,
+	) -> ::core::fmt::Result {
+		write!(writer, "{}", tc.collect_struct::<Self>())
+	}
+
+	fn is_simple() -> bool {
+		false
+	}
+
+	fn solidity_default(
+		writer: &mut impl ::core::fmt::Write,
+		tc: &::evm_coder::solidity::TypeCollector,
+	) -> ::core::fmt::Result {
+		write!(writer, "{}(", tc.collect_struct::<Self>())?;
+		address::solidity_default(writer, tc)?;
+		write!(writer, ",")?;
+		uint256::solidity_default(writer, tc)?;
+		write!(writer, ")")
+	}
+}
+
+impl ::evm_coder::solidity::StructCollect for EthCrossAccount {
+	fn name() -> String {
+		"EthCrossAccount".into()
+	}
+
+	fn declaration() -> String {
+		use std::fmt::Write;
+
+		let mut str = String::new();
+		writeln!(str, "/// @dev Cross account struct").unwrap();
+		writeln!(str, "struct {} {{", Self::name()).unwrap();
+		writeln!(str, "\taddress eth;").unwrap();
+		writeln!(str, "\tuint256 sub;").unwrap();
+		writeln!(str, "}}").unwrap();
+		str
+	}
+}
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -24,12 +24,15 @@
 	weight,
 };
 use up_data_structs::CollectionMode;
-use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
+use pallet_common::{
+	CollectionHandle,
+	erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+	eth::EthCrossAccount,
+};
 use sp_std::vec::Vec;
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use pallet_common::{CollectionHandle, erc::CollectionCall};
 use sp_core::Get;
 
 use crate::{
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -36,8 +36,9 @@
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_std::vec::Vec;
 use pallet_common::{
+	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
 	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
-	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
+	eth::EthCrossAccount,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::call;
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -33,6 +33,7 @@
 use pallet_common::{
 	CollectionHandle, CollectionPropertyPermissions,
 	erc::{CommonEvmHandler, CollectionCall, static_property::key},
+	eth::EthCrossAccount,
 	CommonCollectionOperations,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};