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, weight,27};28use frame_support::traits::Get;29use crate::Pallet;3031use pallet_common::{32 CollectionById,33 dispatch::CollectionDispatch,34 erc::{static_property::key, CollectionHelpersEvents},35 Pallet as PalletCommon,36};37use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};38use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};39use sp_std::vec;40use up_data_structs::{41 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,42 CreateCollectionData,43};4445use crate::{weights::WeightInfo, Config, SelfWeightOf};4647use alloc::format;48use sp_std::vec::Vec;495051pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);52impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {53 fn recorder(&self) -> &SubstrateRecorder<T> {54 &self.055 }5657 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.059 }60}6162fn convert_data<T: Config>(63 caller: caller,64 name: string,65 description: string,66 token_prefix: string,67) -> Result<(68 T::CrossAccountId,69 CollectionName,70 CollectionDescription,71 CollectionTokenPrefix,72)> {73 let caller = T::CrossAccountId::from_eth(caller);74 let name = name75 .encode_utf16()76 .collect::<Vec<u16>>()77 .try_into()78 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;79 let description = description80 .encode_utf16()81 .collect::<Vec<u16>>()82 .try_into()83 .map_err(|_| {84 error_field_too_long(stringify!(description), CollectionDescription::bound())85 })?;86 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {87 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())88 })?;89 Ok((caller, name, description, token_prefix))90}9192#[inline(always)]93fn create_collection_internal<T: Config>(94 caller: caller,95 value: value,96 name: string,97 collection_mode: CollectionMode,98 description: string,99 token_prefix: string,100) -> Result<address> {101 let (caller, name, description, token_prefix) =102 convert_data::<T>(caller, name, description, token_prefix)?;103 let data = CreateCollectionData {104 name,105 mode: collection_mode,106 description,107 token_prefix,108 ..Default::default()109 };110 check_sent_amount_equals_collection_creation_price::<T>(value)?;111 let collection_helpers_address =112 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());113114 let collection_id = T::CollectionDispatch::create(115 caller.clone(),116 collection_helpers_address,117 data,118 Default::default(),119 )120 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;121 let address = pallet_common::eth::collection_id_to_address(collection_id);122 Ok(address)123}124125fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {126 let value = value.as_u128();127 let creation_price: u128 = T::CollectionCreationPrice::get()128 .try_into()129 .map_err(|_| ()) 130 .expect("Collection creation price should be convertible to u128");131 if value != creation_price {132 return Err(format!(133 "Sent amount not equals to collection creation price ({0})",134 creation_price135 )136 .into());137 }138 Ok(())139}140141142#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]143impl<T> EvmCollectionHelpers<T>144where145 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,146{147 148 149 150 151 152 #[weight(<SelfWeightOf<T>>::create_collection())]153 #[solidity(rename_selector = "createNFTCollection")]154 fn create_nft_collection(155 &mut self,156 caller: caller,157 value: value,158 name: string,159 description: string,160 token_prefix: string,161 ) -> Result<address> {162 let (caller, name, description, token_prefix) =163 convert_data::<T>(caller, name, description, token_prefix)?;164 let data = CreateCollectionData {165 name,166 mode: CollectionMode::NFT,167 description,168 token_prefix,169 ..Default::default()170 };171 check_sent_amount_equals_collection_creation_price::<T>(value)?;172 let collection_helpers_address =173 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());174 let collection_id = T::CollectionDispatch::create(175 caller,176 collection_helpers_address,177 data,178 Default::default(),179 )180 .map_err(dispatch_to_evm::<T>)?;181182 let address = pallet_common::eth::collection_id_to_address(collection_id);183 Ok(address)184 }185 186 187 188 189 190 #[weight(<SelfWeightOf<T>>::create_collection())]191 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]192 #[solidity(hide)]193 fn create_nonfungible_collection(194 &mut self,195 caller: caller,196 value: value,197 name: string,198 description: string,199 token_prefix: string,200 ) -> Result<address> {201 create_collection_internal::<T>(202 caller,203 value,204 name,205 CollectionMode::NFT,206 description,207 token_prefix,208 )209 }210211 #[weight(<SelfWeightOf<T>>::create_collection())]212 #[solidity(rename_selector = "createRFTCollection")]213 fn create_rft_collection(214 &mut self,215 caller: caller,216 value: value,217 name: string,218 description: string,219 token_prefix: string,220 ) -> Result<address> {221 create_collection_internal::<T>(222 caller,223 value,224 name,225 CollectionMode::ReFungible,226 description,227 token_prefix,228 )229 }230231 #[weight(<SelfWeightOf<T>>::create_collection())]232 #[solidity(rename_selector = "createFTCollection")]233 fn create_fungible_collection(234 &mut self,235 caller: caller,236 value: value,237 name: string,238 decimals: uint8,239 description: string,240 token_prefix: string,241 ) -> Result<address> {242 create_collection_internal::<T>(243 caller,244 value,245 name,246 CollectionMode::Fungible(decimals),247 description,248 token_prefix,249 )250 }251252 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]253 fn make_collection_metadata_compatible(254 &mut self,255 caller: caller,256 collection: address,257 base_uri: string,258 ) -> Result<()> {259 let caller = T::CrossAccountId::from_eth(caller);260 let collection =261 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;262 let mut collection =263 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;264265 if !matches!(266 collection.mode,267 CollectionMode::NFT | CollectionMode::ReFungible268 ) {269 return Err("target collection should be either NFT or Refungible".into());270 }271272 self.recorder().consume_sstore()?;273 collection274 .check_is_owner_or_admin(&caller)275 .map_err(dispatch_to_evm::<T>)?;276277 if collection.flags.erc721metadata {278 return Err("target collection is already Erc721Metadata compatible".into());279 }280 collection.flags.erc721metadata = true;281282 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);283 if all_permissions.get(&key::url()).is_none() {284 self.recorder().consume_sstore()?;285 <PalletCommon<T>>::set_property_permission(286 &collection,287 &caller,288 up_data_structs::PropertyKeyPermission {289 key: key::url(),290 permission: up_data_structs::PropertyPermission {291 mutable: true,292 collection_admin: true,293 token_owner: false,294 },295 },296 )297 .map_err(dispatch_to_evm::<T>)?;298 }299 if all_permissions.get(&key::suffix()).is_none() {300 self.recorder().consume_sstore()?;301 <PalletCommon<T>>::set_property_permission(302 &collection,303 &caller,304 up_data_structs::PropertyKeyPermission {305 key: key::suffix(),306 permission: up_data_structs::PropertyPermission {307 mutable: true,308 collection_admin: true,309 token_owner: false,310 },311 },312 )313 .map_err(dispatch_to_evm::<T>)?;314 }315316 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);317 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {318 self.recorder().consume_sstore()?;319 <PalletCommon<T>>::set_collection_properties(320 &collection,321 &caller,322 vec![up_data_structs::Property {323 key: key::base_uri(),324 value: base_uri325 .into_bytes()326 .try_into()327 .map_err(|_| "base uri is too large")?,328 }],329 )330 .map_err(dispatch_to_evm::<T>)?;331 }332333 self.recorder().consume_sstore()?;334 collection.save().map_err(dispatch_to_evm::<T>)?;335336 Ok(())337 }338339 #[weight(<SelfWeightOf<T>>::destroy_collection())]340 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {341 let caller = T::CrossAccountId::from_eth(caller);342343 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)344 .ok_or("Invalid collection address format")?;345 <Pallet<T>>::destroy_collection_internal(caller, collection_id)346 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)347 }348349 350 351 352 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {353 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {354 let collection_id = id;355 return Ok(<CollectionById<T>>::contains_key(collection_id));356 }357358 Ok(false)359 }360361 fn collection_creation_fee(&self) -> Result<value> {362 let price: u128 = T::CollectionCreationPrice::get()363 .try_into()364 .map_err(|_| ()) 365 .expect("Collection creation price should be convertible to u128");366 Ok(price.into())367 }368}369370371pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);372impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>373 for CollectionHelpersOnMethodCall<T>374{375 fn is_reserved(contract: &sp_core::H160) -> bool {376 contract == &T::ContractAddress::get()377 }378379 fn is_used(contract: &sp_core::H160) -> bool {380 contract == &T::ContractAddress::get()381 }382383 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {384 if handle.code_address() != T::ContractAddress::get() {385 return None;386 }387388 let helpers =389 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));390 pallet_evm_coder_substrate::call(handle, helpers)391 }392393 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {394 (contract == &T::ContractAddress::get())395 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())396 }397}398399generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);400generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);401402fn error_field_too_long(feild: &str, bound: usize) -> Error {403 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))404}