git.delta.rocks / unique-network / refs/commits / 465cfefa64e5

difftreelog

source

pallets/common/src/erc.rs23.0 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! This module contains the implementation of pallet methods for evm.1819use evm_coder::{20	abi::AbiType,21	solidity_interface, solidity, ToLog,22	types::*,23	types::Property as PropertyStruct,24	execution::{Result, Error},25	weight,26};27pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use up_data_structs::{31	AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,32	SponsoringRateLimit, SponsorshipState,33};34use alloc::format;3536use crate::{37	Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,38	eth::convert_cross_account_to_uint256, weights::WeightInfo,39};4041/// Events for ethereum collection helper.42#[derive(ToLog)]43pub enum CollectionHelpersEvents {44	/// The collection has been created.45	CollectionCreated {46		/// Collection owner.47		#[indexed]48		owner: address,4950		/// Collection ID.51		#[indexed]52		collection_id: address,53	},54	/// The collection has been destroyed.55	CollectionDestroyed {56		/// Collection ID.57		#[indexed]58		collection_id: address,59	},60}6162/// Does not always represent a full collection, for RFT it is either63/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).64pub trait CommonEvmHandler {65	/// Raw compiled binary code of the contract stub66	const CODE: &'static [u8];6768	/// Call precompiled handle.69	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;70}7172/// @title A contract that allows you to work with collections.73#[solidity_interface(name = Collection)]74impl<T: Config> CollectionHandle<T>75where76	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,77{78	/// Set collection property.79	///80	/// @param key Property key.81	/// @param value Propery value.82	#[solidity(hide)]83	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]84	fn set_collection_property(85		&mut self,86		caller: caller,87		key: string,88		value: bytes,89	) -> Result<void> {90		let caller = T::CrossAccountId::from_eth(caller);91		let key = <Vec<u8>>::from(key)92			.try_into()93			.map_err(|_| "key too large")?;94		let value = value.0.try_into().map_err(|_| "value too large")?;9596		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })97			.map_err(dispatch_to_evm::<T>)98	}99100	/// Set collection properties.101	///102	/// @param properties Vector of properties key/value pair.103	#[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]104	fn set_collection_properties(105		&mut self,106		caller: caller,107		properties: Vec<PropertyStruct>,108	) -> Result<void> {109		let caller = T::CrossAccountId::from_eth(caller);110111		let properties = properties112			.into_iter()113			.map(|PropertyStruct { key, value }| {114				let key = <Vec<u8>>::from(key)115					.try_into()116					.map_err(|_| "key too large")?;117118				let value = value.0.try_into().map_err(|_| "value too large")?;119120				Ok(Property { key, value })121			})122			.collect::<Result<Vec<_>>>()?;123124		<Pallet<T>>::set_collection_properties(self, &caller, properties)125			.map_err(dispatch_to_evm::<T>)126	}127128	/// Delete collection property.129	///130	/// @param key Property key.131	#[solidity(hide)]132	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]133	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {134		let caller = T::CrossAccountId::from_eth(caller);135		let key = <Vec<u8>>::from(key)136			.try_into()137			.map_err(|_| "key too large")?;138139		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)140	}141142	/// Delete collection properties.143	///144	/// @param keys Properties keys.145	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]146	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {147		let caller = T::CrossAccountId::from_eth(caller);148		let keys = keys149			.into_iter()150			.map(|key| {151				<Vec<u8>>::from(key)152					.try_into()153					.map_err(|_| Error::Revert("key too large".into()))154			})155			.collect::<Result<Vec<_>>>()?;156157		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)158	}159160	/// Get collection property.161	///162	/// @dev Throws error if key not found.163	///164	/// @param key Property key.165	/// @return bytes The property corresponding to the key.166	fn collection_property(&self, key: string) -> Result<bytes> {167		let key = <Vec<u8>>::from(key)168			.try_into()169			.map_err(|_| "key too large")?;170171		let props = CollectionProperties::<T>::get(self.id);172		let prop = props.get(&key).ok_or("key not found")?;173174		Ok(bytes(prop.to_vec()))175	}176177	/// Get collection properties.178	///179	/// @param keys Properties keys. Empty keys for all propertyes.180	/// @return Vector of properties key/value pairs.181	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {182		let keys = keys183			.into_iter()184			.map(|key| {185				<Vec<u8>>::from(key)186					.try_into()187					.map_err(|_| Error::Revert("key too large".into()))188			})189			.collect::<Result<Vec<_>>>()?;190191		let properties = Pallet::<T>::filter_collection_properties(192			self.id,193			if keys.is_empty() { None } else { Some(keys) },194		)195		.map_err(dispatch_to_evm::<T>)?;196197		let properties = properties198			.into_iter()199			.map(|p| {200				let key =201					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;202				let value = bytes(p.value.to_vec());203				Ok(PropertyStruct { key, value })204			})205			.collect::<Result<Vec<_>>>()?;206		Ok(properties)207	}208209	/// Set the sponsor of the collection.210	///211	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.212	///213	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.214	#[solidity(hide)]215	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {216		self.consume_store_reads_and_writes(1, 1)?;217218		check_is_owner_or_admin(caller, self)?;219220		let sponsor = T::CrossAccountId::from_eth(sponsor);221		self.set_sponsor(sponsor.as_sub().clone())222			.map_err(dispatch_to_evm::<T>)?;223		save(self)224	}225226	/// Set the sponsor of the collection.227	///228	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.229	///230	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.231	fn set_collection_sponsor_cross(232		&mut self,233		caller: caller,234		sponsor: EthCrossAccount,235	) -> Result<void> {236		self.consume_store_reads_and_writes(1, 1)?;237238		check_is_owner_or_admin(caller, self)?;239240		let sponsor = sponsor.into_sub_cross_account::<T>()?;241		self.set_sponsor(sponsor.as_sub().clone())242			.map_err(dispatch_to_evm::<T>)?;243		save(self)244	}245246	/// Whether there is a pending sponsor.247	fn has_collection_pending_sponsor(&self) -> Result<bool> {248		Ok(matches!(249			self.collection.sponsorship,250			SponsorshipState::Unconfirmed(_)251		))252	}253254	/// Collection sponsorship confirmation.255	///256	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.257	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {258		self.consume_store_writes(1)?;259260		let caller = T::CrossAccountId::from_eth(caller);261		if !self262			.confirm_sponsorship(caller.as_sub())263			.map_err(dispatch_to_evm::<T>)?264		{265			return Err("caller is not set as sponsor".into());266		}267		save(self)268	}269270	/// Remove collection sponsor.271	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272		self.consume_store_reads_and_writes(1, 1)?;273		check_is_owner_or_admin(caller, self)?;274		self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;275		save(self)276	}277278	/// Get current sponsor.279	///280	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.281	fn collection_sponsor(&self) -> Result<(address, uint256)> {282		let sponsor = match self.collection.sponsorship.sponsor() {283			Some(sponsor) => sponsor,284			None => return Ok(Default::default()),285		};286		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());287		let result: (address, uint256) = if sponsor.is_canonical_substrate() {288			let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);289			(Default::default(), sponsor)290		} else {291			let sponsor = *sponsor.as_eth();292			(sponsor, Default::default())293		};294		Ok(result)295	}296297	/// Set limits for the collection.298	/// @dev Throws error if limit not found.299	/// @param limit Name of the limit. Valid names:300	/// 	"accountTokenOwnershipLimit",301	/// 	"sponsoredDataSize",302	/// 	"sponsoredDataRateLimit",303	/// 	"tokenLimit",304	/// 	"sponsorTransferTimeout",305	/// 	"sponsorApproveTimeout"306	///  	"ownerCanTransfer",307	/// 	"ownerCanDestroy",308	/// 	"transfersEnabled"309	/// @param value Value of the limit.310	#[solidity(rename_selector = "setCollectionLimit")]311	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {312		self.consume_store_reads_and_writes(1, 1)?;313314		let value = value315			.try_into()316			.map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;317318		let convert_value_to_bool = || match value {319			0 => Ok(false),320			1 => Ok(true),321			_ => {322				return Err(Error::Revert(format!(323					"can't convert value to boolean \"{}\"",324					value325				)))326			}327		};328329		check_is_owner_or_admin(caller, self)?;330		let mut limits = self.limits.clone();331332		match limit.as_str() {333			"accountTokenOwnershipLimit" => {334				limits.account_token_ownership_limit = Some(value);335			}336			"sponsoredDataSize" => {337				limits.sponsored_data_size = Some(value);338			}339			"sponsoredDataRateLimit" => {340				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));341			}342			"tokenLimit" => {343				limits.token_limit = Some(value);344			}345			"sponsorTransferTimeout" => {346				limits.sponsor_transfer_timeout = Some(value);347			}348			"sponsorApproveTimeout" => {349				limits.sponsor_approve_timeout = Some(value);350			}351			"ownerCanTransfer" => {352				limits.owner_can_transfer = Some(convert_value_to_bool()?);353			}354			"ownerCanDestroy" => {355				limits.owner_can_destroy = Some(convert_value_to_bool()?);356			}357			"transfersEnabled" => {358				limits.transfers_enabled = Some(convert_value_to_bool()?);359			}360			_ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),361		}362		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)363			.map_err(dispatch_to_evm::<T>)?;364		save(self)365	}366367	/// Get contract address.368	fn contract_address(&self) -> Result<address> {369		Ok(crate::eth::collection_id_to_address(self.id))370	}371372	/// Add collection admin.373	/// @param newAdmin Cross account administrator address.374	fn add_collection_admin_cross(375		&mut self,376		caller: caller,377		new_admin: EthCrossAccount,378	) -> Result<void> {379		self.consume_store_writes(2)?;380381		let caller = T::CrossAccountId::from_eth(caller);382		let new_admin = new_admin.into_sub_cross_account::<T>()?;383		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;384		Ok(())385	}386387	/// Remove collection admin.388	/// @param admin Cross account administrator address.389	fn remove_collection_admin_cross(390		&mut self,391		caller: caller,392		admin: EthCrossAccount,393	) -> Result<void> {394		self.consume_store_writes(2)?;395396		let caller = T::CrossAccountId::from_eth(caller);397		let admin = admin.into_sub_cross_account::<T>()?;398		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;399		Ok(())400	}401402	/// Add collection admin.403	/// @param newAdmin Address of the added administrator.404	#[solidity(hide)]405	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {406		self.consume_store_writes(2)?;407408		let caller = T::CrossAccountId::from_eth(caller);409		let new_admin = T::CrossAccountId::from_eth(new_admin);410		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;411		Ok(())412	}413414	/// Remove collection admin.415	///416	/// @param admin Address of the removed administrator.417	#[solidity(hide)]418	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {419		self.consume_store_writes(2)?;420421		let caller = T::CrossAccountId::from_eth(caller);422		let admin = T::CrossAccountId::from_eth(admin);423		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;424		Ok(())425	}426427	/// Toggle accessibility of collection nesting.428	///429	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'430	#[solidity(rename_selector = "setCollectionNesting")]431	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {432		self.consume_store_reads_and_writes(1, 1)?;433434		check_is_owner_or_admin(caller, self)?;435436		let mut permissions = self.collection.permissions.clone();437		let mut nesting = permissions.nesting().clone();438		nesting.token_owner = enable;439		nesting.restricted = None;440		permissions.nesting = Some(nesting);441442		self.collection.permissions = <Pallet<T>>::clamp_permissions(443			self.collection.mode.clone(),444			&self.collection.permissions,445			permissions,446		)447		.map_err(dispatch_to_evm::<T>)?;448449		save(self)450	}451452	/// Toggle accessibility of collection nesting.453	///454	/// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'455	/// @param collections Addresses of collections that will be available for nesting.456	#[solidity(rename_selector = "setCollectionNesting")]457	fn set_nesting(458		&mut self,459		caller: caller,460		enable: bool,461		collections: Vec<address>,462	) -> Result<void> {463		self.consume_store_reads_and_writes(1, 1)?;464465		if collections.is_empty() {466			return Err("no addresses provided".into());467		}468		check_is_owner_or_admin(caller, self)?;469470		let mut permissions = self.collection.permissions.clone();471		match enable {472			false => {473				let mut nesting = permissions.nesting().clone();474				nesting.token_owner = false;475				nesting.restricted = None;476				permissions.nesting = Some(nesting);477			}478			true => {479				let mut bv = OwnerRestrictedSet::new();480				for i in collections {481					bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {482						Error::Revert("Can't convert address into collection id".into())483					})?)484					.map_err(|_| "too many collections")?;485				}486				let mut nesting = permissions.nesting().clone();487				nesting.token_owner = true;488				nesting.restricted = Some(bv);489				permissions.nesting = Some(nesting);490			}491		};492493		self.collection.permissions = <Pallet<T>>::clamp_permissions(494			self.collection.mode.clone(),495			&self.collection.permissions,496			permissions,497		)498		.map_err(dispatch_to_evm::<T>)?;499500		save(self)501	}502503	/// Set the collection access method.504	/// @param mode Access mode505	/// 	0 for Normal506	/// 	1 for AllowList507	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {508		self.consume_store_reads_and_writes(1, 1)?;509510		check_is_owner_or_admin(caller, self)?;511		let permissions = CollectionPermissions {512			access: Some(match mode {513				0 => AccessMode::Normal,514				1 => AccessMode::AllowList,515				_ => return Err("not supported access mode".into()),516			}),517			..Default::default()518		};519		self.collection.permissions = <Pallet<T>>::clamp_permissions(520			self.collection.mode.clone(),521			&self.collection.permissions,522			permissions,523		)524		.map_err(dispatch_to_evm::<T>)?;525526		save(self)527	}528529	/// Checks that user allowed to operate with collection.530	///531	/// @param user User address to check.532	fn allowed(&self, user: address) -> Result<bool> {533		Ok(Pallet::<T>::allowed(534			self.id,535			T::CrossAccountId::from_eth(user),536		))537	}538539	/// Add the user to the allowed list.540	///541	/// @param user Address of a trusted user.542	#[solidity(hide)]543	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {544		self.consume_store_writes(1)?;545546		let caller = T::CrossAccountId::from_eth(caller);547		let user = T::CrossAccountId::from_eth(user);548		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;549		Ok(())550	}551552	/// Add user to allowed list.553	///554	/// @param user User cross account address.555	fn add_to_collection_allow_list_cross(556		&mut self,557		caller: caller,558		user: EthCrossAccount,559	) -> Result<void> {560		self.consume_store_writes(1)?;561562		let caller = T::CrossAccountId::from_eth(caller);563		let user = user.into_sub_cross_account::<T>()?;564		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;565		Ok(())566	}567568	/// Remove the user from the allowed list.569	///570	/// @param user Address of a removed user.571	#[solidity(hide)]572	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {573		self.consume_store_writes(1)?;574575		let caller = T::CrossAccountId::from_eth(caller);576		let user = T::CrossAccountId::from_eth(user);577		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;578		Ok(())579	}580581	/// Remove user from allowed list.582	///583	/// @param user User cross account address.584	fn remove_from_collection_allow_list_cross(585		&mut self,586		caller: caller,587		user: EthCrossAccount,588	) -> Result<void> {589		self.consume_store_writes(1)?;590591		let caller = T::CrossAccountId::from_eth(caller);592		let user = user.into_sub_cross_account::<T>()?;593		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;594		Ok(())595	}596597	/// Switch permission for minting.598	///599	/// @param mode Enable if "true".600	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {601		self.consume_store_reads_and_writes(1, 1)?;602603		check_is_owner_or_admin(caller, self)?;604		let permissions = CollectionPermissions {605			mint_mode: Some(mode),606			..Default::default()607		};608		self.collection.permissions = <Pallet<T>>::clamp_permissions(609			self.collection.mode.clone(),610			&self.collection.permissions,611			permissions,612		)613		.map_err(dispatch_to_evm::<T>)?;614615		save(self)616	}617618	/// Check that account is the owner or admin of the collection619	///620	/// @param user account to verify621	/// @return "true" if account is the owner or admin622	#[solidity(hide, rename_selector = "isOwnerOrAdmin")]623	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {624		let user = T::CrossAccountId::from_eth(user);625		Ok(self.is_owner_or_admin(&user))626	}627628	/// Check that account is the owner or admin of the collection629	///630	/// @param user User cross account to verify631	/// @return "true" if account is the owner or admin632	fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {633		let user = user.into_sub_cross_account::<T>()?;634		Ok(self.is_owner_or_admin(&user))635	}636637	/// Returns collection type638	///639	/// @return `Fungible` or `NFT` or `ReFungible`640	fn unique_collection_type(&self) -> Result<string> {641		let mode = match self.collection.mode {642			CollectionMode::Fungible(_) => "Fungible",643			CollectionMode::NFT => "NFT",644			CollectionMode::ReFungible => "ReFungible",645		};646		Ok(mode.into())647	}648649	/// Get collection owner.650	///651	/// @return Tuble with sponsor address and his substrate mirror.652	/// If address is canonical then substrate mirror is zero and vice versa.653	fn collection_owner(&self) -> Result<EthCrossAccount> {654		Ok(EthCrossAccount::from_sub_cross_account::<T>(655			&T::CrossAccountId::from_sub(self.owner.clone()),656		))657	}658659	/// Changes collection owner to another account660	///661	/// @dev Owner can be changed only by current owner662	/// @param newOwner new owner account663	#[solidity(hide, rename_selector = "changeCollectionOwner")]664	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {665		self.consume_store_writes(1)?;666667		let caller = T::CrossAccountId::from_eth(caller);668		let new_owner = T::CrossAccountId::from_eth(new_owner);669		self.set_owner_internal(caller, new_owner)670			.map_err(dispatch_to_evm::<T>)671	}672673	/// Get collection administrators674	///675	/// @return Vector of tuples with admins address and his substrate mirror.676	/// If address is canonical then substrate mirror is zero and vice versa.677	fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {678		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))679			.map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))680			.collect();681		Ok(result)682	}683684	/// Changes collection owner to another account685	///686	/// @dev Owner can be changed only by current owner687	/// @param newOwner new owner cross account688	fn change_collection_owner_cross(689		&mut self,690		caller: caller,691		new_owner: EthCrossAccount,692	) -> Result<void> {693		self.consume_store_writes(1)?;694695		let caller = T::CrossAccountId::from_eth(caller);696		let new_owner = new_owner.into_sub_cross_account::<T>()?;697		self.set_owner_internal(caller, new_owner)698			.map_err(dispatch_to_evm::<T>)699	}700}701702/// ### Note703/// Do not forget to add: `self.consume_store_reads(1)?;`704fn check_is_owner_or_admin<T: Config>(705	caller: caller,706	collection: &CollectionHandle<T>,707) -> Result<T::CrossAccountId> {708	let caller = T::CrossAccountId::from_eth(caller);709	collection710		.check_is_owner_or_admin(&caller)711		.map_err(dispatch_to_evm::<T>)?;712	Ok(caller)713}714715/// ### Note716/// Do not forget to add: `self.consume_store_writes(1)?;`717fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {718	collection719		.check_is_internal()720		.map_err(dispatch_to_evm::<T>)?;721	collection.save().map_err(dispatch_to_evm::<T>)?;722	Ok(())723}724725/// Contains static property keys and values.726pub mod static_property {727	use evm_coder::{728		execution::{Result, Error},729	};730	use alloc::format;731732	const EXPECT_CONVERT_ERROR: &str = "length < limit";733734	/// Keys.735	pub mod key {736		use super::*;737738		/// Key "baseURI".739		pub fn base_uri() -> up_data_structs::PropertyKey {740			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)741		}742743		/// Key "url".744		pub fn url() -> up_data_structs::PropertyKey {745			property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)746		}747748		/// Key "suffix".749		pub fn suffix() -> up_data_structs::PropertyKey {750			property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)751		}752753		/// Key "parentNft".754		pub fn parent_nft() -> up_data_structs::PropertyKey {755			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)756		}757	}758759	/// Convert `byte` to [`PropertyKey`].760	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {761		bytes.to_vec().try_into().map_err(|_| {762			Error::Revert(format!(763				"Property key is too long. Max length is {}.",764				up_data_structs::PropertyKey::bound()765			))766		})767	}768769	/// Convert `bytes` to [`PropertyValue`].770	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {771		bytes.to_vec().try_into().map_err(|_| {772			Error::Revert(format!(773				"Property key is too long. Max length is {}.",774				up_data_structs::PropertyKey::bound()775			))776		})777	}778}