difftreelog
feat remove collection with metadata creation
in: master
1 file changed
pallets/unique/src/eth/mod.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Implementation of CollectionHelpers contract.1819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};22use frame_support::traits::Get;23use pallet_common::{24 CollectionById,25 dispatch::CollectionDispatch,26 erc::{27 CollectionHelpersEvents,28 static_property::{key},29 },30 Pallet as PalletCommon,31};32use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};33use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};34use sp_std::vec;35use up_data_structs::{36 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,37 CollectionMode, PropertyValue, CollectionFlags,38};3940use crate::{Config, SelfWeightOf, weights::WeightInfo};4142use sp_std::vec::Vec;43use alloc::format;4445/// See [`CollectionHelpersCall`]46pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);47impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {48 fn recorder(&self) -> &SubstrateRecorder<T> {49 &self.050 }5152 fn into_recorder(self) -> SubstrateRecorder<T> {53 self.054 }55}5657fn convert_data<T: Config>(58 caller: caller,59 name: string,60 description: string,61 token_prefix: string,62 base_uri: string,63) -> Result<(64 T::CrossAccountId,65 CollectionName,66 CollectionDescription,67 CollectionTokenPrefix,68 PropertyValue,69)> {70 let caller = T::CrossAccountId::from_eth(caller);71 let name = name72 .encode_utf16()73 .collect::<Vec<u16>>()74 .try_into()75 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;76 let description = description77 .encode_utf16()78 .collect::<Vec<u16>>()79 .try_into()80 .map_err(|_| {81 error_field_too_long(stringify!(description), CollectionDescription::bound())82 })?;83 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {84 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())85 })?;86 let base_uri_value = base_uri87 .into_bytes()88 .try_into()89 .map_err(|_| error_field_too_long(stringify!(token_prefix), PropertyValue::bound()))?;90 Ok((caller, name, description, token_prefix, base_uri_value))91}9293fn default_url_pkp() -> up_data_structs::PropertyKeyPermission {94 up_data_structs::PropertyKeyPermission {95 key: key::url(),96 permission: up_data_structs::PropertyPermission {97 mutable: true,98 collection_admin: true,99 token_owner: false,100 },101 }102}103fn default_suffix_pkp() -> up_data_structs::PropertyKeyPermission {104 up_data_structs::PropertyKeyPermission {105 key: key::suffix(),106 permission: up_data_structs::PropertyPermission {107 mutable: true,108 collection_admin: true,109 token_owner: false,110 },111 }112}113fn make_data<T: Config>(114 name: CollectionName,115 mode: CollectionMode,116 description: CollectionDescription,117 token_prefix: CollectionTokenPrefix,118 base_uri_value: PropertyValue,119 add_properties: bool,120) -> Result<CreateCollectionData<T::AccountId>> {121 let token_property_permissions = if add_properties {122 vec![default_url_pkp(), default_suffix_pkp()]123 .try_into()124 .map_err(|e| Error::Revert(format!("{:?}", e)))?125 } else {126 up_data_structs::CollectionPropertiesPermissionsVec::default()127 };128 let properties = if add_properties && !base_uri_value.is_empty() {129 vec![up_data_structs::Property {130 key: key::base_uri(),131 value: base_uri_value,132 }]133 .try_into()134 .expect("limit >= 1")135 } else {136 up_data_structs::CollectionPropertiesVec::default()137 };138139 let data = CreateCollectionData {140 name,141 mode,142 description,143 token_prefix,144 token_property_permissions,145 properties,146 ..Default::default()147 };148 Ok(data)149}150151fn create_refungible_collection_internal<152 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,153>(154 caller: caller,155 value: value,156 name: string,157 description: string,158 token_prefix: string,159 base_uri: string,160 add_properties: bool,161) -> Result<address> {162 let (caller, name, description, token_prefix, base_uri_value) =163 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;164 let data = make_data::<T>(165 name,166 CollectionMode::ReFungible,167 description,168 token_prefix,169 base_uri_value,170 add_properties,171 )?;172 check_sent_amount_equals_collection_creation_price::<T>(value)?;173 let collection_helpers_address =174 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());175176 let collection_id = T::CollectionDispatch::create(177 caller.clone(),178 collection_helpers_address,179 data,180 CollectionFlags {181 erc721metadata: add_properties,182 ..Default::default()183 },184 )185 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;186 let address = pallet_common::eth::collection_id_to_address(collection_id);187 Ok(address)188}189190fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {191 let value = value.as_u128();192 let creation_price: u128 = T::CollectionCreationPrice::get()193 .try_into()194 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait195 .expect("Collection creation price should be convertible to u128");196 if value != creation_price {197 return Err(format!(198 "Sent amount not equals to collection creation price ({0})",199 creation_price200 )201 .into());202 }203 Ok(())204}205206/// @title Contract, which allows users to operate with collections207#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]208impl<T> EvmCollectionHelpers<T>209where210 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,211{212 /// Create an NFT collection213 /// @param name Name of the collection214 /// @param description Informative description of the collection215 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications216 /// @return address Address of the newly created collection217 #[weight(<SelfWeightOf<T>>::create_collection())]218 #[solidity(rename_selector = "createNFTCollection")]219 fn create_nft_collection(220 &mut self,221 caller: caller,222 value: value,223 name: string,224 description: string,225 token_prefix: string,226 ) -> Result<address> {227 let (caller, name, description, token_prefix, _base_uri_value) =228 convert_data::<T>(caller, name, description, token_prefix, "".into())?;229 let data = make_data::<T>(230 name,231 CollectionMode::NFT,232 description,233 token_prefix,234 Default::default(),235 false,236 )?;237 check_sent_amount_equals_collection_creation_price::<T>(value)?;238 let collection_helpers_address =239 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());240 let collection_id = T::CollectionDispatch::create(241 caller,242 collection_helpers_address,243 data,244 Default::default(),245 )246 .map_err(dispatch_to_evm::<T>)?;247248 let address = pallet_common::eth::collection_id_to_address(collection_id);249 Ok(address)250 }251 /// Create an NFT collection252 /// @param name Name of the collection253 /// @param description Informative description of the collection254 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications255 /// @return address Address of the newly created collection256 #[weight(<SelfWeightOf<T>>::create_collection())]257 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]258 fn create_nonfungible_collection(259 &mut self,260 caller: caller,261 value: value,262 name: string,263 description: string,264 token_prefix: string,265 ) -> Result<address> {266 self.create_nft_collection(caller, value, name, description, token_prefix)267 }268269 #[weight(<SelfWeightOf<T>>::create_collection())]270 #[solidity(rename_selector = "createERC721MetadataCompatibleNFTCollection")]271 fn create_nonfungible_collection_with_properties(272 &mut self,273 caller: caller,274 value: value,275 name: string,276 description: string,277 token_prefix: string,278 base_uri: string,279 ) -> Result<address> {280 let (caller, name, description, token_prefix, base_uri_value) =281 convert_data::<T>(caller, name, description, token_prefix, base_uri)?;282 let data = make_data::<T>(283 name,284 CollectionMode::NFT,285 description,286 token_prefix,287 base_uri_value,288 true,289 )?;290 check_sent_amount_equals_collection_creation_price::<T>(value)?;291 let collection_helpers_address =292 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());293 let collection_id = T::CollectionDispatch::create(294 caller,295 collection_helpers_address,296 data,297 CollectionFlags {298 erc721metadata: true,299 ..Default::default()300 },301 )302 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;303304 let address = pallet_common::eth::collection_id_to_address(collection_id);305 Ok(address)306 }307308 #[weight(<SelfWeightOf<T>>::create_collection())]309 #[solidity(rename_selector = "createRFTCollection")]310 fn create_rft_collection(311 &mut self,312 caller: caller,313 value: value,314 name: string,315 description: string,316 token_prefix: string,317 ) -> Result<address> {318 create_refungible_collection_internal::<T>(319 caller,320 value,321 name,322 description,323 token_prefix,324 Default::default(),325 false,326 )327 }328329 #[weight(<SelfWeightOf<T>>::create_collection())]330 #[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]331 fn create_refungible_collection_with_properties(332 &mut self,333 caller: caller,334 value: value,335 name: string,336 description: string,337 token_prefix: string,338 base_uri: string,339 ) -> Result<address> {340 create_refungible_collection_internal::<T>(341 caller,342 value,343 name,344 description,345 token_prefix,346 base_uri,347 true,348 )349 }350351 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]352 fn make_collection_metadata_compatible(353 &mut self,354 caller: caller,355 collection: address,356 base_uri: string,357 ) -> Result<()> {358 let caller = T::CrossAccountId::from_eth(caller);359 let collection =360 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;361 let mut collection =362 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;363364 if !matches!(365 collection.mode,366 CollectionMode::NFT | CollectionMode::ReFungible367 ) {368 return Err("target collection should be either NFT or Refungible".into());369 }370371 self.recorder().consume_sstore()?;372 collection373 .check_is_owner_or_admin(&caller)374 .map_err(dispatch_to_evm::<T>)?;375376 if collection.flags.erc721metadata {377 return Err("target collection is already Erc721Metadata compatible".into());378 }379 collection.flags.erc721metadata = true;380381 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);382 if all_permissions.get(&key::url()).is_none() {383 self.recorder().consume_sstore()?;384 <PalletCommon<T>>::set_property_permission(&collection, &caller, default_url_pkp())385 .map_err(dispatch_to_evm::<T>)?;386 }387 if all_permissions.get(&key::suffix()).is_none() {388 self.recorder().consume_sstore()?;389 <PalletCommon<T>>::set_property_permission(&collection, &caller, default_suffix_pkp())390 .map_err(dispatch_to_evm::<T>)?;391 }392393 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);394 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {395 self.recorder().consume_sstore()?;396 <PalletCommon<T>>::set_collection_properties(397 &collection,398 &caller,399 vec![up_data_structs::Property {400 key: key::base_uri(),401 value: base_uri402 .into_bytes()403 .try_into()404 .map_err(|_| "base uri is too large")?,405 }],406 )407 .map_err(dispatch_to_evm::<T>)?;408 }409410 self.recorder().consume_sstore()?;411 collection.save().map_err(dispatch_to_evm::<T>)?;412413 Ok(())414 }415416 /// Check if a collection exists417 /// @param collectionAddress Address of the collection in question418 /// @return bool Does the collection exist?419 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {420 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {421 let collection_id = id;422 return Ok(<CollectionById<T>>::contains_key(collection_id));423 }424425 Ok(false)426 }427428 fn collection_creation_fee(&self) -> Result<value> {429 let price: u128 = T::CollectionCreationPrice::get()430 .try_into()431 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait432 .expect("Collection creation price should be convertible to u128");433 Ok(price.into())434 }435}436437/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]438pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);439impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>440 for CollectionHelpersOnMethodCall<T>441{442 fn is_reserved(contract: &sp_core::H160) -> bool {443 contract == &T::ContractAddress::get()444 }445446 fn is_used(contract: &sp_core::H160) -> bool {447 contract == &T::ContractAddress::get()448 }449450 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {451 if handle.code_address() != T::ContractAddress::get() {452 return None;453 }454455 let helpers =456 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));457 pallet_evm_coder_substrate::call(handle, helpers)458 }459460 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {461 (contract == &T::ContractAddress::get())462 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())463 }464}465466generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);467generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);468469fn error_field_too_long(feild: &str, bound: usize) -> Error {470 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))471}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! Implementation of CollectionHelpers contract.1819use core::marker::PhantomData;20use ethereum as _;21use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*};22use frame_support::traits::Get;23use pallet_common::{24 CollectionById,25 dispatch::CollectionDispatch,26 erc::{27 CollectionHelpersEvents,28 static_property::{key},29 },30 Pallet as PalletCommon,31};32use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};33use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};34use sp_std::vec;35use up_data_structs::{36 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,37 CollectionMode, PropertyValue, CollectionFlags,38};3940use crate::{Config, SelfWeightOf, weights::WeightInfo};4142use sp_std::vec::Vec;43use alloc::format;4445/// See [`CollectionHelpersCall`]46pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);47impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {48 fn recorder(&self) -> &SubstrateRecorder<T> {49 &self.050 }5152 fn into_recorder(self) -> SubstrateRecorder<T> {53 self.054 }55}5657fn convert_data<T: Config>(58 caller: caller,59 name: string,60 description: string,61 token_prefix: string,62) -> Result<(63 T::CrossAccountId,64 CollectionName,65 CollectionDescription,66 CollectionTokenPrefix,67)> {68 let caller = T::CrossAccountId::from_eth(caller);69 let name = name70 .encode_utf16()71 .collect::<Vec<u16>>()72 .try_into()73 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;74 let description = description75 .encode_utf16()76 .collect::<Vec<u16>>()77 .try_into()78 .map_err(|_| {79 error_field_too_long(stringify!(description), CollectionDescription::bound())80 })?;81 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {82 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())83 })?;84 Ok((caller, name, description, token_prefix))85}8687fn create_refungible_collection_internal<88 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,89>(90 caller: caller,91 value: value,92 name: string,93 description: string,94 token_prefix: string,95) -> Result<address> {96 let (caller, name, description, token_prefix) =97 convert_data::<T>(caller, name, description, token_prefix)?;98 let data = CreateCollectionData {99 name,100 mode: CollectionMode::ReFungible,101 description,102 token_prefix,103 ..Default::default()104 };105 check_sent_amount_equals_collection_creation_price::<T>(value)?;106 let collection_helpers_address =107 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());108109 let collection_id = T::CollectionDispatch::create(110 caller.clone(),111 collection_helpers_address,112 data,113 Default::default(),114 )115 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;116 let address = pallet_common::eth::collection_id_to_address(collection_id);117 Ok(address)118}119120fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {121 let value = value.as_u128();122 let creation_price: u128 = T::CollectionCreationPrice::get()123 .try_into()124 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait125 .expect("Collection creation price should be convertible to u128");126 if value != creation_price {127 return Err(format!(128 "Sent amount not equals to collection creation price ({0})",129 creation_price130 )131 .into());132 }133 Ok(())134}135136/// @title Contract, which allows users to operate with collections137#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]138impl<T> EvmCollectionHelpers<T>139where140 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,141{142 /// Create an NFT collection143 /// @param name Name of the collection144 /// @param description Informative description of the collection145 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications146 /// @return address Address of the newly created collection147 #[weight(<SelfWeightOf<T>>::create_collection())]148 #[solidity(rename_selector = "createNFTCollection")]149 fn create_nft_collection(150 &mut self,151 caller: caller,152 value: value,153 name: string,154 description: string,155 token_prefix: string,156 ) -> Result<address> {157 let (caller, name, description, token_prefix) =158 convert_data::<T>(caller, name, description, token_prefix)?;159 let data = CreateCollectionData {160 name,161 mode: CollectionMode::NFT,162 description,163 token_prefix,164 ..Default::default()165 };166 check_sent_amount_equals_collection_creation_price::<T>(value)?;167 let collection_helpers_address =168 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());169 let collection_id = T::CollectionDispatch::create(170 caller,171 collection_helpers_address,172 data,173 Default::default(),174 )175 .map_err(dispatch_to_evm::<T>)?;176177 let address = pallet_common::eth::collection_id_to_address(collection_id);178 Ok(address)179 }180 /// Create an NFT collection181 /// @param name Name of the collection182 /// @param description Informative description of the collection183 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications184 /// @return address Address of the newly created collection185 #[weight(<SelfWeightOf<T>>::create_collection())]186 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]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 self.create_nft_collection(caller, value, name, description, token_prefix)196 }197198 #[weight(<SelfWeightOf<T>>::create_collection())]199 #[solidity(rename_selector = "createRFTCollection")]200 fn create_rft_collection(201 &mut self,202 caller: caller,203 value: value,204 name: string,205 description: string,206 token_prefix: string,207 ) -> Result<address> {208 create_refungible_collection_internal::<T>(caller, value, name, description, token_prefix)209 }210211 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]212 fn make_collection_metadata_compatible(213 &mut self,214 caller: caller,215 collection: address,216 base_uri: string,217 ) -> Result<()> {218 let caller = T::CrossAccountId::from_eth(caller);219 let collection =220 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;221 let mut collection =222 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;223224 if !matches!(225 collection.mode,226 CollectionMode::NFT | CollectionMode::ReFungible227 ) {228 return Err("target collection should be either NFT or Refungible".into());229 }230231 self.recorder().consume_sstore()?;232 collection233 .check_is_owner_or_admin(&caller)234 .map_err(dispatch_to_evm::<T>)?;235236 if collection.flags.erc721metadata {237 return Err("target collection is already Erc721Metadata compatible".into());238 }239 collection.flags.erc721metadata = true;240241 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);242 if all_permissions.get(&key::url()).is_none() {243 self.recorder().consume_sstore()?;244 <PalletCommon<T>>::set_property_permission(245 &collection,246 &caller,247 up_data_structs::PropertyKeyPermission {248 key: key::url(),249 permission: up_data_structs::PropertyPermission {250 mutable: true,251 collection_admin: true,252 token_owner: false,253 },254 },255 )256 .map_err(dispatch_to_evm::<T>)?;257 }258 if all_permissions.get(&key::suffix()).is_none() {259 self.recorder().consume_sstore()?;260 <PalletCommon<T>>::set_property_permission(261 &collection,262 &caller,263 up_data_structs::PropertyKeyPermission {264 key: key::suffix(),265 permission: up_data_structs::PropertyPermission {266 mutable: true,267 collection_admin: true,268 token_owner: false,269 },270 },271 )272 .map_err(dispatch_to_evm::<T>)?;273 }274275 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);276 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {277 self.recorder().consume_sstore()?;278 <PalletCommon<T>>::set_collection_properties(279 &collection,280 &caller,281 vec![up_data_structs::Property {282 key: key::base_uri(),283 value: base_uri284 .into_bytes()285 .try_into()286 .map_err(|_| "base uri is too large")?,287 }],288 )289 .map_err(dispatch_to_evm::<T>)?;290 }291292 self.recorder().consume_sstore()?;293 collection.save().map_err(dispatch_to_evm::<T>)?;294295 Ok(())296 }297298 /// Check if a collection exists299 /// @param collectionAddress Address of the collection in question300 /// @return bool Does the collection exist?301 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {302 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {303 let collection_id = id;304 return Ok(<CollectionById<T>>::contains_key(collection_id));305 }306307 Ok(false)308 }309310 fn collection_creation_fee(&self) -> Result<value> {311 let price: u128 = T::CollectionCreationPrice::get()312 .try_into()313 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait314 .expect("Collection creation price should be convertible to u128");315 Ok(price.into())316 }317}318319/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]320pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);321impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>322 for CollectionHelpersOnMethodCall<T>323{324 fn is_reserved(contract: &sp_core::H160) -> bool {325 contract == &T::ContractAddress::get()326 }327328 fn is_used(contract: &sp_core::H160) -> bool {329 contract == &T::ContractAddress::get()330 }331332 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {333 if handle.code_address() != T::ContractAddress::get() {334 return None;335 }336337 let helpers =338 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));339 pallet_evm_coder_substrate::call(handle, helpers)340 }341342 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {343 (contract == &T::ContractAddress::get())344 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())345 }346}347348generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);349generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);350351fn error_field_too_long(feild: &str, bound: usize) -> Error {352 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))353}