12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};22use frame_support::traits::Get;23use crate::Pallet;2425use pallet_common::{26 CollectionById,27 dispatch::CollectionDispatch,28 erc::{static_property::key, CollectionHelpersEvents},29 Pallet as PalletCommon,30};31use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};32use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};33use sp_std::vec;34use up_data_structs::{35 CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,36 CreateCollectionData,37};3839use crate::{weights::WeightInfo, Config, SelfWeightOf};4041use alloc::format;42use sp_std::vec::Vec;434445pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);46impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {47 fn recorder(&self) -> &SubstrateRecorder<T> {48 &self.049 }5051 fn into_recorder(self) -> SubstrateRecorder<T> {52 self.053 }54}5556fn convert_data<T: Config>(57 caller: caller,58 name: string,59 description: string,60 token_prefix: string,61) -> Result<(62 T::CrossAccountId,63 CollectionName,64 CollectionDescription,65 CollectionTokenPrefix,66)> {67 let caller = T::CrossAccountId::from_eth(caller);68 let name = name69 .encode_utf16()70 .collect::<Vec<u16>>()71 .try_into()72 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;73 let description = description74 .encode_utf16()75 .collect::<Vec<u16>>()76 .try_into()77 .map_err(|_| {78 error_field_too_long(stringify!(description), CollectionDescription::bound())79 })?;80 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {81 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())82 })?;83 Ok((caller, name, description, token_prefix))84}8586#[inline(always)]87fn create_collection_internal<T: Config>(88 caller: caller,89 value: value,90 name: string,91 collection_mode: CollectionMode,92 description: string,93 token_prefix: string,94) -> Result<address> {95 let (caller, name, description, token_prefix) =96 convert_data::<T>(caller, name, description, token_prefix)?;97 let data = CreateCollectionData {98 name,99 mode: collection_mode,100 description,101 token_prefix,102 ..Default::default()103 };104 check_sent_amount_equals_collection_creation_price::<T>(value)?;105 let collection_helpers_address =106 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());107108 let collection_id = T::CollectionDispatch::create(109 caller.clone(),110 collection_helpers_address,111 data,112 Default::default(),113 )114 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;115 let address = pallet_common::eth::collection_id_to_address(collection_id);116 Ok(address)117}118119fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {120 let value = value.as_u128();121 let creation_price: u128 = T::CollectionCreationPrice::get()122 .try_into()123 .map_err(|_| ()) 124 .expect("Collection creation price should be convertible to u128");125 if value != creation_price {126 return Err(format!(127 "Sent amount not equals to collection creation price ({0})",128 creation_price129 )130 .into());131 }132 Ok(())133}134135136#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]137impl<T> EvmCollectionHelpers<T>138where139 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,140{141 142 143 144 145 146 #[weight(<SelfWeightOf<T>>::create_collection())]147 #[solidity(rename_selector = "createNFTCollection")]148 fn create_nft_collection(149 &mut self,150 caller: caller,151 value: value,152 name: string,153 description: string,154 token_prefix: string,155 ) -> Result<address> {156 let (caller, name, description, token_prefix) =157 convert_data::<T>(caller, name, description, token_prefix)?;158 let data = CreateCollectionData {159 name,160 mode: CollectionMode::NFT,161 description,162 token_prefix,163 ..Default::default()164 };165 check_sent_amount_equals_collection_creation_price::<T>(value)?;166 let collection_helpers_address =167 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());168 let collection_id = T::CollectionDispatch::create(169 caller,170 collection_helpers_address,171 data,172 Default::default(),173 )174 .map_err(dispatch_to_evm::<T>)?;175176 let address = pallet_common::eth::collection_id_to_address(collection_id);177 Ok(address)178 }179 180 181 182 183 184 #[weight(<SelfWeightOf<T>>::create_collection())]185 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]186 #[solidity(hide)]187 fn create_nonfungible_collection(188 &mut self,189 caller: caller,190 value: value,191 name: string,192 description: string,193 token_prefix: string,194 ) -> Result<address> {195 create_collection_internal::<T>(196 caller,197 value,198 name,199 CollectionMode::NFT,200 description,201 token_prefix,202 )203 }204205 #[weight(<SelfWeightOf<T>>::create_collection())]206 #[solidity(rename_selector = "createRFTCollection")]207 fn create_rft_collection(208 &mut self,209 caller: caller,210 value: value,211 name: string,212 description: string,213 token_prefix: string,214 ) -> Result<address> {215 create_collection_internal::<T>(216 caller,217 value,218 name,219 CollectionMode::ReFungible,220 description,221 token_prefix,222 )223 }224225 #[weight(<SelfWeightOf<T>>::create_collection())]226 #[solidity(rename_selector = "createFTCollection")]227 fn create_fungible_collection(228 &mut self,229 caller: caller,230 value: value,231 name: string,232 decimals: uint8,233 description: string,234 token_prefix: string,235 ) -> Result<address> {236 create_collection_internal::<T>(237 caller,238 value,239 name,240 CollectionMode::Fungible(decimals),241 description,242 token_prefix,243 )244 }245246 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]247 fn make_collection_metadata_compatible(248 &mut self,249 caller: caller,250 collection: address,251 base_uri: string,252 ) -> Result<()> {253 let caller = T::CrossAccountId::from_eth(caller);254 let collection =255 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;256 let mut collection =257 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;258259 if !matches!(260 collection.mode,261 CollectionMode::NFT | CollectionMode::ReFungible262 ) {263 return Err("target collection should be either NFT or Refungible".into());264 }265266 self.recorder().consume_sstore()?;267 collection268 .check_is_owner_or_admin(&caller)269 .map_err(dispatch_to_evm::<T>)?;270271 if collection.flags.erc721metadata {272 return Err("target collection is already Erc721Metadata compatible".into());273 }274 collection.flags.erc721metadata = true;275276 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);277 if all_permissions.get(&key::url()).is_none() {278 self.recorder().consume_sstore()?;279 <PalletCommon<T>>::set_property_permission(280 &collection,281 &caller,282 up_data_structs::PropertyKeyPermission {283 key: key::url(),284 permission: up_data_structs::PropertyPermission {285 mutable: true,286 collection_admin: true,287 token_owner: false,288 },289 },290 )291 .map_err(dispatch_to_evm::<T>)?;292 }293 if all_permissions.get(&key::suffix()).is_none() {294 self.recorder().consume_sstore()?;295 <PalletCommon<T>>::set_property_permission(296 &collection,297 &caller,298 up_data_structs::PropertyKeyPermission {299 key: key::suffix(),300 permission: up_data_structs::PropertyPermission {301 mutable: true,302 collection_admin: true,303 token_owner: false,304 },305 },306 )307 .map_err(dispatch_to_evm::<T>)?;308 }309310 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);311 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {312 self.recorder().consume_sstore()?;313 <PalletCommon<T>>::set_collection_properties(314 &collection,315 &caller,316 vec![up_data_structs::Property {317 key: key::base_uri(),318 value: base_uri319 .into_bytes()320 .try_into()321 .map_err(|_| "base uri is too large")?,322 }],323 )324 .map_err(dispatch_to_evm::<T>)?;325 }326327 self.recorder().consume_sstore()?;328 collection.save().map_err(dispatch_to_evm::<T>)?;329330 Ok(())331 }332333 #[weight(<SelfWeightOf<T>>::destroy_collection())]334 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {335 let caller = T::CrossAccountId::from_eth(caller);336337 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)338 .ok_or("Invalid collection address format")?;339 <Pallet<T>>::destroy_collection_internal(caller, collection_id)340 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)341 }342343 344 345 346 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {347 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {348 let collection_id = id;349 return Ok(<CollectionById<T>>::contains_key(collection_id));350 }351352 Ok(false)353 }354355 fn collection_creation_fee(&self) -> Result<value> {356 let price: u128 = T::CollectionCreationPrice::get()357 .try_into()358 .map_err(|_| ()) 359 .expect("Collection creation price should be convertible to u128");360 Ok(price.into())361 }362}363364365pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);366impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>367 for CollectionHelpersOnMethodCall<T>368{369 fn is_reserved(contract: &sp_core::H160) -> bool {370 contract == &T::ContractAddress::get()371 }372373 fn is_used(contract: &sp_core::H160) -> bool {374 contract == &T::ContractAddress::get()375 }376377 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {378 if handle.code_address() != T::ContractAddress::get() {379 return None;380 }381382 let helpers =383 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));384 pallet_evm_coder_substrate::call(handle, helpers)385 }386387 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {388 (contract == &T::ContractAddress::get())389 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())390 }391}392393generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);394generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);395396fn error_field_too_long(feild: &str, bound: usize) -> Error {397 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))398}