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 Pallet as PalletCommon,31};32use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};33use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};34use sp_std::vec;35use up_data_structs::{36 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,37 CollectionMode, PropertyValue, CollectionFlags,38};3940use crate::{Config, SelfWeightOf, weights::WeightInfo};4142use sp_std::vec::Vec;43use alloc::format;444546pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);47impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {48 fn recorder(&self) -> &SubstrateRecorder<T> {49 &self.050 }5152 fn into_recorder(self) -> SubstrateRecorder<T> {53 self.054 }55}5657fn convert_data<T: Config>(58 caller: caller,59 name: string,60 description: string,61 token_prefix: string,62 base_uri: string,63) -> Result<(64 T::CrossAccountId,65 CollectionName,66 CollectionDescription,67 CollectionTokenPrefix,68 PropertyValue,69)> {70 let caller = T::CrossAccountId::from_eth(caller);71 let name = name72 .encode_utf16()73 .collect::<Vec<u16>>()74 .try_into()75 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;76 let description = description77 .encode_utf16()78 .collect::<Vec<u16>>()79 .try_into()80 .map_err(|_| {81 error_field_too_long(stringify!(description), CollectionDescription::bound())82 })?;83 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {84 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())85 })?;86 let base_uri_value = base_uri87 .into_bytes()88 .try_into()89 .map_err(|_| error_field_too_long(stringify!(token_prefix), PropertyValue::bound()))?;90 Ok((caller, name, description, token_prefix, base_uri_value))91}9293fn default_url_pkp() -> up_data_structs::PropertyKeyPermission {94 up_data_structs::PropertyKeyPermission {95 key: key::url(),96 permission: up_data_structs::PropertyPermission {97 mutable: true,98 collection_admin: true,99 token_owner: false,100 },101 }102}103fn default_suffix_pkp() -> up_data_structs::PropertyKeyPermission {104 up_data_structs::PropertyKeyPermission {105 key: key::suffix(),106 permission: up_data_structs::PropertyPermission {107 mutable: true,108 collection_admin: true,109 token_owner: false,110 },111 }112}113fn make_data<T: Config>(114 name: CollectionName,115 mode: CollectionMode,116 description: CollectionDescription,117 token_prefix: CollectionTokenPrefix,118 base_uri_value: PropertyValue,119 add_properties: bool,120) -> Result<CreateCollectionData<T::AccountId>> {121 let token_property_permissions = if add_properties {122 vec![default_url_pkp(), default_suffix_pkp()]123 .try_into()124 .map_err(|e| Error::Revert(format!("{:?}", e)))?125 } else {126 up_data_structs::CollectionPropertiesPermissionsVec::default()127 };128 let properties = if add_properties {129 let mut properties = vec![130 up_data_structs::Property {131 key: key::schema_name(),132 value: property_value::erc721(),133 },134 up_data_structs::Property {135 key: key::schema_version(),136 value: property_value::schema_version(),137 },138 ];139 if !base_uri_value.is_empty() {140 properties.push(up_data_structs::Property {141 key: key::base_uri(),142 value: base_uri_value,143 })144 }145 properties146 .try_into()147 .map_err(|e| Error::Revert(format!("{:?}", e)))?148 } else {149 up_data_structs::CollectionPropertiesVec::default()150 };151152 let data = CreateCollectionData {153 name,154 mode,155 description,156 token_prefix,157 token_property_permissions,158 properties,159 ..Default::default()160 };161 Ok(data)162}163164fn create_refungible_collection_internal<165 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,166>(167 caller: caller,168 value: value,169 name: string,170 description: string,171 token_prefix: string,172 base_uri: string,173 add_properties: bool,174) -> Result<address> {175 let (caller, name, description, token_prefix, base_uri_value) =176 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;177 let data = make_data::<T>(178 name,179 CollectionMode::ReFungible,180 description,181 token_prefix,182 base_uri_value,183 add_properties,184 )?;185 check_sent_amount_equals_collection_creation_price::<T>(value)?;186 let collection_helpers_address =187 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());188189 let collection_id = T::CollectionDispatch::create(190 caller.clone(),191 collection_helpers_address,192 data,193 CollectionFlags {194 erc721metadata: add_properties,195 ..Default::default()196 },197 )198 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;199 let address = pallet_common::eth::collection_id_to_address(collection_id);200 Ok(address)201}202203fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {204 let value = value.as_u128();205 let creation_price: u128 = T::CollectionCreationPrice::get()206 .try_into()207 .map_err(|_| ()) 208 .expect("Collection creation price should be convertible to u128");209 if value != creation_price {210 return Err(format!(211 "Sent amount not equals to collection creation price ({0})",212 creation_price213 )214 .into());215 }216 Ok(())217}218219220#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]221impl<T> EvmCollectionHelpers<T>222where223 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,224{225 226 227 228 229 230 #[weight(<SelfWeightOf<T>>::create_collection())]231 #[solidity(rename_selector = "createNFTCollection")]232 fn create_nft_collection(233 &mut self,234 caller: caller,235 value: value,236 name: string,237 description: string,238 token_prefix: string,239 ) -> Result<address> {240 let (caller, name, description, token_prefix, _base_uri_value) =241 convert_data::<T>(caller, name, description, token_prefix, "".into())?;242 let data = make_data::<T>(243 name,244 CollectionMode::NFT,245 description,246 token_prefix,247 Default::default(),248 false,249 )?;250 check_sent_amount_equals_collection_creation_price::<T>(value)?;251 let collection_helpers_address =252 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());253 let collection_id = T::CollectionDispatch::create(254 caller,255 collection_helpers_address,256 data,257 Default::default(),258 )259 .map_err(dispatch_to_evm::<T>)?;260261 let address = pallet_common::eth::collection_id_to_address(collection_id);262 Ok(address)263 }264 265 266 267 268 269 #[weight(<SelfWeightOf<T>>::create_collection())]270 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]271 fn create_nonfungible_collection(272 &mut self,273 caller: caller,274 value: value,275 name: string,276 description: string,277 token_prefix: string,278 ) -> Result<address> {279 self.create_nft_collection(caller, value, name, description, token_prefix)280 }281282 #[weight(<SelfWeightOf<T>>::create_collection())]283 #[solidity(rename_selector = "createERC721MetadataCompatibleNFTCollection")]284 fn create_nonfungible_collection_with_properties(285 &mut self,286 caller: caller,287 value: value,288 name: string,289 description: string,290 token_prefix: string,291 base_uri: string,292 ) -> Result<address> {293 let (caller, name, description, token_prefix, base_uri_value) =294 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;295 let data = make_data::<T>(296 name,297 CollectionMode::NFT,298 description,299 token_prefix,300 base_uri_value,301 true,302 )?;303 check_sent_amount_equals_collection_creation_price::<T>(value)?;304 let collection_helpers_address =305 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());306 let collection_id = T::CollectionDispatch::create(307 caller,308 collection_helpers_address,309 data,310 CollectionFlags {311 erc721metadata: true,312 ..Default::default()313 },314 )315 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;316317 let address = pallet_common::eth::collection_id_to_address(collection_id);318 Ok(address)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_refungible_collection_internal::<T>(332 caller,333 value,334 name,335 description,336 token_prefix,337 Default::default(),338 false,339 )340 }341342 #[weight(<SelfWeightOf<T>>::create_collection())]343 #[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]344 fn create_refungible_collection_with_properties(345 &mut self,346 caller: caller,347 value: value,348 name: string,349 description: string,350 token_prefix: string,351 base_uri: string,352 ) -> Result<address> {353 create_refungible_collection_internal::<T>(354 caller,355 value,356 name,357 description,358 token_prefix,359 base_uri,360 true,361 )362 }363364 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]365 fn make_collection_metadata_compatible(366 &mut self,367 caller: caller,368 collection: address,369 base_uri: string,370 ) -> Result<()> {371 let caller = T::CrossAccountId::from_eth(caller);372 let collection =373 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;374 let mut collection =375 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;376377 if !matches!(378 collection.mode,379 CollectionMode::NFT | CollectionMode::ReFungible380 ) {381 return Err("target collection should be either NFT or Refungible".into());382 }383384 self.recorder().consume_sstore()?;385 collection386 .check_is_owner_or_admin(&caller)387 .map_err(dispatch_to_evm::<T>)?;388389 if collection.flags.erc721metadata {390 return Err("target collection is already Erc721Metadata compatible".into());391 }392 collection.flags.erc721metadata = true;393394 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);395 if all_permissions.get(&key::url()).is_none() {396 self.recorder().consume_sstore()?;397 <PalletCommon<T>>::set_property_permission(&collection, &caller, default_url_pkp())398 .map_err(dispatch_to_evm::<T>)?;399 }400 if all_permissions.get(&key::suffix()).is_none() {401 self.recorder().consume_sstore()?;402 <PalletCommon<T>>::set_property_permission(&collection, &caller, default_suffix_pkp())403 .map_err(dispatch_to_evm::<T>)?;404 }405406 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);407 let mut new_properties = vec![];408 if all_properties.get(&key::schema_name()).is_none() {409 self.recorder().consume_sstore()?;410 new_properties.push(up_data_structs::Property {411 key: key::schema_name(),412 value: property_value::erc721(),413 });414 new_properties.push(up_data_structs::Property {415 key: key::schema_version(),416 value: property_value::schema_version(),417 });418 }419 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {420 new_properties.push(up_data_structs::Property {421 key: key::base_uri(),422 value: base_uri423 .into_bytes()424 .try_into()425 .map_err(|_| "base uri is too large")?,426 });427 }428429 if !new_properties.is_empty() {430 self.recorder().consume_sstore()?;431 <PalletCommon<T>>::set_collection_properties(&collection, &caller, new_properties)432 .map_err(dispatch_to_evm::<T>)?;433 }434435 self.recorder().consume_sstore()?;436 collection.save().map_err(dispatch_to_evm::<T>)?;437438 Ok(())439 }440441 442 443 444 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {445 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {446 let collection_id = id;447 return Ok(<CollectionById<T>>::contains_key(collection_id));448 }449450 Ok(false)451 }452453 fn collection_creation_fee(&self) -> Result<value> {454 let price: u128 = T::CollectionCreationPrice::get()455 .try_into()456 .map_err(|_| ()) 457 .expect("Collection creation price should be convertible to u128");458 Ok(price.into())459 }460}461462463pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);464impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>465 for CollectionHelpersOnMethodCall<T>466{467 fn is_reserved(contract: &sp_core::H160) -> bool {468 contract == &T::ContractAddress::get()469 }470471 fn is_used(contract: &sp_core::H160) -> bool {472 contract == &T::ContractAddress::get()473 }474475 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {476 if handle.code_address() != T::ContractAddress::get() {477 return None;478 }479480 let helpers =481 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));482 pallet_evm_coder_substrate::call(handle, helpers)483 }484485 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {486 (contract == &T::ContractAddress::get())487 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())488 }489}490491generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);492generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);493494fn error_field_too_long(feild: &str, bound: usize) -> Error {495 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))496}