12345678910111213141516171819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};22use frame_support::traits::Get;2324use crate::Pallet;2526use pallet_common::{27 CollectionById,28 dispatch::CollectionDispatch,29 erc::{30 CollectionHelpersEvents,31 static_property::{key},32 },33 Pallet as PalletCommon,34};35use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};36use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};37use sp_std::vec;38use up_data_structs::{39 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,40 CollectionMode, PropertyValue,41};4243use crate::{Config, SelfWeightOf, weights::WeightInfo};4445use sp_std::vec::Vec;46use alloc::format;474849pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);50impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {51 fn recorder(&self) -> &SubstrateRecorder<T> {52 &self.053 }5455 fn into_recorder(self) -> SubstrateRecorder<T> {56 self.057 }58}5960fn convert_data<T: Config>(61 caller: caller,62 name: string,63 description: string,64 token_prefix: string,65) -> Result<(66 T::CrossAccountId,67 CollectionName,68 CollectionDescription,69 CollectionTokenPrefix,70)> {71 let caller = T::CrossAccountId::from_eth(caller);72 let name = name73 .encode_utf16()74 .collect::<Vec<u16>>()75 .try_into()76 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;77 let description = description78 .encode_utf16()79 .collect::<Vec<u16>>()80 .try_into()81 .map_err(|_| {82 error_field_too_long(stringify!(description), CollectionDescription::bound())83 })?;84 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {85 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())86 })?;87 Ok((caller, name, description, token_prefix))88}8990fn create_refungible_collection_internal<T: Config>(91 caller: caller,92 value: value,93 name: string,94 description: string,95 token_prefix: string,96) -> Result<address> {97 self::create_collection_internal::<T>(98 caller,99 value,100 name,101 CollectionMode::ReFungible,102 description,103 token_prefix104 )105}106107fn create_collection_internal<T: Config>(108 caller: caller,109 value: value,110 name: string,111 collection_mode: CollectionMode,112 description: string,113 token_prefix: string,114) -> Result<address> {115 let (caller, name, description, token_prefix) =116 convert_data::<T>(caller, name, description, token_prefix)?;117 let data = CreateCollectionData {118 name,119 mode: collection_mode,120 description,121 token_prefix,122 ..Default::default()123 };124 check_sent_amount_equals_collection_creation_price::<T>(value)?;125 let collection_helpers_address =126 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());127128 let collection_id = T::CollectionDispatch::create(129 caller.clone(),130 collection_helpers_address,131 data,132 Default::default(),133 )134 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;135 let address = pallet_common::eth::collection_id_to_address(collection_id);136 Ok(address)137}138139fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {140 let value = value.as_u128();141 let creation_price: u128 = T::CollectionCreationPrice::get()142 .try_into()143 .map_err(|_| ()) 144 .expect("Collection creation price should be convertible to u128");145 if value != creation_price {146 return Err(format!(147 "Sent amount not equals to collection creation price ({0})",148 creation_price149 )150 .into());151 }152 Ok(())153}154155156#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]157impl<T> EvmCollectionHelpers<T>158where159 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,160{161 162 163 164 165 166 #[weight(<SelfWeightOf<T>>::create_collection())]167 #[solidity(rename_selector = "createNFTCollection")]168 fn create_nft_collection(169 &mut self,170 caller: caller,171 value: value,172 name: string,173 description: string,174 token_prefix: string,175 ) -> Result<address> {176 let (caller, name, description, token_prefix) =177 convert_data::<T>(caller, name, description, token_prefix)?;178 let data = CreateCollectionData {179 name,180 mode: CollectionMode::NFT,181 description,182 token_prefix,183 ..Default::default()184 };185 check_sent_amount_equals_collection_creation_price::<T>(value)?;186 let collection_helpers_address =187 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());188 let collection_id = T::CollectionDispatch::create(189 caller,190 collection_helpers_address,191 data,192 Default::default(),193 )194 .map_err(dispatch_to_evm::<T>)?;195196 let address = pallet_common::eth::collection_id_to_address(collection_id);197 Ok(address)198 }199 200 201 202 203 204 #[weight(<SelfWeightOf<T>>::create_collection())]205 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]206 #[solidity(hide)]207 fn create_nonfungible_collection(208 &mut self,209 caller: caller,210 value: value,211 name: string,212 description: string,213 token_prefix: string,214 ) -> Result<address> {215 self.create_nft_collection(caller, value, name, description, token_prefix)216 }217218 #[weight(<SelfWeightOf<T>>::create_collection())]219 #[solidity(rename_selector = "createRFTCollection")]220 fn create_rft_collection(221 &mut self,222 caller: caller,223 value: value,224 name: string,225 description: string,226 token_prefix: string,227 ) -> Result<address> {228 create_refungible_collection_internal::<T>(caller, value, name, description, token_prefix)229 }230231 #[weight(<SelfWeightOf<T>>::create_collection())]232 #[solidity(rename_selector = "createRTCollection")]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}