git.delta.rocks / unique-network / refs/commits / c0ab2ae2da4d

difftreelog

source

pallets/common/src/erc.rs18.4 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	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_substrate_address_to_cross_account_id,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]>,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_substrate_address_to_cross_account_id::<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_substrate_address_to_cross_account_id::<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_substrate_address_to_cross_account_id::<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_substrate_address_to_cross_account_id::<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}