git.delta.rocks / unique-network / refs/commits / 527498be3f5d

difftreelog

refactor AbiRead

Trubnikov Sergey2022-11-02parent: #43fd1a2.patch.diff
in: master

4 files changed

modifiedcrates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -403,8 +403,9 @@
 	fn expand_parse(&self) -> proc_macro2::TokenStream {
 		assert!(!self.is_special());
 		let name = &self.name;
+		let ty = &self.ty;
 		quote! {
-			#name: reader.abi_read()?
+			#name: <#ty>::abi_read(reader)?
 		}
 	}
 
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -342,14 +342,12 @@
 	}
 }
 
-/// [`AbiReader`] implements reading of many types, but it should
-/// be limited to types defined in spec
-///
-/// As this trait can't be made sealed,
-/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`
-pub trait AbiRead<T> {
+/// [`AbiReader`] implements reading of many types.
+pub trait AbiRead {
 	/// Read item from current position, advanding decoder
-	fn abi_read(&mut self) -> Result<T>;
+	fn abi_read(reader: &mut AbiReader) -> Result<Self>
+	where
+		Self: Sized;
 }
 
 macro_rules! impl_abi_readable {
@@ -363,9 +361,9 @@
 				ABI_ALIGNMENT
 			}
 		}
-		impl AbiRead<$ty> for AbiReader<'_> {
-			fn abi_read(&mut self) -> Result<$ty> {
-				self.$method()
+		impl AbiRead for $ty {
+			fn abi_read(reader: &mut AbiReader) -> Result<$ty> {
+				reader.$method()
 			}
 		}
 	};
@@ -389,9 +387,9 @@
 		ABI_ALIGNMENT
 	}
 }
-impl AbiRead<bytes> for AbiReader<'_> {
-	fn abi_read(&mut self) -> Result<bytes> {
-		Ok(bytes(self.bytes()?))
+impl AbiRead for bytes {
+	fn abi_read(reader: &mut AbiReader) -> Result<bytes> {
+		Ok(bytes(reader.bytes()?))
 	}
 }
 
@@ -405,17 +403,14 @@
 impl sealed::CanBePlacedInVec for H160 {}
 impl sealed::CanBePlacedInVec for EthCrossAccount {}
 
-impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>
-where
-	Self: AbiRead<R>,
-{
-	fn abi_read(&mut self) -> Result<Vec<R>> {
-		let mut sub = self.subresult(None)?;
+impl<R: AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<R> {
+	fn abi_read(reader: &mut AbiReader) -> Result<Vec<R>> {
+		let mut sub = reader.subresult(None)?;
 		let size = sub.uint32()? as usize;
 		sub.subresult_offset = sub.offset;
 		let mut out = Vec::with_capacity(size);
 		for _ in 0..size {
-			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);
+			out.push(<R>::abi_read(&mut sub)?);
 		}
 		Ok(out)
 	}
@@ -435,16 +430,16 @@
 	}
 }
 
-impl AbiRead<EthCrossAccount> for AbiReader<'_> {
-	fn abi_read(&mut self) -> Result<EthCrossAccount> {
+impl AbiRead for EthCrossAccount {
+	fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
 		let size = if !EthCrossAccount::is_dynamic() {
 			Some(<EthCrossAccount as TypeHelper>::size())
 		} else {
 			None
 		};
-		let mut subresult = self.subresult(size)?;
-		let eth = <Self as AbiRead<address>>::abi_read(&mut subresult)?;
-		let sub = <Self as AbiRead<uint256>>::abi_read(&mut subresult)?;
+		let mut subresult = reader.subresult(size)?;
+		let eth = <address>::abi_read(&mut subresult)?;
+		let sub = <uint256>::abi_read(&mut subresult)?;
 
 		Ok(EthCrossAccount { eth, sub })
 	}
@@ -479,18 +474,16 @@
 
 		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
 
-		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>
+		impl<$($ident),+> AbiRead for ($($ident,)+)
 		where
-			$(
-				Self: AbiRead<$ident>,
-			)+
+			$($ident: AbiRead,)+
 			($($ident,)+): TypeHelper,
 		{
-			fn abi_read(&mut self) -> Result<($($ident,)+)> {
+			fn abi_read(reader: &mut AbiReader) -> Result<($($ident,)+)> {
 				let size = if !<($($ident,)+)>::is_dynamic() { Some(<($($ident,)+)>::size()) } else { None };
-				let mut subresult = self.subresult(size)?;
+				let mut subresult = reader.subresult(size)?;
 				Ok((
-					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+
+					$(<$ident>::abi_read(&mut subresult)?,)+
 				))
 			}
 		}
@@ -683,7 +676,7 @@
 
 					let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
 					assert_eq!(call, u32::to_be_bytes(function_identifier));
-					let data = <AbiReader<'_> as AbiRead<$type>>::abi_read(&mut decoder).unwrap();
+					let data = <$type>::abi_read(&mut decoder).unwrap();
 					assert_eq!(data, decoded_data);
 
 					let mut writer = AbiWriter::new_call(function_identifier);
@@ -889,8 +882,7 @@
 		let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
 		assert_eq!(call, u32::to_be_bytes(decoded_data.0));
 		let address = decoder.address().unwrap();
-		let data =
-			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();
+		let data = <Vec<(uint256, string)>>::abi_read(&mut decoder).unwrap();
 		assert_eq!(data, decoded_data.1);
 
 		let mut writer = AbiWriter::new_call(decoded_data.0);
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -341,7 +341,7 @@
 			return Ok(None);
 		}
 		Ok(Some(Self::SupportsInterface {
-			interface_id: input.abi_read()?,
+			interface_id: types::bytes4::abi_read(input)?,
 		}))
 	}
 }
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	solidity_interface, solidity, ToLog,21	types::*,22	execution::{Result, Error},23	weight,24	custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},25	make_signature,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, convert_tuple_to_cross_account},39	weights::WeightInfo,40};4142/// Events for ethereum collection helper.43#[derive(ToLog)]44pub enum CollectionHelpersEvents {45	/// The collection has been created.46	CollectionCreated {47		/// Collection owner.48		#[indexed]49		owner: address,5051		/// Collection ID.52		#[indexed]53		collection_id: address,54	},55	/// The collection has been destroyed.56	CollectionDestroyed {57		/// Collection ID.58		#[indexed]59		collection_id: address,60	},61}6263/// Does not always represent a full collection, for RFT it is either64/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).65pub trait CommonEvmHandler {66	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	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]83	fn set_collection_property(84		&mut self,85		caller: caller,86		key: string,87		value: bytes,88	) -> Result<void> {89		let caller = T::CrossAccountId::from_eth(caller);90		let key = <Vec<u8>>::from(key)91			.try_into()92			.map_err(|_| "key too large")?;93		let value = value.0.try_into().map_err(|_| "value too large")?;9495		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })96			.map_err(dispatch_to_evm::<T>)97	}9899	/// Set collection properties.100	///101	/// @param properties Vector of properties key/value pair.102	#[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]103	fn set_collection_properties(104		&mut self,105		caller: caller,106		properties: Vec<(string, bytes)>,107	) -> Result<void> {108		let caller = T::CrossAccountId::from_eth(caller);109110		let properties = properties111			.into_iter()112			.map(|(key, value)| {113				let key = <Vec<u8>>::from(key)114					.try_into()115					.map_err(|_| "key too large")?;116117				let value = value.0.try_into().map_err(|_| "value too large")?;118119				Ok(Property { key, value })120			})121			.collect::<Result<Vec<_>>>()?;122123		<Pallet<T>>::set_collection_properties(self, &caller, properties)124			.map_err(dispatch_to_evm::<T>)125	}126127	/// Delete collection property.128	///129	/// @param key Property key.130	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]131	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {132		let caller = T::CrossAccountId::from_eth(caller);133		let key = <Vec<u8>>::from(key)134			.try_into()135			.map_err(|_| "key too large")?;136137		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)138	}139140	/// Delete collection properties.141	///142	/// @param keys Properties keys.143	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]144	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {145		let caller = T::CrossAccountId::from_eth(caller);146		let keys = keys147			.into_iter()148			.map(|key| {149				<Vec<u8>>::from(key)150					.try_into()151					.map_err(|_| Error::Revert("key too large".into()))152			})153			.collect::<Result<Vec<_>>>()?;154155		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)156	}157158	/// Get collection property.159	///160	/// @dev Throws error if key not found.161	///162	/// @param key Property key.163	/// @return bytes The property corresponding to the key.164	fn collection_property(&self, key: string) -> Result<bytes> {165		let key = <Vec<u8>>::from(key)166			.try_into()167			.map_err(|_| "key too large")?;168169		let props = CollectionProperties::<T>::get(self.id);170		let prop = props.get(&key).ok_or("key not found")?;171172		Ok(bytes(prop.to_vec()))173	}174175	/// Get collection properties.176	///177	/// @param keys Properties keys. Empty keys for all propertyes.178	/// @return Vector of properties key/value pairs.179	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {180		let keys = keys181			.into_iter()182			.map(|key| {183				<Vec<u8>>::from(key)184					.try_into()185					.map_err(|_| Error::Revert("key too large".into()))186			})187			.collect::<Result<Vec<_>>>()?;188189		let properties = Pallet::<T>::filter_collection_properties(190			self.id,191			if keys.is_empty() { None } else { Some(keys) },192		)193		.map_err(dispatch_to_evm::<T>)?;194195		let properties = properties196			.into_iter()197			.map(|p| {198				let key =199					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;200				let value = bytes(p.value.to_vec());201				Ok((key, value))202			})203			.collect::<Result<Vec<_>>>()?;204		Ok(properties)205	}206207	/// Set the sponsor of the collection.208	///209	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.210	///211	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.212	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {213		self.consume_store_reads_and_writes(1, 1)?;214215		check_is_owner_or_admin(caller, self)?;216217		let sponsor = T::CrossAccountId::from_eth(sponsor);218		self.set_sponsor(sponsor.as_sub().clone())219			.map_err(dispatch_to_evm::<T>)?;220		save(self)221	}222223	/// Set the sponsor of the collection.224	///225	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.226	///227	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.228	fn set_collection_sponsor_cross(229		&mut self,230		caller: caller,231		sponsor: EthCrossAccount,232	) -> Result<void> {233		self.consume_store_reads_and_writes(1, 1)?;234235		check_is_owner_or_admin(caller, self)?;236237		let sponsor = sponsor.into_sub_cross_account::<T>()?;238		self.set_sponsor(sponsor.as_sub().clone())239			.map_err(dispatch_to_evm::<T>)?;240		save(self)241	}242243	/// Whether there is a pending sponsor.244	fn has_collection_pending_sponsor(&self) -> Result<bool> {245		Ok(matches!(246			self.collection.sponsorship,247			SponsorshipState::Unconfirmed(_)248		))249	}250251	/// Collection sponsorship confirmation.252	///253	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.254	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {255		self.consume_store_writes(1)?;256257		let caller = T::CrossAccountId::from_eth(caller);258		if !self259			.confirm_sponsorship(caller.as_sub())260			.map_err(dispatch_to_evm::<T>)?261		{262			return Err("caller is not set as sponsor".into());263		}264		save(self)265	}266267	/// Remove collection sponsor.268	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {269		self.consume_store_reads_and_writes(1, 1)?;270		check_is_owner_or_admin(caller, self)?;271		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;272		save(self)273	}274275	/// Get current sponsor.276	///277	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.278	fn collection_sponsor(&self) -> Result<(address, uint256)> {279		let sponsor = match self.collection.sponsorship.sponsor() {280			Some(sponsor) => sponsor,281			None => return Ok(Default::default()),282		};283		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());284		let result: (address, uint256) = if sponsor.is_canonical_substrate() {285			let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);286			(Default::default(), sponsor)287		} else {288			let sponsor = *sponsor.as_eth();289			(sponsor, Default::default())290		};291		Ok(result)292	}293294	/// Set limits for the collection.295	/// @dev Throws error if limit not found.296	/// @param limit Name of the limit. Valid names:297	/// 	"accountTokenOwnershipLimit",298	/// 	"sponsoredDataSize",299	/// 	"sponsoredDataRateLimit",300	/// 	"tokenLimit",301	/// 	"sponsorTransferTimeout",302	/// 	"sponsorApproveTimeout"303	/// @param value Value of the limit.304	#[solidity(rename_selector = "setCollectionLimit")]305	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {306		self.consume_store_reads_and_writes(1, 1)?;307308		check_is_owner_or_admin(caller, self)?;309		let mut limits = self.limits.clone();310311		match limit.as_str() {312			"accountTokenOwnershipLimit" => {313				limits.account_token_ownership_limit = Some(value);314			}315			"sponsoredDataSize" => {316				limits.sponsored_data_size = Some(value);317			}318			"sponsoredDataRateLimit" => {319				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));320			}321			"tokenLimit" => {322				limits.token_limit = Some(value);323			}324			"sponsorTransferTimeout" => {325				limits.sponsor_transfer_timeout = Some(value);326			}327			"sponsorApproveTimeout" => {328				limits.sponsor_approve_timeout = Some(value);329			}330			_ => {331				return Err(Error::Revert(format!(332					"unknown integer limit \"{}\"",333					limit334				)))335			}336		}337		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)338			.map_err(dispatch_to_evm::<T>)?;339		save(self)340	}341342	/// Set limits for the collection.343	/// @dev Throws error if limit not found.344	/// @param limit Name of the limit. Valid names:345	/// 	"ownerCanTransfer",346	/// 	"ownerCanDestroy",347	/// 	"transfersEnabled"348	/// @param value Value of the limit.349	#[solidity(rename_selector = "setCollectionLimit")]350	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {351		self.consume_store_reads_and_writes(1, 1)?;352353		check_is_owner_or_admin(caller, self)?;354		let mut limits = self.limits.clone();355356		match limit.as_str() {357			"ownerCanTransfer" => {358				limits.owner_can_transfer = Some(value);359			}360			"ownerCanDestroy" => {361				limits.owner_can_destroy = Some(value);362			}363			"transfersEnabled" => {364				limits.transfers_enabled = Some(value);365			}366			_ => {367				return Err(Error::Revert(format!(368					"unknown boolean limit \"{}\"",369					limit370				)))371			}372		}373		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)374			.map_err(dispatch_to_evm::<T>)?;375		save(self)376	}377378	/// Get contract address.379	fn contract_address(&self) -> Result<address> {380		Ok(crate::eth::collection_id_to_address(self.id))381	}382383	/// Add collection admin.384	/// @param newAdmin Cross account administrator address.385	fn add_collection_admin_cross(386		&mut self,387		caller: caller,388		new_admin: EthCrossAccount,389	) -> Result<void> {390		self.consume_store_writes(2)?;391392		let caller = T::CrossAccountId::from_eth(caller);393		let new_admin = new_admin.into_sub_cross_account::<T>()?;394		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;395		Ok(())396	}397398	/// Remove collection admin.399	/// @param admin Cross account administrator address.400	fn remove_collection_admin_cross(401		&mut self,402		caller: caller,403		admin: EthCrossAccount,404	) -> Result<void> {405		self.consume_store_writes(2)?;406407		let caller = T::CrossAccountId::from_eth(caller);408		let admin = admin.into_sub_cross_account::<T>()?;409		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;410		Ok(())411	}412413	/// Add collection admin.414	/// @param newAdmin Address of the added administrator.415	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {416		self.consume_store_writes(2)?;417418		let caller = T::CrossAccountId::from_eth(caller);419		let new_admin = T::CrossAccountId::from_eth(new_admin);420		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;421		Ok(())422	}423424	/// Remove collection admin.425	///426	/// @param admin Address of the removed administrator.427	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {428		self.consume_store_writes(2)?;429430		let caller = T::CrossAccountId::from_eth(caller);431		let admin = T::CrossAccountId::from_eth(admin);432		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;433		Ok(())434	}435436	/// Toggle accessibility of collection nesting.437	///438	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'439	#[solidity(rename_selector = "setCollectionNesting")]440	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {441		self.consume_store_reads_and_writes(1, 1)?;442443		check_is_owner_or_admin(caller, self)?;444445		let mut permissions = self.collection.permissions.clone();446		let mut nesting = permissions.nesting().clone();447		nesting.token_owner = enable;448		nesting.restricted = None;449		permissions.nesting = Some(nesting);450451		self.collection.permissions = <Pallet<T>>::clamp_permissions(452			self.collection.mode.clone(),453			&self.collection.permissions,454			permissions,455		)456		.map_err(dispatch_to_evm::<T>)?;457458		save(self)459	}460461	/// Toggle accessibility of collection nesting.462	///463	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'464	/// @param collections Addresses of collections that will be available for nesting.465	#[solidity(rename_selector = "setCollectionNesting")]466	fn set_nesting(467		&mut self,468		caller: caller,469		enable: bool,470		collections: Vec<address>,471	) -> Result<void> {472		self.consume_store_reads_and_writes(1, 1)?;473474		if collections.is_empty() {475			return Err("no addresses provided".into());476		}477		check_is_owner_or_admin(caller, self)?;478479		let mut permissions = self.collection.permissions.clone();480		match enable {481			false => {482				let mut nesting = permissions.nesting().clone();483				nesting.token_owner = false;484				nesting.restricted = None;485				permissions.nesting = Some(nesting);486			}487			true => {488				let mut bv = OwnerRestrictedSet::new();489				for i in collections {490					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {491						Error::Revert("Can't convert address into collection id".into())492					})?)493					.map_err(|_| "too many collections")?;494				}495				let mut nesting = permissions.nesting().clone();496				nesting.token_owner = true;497				nesting.restricted = Some(bv);498				permissions.nesting = Some(nesting);499			}500		};501502		self.collection.permissions = <Pallet<T>>::clamp_permissions(503			self.collection.mode.clone(),504			&self.collection.permissions,505			permissions,506		)507		.map_err(dispatch_to_evm::<T>)?;508509		save(self)510	}511512	/// Set the collection access method.513	/// @param mode Access mode514	/// 	0 for Normal515	/// 	1 for AllowList516	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {517		self.consume_store_reads_and_writes(1, 1)?;518519		check_is_owner_or_admin(caller, self)?;520		let permissions = CollectionPermissions {521			access: Some(match mode {522				0 => AccessMode::Normal,523				1 => AccessMode::AllowList,524				_ => return Err("not supported access mode".into()),525			}),526			..Default::default()527		};528		self.collection.permissions = <Pallet<T>>::clamp_permissions(529			self.collection.mode.clone(),530			&self.collection.permissions,531			permissions,532		)533		.map_err(dispatch_to_evm::<T>)?;534535		save(self)536	}537538	/// Checks that user allowed to operate with collection.539	///540	/// @param user User address to check.541	fn allowed(&self, user: address) -> Result<bool> {542		Ok(Pallet::<T>::allowed(543			self.id,544			T::CrossAccountId::from_eth(user),545		))546	}547548	/// Add the user to the allowed list.549	///550	/// @param user Address of a trusted user.551	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {552		self.consume_store_writes(1)?;553554		let caller = T::CrossAccountId::from_eth(caller);555		let user = T::CrossAccountId::from_eth(user);556		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;557		Ok(())558	}559560	/// Add user to allowed list.561	///562	/// @param user User cross account address.563	fn add_to_collection_allow_list_cross(564		&mut self,565		caller: caller,566		user: EthCrossAccount,567	) -> Result<void> {568		self.consume_store_writes(1)?;569570		let caller = T::CrossAccountId::from_eth(caller);571		let user = user.into_sub_cross_account::<T>()?;572		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;573		Ok(())574	}575576	/// Remove the user from the allowed list.577	///578	/// @param user Address of a removed user.579	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {580		self.consume_store_writes(1)?;581582		let caller = T::CrossAccountId::from_eth(caller);583		let user = T::CrossAccountId::from_eth(user);584		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;585		Ok(())586	}587588	/// Remove user from allowed list.589	///590	/// @param user User cross account address.591	fn remove_from_collection_allow_list_cross(592		&mut self,593		caller: caller,594		user: EthCrossAccount,595	) -> Result<void> {596		self.consume_store_writes(1)?;597598		let caller = T::CrossAccountId::from_eth(caller);599		let user = user.into_sub_cross_account::<T>()?;600		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;601		Ok(())602	}603604	/// Switch permission for minting.605	///606	/// @param mode Enable if "true".607	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {608		self.consume_store_reads_and_writes(1, 1)?;609610		check_is_owner_or_admin(caller, self)?;611		let permissions = CollectionPermissions {612			mint_mode: Some(mode),613			..Default::default()614		};615		self.collection.permissions = <Pallet<T>>::clamp_permissions(616			self.collection.mode.clone(),617			&self.collection.permissions,618			permissions,619		)620		.map_err(dispatch_to_evm::<T>)?;621622		save(self)623	}624625	/// Check that account is the owner or admin of the collection626	///627	/// @param user account to verify628	/// @return "true" if account is the owner or admin629	#[solidity(rename_selector = "isOwnerOrAdmin")]630	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {631		let user = T::CrossAccountId::from_eth(user);632		Ok(self.is_owner_or_admin(&user))633	}634635	/// Check that account is the owner or admin of the collection636	///637	/// @param user User cross account to verify638	/// @return "true" if account is the owner or admin639	fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {640		let user = user.into_sub_cross_account::<T>()?;641		Ok(self.is_owner_or_admin(&user))642	}643644	/// Returns collection type645	///646	/// @return `Fungible` or `NFT` or `ReFungible`647	fn unique_collection_type(&self) -> Result<string> {648		let mode = match self.collection.mode {649			CollectionMode::Fungible(_) => "Fungible",650			CollectionMode::NFT => "NFT",651			CollectionMode::ReFungible => "ReFungible",652		};653		Ok(mode.into())654	}655656	/// Get collection owner.657	///658	/// @return Tuble with sponsor address and his substrate mirror.659	/// If address is canonical then substrate mirror is zero and vice versa.660	fn collection_owner(&self) -> Result<EthCrossAccount> {661		Ok(EthCrossAccount::from_sub_cross_account::<T>(662			&T::CrossAccountId::from_sub(self.owner.clone()),663		))664	}665666	/// Changes collection owner to another account667	///668	/// @dev Owner can be changed only by current owner669	/// @param newOwner new owner account670	#[solidity(rename_selector = "changeCollectionOwner")]671	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {672		self.consume_store_writes(1)?;673674		let caller = T::CrossAccountId::from_eth(caller);675		let new_owner = T::CrossAccountId::from_eth(new_owner);676		self.set_owner_internal(caller, new_owner)677			.map_err(dispatch_to_evm::<T>)678	}679680	/// Get collection administrators681	///682	/// @return Vector of tuples with admins address and his substrate mirror.683	/// If address is canonical then substrate mirror is zero and vice versa.684	fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {685		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))686			.map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))687			.collect();688		Ok(result)689	}690691	/// Changes collection owner to another account692	///693	/// @dev Owner can be changed only by current owner694	/// @param newOwner new owner cross account695	fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {696		self.consume_store_writes(1)?;697698		let caller = T::CrossAccountId::from_eth(caller);699		let new_owner = new_owner.into_sub_cross_account::<T>()?;700		self.set_owner_internal(caller, new_owner)701			.map_err(dispatch_to_evm::<T>)702	}703}704705/// ### Note706/// Do not forget to add: `self.consume_store_reads(1)?;`707fn check_is_owner_or_admin<T: Config>(708	caller: caller,709	collection: &CollectionHandle<T>,710) -> Result<T::CrossAccountId> {711	let caller = T::CrossAccountId::from_eth(caller);712	collection713		.check_is_owner_or_admin(&caller)714		.map_err(dispatch_to_evm::<T>)?;715	Ok(caller)716}717718/// ### Note719/// Do not forget to add: `self.consume_store_writes(1)?;`720fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {721	collection722		.check_is_internal()723		.map_err(dispatch_to_evm::<T>)?;724	collection.save().map_err(dispatch_to_evm::<T>)?;725	Ok(())726}727728/// Contains static property keys and values.729pub mod static_property {730	use evm_coder::{731		execution::{Result, Error},732	};733	use alloc::format;734735	const EXPECT_CONVERT_ERROR: &str = "length < limit";736737	/// Keys.738	pub mod key {739		use super::*;740741		/// Key "baseURI".742		pub fn base_uri() -> up_data_structs::PropertyKey {743			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)744		}745746		/// Key "url".747		pub fn url() -> up_data_structs::PropertyKey {748			property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)749		}750751		/// Key "suffix".752		pub fn suffix() -> up_data_structs::PropertyKey {753			property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)754		}755756		/// Key "parentNft".757		pub fn parent_nft() -> up_data_structs::PropertyKey {758			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)759		}760	}761762	/// Convert `byte` to [`PropertyKey`].763	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {764		bytes.to_vec().try_into().map_err(|_| {765			Error::Revert(format!(766				"Property key is too long. Max length is {}.",767				up_data_structs::PropertyKey::bound()768			))769		})770	}771772	/// Convert `bytes` to [`PropertyValue`].773	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {774		bytes.to_vec().try_into().map_err(|_| {775			Error::Revert(format!(776				"Property key is too long. Max length is {}.",777				up_data_structs::PropertyKey::bound()778			))779		})780	}781}
after · 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	solidity_interface, solidity, ToLog,21	types::*,22	execution::{Result, Error},23	weight,24	custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},25	make_signature,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	const CODE: &'static [u8];6667	/// Call precompiled handle.68	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;69}7071/// @title A contract that allows you to work with collections.72#[solidity_interface(name = Collection)]73impl<T: Config> CollectionHandle<T>74where75	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,76{77	/// Set collection property.78	///79	/// @param key Property key.80	/// @param value Propery value.81	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]82	fn set_collection_property(83		&mut self,84		caller: caller,85		key: string,86		value: bytes,87	) -> Result<void> {88		let caller = T::CrossAccountId::from_eth(caller);89		let key = <Vec<u8>>::from(key)90			.try_into()91			.map_err(|_| "key too large")?;92		let value = value.0.try_into().map_err(|_| "value too large")?;9394		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })95			.map_err(dispatch_to_evm::<T>)96	}9798	/// Set collection properties.99	///100	/// @param properties Vector of properties key/value pair.101	#[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]102	fn set_collection_properties(103		&mut self,104		caller: caller,105		properties: Vec<(string, bytes)>,106	) -> Result<void> {107		let caller = T::CrossAccountId::from_eth(caller);108109		let properties = properties110			.into_iter()111			.map(|(key, value)| {112				let key = <Vec<u8>>::from(key)113					.try_into()114					.map_err(|_| "key too large")?;115116				let value = value.0.try_into().map_err(|_| "value too large")?;117118				Ok(Property { key, value })119			})120			.collect::<Result<Vec<_>>>()?;121122		<Pallet<T>>::set_collection_properties(self, &caller, properties)123			.map_err(dispatch_to_evm::<T>)124	}125126	/// Delete collection property.127	///128	/// @param key Property key.129	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]130	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {131		let caller = T::CrossAccountId::from_eth(caller);132		let key = <Vec<u8>>::from(key)133			.try_into()134			.map_err(|_| "key too large")?;135136		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)137	}138139	/// Delete collection properties.140	///141	/// @param keys Properties keys.142	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]143	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {144		let caller = T::CrossAccountId::from_eth(caller);145		let keys = keys146			.into_iter()147			.map(|key| {148				<Vec<u8>>::from(key)149					.try_into()150					.map_err(|_| Error::Revert("key too large".into()))151			})152			.collect::<Result<Vec<_>>>()?;153154		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)155	}156157	/// Get collection property.158	///159	/// @dev Throws error if key not found.160	///161	/// @param key Property key.162	/// @return bytes The property corresponding to the key.163	fn collection_property(&self, key: string) -> Result<bytes> {164		let key = <Vec<u8>>::from(key)165			.try_into()166			.map_err(|_| "key too large")?;167168		let props = CollectionProperties::<T>::get(self.id);169		let prop = props.get(&key).ok_or("key not found")?;170171		Ok(bytes(prop.to_vec()))172	}173174	/// Get collection properties.175	///176	/// @param keys Properties keys. Empty keys for all propertyes.177	/// @return Vector of properties key/value pairs.178	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {179		let keys = keys180			.into_iter()181			.map(|key| {182				<Vec<u8>>::from(key)183					.try_into()184					.map_err(|_| Error::Revert("key too large".into()))185			})186			.collect::<Result<Vec<_>>>()?;187188		let properties = Pallet::<T>::filter_collection_properties(189			self.id,190			if keys.is_empty() { None } else { Some(keys) },191		)192		.map_err(dispatch_to_evm::<T>)?;193194		let properties = properties195			.into_iter()196			.map(|p| {197				let key =198					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;199				let value = bytes(p.value.to_vec());200				Ok((key, value))201			})202			.collect::<Result<Vec<_>>>()?;203		Ok(properties)204	}205206	/// Set the sponsor of the collection.207	///208	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.209	///210	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.211	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {212		self.consume_store_reads_and_writes(1, 1)?;213214		check_is_owner_or_admin(caller, self)?;215216		let sponsor = T::CrossAccountId::from_eth(sponsor);217		self.set_sponsor(sponsor.as_sub().clone())218			.map_err(dispatch_to_evm::<T>)?;219		save(self)220	}221222	/// Set the sponsor of the collection.223	///224	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.225	///226	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.227	fn set_collection_sponsor_cross(228		&mut self,229		caller: caller,230		sponsor: EthCrossAccount,231	) -> Result<void> {232		self.consume_store_reads_and_writes(1, 1)?;233234		check_is_owner_or_admin(caller, self)?;235236		let sponsor = sponsor.into_sub_cross_account::<T>()?;237		self.set_sponsor(sponsor.as_sub().clone())238			.map_err(dispatch_to_evm::<T>)?;239		save(self)240	}241242	/// Whether there is a pending sponsor.243	fn has_collection_pending_sponsor(&self) -> Result<bool> {244		Ok(matches!(245			self.collection.sponsorship,246			SponsorshipState::Unconfirmed(_)247		))248	}249250	/// Collection sponsorship confirmation.251	///252	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.253	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {254		self.consume_store_writes(1)?;255256		let caller = T::CrossAccountId::from_eth(caller);257		if !self258			.confirm_sponsorship(caller.as_sub())259			.map_err(dispatch_to_evm::<T>)?260		{261			return Err("caller is not set as sponsor".into());262		}263		save(self)264	}265266	/// Remove collection sponsor.267	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {268		self.consume_store_reads_and_writes(1, 1)?;269		check_is_owner_or_admin(caller, self)?;270		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;271		save(self)272	}273274	/// Get current sponsor.275	///276	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.277	fn collection_sponsor(&self) -> Result<(address, uint256)> {278		let sponsor = match self.collection.sponsorship.sponsor() {279			Some(sponsor) => sponsor,280			None => return Ok(Default::default()),281		};282		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());283		let result: (address, uint256) = if sponsor.is_canonical_substrate() {284			let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);285			(Default::default(), sponsor)286		} else {287			let sponsor = *sponsor.as_eth();288			(sponsor, Default::default())289		};290		Ok(result)291	}292293	/// Set limits for the collection.294	/// @dev Throws error if limit not found.295	/// @param limit Name of the limit. Valid names:296	/// 	"accountTokenOwnershipLimit",297	/// 	"sponsoredDataSize",298	/// 	"sponsoredDataRateLimit",299	/// 	"tokenLimit",300	/// 	"sponsorTransferTimeout",301	/// 	"sponsorApproveTimeout"302	/// @param value Value of the limit.303	#[solidity(rename_selector = "setCollectionLimit")]304	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {305		self.consume_store_reads_and_writes(1, 1)?;306307		check_is_owner_or_admin(caller, self)?;308		let mut limits = self.limits.clone();309310		match limit.as_str() {311			"accountTokenOwnershipLimit" => {312				limits.account_token_ownership_limit = Some(value);313			}314			"sponsoredDataSize" => {315				limits.sponsored_data_size = Some(value);316			}317			"sponsoredDataRateLimit" => {318				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));319			}320			"tokenLimit" => {321				limits.token_limit = Some(value);322			}323			"sponsorTransferTimeout" => {324				limits.sponsor_transfer_timeout = Some(value);325			}326			"sponsorApproveTimeout" => {327				limits.sponsor_approve_timeout = Some(value);328			}329			_ => {330				return Err(Error::Revert(format!(331					"unknown integer limit \"{}\"",332					limit333				)))334			}335		}336		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)337			.map_err(dispatch_to_evm::<T>)?;338		save(self)339	}340341	/// Set limits for the collection.342	/// @dev Throws error if limit not found.343	/// @param limit Name of the limit. Valid names:344	/// 	"ownerCanTransfer",345	/// 	"ownerCanDestroy",346	/// 	"transfersEnabled"347	/// @param value Value of the limit.348	#[solidity(rename_selector = "setCollectionLimit")]349	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {350		self.consume_store_reads_and_writes(1, 1)?;351352		check_is_owner_or_admin(caller, self)?;353		let mut limits = self.limits.clone();354355		match limit.as_str() {356			"ownerCanTransfer" => {357				limits.owner_can_transfer = Some(value);358			}359			"ownerCanDestroy" => {360				limits.owner_can_destroy = Some(value);361			}362			"transfersEnabled" => {363				limits.transfers_enabled = Some(value);364			}365			_ => {366				return Err(Error::Revert(format!(367					"unknown boolean limit \"{}\"",368					limit369				)))370			}371		}372		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)373			.map_err(dispatch_to_evm::<T>)?;374		save(self)375	}376377	/// Get contract address.378	fn contract_address(&self) -> Result<address> {379		Ok(crate::eth::collection_id_to_address(self.id))380	}381382	/// Add collection admin.383	/// @param newAdmin Cross account administrator address.384	fn add_collection_admin_cross(385		&mut self,386		caller: caller,387		new_admin: EthCrossAccount,388	) -> Result<void> {389		self.consume_store_writes(2)?;390391		let caller = T::CrossAccountId::from_eth(caller);392		let new_admin = new_admin.into_sub_cross_account::<T>()?;393		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;394		Ok(())395	}396397	/// Remove collection admin.398	/// @param admin Cross account administrator address.399	fn remove_collection_admin_cross(400		&mut self,401		caller: caller,402		admin: EthCrossAccount,403	) -> Result<void> {404		self.consume_store_writes(2)?;405406		let caller = T::CrossAccountId::from_eth(caller);407		let admin = admin.into_sub_cross_account::<T>()?;408		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;409		Ok(())410	}411412	/// Add collection admin.413	/// @param newAdmin Address of the added administrator.414	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {415		self.consume_store_writes(2)?;416417		let caller = T::CrossAccountId::from_eth(caller);418		let new_admin = T::CrossAccountId::from_eth(new_admin);419		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;420		Ok(())421	}422423	/// Remove collection admin.424	///425	/// @param admin Address of the removed administrator.426	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {427		self.consume_store_writes(2)?;428429		let caller = T::CrossAccountId::from_eth(caller);430		let admin = T::CrossAccountId::from_eth(admin);431		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;432		Ok(())433	}434435	/// Toggle accessibility of collection nesting.436	///437	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'438	#[solidity(rename_selector = "setCollectionNesting")]439	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {440		self.consume_store_reads_and_writes(1, 1)?;441442		check_is_owner_or_admin(caller, self)?;443444		let mut permissions = self.collection.permissions.clone();445		let mut nesting = permissions.nesting().clone();446		nesting.token_owner = enable;447		nesting.restricted = None;448		permissions.nesting = Some(nesting);449450		self.collection.permissions = <Pallet<T>>::clamp_permissions(451			self.collection.mode.clone(),452			&self.collection.permissions,453			permissions,454		)455		.map_err(dispatch_to_evm::<T>)?;456457		save(self)458	}459460	/// Toggle accessibility of collection nesting.461	///462	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'463	/// @param collections Addresses of collections that will be available for nesting.464	#[solidity(rename_selector = "setCollectionNesting")]465	fn set_nesting(466		&mut self,467		caller: caller,468		enable: bool,469		collections: Vec<address>,470	) -> Result<void> {471		self.consume_store_reads_and_writes(1, 1)?;472473		if collections.is_empty() {474			return Err("no addresses provided".into());475		}476		check_is_owner_or_admin(caller, self)?;477478		let mut permissions = self.collection.permissions.clone();479		match enable {480			false => {481				let mut nesting = permissions.nesting().clone();482				nesting.token_owner = false;483				nesting.restricted = None;484				permissions.nesting = Some(nesting);485			}486			true => {487				let mut bv = OwnerRestrictedSet::new();488				for i in collections {489					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {490						Error::Revert("Can't convert address into collection id".into())491					})?)492					.map_err(|_| "too many collections")?;493				}494				let mut nesting = permissions.nesting().clone();495				nesting.token_owner = true;496				nesting.restricted = Some(bv);497				permissions.nesting = Some(nesting);498			}499		};500501		self.collection.permissions = <Pallet<T>>::clamp_permissions(502			self.collection.mode.clone(),503			&self.collection.permissions,504			permissions,505		)506		.map_err(dispatch_to_evm::<T>)?;507508		save(self)509	}510511	/// Set the collection access method.512	/// @param mode Access mode513	/// 	0 for Normal514	/// 	1 for AllowList515	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {516		self.consume_store_reads_and_writes(1, 1)?;517518		check_is_owner_or_admin(caller, self)?;519		let permissions = CollectionPermissions {520			access: Some(match mode {521				0 => AccessMode::Normal,522				1 => AccessMode::AllowList,523				_ => return Err("not supported access mode".into()),524			}),525			..Default::default()526		};527		self.collection.permissions = <Pallet<T>>::clamp_permissions(528			self.collection.mode.clone(),529			&self.collection.permissions,530			permissions,531		)532		.map_err(dispatch_to_evm::<T>)?;533534		save(self)535	}536537	/// Checks that user allowed to operate with collection.538	///539	/// @param user User address to check.540	fn allowed(&self, user: address) -> Result<bool> {541		Ok(Pallet::<T>::allowed(542			self.id,543			T::CrossAccountId::from_eth(user),544		))545	}546547	/// Add the user to the allowed list.548	///549	/// @param user Address of a trusted user.550	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {551		self.consume_store_writes(1)?;552553		let caller = T::CrossAccountId::from_eth(caller);554		let user = T::CrossAccountId::from_eth(user);555		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;556		Ok(())557	}558559	/// Add user to allowed list.560	///561	/// @param user User cross account address.562	fn add_to_collection_allow_list_cross(563		&mut self,564		caller: caller,565		user: EthCrossAccount,566	) -> Result<void> {567		self.consume_store_writes(1)?;568569		let caller = T::CrossAccountId::from_eth(caller);570		let user = user.into_sub_cross_account::<T>()?;571		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;572		Ok(())573	}574575	/// Remove the user from the allowed list.576	///577	/// @param user Address of a removed user.578	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {579		self.consume_store_writes(1)?;580581		let caller = T::CrossAccountId::from_eth(caller);582		let user = T::CrossAccountId::from_eth(user);583		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;584		Ok(())585	}586587	/// Remove user from allowed list.588	///589	/// @param user User cross account address.590	fn remove_from_collection_allow_list_cross(591		&mut self,592		caller: caller,593		user: EthCrossAccount,594	) -> Result<void> {595		self.consume_store_writes(1)?;596597		let caller = T::CrossAccountId::from_eth(caller);598		let user = user.into_sub_cross_account::<T>()?;599		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;600		Ok(())601	}602603	/// Switch permission for minting.604	///605	/// @param mode Enable if "true".606	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {607		self.consume_store_reads_and_writes(1, 1)?;608609		check_is_owner_or_admin(caller, self)?;610		let permissions = CollectionPermissions {611			mint_mode: Some(mode),612			..Default::default()613		};614		self.collection.permissions = <Pallet<T>>::clamp_permissions(615			self.collection.mode.clone(),616			&self.collection.permissions,617			permissions,618		)619		.map_err(dispatch_to_evm::<T>)?;620621		save(self)622	}623624	/// Check that account is the owner or admin of the collection625	///626	/// @param user account to verify627	/// @return "true" if account is the owner or admin628	#[solidity(rename_selector = "isOwnerOrAdmin")]629	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {630		let user = T::CrossAccountId::from_eth(user);631		Ok(self.is_owner_or_admin(&user))632	}633634	/// Check that account is the owner or admin of the collection635	///636	/// @param user User cross account to verify637	/// @return "true" if account is the owner or admin638	fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {639		let user = user.into_sub_cross_account::<T>()?;640		Ok(self.is_owner_or_admin(&user))641	}642643	/// Returns collection type644	///645	/// @return `Fungible` or `NFT` or `ReFungible`646	fn unique_collection_type(&self) -> Result<string> {647		let mode = match self.collection.mode {648			CollectionMode::Fungible(_) => "Fungible",649			CollectionMode::NFT => "NFT",650			CollectionMode::ReFungible => "ReFungible",651		};652		Ok(mode.into())653	}654655	/// Get collection owner.656	///657	/// @return Tuble with sponsor address and his substrate mirror.658	/// If address is canonical then substrate mirror is zero and vice versa.659	fn collection_owner(&self) -> Result<EthCrossAccount> {660		Ok(EthCrossAccount::from_sub_cross_account::<T>(661			&T::CrossAccountId::from_sub(self.owner.clone()),662		))663	}664665	/// Changes collection owner to another account666	///667	/// @dev Owner can be changed only by current owner668	/// @param newOwner new owner account669	#[solidity(rename_selector = "changeCollectionOwner")]670	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {671		self.consume_store_writes(1)?;672673		let caller = T::CrossAccountId::from_eth(caller);674		let new_owner = T::CrossAccountId::from_eth(new_owner);675		self.set_owner_internal(caller, new_owner)676			.map_err(dispatch_to_evm::<T>)677	}678679	/// Get collection administrators680	///681	/// @return Vector of tuples with admins address and his substrate mirror.682	/// If address is canonical then substrate mirror is zero and vice versa.683	fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {684		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))685			.map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))686			.collect();687		Ok(result)688	}689690	/// Changes collection owner to another account691	///692	/// @dev Owner can be changed only by current owner693	/// @param newOwner new owner cross account694	fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {695		self.consume_store_writes(1)?;696697		let caller = T::CrossAccountId::from_eth(caller);698		let new_owner = new_owner.into_sub_cross_account::<T>()?;699		self.set_owner_internal(caller, new_owner)700			.map_err(dispatch_to_evm::<T>)701	}702}703704/// ### Note705/// Do not forget to add: `self.consume_store_reads(1)?;`706fn check_is_owner_or_admin<T: Config>(707	caller: caller,708	collection: &CollectionHandle<T>,709) -> Result<T::CrossAccountId> {710	let caller = T::CrossAccountId::from_eth(caller);711	collection712		.check_is_owner_or_admin(&caller)713		.map_err(dispatch_to_evm::<T>)?;714	Ok(caller)715}716717/// ### Note718/// Do not forget to add: `self.consume_store_writes(1)?;`719fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {720	collection721		.check_is_internal()722		.map_err(dispatch_to_evm::<T>)?;723	collection.save().map_err(dispatch_to_evm::<T>)?;724	Ok(())725}726727/// Contains static property keys and values.728pub mod static_property {729	use evm_coder::{730		execution::{Result, Error},731	};732	use alloc::format;733734	const EXPECT_CONVERT_ERROR: &str = "length < limit";735736	/// Keys.737	pub mod key {738		use super::*;739740		/// Key "baseURI".741		pub fn base_uri() -> up_data_structs::PropertyKey {742			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)743		}744745		/// Key "url".746		pub fn url() -> up_data_structs::PropertyKey {747			property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)748		}749750		/// Key "suffix".751		pub fn suffix() -> up_data_structs::PropertyKey {752			property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)753		}754755		/// Key "parentNft".756		pub fn parent_nft() -> up_data_structs::PropertyKey {757			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)758		}759	}760761	/// Convert `byte` to [`PropertyKey`].762	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {763		bytes.to_vec().try_into().map_err(|_| {764			Error::Revert(format!(765				"Property key is too long. Max length is {}.",766				up_data_structs::PropertyKey::bound()767			))768		})769	}770771	/// Convert `bytes` to [`PropertyValue`].772	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {773		bytes.to_vec().try_into().map_err(|_| {774			Error::Revert(format!(775				"Property key is too long. Max length is {}.",776				up_data_structs::PropertyKey::bound()777			))778		})779	}780}