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}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/);
});