12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};22use frame_support::traits::Get;23use pallet_common::{24 CollectionById,25 dispatch::CollectionDispatch,26 erc::{27 CollectionHelpersEvents,28 static_property::{key, value as property_value},29 },30};31use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};32use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};33use up_data_structs::{34 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,35 CollectionMode, PropertyValue,36};3738use crate::{Config, SelfWeightOf, weights::WeightInfo};3940use sp_std::vec::Vec;41use alloc::format;424344pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);45impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {46 fn recorder(&self) -> &SubstrateRecorder<T> {47 &self.048 }4950 fn into_recorder(self) -> SubstrateRecorder<T> {51 self.052 }53}5455fn convert_data<T: Config>(56 caller: caller,57 name: string,58 description: string,59 token_prefix: string,60 base_uri: string,61) -> Result<(62 T::CrossAccountId,63 CollectionName,64 CollectionDescription,65 CollectionTokenPrefix,66 PropertyValue,67)> {68 let caller = T::CrossAccountId::from_eth(caller);69 let name = name70 .encode_utf16()71 .collect::<Vec<u16>>()72 .try_into()73 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;74 let description = description75 .encode_utf16()76 .collect::<Vec<u16>>()77 .try_into()78 .map_err(|_| {79 error_field_too_long(stringify!(description), CollectionDescription::bound())80 })?;81 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {82 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())83 })?;84 let base_uri_value = base_uri85 .into_bytes()86 .try_into()87 .map_err(|_| error_field_too_long(stringify!(token_prefix), PropertyValue::bound()))?;88 Ok((caller, name, description, token_prefix, base_uri_value))89}9091fn make_data<T: Config>(92 name: CollectionName,93 mode: CollectionMode,94 description: CollectionDescription,95 token_prefix: CollectionTokenPrefix,96 base_uri_value: PropertyValue,97 add_properties: bool,98) -> Result<CreateCollectionData<T::AccountId>> {99 let mut properties = up_data_structs::CollectionPropertiesVec::default();100 let mut token_property_permissions =101 up_data_structs::CollectionPropertiesPermissionsVec::default();102103 token_property_permissions104 .try_push(up_data_structs::PropertyKeyPermission {105 key: key::url(),106 permission: up_data_structs::PropertyPermission {107 mutable: false,108 collection_admin: true,109 token_owner: false,110 },111 })112 .map_err(|e| Error::Revert(format!("{:?}", e)))?;113114 if add_properties {115 token_property_permissions116 .try_push(up_data_structs::PropertyKeyPermission {117 key: key::suffix(),118 permission: up_data_structs::PropertyPermission {119 mutable: true,120 collection_admin: true,121 token_owner: false,122 },123 })124 .map_err(|e| Error::Revert(format!("{:?}", e)))?;125126 token_property_permissions127 .try_push(up_data_structs::PropertyKeyPermission {128 key: key::url(),129 permission: up_data_structs::PropertyPermission {130 mutable: true,131 collection_admin: true,132 token_owner: false,133 },134 })135 .map_err(|e| Error::Revert(format!("{:?}", e)))?;136137 properties138 .try_push(up_data_structs::Property {139 key: key::schema_name(),140 value: property_value::erc721(),141 })142 .map_err(|e| Error::Revert(format!("{:?}", e)))?;143144 properties145 .try_push(up_data_structs::Property {146 key: key::schema_version(),147 value: property_value::schema_version(),148 })149 .map_err(|e| Error::Revert(format!("{:?}", e)))?;150151 properties152 .try_push(up_data_structs::Property {153 key: key::erc721_metadata(),154 value: property_value::erc721_metadata_supported(),155 })156 .map_err(|e| Error::Revert(format!("{:?}", e)))?;157158 if !base_uri_value.is_empty() {159 properties160 .try_push(up_data_structs::Property {161 key: key::base_uri(),162 value: base_uri_value,163 })164 .map_err(|e| Error::Revert(format!("{:?}", e)))?;165 }166 }167168 let data = CreateCollectionData {169 name,170 mode,171 description,172 token_prefix,173 token_property_permissions,174 properties,175 ..Default::default()176 };177 Ok(data)178}179180fn create_refungible_collection_internal<181 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,182>(183 caller: caller,184 value: value,185 name: string,186 description: string,187 token_prefix: string,188 base_uri: string,189 add_properties: bool,190) -> Result<address> {191 let (caller, name, description, token_prefix, base_uri_value) =192 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;193 let data = make_data::<T>(194 name,195 CollectionMode::ReFungible,196 description,197 token_prefix,198 base_uri_value,199 add_properties,200 )?;201 check_sent_amount_equals_collection_creation_price::<T>(value)?;202 let collection_helpers_address =203 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());204205 let collection_id =206 T::CollectionDispatch::create(caller.clone(), collection_helpers_address, data)207 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;208 let address = pallet_common::eth::collection_id_to_address(collection_id);209 Ok(address)210}211212fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {213 let value = value.as_u128();214 let creation_price: u128 = T::CollectionCreationPrice::get()215 .try_into()216 .map_err(|_| ()) 217 .expect("Collection creation price should be convertible to u128");218 if value != creation_price {219 return Err(format!(220 "Sent amount not equals to collection creation price ({0})",221 creation_price222 )223 .into());224 }225 Ok(())226}227228229#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]230impl<T> EvmCollectionHelpers<T>231where232 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,233{234 235 236 237 238 239 #[weight(<SelfWeightOf<T>>::create_collection())]240 #[solidity(rename_selector = "createNFTCollection")]241 fn create_nft_collection(242 &mut self,243 caller: caller,244 value: value,245 name: string,246 description: string,247 token_prefix: string,248 ) -> Result<address> {249 let (caller, name, description, token_prefix, _base_uri_value) =250 convert_data::<T>(caller, name, description, token_prefix, "".into())?;251 let data = make_data::<T>(252 name,253 CollectionMode::NFT,254 description,255 token_prefix,256 Default::default(),257 false,258 )?;259 check_sent_amount_equals_collection_creation_price::<T>(value)?;260 let collection_helpers_address =261 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());262 let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)263 .map_err(dispatch_to_evm::<T>)?;264265 let address = pallet_common::eth::collection_id_to_address(collection_id);266 Ok(address)267 }268 269 270 271 272 273 #[weight(<SelfWeightOf<T>>::create_collection())]274 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]275 fn create_nonfungible_collection(276 &mut self,277 caller: caller,278 value: value,279 name: string,280 description: string,281 token_prefix: string,282 ) -> Result<address> {283 self.create_nft_collection(caller, value, name, description, token_prefix)284 }285286 #[weight(<SelfWeightOf<T>>::create_collection())]287 #[solidity(rename_selector = "createERC721MetadataCompatibleNFTCollection")]288 fn create_nonfungible_collection_with_properties(289 &mut self,290 caller: caller,291 value: value,292 name: string,293 description: string,294 token_prefix: string,295 base_uri: string,296 ) -> Result<address> {297 let (caller, name, description, token_prefix, base_uri_value) =298 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;299 let data = make_data::<T>(300 name,301 CollectionMode::NFT,302 description,303 token_prefix,304 base_uri_value,305 true,306 )?;307 check_sent_amount_equals_collection_creation_price::<T>(value)?;308 let collection_helpers_address =309 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());310 let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)311 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;312313 let address = pallet_common::eth::collection_id_to_address(collection_id);314 Ok(address)315 }316317 #[weight(<SelfWeightOf<T>>::create_collection())]318 #[solidity(rename_selector = "createRFTCollection")]319 fn create_rft_collection(320 &mut self,321 caller: caller,322 value: value,323 name: string,324 description: string,325 token_prefix: string,326 ) -> Result<address> {327 create_refungible_collection_internal::<T>(328 caller,329 value,330 name,331 description,332 token_prefix,333 Default::default(),334 false,335 )336 }337338 #[weight(<SelfWeightOf<T>>::create_collection())]339 #[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]340 fn create_refungible_collection_with_properties(341 &mut self,342 caller: caller,343 value: value,344 name: string,345 description: string,346 token_prefix: string,347 base_uri: string,348 ) -> Result<address> {349 create_refungible_collection_internal::<T>(350 caller,351 value,352 name,353 description,354 token_prefix,355 base_uri,356 true,357 )358 }359360 361 362 363 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {364 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {365 let collection_id = id;366 return Ok(<CollectionById<T>>::contains_key(collection_id));367 }368369 Ok(false)370 }371372 fn collection_creation_fee(&self) -> Result<value> {373 let price: u128 = T::CollectionCreationPrice::get()374 .try_into()375 .map_err(|_| ()) 376 .expect("Collection creation price should be convertible to u128");377 Ok(price.into())378 }379}380381382pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);383impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>384 for CollectionHelpersOnMethodCall<T>385{386 fn is_reserved(contract: &sp_core::H160) -> bool {387 contract == &T::ContractAddress::get()388 }389390 fn is_used(contract: &sp_core::H160) -> bool {391 contract == &T::ContractAddress::get()392 }393394 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {395 if handle.code_address() != T::ContractAddress::get() {396 return None;397 }398399 let helpers =400 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));401 pallet_evm_coder_substrate::call(handle, helpers)402 }403404 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {405 (contract == &T::ContractAddress::get())406 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())407 }408}409410generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);411generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);412413fn error_field_too_long(feild: &str, bound: usize) -> Error {414 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))415}