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::{46 weights::WeightInfo, Config, SelfWeightOf, NftTransferBasket, FungibleTransferBasket,47 ReFungibleTransferBasket, NftApproveBasket, FungibleApproveBasket, RefungibleApproveBasket,48};4950use alloc::format;51use sp_std::vec::Vec;525354pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);55impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {56 fn recorder(&self) -> &SubstrateRecorder<T> {57 &self.058 }5960 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.062 }63}6465fn convert_data<T: Config>(66 caller: caller,67 name: string,68 description: string,69 token_prefix: string,70) -> Result<(71 T::CrossAccountId,72 CollectionName,73 CollectionDescription,74 CollectionTokenPrefix,75)> {76 let caller = T::CrossAccountId::from_eth(caller);77 let name = name78 .encode_utf16()79 .collect::<Vec<u16>>()80 .try_into()81 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;82 let description = description83 .encode_utf16()84 .collect::<Vec<u16>>()85 .try_into()86 .map_err(|_| {87 error_field_too_long(stringify!(description), CollectionDescription::bound())88 })?;89 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {90 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())91 })?;92 Ok((caller, name, description, token_prefix))93}9495#[inline(always)]96fn create_collection_internal<T: Config>(97 caller: caller,98 value: value,99 name: string,100 collection_mode: CollectionMode,101 description: string,102 token_prefix: string,103) -> Result<address> {104 let (caller, name, description, token_prefix) =105 convert_data::<T>(caller, name, description, token_prefix)?;106 let data = CreateCollectionData {107 name,108 mode: collection_mode,109 description,110 token_prefix,111 ..Default::default()112 };113 check_sent_amount_equals_collection_creation_price::<T>(value)?;114 let collection_helpers_address =115 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());116117 let collection_id = T::CollectionDispatch::create(118 caller.clone(),119 collection_helpers_address,120 data,121 Default::default(),122 )123 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;124 let address = pallet_common::eth::collection_id_to_address(collection_id);125 Ok(address)126}127128fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {129 let value = value.as_u128();130 let creation_price: u128 = T::CollectionCreationPrice::get()131 .try_into()132 .map_err(|_| ()) 133 .expect("Collection creation price should be convertible to u128");134 if value != creation_price {135 return Err(format!(136 "Sent amount not equals to collection creation price ({0})",137 creation_price138 )139 .into());140 }141 Ok(())142}143144145#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]146impl<T> EvmCollectionHelpers<T>147where148 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,149{150 151 152 153 154 155 #[weight(<SelfWeightOf<T>>::create_collection())]156 #[solidity(rename_selector = "createNFTCollection")]157 fn create_nft_collection(158 &mut self,159 caller: caller,160 value: value,161 name: string,162 description: string,163 token_prefix: string,164 ) -> Result<address> {165 let (caller, name, description, token_prefix) =166 convert_data::<T>(caller, name, description, token_prefix)?;167 let data = CreateCollectionData {168 name,169 mode: CollectionMode::NFT,170 description,171 token_prefix,172 ..Default::default()173 };174 check_sent_amount_equals_collection_creation_price::<T>(value)?;175 let collection_helpers_address =176 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());177 let collection_id = T::CollectionDispatch::create(178 caller,179 collection_helpers_address,180 data,181 Default::default(),182 )183 .map_err(dispatch_to_evm::<T>)?;184185 let address = pallet_common::eth::collection_id_to_address(collection_id);186 Ok(address)187 }188 189 190 191 192 193 #[weight(<SelfWeightOf<T>>::create_collection())]194 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]195 #[solidity(hide)]196 fn create_nonfungible_collection(197 &mut self,198 caller: caller,199 value: value,200 name: string,201 description: string,202 token_prefix: string,203 ) -> Result<address> {204 create_collection_internal::<T>(205 caller,206 value,207 name,208 CollectionMode::NFT,209 description,210 token_prefix,211 )212 }213214 #[weight(<SelfWeightOf<T>>::create_collection())]215 #[solidity(rename_selector = "createRFTCollection")]216 fn create_rft_collection(217 &mut self,218 caller: caller,219 value: value,220 name: string,221 description: string,222 token_prefix: string,223 ) -> Result<address> {224 create_collection_internal::<T>(225 caller,226 value,227 name,228 CollectionMode::ReFungible,229 description,230 token_prefix,231 )232 }233234 #[weight(<SelfWeightOf<T>>::create_collection())]235 #[solidity(rename_selector = "createFTCollection")]236 fn create_fungible_collection(237 &mut self,238 caller: caller,239 value: value,240 name: string,241 decimals: uint8,242 description: string,243 token_prefix: string,244 ) -> Result<address> {245 create_collection_internal::<T>(246 caller,247 value,248 name,249 CollectionMode::Fungible(decimals),250 description,251 token_prefix,252 )253 }254255 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]256 fn make_collection_metadata_compatible(257 &mut self,258 caller: caller,259 collection: address,260 base_uri: string,261 ) -> Result<()> {262 let caller = T::CrossAccountId::from_eth(caller);263 let collection =264 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;265 let mut collection =266 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;267268 if !matches!(269 collection.mode,270 CollectionMode::NFT | CollectionMode::ReFungible271 ) {272 return Err("target collection should be either NFT or Refungible".into());273 }274275 self.recorder().consume_sstore()?;276 collection277 .check_is_owner_or_admin(&caller)278 .map_err(dispatch_to_evm::<T>)?;279280 if collection.flags.erc721metadata {281 return Err("target collection is already Erc721Metadata compatible".into());282 }283 collection.flags.erc721metadata = true;284285 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);286 if all_permissions.get(&key::url()).is_none() {287 self.recorder().consume_sstore()?;288 <PalletCommon<T>>::set_property_permission(289 &collection,290 &caller,291 up_data_structs::PropertyKeyPermission {292 key: key::url(),293 permission: up_data_structs::PropertyPermission {294 mutable: true,295 collection_admin: true,296 token_owner: false,297 },298 },299 )300 .map_err(dispatch_to_evm::<T>)?;301 }302 if all_permissions.get(&key::suffix()).is_none() {303 self.recorder().consume_sstore()?;304 <PalletCommon<T>>::set_property_permission(305 &collection,306 &caller,307 up_data_structs::PropertyKeyPermission {308 key: key::suffix(),309 permission: up_data_structs::PropertyPermission {310 mutable: true,311 collection_admin: true,312 token_owner: false,313 },314 },315 )316 .map_err(dispatch_to_evm::<T>)?;317 }318319 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);320 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {321 self.recorder().consume_sstore()?;322 <PalletCommon<T>>::set_collection_properties(323 &collection,324 &caller,325 vec![up_data_structs::Property {326 key: key::base_uri(),327 value: base_uri328 .into_bytes()329 .try_into()330 .map_err(|_| "base uri is too large")?,331 }],332 )333 .map_err(dispatch_to_evm::<T>)?;334 }335336 self.recorder().consume_sstore()?;337 collection.save().map_err(dispatch_to_evm::<T>)?;338339 Ok(())340 }341342 #[weight(<SelfWeightOf<T>>::destroy_collection())]343 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {344 let caller = T::CrossAccountId::from_eth(caller);345346 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)347 .ok_or("Invalid collection address format")?;348 <Pallet<T>>::destroy_collection_internal(caller, collection_id)349 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)350 }351352 353 354 355 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {356 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {357 let collection_id = id;358 return Ok(<CollectionById<T>>::contains_key(collection_id));359 }360361 Ok(false)362 }363364 fn collection_creation_fee(&self) -> Result<value> {365 let price: u128 = T::CollectionCreationPrice::get()366 .try_into()367 .map_err(|_| ()) 368 .expect("Collection creation price should be convertible to u128");369 Ok(price.into())370 }371}372373374pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);375impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>376 for CollectionHelpersOnMethodCall<T>377{378 fn is_reserved(contract: &sp_core::H160) -> bool {379 contract == &T::ContractAddress::get()380 }381382 fn is_used(contract: &sp_core::H160) -> bool {383 contract == &T::ContractAddress::get()384 }385386 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {387 if handle.code_address() != T::ContractAddress::get() {388 return None;389 }390391 let helpers =392 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));393 pallet_evm_coder_substrate::call(handle, helpers)394 }395396 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {397 (contract == &T::ContractAddress::get())398 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())399 }400}401402generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);403generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);404405fn error_field_too_long(feild: &str, bound: usize) -> Error {406 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))407}