12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};22use frame_support::traits::Get;2324use crate::Pallet;2526use pallet_common::{27 CollectionById,28 dispatch::CollectionDispatch,29 erc::{30 CollectionHelpersEvents,31 static_property::{key},32 },33 Pallet as PalletCommon,34};35use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};36use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};37use sp_std::vec;38use up_data_structs::{39 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,40 CollectionMode, PropertyValue, CollectionFlags,41};4243use crate::{Config, SelfWeightOf, weights::WeightInfo};4445use sp_std::vec::Vec;46use alloc::format;474849pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);50impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {51 fn recorder(&self) -> &SubstrateRecorder<T> {52 &self.053 }5455 fn into_recorder(self) -> SubstrateRecorder<T> {56 self.057 }58}5960fn convert_data<T: Config>(61 caller: caller,62 name: string,63 description: string,64 token_prefix: string,65) -> Result<(66 T::CrossAccountId,67 CollectionName,68 CollectionDescription,69 CollectionTokenPrefix,70)> {71 let caller = T::CrossAccountId::from_eth(caller);72 let name = name73 .encode_utf16()74 .collect::<Vec<u16>>()75 .try_into()76 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;77 let description = description78 .encode_utf16()79 .collect::<Vec<u16>>()80 .try_into()81 .map_err(|_| {82 error_field_too_long(stringify!(description), CollectionDescription::bound())83 })?;84 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {85 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())86 })?;87 Ok((caller, name, description, token_prefix))88}8990fn create_refungible_collection_internal<91 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,92>(93 caller: caller,94 value: value,95 name: string,96 description: string,97 token_prefix: string,98) -> Result<address> {99 let (caller, name, description, token_prefix) =100 convert_data::<T>(caller, name, description, token_prefix)?;101 let data = CreateCollectionData {102 name,103 mode: CollectionMode::ReFungible,104 description,105 token_prefix,106 ..Default::default()107 };108 check_sent_amount_equals_collection_creation_price::<T>(value)?;109 let collection_helpers_address =110 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());111112 let collection_id = T::CollectionDispatch::create(113 caller.clone(),114 collection_helpers_address,115 data,116 Default::default(),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(|_| ()) 128 .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 ({0})",132 creation_price133 )134 .into());135 }136 Ok(())137}138139140#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]141impl<T> EvmCollectionHelpers<T>142where143 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,144{145 146 147 148 149 150 #[weight(<SelfWeightOf<T>>::create_collection())]151 #[solidity(rename_selector = "createNFTCollection")]152 fn create_nft_collection(153 &mut self,154 caller: caller,155 value: value,156 name: string,157 description: string,158 token_prefix: string,159 ) -> Result<address> {160 let (caller, name, description, token_prefix) =161 convert_data::<T>(caller, name, description, token_prefix)?;162 let data = CreateCollectionData {163 name,164 mode: CollectionMode::NFT,165 description,166 token_prefix,167 ..Default::default()168 };169 check_sent_amount_equals_collection_creation_price::<T>(value)?;170 let collection_helpers_address =171 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());172 let collection_id = T::CollectionDispatch::create(173 caller,174 collection_helpers_address,175 data,176 Default::default(),177 )178 .map_err(dispatch_to_evm::<T>)?;179180 let address = pallet_common::eth::collection_id_to_address(collection_id);181 Ok(address)182 }183 184 185 186 187 188 #[weight(<SelfWeightOf<T>>::create_collection())]189 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]190 #[solidity(hide)]191 fn create_nonfungible_collection(192 &mut self,193 caller: caller,194 value: value,195 name: string,196 description: string,197 token_prefix: string,198 ) -> Result<address> {199 self.create_nft_collection(caller, value, name, description, token_prefix)200 }201202 #[weight(<SelfWeightOf<T>>::create_collection())]203 #[solidity(rename_selector = "createRFTCollection")]204 fn create_rft_collection(205 &mut self,206 caller: caller,207 value: value,208 name: string,209 description: string,210 token_prefix: string,211 ) -> Result<address> {212 create_refungible_collection_internal::<T>(caller, value, name, description, token_prefix)213 }214215 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]216 fn make_collection_metadata_compatible(217 &mut self,218 caller: caller,219 collection: address,220 base_uri: string,221 ) -> Result<()> {222 let caller = T::CrossAccountId::from_eth(caller);223 let collection =224 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;225 let mut collection =226 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;227228 if !matches!(229 collection.mode,230 CollectionMode::NFT | CollectionMode::ReFungible231 ) {232 return Err("target collection should be either NFT or Refungible".into());233 }234235 self.recorder().consume_sstore()?;236 collection237 .check_is_owner_or_admin(&caller)238 .map_err(dispatch_to_evm::<T>)?;239240 if collection.flags.erc721metadata {241 return Err("target collection is already Erc721Metadata compatible".into());242 }243 collection.flags.erc721metadata = true;244245 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);246 if all_permissions.get(&key::url()).is_none() {247 self.recorder().consume_sstore()?;248 <PalletCommon<T>>::set_property_permission(249 &collection,250 &caller,251 up_data_structs::PropertyKeyPermission {252 key: key::url(),253 permission: up_data_structs::PropertyPermission {254 mutable: true,255 collection_admin: true,256 token_owner: false,257 },258 },259 )260 .map_err(dispatch_to_evm::<T>)?;261 }262 if all_permissions.get(&key::suffix()).is_none() {263 self.recorder().consume_sstore()?;264 <PalletCommon<T>>::set_property_permission(265 &collection,266 &caller,267 up_data_structs::PropertyKeyPermission {268 key: key::suffix(),269 permission: up_data_structs::PropertyPermission {270 mutable: true,271 collection_admin: true,272 token_owner: false,273 },274 },275 )276 .map_err(dispatch_to_evm::<T>)?;277 }278279 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);280 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {281 self.recorder().consume_sstore()?;282 <PalletCommon<T>>::set_collection_properties(283 &collection,284 &caller,285 vec![up_data_structs::Property {286 key: key::base_uri(),287 value: base_uri288 .into_bytes()289 .try_into()290 .map_err(|_| "base uri is too large")?,291 }],292 )293 .map_err(dispatch_to_evm::<T>)?;294 }295296 self.recorder().consume_sstore()?;297 collection.save().map_err(dispatch_to_evm::<T>)?;298299 Ok(())300 }301302 #[weight(<SelfWeightOf<T>>::destroy_collection())]303 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {304 let caller = T::CrossAccountId::from_eth(caller);305306 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)307 .ok_or("Invalid collection address format")?;308 <Pallet<T>>::destroy_collection_internal(caller, collection_id)309 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)310 }311312 313 314 315 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {316 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {317 let collection_id = id;318 return Ok(<CollectionById<T>>::contains_key(collection_id));319 }320321 Ok(false)322 }323324 fn collection_creation_fee(&self) -> Result<value> {325 let price: u128 = T::CollectionCreationPrice::get()326 .try_into()327 .map_err(|_| ()) 328 .expect("Collection creation price should be convertible to u128");329 Ok(price.into())330 }331}332333334pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);335impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>336 for CollectionHelpersOnMethodCall<T>337{338 fn is_reserved(contract: &sp_core::H160) -> bool {339 contract == &T::ContractAddress::get()340 }341342 fn is_used(contract: &sp_core::H160) -> bool {343 contract == &T::ContractAddress::get()344 }345346 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {347 if handle.code_address() != T::ContractAddress::get() {348 return None;349 }350351 let helpers =352 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));353 pallet_evm_coder_substrate::call(handle, helpers)354 }355356 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {357 (contract == &T::ContractAddress::get())358 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())359 }360}361362generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);363generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);364365fn error_field_too_long(feild: &str, bound: usize) -> Error {366 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))367}