--- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -430,10 +430,53 @@ Ok(()) } -/// Get the "tokenURI" key as [PropertyKey](up_data_structs::PropertyKey). -pub fn token_uri_key() -> up_data_structs::PropertyKey { - b"tokenURI" - .to_vec() - .try_into() - .expect("length < limit; qed") +pub mod static_property_key_value { + use evm_coder::{ + execution::{Result, Error}, + }; + use alloc::format; + + const EXPECT_CONVERT_ERROR: &str = "length < limit"; + /// Get the "tokenURI" key as [PropertyKey](up_data_structs::PropertyKey). + pub fn token_uri_key() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"tokenURI").expect(EXPECT_CONVERT_ERROR) + } + + pub fn schema_name_key() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR) + } + + pub fn base_uri_key() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR) + } + + pub fn u_key() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"u").expect(EXPECT_CONVERT_ERROR) + } + + pub fn s_key() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"s").expect(EXPECT_CONVERT_ERROR) + } + + pub fn erc721_value() -> up_data_structs::PropertyValue { + property_value_from_bytes(b"ERC721").expect(EXPECT_CONVERT_ERROR) + } + + pub fn property_key_from_bytes(bytes: &[u8]) -> Result { + bytes.to_vec().try_into().map_err(|_| { + Error::Revert(format!( + "Property key is too long. Max length is {}.", + up_data_structs::PropertyKey::bound() + )) + }) + } + + pub fn property_value_from_bytes(bytes: &[u8]) -> Result { + bytes.to_vec().try_into().map_err(|_| { + Error::Revert(format!( + "Property key is too long. Max length is {}.", + up_data_structs::PropertyKey::bound() + )) + }) + } } --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -33,7 +33,7 @@ use pallet_evm_coder_substrate::dispatch_to_evm; use sp_std::vec::Vec; use pallet_common::{ - erc::{CommonEvmHandler, PrecompileResult, CollectionCall, token_uri_key}, + erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property_key_value::*}, CollectionHandle, CollectionPropertyPermissions, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -17,18 +17,18 @@ //! Implementation of CollectionHelpers contract. use core::marker::PhantomData; -use evm_coder::{execution::*, generate_stubgen, solidity_interface, weight, types::*}; +use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use ethereum as _; use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId, PrecompileHandle}; use up_data_structs::{ - CreateCollectionData, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, - MAX_COLLECTION_NAME_LENGTH, + CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, + CollectionMode, PropertyValue, }; use frame_support::traits::Get; use pallet_common::{ CollectionById, - erc::{token_uri_key, CollectionHelpersEvents}, + erc::{static_property_key_value::*, CollectionHelpersEvents}, }; use crate::{SelfWeightOf, Config, weights::WeightInfo}; @@ -47,6 +47,116 @@ } } +fn convert_data( + caller: caller, + name: string, + description: string, + token_prefix: string, + base_uri: string, +) -> Result<( + T::CrossAccountId, + CollectionName, + CollectionDescription, + CollectionTokenPrefix, + PropertyValue, +)> { + let caller = T::CrossAccountId::from_eth(caller); + let name = name + .encode_utf16() + .collect::>() + .try_into() + .map_err(|_| error_feild_too_long(stringify!(name), CollectionName::bound()))?; + let description = description + .encode_utf16() + .collect::>() + .try_into() + .map_err(|_| { + error_feild_too_long(stringify!(description), CollectionDescription::bound()) + })?; + let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| { + error_feild_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound()) + })?; + let base_uri_value = base_uri + .into_bytes() + .try_into() + .map_err(|_| error_feild_too_long(stringify!(token_prefix), PropertyValue::bound()))?; + Ok((caller, name, description, token_prefix, base_uri_value)) +} + +fn make_data( + name: CollectionName, + mode: CollectionMode, + description: CollectionDescription, + token_prefix: CollectionTokenPrefix, + base_uri_value: PropertyValue, + add_properties: bool, +) -> Result> { + let mut collection_properties = up_data_structs::CollectionPropertiesVec::default(); + let mut token_property_permissions = + up_data_structs::CollectionPropertiesPermissionsVec::default(); + + if add_properties { + token_property_permissions + .try_push(up_data_structs::PropertyKeyPermission { + key: token_uri_key(), + permission: up_data_structs::PropertyPermission { + mutable: true, + collection_admin: true, + token_owner: false, + }, + }) + .map_err(|e| Error::Revert(format!("{:?}", e)))?; + + token_property_permissions + .try_push(up_data_structs::PropertyKeyPermission { + key: u_key(), + permission: up_data_structs::PropertyPermission { + mutable: false, + collection_admin: true, + token_owner: false, + }, + }) + .map_err(|e| Error::Revert(format!("{:?}", e)))?; + + token_property_permissions + .try_push(up_data_structs::PropertyKeyPermission { + key: s_key(), + permission: up_data_structs::PropertyPermission { + mutable: false, + collection_admin: true, + token_owner: false, + }, + }) + .map_err(|e| Error::Revert(format!("{:?}", e)))?; + + collection_properties + .try_push(up_data_structs::Property { + key: schema_name_key(), + value: erc721_value(), + }) + .map_err(|e| Error::Revert(format!("{:?}", e)))?; + + if !base_uri_value.is_empty() { + collection_properties + .try_push(up_data_structs::Property { + key: base_uri_key(), + value: base_uri_value, + }) + .map_err(|e| Error::Revert(format!("{:?}", e)))?; + } + } + + let data = CreateCollectionData { + name, + mode, + description, + token_prefix, + token_property_permissions, + ..Default::default() + }; + Ok(data) +} + /// @title Contract, which allows users to operate with collections #[solidity_interface(name = "CollectionHelpers", events(CollectionHelpersEvents))] impl EvmCollectionHelpers { @@ -63,44 +173,30 @@ description: string, token_prefix: string, ) -> Result
{ - let caller = T::CrossAccountId::from_eth(caller); - let name = name - .encode_utf16() - .collect::>() - .try_into() - .map_err(|_| error_feild_too_long(stringify!(name), MAX_COLLECTION_NAME_LENGTH))?; - let description = description - .encode_utf16() - .collect::>() - .try_into() - .map_err(|_| { - error_feild_too_long(stringify!(description), MAX_COLLECTION_DESCRIPTION_LENGTH) - })?; - let token_prefix = token_prefix - .into_bytes() - .try_into() - .map_err(|_| error_feild_too_long(stringify!(token_prefix), MAX_TOKEN_PREFIX_LENGTH))?; + let (caller, name, description, token_prefix, _base_uri_value) = + convert_data::(caller, name, description, token_prefix, "".into())?; + let data = make_data::(name, CollectionMode::NFT, description, token_prefix, Default::default(), false)?; + let collection_id = + >::init_collection(caller.clone(), data, false) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; - let key = token_uri_key(); - let permission = up_data_structs::PropertyPermission { - mutable: true, - collection_admin: true, - token_owner: false, - }; - let mut token_property_permissions = - up_data_structs::CollectionPropertiesPermissionsVec::default(); - token_property_permissions - .try_push(up_data_structs::PropertyKeyPermission { key, permission }) - .map_err(|e| Error::Revert(format!("{:?}", e)))?; + let address = pallet_common::eth::collection_id_to_address(collection_id); + Ok(address) + } - let data = CreateCollectionData { - name, - description, - token_prefix, - token_property_permissions, - ..Default::default() - }; - + #[weight(>::create_collection())] + #[solidity(rename_selector = "createERC721MetadataCompatibleCollection")] + fn create_nonfungible_collection_with_properties( + &mut self, + caller: caller, + name: string, + description: string, + token_prefix: string, + base_uri: string, + ) -> Result
{ + let (caller, name, description, token_prefix, base_uri_value) = + convert_data::(caller, name, description, token_prefix, base_uri)?; + let data = make_data::(name, CollectionMode::NFT, description, token_prefix, base_uri_value, true)?; let collection_id = >::init_collection(caller.clone(), data, false) .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; @@ -152,6 +248,6 @@ generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true); generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false); -fn error_feild_too_long(feild: &str, bound: u32) -> Error { +fn error_feild_too_long(feild: &str, bound: usize) -> Error { Error::Revert(format!("{} is too long. Max length is {}.", feild, bound)) } --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -286,6 +286,10 @@ } } +pub type CollectionName = BoundedVec>; +pub type CollectionDescription = BoundedVec>; +pub type CollectionTokenPrefix = BoundedVec>; + /// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version). #[struct_versioning::versioned(version = 2, upper)] #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]