difftreelog
refac: incapsulate CollectionHandler into CollectionDispatch
in: master
13 files changed
pallets/balances-adapter/src/lib.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -3,6 +3,7 @@
#![warn(missing_docs)]
extern crate alloc;
+use frame_support::sp_runtime::DispatchResult;
pub use pallet::*;
use pallet_common::CollectionHandle;
use pallet_evm_coder_substrate::{WithRecorder, SubstrateRecorder};
@@ -10,24 +11,23 @@
pub mod common;
pub mod erc;
-pub struct NativeFungibleHandle<T: Config>(CollectionHandle<T>);
+pub struct NativeFungibleHandle<T: Config>(SubstrateRecorder<T>);
impl<T: Config> NativeFungibleHandle<T> {
- pub fn cast(inner: CollectionHandle<T>) -> Self {
- Self(inner)
+ pub fn new() -> NativeFungibleHandle<T> {
+ Self(SubstrateRecorder::new(u64::MAX))
}
- /// Casts [`NativeFungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].
- pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {
- self.0
+ pub fn check_is_internal(&self) -> DispatchResult {
+ Ok(())
}
}
impl<T: Config> WithRecorder<T> for NativeFungibleHandle<T> {
fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
- &self.0.recorder
+ &self.0
}
fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
- self.0.recorder
+ self.0
}
}
#[frame_support::pallet]
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -34,16 +34,11 @@
collection: CollectionId,
call: C,
) -> DispatchResultWithPostInfo {
- let handle =
- CollectionHandle::try_get(collection).map_err(|error| DispatchErrorWithPostInfo {
- post_info: PostDispatchInfo {
- actual_weight: Some(dispatch_weight::<T>()),
- pays_fee: Pays::Yes,
- },
- error,
- })?;
- handle
- .check_is_internal()
+ let dispatched = T::CollectionDispatch::dispatch(collection)
+ .and_then(|dispatched| {
+ dispatched.check_is_internal()?;
+ Ok(dispatched)
+ })
.map_err(|error| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(dispatch_weight::<T>()),
@@ -51,7 +46,6 @@
},
error,
})?;
- let dispatched = T::CollectionDispatch::dispatch(handle);
let mut result = call(dispatched.as_dyn());
match &mut result {
Ok(PostDispatchInfo {
@@ -72,6 +66,8 @@
/// Interface for working with different collections through the dispatcher.
pub trait CollectionDispatch<T: Config> {
+ fn check_is_internal(&self) -> DispatchResult;
+
/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
///
/// * `sender` - The user who will become the owner of the collection.
@@ -92,7 +88,9 @@
/// Get a specialized collection from the handle.
///
/// * `handle` - Collection handle.
- fn dispatch(handle: CollectionHandle<T>) -> Self;
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>
+ where
+ Self: Sized;
/// Get the implementation of [`CommonCollectionOperations`].
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;
pallets/common/src/erc.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//! This module contains the implementation of pallet methods for evm.1819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use pallet_evm_coder_substrate::{21 abi::AbiType,22 solidity_interface, ToLog,23 types::*,24 execution::{Result, Error, PreDispatch},25 frontier_contract,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,31 SponsorshipState,32};3334use crate::{35 Pallet, CollectionHandle, Config, CollectionProperties, eth, SelfWeightOf, weights::WeightInfo,36};3738frontier_contract! {39 macro_rules! CollectionHandle_result {...}40 impl<T: Config> Contract for CollectionHandle<T> {...}41}4243/// Events for ethereum collection helper.44#[derive(ToLog)]45pub enum CollectionHelpersEvents {46 /// The collection has been created.47 CollectionCreated {48 /// Collection owner.49 #[indexed]50 owner: Address,5152 /// Collection ID.53 #[indexed]54 collection_id: Address,55 },56 /// The collection has been destroyed.57 CollectionDestroyed {58 /// Collection ID.59 #[indexed]60 collection_id: Address,61 },62 /// The collection has been changed.63 CollectionChanged {64 /// Collection ID.65 #[indexed]66 collection_id: Address,67 },68}6970/// Does not always represent a full collection, for RFT it is either71/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).72pub trait CommonEvmHandler {73 /// Raw compiled binary code of the contract stub74 const CODE: &'static [u8];7576 /// Call precompiled handle.77 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;78}7980/// @title A contract that allows you to work with collections.81#[solidity_interface(name = Collection, enum(derive(PreDispatch)), enum_attr(weight))]82impl<T: Config> CollectionHandle<T>83where84 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,85{86 /// Set collection property.87 ///88 /// @param key Property key.89 /// @param value Propery value.90 #[solidity(hide)]91 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]92 fn set_collection_property(&mut self, caller: Caller, key: String, value: Bytes) -> Result<()> {93 let caller = T::CrossAccountId::from_eth(caller);94 let key = <Vec<u8>>::from(key)95 .try_into()96 .map_err(|_| "key too large")?;97 let value = value.0.try_into().map_err(|_| "value too large")?;9899 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })100 .map_err(dispatch_to_evm::<T>)101 }102103 /// Set collection properties.104 ///105 /// @param properties Vector of properties key/value pair.106 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]107 fn set_collection_properties(108 &mut self,109 caller: Caller,110 properties: Vec<eth::Property>,111 ) -> Result<()> {112 let caller = T::CrossAccountId::from_eth(caller);113114 let properties = properties115 .into_iter()116 .map(eth::Property::try_into)117 .collect::<Result<Vec<_>>>()?;118119 <Pallet<T>>::set_collection_properties(self, &caller, properties.into_iter())120 .map_err(dispatch_to_evm::<T>)121 }122123 /// Delete collection property.124 ///125 /// @param key Property key.126 #[solidity(hide)]127 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]128 fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {129 let caller = T::CrossAccountId::from_eth(caller);130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too large")?;133134 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)135 }136137 /// Delete collection properties.138 ///139 /// @param keys Properties keys.140 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]141 fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {142 let caller = T::CrossAccountId::from_eth(caller);143 let keys = keys144 .into_iter()145 .map(|key| {146 <Vec<u8>>::from(key)147 .try_into()148 .map_err(|_| Error::Revert("key too large".into()))149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::delete_collection_properties(self, &caller, keys.into_iter())153 .map_err(dispatch_to_evm::<T>)154 }155156 /// Get collection property.157 ///158 /// @dev Throws error if key not found.159 ///160 /// @param key Property key.161 /// @return bytes The property corresponding to the key.162 fn collection_property(&self, key: String) -> Result<Bytes> {163 let key = <Vec<u8>>::from(key)164 .try_into()165 .map_err(|_| "key too large")?;166167 let props = CollectionProperties::<T>::get(self.id);168 let prop = props.get(&key).ok_or("key not found")?;169170 Ok(Bytes(prop.to_vec()))171 }172173 /// Get collection properties.174 ///175 /// @param keys Properties keys. Empty keys for all propertyes.176 /// @return Vector of properties key/value pairs.177 fn collection_properties(&self, keys: Vec<String>) -> Result<Vec<eth::Property>> {178 let keys = keys179 .into_iter()180 .map(|key| {181 <Vec<u8>>::from(key)182 .try_into()183 .map_err(|_| Error::Revert("key too large".into()))184 })185 .collect::<Result<Vec<_>>>()?;186187 let properties = Pallet::<T>::filter_collection_properties(188 self.id,189 if keys.is_empty() { None } else { Some(keys) },190 )191 .map_err(dispatch_to_evm::<T>)?;192193 let properties = properties194 .into_iter()195 .map(Property::try_into)196 .collect::<Result<Vec<_>>>()?;197 Ok(properties)198 }199200 /// Set the sponsor of the collection.201 ///202 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.203 ///204 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.205 #[solidity(hide)]206 fn set_collection_sponsor(&mut self, caller: Caller, sponsor: Address) -> Result<()> {207 self.consume_store_reads_and_writes(1, 1)?;208209 let caller = T::CrossAccountId::from_eth(caller);210211 let sponsor = T::CrossAccountId::from_eth(sponsor);212 self.set_sponsor(&caller, sponsor.as_sub().clone())213 .map_err(dispatch_to_evm::<T>)214 }215216 /// Set the sponsor of the collection.217 ///218 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.219 ///220 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.221 fn set_collection_sponsor_cross(222 &mut self,223 caller: Caller,224 sponsor: eth::CrossAddress,225 ) -> Result<()> {226 self.consume_store_reads_and_writes(1, 1)?;227228 let caller = T::CrossAccountId::from_eth(caller);229230 let sponsor = sponsor.into_sub_cross_account::<T>()?;231 self.set_sponsor(&caller, sponsor.as_sub().clone())232 .map_err(dispatch_to_evm::<T>)233 }234235 /// Whether there is a pending sponsor.236 fn has_collection_pending_sponsor(&self) -> Result<bool> {237 Ok(matches!(238 self.collection.sponsorship,239 SponsorshipState::Unconfirmed(_)240 ))241 }242243 /// Collection sponsorship confirmation.244 ///245 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.246 fn confirm_collection_sponsorship(&mut self, caller: Caller) -> Result<()> {247 self.consume_store_writes(1)?;248249 let caller = T::CrossAccountId::from_eth(caller);250 self.confirm_sponsorship(caller.as_sub())251 .map_err(dispatch_to_evm::<T>)252 }253254 /// Remove collection sponsor.255 fn remove_collection_sponsor(&mut self, caller: Caller) -> Result<()> {256 self.consume_store_reads_and_writes(1, 1)?;257 let caller = T::CrossAccountId::from_eth(caller);258 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)259 }260261 /// Get current sponsor.262 ///263 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.264 fn collection_sponsor(&self) -> Result<eth::CrossAddress> {265 let sponsor = match self.collection.sponsorship.sponsor() {266 Some(sponsor) => sponsor,267 None => return Ok(Default::default()),268 };269270 Ok(eth::CrossAddress::from_sub::<T>(sponsor))271 }272273 /// Get current collection limits.274 ///275 /// @return Array of collection limits276 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {277 let limits = &self.collection.limits;278279 Ok(vec![280 eth::CollectionLimit::new(281 eth::CollectionLimitField::AccountTokenOwnership,282 limits.account_token_ownership_limit,283 ),284 eth::CollectionLimit::new(285 eth::CollectionLimitField::SponsoredDataSize,286 limits.sponsored_data_size,287 ),288 limits289 .sponsored_data_rate_limit290 .and_then(|limit| {291 if let SponsoringRateLimit::Blocks(blocks) = limit {292 Some(eth::CollectionLimit::new(293 eth::CollectionLimitField::SponsoredDataRateLimit,294 Some(blocks),295 ))296 } else {297 None298 }299 })300 .unwrap_or_else(|| {301 eth::CollectionLimit::new(302 eth::CollectionLimitField::SponsoredDataRateLimit,303 Default::default(),304 )305 }),306 eth::CollectionLimit::new(eth::CollectionLimitField::TokenLimit, limits.token_limit),307 eth::CollectionLimit::new(308 eth::CollectionLimitField::SponsorTransferTimeout,309 limits.sponsor_transfer_timeout,310 ),311 eth::CollectionLimit::new(312 eth::CollectionLimitField::SponsorApproveTimeout,313 limits.sponsor_approve_timeout,314 ),315 eth::CollectionLimit::new(316 eth::CollectionLimitField::OwnerCanTransfer,317 limits.owner_can_transfer.map(u32::from),318 ),319 eth::CollectionLimit::new(320 eth::CollectionLimitField::OwnerCanDestroy,321 limits.owner_can_destroy.map(u32::from),322 ),323 eth::CollectionLimit::new(324 eth::CollectionLimitField::TransferEnabled,325 limits.transfers_enabled.map(u32::from),326 ),327 ])328 }329330 /// Set limits for the collection.331 /// @dev Throws error if limit not found.332 /// @param limit Some limit.333 #[solidity(rename_selector = "setCollectionLimit")]334 fn set_collection_limit(&mut self, caller: Caller, limit: eth::CollectionLimit) -> Result<()> {335 self.consume_store_reads_and_writes(1, 1)?;336337 if !limit.has_value() {338 return Err(Error::Revert("user can't disable limits".into()));339 }340341 let caller = T::CrossAccountId::from_eth(caller);342 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)343 }344345 /// Get contract address.346 fn contract_address(&self) -> Result<Address> {347 Ok(crate::eth::collection_id_to_address(self.id))348 }349350 /// Add collection admin.351 /// @param newAdmin Cross account administrator address.352 fn add_collection_admin_cross(353 &mut self,354 caller: Caller,355 new_admin: eth::CrossAddress,356 ) -> Result<()> {357 self.consume_store_reads_and_writes(2, 2)?;358359 let caller = T::CrossAccountId::from_eth(caller);360 let new_admin = new_admin.into_sub_cross_account::<T>()?;361 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;362 Ok(())363 }364365 /// Remove collection admin.366 /// @param admin Cross account administrator address.367 fn remove_collection_admin_cross(368 &mut self,369 caller: Caller,370 admin: eth::CrossAddress,371 ) -> Result<()> {372 self.consume_store_reads_and_writes(2, 2)?;373374 let caller = T::CrossAccountId::from_eth(caller);375 let admin = admin.into_sub_cross_account::<T>()?;376 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;377 Ok(())378 }379380 /// Add collection admin.381 /// @param newAdmin Address of the added administrator.382 #[solidity(hide)]383 fn add_collection_admin(&mut self, caller: Caller, new_admin: Address) -> Result<()> {384 self.consume_store_reads_and_writes(2, 2)?;385386 let caller = T::CrossAccountId::from_eth(caller);387 let new_admin = T::CrossAccountId::from_eth(new_admin);388 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;389 Ok(())390 }391392 /// Remove collection admin.393 ///394 /// @param admin Address of the removed administrator.395 #[solidity(hide)]396 fn remove_collection_admin(&mut self, caller: Caller, admin: Address) -> Result<()> {397 self.consume_store_reads_and_writes(2, 2)?;398399 let caller = T::CrossAccountId::from_eth(caller);400 let admin = T::CrossAccountId::from_eth(admin);401 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;402 Ok(())403 }404405 /// Toggle accessibility of collection nesting.406 ///407 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'408 #[solidity(rename_selector = "setCollectionNesting")]409 fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> {410 self.consume_store_reads_and_writes(1, 1)?;411412 let caller = T::CrossAccountId::from_eth(caller);413414 let mut permissions = self.collection.permissions.clone();415 let mut nesting = permissions.nesting().clone();416 nesting.token_owner = enable;417 nesting.restricted = None;418 permissions.nesting = Some(nesting);419420 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)421 }422423 /// Toggle accessibility of collection nesting.424 ///425 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'426 /// @param collections Addresses of collections that will be available for nesting.427 #[solidity(rename_selector = "setCollectionNesting")]428 fn set_nesting(429 &mut self,430 caller: Caller,431 enable: bool,432 collections: Vec<Address>,433 ) -> Result<()> {434 self.consume_store_reads_and_writes(1, 1)?;435436 if collections.is_empty() {437 return Err("no addresses provided".into());438 }439 let caller = T::CrossAccountId::from_eth(caller);440441 let mut permissions = self.collection.permissions.clone();442 match enable {443 false => {444 let mut nesting = permissions.nesting().clone();445 nesting.token_owner = false;446 nesting.restricted = None;447 permissions.nesting = Some(nesting);448 }449 true => {450 let mut bv = OwnerRestrictedSet::new();451 for i in collections {452 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {453 Error::Revert("Can't convert address into collection id".into())454 })?)455 .map_err(|_| "too many collections")?;456 }457 let mut nesting = permissions.nesting().clone();458 nesting.token_owner = true;459 nesting.restricted = Some(bv);460 permissions.nesting = Some(nesting);461 }462 };463464 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)465 }466467 /// Returns nesting for a collection468 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]469 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {470 let nesting = self.collection.permissions.nesting();471472 Ok(eth::CollectionNesting::new(473 nesting.token_owner,474 nesting475 .restricted476 .clone()477 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())478 .unwrap_or_default(),479 ))480 }481482 /// Returns permissions for a collection483 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {484 let nesting = self.collection.permissions.nesting();485 Ok(vec![486 eth::CollectionNestingPermission::new(487 eth::CollectionPermissionField::CollectionAdmin,488 nesting.collection_admin,489 ),490 eth::CollectionNestingPermission::new(491 eth::CollectionPermissionField::TokenOwner,492 nesting.token_owner,493 ),494 ])495 }496 /// Set the collection access method.497 /// @param mode Access mode498 fn set_collection_access(&mut self, caller: Caller, mode: eth::AccessMode) -> Result<()> {499 self.consume_store_reads_and_writes(1, 1)?;500501 let caller = T::CrossAccountId::from_eth(caller);502 let permissions = CollectionPermissions {503 access: Some(mode.into()),504 ..Default::default()505 };506 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)507 }508509 /// Checks that user allowed to operate with collection.510 ///511 /// @param user User address to check.512 fn allowlisted_cross(&self, user: eth::CrossAddress) -> Result<bool> {513 let user = user.into_sub_cross_account::<T>()?;514 Ok(Pallet::<T>::allowed(self.id, user))515 }516517 /// Add the user to the allowed list.518 ///519 /// @param user Address of a trusted user.520 #[solidity(hide)]521 fn add_to_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {522 self.consume_store_writes(1)?;523524 let caller = T::CrossAccountId::from_eth(caller);525 let user = T::CrossAccountId::from_eth(user);526 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;527 Ok(())528 }529530 /// Add user to allowed list.531 ///532 /// @param user User cross account address.533 fn add_to_collection_allow_list_cross(534 &mut self,535 caller: Caller,536 user: eth::CrossAddress,537 ) -> Result<()> {538 self.consume_store_writes(1)?;539540 let caller = T::CrossAccountId::from_eth(caller);541 let user = user.into_sub_cross_account::<T>()?;542 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;543 Ok(())544 }545546 /// Remove the user from the allowed list.547 ///548 /// @param user Address of a removed user.549 #[solidity(hide)]550 fn remove_from_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {551 self.consume_store_writes(1)?;552553 let caller = T::CrossAccountId::from_eth(caller);554 let user = T::CrossAccountId::from_eth(user);555 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558559 /// Remove user from allowed list.560 ///561 /// @param user User cross account address.562 fn remove_from_collection_allow_list_cross(563 &mut self,564 caller: Caller,565 user: eth::CrossAddress,566 ) -> Result<()> {567 self.consume_store_writes(1)?;568569 let caller = T::CrossAccountId::from_eth(caller);570 let user = user.into_sub_cross_account::<T>()?;571 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;572 Ok(())573 }574575 /// Switch permission for minting.576 ///577 /// @param mode Enable if "true".578 fn set_collection_mint_mode(&mut self, caller: Caller, mode: bool) -> Result<()> {579 self.consume_store_reads_and_writes(1, 1)?;580581 let caller = T::CrossAccountId::from_eth(caller);582 let permissions = CollectionPermissions {583 mint_mode: Some(mode),584 ..Default::default()585 };586 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)587 }588589 /// Check that account is the owner or admin of the collection590 ///591 /// @param user account to verify592 /// @return "true" if account is the owner or admin593 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]594 fn is_owner_or_admin_eth(&self, user: Address) -> Result<bool> {595 let user = T::CrossAccountId::from_eth(user);596 Ok(self.is_owner_or_admin(&user))597 }598599 /// Check that account is the owner or admin of the collection600 ///601 /// @param user User cross account to verify602 /// @return "true" if account is the owner or admin603 fn is_owner_or_admin_cross(&self, user: eth::CrossAddress) -> Result<bool> {604 let user = user.into_sub_cross_account::<T>()?;605 Ok(self.is_owner_or_admin(&user))606 }607608 /// Returns collection type609 ///610 /// @return `Fungible` or `NFT` or `ReFungible`611 fn unique_collection_type(&self) -> Result<String> {612 let mode = match self.collection.mode {613 CollectionMode::Fungible(_) => "Fungible",614 CollectionMode::NFT => "NFT",615 CollectionMode::ReFungible => "ReFungible",616 };617 Ok(mode.into())618 }619620 /// Get collection owner.621 ///622 /// @return Tuble with sponsor address and his substrate mirror.623 /// If address is canonical then substrate mirror is zero and vice versa.624 fn collection_owner(&self) -> Result<eth::CrossAddress> {625 Ok(eth::CrossAddress::from_sub_cross_account::<T>(626 &T::CrossAccountId::from_sub(self.owner.clone()),627 ))628 }629630 /// Changes collection owner to another account631 ///632 /// @dev Owner can be changed only by current owner633 /// @param newOwner new owner account634 #[solidity(hide, rename_selector = "changeCollectionOwner")]635 fn set_owner(&mut self, caller: Caller, new_owner: Address) -> Result<()> {636 self.consume_store_writes(1)?;637638 let caller = T::CrossAccountId::from_eth(caller);639 let new_owner = T::CrossAccountId::from_eth(new_owner);640 self.change_owner(caller, new_owner)641 .map_err(dispatch_to_evm::<T>)642 }643644 /// Get collection administrators645 ///646 /// @return Vector of tuples with admins address and his substrate mirror.647 /// If address is canonical then substrate mirror is zero and vice versa.648 fn collection_admins(&self) -> Result<Vec<eth::CrossAddress>> {649 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))650 .map(|(admin, _)| eth::CrossAddress::from_sub_cross_account::<T>(&admin))651 .collect();652 Ok(result)653 }654655 /// Changes collection owner to another account656 ///657 /// @dev Owner can be changed only by current owner658 /// @param newOwner new owner cross account659 fn change_collection_owner_cross(660 &mut self,661 caller: Caller,662 new_owner: eth::CrossAddress,663 ) -> Result<()> {664 self.consume_store_writes(1)?;665666 let caller = T::CrossAccountId::from_eth(caller);667 let new_owner = new_owner.into_sub_cross_account::<T>()?;668 self.change_owner(caller, new_owner)669 .map_err(dispatch_to_evm::<T>)670 }671}672673/// Contains static property keys and values.674pub mod static_property {675 use pallet_evm_coder_substrate::{676 execution::{Result, Error},677 };678 use alloc::format;679680 const EXPECT_CONVERT_ERROR: &str = "length < limit";681682 /// Keys.683 pub mod key {684 use super::*;685686 /// Key "baseURI".687 pub fn base_uri() -> up_data_structs::PropertyKey {688 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)689 }690691 /// Key "url".692 pub fn url() -> up_data_structs::PropertyKey {693 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)694 }695696 /// Key "suffix".697 pub fn suffix() -> up_data_structs::PropertyKey {698 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)699 }700701 /// Key "parentNft".702 pub fn parent_nft() -> up_data_structs::PropertyKey {703 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)704 }705 }706707 /// Convert `byte` to [`PropertyKey`].708 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {709 bytes.to_vec().try_into().map_err(|_| {710 Error::Revert(format!(711 "Property key is too long. Max length is {}.",712 up_data_structs::PropertyKey::bound()713 ))714 })715 }716717 /// Convert `bytes` to [`PropertyValue`].718 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {719 bytes.to_vec().try_into().map_err(|_| {720 Error::Revert(format!(721 "Property key is too long. Max length is {}.",722 up_data_structs::PropertyKey::bound()723 ))724 })725 }726}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//! This module contains the implementation of pallet methods for evm.1819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use pallet_evm_coder_substrate::{21 abi::AbiType,22 solidity_interface, ToLog,23 types::*,24 execution::{Result, Error, PreDispatch},25 frontier_contract,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,31 SponsorshipState,32};3334use crate::{35 Pallet, CollectionHandle, Config, CollectionProperties, eth, SelfWeightOf, weights::WeightInfo,36};3738frontier_contract! {39 macro_rules! CollectionHandle_result {...}40 impl<T: Config> Contract for CollectionHandle<T> {...}41}4243/// Events for ethereum collection helper.44#[derive(ToLog)]45pub enum CollectionHelpersEvents {46 /// The collection has been created.47 CollectionCreated {48 /// Collection owner.49 #[indexed]50 owner: Address,5152 /// Collection ID.53 #[indexed]54 collection_id: Address,55 },56 /// The collection has been destroyed.57 CollectionDestroyed {58 /// Collection ID.59 #[indexed]60 collection_id: Address,61 },62 /// The collection has been changed.63 CollectionChanged {64 /// Collection ID.65 #[indexed]66 collection_id: Address,67 },68}6970/// Does not always represent a full collection, for RFT it is either71/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).72pub trait CommonEvmHandler {73 /// Raw compiled binary code of the contract stub74 const CODE: &'static [u8];7576 /// Call precompiled handle.77 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;78}7980impl CommonEvmHandler for () {81 const CODE: &'static [u8] = &[];8283 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {84 None85 }86}8788/// @title A contract that allows you to work with collections.89#[solidity_interface(name = Collection, enum(derive(PreDispatch)), enum_attr(weight))]90impl<T: Config> CollectionHandle<T>91where92 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,93{94 /// Set collection property.95 ///96 /// @param key Property key.97 /// @param value Propery value.98 #[solidity(hide)]99 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]100 fn set_collection_property(&mut self, caller: Caller, key: String, value: Bytes) -> Result<()> {101 let caller = T::CrossAccountId::from_eth(caller);102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too large")?;105 let value = value.0.try_into().map_err(|_| "value too large")?;106107 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })108 .map_err(dispatch_to_evm::<T>)109 }110111 /// Set collection properties.112 ///113 /// @param properties Vector of properties key/value pair.114 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]115 fn set_collection_properties(116 &mut self,117 caller: Caller,118 properties: Vec<eth::Property>,119 ) -> Result<()> {120 let caller = T::CrossAccountId::from_eth(caller);121122 let properties = properties123 .into_iter()124 .map(eth::Property::try_into)125 .collect::<Result<Vec<_>>>()?;126127 <Pallet<T>>::set_collection_properties(self, &caller, properties.into_iter())128 .map_err(dispatch_to_evm::<T>)129 }130131 /// Delete collection property.132 ///133 /// @param key Property key.134 #[solidity(hide)]135 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]136 fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {137 let caller = T::CrossAccountId::from_eth(caller);138 let key = <Vec<u8>>::from(key)139 .try_into()140 .map_err(|_| "key too large")?;141142 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)143 }144145 /// Delete collection properties.146 ///147 /// @param keys Properties keys.148 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]149 fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {150 let caller = T::CrossAccountId::from_eth(caller);151 let keys = keys152 .into_iter()153 .map(|key| {154 <Vec<u8>>::from(key)155 .try_into()156 .map_err(|_| Error::Revert("key too large".into()))157 })158 .collect::<Result<Vec<_>>>()?;159160 <Pallet<T>>::delete_collection_properties(self, &caller, keys.into_iter())161 .map_err(dispatch_to_evm::<T>)162 }163164 /// Get collection property.165 ///166 /// @dev Throws error if key not found.167 ///168 /// @param key Property key.169 /// @return bytes The property corresponding to the key.170 fn collection_property(&self, key: String) -> Result<Bytes> {171 let key = <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| "key too large")?;174175 let props = CollectionProperties::<T>::get(self.id);176 let prop = props.get(&key).ok_or("key not found")?;177178 Ok(Bytes(prop.to_vec()))179 }180181 /// Get collection properties.182 ///183 /// @param keys Properties keys. Empty keys for all propertyes.184 /// @return Vector of properties key/value pairs.185 fn collection_properties(&self, keys: Vec<String>) -> Result<Vec<eth::Property>> {186 let keys = keys187 .into_iter()188 .map(|key| {189 <Vec<u8>>::from(key)190 .try_into()191 .map_err(|_| Error::Revert("key too large".into()))192 })193 .collect::<Result<Vec<_>>>()?;194195 let properties = Pallet::<T>::filter_collection_properties(196 self.id,197 if keys.is_empty() { None } else { Some(keys) },198 )199 .map_err(dispatch_to_evm::<T>)?;200201 let properties = properties202 .into_iter()203 .map(Property::try_into)204 .collect::<Result<Vec<_>>>()?;205 Ok(properties)206 }207208 /// Set the sponsor of the collection.209 ///210 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.211 ///212 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.213 #[solidity(hide)]214 fn set_collection_sponsor(&mut self, caller: Caller, sponsor: Address) -> Result<()> {215 self.consume_store_reads_and_writes(1, 1)?;216217 let caller = T::CrossAccountId::from_eth(caller);218219 let sponsor = T::CrossAccountId::from_eth(sponsor);220 self.set_sponsor(&caller, sponsor.as_sub().clone())221 .map_err(dispatch_to_evm::<T>)222 }223224 /// Set the sponsor of the collection.225 ///226 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.227 ///228 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.229 fn set_collection_sponsor_cross(230 &mut self,231 caller: Caller,232 sponsor: eth::CrossAddress,233 ) -> Result<()> {234 self.consume_store_reads_and_writes(1, 1)?;235236 let caller = T::CrossAccountId::from_eth(caller);237238 let sponsor = sponsor.into_sub_cross_account::<T>()?;239 self.set_sponsor(&caller, sponsor.as_sub().clone())240 .map_err(dispatch_to_evm::<T>)241 }242243 /// Whether there is a pending sponsor.244 fn has_collection_pending_sponsor(&self) -> Result<bool> {245 Ok(matches!(246 self.collection.sponsorship,247 SponsorshipState::Unconfirmed(_)248 ))249 }250251 /// Collection sponsorship confirmation.252 ///253 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.254 fn confirm_collection_sponsorship(&mut self, caller: Caller) -> Result<()> {255 self.consume_store_writes(1)?;256257 let caller = T::CrossAccountId::from_eth(caller);258 self.confirm_sponsorship(caller.as_sub())259 .map_err(dispatch_to_evm::<T>)260 }261262 /// Remove collection sponsor.263 fn remove_collection_sponsor(&mut self, caller: Caller) -> Result<()> {264 self.consume_store_reads_and_writes(1, 1)?;265 let caller = T::CrossAccountId::from_eth(caller);266 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)267 }268269 /// Get current sponsor.270 ///271 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.272 fn collection_sponsor(&self) -> Result<eth::CrossAddress> {273 let sponsor = match self.collection.sponsorship.sponsor() {274 Some(sponsor) => sponsor,275 None => return Ok(Default::default()),276 };277278 Ok(eth::CrossAddress::from_sub::<T>(sponsor))279 }280281 /// Get current collection limits.282 ///283 /// @return Array of collection limits284 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {285 let limits = &self.collection.limits;286287 Ok(vec![288 eth::CollectionLimit::new(289 eth::CollectionLimitField::AccountTokenOwnership,290 limits.account_token_ownership_limit,291 ),292 eth::CollectionLimit::new(293 eth::CollectionLimitField::SponsoredDataSize,294 limits.sponsored_data_size,295 ),296 limits297 .sponsored_data_rate_limit298 .and_then(|limit| {299 if let SponsoringRateLimit::Blocks(blocks) = limit {300 Some(eth::CollectionLimit::new(301 eth::CollectionLimitField::SponsoredDataRateLimit,302 Some(blocks),303 ))304 } else {305 None306 }307 })308 .unwrap_or_else(|| {309 eth::CollectionLimit::new(310 eth::CollectionLimitField::SponsoredDataRateLimit,311 Default::default(),312 )313 }),314 eth::CollectionLimit::new(eth::CollectionLimitField::TokenLimit, limits.token_limit),315 eth::CollectionLimit::new(316 eth::CollectionLimitField::SponsorTransferTimeout,317 limits.sponsor_transfer_timeout,318 ),319 eth::CollectionLimit::new(320 eth::CollectionLimitField::SponsorApproveTimeout,321 limits.sponsor_approve_timeout,322 ),323 eth::CollectionLimit::new(324 eth::CollectionLimitField::OwnerCanTransfer,325 limits.owner_can_transfer.map(u32::from),326 ),327 eth::CollectionLimit::new(328 eth::CollectionLimitField::OwnerCanDestroy,329 limits.owner_can_destroy.map(u32::from),330 ),331 eth::CollectionLimit::new(332 eth::CollectionLimitField::TransferEnabled,333 limits.transfers_enabled.map(u32::from),334 ),335 ])336 }337338 /// Set limits for the collection.339 /// @dev Throws error if limit not found.340 /// @param limit Some limit.341 #[solidity(rename_selector = "setCollectionLimit")]342 fn set_collection_limit(&mut self, caller: Caller, limit: eth::CollectionLimit) -> Result<()> {343 self.consume_store_reads_and_writes(1, 1)?;344345 if !limit.has_value() {346 return Err(Error::Revert("user can't disable limits".into()));347 }348349 let caller = T::CrossAccountId::from_eth(caller);350 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)351 }352353 /// Get contract address.354 fn contract_address(&self) -> Result<Address> {355 Ok(crate::eth::collection_id_to_address(self.id))356 }357358 /// Add collection admin.359 /// @param newAdmin Cross account administrator address.360 fn add_collection_admin_cross(361 &mut self,362 caller: Caller,363 new_admin: eth::CrossAddress,364 ) -> Result<()> {365 self.consume_store_reads_and_writes(2, 2)?;366367 let caller = T::CrossAccountId::from_eth(caller);368 let new_admin = new_admin.into_sub_cross_account::<T>()?;369 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;370 Ok(())371 }372373 /// Remove collection admin.374 /// @param admin Cross account administrator address.375 fn remove_collection_admin_cross(376 &mut self,377 caller: Caller,378 admin: eth::CrossAddress,379 ) -> Result<()> {380 self.consume_store_reads_and_writes(2, 2)?;381382 let caller = T::CrossAccountId::from_eth(caller);383 let admin = admin.into_sub_cross_account::<T>()?;384 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;385 Ok(())386 }387388 /// Add collection admin.389 /// @param newAdmin Address of the added administrator.390 #[solidity(hide)]391 fn add_collection_admin(&mut self, caller: Caller, new_admin: Address) -> Result<()> {392 self.consume_store_reads_and_writes(2, 2)?;393394 let caller = T::CrossAccountId::from_eth(caller);395 let new_admin = T::CrossAccountId::from_eth(new_admin);396 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;397 Ok(())398 }399400 /// Remove collection admin.401 ///402 /// @param admin Address of the removed administrator.403 #[solidity(hide)]404 fn remove_collection_admin(&mut self, caller: Caller, admin: Address) -> Result<()> {405 self.consume_store_reads_and_writes(2, 2)?;406407 let caller = T::CrossAccountId::from_eth(caller);408 let admin = T::CrossAccountId::from_eth(admin);409 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;410 Ok(())411 }412413 /// Toggle accessibility of collection nesting.414 ///415 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'416 #[solidity(rename_selector = "setCollectionNesting")]417 fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> {418 self.consume_store_reads_and_writes(1, 1)?;419420 let caller = T::CrossAccountId::from_eth(caller);421422 let mut permissions = self.collection.permissions.clone();423 let mut nesting = permissions.nesting().clone();424 nesting.token_owner = enable;425 nesting.restricted = None;426 permissions.nesting = Some(nesting);427428 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)429 }430431 /// Toggle accessibility of collection nesting.432 ///433 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'434 /// @param collections Addresses of collections that will be available for nesting.435 #[solidity(rename_selector = "setCollectionNesting")]436 fn set_nesting(437 &mut self,438 caller: Caller,439 enable: bool,440 collections: Vec<Address>,441 ) -> Result<()> {442 self.consume_store_reads_and_writes(1, 1)?;443444 if collections.is_empty() {445 return Err("no addresses provided".into());446 }447 let caller = T::CrossAccountId::from_eth(caller);448449 let mut permissions = self.collection.permissions.clone();450 match enable {451 false => {452 let mut nesting = permissions.nesting().clone();453 nesting.token_owner = false;454 nesting.restricted = None;455 permissions.nesting = Some(nesting);456 }457 true => {458 let mut bv = OwnerRestrictedSet::new();459 for i in collections {460 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {461 Error::Revert("Can't convert address into collection id".into())462 })?)463 .map_err(|_| "too many collections")?;464 }465 let mut nesting = permissions.nesting().clone();466 nesting.token_owner = true;467 nesting.restricted = Some(bv);468 permissions.nesting = Some(nesting);469 }470 };471472 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)473 }474475 /// Returns nesting for a collection476 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]477 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {478 let nesting = self.collection.permissions.nesting();479480 Ok(eth::CollectionNesting::new(481 nesting.token_owner,482 nesting483 .restricted484 .clone()485 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())486 .unwrap_or_default(),487 ))488 }489490 /// Returns permissions for a collection491 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {492 let nesting = self.collection.permissions.nesting();493 Ok(vec![494 eth::CollectionNestingPermission::new(495 eth::CollectionPermissionField::CollectionAdmin,496 nesting.collection_admin,497 ),498 eth::CollectionNestingPermission::new(499 eth::CollectionPermissionField::TokenOwner,500 nesting.token_owner,501 ),502 ])503 }504 /// Set the collection access method.505 /// @param mode Access mode506 fn set_collection_access(&mut self, caller: Caller, mode: eth::AccessMode) -> Result<()> {507 self.consume_store_reads_and_writes(1, 1)?;508509 let caller = T::CrossAccountId::from_eth(caller);510 let permissions = CollectionPermissions {511 access: Some(mode.into()),512 ..Default::default()513 };514 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)515 }516517 /// Checks that user allowed to operate with collection.518 ///519 /// @param user User address to check.520 fn allowlisted_cross(&self, user: eth::CrossAddress) -> Result<bool> {521 let user = user.into_sub_cross_account::<T>()?;522 Ok(Pallet::<T>::allowed(self.id, user))523 }524525 /// Add the user to the allowed list.526 ///527 /// @param user Address of a trusted user.528 #[solidity(hide)]529 fn add_to_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {530 self.consume_store_writes(1)?;531532 let caller = T::CrossAccountId::from_eth(caller);533 let user = T::CrossAccountId::from_eth(user);534 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;535 Ok(())536 }537538 /// Add user to allowed list.539 ///540 /// @param user User cross account address.541 fn add_to_collection_allow_list_cross(542 &mut self,543 caller: Caller,544 user: eth::CrossAddress,545 ) -> Result<()> {546 self.consume_store_writes(1)?;547548 let caller = T::CrossAccountId::from_eth(caller);549 let user = user.into_sub_cross_account::<T>()?;550 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;551 Ok(())552 }553554 /// Remove the user from the allowed list.555 ///556 /// @param user Address of a removed user.557 #[solidity(hide)]558 fn remove_from_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {559 self.consume_store_writes(1)?;560561 let caller = T::CrossAccountId::from_eth(caller);562 let user = T::CrossAccountId::from_eth(user);563 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;564 Ok(())565 }566567 /// Remove user from allowed list.568 ///569 /// @param user User cross account address.570 fn remove_from_collection_allow_list_cross(571 &mut self,572 caller: Caller,573 user: eth::CrossAddress,574 ) -> Result<()> {575 self.consume_store_writes(1)?;576577 let caller = T::CrossAccountId::from_eth(caller);578 let user = user.into_sub_cross_account::<T>()?;579 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;580 Ok(())581 }582583 /// Switch permission for minting.584 ///585 /// @param mode Enable if "true".586 fn set_collection_mint_mode(&mut self, caller: Caller, mode: bool) -> Result<()> {587 self.consume_store_reads_and_writes(1, 1)?;588589 let caller = T::CrossAccountId::from_eth(caller);590 let permissions = CollectionPermissions {591 mint_mode: Some(mode),592 ..Default::default()593 };594 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)595 }596597 /// Check that account is the owner or admin of the collection598 ///599 /// @param user account to verify600 /// @return "true" if account is the owner or admin601 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]602 fn is_owner_or_admin_eth(&self, user: Address) -> Result<bool> {603 let user = T::CrossAccountId::from_eth(user);604 Ok(self.is_owner_or_admin(&user))605 }606607 /// Check that account is the owner or admin of the collection608 ///609 /// @param user User cross account to verify610 /// @return "true" if account is the owner or admin611 fn is_owner_or_admin_cross(&self, user: eth::CrossAddress) -> Result<bool> {612 let user = user.into_sub_cross_account::<T>()?;613 Ok(self.is_owner_or_admin(&user))614 }615616 /// Returns collection type617 ///618 /// @return `Fungible` or `NFT` or `ReFungible`619 fn unique_collection_type(&self) -> Result<String> {620 let mode = match self.collection.mode {621 CollectionMode::Fungible(_) => "Fungible",622 CollectionMode::NFT => "NFT",623 CollectionMode::ReFungible => "ReFungible",624 };625 Ok(mode.into())626 }627628 /// Get collection owner.629 ///630 /// @return Tuble with sponsor address and his substrate mirror.631 /// If address is canonical then substrate mirror is zero and vice versa.632 fn collection_owner(&self) -> Result<eth::CrossAddress> {633 Ok(eth::CrossAddress::from_sub_cross_account::<T>(634 &T::CrossAccountId::from_sub(self.owner.clone()),635 ))636 }637638 /// Changes collection owner to another account639 ///640 /// @dev Owner can be changed only by current owner641 /// @param newOwner new owner account642 #[solidity(hide, rename_selector = "changeCollectionOwner")]643 fn set_owner(&mut self, caller: Caller, new_owner: Address) -> Result<()> {644 self.consume_store_writes(1)?;645646 let caller = T::CrossAccountId::from_eth(caller);647 let new_owner = T::CrossAccountId::from_eth(new_owner);648 self.change_owner(caller, new_owner)649 .map_err(dispatch_to_evm::<T>)650 }651652 /// Get collection administrators653 ///654 /// @return Vector of tuples with admins address and his substrate mirror.655 /// If address is canonical then substrate mirror is zero and vice versa.656 fn collection_admins(&self) -> Result<Vec<eth::CrossAddress>> {657 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))658 .map(|(admin, _)| eth::CrossAddress::from_sub_cross_account::<T>(&admin))659 .collect();660 Ok(result)661 }662663 /// Changes collection owner to another account664 ///665 /// @dev Owner can be changed only by current owner666 /// @param newOwner new owner cross account667 fn change_collection_owner_cross(668 &mut self,669 caller: Caller,670 new_owner: eth::CrossAddress,671 ) -> Result<()> {672 self.consume_store_writes(1)?;673674 let caller = T::CrossAccountId::from_eth(caller);675 let new_owner = new_owner.into_sub_cross_account::<T>()?;676 self.change_owner(caller, new_owner)677 .map_err(dispatch_to_evm::<T>)678 }679}680681/// Contains static property keys and values.682pub mod static_property {683 use pallet_evm_coder_substrate::{684 execution::{Result, Error},685 };686 use alloc::format;687688 const EXPECT_CONVERT_ERROR: &str = "length < limit";689690 /// Keys.691 pub mod key {692 use super::*;693694 /// Key "baseURI".695 pub fn base_uri() -> up_data_structs::PropertyKey {696 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)697 }698699 /// Key "url".700 pub fn url() -> up_data_structs::PropertyKey {701 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)702 }703704 /// Key "suffix".705 pub fn suffix() -> up_data_structs::PropertyKey {706 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)707 }708709 /// Key "parentNft".710 pub fn parent_nft() -> up_data_structs::PropertyKey {711 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)712 }713 }714715 /// Convert `byte` to [`PropertyKey`].716 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {717 bytes.to_vec().try_into().map_err(|_| {718 Error::Revert(format!(719 "Property key is too long. Max length is {}.",720 up_data_structs::PropertyKey::bound()721 ))722 })723 }724725 /// Convert `bytes` to [`PropertyValue`].726 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {727 bytes.to_vec().try_into().map_err(|_| {728 Error::Revert(format!(729 "Property key is too long. Max length is {}.",730 up_data_structs::PropertyKey::bound()731 ))732 })733 }734}pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -42,7 +42,7 @@
},
CollectionFlags::default(),
)?;
- let dispatch = T::CollectionDispatch::dispatch(CollectionHandle::try_get(CollectionId(1))?);
+ let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?;
let dispatch = dispatch.as_dyn();
dispatch.create_item(caller_cross.clone(), caller_cross.clone(), CreateItemData::NFT(CreateNftData::default()), &Unlimited)?;
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -155,11 +155,10 @@
token: TokenId,
) -> Result<Parent<T::CrossAccountId>, DispatchError> {
// TODO: Reduce cost by not reading collection config
- let handle = match CollectionHandle::try_get(collection) {
+ let handle = match T::CollectionDispatch::dispatch(collection) {
Ok(v) => v,
Err(_) => return Ok(Parent::TokenNotFound),
};
- let handle = T::CollectionDispatch::dispatch(handle);
let handle = handle.as_dyn();
Ok(match handle.token_owner(token) {
@@ -279,8 +278,7 @@
self_budget: &dyn Budget,
breadth_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
- let handle = <CollectionHandle<T>>::try_get(collection)?;
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
}
@@ -404,10 +402,8 @@
let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {
return Ok(())
};
-
- let handle = <CollectionHandle<T>>::try_get(collection)?;
- let dispatch = T::CollectionDispatch::dispatch(handle);
+ let dispatch = T::CollectionDispatch::dispatch(collection)?;
let dispatch = dispatch.as_dyn();
action(dispatch, token)
runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -50,6 +50,7 @@
Refungible(RefungibleHandle<T>),
NativeFungible(NativeFungibleHandle<T>),
}
+
impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
where
T: pallet_common::Config
@@ -59,6 +60,15 @@
+ pallet_refungible::Config
+ pallet_balances_adapter::Config,
{
+ fn check_is_internal(&self) -> DispatchResult {
+ match self {
+ Self::Fungible(h) => h.check_is_internal(),
+ Self::Nonfungible(h) => h.check_is_internal(),
+ Self::Refungible(h) => h.check_is_internal(),
+ Self::NativeFungible(h) => h.check_is_internal(),
+ }
+ }
+
fn create(
sender: T::CrossAccountId,
payer: T::CrossAccountId,
@@ -104,18 +114,17 @@
Ok(())
}
- fn dispatch(handle: CollectionHandle<T>) -> Self {
- match handle.mode {
- CollectionMode::Fungible(_) => {
- if handle.id != up_data_structs::CollectionId(0) {
- Self::Fungible(FungibleHandle::cast(handle))
- } else {
- Self::NativeFungible(NativeFungibleHandle::cast(handle))
- }
- }
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError> {
+ if collection_id == CollectionId(0) {
+ return Ok(Self::NativeFungible(NativeFungibleHandle::new()));
+ }
+
+ let handle = <CollectionHandle<T>>::try_get(collection_id)?;
+ Ok(match handle.mode {
+ CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
- }
+ })
}
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
@@ -172,15 +181,19 @@
}
fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
- let collection =
- <CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
- let dispatched = Self::dispatch(collection);
+ if collection_id == CollectionId(0) {
+ <NativeFungibleHandle<T>>::new().call(handle)
+ } else {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(
+ collection_id,
+ handle.remaining_gas(),
+ )?;
- match dispatched {
- Self::Fungible(h) => h.call(handle),
- Self::Nonfungible(h) => h.call(handle),
- Self::Refungible(h) => h.call(handle),
- Self::NativeFungible(h) => h.call(handle),
+ match collection.mode {
+ CollectionMode::Fungible(_) => FungibleHandle::cast(collection).call(handle),
+ CollectionMode::NFT => NonfungibleHandle::cast(collection).call(handle),
+ CollectionMode::ReFungible => RefungibleHandle::cast(collection).call(handle),
+ }
}
} else if let Some((collection_id, token_id)) =
<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -17,7 +17,7 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
- let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+ let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch($collection)?;
let dispatch = collection.as_dyn();
Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -33,7 +33,7 @@
'substrate' as const,
'ethereum' as const,
].map(testCase => {
- itEth.only(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {
+ itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {
// 1. Create receiver depending on the test case:
const receiverEth = helper.eth.createAccount();
const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);
tests/src/eth/nativeFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nativeFungible.test.ts
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -29,7 +29,7 @@
});
});
- itEth.only('Can perform approve()', async ({helper}) => {
+ itEth.skip('Can perform approve()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = helper.eth.createAccount();
const collection = await helper.ft.mintCollection(alice);
tests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -48,3 +48,5 @@
field: CollectionLimitField,
value: OptionUint,
}
+
+export const NON_EXISTENT_COLLECTION_ID = 4_294_967_295;
\ No newline at end of file
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -19,6 +19,7 @@
// Pallets that must always be present
const requiredPallets = [
'balances',
+ 'balancesadapter',
'common',
'timestamp',
'transactionpayment',
tests/src/transfer.test.tsdiffbeforeafterboth--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -17,6 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itEth, usingEthPlaygrounds} from './eth/util';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './eth/util/playgrounds/types';
describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {
let donor: IKeyringPair;
@@ -124,20 +125,17 @@
itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.nft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))
+ await expect(helper.ft.transfer(alice, NON_EXISTENT_COLLECTION_ID, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.rft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/transferFrom.test.tsdiffbeforeafterboth--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './eth/util/playgrounds/types';
describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
let alice: IKeyringPair;
@@ -97,10 +98,9 @@
});
itSub('transferFrom for a collection that does not exist', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.collection.approveToken(alice, collectionId, 0, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.approveToken(alice, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
- await expect(helper.collection.transferTokenFrom(bob, collectionId, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.transferTokenFrom(bob, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});