12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*};22use frame_support::traits::Get;23use crate::Pallet;2425use pallet_common::{26 CollectionById,27 dispatch::CollectionDispatch,28 erc::{CollectionHelpersEvents, static_property::key},29 eth::{map_eth_to_id, collection_id_to_address},30 Pallet as PalletCommon, CollectionHandle,31};32use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, SubstrateRecorder, WithRecorder,35 execution::{PreDispatch, Result, Error},36 frontier_contract,37};38use up_data_structs::{39 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,40 CreateCollectionData,41};4243use crate::{weights::WeightInfo, Config, SelfWeightOf};4445use alloc::format;46use sp_std::vec::Vec;4748frontier_contract! {49 macro_rules! EvmCollectionHelpers_result {...}50 impl<T: Config> Contract for EvmCollectionHelpers<T> {...}51}5253pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);54impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {55 fn recorder(&self) -> &SubstrateRecorder<T> {56 &self.057 }5859 fn into_recorder(self) -> SubstrateRecorder<T> {60 self.061 }62}6364fn convert_data<T: Config>(65 caller: Caller,66 name: String,67 description: String,68 token_prefix: String,69) -> Result<(70 T::CrossAccountId,71 CollectionName,72 CollectionDescription,73 CollectionTokenPrefix,74)> {75 let caller = T::CrossAccountId::from_eth(caller);76 let name = name77 .encode_utf16()78 .collect::<Vec<u16>>()79 .try_into()80 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;81 let description = description82 .encode_utf16()83 .collect::<Vec<u16>>()84 .try_into()85 .map_err(|_| {86 error_field_too_long(stringify!(description), CollectionDescription::bound())87 })?;88 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {89 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())90 })?;91 Ok((caller, name, description, token_prefix))92}9394#[inline(always)]95fn create_collection_internal<T: Config>(96 caller: Caller,97 value: Value,98 name: String,99 collection_mode: CollectionMode,100 description: String,101 token_prefix: String,102) -> Result<Address> {103 let (caller, name, description, token_prefix) =104 convert_data::<T>(caller, name, description, token_prefix)?;105 let data = CreateCollectionData {106 name,107 mode: collection_mode,108 description,109 token_prefix,110 ..Default::default()111 };112 check_sent_amount_equals_collection_creation_price::<T>(value)?;113 let collection_helpers_address =114 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());115116 let collection_id =117 T::CollectionDispatch::create(caller, collection_helpers_address, data, Default::default())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 ({creation_price})",132 )133 .into());134 }135 Ok(())136}137138139#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents), enum(derive(PreDispatch)), enum_attr(weight))]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 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]188 #[solidity(hide)]189 #[weight(<SelfWeightOf<T>>::create_collection())]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: u8,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 <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 [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 .into_iter(),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<()> {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 }366367 368 369 370 fn collection_address(&self, collection_id: u32) -> Result<Address> {371 Ok(collection_id_to_address(collection_id.into()))372 }373374 375 376 377 fn collection_id(&self, collection_address: Address) -> Result<u32> {378 map_eth_to_id(&collection_address)379 .map(|id| id.0)380 .ok_or(Error::Revert(format!(381 "failed to convert address {collection_address} into collectionId."382 )))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!("{feild} is too long. Max length is {bound}."))420}