git.delta.rocks / unique-network / refs/commits / 5811ea85a5fa

difftreelog

source

pallets/common/src/erc.rs22.9 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.1819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use evm_coder::{21	abi::AbiType,22	solidity_interface, solidity, ToLog,23	types::*,24	types::Property as PropertyStruct,25	execution::{Result, Error},26	weight,27};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::{EthCrossAccount, convert_cross_account_to_uint256},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	/// The collection has been changed.62	CollectionChanged {63		/// Collection ID.64		#[indexed]65		collection_id: address,66	},6768	/// The token has been changed.69	TokenChanged {70		/// Collection ID.71		#[indexed]72		collection_id: address,73		/// Token ID.74		token_id: uint256,75	},76}7778/// Does not always represent a full collection, for RFT it is either79/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).80pub trait CommonEvmHandler {81	/// Raw compiled binary code of the contract stub82	const CODE: &'static [u8];8384	/// Call precompiled handle.85	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;86}8788/// @title A contract that allows you to work with collections.89#[solidity_interface(name = Collection)]90impl<T: Config> CollectionHandle<T>91where92	T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,93{94	/// Set collection property.95	///96	/// @param key Property key.97	/// @param value Propery value.98	#[solidity(hide)]99	#[weight(<SelfWeightOf<T>>::set_collection_properties(1))]100	fn set_collection_property(101		&mut self,102		caller: caller,103		key: string,104		value: bytes,105	) -> Result<void> {106		let caller = T::CrossAccountId::from_eth(caller);107		let key = <Vec<u8>>::from(key)108			.try_into()109			.map_err(|_| "key too large")?;110		let value = value.0.try_into().map_err(|_| "value too large")?;111112		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })113			.map_err(dispatch_to_evm::<T>)114	}115116	/// Set collection properties.117	///118	/// @param properties Vector of properties key/value pair.119	#[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]120	fn set_collection_properties(121		&mut self,122		caller: caller,123		properties: Vec<PropertyStruct>,124	) -> Result<void> {125		let caller = T::CrossAccountId::from_eth(caller);126127		let properties = properties128			.into_iter()129			.map(|PropertyStruct { key, value }| {130				let key = <Vec<u8>>::from(key)131					.try_into()132					.map_err(|_| "key too large")?;133134				let value = value.0.try_into().map_err(|_| "value too large")?;135136				Ok(Property { key, value })137			})138			.collect::<Result<Vec<_>>>()?;139140		<Pallet<T>>::set_collection_properties(self, &caller, properties)141			.map_err(dispatch_to_evm::<T>)142	}143144	/// Delete collection property.145	///146	/// @param key Property key.147	#[solidity(hide)]148	#[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]149	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {150		let caller = T::CrossAccountId::from_eth(caller);151		let key = <Vec<u8>>::from(key)152			.try_into()153			.map_err(|_| "key too large")?;154155		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)156	}157158	/// Delete collection properties.159	///160	/// @param keys Properties keys.161	#[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]162	fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {163		let caller = T::CrossAccountId::from_eth(caller);164		let keys = keys165			.into_iter()166			.map(|key| {167				<Vec<u8>>::from(key)168					.try_into()169					.map_err(|_| Error::Revert("key too large".into()))170			})171			.collect::<Result<Vec<_>>>()?;172173		<Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)174	}175176	/// Get collection property.177	///178	/// @dev Throws error if key not found.179	///180	/// @param key Property key.181	/// @return bytes The property corresponding to the key.182	fn collection_property(&self, key: string) -> Result<bytes> {183		let key = <Vec<u8>>::from(key)184			.try_into()185			.map_err(|_| "key too large")?;186187		let props = CollectionProperties::<T>::get(self.id);188		let prop = props.get(&key).ok_or("key not found")?;189190		Ok(bytes(prop.to_vec()))191	}192193	/// Get collection properties.194	///195	/// @param keys Properties keys. Empty keys for all propertyes.196	/// @return Vector of properties key/value pairs.197	fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {198		let keys = keys199			.into_iter()200			.map(|key| {201				<Vec<u8>>::from(key)202					.try_into()203					.map_err(|_| Error::Revert("key too large".into()))204			})205			.collect::<Result<Vec<_>>>()?;206207		let properties = Pallet::<T>::filter_collection_properties(208			self.id,209			if keys.is_empty() { None } else { Some(keys) },210		)211		.map_err(dispatch_to_evm::<T>)?;212213		let properties = properties214			.into_iter()215			.map(|p| {216				let key =217					string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;218				let value = bytes(p.value.to_vec());219				Ok(PropertyStruct { key, value })220			})221			.collect::<Result<Vec<_>>>()?;222		Ok(properties)223	}224225	/// Set the sponsor of the collection.226	///227	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.228	///229	/// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.230	#[solidity(hide)]231	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {232		self.consume_store_reads_and_writes(1, 1)?;233234		let caller = T::CrossAccountId::from_eth(caller);235236		let sponsor = T::CrossAccountId::from_eth(sponsor);237		self.set_sponsor(&caller, sponsor.as_sub().clone())238			.map_err(dispatch_to_evm::<T>)239	}240241	/// Set the sponsor of the collection.242	///243	/// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.244	///245	/// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.246	fn set_collection_sponsor_cross(247		&mut self,248		caller: caller,249		sponsor: EthCrossAccount,250	) -> Result<void> {251		self.consume_store_reads_and_writes(1, 1)?;252253		let caller = T::CrossAccountId::from_eth(caller);254255		let sponsor = sponsor.into_sub_cross_account::<T>()?;256		self.set_sponsor(&caller, sponsor.as_sub().clone())257			.map_err(dispatch_to_evm::<T>)258	}259260	/// Whether there is a pending sponsor.261	fn has_collection_pending_sponsor(&self) -> Result<bool> {262		Ok(matches!(263			self.collection.sponsorship,264			SponsorshipState::Unconfirmed(_)265		))266	}267268	/// Collection sponsorship confirmation.269	///270	/// @dev After setting the sponsor for the collection, it must be confirmed with this function.271	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {272		self.consume_store_writes(1)?;273274		let caller = T::CrossAccountId::from_eth(caller);275		self.confirm_sponsorship(caller.as_sub())276			.map_err(dispatch_to_evm::<T>)277	}278279	/// Remove collection sponsor.280	fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {281		self.consume_store_reads_and_writes(1, 1)?;282		let caller = T::CrossAccountId::from_eth(caller);283		self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)284	}285286	/// Get current sponsor.287	///288	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.289	fn collection_sponsor(&self) -> Result<(address, uint256)> {290		let sponsor = match self.collection.sponsorship.sponsor() {291			Some(sponsor) => sponsor,292			None => return Ok(Default::default()),293		};294		let sponsor = T::CrossAccountId::from_sub(sponsor.clone());295		let result: (address, uint256) = if sponsor.is_canonical_substrate() {296			let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);297			(Default::default(), sponsor)298		} else {299			let sponsor = *sponsor.as_eth();300			(sponsor, Default::default())301		};302		Ok(result)303	}304305	/// Set limits for the collection.306	/// @dev Throws error if limit not found.307	/// @param limit Name of the limit. Valid names:308	/// 	"accountTokenOwnershipLimit",309	/// 	"sponsoredDataSize",310	/// 	"sponsoredDataRateLimit",311	/// 	"tokenLimit",312	/// 	"sponsorTransferTimeout",313	/// 	"sponsorApproveTimeout"314	///  	"ownerCanTransfer",315	/// 	"ownerCanDestroy",316	/// 	"transfersEnabled"317	/// @param value Value of the limit.318	#[solidity(rename_selector = "setCollectionLimit")]319	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {320		self.consume_store_reads_and_writes(1, 1)?;321322		let value = value323			.try_into()324			.map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;325326		let convert_value_to_bool = || match value {327			0 => Ok(false),328			1 => Ok(true),329			_ => {330				return Err(Error::Revert(format!(331					"can't convert value to boolean \"{}\"",332					value333				)))334			}335		};336337		let mut limits = self.limits.clone();338339		match limit.as_str() {340			"accountTokenOwnershipLimit" => {341				limits.account_token_ownership_limit = Some(value);342			}343			"sponsoredDataSize" => {344				limits.sponsored_data_size = Some(value);345			}346			"sponsoredDataRateLimit" => {347				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));348			}349			"tokenLimit" => {350				limits.token_limit = Some(value);351			}352			"sponsorTransferTimeout" => {353				limits.sponsor_transfer_timeout = Some(value);354			}355			"sponsorApproveTimeout" => {356				limits.sponsor_approve_timeout = Some(value);357			}358			"ownerCanTransfer" => {359				limits.owner_can_transfer = Some(convert_value_to_bool()?);360			}361			"ownerCanDestroy" => {362				limits.owner_can_destroy = Some(convert_value_to_bool()?);363			}364			"transfersEnabled" => {365				limits.transfers_enabled = Some(convert_value_to_bool()?);366			}367			_ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),368		}369370		let caller = T::CrossAccountId::from_eth(caller);371		<Pallet<T>>::update_limits(&caller, self, limits).map_err(dispatch_to_evm::<T>)372	}373374	/// Get contract address.375	fn contract_address(&self) -> Result<address> {376		Ok(crate::eth::collection_id_to_address(self.id))377	}378379	/// Add collection admin.380	/// @param newAdmin Cross account administrator address.381	fn add_collection_admin_cross(382		&mut self,383		caller: caller,384		new_admin: EthCrossAccount,385	) -> Result<void> {386		self.consume_store_reads_and_writes(2, 2)?;387388		let caller = T::CrossAccountId::from_eth(caller);389		let new_admin = new_admin.into_sub_cross_account::<T>()?;390		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;391		Ok(())392	}393394	/// Remove collection admin.395	/// @param admin Cross account administrator address.396	fn remove_collection_admin_cross(397		&mut self,398		caller: caller,399		admin: EthCrossAccount,400	) -> Result<void> {401		self.consume_store_reads_and_writes(2, 2)?;402403		let caller = T::CrossAccountId::from_eth(caller);404		let admin = admin.into_sub_cross_account::<T>()?;405		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;406		Ok(())407	}408409	/// Add collection admin.410	/// @param newAdmin Address of the added administrator.411	#[solidity(hide)]412	fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {413		self.consume_store_reads_and_writes(2, 2)?;414415		let caller = T::CrossAccountId::from_eth(caller);416		let new_admin = T::CrossAccountId::from_eth(new_admin);417		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;418		Ok(())419	}420421	/// Remove collection admin.422	///423	/// @param admin Address of the removed administrator.424	#[solidity(hide)]425	fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {426		self.consume_store_reads_and_writes(2, 2)?;427428		let caller = T::CrossAccountId::from_eth(caller);429		let admin = T::CrossAccountId::from_eth(admin);430		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;431		Ok(())432	}433434	/// Toggle accessibility of collection nesting.435	///436	/// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'437	#[solidity(rename_selector = "setCollectionNesting")]438	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {439		self.consume_store_reads_and_writes(1, 1)?;440441		let caller = T::CrossAccountId::from_eth(caller);442443		let mut permissions = self.collection.permissions.clone();444		let mut nesting = permissions.nesting().clone();445		nesting.token_owner = enable;446		nesting.restricted = None;447		permissions.nesting = Some(nesting);448449		<Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)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		let caller = T::CrossAccountId::from_eth(caller);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		<Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)494	}495496	/// Set the collection access method.497	/// @param mode Access mode498	/// 	0 for Normal499	/// 	1 for AllowList500	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {501		self.consume_store_reads_and_writes(1, 1)?;502503		let caller = T::CrossAccountId::from_eth(caller);504		let permissions = CollectionPermissions {505			access: Some(match mode {506				0 => AccessMode::Normal,507				1 => AccessMode::AllowList,508				_ => return Err("not supported access mode".into()),509			}),510			..Default::default()511		};512		<Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)513	}514515	/// Checks that user allowed to operate with collection.516	///517	/// @param user User address to check.518	fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {519		let user = user.into_sub_cross_account::<T>()?;520		Ok(Pallet::<T>::allowed(self.id, user))521	}522523	/// Add the user to the allowed list.524	///525	/// @param user Address of a trusted user.526	#[solidity(hide)]527	fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {528		self.consume_store_writes(1)?;529530		let caller = T::CrossAccountId::from_eth(caller);531		let user = T::CrossAccountId::from_eth(user);532		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;533		Ok(())534	}535536	/// Add user to allowed list.537	///538	/// @param user User cross account address.539	fn add_to_collection_allow_list_cross(540		&mut self,541		caller: caller,542		user: EthCrossAccount,543	) -> Result<void> {544		self.consume_store_writes(1)?;545546		let caller = T::CrossAccountId::from_eth(caller);547		let user = user.into_sub_cross_account::<T>()?;548		Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;549		Ok(())550	}551552	/// Remove the user from the allowed list.553	///554	/// @param user Address of a removed user.555	#[solidity(hide)]556	fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {557		self.consume_store_writes(1)?;558559		let caller = T::CrossAccountId::from_eth(caller);560		let user = T::CrossAccountId::from_eth(user);561		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;562		Ok(())563	}564565	/// Remove user from allowed list.566	///567	/// @param user User cross account address.568	fn remove_from_collection_allow_list_cross(569		&mut self,570		caller: caller,571		user: EthCrossAccount,572	) -> Result<void> {573		self.consume_store_writes(1)?;574575		let caller = T::CrossAccountId::from_eth(caller);576		let user = user.into_sub_cross_account::<T>()?;577		Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;578		Ok(())579	}580581	/// Switch permission for minting.582	///583	/// @param mode Enable if "true".584	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {585		self.consume_store_reads_and_writes(1, 1)?;586587		let caller = T::CrossAccountId::from_eth(caller);588		let permissions = CollectionPermissions {589			mint_mode: Some(mode),590			..Default::default()591		};592		<Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)593	}594595	/// Check that account is the owner or admin of the collection596	///597	/// @param user account to verify598	/// @return "true" if account is the owner or admin599	#[solidity(hide, rename_selector = "isOwnerOrAdmin")]600	fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {601		let user = T::CrossAccountId::from_eth(user);602		Ok(self.is_owner_or_admin(&user))603	}604605	/// Check that account is the owner or admin of the collection606	///607	/// @param user User cross account to verify608	/// @return "true" if account is the owner or admin609	fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {610		let user = user.into_sub_cross_account::<T>()?;611		Ok(self.is_owner_or_admin(&user))612	}613614	/// Returns collection type615	///616	/// @return `Fungible` or `NFT` or `ReFungible`617	fn unique_collection_type(&self) -> Result<string> {618		let mode = match self.collection.mode {619			CollectionMode::Fungible(_) => "Fungible",620			CollectionMode::NFT => "NFT",621			CollectionMode::ReFungible => "ReFungible",622		};623		Ok(mode.into())624	}625626	/// Get collection owner.627	///628	/// @return Tuble with sponsor address and his substrate mirror.629	/// If address is canonical then substrate mirror is zero and vice versa.630	fn collection_owner(&self) -> Result<EthCrossAccount> {631		Ok(EthCrossAccount::from_sub_cross_account::<T>(632			&T::CrossAccountId::from_sub(self.owner.clone()),633		))634	}635636	/// Changes collection owner to another account637	///638	/// @dev Owner can be changed only by current owner639	/// @param newOwner new owner account640	#[solidity(hide, rename_selector = "changeCollectionOwner")]641	fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {642		self.consume_store_writes(1)?;643644		let caller = T::CrossAccountId::from_eth(caller);645		let new_owner = T::CrossAccountId::from_eth(new_owner);646		self.change_owner(caller, new_owner)647			.map_err(dispatch_to_evm::<T>)648	}649650	/// Get collection administrators651	///652	/// @return Vector of tuples with admins address and his substrate mirror.653	/// If address is canonical then substrate mirror is zero and vice versa.654	fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {655		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))656			.map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))657			.collect();658		Ok(result)659	}660661	/// Changes collection owner to another account662	///663	/// @dev Owner can be changed only by current owner664	/// @param newOwner new owner cross account665	fn change_collection_owner_cross(666		&mut self,667		caller: caller,668		new_owner: EthCrossAccount,669	) -> Result<void> {670		self.consume_store_writes(1)?;671672		let caller = T::CrossAccountId::from_eth(caller);673		let new_owner = new_owner.into_sub_cross_account::<T>()?;674		self.change_owner(caller, new_owner)675			.map_err(dispatch_to_evm::<T>)676	}677}678679/// ### Note680/// Do not forget to add: `self.consume_store_reads(1)?;`681fn check_is_owner_or_admin<T: Config>(682	caller: caller,683	collection: &CollectionHandle<T>,684) -> Result<T::CrossAccountId> {685	let caller = T::CrossAccountId::from_eth(caller);686	collection687		.check_is_owner_or_admin(&caller)688		.map_err(dispatch_to_evm::<T>)?;689	Ok(caller)690}691692/// ### Note693/// Do not forget to add: `self.consume_store_writes(1)?;`694fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {695	collection696		.check_is_internal()697		.map_err(dispatch_to_evm::<T>)?;698	collection.save().map_err(dispatch_to_evm::<T>)?;699	Ok(())700}701702/// Contains static property keys and values.703pub mod static_property {704	use evm_coder::{705		execution::{Result, Error},706	};707	use alloc::format;708709	const EXPECT_CONVERT_ERROR: &str = "length < limit";710711	/// Keys.712	pub mod key {713		use super::*;714715		/// Key "baseURI".716		pub fn base_uri() -> up_data_structs::PropertyKey {717			property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)718		}719720		/// Key "url".721		pub fn url() -> up_data_structs::PropertyKey {722			property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)723		}724725		/// Key "suffix".726		pub fn suffix() -> up_data_structs::PropertyKey {727			property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)728		}729730		/// Key "parentNft".731		pub fn parent_nft() -> up_data_structs::PropertyKey {732			property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)733		}734	}735736	/// Convert `byte` to [`PropertyKey`].737	pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {738		bytes.to_vec().try_into().map_err(|_| {739			Error::Revert(format!(740				"Property key is too long. Max length is {}.",741				up_data_structs::PropertyKey::bound()742			))743		})744	}745746	/// Convert `bytes` to [`PropertyValue`].747	pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {748		bytes.to_vec().try_into().map_err(|_| {749			Error::Revert(format!(750				"Property key is too long. Max length is {}.",751				up_data_structs::PropertyKey::bound()752			))753		})754	}755}