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

difftreelog

source

pallets/unique/src/eth/mod.rs16.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//! Implementation of CollectionHelpers contract.18//!19use alloc::{collections::BTreeSet, format};20use core::marker::PhantomData;2122use ethereum as _;23use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*};24use frame_support::{traits::Get, BoundedVec};25use pallet_common::{26	dispatch::CollectionDispatch,27	erc::{static_property::key, CollectionHelpersEvents},28	eth::{self, collection_id_to_address, map_eth_to_id},29	CollectionById, CollectionHandle, CollectionIssuer, Pallet as PalletCommon,30};31use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};32use pallet_evm_coder_substrate::{33	dispatch_to_evm,34	execution::{Error, PreDispatch, Result},35	frontier_contract, SubstrateRecorder, WithRecorder,36};37use sp_std::vec::Vec;38use up_data_structs::{39	CollectionDescription, CollectionMode, CollectionName, CollectionPermissions,40	CollectionTokenPrefix, CreateCollectionData, NestingPermissions,41};4243use crate::{weights::WeightInfo, Config, Pallet, SelfWeightOf};4445frontier_contract! {46	macro_rules! EvmCollectionHelpers_result {...}47	impl<T: Config> Contract for EvmCollectionHelpers<T> {...}48}49/// See [`CollectionHelpersCall`]50pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);51impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {52	fn recorder(&self) -> &SubstrateRecorder<T> {53		&self.054	}5556	fn into_recorder(self) -> SubstrateRecorder<T> {57		self.058	}59}6061fn convert_data<T: Config>(62	caller: Caller,63	name: String,64	description: String,65	token_prefix: String,66) -> Result<(67	T::CrossAccountId,68	CollectionName,69	CollectionDescription,70	CollectionTokenPrefix,71)> {72	let caller = T::CrossAccountId::from_eth(caller);73	let name = name74		.encode_utf16()75		.collect::<Vec<u16>>()76		.try_into()77		.map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;78	let description = description79		.encode_utf16()80		.collect::<Vec<u16>>()81		.try_into()82		.map_err(|_| {83			error_field_too_long(stringify!(description), CollectionDescription::bound())84		})?;85	let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {86		error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())87	})?;88	Ok((caller, name, description, token_prefix))89}9091#[inline(always)]92fn create_collection_internal<T: Config>(93	caller: Caller,94	value: Value,95	name: String,96	collection_mode: CollectionMode,97	description: String,98	token_prefix: String,99) -> Result<Address> {100	let (caller, name, description, token_prefix) =101		convert_data::<T>(caller, name, description, token_prefix)?;102	let data = CreateCollectionData {103		name,104		mode: collection_mode,105		description,106		token_prefix,107		..Default::default()108	};109	check_sent_amount_equals_collection_creation_price::<T>(value)?;110	let collection_helpers_address =111		T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());112113	let collection_id = T::CollectionDispatch::create(114		caller,115		CollectionIssuer::User(collection_helpers_address),116		data,117	)118	.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;119	let address = pallet_common::eth::collection_id_to_address(collection_id);120	Ok(address)121}122123fn check_sent_amount_equals_collection_creation_price<T: Config>(value: Value) -> Result<()> {124	let value = value.as_u128();125	let creation_price: u128 = T::CollectionCreationPrice::get()126		.try_into()127		.map_err(|_| ()) // workaround for `expect` requiring `Debug` trait128		.expect("Collection creation price should be convertible to u128");129	if value != creation_price {130		return Err(format!(131			"Sent amount not equals to collection creation price ({creation_price})",132		)133		.into());134	}135	Ok(())136}137138/// @title Contract, which allows users to operate with collections139#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents), enum(derive(PreDispatch)), enum_attr(weight))]140impl<T> EvmCollectionHelpers<T>141where142	T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,143	T::AccountId: From<[u8; 32]>,144{145	/// Create a collection146	/// @return address Address of the newly created collection147	#[weight(<SelfWeightOf<T>>::create_collection())]148	#[solidity(rename_selector = "createCollection")]149	fn create_collection(150		&mut self,151		caller: Caller,152		value: Value,153		data: eth::CreateCollectionData,154	) -> Result<Address> {155		let (caller, name, description, token_prefix) =156			convert_data::<T>(caller, data.name, data.description, data.token_prefix)?;157		if data.mode != eth::CollectionMode::Fungible && data.decimals != 0 {158			return Err("decimals are only supported for NFT and RFT collections".into());159		}160		let mode = match data.mode {161			eth::CollectionMode::Fungible => CollectionMode::Fungible(data.decimals),162			eth::CollectionMode::Nonfungible => CollectionMode::NFT,163			eth::CollectionMode::Refungible => CollectionMode::ReFungible,164		};165166		let properties: BoundedVec<_, _> = data167			.properties168			.into_iter()169			.map(eth::Property::try_into)170			.collect::<Result<Vec<_>>>()?171			.try_into()172			.map_err(|_| "too many properties")?;173174		let token_property_permissions =175			eth::TokenPropertyPermission::into_property_key_permissions(176				data.token_property_permissions,177			)?178			.try_into()179			.map_err(|_| "too many property permissions")?;180181		let limits = if !data.limits.is_empty() {182			Some(183				data.limits184					.into_iter()185					.collect::<Result<up_data_structs::CollectionLimits>>()?,186			)187		} else {188			None189		};190191		let pending_sponsor = data.pending_sponsor.into_option_sub_cross_account::<T>()?;192193		let restricted = if !data.nesting_settings.restricted.is_empty() {194			Some(195				data.nesting_settings196					.restricted197					.iter()198					.map(map_eth_to_id)199					.collect::<Option<BTreeSet<_>>>()200					.ok_or("can't convert address into collection id")?201					.try_into()202					.map_err(|_| "too many collections")?,203			)204		} else {205			None206		};207208		let admin_list = data209			.admin_list210			.into_iter()211			.map(|admin| admin.into_sub_cross_account::<T>())212			.collect::<Result<Vec<_>>>()?;213214		let flags = data.flags;215		if !flags.is_allowed_for_user() {216			return Err("internal flags were used".into());217		}218219		let data = CreateCollectionData {220			name,221			mode,222			description,223			token_prefix,224			properties,225			token_property_permissions,226			limits,227			pending_sponsor,228			access: None,229			permissions: Some(CollectionPermissions {230				access: None,231				mint_mode: None,232				nesting: Some(NestingPermissions {233					token_owner: data.nesting_settings.token_owner,234					collection_admin: data.nesting_settings.collection_admin,235					restricted,236					#[cfg(feature = "runtime-benchmarks")]237					permissive: true,238				}),239			}),240			admin_list,241			flags,242		};243		check_sent_amount_equals_collection_creation_price::<T>(value)?;244		let collection_helpers_address =245			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());246247		let collection_id = T::CollectionDispatch::create(248			caller,249			CollectionIssuer::User(collection_helpers_address),250			data,251		)252		.map_err(dispatch_to_evm::<T>)?;253254		let address = pallet_common::eth::collection_id_to_address(collection_id);255		Ok(address)256	}257258	/// Create an NFT collection259	/// @param name Name of the collection260	/// @param description Informative description of the collection261	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications262	/// @return address Address of the newly created collection263	#[weight(<SelfWeightOf<T>>::create_collection())]264	#[solidity(rename_selector = "createNFTCollection")]265	fn create_nft_collection(266		&mut self,267		caller: Caller,268		value: Value,269		name: String,270		description: String,271		token_prefix: String,272	) -> Result<Address> {273		let (caller, name, description, token_prefix) =274			convert_data::<T>(caller, name, description, token_prefix)?;275		let data = CreateCollectionData {276			name,277			mode: CollectionMode::NFT,278			description,279			token_prefix,280			..Default::default()281		};282		check_sent_amount_equals_collection_creation_price::<T>(value)?;283		let collection_helpers_address =284			T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());285		let collection_id = T::CollectionDispatch::create(286			caller,287			CollectionIssuer::User(collection_helpers_address),288			data,289		)290		.map_err(dispatch_to_evm::<T>)?;291292		let address = pallet_common::eth::collection_id_to_address(collection_id);293		Ok(address)294	}295	/// Create an NFT collection296	/// @param name Name of the collection297	/// @param description Informative description of the collection298	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications299	/// @return address Address of the newly created collection300	#[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]301	#[solidity(hide)]302	#[weight(<SelfWeightOf<T>>::create_collection())]303	fn create_nonfungible_collection(304		&mut self,305		caller: Caller,306		value: Value,307		name: String,308		description: String,309		token_prefix: String,310	) -> Result<Address> {311		create_collection_internal::<T>(312			caller,313			value,314			name,315			CollectionMode::NFT,316			description,317			token_prefix,318		)319	}320321	#[weight(<SelfWeightOf<T>>::create_collection())]322	#[solidity(rename_selector = "createRFTCollection")]323	fn create_rft_collection(324		&mut self,325		caller: Caller,326		value: Value,327		name: String,328		description: String,329		token_prefix: String,330	) -> Result<Address> {331		create_collection_internal::<T>(332			caller,333			value,334			name,335			CollectionMode::ReFungible,336			description,337			token_prefix,338		)339	}340341	#[weight(<SelfWeightOf<T>>::create_collection())]342	#[solidity(rename_selector = "createFTCollection")]343	fn create_fungible_collection(344		&mut self,345		caller: Caller,346		value: Value,347		name: String,348		decimals: u8,349		description: String,350		token_prefix: String,351	) -> Result<Address> {352		create_collection_internal::<T>(353			caller,354			value,355			name,356			CollectionMode::Fungible(decimals),357			description,358			token_prefix,359		)360	}361362	#[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]363	fn make_collection_metadata_compatible(364		&mut self,365		caller: Caller,366		collection: Address,367		base_uri: String,368	) -> Result<()> {369		let caller = T::CrossAccountId::from_eth(caller);370		let collection =371			pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;372		let mut collection =373			<CollectionHandle<T>>::new(collection).ok_or("collection not found")?;374375		if !matches!(376			collection.mode,377			CollectionMode::NFT | CollectionMode::ReFungible378		) {379			return Err("target collection should be either NFT or Refungible".into());380		}381382		self.recorder().consume_sstore()?;383		collection384			.check_is_owner_or_admin(&caller)385			.map_err(dispatch_to_evm::<T>)?;386387		if collection.flags.erc721metadata {388			return Err("target collection is already Erc721Metadata compatible".into());389		}390		collection.flags.erc721metadata = true;391392		let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);393		if all_permissions.get(&key::url()).is_none() {394			self.recorder().consume_sstore()?;395			<PalletCommon<T>>::set_property_permission(396				&collection,397				&caller,398				up_data_structs::PropertyKeyPermission {399					key: key::url(),400					permission: up_data_structs::PropertyPermission {401						mutable: true,402						collection_admin: true,403						token_owner: false,404					},405				},406			)407			.map_err(dispatch_to_evm::<T>)?;408		}409		if all_permissions.get(&key::suffix()).is_none() {410			self.recorder().consume_sstore()?;411			<PalletCommon<T>>::set_property_permission(412				&collection,413				&caller,414				up_data_structs::PropertyKeyPermission {415					key: key::suffix(),416					permission: up_data_structs::PropertyPermission {417						mutable: true,418						collection_admin: true,419						token_owner: false,420					},421				},422			)423			.map_err(dispatch_to_evm::<T>)?;424		}425426		let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);427		if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {428			self.recorder().consume_sstore()?;429			<PalletCommon<T>>::set_collection_properties(430				&collection,431				&caller,432				[up_data_structs::Property {433					key: key::base_uri(),434					value: base_uri435						.into_bytes()436						.try_into()437						.map_err(|_| "base uri is too large")?,438				}]439				.into_iter(),440			)441			.map_err(dispatch_to_evm::<T>)?;442		}443444		self.recorder().consume_sstore()?;445		collection.save().map_err(dispatch_to_evm::<T>)?;446447		Ok(())448	}449450	#[weight(<SelfWeightOf<T>>::destroy_collection())]451	fn destroy_collection(&mut self, caller: Caller, collection_address: Address) -> Result<()> {452		let caller = T::CrossAccountId::from_eth(caller);453454		let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)455			.ok_or("Invalid collection address format")?;456		<Pallet<T>>::destroy_collection_internal(caller, collection_id)457			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)458	}459460	/// Check if a collection exists461	/// @param collectionAddress Address of the collection in question462	/// @return bool Does the collection exist?463	fn is_collection_exist(&self, _caller: Caller, collection_address: Address) -> Result<bool> {464		if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {465			let collection_id = id;466			return Ok(<CollectionById<T>>::contains_key(collection_id));467		}468469		Ok(false)470	}471472	fn collection_creation_fee(&self) -> Result<Value> {473		let price: u128 = T::CollectionCreationPrice::get()474			.try_into()475			.map_err(|_| ()) // workaround for `expect` requiring `Debug` trait476			.expect("Collection creation price should be convertible to u128");477		Ok(price.into())478	}479480	/// Returns address of a collection.481	/// @param collectionId  - CollectionId  of the collection482	/// @return eth mirror address of the collection483	fn collection_address(&self, collection_id: u32) -> Result<Address> {484		Ok(collection_id_to_address(collection_id.into()))485	}486487	/// Returns collectionId of a collection.488	/// @param collectionAddress  - Eth address of the collection489	/// @return collectionId of the collection490	fn collection_id(&self, collection_address: Address) -> Result<u32> {491		map_eth_to_id(&collection_address)492			.map(|id| id.0)493			.ok_or(Error::Revert(format!(494				"failed to convert address {collection_address} into collectionId."495			)))496	}497}498499/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]500pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);501impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>502	for CollectionHelpersOnMethodCall<T>503where504	T::AccountId: From<[u8; 32]>,505{506	fn is_reserved(contract: &sp_core::H160) -> bool {507		contract == &T::ContractAddress::get()508	}509510	fn is_used(contract: &sp_core::H160) -> bool {511		contract == &T::ContractAddress::get()512	}513514	fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {515		if handle.code_address() != T::ContractAddress::get() {516			return None;517		}518519		let helpers =520			EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));521		pallet_evm_coder_substrate::call(handle, helpers)522	}523524	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {525		(contract == &T::ContractAddress::get())526			.then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())527	}528}529530generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);531generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);532533fn error_field_too_long(feild: &str, bound: usize) -> Error {534	Error::Revert(format!("{feild} is too long. Max length is {bound}."))535}