git.delta.rocks / unique-network / refs/commits / 9d78c86ffab5

difftreelog

fmt

Trubnikov Sergey2022-08-25parent: #2ba119d.patch.diff
in: master

3 files changed

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};24pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_std::vec::Vec;27use up_data_structs::{28	AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,29	SponsoringRateLimit, SponsorshipState,30};31use alloc::format;3233use crate::{34	Pallet, CollectionHandle, Config, CollectionProperties,35	eth::{convert_cross_account_to_uint256, convert_uint256_to_cross_account},36};3738/// Events for ethereum collection helper.39#[derive(ToLog)]40pub enum CollectionHelpersEvents {41	/// The collection has been created.42	CollectionCreated {43		/// Collection owner.44		#[indexed]45		owner: address,4647		/// Collection ID.48		#[indexed]49		collection_id: address,50	},51}5253/// Does not always represent a full collection, for RFT it is either54/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).55pub trait CommonEvmHandler {56	const CODE: &'static [u8];5758	/// Call precompiled handle.59	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;60}6162/// @title A contract that allows you to work with collections.63#[solidity_interface(name = Collection)]64impl<T: Config> CollectionHandle<T>65where66	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,67{68	/// Set collection property.69	///70	/// @param key Property key.71	/// @param value Propery value.72	fn set_collection_property(73		&mut self,74		caller: caller,75		key: string,76		value: bytes,77	) -> Result<void> {78		let caller = T::CrossAccountId::from_eth(caller);79		let key = <Vec<u8>>::from(key)80			.try_into()81			.map_err(|_| "key too large")?;82		let value = value.try_into().map_err(|_| "value too large")?;8384		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })85			.map_err(dispatch_to_evm::<T>)86	}8788	/// Delete collection property.89	///90	/// @param key Property key.91	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {92		let caller = T::CrossAccountId::from_eth(caller);93		let key = <Vec<u8>>::from(key)94			.try_into()95			.map_err(|_| "key too large")?;9697		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)98	}99100	/// Get collection property.101	///102	/// @dev Throws error if key not found.103	///104	/// @param key Property key.105	/// @return bytes The property corresponding to the key.106	fn collection_property(&self, key: string) -> Result<bytes> {107		let key = <Vec<u8>>::from(key)108			.try_into()109			.map_err(|_| "key too large")?;110111		let props = <CollectionProperties<T>>::get(self.id);112		let prop = props.get(&key).ok_or("key not found")?;113114		Ok(prop.to_vec())115	}116117	/// Set the sponsor of the collection.118	///119	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.120	///121	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.122	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {123		check_is_owner_or_admin(caller, self)?;124125		let sponsor = T::CrossAccountId::from_eth(sponsor);126		self.set_sponsor(sponsor.as_sub().clone())127			.map_err(dispatch_to_evm::<T>)?;128		save(self)129	}130131	/// Set the substrate sponsor of the collection.132	///133	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.134	///135	/// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.136	fn set_collection_sponsor_substrate(&mut self, caller: caller, sponsor: uint256) -> Result<void> {137		check_is_owner_or_admin(caller, self)?;138139		let sponsor = convert_uint256_to_cross_account::<T>(sponsor);140		self.set_sponsor(sponsor.as_sub().clone())141			.map_err(dispatch_to_evm::<T>)?;142		save(self)143	}144145	// /// Whether there is a pending sponsor.146	fn has_collection_pending_sponsor(&self) -> Result<bool> {147		Ok(matches!(self.collection.sponsorship, SponsorshipState::Unconfirmed(_)))148	}149150	/// Collection sponsorship confirmation.151	///152	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.153	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {154		let caller = T::CrossAccountId::from_eth(caller);155		if !self156			.confirm_sponsorship(caller.as_sub())157			.map_err(dispatch_to_evm::<T>)?158		{159			return Err("caller is not set as sponsor".into());160		}161		save(self)162	}163164	/// Remove collection sponsor.165	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {166		check_is_owner_or_admin(caller, self)?;167		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;168		save(self)169	}170171	/// Get current sponsor.172	///173	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.174	fn get_collection_sponsor(&self) -> Result<(address, uint256)> {175		let sponsor = match self.collection.sponsorship {176			SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => return Ok(Default::default()),177			SponsorshipState::Confirmed(ref sponsor) => sponsor,178		};179		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());180		let sponsor_sub = convert_cross_account_to_uint256::<T>(&sponsor);181		Ok((*sponsor.as_eth(), sponsor_sub))182	}183184	/// Set limits for the collection.185	/// @dev Throws error if limit not found.186	/// @param limit Name of the limit. Valid names:187	/// 	"accountTokenOwnershipLimit",188	/// 	"sponsoredDataSize",189	/// 	"sponsoredDataRateLimit",190	/// 	"tokenLimit",191	/// 	"sponsorTransferTimeout",192	/// 	"sponsorApproveTimeout"193	/// @param value Value of the limit.194	#[solidity(rename_selector = "setCollectionLimit")]195	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {196		check_is_owner_or_admin(caller, self)?;197		let mut limits = self.limits.clone();198199		match limit.as_str() {200			"accountTokenOwnershipLimit" => {201				limits.account_token_ownership_limit = Some(value);202			}203			"sponsoredDataSize" => {204				limits.sponsored_data_size = Some(value);205			}206			"sponsoredDataRateLimit" => {207				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));208			}209			"tokenLimit" => {210				limits.token_limit = Some(value);211			}212			"sponsorTransferTimeout" => {213				limits.sponsor_transfer_timeout = Some(value);214			}215			"sponsorApproveTimeout" => {216				limits.sponsor_approve_timeout = Some(value);217			}218			_ => {219				return Err(Error::Revert(format!(220					"unknown integer limit \"{}\"",221					limit222				)))223			}224		}225		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)226			.map_err(dispatch_to_evm::<T>)?;227		save(self)228	}229230	/// Set limits for the collection.231	/// @dev Throws error if limit not found.232	/// @param limit Name of the limit. Valid names:233	/// 	"ownerCanTransfer",234	/// 	"ownerCanDestroy",235	/// 	"transfersEnabled"236	/// @param value Value of the limit.237	#[solidity(rename_selector = "setCollectionLimit")]238	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {239		check_is_owner_or_admin(caller, self)?;240		let mut limits = self.limits.clone();241242		match limit.as_str() {243			"ownerCanTransfer" => {244				limits.owner_can_transfer = Some(value);245			}246			"ownerCanDestroy" => {247				limits.owner_can_destroy = Some(value);248			}249			"transfersEnabled" => {250				limits.transfers_enabled = Some(value);251			}252			_ => {253				return Err(Error::Revert(format!(254					"unknown boolean limit \"{}\"",255					limit256				)))257			}258		}259		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)260			.map_err(dispatch_to_evm::<T>)?;261		save(self)262	}263264	/// Get contract address.265	fn contract_address(&self, _caller: caller) -> Result<address> {266		Ok(crate::eth::collection_id_to_address(self.id))267	}268269	/// Add collection admin by substrate address.270	/// @param newAdmin Substrate administrator address.271	fn add_collection_admin_substrate(272		&mut self,273		caller: caller,274		new_admin: uint256,275	) -> Result<void> {276		let caller = T::CrossAccountId::from_eth(caller);277		let new_admin = convert_uint256_to_cross_account::<T>(new_admin);278		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;279		Ok(())280	}281282	/// Remove collection admin by substrate address.283	/// @param admin Substrate administrator address.284	fn remove_collection_admin_substrate(285		&mut self,286		caller: caller,287		admin: uint256,288	) -> Result<void> {289		let caller = T::CrossAccountId::from_eth(caller);290		let admin = convert_uint256_to_cross_account::<T>(admin);291		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;292		Ok(())293	}294295	/// Add collection admin.296	/// @param newAdmin Address of the added administrator.297	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {298		let caller = T::CrossAccountId::from_eth(caller);299		let new_admin = T::CrossAccountId::from_eth(new_admin);300		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;301		Ok(())302	}303304	/// Remove collection admin.305	///306	/// @param admin Address of the removed administrator.307	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {308		let caller = T::CrossAccountId::from_eth(caller);309		let admin = T::CrossAccountId::from_eth(admin);310		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;311		Ok(())312	}313314	/// Toggle accessibility of collection nesting.315	///316	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'317	#[solidity(rename_selector = "setCollectionNesting")]318	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {319		check_is_owner_or_admin(caller, self)?;320321		let mut permissions = self.collection.permissions.clone();322		let mut nesting = permissions.nesting().clone();323		nesting.token_owner = enable;324		nesting.restricted = None;325		permissions.nesting = Some(nesting);326327		self.collection.permissions = <Pallet<T>>::clamp_permissions(328			self.collection.mode.clone(),329			&self.collection.permissions,330			permissions,331		)332		.map_err(dispatch_to_evm::<T>)?;333334		save(self)335	}336337	/// Toggle accessibility of collection nesting.338	///339	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'340	/// @param collections Addresses of collections that will be available for nesting.341	#[solidity(rename_selector = "setCollectionNesting")]342	fn set_nesting(343		&mut self,344		caller: caller,345		enable: bool,346		collections: Vec<address>,347	) -> Result<void> {348		if collections.is_empty() {349			return Err("no addresses provided".into());350		}351		check_is_owner_or_admin(caller, self)?;352353		let mut permissions = self.collection.permissions.clone();354		match enable {355			false => {356				let mut nesting = permissions.nesting().clone();357				nesting.token_owner = false;358				nesting.restricted = None;359				permissions.nesting = Some(nesting);360			}361			true => {362				let mut bv = OwnerRestrictedSet::new();363				for i in collections {364					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(365						"Can't convert address into collection id".into(),366					))?)367					.map_err(|_| "too many collections")?;368				}369				let mut nesting = permissions.nesting().clone();370				nesting.token_owner = true;371				nesting.restricted = Some(bv);372				permissions.nesting = Some(nesting);373			}374		};375376		self.collection.permissions = <Pallet<T>>::clamp_permissions(377			self.collection.mode.clone(),378			&self.collection.permissions,379			permissions,380		)381		.map_err(dispatch_to_evm::<T>)?;382383		save(self)384	}385386	/// Set the collection access method.387	/// @param mode Access mode388	/// 	0 for Normal389	/// 	1 for AllowList390	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {391		check_is_owner_or_admin(caller, self)?;392		let permissions = CollectionPermissions {393			access: Some(match mode {394				0 => AccessMode::Normal,395				1 => AccessMode::AllowList,396				_ => return Err("not supported access mode".into()),397			}),398			..Default::default()399		};400		self.collection.permissions = <Pallet<T>>::clamp_permissions(401			self.collection.mode.clone(),402			&self.collection.permissions,403			permissions,404		)405		.map_err(dispatch_to_evm::<T>)?;406407		save(self)408	}409410	/// Add the user to the allowed list.411	///412	/// @param user Address of a trusted user.413	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {414		let caller = T::CrossAccountId::from_eth(caller);415		let user = T::CrossAccountId::from_eth(user);416		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;417		Ok(())418	}419420	/// Remove the user from the allowed list.421	///422	/// @param user Address of a removed user.423	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {424		let caller = T::CrossAccountId::from_eth(caller);425		let user = T::CrossAccountId::from_eth(user);426		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;427		Ok(())428	}429430	/// Switch permission for minting.431	///432	/// @param mode Enable if "true".433	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {434		check_is_owner_or_admin(caller, self)?;435		let permissions = CollectionPermissions {436			mint_mode: Some(mode),437			..Default::default()438		};439		self.collection.permissions = <Pallet<T>>::clamp_permissions(440			self.collection.mode.clone(),441			&self.collection.permissions,442			permissions,443		)444		.map_err(dispatch_to_evm::<T>)?;445446		save(self)447	}448449	/// Check that account is the owner or admin of the collection450	///451	/// @param user account to verify452	/// @return "true" if account is the owner or admin453	#[solidity(rename_selector = "isOwnerOrAdmin")]454	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {455		let user = T::CrossAccountId::from_eth(user);456		Ok(self.is_owner_or_admin(&user))457	}458459	/// Check that substrate account is the owner or admin of the collection460	///461	/// @param user account to verify462	/// @return "true" if account is the owner or admin463	fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {464		let user = convert_uint256_to_cross_account::<T>(user);465		Ok(self.is_owner_or_admin(&user))466	}467468	/// Returns collection type469	///470	/// @return `Fungible` or `NFT` or `ReFungible`471	fn unique_collection_type(&mut self) -> Result<string> {472		let mode = match self.collection.mode {473			CollectionMode::Fungible(_) => "Fungible",474			CollectionMode::NFT => "NFT",475			CollectionMode::ReFungible => "ReFungible",476		};477		Ok(mode.into())478	}479480	/// Changes collection owner to another account481	///482	/// @dev Owner can be changed only by current owner483	/// @param newOwner new owner account484	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {485		let caller = T::CrossAccountId::from_eth(caller);486		let new_owner = T::CrossAccountId::from_eth(new_owner);487		self.set_owner_internal(caller, new_owner)488			.map_err(dispatch_to_evm::<T>)489	}490491	/// Changes collection owner to another substrate account492	///493	/// @dev Owner can be changed only by current owner494	/// @param newOwner new owner substrate account495	fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {496		let caller = T::CrossAccountId::from_eth(caller);497		let new_owner = convert_uint256_to_cross_account::<T>(new_owner);498		self.set_owner_internal(caller, new_owner)499			.map_err(dispatch_to_evm::<T>)500	}501}502503fn check_is_owner_or_admin<T: Config>(504	caller: caller,505	collection: &CollectionHandle<T>,506) -> Result<T::CrossAccountId> {507	let caller = T::CrossAccountId::from_eth(caller);508	collection509		.check_is_owner_or_admin(&caller)510		.map_err(dispatch_to_evm::<T>)?;511	Ok(caller)512}513514fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {515	// TODO possibly delete for the lack of transaction516	collection.consume_store_writes(1)?;517	collection518		.check_is_internal()519		.map_err(dispatch_to_evm::<T>)?;520	collection.save().map_err(dispatch_to_evm::<T>)?;521	Ok(())522}523524/// Contains static property keys and values.525pub mod static_property {526	use evm_coder::{527		execution::{Result, Error},528	};529	use alloc::format;530531	const EXPECT_CONVERT_ERROR: &str = "length < limit";532533	/// Keys.534	pub mod key {535		use super::*;536537		/// Key "schemaName".538		pub fn schema_name() -> up_data_structs::PropertyKey {539			property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)540		}541542		/// Key "baseURI".543		pub fn base_uri() -> up_data_structs::PropertyKey {544			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)545		}546547		/// Key "url".548		pub fn url() -> up_data_structs::PropertyKey {549			property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)550		}551552		/// Key "suffix".553		pub fn suffix() -> up_data_structs::PropertyKey {554			property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)555		}556557		/// Key "parentNft".558		pub fn parent_nft() -> up_data_structs::PropertyKey {559			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)560		}561	}562563	/// Values.564	pub mod value {565		use super::*;566567		/// Value "ERC721Metadata".568		pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";569570		/// Value for [`ERC721_METADATA`].571		pub fn erc721() -> up_data_structs::PropertyValue {572			property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)573		}574	}575576	/// Convert `byte` to [`PropertyKey`].577	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {578		bytes.to_vec().try_into().map_err(|_| {579			Error::Revert(format!(580				"Property key is too long. Max length is {}.",581				up_data_structs::PropertyKey::bound()582			))583		})584	}585586	/// Convert `bytes` to [`PropertyValue`].587	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {588		bytes.to_vec().try_into().map_err(|_| {589			Error::Revert(format!(590				"Property key is too long. Max length is {}.",591				up_data_structs::PropertyKey::bound()592			))593		})594	}595}
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};24pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_std::vec::Vec;27use up_data_structs::{28	AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,29	SponsoringRateLimit, SponsorshipState,30};31use alloc::format;3233use crate::{34	Pallet, CollectionHandle, Config, CollectionProperties,35	eth::{convert_cross_account_to_uint256, convert_uint256_to_cross_account},36};3738/// Events for ethereum collection helper.39#[derive(ToLog)]40pub enum CollectionHelpersEvents {41	/// The collection has been created.42	CollectionCreated {43		/// Collection owner.44		#[indexed]45		owner: address,4647		/// Collection ID.48		#[indexed]49		collection_id: address,50	},51}5253/// Does not always represent a full collection, for RFT it is either54/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).55pub trait CommonEvmHandler {56	const CODE: &'static [u8];5758	/// Call precompiled handle.59	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;60}6162/// @title A contract that allows you to work with collections.63#[solidity_interface(name = Collection)]64impl<T: Config> CollectionHandle<T>65where66	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,67{68	/// Set collection property.69	///70	/// @param key Property key.71	/// @param value Propery value.72	fn set_collection_property(73		&mut self,74		caller: caller,75		key: string,76		value: bytes,77	) -> Result<void> {78		let caller = T::CrossAccountId::from_eth(caller);79		let key = <Vec<u8>>::from(key)80			.try_into()81			.map_err(|_| "key too large")?;82		let value = value.try_into().map_err(|_| "value too large")?;8384		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })85			.map_err(dispatch_to_evm::<T>)86	}8788	/// Delete collection property.89	///90	/// @param key Property key.91	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {92		let caller = T::CrossAccountId::from_eth(caller);93		let key = <Vec<u8>>::from(key)94			.try_into()95			.map_err(|_| "key too large")?;9697		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)98	}99100	/// Get collection property.101	///102	/// @dev Throws error if key not found.103	///104	/// @param key Property key.105	/// @return bytes The property corresponding to the key.106	fn collection_property(&self, key: string) -> Result<bytes> {107		let key = <Vec<u8>>::from(key)108			.try_into()109			.map_err(|_| "key too large")?;110111		let props = <CollectionProperties<T>>::get(self.id);112		let prop = props.get(&key).ok_or("key not found")?;113114		Ok(prop.to_vec())115	}116117	/// Set the sponsor of the collection.118	///119	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.120	///121	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.122	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {123		check_is_owner_or_admin(caller, self)?;124125		let sponsor = T::CrossAccountId::from_eth(sponsor);126		self.set_sponsor(sponsor.as_sub().clone())127			.map_err(dispatch_to_evm::<T>)?;128		save(self)129	}130131	/// Set the substrate sponsor of the collection.132	///133	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.134	///135	/// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.136	fn set_collection_sponsor_substrate(137		&mut self,138		caller: caller,139		sponsor: uint256,140	) -> Result<void> {141		check_is_owner_or_admin(caller, self)?;142143		let sponsor = convert_uint256_to_cross_account::<T>(sponsor);144		self.set_sponsor(sponsor.as_sub().clone())145			.map_err(dispatch_to_evm::<T>)?;146		save(self)147	}148149	// /// Whether there is a pending sponsor.150	fn has_collection_pending_sponsor(&self) -> Result<bool> {151		Ok(matches!(152			self.collection.sponsorship,153			SponsorshipState::Unconfirmed(_)154		))155	}156157	/// Collection sponsorship confirmation.158	///159	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.160	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {161		let caller = T::CrossAccountId::from_eth(caller);162		if !self163			.confirm_sponsorship(caller.as_sub())164			.map_err(dispatch_to_evm::<T>)?165		{166			return Err("caller is not set as sponsor".into());167		}168		save(self)169	}170171	/// Remove collection sponsor.172	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {173		check_is_owner_or_admin(caller, self)?;174		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;175		save(self)176	}177178	/// Get current sponsor.179	///180	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.181	fn get_collection_sponsor(&self) -> Result<(address, uint256)> {182		let sponsor = match self.collection.sponsorship {183			SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => {184				return Ok(Default::default())185			}186			SponsorshipState::Confirmed(ref sponsor) => sponsor,187		};188		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());189		let sponsor_sub = convert_cross_account_to_uint256::<T>(&sponsor);190		Ok((*sponsor.as_eth(), sponsor_sub))191	}192193	/// Set limits for the collection.194	/// @dev Throws error if limit not found.195	/// @param limit Name of the limit. Valid names:196	/// 	"accountTokenOwnershipLimit",197	/// 	"sponsoredDataSize",198	/// 	"sponsoredDataRateLimit",199	/// 	"tokenLimit",200	/// 	"sponsorTransferTimeout",201	/// 	"sponsorApproveTimeout"202	/// @param value Value of the limit.203	#[solidity(rename_selector = "setCollectionLimit")]204	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {205		check_is_owner_or_admin(caller, self)?;206		let mut limits = self.limits.clone();207208		match limit.as_str() {209			"accountTokenOwnershipLimit" => {210				limits.account_token_ownership_limit = Some(value);211			}212			"sponsoredDataSize" => {213				limits.sponsored_data_size = Some(value);214			}215			"sponsoredDataRateLimit" => {216				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));217			}218			"tokenLimit" => {219				limits.token_limit = Some(value);220			}221			"sponsorTransferTimeout" => {222				limits.sponsor_transfer_timeout = Some(value);223			}224			"sponsorApproveTimeout" => {225				limits.sponsor_approve_timeout = Some(value);226			}227			_ => {228				return Err(Error::Revert(format!(229					"unknown integer limit \"{}\"",230					limit231				)))232			}233		}234		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)235			.map_err(dispatch_to_evm::<T>)?;236		save(self)237	}238239	/// Set limits for the collection.240	/// @dev Throws error if limit not found.241	/// @param limit Name of the limit. Valid names:242	/// 	"ownerCanTransfer",243	/// 	"ownerCanDestroy",244	/// 	"transfersEnabled"245	/// @param value Value of the limit.246	#[solidity(rename_selector = "setCollectionLimit")]247	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {248		check_is_owner_or_admin(caller, self)?;249		let mut limits = self.limits.clone();250251		match limit.as_str() {252			"ownerCanTransfer" => {253				limits.owner_can_transfer = Some(value);254			}255			"ownerCanDestroy" => {256				limits.owner_can_destroy = Some(value);257			}258			"transfersEnabled" => {259				limits.transfers_enabled = Some(value);260			}261			_ => {262				return Err(Error::Revert(format!(263					"unknown boolean limit \"{}\"",264					limit265				)))266			}267		}268		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)269			.map_err(dispatch_to_evm::<T>)?;270		save(self)271	}272273	/// Get contract address.274	fn contract_address(&self, _caller: caller) -> Result<address> {275		Ok(crate::eth::collection_id_to_address(self.id))276	}277278	/// Add collection admin by substrate address.279	/// @param newAdmin Substrate administrator address.280	fn add_collection_admin_substrate(281		&mut self,282		caller: caller,283		new_admin: uint256,284	) -> Result<void> {285		let caller = T::CrossAccountId::from_eth(caller);286		let new_admin = convert_uint256_to_cross_account::<T>(new_admin);287		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;288		Ok(())289	}290291	/// Remove collection admin by substrate address.292	/// @param admin Substrate administrator address.293	fn remove_collection_admin_substrate(294		&mut self,295		caller: caller,296		admin: uint256,297	) -> Result<void> {298		let caller = T::CrossAccountId::from_eth(caller);299		let admin = convert_uint256_to_cross_account::<T>(admin);300		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;301		Ok(())302	}303304	/// Add collection admin.305	/// @param newAdmin Address of the added administrator.306	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {307		let caller = T::CrossAccountId::from_eth(caller);308		let new_admin = T::CrossAccountId::from_eth(new_admin);309		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;310		Ok(())311	}312313	/// Remove collection admin.314	///315	/// @param admin Address of the removed administrator.316	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {317		let caller = T::CrossAccountId::from_eth(caller);318		let admin = T::CrossAccountId::from_eth(admin);319		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;320		Ok(())321	}322323	/// Toggle accessibility of collection nesting.324	///325	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'326	#[solidity(rename_selector = "setCollectionNesting")]327	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {328		check_is_owner_or_admin(caller, self)?;329330		let mut permissions = self.collection.permissions.clone();331		let mut nesting = permissions.nesting().clone();332		nesting.token_owner = enable;333		nesting.restricted = None;334		permissions.nesting = Some(nesting);335336		self.collection.permissions = <Pallet<T>>::clamp_permissions(337			self.collection.mode.clone(),338			&self.collection.permissions,339			permissions,340		)341		.map_err(dispatch_to_evm::<T>)?;342343		save(self)344	}345346	/// Toggle accessibility of collection nesting.347	///348	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'349	/// @param collections Addresses of collections that will be available for nesting.350	#[solidity(rename_selector = "setCollectionNesting")]351	fn set_nesting(352		&mut self,353		caller: caller,354		enable: bool,355		collections: Vec<address>,356	) -> Result<void> {357		if collections.is_empty() {358			return Err("no addresses provided".into());359		}360		check_is_owner_or_admin(caller, self)?;361362		let mut permissions = self.collection.permissions.clone();363		match enable {364			false => {365				let mut nesting = permissions.nesting().clone();366				nesting.token_owner = false;367				nesting.restricted = None;368				permissions.nesting = Some(nesting);369			}370			true => {371				let mut bv = OwnerRestrictedSet::new();372				for i in collections {373					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(374						"Can't convert address into collection id".into(),375					))?)376					.map_err(|_| "too many collections")?;377				}378				let mut nesting = permissions.nesting().clone();379				nesting.token_owner = true;380				nesting.restricted = Some(bv);381				permissions.nesting = Some(nesting);382			}383		};384385		self.collection.permissions = <Pallet<T>>::clamp_permissions(386			self.collection.mode.clone(),387			&self.collection.permissions,388			permissions,389		)390		.map_err(dispatch_to_evm::<T>)?;391392		save(self)393	}394395	/// Set the collection access method.396	/// @param mode Access mode397	/// 	0 for Normal398	/// 	1 for AllowList399	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {400		check_is_owner_or_admin(caller, self)?;401		let permissions = CollectionPermissions {402			access: Some(match mode {403				0 => AccessMode::Normal,404				1 => AccessMode::AllowList,405				_ => return Err("not supported access mode".into()),406			}),407			..Default::default()408		};409		self.collection.permissions = <Pallet<T>>::clamp_permissions(410			self.collection.mode.clone(),411			&self.collection.permissions,412			permissions,413		)414		.map_err(dispatch_to_evm::<T>)?;415416		save(self)417	}418419	/// Add the user to the allowed list.420	///421	/// @param user Address of a trusted user.422	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {423		let caller = T::CrossAccountId::from_eth(caller);424		let user = T::CrossAccountId::from_eth(user);425		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;426		Ok(())427	}428429	/// Remove the user from the allowed list.430	///431	/// @param user Address of a removed user.432	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {433		let caller = T::CrossAccountId::from_eth(caller);434		let user = T::CrossAccountId::from_eth(user);435		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;436		Ok(())437	}438439	/// Switch permission for minting.440	///441	/// @param mode Enable if "true".442	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {443		check_is_owner_or_admin(caller, self)?;444		let permissions = CollectionPermissions {445			mint_mode: Some(mode),446			..Default::default()447		};448		self.collection.permissions = <Pallet<T>>::clamp_permissions(449			self.collection.mode.clone(),450			&self.collection.permissions,451			permissions,452		)453		.map_err(dispatch_to_evm::<T>)?;454455		save(self)456	}457458	/// Check that account is the owner or admin of the collection459	///460	/// @param user account to verify461	/// @return "true" if account is the owner or admin462	#[solidity(rename_selector = "isOwnerOrAdmin")]463	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {464		let user = T::CrossAccountId::from_eth(user);465		Ok(self.is_owner_or_admin(&user))466	}467468	/// Check that substrate account is the owner or admin of the collection469	///470	/// @param user account to verify471	/// @return "true" if account is the owner or admin472	fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {473		let user = convert_uint256_to_cross_account::<T>(user);474		Ok(self.is_owner_or_admin(&user))475	}476477	/// Returns collection type478	///479	/// @return `Fungible` or `NFT` or `ReFungible`480	fn unique_collection_type(&mut self) -> Result<string> {481		let mode = match self.collection.mode {482			CollectionMode::Fungible(_) => "Fungible",483			CollectionMode::NFT => "NFT",484			CollectionMode::ReFungible => "ReFungible",485		};486		Ok(mode.into())487	}488489	/// Changes collection owner to another account490	///491	/// @dev Owner can be changed only by current owner492	/// @param newOwner new owner account493	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {494		let caller = T::CrossAccountId::from_eth(caller);495		let new_owner = T::CrossAccountId::from_eth(new_owner);496		self.set_owner_internal(caller, new_owner)497			.map_err(dispatch_to_evm::<T>)498	}499500	/// Changes collection owner to another substrate account501	///502	/// @dev Owner can be changed only by current owner503	/// @param newOwner new owner substrate account504	fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {505		let caller = T::CrossAccountId::from_eth(caller);506		let new_owner = convert_uint256_to_cross_account::<T>(new_owner);507		self.set_owner_internal(caller, new_owner)508			.map_err(dispatch_to_evm::<T>)509	}510}511512fn check_is_owner_or_admin<T: Config>(513	caller: caller,514	collection: &CollectionHandle<T>,515) -> Result<T::CrossAccountId> {516	let caller = T::CrossAccountId::from_eth(caller);517	collection518		.check_is_owner_or_admin(&caller)519		.map_err(dispatch_to_evm::<T>)?;520	Ok(caller)521}522523fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {524	// TODO possibly delete for the lack of transaction525	collection.consume_store_writes(1)?;526	collection527		.check_is_internal()528		.map_err(dispatch_to_evm::<T>)?;529	collection.save().map_err(dispatch_to_evm::<T>)?;530	Ok(())531}532533/// Contains static property keys and values.534pub mod static_property {535	use evm_coder::{536		execution::{Result, Error},537	};538	use alloc::format;539540	const EXPECT_CONVERT_ERROR: &str = "length < limit";541542	/// Keys.543	pub mod key {544		use super::*;545546		/// Key "schemaName".547		pub fn schema_name() -> up_data_structs::PropertyKey {548			property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)549		}550551		/// Key "baseURI".552		pub fn base_uri() -> up_data_structs::PropertyKey {553			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)554		}555556		/// Key "url".557		pub fn url() -> up_data_structs::PropertyKey {558			property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)559		}560561		/// Key "suffix".562		pub fn suffix() -> up_data_structs::PropertyKey {563			property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)564		}565566		/// Key "parentNft".567		pub fn parent_nft() -> up_data_structs::PropertyKey {568			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)569		}570	}571572	/// Values.573	pub mod value {574		use super::*;575576		/// Value "ERC721Metadata".577		pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";578579		/// Value for [`ERC721_METADATA`].580		pub fn erc721() -> up_data_structs::PropertyValue {581			property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)582		}583	}584585	/// Convert `byte` to [`PropertyKey`].586	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {587		bytes.to_vec().try_into().map_err(|_| {588			Error::Revert(format!(589				"Property key is too long. Max length is {}.",590				up_data_structs::PropertyKey::bound()591			))592		})593	}594595	/// Convert `bytes` to [`PropertyValue`].596	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {597		bytes.to_vec().try_into().map_err(|_| {598			Error::Revert(format!(599				"Property key is too long. Max length is {}.",600				up_data_structs::PropertyKey::bound()601			))602		})603	}604}
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -60,7 +60,7 @@
 }
 
 /// Convert `uint256` to `CrossAccountId`.
-pub fn convert_uint256_to_cross_account<T: Config>(from: uint256) -> T::CrossAccountId 
+pub fn convert_uint256_to_cross_account<T: Config>(from: uint256) -> T::CrossAccountId
 where
 	T::AccountId: From<[u8; 32]>,
 {
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -357,7 +357,7 @@
 
 		<SponsorBasket<T>>::insert(contract_address, who.as_eth(), block_number);
 
-		Some(sponsor) 
+		Some(sponsor)
 	}
 }