difftreelog
added `destroyCollection`method to `CollectionHelpers`
in: master
10 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6799,7 +6799,7 @@
[[package]]
name = "pallet-unique"
-version = "0.2.0"
+version = "0.2.1"
dependencies = [
"ethereum",
"evm-coder",
pallets/unique/CHANGELOG.mddiffbeforeafterboth--- a/pallets/unique/CHANGELOG.md
+++ b/pallets/unique/CHANGELOG.md
@@ -4,33 +4,40 @@
<!-- bureaucrate goes here -->
+## [v0.2.1] 2022-10-10
+
+### Changes
+
+- Addded **CollectionHelpers** method `destroyCollection`.
+
## [v0.2.0] 2022-09-13
### Changes
-- Change **collectionHelper** method `createRefungibleCollection` to `createRFTCollection`,
+- Change **collectionHelper** method `createRefungibleCollection` to `createRFTCollection`,
+
## [v0.1.4] 2022-09-05
### Added
-- Methods `force_set_sponsor` , `force_remove_collection_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
+- Methods `force_set_sponsor` , `force_remove_collection_sponsor` to be able to administer sponsorships with other pallets. Added to implement `AppPromotion` pallet logic.
## [v0.1.3] 2022-08-16
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
-- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
+- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
-- refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
+- refactor: Remove `#[transactional]` from extrinsics 7fd36cea2f6e00c02c67ccc1de9649ae404efd31
Every extrinsic now runs in transaction implicitly, and
`#[transactional]` on pallet dispatchable is now meaningless
Upstream-Change: https://github.com/paritytech/substrate/issues/10806
-- refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
+- refactor: Switch to new prefix removal methods 26734e9567589d75cdd99e404eabf11d5a97d975
New methods allows to call `remove_prefix` with limit multiple times
in the same block
@@ -39,12 +46,12 @@
Upstream-Change: https://github.com/paritytech/substrate/pull/11490
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
## [v0.1.1] - 2022-07-25
### Added
-- Method for creating `ERC721Metadata` compatible NFT collection.
-- Method for creating `ERC721Metadata` compatible ReFungible collection.
-- Method for creating ReFungible collection.
+- Method for creating `ERC721Metadata` compatible NFT collection.
+- Method for creating `ERC721Metadata` compatible ReFungible collection.
+- Method for creating ReFungible collection.
pallets/unique/Cargo.tomldiffbeforeafterboth--- a/pallets/unique/Cargo.toml
+++ b/pallets/unique/Cargo.toml
@@ -9,7 +9,7 @@
license = 'GPLv3'
name = 'pallet-unique'
repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = "0.2.0"
+version = "0.2.1"
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
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) -> 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 #[solidity(hide)]188 fn create_nonfungible_collection(189 &mut self,190 caller: caller,191 value: value,192 name: string,193 description: string,194 token_prefix: string,195 ) -> Result<address> {196 self.create_nft_collection(caller, value, name, description, token_prefix)197 }198199 #[weight(<SelfWeightOf<T>>::create_collection())]200 #[solidity(rename_selector = "createRFTCollection")]201 fn create_rft_collection(202 &mut self,203 caller: caller,204 value: value,205 name: string,206 description: string,207 token_prefix: string,208 ) -> Result<address> {209 create_refungible_collection_internal::<T>(caller, value, name, description, token_prefix)210 }211212 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]213 fn make_collection_metadata_compatible(214 &mut self,215 caller: caller,216 collection: address,217 base_uri: string,218 ) -> Result<()> {219 let caller = T::CrossAccountId::from_eth(caller);220 let collection =221 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;222 let mut collection =223 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;224225 if !matches!(226 collection.mode,227 CollectionMode::NFT | CollectionMode::ReFungible228 ) {229 return Err("target collection should be either NFT or Refungible".into());230 }231232 self.recorder().consume_sstore()?;233 collection234 .check_is_owner_or_admin(&caller)235 .map_err(dispatch_to_evm::<T>)?;236237 if collection.flags.erc721metadata {238 return Err("target collection is already Erc721Metadata compatible".into());239 }240 collection.flags.erc721metadata = true;241242 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);243 if all_permissions.get(&key::url()).is_none() {244 self.recorder().consume_sstore()?;245 <PalletCommon<T>>::set_property_permission(246 &collection,247 &caller,248 up_data_structs::PropertyKeyPermission {249 key: key::url(),250 permission: up_data_structs::PropertyPermission {251 mutable: true,252 collection_admin: true,253 token_owner: false,254 },255 },256 )257 .map_err(dispatch_to_evm::<T>)?;258 }259 if all_permissions.get(&key::suffix()).is_none() {260 self.recorder().consume_sstore()?;261 <PalletCommon<T>>::set_property_permission(262 &collection,263 &caller,264 up_data_structs::PropertyKeyPermission {265 key: key::suffix(),266 permission: up_data_structs::PropertyPermission {267 mutable: true,268 collection_admin: true,269 token_owner: false,270 },271 },272 )273 .map_err(dispatch_to_evm::<T>)?;274 }275276 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);277 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {278 self.recorder().consume_sstore()?;279 <PalletCommon<T>>::set_collection_properties(280 &collection,281 &caller,282 vec![up_data_structs::Property {283 key: key::base_uri(),284 value: base_uri285 .into_bytes()286 .try_into()287 .map_err(|_| "base uri is too large")?,288 }],289 )290 .map_err(dispatch_to_evm::<T>)?;291 }292293 self.recorder().consume_sstore()?;294 collection.save().map_err(dispatch_to_evm::<T>)?;295296 Ok(())297 }298299 /// Check if a collection exists300 /// @param collectionAddress Address of the collection in question301 /// @return bool Does the collection exist?302 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {303 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {304 let collection_id = id;305 return Ok(<CollectionById<T>>::contains_key(collection_id));306 }307308 Ok(false)309 }310311 fn collection_creation_fee(&self) -> Result<value> {312 let price: u128 = T::CollectionCreationPrice::get()313 .try_into()314 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait315 .expect("Collection creation price should be convertible to u128");316 Ok(price.into())317 }318}319320/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]321pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);322impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>323 for CollectionHelpersOnMethodCall<T>324{325 fn is_reserved(contract: &sp_core::H160) -> bool {326 contract == &T::ContractAddress::get()327 }328329 fn is_used(contract: &sp_core::H160) -> bool {330 contract == &T::ContractAddress::get()331 }332333 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {334 if handle.code_address() != T::ContractAddress::get() {335 return None;336 }337338 let helpers =339 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));340 pallet_evm_coder_substrate::call(handle, helpers)341 }342343 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {344 (contract == &T::ContractAddress::get())345 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())346 }347}348349generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);350generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);351352fn error_field_too_long(feild: &str, bound: usize) -> Error {353 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))354}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, storage::StorageNMap};2324use crate::sp_api_hidden_includes_decl_storage::hidden_include::StorageDoubleMap;25use pallet_common::{26 CollectionById,27 dispatch::CollectionDispatch,28 erc::{29 CollectionHelpersEvents,30 static_property::{key},31 },32 Pallet as PalletCommon,33};34use pallet_evm_coder_substrate::{dispatch_to_evm, SubstrateRecorder, WithRecorder};35use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};36use sp_std::vec;37use up_data_structs::{38 CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData,39 CollectionMode, PropertyValue, CollectionFlags,40};4142use crate::{43 Config, SelfWeightOf, weights::WeightInfo, NftTransferBasket, FungibleTransferBasket,44 ReFungibleTransferBasket, NftApproveBasket, FungibleApproveBasket, RefungibleApproveBasket,45};4647use sp_std::vec::Vec;48use alloc::format;4950/// See [`CollectionHelpersCall`]51pub struct EvmCollectionHelpers<T: Config>(SubstrateRecorder<T>);52impl<T: Config> WithRecorder<T> for EvmCollectionHelpers<T> {53 fn recorder(&self) -> &SubstrateRecorder<T> {54 &self.055 }5657 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.059 }60}6162fn convert_data<T: Config>(63 caller: caller,64 name: string,65 description: string,66 token_prefix: string,67) -> Result<(68 T::CrossAccountId,69 CollectionName,70 CollectionDescription,71 CollectionTokenPrefix,72)> {73 let caller = T::CrossAccountId::from_eth(caller);74 let name = name75 .encode_utf16()76 .collect::<Vec<u16>>()77 .try_into()78 .map_err(|_| error_field_too_long(stringify!(name), CollectionName::bound()))?;79 let description = description80 .encode_utf16()81 .collect::<Vec<u16>>()82 .try_into()83 .map_err(|_| {84 error_field_too_long(stringify!(description), CollectionDescription::bound())85 })?;86 let token_prefix = token_prefix.into_bytes().try_into().map_err(|_| {87 error_field_too_long(stringify!(token_prefix), CollectionTokenPrefix::bound())88 })?;89 Ok((caller, name, description, token_prefix))90}9192fn create_refungible_collection_internal<93 T: Config + pallet_nonfungible::Config + pallet_refungible::Config,94>(95 caller: caller,96 value: value,97 name: string,98 description: string,99 token_prefix: string,100) -> Result<address> {101 let (caller, name, description, token_prefix) =102 convert_data::<T>(caller, name, description, token_prefix)?;103 let data = CreateCollectionData {104 name,105 mode: CollectionMode::ReFungible,106 description,107 token_prefix,108 ..Default::default()109 };110 check_sent_amount_equals_collection_creation_price::<T>(value)?;111 let collection_helpers_address =112 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());113114 let collection_id = T::CollectionDispatch::create(115 caller.clone(),116 collection_helpers_address,117 data,118 Default::default(),119 )120 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;121 let address = pallet_common::eth::collection_id_to_address(collection_id);122 Ok(address)123}124125fn check_sent_amount_equals_collection_creation_price<T: Config>(value: value) -> Result<()> {126 let value = value.as_u128();127 let creation_price: u128 = T::CollectionCreationPrice::get()128 .try_into()129 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait130 .expect("Collection creation price should be convertible to u128");131 if value != creation_price {132 return Err(format!(133 "Sent amount not equals to collection creation price ({0})",134 creation_price135 )136 .into());137 }138 Ok(())139}140141/// @title Contract, which allows users to operate with collections142#[solidity_interface(name = CollectionHelpers, events(CollectionHelpersEvents))]143impl<T> EvmCollectionHelpers<T>144where145 T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config,146{147 /// Create an NFT collection148 /// @param name Name of the collection149 /// @param description Informative description of the collection150 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications151 /// @return address Address of the newly created collection152 #[weight(<SelfWeightOf<T>>::create_collection())]153 #[solidity(rename_selector = "createNFTCollection")]154 fn create_nft_collection(155 &mut self,156 caller: caller,157 value: value,158 name: string,159 description: string,160 token_prefix: string,161 ) -> Result<address> {162 let (caller, name, description, token_prefix) =163 convert_data::<T>(caller, name, description, token_prefix)?;164 let data = CreateCollectionData {165 name,166 mode: CollectionMode::NFT,167 description,168 token_prefix,169 ..Default::default()170 };171 check_sent_amount_equals_collection_creation_price::<T>(value)?;172 let collection_helpers_address =173 T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());174 let collection_id = T::CollectionDispatch::create(175 caller,176 collection_helpers_address,177 data,178 Default::default(),179 )180 .map_err(dispatch_to_evm::<T>)?;181182 let address = pallet_common::eth::collection_id_to_address(collection_id);183 Ok(address)184 }185 /// Create an NFT collection186 /// @param name Name of the collection187 /// @param description Informative description of the collection188 /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications189 /// @return address Address of the newly created collection190 #[weight(<SelfWeightOf<T>>::create_collection())]191 #[deprecated(note = "mathod was renamed to `create_nft_collection`, prefer it instead")]192 #[solidity(hide)]193 fn create_nonfungible_collection(194 &mut self,195 caller: caller,196 value: value,197 name: string,198 description: string,199 token_prefix: string,200 ) -> Result<address> {201 self.create_nft_collection(caller, value, name, description, token_prefix)202 }203204 #[weight(<SelfWeightOf<T>>::create_collection())]205 #[solidity(rename_selector = "createRFTCollection")]206 fn create_rft_collection(207 &mut self,208 caller: caller,209 value: value,210 name: string,211 description: string,212 token_prefix: string,213 ) -> Result<address> {214 create_refungible_collection_internal::<T>(caller, value, name, description, token_prefix)215 }216217 #[solidity(rename_selector = "makeCollectionERC721MetadataCompatible")]218 fn make_collection_metadata_compatible(219 &mut self,220 caller: caller,221 collection: address,222 base_uri: string,223 ) -> Result<()> {224 let caller = T::CrossAccountId::from_eth(caller);225 let collection =226 pallet_common::eth::map_eth_to_id(&collection).ok_or("not a collection address")?;227 let mut collection =228 <crate::CollectionHandle<T>>::new(collection).ok_or("collection not found")?;229230 if !matches!(231 collection.mode,232 CollectionMode::NFT | CollectionMode::ReFungible233 ) {234 return Err("target collection should be either NFT or Refungible".into());235 }236237 self.recorder().consume_sstore()?;238 collection239 .check_is_owner_or_admin(&caller)240 .map_err(dispatch_to_evm::<T>)?;241242 if collection.flags.erc721metadata {243 return Err("target collection is already Erc721Metadata compatible".into());244 }245 collection.flags.erc721metadata = true;246247 let all_permissions = <pallet_common::CollectionPropertyPermissions<T>>::get(collection.id);248 if all_permissions.get(&key::url()).is_none() {249 self.recorder().consume_sstore()?;250 <PalletCommon<T>>::set_property_permission(251 &collection,252 &caller,253 up_data_structs::PropertyKeyPermission {254 key: key::url(),255 permission: up_data_structs::PropertyPermission {256 mutable: true,257 collection_admin: true,258 token_owner: false,259 },260 },261 )262 .map_err(dispatch_to_evm::<T>)?;263 }264 if all_permissions.get(&key::suffix()).is_none() {265 self.recorder().consume_sstore()?;266 <PalletCommon<T>>::set_property_permission(267 &collection,268 &caller,269 up_data_structs::PropertyKeyPermission {270 key: key::suffix(),271 permission: up_data_structs::PropertyPermission {272 mutable: true,273 collection_admin: true,274 token_owner: false,275 },276 },277 )278 .map_err(dispatch_to_evm::<T>)?;279 }280281 let all_properties = <pallet_common::CollectionProperties<T>>::get(collection.id);282 if all_properties.get(&key::base_uri()).is_none() && !base_uri.is_empty() {283 self.recorder().consume_sstore()?;284 <PalletCommon<T>>::set_collection_properties(285 &collection,286 &caller,287 vec![up_data_structs::Property {288 key: key::base_uri(),289 value: base_uri290 .into_bytes()291 .try_into()292 .map_err(|_| "base uri is too large")?,293 }],294 )295 .map_err(dispatch_to_evm::<T>)?;296 }297298 self.recorder().consume_sstore()?;299 collection.save().map_err(dispatch_to_evm::<T>)?;300301 Ok(())302 }303304 #[weight(<SelfWeightOf<T>>::destroy_collection())]305 #[solidity(rename_selector = "destroyCollection")]306 fn destroy_collection(&mut self, caller: caller, collection_address: address) -> Result<void> {307 let caller = T::CrossAccountId::from_eth(caller);308 let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)309 .ok_or("Invalid collection address format".into())310 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;311 let collection = <pallet_common::CollectionHandle<T>>::try_get(collection_id)312 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;313 collection314 .check_is_internal()315 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;316317 T::CollectionDispatch::destroy(caller, collection)318 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;319320 let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);321 let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);322 let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);323324 let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);325 let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);326 let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);327328 Ok(())329 }330331 /// Check if a collection exists332 /// @param collectionAddress Address of the collection in question333 /// @return bool Does the collection exist?334 fn is_collection_exist(&self, _caller: caller, collection_address: address) -> Result<bool> {335 if let Some(id) = pallet_common::eth::map_eth_to_id(&collection_address) {336 let collection_id = id;337 return Ok(<CollectionById<T>>::contains_key(collection_id));338 }339340 Ok(false)341 }342343 fn collection_creation_fee(&self) -> Result<value> {344 let price: u128 = T::CollectionCreationPrice::get()345 .try_into()346 .map_err(|_| ()) // workaround for `expect` requiring `Debug` trait347 .expect("Collection creation price should be convertible to u128");348 Ok(price.into())349 }350}351352/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]353pub struct CollectionHelpersOnMethodCall<T: Config>(PhantomData<*const T>);354impl<T: Config + pallet_nonfungible::Config + pallet_refungible::Config> OnMethodCall<T>355 for CollectionHelpersOnMethodCall<T>356{357 fn is_reserved(contract: &sp_core::H160) -> bool {358 contract == &T::ContractAddress::get()359 }360361 fn is_used(contract: &sp_core::H160) -> bool {362 contract == &T::ContractAddress::get()363 }364365 fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {366 if handle.code_address() != T::ContractAddress::get() {367 return None;368 }369370 let helpers =371 EvmCollectionHelpers::<T>(SubstrateRecorder::<T>::new(handle.remaining_gas()));372 pallet_evm_coder_substrate::call(handle, helpers)373 }374375 fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {376 (contract == &T::ContractAddress::get())377 .then(|| include_bytes!("./stubs/CollectionHelpers.raw").to_vec())378 }379}380381generate_stubgen!(collection_helper_impl, CollectionHelpersCall<()>, true);382generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);383384fn error_field_too_long(feild: &str, bound: usize) -> Error {385 Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))386}pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -23,7 +23,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x58918631
+/// @dev the ERC-165 identifier for this interface is 0x0edfb42e
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -85,6 +85,14 @@
dummy = 0;
}
+ /// @dev EVM selector for this function is: 0x564e321f,
+ /// or in textual repr: destroyCollection(address)
+ function destroyCollection(address collectionAddress) public {
+ require(false, stub_error);
+ collectionAddress;
+ dummy = 0;
+ }
+
/// Check if a collection exists
/// @param collectionAddress Address of the collection in question
/// @return bool Does the collection exist?
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -18,7 +18,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x58918631
+/// @dev the ERC-165 identifier for this interface is 0x0edfb42e
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -54,6 +54,10 @@
/// or in textual repr: makeCollectionERC721MetadataCompatible(address,string)
function makeCollectionERC721MetadataCompatible(address collection, string memory baseUri) external;
+ /// @dev EVM selector for this function is: 0x564e321f,
+ /// or in textual repr: destroyCollection(address)
+ function destroyCollection(address collectionAddress) external;
+
/// Check if a collection exists
/// @param collectionAddress Address of the collection in question
/// @return bool Does the collection exist?
tests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -55,6 +55,19 @@
"type": "address"
}
],
+ "name": "destroyCollection",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "collectionAddress",
+ "type": "address"
+ }
+ ],
"name": "isCollectionExist",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createRFTCollection.test.ts
+++ b/tests/src/eth/createRFTCollection.test.ts
@@ -263,4 +263,18 @@
.setCollectionLimit('badLimit', 'true')
.call()).to.be.rejectedWith('unknown boolean limit "badLimit"');
});
+
+ itEth('destroyCollection test', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
+ const collectionHelper = helper.ethNativeContract.collectionHelpers(owner);
+
+ await expect(collectionHelper.methods
+ .destroyCollection(collectionAddress)
+ .send({from: owner})).to.be.fulfilled;
+
+ expect(await collectionHelper.methods
+ .isCollectionExist(collectionAddress)
+ .call()).to.be.false;
+ });
});
tests/src/eth/destroyCollection.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/destroyCollection.test.ts
@@ -0,0 +1,76 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets, requirePalletsOrSkip} from '../util';
+import {expect, itEth, usingEthPlaygrounds} from './util';
+
+
+describe('Destroy Collection from EVM', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible, Pallets.NFT]);
+ donor = await privateKey('//Alice');
+ });
+ });
+
+
+ itEth('(!negative test!) RFT', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const signer = await helper.eth.createAccountWithBalance(donor);
+
+ const unexistedCollection = helper.ethAddress.fromCollectionId(1000000);
+
+ const {collectionAddress} = await helper.eth.createRFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
+ const collectionHelper = helper.ethNativeContract.collectionHelpers(signer);
+
+ await expect(collectionHelper.methods
+ .destroyCollection(collectionAddress)
+ .send({from: signer})).to.be.rejected;
+
+ await expect(collectionHelper.methods
+ .destroyCollection(unexistedCollection)
+ .send({from: signer})).to.be.rejected;
+
+ expect(await collectionHelper.methods
+ .isCollectionExist(unexistedCollection)
+ .call()).to.be.false;
+ });
+
+ itEth('(!negative test!) NFT', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const signer = await helper.eth.createAccountWithBalance(donor);
+
+ const unexistedCollection = helper.ethAddress.fromCollectionId(1000000);
+
+ const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'Limits', 'absolutely anything', 'OLF');
+ const collectionHelper = helper.ethNativeContract.collectionHelpers(signer);
+
+ await expect(collectionHelper.methods
+ .destroyCollection(collectionAddress)
+ .send({from: signer})).to.be.rejected;
+
+ await expect(collectionHelper.methods
+ .destroyCollection(unexistedCollection)
+ .send({from: signer})).to.be.rejected;
+
+ expect(await collectionHelper.methods
+ .isCollectionExist(unexistedCollection)
+ .call()).to.be.false;
+ });
+});