12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{22 abi::AbiType, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight,23};24use frame_support::traits::Get;25use crate::Pallet;2627use pallet_common::{28 CollectionById,29 dispatch::CollectionDispatch,30 erc::{CollectionHelpersEvents, static_property::key},31 eth::{map_eth_to_id, collection_id_to_address},32 Pallet as PalletCommon,33};34use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};35use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};36use sp_std::vec;37use up_data_structs::{38 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,39 CreateCollectionData,40};4142use crate::{weights::WeightInfo, Config, SelfWeightOf};4344use alloc::format;45use sp_std::vec::Vec;464748pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);49impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {50 fn recorder(&self) -> &SubstrateRecorder<T> {51 &self.052 }5354 fn into_recorder(self) -> SubstrateRecorder<T> {55 self.056 }57}5859fn convert_data<T: Config>(60 caller: caller,61 name: string,62 description: string,63 token_prefix: string,64) -> Result<(65 T::CrossAccountId,66 CollectionName,67 CollectionDescription,68 CollectionTokenPrefix,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 Ok((caller, name, description, token_prefix))87}8889#[inline(always)]90fn create_collection_internal<T: Config>(91 caller: caller,92 value: value,93 name: string,94 collection_mode: CollectionMode,95 description: string,96 token_prefix: string,97) -> Result<address> {98 let (caller, name, description, token_prefix) =99 convert_data::<T>(caller, name, description, token_prefix)?;100 let data = CreateCollectionData {101 name,102 mode: collection_mode,103 description,104 token_prefix,105 ..Default::default()106 };107 check_sent_amount_equals_collection_creation_price::<T>(value)?;108 let collection_helpers_address =109 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());110111 let collection_id = T::CollectionDispatch::create(112 caller.clone(),113 collection_helpers_address,114 data,115 Default::default(),116 )117 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;118 let address = pallet_common::eth::collection_id_to_address(collection_id);119 Ok(address)120}121122fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {123 let value = value.as_u128();124 let creation_price: u128 = T::CollectionCreationPrice::get()125 .try_into()126 .map_err(|_| ()) 127 .expect("Collection creation price should be convertible to u128");128 if value != creation_price {129 return Err(format!(130 "Sent amount not equals to collection creation price ({0})",131 creation_price132 )133 .into());134 }135 Ok(())136}137138139#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]140impl<T> EvmCollectionHelpers<T>141where142 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,143{144 145 146 147 148 149 #[weight(<SelfWeightOf<T>>::create_collection())]150 #[solidity(rename_selector = "createNFTCollection")]151 fn create_nft_collection(152 &mut self,153 caller: caller,154 value: value,155 name: string,156 description: string,157 token_prefix: string,158 ) -> Result<address> {159 let (caller, name, description, token_prefix) =160 convert_data::<T>(caller, name, description, token_prefix)?;161 let data = CreateCollectionData {162 name,163 mode: CollectionMode::NFT,164 description,165 token_prefix,166 ..Default::default()167 };168 check_sent_amount_equals_collection_creation_price::<T>(value)?;169 let collection_helpers_address =170 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());171 let collection_id = T::CollectionDispatch::create(172 caller,173 collection_helpers_address,174 data,175 Default::default(),176 )177 .map_err(dispatch_to_evm::<T>)?;178179 let address = pallet_common::eth::collection_id_to_address(collection_id);180 Ok(address)181 }182 183 184 185 186 187 #[weight(<SelfWeightOf<T>>::create_collection())]188 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]189 #[solidity(hide)]190 fn create_nonfungible_collection(191 &mut self,192 caller: caller,193 value: value,194 name: string,195 description: string,196 token_prefix: string,197 ) -> Result<address> {198 create_collection_internal::<T>(199 caller,200 value,201 name,202 CollectionMode::NFT,203 description,204 token_prefix,205 )206 }207208 #[weight(<SelfWeightOf<T>>::create_collection())]209 #[solidity(rename_selector = "createRFTCollection")]210 fn create_rft_collection(211 &mut self,212 caller: caller,213 value: value,214 name: string,215 description: string,216 token_prefix: string,217 ) -> Result<address> {218 create_collection_internal::<T>(219 caller,220 value,221 name,222 CollectionMode::ReFungible,223 description,224 token_prefix,225 )226 }227228 #[weight(<SelfWeightOf<T>>::create_collection())]229 #[solidity(rename_selector = "createFTCollection")]230 fn create_fungible_collection(231 &mut self,232 caller: caller,233 value: value,234 name: string,235 decimals: uint8,236 description: string,237 token_prefix: string,238 ) -> Result<address> {239 create_collection_internal::<T>(240 caller,241 value,242 name,243 CollectionMode::Fungible(decimals),244 description,245 token_prefix,246 )247 }248249 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]250 fn make_collection_metadata_compatible(251 &mut self,252 caller: caller,253 collection: address,254 base_uri: string,255 ) -> Result<()> {256 let caller = T::CrossAccountId::from_eth(caller);257 let collection =258 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;259 let mut collection =260 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;261262 if !matches!(263 collection.mode,264 CollectionMode::NFT | CollectionMode::ReFungible265 ) {266 return Err("target collection should be either NFT or Refungible".into());267 }268269 self.recorder().consume_sstore()?;270 collection271 .check_is_owner_or_admin(&caller)272 .map_err(dispatch_to_evm::<T>)?;273274 if collection.flags.erc721metadata {275 return Err("target collection is already Erc721Metadata compatible".into());276 }277 collection.flags.erc721metadata = true;278279 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);280 if all_permissions.get(&key::url()).is_none() {281 self.recorder().consume_sstore()?;282 <PalletCommon<T>>::set_property_permission(283 &collection,284 &caller,285 up_data_structs::PropertyKeyPermission {286 key: key::url(),287 permission: up_data_structs::PropertyPermission {288 mutable: true,289 collection_admin: true,290 token_owner: false,291 },292 },293 )294 .map_err(dispatch_to_evm::<T>)?;295 }296 if all_permissions.get(&key::suffix()).is_none() {297 self.recorder().consume_sstore()?;298 <PalletCommon<T>>::set_property_permission(299 &collection,300 &caller,301 up_data_structs::PropertyKeyPermission {302 key: key::suffix(),303 permission: up_data_structs::PropertyPermission {304 mutable: true,305 collection_admin: true,306 token_owner: false,307 },308 },309 )310 .map_err(dispatch_to_evm::<T>)?;311 }312313 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);314 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {315 self.recorder().consume_sstore()?;316 <PalletCommon<T>>::set_collection_properties(317 &collection,318 &caller,319 vec![up_data_structs::Property {320 key: key::base_uri(),321 value: base_uri322 .into_bytes()323 .try_into()324 .map_err(|_| "base uri is too large")?,325 }],326 )327 .map_err(dispatch_to_evm::<T>)?;328 }329330 self.recorder().consume_sstore()?;331 collection.save().map_err(dispatch_to_evm::<T>)?;332333 Ok(())334 }335336 #[weight(<SelfWeightOf<T>>::destroy_collection())]337 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {338 let caller = T::CrossAccountId::from_eth(caller);339340 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)341 .ok_or("Invalid collection address format")?;342 <Pallet<T>>::destroy_collection_internal(caller, collection_id)343 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)344 }345346 347 348 349 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {350 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {351 let collection_id = id;352 return Ok(<CollectionById<T>>::contains_key(collection_id));353 }354355 Ok(false)356 }357358 fn collection_creation_fee(&self) -> Result<value> {359 let price: u128 = T::CollectionCreationPrice::get()360 .try_into()361 .map_err(|_| ()) 362 .expect("Collection creation price should be convertible to u128");363 Ok(price.into())364 }365366 367 368 369 fn collection_address(&self, collection_id: uint32) -> Result<address> {370 Ok(collection_id_to_address(collection_id.into()))371 }372373 374 375 376 fn collection_id(&self, collection_address: address) -> Result<uint32> {377 map_eth_to_id(&collection_address)378 .map(|id| id.0)379 .ok_or(Error::Revert(format!(380 "failed to convert address {} into collectionId.",381 collection_address382 )))383 }384}385386387pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);388impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>389 for CollectionHelpersOnMethodCall<T>390{391 fn is_reserved(contract: &sp_core::H160) -> bool {392 contract == &T::ContractAddress::get()393 }394395 fn is_used(contract: &sp_core::H160) -> bool {396 contract == &T::ContractAddress::get()397 }398399 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {400 if handle.code_address() != T::ContractAddress::get() {401 return None;402 }403404 let helpers =405 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));406 pallet_evm_coder_substrate::call(handle, helpers)407 }408409 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {410 (contract == &T::ContractAddress::get())411 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())412 }413}414415generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);416generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);417418fn error_field_too_long(feild: &str, bound: usize) -> Error {419 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))420}