12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{22 execution::*,23 generate_stubgen, solidity, solidity_interface,24 types::*,25 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},26 make_signature,27, weight};28use frame_support::{traits::Get, storage::StorageNMap};29use crate::sp_api_hidden_includes_decl_storage::hidden_include::StorageDoubleMap;30use crate::Pallet;3132use pallet_common::{33 CollectionById,34 dispatch::CollectionDispatch,35 erc::{static_property::key, CollectionHelpersEvents},36 Pallet as PalletCommon,37};38use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};39use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};40use sp_std::vec;41use up_data_structs::{42 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,43 CreateCollectionData,44};4546use crate::{47 weights::WeightInfo, Config, SelfWeightOf, NftTransferBasket, FungibleTransferBasket,48 ReFungibleTransferBasket, NftApproveBasket, FungibleApproveBasket, RefungibleApproveBasket,49};5051use alloc::format;52use sp_std::vec::Vec;535455pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);56impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.059 }6061 fn into_recorder(self) -> SubstrateRecorder<T> {62 self.063 }64}6566fn convert_data<T: Config>(67 caller: caller,68 name: string,69 description: string,70 token_prefix: string,71) -> Result<(72 T::CrossAccountId,73 CollectionName,74 CollectionDescription,75 CollectionTokenPrefix,76)> {77 let caller = T::CrossAccountId::from_eth(caller);78 let name = name79 .encode_utf16()80 .collect::<Vec<u16>>()81 .try_into()82 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;83 let description = description84 .encode_utf16()85 .collect::<Vec<u16>>()86 .try_into()87 .map_err(|_| {88 error_field_too_long(stringify!(description), CollectionDescription::bound())89 })?;90 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {91 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())92 })?;93 Ok((caller, name, description, token_prefix))94}9596#[inline(always)]97fn create_collection_internal<T: Config>(98 caller: caller,99 value: value,100 name: string,101 collection_mode: CollectionMode,102 description: string,103 token_prefix: string,104) -> Result<address> {105 let (caller, name, description, token_prefix) =106 convert_data::<T>(caller, name, description, token_prefix)?;107 let data = CreateCollectionData {108 name,109 mode: collection_mode,110 description,111 token_prefix,112 ..Default::default()113 };114 check_sent_amount_equals_collection_creation_price::<T>(value)?;115 let collection_helpers_address =116 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());117118 let collection_id = T::CollectionDispatch::create(119 caller.clone(),120 collection_helpers_address,121 data,122 Default::default(),123 )124 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;125 let address = pallet_common::eth::collection_id_to_address(collection_id);126 Ok(address)127}128129fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {130 let value = value.as_u128();131 let creation_price: u128 = T::CollectionCreationPrice::get()132 .try_into()133 .map_err(|_| ()) 134 .expect("Collection creation price should be convertible to u128");135 if value != creation_price {136 return Err(format!(137 "Sent amount not equals to collection creation price ({0})",138 creation_price139 )140 .into());141 }142 Ok(())143}144145146#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]147impl<T> EvmCollectionHelpers<T>148where149 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,150{151 152 153 154 155 156 #[weight(<SelfWeightOf<T>>::create_collection())]157 #[solidity(rename_selector = "createNFTCollection")]158 fn create_nft_collection(159 &mut self,160 caller: caller,161 value: value,162 name: string,163 description: string,164 token_prefix: string,165 ) -> Result<address> {166 let (caller, name, description, token_prefix) =167 convert_data::<T>(caller, name, description, token_prefix)?;168 let data = CreateCollectionData {169 name,170 mode: CollectionMode::NFT,171 description,172 token_prefix,173 ..Default::default()174 };175 check_sent_amount_equals_collection_creation_price::<T>(value)?;176 let collection_helpers_address =177 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());178 let collection_id = T::CollectionDispatch::create(179 caller,180 collection_helpers_address,181 data,182 Default::default(),183 )184 .map_err(dispatch_to_evm::<T>)?;185186 let address = pallet_common::eth::collection_id_to_address(collection_id);187 Ok(address)188 }189 190 191 192 193 194 #[weight(<SelfWeightOf<T>>::create_collection())]195 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]196 #[solidity(hide)]197 fn create_nonfungible_collection(198 &mut self,199 caller: caller,200 value: value,201 name: string,202 description: string,203 token_prefix: string,204 ) -> Result<address> {205 create_collection_internal::<T>(206 caller,207 value,208 name,209 CollectionMode::NFT,210 description,211 token_prefix,212 )213 }214215 #[weight(<SelfWeightOf<T>>::create_collection())]216 #[solidity(rename_selector = "createRFTCollection")]217 fn create_rft_collection(218 &mut self,219 caller: caller,220 value: value,221 name: string,222 description: string,223 token_prefix: string,224 ) -> Result<address> {225 create_collection_internal::<T>(226 caller,227 value,228 name,229 CollectionMode::ReFungible,230 description,231 token_prefix,232 )233 }234235 #[weight(<SelfWeightOf<T>>::create_collection())]236 #[solidity(rename_selector = "createFTCollection")]237 fn create_fungible_collection(238 &mut self,239 caller: caller,240 value: value,241 name: string,242 decimals: uint8,243 description: string,244 token_prefix: string,245 ) -> Result<address> {246 create_collection_internal::<T>(247 caller,248 value,249 name,250 CollectionMode::Fungible(decimals),251 description,252 token_prefix,253 )254 }255256 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]257 fn make_collection_metadata_compatible(258 &mut self,259 caller: caller,260 collection: address,261 base_uri: string,262 ) -> Result<()> {263 let caller = T::CrossAccountId::from_eth(caller);264 let collection =265 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;266 let mut collection =267 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;268269 if !matches!(270 collection.mode,271 CollectionMode::NFT | CollectionMode::ReFungible272 ) {273 return Err("target collection should be either NFT or Refungible".into());274 }275276 self.recorder().consume_sstore()?;277 collection278 .check_is_owner_or_admin(&caller)279 .map_err(dispatch_to_evm::<T>)?;280281 if collection.flags.erc721metadata {282 return Err("target collection is already Erc721Metadata compatible".into());283 }284 collection.flags.erc721metadata = true;285286 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);287 if all_permissions.get(&key::url()).is_none() {288 self.recorder().consume_sstore()?;289 <PalletCommon<T>>::set_property_permission(290 &collection,291 &caller,292 up_data_structs::PropertyKeyPermission {293 key: key::url(),294 permission: up_data_structs::PropertyPermission {295 mutable: true,296 collection_admin: true,297 token_owner: false,298 },299 },300 )301 .map_err(dispatch_to_evm::<T>)?;302 }303 if all_permissions.get(&key::suffix()).is_none() {304 self.recorder().consume_sstore()?;305 <PalletCommon<T>>::set_property_permission(306 &collection,307 &caller,308 up_data_structs::PropertyKeyPermission {309 key: key::suffix(),310 permission: up_data_structs::PropertyPermission {311 mutable: true,312 collection_admin: true,313 token_owner: false,314 },315 },316 )317 .map_err(dispatch_to_evm::<T>)?;318 }319320 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);321 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {322 self.recorder().consume_sstore()?;323 <PalletCommon<T>>::set_collection_properties(324 &collection,325 &caller,326 vec![up_data_structs::Property {327 key: key::base_uri(),328 value: base_uri329 .into_bytes()330 .try_into()331 .map_err(|_| "base uri is too large")?,332 }],333 )334 .map_err(dispatch_to_evm::<T>)?;335 }336337 self.recorder().consume_sstore()?;338 collection.save().map_err(dispatch_to_evm::<T>)?;339340 Ok(())341 }342343 #[weight(<SelfWeightOf<T>>::destroy_collection())]344 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {345 let caller = T::CrossAccountId::from_eth(caller);346347 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)348 .ok_or("Invalid collection address format")?;349 <Pallet<T>>::destroy_collection_internal(caller, collection_id)350 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)351 }352353 354 355 356 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {357 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {358 let collection_id = id;359 return Ok(<CollectionById<T>>::contains_key(collection_id));360 }361362 Ok(false)363 }364365 fn collection_creation_fee(&self) -> Result<value> {366 let price: u128 = T::CollectionCreationPrice::get()367 .try_into()368 .map_err(|_| ()) 369 .expect("Collection creation price should be convertible to u128");370 Ok(price.into())371 }372}373374375pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);376impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>377 for CollectionHelpersOnMethodCall<T>378{379 fn is_reserved(contract: &sp_core::H160) -> bool {380 contract == &T::ContractAddress::get()381 }382383 fn is_used(contract: &sp_core::H160) -> bool {384 contract == &T::ContractAddress::get()385 }386387 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {388 if handle.code_address() != T::ContractAddress::get() {389 return None;390 }391392 let helpers =393 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));394 pallet_evm_coder_substrate::call(handle, helpers)395 }396397 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {398 (contract == &T::ContractAddress::get())399 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())400 }401}402403generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);404generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);405406fn error_field_too_long(feild: &str, bound: usize) -> Error {407 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))408}