12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};22use frame_support::{traits::Get, storage::StorageNMap};23use crate::sp_api_hidden_includes_decl_storage::hidden_include::StorageDoubleMap;24use crate::Pallet;2526use pallet_common::{27 CollectionById,28 dispatch::CollectionDispatch,29 erc::{static_property::key, CollectionHelpersEvents},30 Pallet as PalletCommon,31};32use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};33use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};34use sp_std::vec;35use up_data_structs::{36 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,37 CreateCollectionData,38};3940use crate::{41 weights::WeightInfo, Config, SelfWeightOf, NftTransferBasket, FungibleTransferBasket,42 ReFungibleTransferBasket, NftApproveBasket, FungibleApproveBasket, RefungibleApproveBasket,43};4445use alloc::format;46use sp_std::vec::Vec;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}8990#[inline(always)]91fn create_collection_internal<T: Config>(92 caller: caller,93 value: value,94 name: string,95 collection_mode: CollectionMode,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: collection_mode,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 create_collection_internal::<T>(200 caller,201 value,202 name,203 CollectionMode::NFT,204 description,205 token_prefix,206 )207 }208209 #[weight(<SelfWeightOf<T>>::create_collection())]210 #[solidity(rename_selector = "createRFTCollection")]211 fn create_rft_collection(212 &mut self,213 caller: caller,214 value: value,215 name: string,216 description: string,217 token_prefix: string,218 ) -> Result<address> {219 create_collection_internal::<T>(220 caller,221 value,222 name,223 CollectionMode::ReFungible,224 description,225 token_prefix,226 )227 }228229 #[weight(<SelfWeightOf<T>>::create_collection())]230 #[solidity(rename_selector = "createFTCollection")]231 fn create_fungible_collection(232 &mut self,233 caller: caller,234 value: value,235 name: string,236 decimals: uint8,237 description: string,238 token_prefix: string,239 ) -> Result<address> {240 create_collection_internal::<T>(241 caller,242 value,243 name,244 CollectionMode::Fungible(decimals),245 description,246 token_prefix,247 )248 }249250 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]251 fn make_collection_metadata_compatible(252 &mut self,253 caller: caller,254 collection: address,255 base_uri: string,256 ) -> Result<()> {257 let caller = T::CrossAccountId::from_eth(caller);258 let collection =259 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;260 let mut collection =261 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;262263 if !matches!(264 collection.mode,265 CollectionMode::NFT | CollectionMode::ReFungible266 ) {267 return Err("target collection should be either NFT or Refungible".into());268 }269270 self.recorder().consume_sstore()?;271 collection272 .check_is_owner_or_admin(&caller)273 .map_err(dispatch_to_evm::<T>)?;274275 if collection.flags.erc721metadata {276 return Err("target collection is already Erc721Metadata compatible".into());277 }278 collection.flags.erc721metadata = true;279280 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);281 if all_permissions.get(&key::url()).is_none() {282 self.recorder().consume_sstore()?;283 <PalletCommon<T>>::set_property_permission(284 &collection,285 &caller,286 up_data_structs::PropertyKeyPermission {287 key: key::url(),288 permission: up_data_structs::PropertyPermission {289 mutable: true,290 collection_admin: true,291 token_owner: false,292 },293 },294 )295 .map_err(dispatch_to_evm::<T>)?;296 }297 if all_permissions.get(&key::suffix()).is_none() {298 self.recorder().consume_sstore()?;299 <PalletCommon<T>>::set_property_permission(300 &collection,301 &caller,302 up_data_structs::PropertyKeyPermission {303 key: key::suffix(),304 permission: up_data_structs::PropertyPermission {305 mutable: true,306 collection_admin: true,307 token_owner: false,308 },309 },310 )311 .map_err(dispatch_to_evm::<T>)?;312 }313314 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);315 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {316 self.recorder().consume_sstore()?;317 <PalletCommon<T>>::set_collection_properties(318 &collection,319 &caller,320 vec![up_data_structs::Property {321 key: key::base_uri(),322 value: base_uri323 .into_bytes()324 .try_into()325 .map_err(|_| "base uri is too large")?,326 }],327 )328 .map_err(dispatch_to_evm::<T>)?;329 }330331 self.recorder().consume_sstore()?;332 collection.save().map_err(dispatch_to_evm::<T>)?;333334 Ok(())335 }336337 #[weight(<SelfWeightOf<T>>::destroy_collection())]338 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {339 let caller = T::CrossAccountId::from_eth(caller);340341 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)342 .ok_or("Invalid collection address format")?;343 <Pallet<T>>::destroy_collection_internal(caller, collection_id)344 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)345 }346347 348 349 350 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {351 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {352 let collection_id = id;353 return Ok(<CollectionById<T>>::contains_key(collection_id));354 }355356 Ok(false)357 }358359 fn collection_creation_fee(&self) -> Result<value> {360 let price: u128 = T::CollectionCreationPrice::get()361 .try_into()362 .map_err(|_| ()) 363 .expect("Collection creation price should be convertible to u128");364 Ok(price.into())365 }366}367368369pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);370impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>371 for CollectionHelpersOnMethodCall<T>372{373 fn is_reserved(contract: &sp_core::H160) -> bool {374 contract == &T::ContractAddress::get()375 }376377 fn is_used(contract: &sp_core::H160) -> bool {378 contract == &T::ContractAddress::get()379 }380381 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {382 if handle.code_address() != T::ContractAddress::get() {383 return None;384 }385386 let helpers =387 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));388 pallet_evm_coder_substrate::call(handle, helpers)389 }390391 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {392 (contract == &T::ContractAddress::get())393 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())394 }395}396397generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);398generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);399400fn error_field_too_long(feild: &str, bound: usize) -> Error {401 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))402}