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 up_data_structs::{37 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,38 CreateCollectionData,39};4041use crate::{weights::WeightInfo, Config, SelfWeightOf};4243use alloc::format;44use sp_std::vec::Vec;454647pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);48impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {49 fn recorder(&self) -> &SubstrateRecorder<T> {50 &self.051 }5253 fn into_recorder(self) -> SubstrateRecorder<T> {54 self.055 }56}5758fn convert_data<T: Config>(59 caller: Caller,60 name: String,61 description: String,62 token_prefix: String,63) -> Result<(64 T::CrossAccountId,65 CollectionName,66 CollectionDescription,67 CollectionTokenPrefix,68)> {69 let caller = T::CrossAccountId::from_eth(caller);70 let name = name71 .encode_utf16()72 .collect::<Vec<u16>>()73 .try_into()74 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;75 let description = description76 .encode_utf16()77 .collect::<Vec<u16>>()78 .try_into()79 .map_err(|_| {80 error_field_too_long(stringify!(description), CollectionDescription::bound())81 })?;82 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {83 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())84 })?;85 Ok((caller, name, description, token_prefix))86}8788#[inline(always)]89fn create_collection_internal<T: Config>(90 caller: Caller,91 value: Value,92 name: String,93 collection_mode: CollectionMode,94 description: String,95 token_prefix: String,96) -> Result<Address> {97 let (caller, name, description, token_prefix) =98 convert_data::<T>(caller, name, description, token_prefix)?;99 let data = CreateCollectionData {100 name,101 mode: collection_mode,102 description,103 token_prefix,104 ..Default::default()105 };106 check_sent_amount_equals_collection_creation_price::<T>(value)?;107 let collection_helpers_address =108 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());109110 let collection_id = T::CollectionDispatch::create(111 caller.clone(),112 collection_helpers_address,113 data,114 Default::default(),115 )116 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;117 let address = pallet_common::eth::collection_id_to_address(collection_id);118 Ok(address)119}120121fn check_sent_amount_equals_collection_creation_price<T: Config>(value: Value) -> Result<()> {122 let value = value.as_u128();123 let creation_price: u128 = T::CollectionCreationPrice::get()124 .try_into()125 .map_err(|_| ()) 126 .expect("Collection creation price should be convertible to u128");127 if value != creation_price {128 return Err(format!(129 "Sent amount not equals to collection creation price ({0})",130 creation_price131 )132 .into());133 }134 Ok(())135}136137138#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]139impl<T> EvmCollectionHelpers<T>140where141 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,142{143 144 145 146 147 148 #[weight(<SelfWeightOf<T>>::create_collection())]149 #[solidity(rename_selector = "createNFTCollection")]150 fn create_nft_collection(151 &mut self,152 caller: Caller,153 value: Value,154 name: String,155 description: String,156 token_prefix: String,157 ) -> Result<Address> {158 let (caller, name, description, token_prefix) =159 convert_data::<T>(caller, name, description, token_prefix)?;160 let data = CreateCollectionData {161 name,162 mode: CollectionMode::NFT,163 description,164 token_prefix,165 ..Default::default()166 };167 check_sent_amount_equals_collection_creation_price::<T>(value)?;168 let collection_helpers_address =169 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());170 let collection_id = T::CollectionDispatch::create(171 caller,172 collection_helpers_address,173 data,174 Default::default(),175 )176 .map_err(dispatch_to_evm::<T>)?;177178 let address = pallet_common::eth::collection_id_to_address(collection_id);179 Ok(address)180 }181 182 183 184 185 186 #[weight(<SelfWeightOf<T>>::create_collection())]187 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]188 #[solidity(hide)]189 fn create_nonfungible_collection(190 &mut self,191 caller: Caller,192 value: Value,193 name: String,194 description: String,195 token_prefix: String,196 ) -> Result<Address> {197 create_collection_internal::<T>(198 caller,199 value,200 name,201 CollectionMode::NFT,202 description,203 token_prefix,204 )205 }206207 #[weight(<SelfWeightOf<T>>::create_collection())]208 #[solidity(rename_selector = "createRFTCollection")]209 fn create_rft_collection(210 &mut self,211 caller: Caller,212 value: Value,213 name: String,214 description: String,215 token_prefix: String,216 ) -> Result<Address> {217 create_collection_internal::<T>(218 caller,219 value,220 name,221 CollectionMode::ReFungible,222 description,223 token_prefix,224 )225 }226227 #[weight(<SelfWeightOf<T>>::create_collection())]228 #[solidity(rename_selector = "createFTCollection")]229 fn create_fungible_collection(230 &mut self,231 caller: Caller,232 value: Value,233 name: String,234 decimals: u8,235 description: String,236 token_prefix: String,237 ) -> Result<Address> {238 create_collection_internal::<T>(239 caller,240 value,241 name,242 CollectionMode::Fungible(decimals),243 description,244 token_prefix,245 )246 }247248 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]249 fn make_collection_metadata_compatible(250 &mut self,251 caller: Caller,252 collection: Address,253 base_uri: String,254 ) -> Result<()> {255 let caller = T::CrossAccountId::from_eth(caller);256 let collection =257 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;258 let mut collection =259 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;260261 if !matches!(262 collection.mode,263 CollectionMode::NFT | CollectionMode::ReFungible264 ) {265 return Err("target collection should be either NFT or Refungible".into());266 }267268 self.recorder().consume_sstore()?;269 collection270 .check_is_owner_or_admin(&caller)271 .map_err(dispatch_to_evm::<T>)?;272273 if collection.flags.erc721metadata {274 return Err("target collection is already Erc721Metadata compatible".into());275 }276 collection.flags.erc721metadata = true;277278 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);279 if all_permissions.get(&key::url()).is_none() {280 self.recorder().consume_sstore()?;281 <PalletCommon<T>>::set_property_permission(282 &collection,283 &caller,284 up_data_structs::PropertyKeyPermission {285 key: key::url(),286 permission: up_data_structs::PropertyPermission {287 mutable: true,288 collection_admin: true,289 token_owner: false,290 },291 },292 )293 .map_err(dispatch_to_evm::<T>)?;294 }295 if all_permissions.get(&key::suffix()).is_none() {296 self.recorder().consume_sstore()?;297 <PalletCommon<T>>::set_property_permission(298 &collection,299 &caller,300 up_data_structs::PropertyKeyPermission {301 key: key::suffix(),302 permission: up_data_structs::PropertyPermission {303 mutable: true,304 collection_admin: true,305 token_owner: false,306 },307 },308 )309 .map_err(dispatch_to_evm::<T>)?;310 }311312 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);313 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {314 self.recorder().consume_sstore()?;315 <PalletCommon<T>>::set_collection_properties(316 &collection,317 &caller,318 [up_data_structs::Property {319 key: key::base_uri(),320 value: base_uri321 .into_bytes()322 .try_into()323 .map_err(|_| "base uri is too large")?,324 }]325 .into_iter(),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<()> {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: u32) -> Result<Address> {370 Ok(collection_id_to_address(collection_id.into()))371 }372373 374 375 376 fn collection_id(&self, collection_address: Address) -> Result<u32> {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}