git.delta.rocks / unique-network / refs/commits / 40b1404794e3

difftreelog

Cargo fmt

Trubnikov Sergey2022-06-10parent: #f26467c.patch.diff
in: master

1 file 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/>.1617use evm_coder::{18	solidity_interface, solidity, ToLog,19	types::*,20	execution::{Result, Error},21};22pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};23use pallet_evm_coder_substrate::dispatch_to_evm;24use sp_std::vec::Vec;25use up_data_structs::{26	Property, SponsoringRateLimit, NestingRule, OwnerRestrictedSet, AccessMode,27	CollectionPermissions,28};29use alloc::format;3031use crate::{Pallet, CollectionHandle, Config, CollectionProperties};3233#[derive(ToLog)]34pub enum CollectionHelpersEvents {35	CollectionCreated {36		#[indexed]37		owner: address,38		#[indexed]39		collection_id: address,40	},41}4243/// Does not always represent a full collection, for RFT it is either44/// collection (Implementing ERC721), or specific collection token (Implementing ERC20)45pub trait CommonEvmHandler {46	const CODE: &'static [u8];4748	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;49}5051#[solidity_interface(name = "Collection")]52impl<T: Config> CollectionHandle<T>53where54	T::AccountId: From<[u8; 32]>,55{56	fn set_collection_property(&mut self, caller: caller, key: string, value: bytes) -> Result<void> {57		let caller = T::CrossAccountId::from_eth(caller);58		let key = <Vec<u8>>::from(key)59			.try_into()60			.map_err(|_| "key too large")?;61		let value = value.try_into().map_err(|_| "value too large")?;6263		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })64			.map_err(dispatch_to_evm::<T>)65	}6667	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {68		let caller = T::CrossAccountId::from_eth(caller);69		let key = <Vec<u8>>::from(key)70			.try_into()71			.map_err(|_| "key too large")?;7273		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)74	}7576	/// Throws error if key not found77	fn collection_property(&self, key: string) -> Result<bytes> {78		let key = <Vec<u8>>::from(key)79			.try_into()80			.map_err(|_| "key too large")?;8182		let props = <CollectionProperties<T>>::get(self.id);83		let prop = props.get(&key).ok_or("key not found")?;8485		Ok(prop.to_vec())86	}8788	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {89		check_is_owner_or_admin(caller, self)?;9091		let sponsor = T::CrossAccountId::from_eth(sponsor);92		self.set_sponsor(sponsor.as_sub().clone())93			.map_err(dispatch_to_evm::<T>)?;94		save(self)95	}9697	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {98		let caller = T::CrossAccountId::from_eth(caller);99		if !self100			.confirm_sponsorship(caller.as_sub())101			.map_err(dispatch_to_evm::<T>)?102		{103			return Err("Caller is not set as sponsor".into());104		}105		save(self)106	}107108	#[solidity(rename_selector = "setCollectionLimit")]109	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {110		check_is_owner_or_admin(caller, self)?;111		let mut limits = self.limits.clone();112113		match limit.as_str() {114			"accountTokenOwnershipLimit" => {115				limits.account_token_ownership_limit = Some(value);116			}117			"sponsoredDataSize" => {118				limits.sponsored_data_size = Some(value);119			}120			"sponsoredDataRateLimit" => {121				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));122			}123			"tokenLimit" => {124				limits.token_limit = Some(value);125			}126			"sponsorTransferTimeout" => {127				limits.sponsor_transfer_timeout = Some(value);128			}129			"sponsorApproveTimeout" => {130				limits.sponsor_approve_timeout = Some(value);131			}132			_ => {133				return Err(Error::Revert(format!(134					"Unknown integer limit \"{}\"",135					limit136				)))137			}138		}139		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)140			.map_err(dispatch_to_evm::<T>)?;141		save(self)142	}143144	#[solidity(rename_selector = "setCollectionLimit")]145	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {146		check_is_owner_or_admin(caller, self)?;147		let mut limits = self.limits.clone();148149		match limit.as_str() {150			"ownerCanTransfer" => {151				limits.owner_can_transfer = Some(value);152			}153			"ownerCanDestroy" => {154				limits.owner_can_destroy = Some(value);155			}156			"transfersEnabled" => {157				limits.transfers_enabled = Some(value);158			}159			_ => {160				return Err(Error::Revert(format!(161					"Unknown boolean limit \"{}\"",162					limit163				)))164			}165		}166		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)167			.map_err(dispatch_to_evm::<T>)?;168		save(self)169	}170171	fn contract_address(&self, _caller: caller) -> Result<address> {172		Ok(crate::eth::collection_id_to_address(self.id))173	}174175	fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {176		let caller = T::CrossAccountId::from_eth(caller);177		let mut new_admin_arr: [u8; 32] = Default::default();178		new_admin.to_big_endian(&mut new_admin_arr);179		let account_id = T::AccountId::from(new_admin_arr);180		let new_admin = T::CrossAccountId::from_sub(account_id);181		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;182		Ok(())183	}184185	fn remove_collection_admin_substrate(186		&self,187		caller: caller,188		new_admin: uint256,189	) -> Result<void> {190		let caller = T::CrossAccountId::from_eth(caller);191		let mut new_admin_arr: [u8; 32] = Default::default();192		new_admin.to_big_endian(&mut new_admin_arr);193		let account_id = T::AccountId::from(new_admin_arr);194		let new_admin = T::CrossAccountId::from_sub(account_id);195		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, false)196			.map_err(dispatch_to_evm::<T>)?;197		Ok(())198	}199200	fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result<void> {201		let caller = T::CrossAccountId::from_eth(caller);202		let new_admin = T::CrossAccountId::from_eth(new_admin);203		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;204		Ok(())205	}206207	fn remove_collection_admin(&self, caller: caller, admin: address) -> Result<void> {208		let caller = T::CrossAccountId::from_eth(caller);209		let admin = T::CrossAccountId::from_eth(admin);210		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;211		Ok(())212	}213214	#[solidity(rename_selector = "setCollectionNesting")]215	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {216		check_is_owner_or_admin(caller, self)?;217		let permissions = CollectionPermissions {218			nesting: Some(match enable {219				false => NestingRule::Disabled,220				true => NestingRule::Owner,221			}),222			..Default::default()223		};224		self.collection.permissions = <Pallet<T>>::clamp_permissions(225			self.collection.mode.clone(),226			&self.collection.permissions,227			permissions,228		)229		.map_err(dispatch_to_evm::<T>)?;230231		save(self)232	}233234	#[solidity(rename_selector = "setCollectionNesting")]235	fn set_nesting(236		&mut self,237		caller: caller,238		enable: bool,239		collections: Vec<address>,240	) -> Result<void> {241		if collections.is_empty() {242			return Err("No addresses provided".into());243		}244		if collections.len() >= OwnerRestrictedSet::bound() {245			return Err(Error::Revert(format!(246				"Out of bound: {} >= {}",247				collections.len(),248				OwnerRestrictedSet::bound()249			)));250		}251		check_is_owner_or_admin(caller, self)?;252		let permissions = CollectionPermissions {253			nesting: Some(match enable {254				false => NestingRule::Disabled,255				true => {256					let mut bv = OwnerRestrictedSet::new();257					for i in collections {258						bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {259							Error::Revert("Can't convert address into collection id".into())260						})?)261						.map_err(|e| Error::Revert(format!("{:?}", e)))?;262					}263					NestingRule::OwnerRestricted(bv)264				}265			}),266			..Default::default()267		};268		self.collection.permissions = <Pallet<T>>::clamp_permissions(269			self.collection.mode.clone(),270			&self.collection.permissions,271			permissions,272		)273		.map_err(dispatch_to_evm::<T>)?;274		275		save(self)276	}277278	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {279		check_is_owner_or_admin(caller, self)?;280		let permissions = CollectionPermissions{281			access: Some(match mode {282				0 => AccessMode::Normal,283				1 => AccessMode::AllowList,284				_ => return Err("Not supported access mode".into()),285			}),286			.. Default::default()287		};288		self.collection.permissions = <Pallet<T>>::clamp_permissions(289			self.collection.mode.clone(),290			&self.collection.permissions,291			permissions,292		)293		.map_err(dispatch_to_evm::<T>)?;294295		save(self)296	}297298	fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {299		let caller = T::CrossAccountId::from_eth(caller);300		let user = T::CrossAccountId::from_eth(user);301		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;302		Ok(())303	}304305	fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {306		let caller = T::CrossAccountId::from_eth(caller);307		let user = T::CrossAccountId::from_eth(user);308		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;309		Ok(())310	}311312	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {313		check_is_owner_or_admin(caller, self)?;314		let permissions = CollectionPermissions { mint_mode: Some(mode), .. Default::default() };315		self.collection.permissions = <Pallet<T>>::clamp_permissions(316			self.collection.mode.clone(),317			&self.collection.permissions,318			permissions,319		)320		.map_err(dispatch_to_evm::<T>)?;321322		save(self)323	}324}325326fn check_is_owner_or_admin<T: Config>(327	caller: caller,328	collection: &CollectionHandle<T>,329) -> Result<T::CrossAccountId> {330	let caller = T::CrossAccountId::from_eth(caller);331	collection332		.check_is_owner_or_admin(&caller)333		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;334	Ok(caller)335}336337fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {338	// TODO possibly delete for the lack of transaction339	collection340		.check_is_internal()341		.map_err(dispatch_to_evm::<T>)?;342	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());343	Ok(())344}345346pub fn token_uri_key() -> up_data_structs::PropertyKey {347	b"tokenURI"348		.to_vec()349		.try_into()350		.expect("length < limit; qed")351}
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/>.1617use evm_coder::{18	solidity_interface, solidity, ToLog,19	types::*,20	execution::{Result, Error},21};22pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};23use pallet_evm_coder_substrate::dispatch_to_evm;24use sp_std::vec::Vec;25use up_data_structs::{26	Property, SponsoringRateLimit, NestingRule, OwnerRestrictedSet, AccessMode,27	CollectionPermissions,28};29use alloc::format;3031use crate::{Pallet, CollectionHandle, Config, CollectionProperties};3233#[derive(ToLog)]34pub enum CollectionHelpersEvents {35	CollectionCreated {36		#[indexed]37		owner: address,38		#[indexed]39		collection_id: address,40	},41}4243/// Does not always represent a full collection, for RFT it is either44/// collection (Implementing ERC721), or specific collection token (Implementing ERC20)45pub trait CommonEvmHandler {46	const CODE: &'static [u8];4748	fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;49}5051#[solidity_interface(name = "Collection")]52impl<T: Config> CollectionHandle<T>53where54	T::AccountId: From<[u8; 32]>,55{56	fn set_collection_property(57		&mut self,58		caller: caller,59		key: string,60		value: bytes,61	) -> Result<void> {62		let caller = T::CrossAccountId::from_eth(caller);63		let key = <Vec<u8>>::from(key)64			.try_into()65			.map_err(|_| "key too large")?;66		let value = value.try_into().map_err(|_| "value too large")?;6768		<Pallet<T>>::set_collection_property(self, &caller, Property { key, value })69			.map_err(dispatch_to_evm::<T>)70	}7172	fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {73		let caller = T::CrossAccountId::from_eth(caller);74		let key = <Vec<u8>>::from(key)75			.try_into()76			.map_err(|_| "key too large")?;7778		<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)79	}8081	/// Throws error if key not found82	fn collection_property(&self, key: string) -> Result<bytes> {83		let key = <Vec<u8>>::from(key)84			.try_into()85			.map_err(|_| "key too large")?;8687		let props = <CollectionProperties<T>>::get(self.id);88		let prop = props.get(&key).ok_or("key not found")?;8990		Ok(prop.to_vec())91	}9293	fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {94		check_is_owner_or_admin(caller, self)?;9596		let sponsor = T::CrossAccountId::from_eth(sponsor);97		self.set_sponsor(sponsor.as_sub().clone())98			.map_err(dispatch_to_evm::<T>)?;99		save(self)100	}101102	fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {103		let caller = T::CrossAccountId::from_eth(caller);104		if !self105			.confirm_sponsorship(caller.as_sub())106			.map_err(dispatch_to_evm::<T>)?107		{108			return Err("Caller is not set as sponsor".into());109		}110		save(self)111	}112113	#[solidity(rename_selector = "setCollectionLimit")]114	fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {115		check_is_owner_or_admin(caller, self)?;116		let mut limits = self.limits.clone();117118		match limit.as_str() {119			"accountTokenOwnershipLimit" => {120				limits.account_token_ownership_limit = Some(value);121			}122			"sponsoredDataSize" => {123				limits.sponsored_data_size = Some(value);124			}125			"sponsoredDataRateLimit" => {126				limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));127			}128			"tokenLimit" => {129				limits.token_limit = Some(value);130			}131			"sponsorTransferTimeout" => {132				limits.sponsor_transfer_timeout = Some(value);133			}134			"sponsorApproveTimeout" => {135				limits.sponsor_approve_timeout = Some(value);136			}137			_ => {138				return Err(Error::Revert(format!(139					"Unknown integer limit \"{}\"",140					limit141				)))142			}143		}144		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)145			.map_err(dispatch_to_evm::<T>)?;146		save(self)147	}148149	#[solidity(rename_selector = "setCollectionLimit")]150	fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {151		check_is_owner_or_admin(caller, self)?;152		let mut limits = self.limits.clone();153154		match limit.as_str() {155			"ownerCanTransfer" => {156				limits.owner_can_transfer = Some(value);157			}158			"ownerCanDestroy" => {159				limits.owner_can_destroy = Some(value);160			}161			"transfersEnabled" => {162				limits.transfers_enabled = Some(value);163			}164			_ => {165				return Err(Error::Revert(format!(166					"Unknown boolean limit \"{}\"",167					limit168				)))169			}170		}171		self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)172			.map_err(dispatch_to_evm::<T>)?;173		save(self)174	}175176	fn contract_address(&self, _caller: caller) -> Result<address> {177		Ok(crate::eth::collection_id_to_address(self.id))178	}179180	fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {181		let caller = T::CrossAccountId::from_eth(caller);182		let mut new_admin_arr: [u8; 32] = Default::default();183		new_admin.to_big_endian(&mut new_admin_arr);184		let account_id = T::AccountId::from(new_admin_arr);185		let new_admin = T::CrossAccountId::from_sub(account_id);186		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;187		Ok(())188	}189190	fn remove_collection_admin_substrate(191		&self,192		caller: caller,193		new_admin: uint256,194	) -> Result<void> {195		let caller = T::CrossAccountId::from_eth(caller);196		let mut new_admin_arr: [u8; 32] = Default::default();197		new_admin.to_big_endian(&mut new_admin_arr);198		let account_id = T::AccountId::from(new_admin_arr);199		let new_admin = T::CrossAccountId::from_sub(account_id);200		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, false)201			.map_err(dispatch_to_evm::<T>)?;202		Ok(())203	}204205	fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result<void> {206		let caller = T::CrossAccountId::from_eth(caller);207		let new_admin = T::CrossAccountId::from_eth(new_admin);208		<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;209		Ok(())210	}211212	fn remove_collection_admin(&self, caller: caller, admin: address) -> Result<void> {213		let caller = T::CrossAccountId::from_eth(caller);214		let admin = T::CrossAccountId::from_eth(admin);215		<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;216		Ok(())217	}218219	#[solidity(rename_selector = "setCollectionNesting")]220	fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {221		check_is_owner_or_admin(caller, self)?;222		let permissions = CollectionPermissions {223			nesting: Some(match enable {224				false => NestingRule::Disabled,225				true => NestingRule::Owner,226			}),227			..Default::default()228		};229		self.collection.permissions = <Pallet<T>>::clamp_permissions(230			self.collection.mode.clone(),231			&self.collection.permissions,232			permissions,233		)234		.map_err(dispatch_to_evm::<T>)?;235236		save(self)237	}238239	#[solidity(rename_selector = "setCollectionNesting")]240	fn set_nesting(241		&mut self,242		caller: caller,243		enable: bool,244		collections: Vec<address>,245	) -> Result<void> {246		if collections.is_empty() {247			return Err("No addresses provided".into());248		}249		if collections.len() >= OwnerRestrictedSet::bound() {250			return Err(Error::Revert(format!(251				"Out of bound: {} >= {}",252				collections.len(),253				OwnerRestrictedSet::bound()254			)));255		}256		check_is_owner_or_admin(caller, self)?;257		let permissions = CollectionPermissions {258			nesting: Some(match enable {259				false => NestingRule::Disabled,260				true => {261					let mut bv = OwnerRestrictedSet::new();262					for i in collections {263						bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {264							Error::Revert("Can't convert address into collection id".into())265						})?)266						.map_err(|e| Error::Revert(format!("{:?}", e)))?;267					}268					NestingRule::OwnerRestricted(bv)269				}270			}),271			..Default::default()272		};273		self.collection.permissions = <Pallet<T>>::clamp_permissions(274			self.collection.mode.clone(),275			&self.collection.permissions,276			permissions,277		)278		.map_err(dispatch_to_evm::<T>)?;279280		save(self)281	}282283	fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {284		check_is_owner_or_admin(caller, self)?;285		let permissions = CollectionPermissions {286			access: Some(match mode {287				0 => AccessMode::Normal,288				1 => AccessMode::AllowList,289				_ => return Err("Not supported access mode".into()),290			}),291			..Default::default()292		};293		self.collection.permissions = <Pallet<T>>::clamp_permissions(294			self.collection.mode.clone(),295			&self.collection.permissions,296			permissions,297		)298		.map_err(dispatch_to_evm::<T>)?;299300		save(self)301	}302303	fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {304		let caller = T::CrossAccountId::from_eth(caller);305		let user = T::CrossAccountId::from_eth(user);306		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;307		Ok(())308	}309310	fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {311		let caller = T::CrossAccountId::from_eth(caller);312		let user = T::CrossAccountId::from_eth(user);313		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;314		Ok(())315	}316317	fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {318		check_is_owner_or_admin(caller, self)?;319		let permissions = CollectionPermissions {320			mint_mode: Some(mode),321			..Default::default()322		};323		self.collection.permissions = <Pallet<T>>::clamp_permissions(324			self.collection.mode.clone(),325			&self.collection.permissions,326			permissions,327		)328		.map_err(dispatch_to_evm::<T>)?;329330		save(self)331	}332}333334fn check_is_owner_or_admin<T: Config>(335	caller: caller,336	collection: &CollectionHandle<T>,337) -> Result<T::CrossAccountId> {338	let caller = T::CrossAccountId::from_eth(caller);339	collection340		.check_is_owner_or_admin(&caller)341		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;342	Ok(caller)343}344345fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {346	// TODO possibly delete for the lack of transaction347	collection348		.check_is_internal()349		.map_err(dispatch_to_evm::<T>)?;350	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());351	Ok(())352}353354pub fn token_uri_key() -> up_data_structs::PropertyKey {355	b"tokenURI"356		.to_vec()357		.try_into()358		.expect("length < limit; qed")359}