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: false,120 collection_admin: true,121 token_owner: false,122 },123 })124 .map_err(|e| Error::Revert(format!("{:?}", e)))?;125126 properties127 .try_push(up_data_structs::Property {128 key: key::schema_name(),129 value: property_value::erc721(),130 })131 .map_err(|e| Error::Revert(format!("{:?}", e)))?;132133 properties134 .try_push(up_data_structs::Property {135 key: key::erc721_metadata(),136 value: property_value::erc721_metadata_supported(),137 })138 .map_err(|e| Error::Revert(format!("{:?}", e)))?;139140 if !base_uri_value.is_empty() {141 properties142 .try_push(up_data_structs::Property {143 key: key::base_uri(),144 value: base_uri_value,145 })146 .map_err(|e| Error::Revert(format!("{:?}", e)))?;147 }148 }149150 let data = CreateCollectionData {151 name,152 mode,153 description,154 token_prefix,155 token_property_permissions,156 properties,157 ..Default::default()158 };159 Ok(data)160}161162fn create_refungible_collection_internal<163 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,164>(165 caller: caller,166 value: value,167 name: string,168 description: string,169 token_prefix: string,170 base_uri: string,171 add_properties: bool,172) -> Result<address> {173 let (caller, name, description, token_prefix, base_uri_value) =174 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;175 let data = make_data::<T>(176 name,177 CollectionMode::ReFungible,178 description,179 token_prefix,180 base_uri_value,181 add_properties,182 )?;183 check_sent_amount_equals_collection_creation_price::<T>(value)?;184 let collection_helpers_address =185 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());186187 let collection_id =188 T::CollectionDispatch::create(caller.clone(), collection_helpers_address, data)189 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;190 let address = pallet_common::eth::collection_id_to_address(collection_id);191 Ok(address)192}193194fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {195 let value = value.as_u128();196 let creation_price: u128 = T::CollectionCreationPrice::get()197 .try_into()198 .map_err(|_| ()) 199 .expect("Collection creation price should be convertible to u128");200 if value != creation_price {201 return Err(format!(202 "Sent amount not equals to collection creation price ({0})",203 creation_price204 )205 .into());206 }207 Ok(())208}209210211#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]212impl<T> EvmCollectionHelpers<T>213where214 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,215{216 217 218 219 220 221 #[weight(<SelfWeightOf<T>>::create_collection())]222 #[solidity(rename_selector = "createNFTCollection")]223 fn create_nft_collection(224 &mut self,225 caller: caller,226 value: value,227 name: string,228 description: string,229 token_prefix: string,230 ) -> Result<address> {231 let (caller, name, description, token_prefix, _base_uri_value) =232 convert_data::<T>(caller, name, description, token_prefix, "".into())?;233 let data = make_data::<T>(234 name,235 CollectionMode::NFT,236 description,237 token_prefix,238 Default::default(),239 false,240 )?;241 check_sent_amount_equals_collection_creation_price::<T>(value)?;242 let collection_helpers_address =243 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());244 let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)245 .map_err(dispatch_to_evm::<T>)?;246247 let address = pallet_common::eth::collection_id_to_address(collection_id);248 Ok(address)249 }250 251 252 253 254 255 #[weight(<SelfWeightOf<T>>::create_collection())]256 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]257 fn create_nonfungible_collection(258 &mut self,259 caller: caller,260 value: value,261 name: string,262 description: string,263 token_prefix: string,264 ) -> Result<address> {265 self.create_nft_collection(caller, value, name, description, token_prefix)266 }267268 #[weight(<SelfWeightOf<T>>::create_collection())]269 #[solidity(rename_selector = "createERC721MetadataNFTCollection")]270 fn create_nonfungible_collection_with_properties(271 &mut self,272 caller: caller,273 value: value,274 name: string,275 description: string,276 token_prefix: string,277 base_uri: string,278 ) -> Result<address> {279 let (caller, name, description, token_prefix, base_uri_value) =280 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;281 let data = make_data::<T>(282 name,283 CollectionMode::NFT,284 description,285 token_prefix,286 base_uri_value,287 true,288 )?;289 check_sent_amount_equals_collection_creation_price::<T>(value)?;290 let collection_helpers_address =291 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());292 let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data)293 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;294295 let address = pallet_common::eth::collection_id_to_address(collection_id);296 Ok(address)297 }298299 #[weight(<SelfWeightOf<T>>::create_collection())]300 #[solidity(rename_selector = "createRFTCollection")]301 fn create_rft_collection(302 &mut self,303 caller: caller,304 value: value,305 name: string,306 description: string,307 token_prefix: string,308 ) -> Result<address> {309 create_refungible_collection_internal::<T>(310 caller,311 value,312 name,313 description,314 token_prefix,315 Default::default(),316 false,317 )318 }319320 #[weight(<SelfWeightOf<T>>::create_collection())]321 #[deprecated(note = "mathod was renamed to `create_rft_collection`, prefer it instead")]322 fn create_refungible_collection(323 &mut self,324 caller: caller,325 value: value,326 name: string,327 description: string,328 token_prefix: string,329 ) -> Result<address> {330 create_refungible_collection_internal::<T>(331 caller,332 value,333 name,334 description,335 token_prefix,336 Default::default(),337 false,338 )339 }340341 #[weight(<SelfWeightOf<T>>::create_collection())]342 #[solidity(rename_selector = "createERC721MetadataRFTCollection")]343 fn create_refungible_collection_with_properties(344 &mut self,345 caller: caller,346 value: value,347 name: string,348 description: string,349 token_prefix: string,350 base_uri: string,351 ) -> Result<address> {352 create_refungible_collection_internal::<T>(353 caller,354 value,355 name,356 description,357 token_prefix,358 base_uri,359 true,360 )361 }362363 364 365 366 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {367 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {368 let collection_id = id;369 return Ok(<CollectionById<T>>::contains_key(collection_id));370 }371372 Ok(false)373 }374375 fn collection_creation_fee(&self) -> Result<value> {376 let price: u128 = T::CollectionCreationPrice::get()377 .try_into()378 .map_err(|_| ()) 379 .expect("Collection creation price should be convertible to u128");380 Ok(price.into())381 }382}383384385pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);386impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>387 for CollectionHelpersOnMethodCall<T>388{389 fn is_reserved(contract: &sp_core::H160) -> bool {390 contract == &T::ContractAddress::get()391 }392393 fn is_used(contract: &sp_core::H160) -> bool {394 contract == &T::ContractAddress::get()395 }396397 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {398 if handle.code_address() != T::ContractAddress::get() {399 return None;400 }401402 let helpers =403 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));404 pallet_evm_coder_substrate::call(handle, helpers)405 }406407 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {408 (contract == &T::ContractAddress::get())409 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())410 }411}412413generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);414generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);415416fn error_field_too_long(feild: &str, bound: usize) -> Error {417 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))418}