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 = T::CollectionDispatch::create(117 caller.clone(),118 collection_helpers_address,119 data,120 Default::default(),121 )122 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;123 let address = pallet_common::eth::collection_id_to_address(collection_id);124 Ok(address)125}126127fn check_sent_amount_equals_collection_creation_price<T: Config>(value: Value) -> Result<()> {128 let value = value.as_u128();129 let creation_price: u128 = T::CollectionCreationPrice::get()130 .try_into()131 .map_err(|_| ()) 132 .expect("Collection creation price should be convertible to u128");133 if value != creation_price {134 return Err(format!(135 "Sent amount not equals to collection creation price ({0})",136 creation_price137 )138 .into());139 }140 Ok(())141}142143144#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents), enum(derive(PreDispatch)), enum_attr(weight))]145impl<T> EvmCollectionHelpers<T>146where147 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,148{149 150 151 152 153 154 #[weight(<SelfWeightOf<T>>::create_collection())]155 #[solidity(rename_selector = "createNFTCollection")]156 fn create_nft_collection(157 &mut self,158 caller: Caller,159 value: Value,160 name: String,161 description: String,162 token_prefix: String,163 ) -> Result<Address> {164 let (caller, name, description, token_prefix) =165 convert_data::<T>(caller, name, description, token_prefix)?;166 let data = CreateCollectionData {167 name,168 mode: CollectionMode::NFT,169 description,170 token_prefix,171 ..Default::default()172 };173 check_sent_amount_equals_collection_creation_price::<T>(value)?;174 let collection_helpers_address =175 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());176 let collection_id = T::CollectionDispatch::create(177 caller,178 collection_helpers_address,179 data,180 Default::default(),181 )182 .map_err(dispatch_to_evm::<T>)?;183184 let address = pallet_common::eth::collection_id_to_address(collection_id);185 Ok(address)186 }187 188 189 190 191 192 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]193 #[solidity(hide)]194 #[weight(<SelfWeightOf<T>>::create_collection())]195 fn create_nonfungible_collection(196 &mut self,197 caller: Caller,198 value: Value,199 name: String,200 description: String,201 token_prefix: String,202 ) -> Result<Address> {203 create_collection_internal::<T>(204 caller,205 value,206 name,207 CollectionMode::NFT,208 description,209 token_prefix,210 )211 }212213 #[weight(<SelfWeightOf<T>>::create_collection())]214 #[solidity(rename_selector = "createRFTCollection")]215 fn create_rft_collection(216 &mut self,217 caller: Caller,218 value: Value,219 name: String,220 description: String,221 token_prefix: String,222 ) -> Result<Address> {223 create_collection_internal::<T>(224 caller,225 value,226 name,227 CollectionMode::ReFungible,228 description,229 token_prefix,230 )231 }232233 #[weight(<SelfWeightOf<T>>::create_collection())]234 #[solidity(rename_selector = "createFTCollection")]235 fn create_fungible_collection(236 &mut self,237 caller: Caller,238 value: Value,239 name: String,240 decimals: u8,241 description: String,242 token_prefix: String,243 ) -> Result<Address> {244 create_collection_internal::<T>(245 caller,246 value,247 name,248 CollectionMode::Fungible(decimals),249 description,250 token_prefix,251 )252 }253254 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]255 fn make_collection_metadata_compatible(256 &mut self,257 caller: Caller,258 collection: Address,259 base_uri: String,260 ) -> Result<()> {261 let caller = T::CrossAccountId::from_eth(caller);262 let collection =263 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;264 let mut collection =265 <CollectionHandle<T>>::new(collection).ok_or("collection not found")?;266267 if !matches!(268 collection.mode,269 CollectionMode::NFT | CollectionMode::ReFungible270 ) {271 return Err("target collection should be either NFT or Refungible".into());272 }273274 self.recorder().consume_sstore()?;275 collection276 .check_is_owner_or_admin(&caller)277 .map_err(dispatch_to_evm::<T>)?;278279 if collection.flags.erc721metadata {280 return Err("target collection is already Erc721Metadata compatible".into());281 }282 collection.flags.erc721metadata = true;283284 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);285 if all_permissions.get(&key::url()).is_none() {286 self.recorder().consume_sstore()?;287 <PalletCommon<T>>::set_property_permission(288 &collection,289 &caller,290 up_data_structs::PropertyKeyPermission {291 key: key::url(),292 permission: up_data_structs::PropertyPermission {293 mutable: true,294 collection_admin: true,295 token_owner: false,296 },297 },298 )299 .map_err(dispatch_to_evm::<T>)?;300 }301 if all_permissions.get(&key::suffix()).is_none() {302 self.recorder().consume_sstore()?;303 <PalletCommon<T>>::set_property_permission(304 &collection,305 &caller,306 up_data_structs::PropertyKeyPermission {307 key: key::suffix(),308 permission: up_data_structs::PropertyPermission {309 mutable: true,310 collection_admin: true,311 token_owner: false,312 },313 },314 )315 .map_err(dispatch_to_evm::<T>)?;316 }317318 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);319 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {320 self.recorder().consume_sstore()?;321 <PalletCommon<T>>::set_collection_properties(322 &collection,323 &caller,324 [up_data_structs::Property {325 key: key::base_uri(),326 value: base_uri327 .into_bytes()328 .try_into()329 .map_err(|_| "base uri is too large")?,330 }]331 .into_iter(),332 )333 .map_err(dispatch_to_evm::<T>)?;334 }335336 self.recorder().consume_sstore()?;337 collection.save().map_err(dispatch_to_evm::<T>)?;338339 Ok(())340 }341342 #[weight(<SelfWeightOf<T>>::destroy_collection())]343 fn destroy_collection(&mut self, caller: Caller, collection_address: Address) -> Result<()> {344 let caller = T::CrossAccountId::from_eth(caller);345346 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)347 .ok_or("Invalid collection address format")?;348 <Pallet<T>>::destroy_collection_internal(caller, collection_id)349 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)350 }351352 353 354 355 fn is_collection_exist(&self, _caller: Caller, collection_address: Address) -> Result<bool> {356 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {357 let collection_id = id;358 return Ok(<CollectionById<T>>::contains_key(collection_id));359 }360361 Ok(false)362 }363364 fn collection_creation_fee(&self) -> Result<Value> {365 let price: u128 = T::CollectionCreationPrice::get()366 .try_into()367 .map_err(|_| ()) 368 .expect("Collection creation price should be convertible to u128");369 Ok(price.into())370 }371372 373 374 375 fn collection_address(&self, collection_id: u32) -> Result<Address> {376 Ok(collection_id_to_address(collection_id.into()))377 }378379 380 381 382 fn collection_id(&self, collection_address: Address) -> Result<u32> {383 map_eth_to_id(&collection_address)384 .map(|id| id.0)385 .ok_or(Error::Revert(format!(386 "failed to convert address {} into collectionId.",387 collection_address388 )))389 }390}391392393pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);394impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>395 for CollectionHelpersOnMethodCall<T>396{397 fn is_reserved(contract: &sp_core::H160) -> bool {398 contract == &T::ContractAddress::get()399 }400401 fn is_used(contract: &sp_core::H160) -> bool {402 contract == &T::ContractAddress::get()403 }404405 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {406 if handle.code_address() != T::ContractAddress::get() {407 return None;408 }409410 let helpers =411 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));412 pallet_evm_coder_substrate::call(handle, helpers)413 }414415 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {416 (contract == &T::ContractAddress::get())417 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())418 }419}420421generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);422generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);423424fn error_field_too_long(feild: &str, bound: usize) -> Error {425 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))426}