difftreelog
refactor remove last anonimous structs
in: master
14 files changed
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 evm_coder::{21 abi::AbiType,22 solidity_interface, solidity, ToLog,23 types::*,24 execution::{Result, Error},25 weight,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,31 SponsoringRateLimit, SponsorshipState,32};33use alloc::format;3435use crate::{36 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,37 eth::{38 CollectionPermissions as EvmPermissions, CollectionLimitField as EvmCollectionLimits, self,39 },40 weights::WeightInfo,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 },6869 /// The token has been changed.70 TokenChanged {71 /// Collection ID.72 #[indexed]73 collection_id: address,74 /// Token ID.75 token_id: uint256,76 },77}7879/// Does not always represent a full collection, for RFT it is either80/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).81pub trait CommonEvmHandler {82 /// Raw compiled binary code of the contract stub83 const CODE: &'static [u8];8485 /// Call precompiled handle.86 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;87}8889/// @title A contract that allows you to work with collections.90#[solidity_interface(name = Collection)]91impl<T: Config> CollectionHandle<T>92where93 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,94{95 /// Set collection property.96 ///97 /// @param key Property key.98 /// @param value Propery value.99 #[solidity(hide)]100 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]101 fn set_collection_property(102 &mut self,103 caller: caller,104 key: string,105 value: bytes,106 ) -> Result<void> {107 let caller = T::CrossAccountId::from_eth(caller);108 let key = <Vec<u8>>::from(key)109 .try_into()110 .map_err(|_| "key too large")?;111 let value = value.0.try_into().map_err(|_| "value too large")?;112113 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })114 .map_err(dispatch_to_evm::<T>)115 }116117 /// Set collection properties.118 ///119 /// @param properties Vector of properties key/value pair.120 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]121 fn set_collection_properties(122 &mut self,123 caller: caller,124 properties: Vec<eth::Property>,125 ) -> Result<void> {126 let caller = T::CrossAccountId::from_eth(caller);127128 let properties = properties129 .into_iter()130 .map(|eth::Property { key, value }| {131 let key = <Vec<u8>>::from(key)132 .try_into()133 .map_err(|_| "key too large")?;134135 let value = value.0.try_into().map_err(|_| "value too large")?;136137 Ok(Property { key, value })138 })139 .collect::<Result<Vec<_>>>()?;140141 <Pallet<T>>::set_collection_properties(self, &caller, properties)142 .map_err(dispatch_to_evm::<T>)143 }144145 /// Delete collection property.146 ///147 /// @param key Property key.148 #[solidity(hide)]149 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]150 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {151 let caller = T::CrossAccountId::from_eth(caller);152 let key = <Vec<u8>>::from(key)153 .try_into()154 .map_err(|_| "key too large")?;155156 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)157 }158159 /// Delete collection properties.160 ///161 /// @param keys Properties keys.162 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]163 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let keys = keys166 .into_iter()167 .map(|key| {168 <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| Error::Revert("key too large".into()))171 })172 .collect::<Result<Vec<_>>>()?;173174 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)175 }176177 /// Get collection property.178 ///179 /// @dev Throws error if key not found.180 ///181 /// @param key Property key.182 /// @return bytes The property corresponding to the key.183 fn collection_property(&self, key: string) -> Result<bytes> {184 let key = <Vec<u8>>::from(key)185 .try_into()186 .map_err(|_| "key too large")?;187188 let props = CollectionProperties::<T>::get(self.id);189 let prop = props.get(&key).ok_or("key not found")?;190191 Ok(bytes(prop.to_vec()))192 }193194 /// Get collection properties.195 ///196 /// @param keys Properties keys. Empty keys for all propertyes.197 /// @return Vector of properties key/value pairs.198 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<eth::Property>> {199 let keys = keys200 .into_iter()201 .map(|key| {202 <Vec<u8>>::from(key)203 .try_into()204 .map_err(|_| Error::Revert("key too large".into()))205 })206 .collect::<Result<Vec<_>>>()?;207208 let properties = Pallet::<T>::filter_collection_properties(209 self.id,210 if keys.is_empty() { None } else { Some(keys) },211 )212 .map_err(dispatch_to_evm::<T>)?;213214 let properties = properties215 .into_iter()216 .map(|p| {217 let key =218 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;219 let value = bytes(p.value.to_vec());220 Ok(eth::Property { key, value })221 })222 .collect::<Result<Vec<_>>>()?;223 Ok(properties)224 }225226 /// Set the sponsor of the collection.227 ///228 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.229 ///230 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.231 #[solidity(hide)]232 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {233 self.consume_store_reads_and_writes(1, 1)?;234235 let caller = T::CrossAccountId::from_eth(caller);236237 let sponsor = T::CrossAccountId::from_eth(sponsor);238 self.set_sponsor(&caller, sponsor.as_sub().clone())239 .map_err(dispatch_to_evm::<T>)240 }241242 /// Set the sponsor of the collection.243 ///244 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.245 ///246 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.247 fn set_collection_sponsor_cross(248 &mut self,249 caller: caller,250 sponsor: eth::CrossAccount,251 ) -> Result<void> {252 self.consume_store_reads_and_writes(1, 1)?;253254 let caller = T::CrossAccountId::from_eth(caller);255256 let sponsor = sponsor.into_sub_cross_account::<T>()?;257 self.set_sponsor(&caller, sponsor.as_sub().clone())258 .map_err(dispatch_to_evm::<T>)259 }260261 /// Whether there is a pending sponsor.262 fn has_collection_pending_sponsor(&self) -> Result<bool> {263 Ok(matches!(264 self.collection.sponsorship,265 SponsorshipState::Unconfirmed(_)266 ))267 }268269 /// Collection sponsorship confirmation.270 ///271 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.272 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {273 self.consume_store_writes(1)?;274275 let caller = T::CrossAccountId::from_eth(caller);276 self.confirm_sponsorship(caller.as_sub())277 .map_err(dispatch_to_evm::<T>)278 }279280 /// Remove collection sponsor.281 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {282 self.consume_store_reads_and_writes(1, 1)?;283 let caller = T::CrossAccountId::from_eth(caller);284 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)285 }286287 /// Get current sponsor.288 ///289 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.290 fn collection_sponsor(&self) -> Result<eth::CrossAccount> {291 let sponsor = match self.collection.sponsorship.sponsor() {292 Some(sponsor) => sponsor,293 None => return Ok(Default::default()),294 };295296 Ok(eth::CrossAccount::from_sub::<T>(&sponsor))297 }298299 /// Get current collection limits.300 ///301 /// @return Array of collection limits302 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {303 let limits = &self.collection.limits;304305 Ok(vec![306 eth::CollectionLimit::from_opt_int(307 EvmCollectionLimits::AccountTokenOwnership,308 limits.account_token_ownership_limit,309 ),310 eth::CollectionLimit::from_opt_int(311 EvmCollectionLimits::SponsoredDataSize,312 limits.sponsored_data_size,313 ),314 limits315 .sponsored_data_rate_limit316 .and_then(|limit| {317 if let SponsoringRateLimit::Blocks(blocks) = limit {318 Some(eth::CollectionLimit::from_int(319 EvmCollectionLimits::SponsoredDataRateLimit,320 blocks,321 ))322 } else {323 None324 }325 })326 .unwrap_or(eth::CollectionLimit::from_int(327 EvmCollectionLimits::SponsoredDataRateLimit,328 Default::default(),329 )),330 eth::CollectionLimit::from_opt_int(EvmCollectionLimits::TokenLimit, limits.token_limit),331 eth::CollectionLimit::from_opt_int(332 EvmCollectionLimits::SponsorTransferTimeout,333 limits.sponsor_transfer_timeout,334 ),335 eth::CollectionLimit::from_opt_int(336 EvmCollectionLimits::SponsorApproveTimeout,337 limits.sponsor_approve_timeout,338 ),339 eth::CollectionLimit::from_opt_bool(340 EvmCollectionLimits::OwnerCanTransfer,341 limits.owner_can_transfer,342 ),343 eth::CollectionLimit::from_opt_bool(344 EvmCollectionLimits::OwnerCanDestroy,345 limits.owner_can_destroy,346 ),347 eth::CollectionLimit::from_opt_bool(348 EvmCollectionLimits::TransferEnabled,349 limits.transfers_enabled,350 ),351 ])352 }353354 /// Set limits for the collection.355 /// @dev Throws error if limit not found.356 /// @param limit Some limit.357 #[solidity(rename_selector = "setCollectionLimit")]358 fn set_collection_limit(359 &mut self,360 caller: caller,361 limit: eth::CollectionLimit,362 ) -> Result<void> {363 self.consume_store_reads_and_writes(1, 1)?;364365 let caller = T::CrossAccountId::from_eth(caller);366 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)367 }368369 /// Get contract address.370 fn contract_address(&self) -> Result<address> {371 Ok(crate::eth::collection_id_to_address(self.id))372 }373374 /// Add collection admin.375 /// @param newAdmin Cross account administrator address.376 fn add_collection_admin_cross(377 &mut self,378 caller: caller,379 new_admin: eth::CrossAccount,380 ) -> Result<void> {381 self.consume_store_reads_and_writes(2, 2)?;382383 let caller = T::CrossAccountId::from_eth(caller);384 let new_admin = new_admin.into_sub_cross_account::<T>()?;385 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;386 Ok(())387 }388389 /// Remove collection admin.390 /// @param admin Cross account administrator address.391 fn remove_collection_admin_cross(392 &mut self,393 caller: caller,394 admin: eth::CrossAccount,395 ) -> Result<void> {396 self.consume_store_reads_and_writes(2, 2)?;397398 let caller = T::CrossAccountId::from_eth(caller);399 let admin = admin.into_sub_cross_account::<T>()?;400 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;401 Ok(())402 }403404 /// Add collection admin.405 /// @param newAdmin Address of the added administrator.406 #[solidity(hide)]407 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {408 self.consume_store_reads_and_writes(2, 2)?;409410 let caller = T::CrossAccountId::from_eth(caller);411 let new_admin = T::CrossAccountId::from_eth(new_admin);412 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;413 Ok(())414 }415416 /// Remove collection admin.417 ///418 /// @param admin Address of the removed administrator.419 #[solidity(hide)]420 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {421 self.consume_store_reads_and_writes(2, 2)?;422423 let caller = T::CrossAccountId::from_eth(caller);424 let admin = T::CrossAccountId::from_eth(admin);425 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;426 Ok(())427 }428429 /// Toggle accessibility of collection nesting.430 ///431 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'432 #[solidity(rename_selector = "setCollectionNesting")]433 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {434 self.consume_store_reads_and_writes(1, 1)?;435436 let caller = T::CrossAccountId::from_eth(caller);437438 let mut permissions = self.collection.permissions.clone();439 let mut nesting = permissions.nesting().clone();440 nesting.token_owner = enable;441 nesting.restricted = None;442 permissions.nesting = Some(nesting);443444 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)445 }446447 /// Toggle accessibility of collection nesting.448 ///449 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'450 /// @param collections Addresses of collections that will be available for nesting.451 #[solidity(rename_selector = "setCollectionNesting")]452 fn set_nesting(453 &mut self,454 caller: caller,455 enable: bool,456 collections: Vec<address>,457 ) -> Result<void> {458 self.consume_store_reads_and_writes(1, 1)?;459460 if collections.is_empty() {461 return Err("no addresses provided".into());462 }463 let caller = T::CrossAccountId::from_eth(caller);464465 let mut permissions = self.collection.permissions.clone();466 match enable {467 false => {468 let mut nesting = permissions.nesting().clone();469 nesting.token_owner = false;470 nesting.restricted = None;471 permissions.nesting = Some(nesting);472 }473 true => {474 let mut bv = OwnerRestrictedSet::new();475 for i in collections {476 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {477 Error::Revert("Can't convert address into collection id".into())478 })?)479 .map_err(|_| "too many collections")?;480 }481 let mut nesting = permissions.nesting().clone();482 nesting.token_owner = true;483 nesting.restricted = Some(bv);484 permissions.nesting = Some(nesting);485 }486 };487488 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)489 }490491 /// Returns nesting for a collection492 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]493 fn collection_nesting_restricted_ids(&self) -> Result<(bool, Vec<uint256>)> {494 let nesting = self.collection.permissions.nesting();495496 Ok((497 nesting.token_owner,498 nesting499 .restricted500 .clone()501 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())502 .unwrap_or_default(),503 ))504 }505506 /// Returns permissions for a collection507 fn collection_nesting_permissions(&self) -> Result<Vec<(EvmPermissions, bool)>> {508 let nesting = self.collection.permissions.nesting();509 Ok(vec![510 (EvmPermissions::CollectionAdmin, nesting.collection_admin),511 (EvmPermissions::TokenOwner, nesting.token_owner),512 ])513 }514 /// Set the collection access method.515 /// @param mode Access mode516 /// 0 for Normal517 /// 1 for AllowList518 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {519 self.consume_store_reads_and_writes(1, 1)?;520521 let caller = T::CrossAccountId::from_eth(caller);522 let permissions = CollectionPermissions {523 access: Some(match mode {524 0 => AccessMode::Normal,525 1 => AccessMode::AllowList,526 _ => return Err("not supported access mode".into()),527 }),528 ..Default::default()529 };530 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)531 }532533 /// Checks that user allowed to operate with collection.534 ///535 /// @param user User address to check.536 fn allowlisted_cross(&self, user: eth::CrossAccount) -> Result<bool> {537 let user = user.into_sub_cross_account::<T>()?;538 Ok(Pallet::<T>::allowed(self.id, user))539 }540541 /// Add the user to the allowed list.542 ///543 /// @param user Address of a trusted user.544 #[solidity(hide)]545 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {546 self.consume_store_writes(1)?;547548 let caller = T::CrossAccountId::from_eth(caller);549 let user = T::CrossAccountId::from_eth(user);550 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;551 Ok(())552 }553554 /// Add user to allowed list.555 ///556 /// @param user User cross account address.557 fn add_to_collection_allow_list_cross(558 &mut self,559 caller: caller,560 user: eth::CrossAccount,561 ) -> Result<void> {562 self.consume_store_writes(1)?;563564 let caller = T::CrossAccountId::from_eth(caller);565 let user = user.into_sub_cross_account::<T>()?;566 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;567 Ok(())568 }569570 /// Remove the user from the allowed list.571 ///572 /// @param user Address of a removed user.573 #[solidity(hide)]574 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {575 self.consume_store_writes(1)?;576577 let caller = T::CrossAccountId::from_eth(caller);578 let user = T::CrossAccountId::from_eth(user);579 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;580 Ok(())581 }582583 /// Remove user from allowed list.584 ///585 /// @param user User cross account address.586 fn remove_from_collection_allow_list_cross(587 &mut self,588 caller: caller,589 user: eth::CrossAccount,590 ) -> Result<void> {591 self.consume_store_writes(1)?;592593 let caller = T::CrossAccountId::from_eth(caller);594 let user = user.into_sub_cross_account::<T>()?;595 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;596 Ok(())597 }598599 /// Switch permission for minting.600 ///601 /// @param mode Enable if "true".602 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {603 self.consume_store_reads_and_writes(1, 1)?;604605 let caller = T::CrossAccountId::from_eth(caller);606 let permissions = CollectionPermissions {607 mint_mode: Some(mode),608 ..Default::default()609 };610 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)611 }612613 /// Check that account is the owner or admin of the collection614 ///615 /// @param user account to verify616 /// @return "true" if account is the owner or admin617 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]618 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {619 let user = T::CrossAccountId::from_eth(user);620 Ok(self.is_owner_or_admin(&user))621 }622623 /// Check that account is the owner or admin of the collection624 ///625 /// @param user User cross account to verify626 /// @return "true" if account is the owner or admin627 fn is_owner_or_admin_cross(&self, user: eth::CrossAccount) -> Result<bool> {628 let user = user.into_sub_cross_account::<T>()?;629 Ok(self.is_owner_or_admin(&user))630 }631632 /// Returns collection type633 ///634 /// @return `Fungible` or `NFT` or `ReFungible`635 fn unique_collection_type(&self) -> Result<string> {636 let mode = match self.collection.mode {637 CollectionMode::Fungible(_) => "Fungible",638 CollectionMode::NFT => "NFT",639 CollectionMode::ReFungible => "ReFungible",640 };641 Ok(mode.into())642 }643644 /// Get collection owner.645 ///646 /// @return Tuble with sponsor address and his substrate mirror.647 /// If address is canonical then substrate mirror is zero and vice versa.648 fn collection_owner(&self) -> Result<eth::CrossAccount> {649 Ok(eth::CrossAccount::from_sub_cross_account::<T>(650 &T::CrossAccountId::from_sub(self.owner.clone()),651 ))652 }653654 /// Changes collection owner to another account655 ///656 /// @dev Owner can be changed only by current owner657 /// @param newOwner new owner account658 #[solidity(hide, rename_selector = "changeCollectionOwner")]659 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {660 self.consume_store_writes(1)?;661662 let caller = T::CrossAccountId::from_eth(caller);663 let new_owner = T::CrossAccountId::from_eth(new_owner);664 self.change_owner(caller, new_owner)665 .map_err(dispatch_to_evm::<T>)666 }667668 /// Get collection administrators669 ///670 /// @return Vector of tuples with admins address and his substrate mirror.671 /// If address is canonical then substrate mirror is zero and vice versa.672 fn collection_admins(&self) -> Result<Vec<eth::CrossAccount>> {673 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))674 .map(|(admin, _)| eth::CrossAccount::from_sub_cross_account::<T>(&admin))675 .collect();676 Ok(result)677 }678679 /// Changes collection owner to another account680 ///681 /// @dev Owner can be changed only by current owner682 /// @param newOwner new owner cross account683 fn change_collection_owner_cross(684 &mut self,685 caller: caller,686 new_owner: eth::CrossAccount,687 ) -> Result<void> {688 self.consume_store_writes(1)?;689690 let caller = T::CrossAccountId::from_eth(caller);691 let new_owner = new_owner.into_sub_cross_account::<T>()?;692 self.change_owner(caller, new_owner)693 .map_err(dispatch_to_evm::<T>)694 }695}696697/// Contains static property keys and values.698pub mod static_property {699 use evm_coder::{700 execution::{Result, Error},701 };702 use alloc::format;703704 const EXPECT_CONVERT_ERROR: &str = "length < limit";705706 /// Keys.707 pub mod key {708 use super::*;709710 /// Key "baseURI".711 pub fn base_uri() -> up_data_structs::PropertyKey {712 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)713 }714715 /// Key "url".716 pub fn url() -> up_data_structs::PropertyKey {717 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)718 }719720 /// Key "suffix".721 pub fn suffix() -> up_data_structs::PropertyKey {722 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)723 }724725 /// Key "parentNft".726 pub fn parent_nft() -> up_data_structs::PropertyKey {727 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)728 }729 }730731 /// Convert `byte` to [`PropertyKey`].732 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {733 bytes.to_vec().try_into().map_err(|_| {734 Error::Revert(format!(735 "Property key is too long. Max length is {}.",736 up_data_structs::PropertyKey::bound()737 ))738 })739 }740741 /// Convert `bytes` to [`PropertyValue`].742 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {743 bytes.to_vec().try_into().map_err(|_| {744 Error::Revert(format!(745 "Property key is too long. Max length is {}.",746 up_data_structs::PropertyKey::bound()747 ))748 })749 }750}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 evm_coder::{21 abi::AbiType,22 solidity_interface, solidity, ToLog,23 types::*,24 execution::{Result, Error},25 weight,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,31 SponsoringRateLimit, SponsorshipState,32};33use alloc::format;3435use crate::{36 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,37 eth::{CollectionLimitField as EvmCollectionLimits, self},38 weights::WeightInfo,39};4041/// Events for ethereum collection helper.42#[derive(ToLog)]43pub enum CollectionHelpersEvents {44 /// The collection has been created.45 CollectionCreated {46 /// Collection owner.47 #[indexed]48 owner: address,4950 /// Collection ID.51 #[indexed]52 collection_id: address,53 },54 /// The collection has been destroyed.55 CollectionDestroyed {56 /// Collection ID.57 #[indexed]58 collection_id: address,59 },60 /// The collection has been changed.61 CollectionChanged {62 /// Collection ID.63 #[indexed]64 collection_id: address,65 },6667 /// The token has been changed.68 TokenChanged {69 /// Collection ID.70 #[indexed]71 collection_id: address,72 /// Token ID.73 token_id: uint256,74 },75}7677/// Does not always represent a full collection, for RFT it is either78/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).79pub trait CommonEvmHandler {80 /// Raw compiled binary code of the contract stub81 const CODE: &'static [u8];8283 /// Call precompiled handle.84 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;85}8687/// @title A contract that allows you to work with collections.88#[solidity_interface(name = Collection)]89impl<T: Config> CollectionHandle<T>90where91 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,92{93 /// Set collection property.94 ///95 /// @param key Property key.96 /// @param value Propery value.97 #[solidity(hide)]98 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]99 fn set_collection_property(100 &mut self,101 caller: caller,102 key: string,103 value: bytes,104 ) -> Result<void> {105 let caller = T::CrossAccountId::from_eth(caller);106 let key = <Vec<u8>>::from(key)107 .try_into()108 .map_err(|_| "key too large")?;109 let value = value.0.try_into().map_err(|_| "value too large")?;110111 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })112 .map_err(dispatch_to_evm::<T>)113 }114115 /// Set collection properties.116 ///117 /// @param properties Vector of properties key/value pair.118 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]119 fn set_collection_properties(120 &mut self,121 caller: caller,122 properties: Vec<eth::Property>,123 ) -> Result<void> {124 let caller = T::CrossAccountId::from_eth(caller);125126 let properties = properties127 .into_iter()128 .map(|eth::Property { key, value }| {129 let key = <Vec<u8>>::from(key)130 .try_into()131 .map_err(|_| "key too large")?;132133 let value = value.0.try_into().map_err(|_| "value too large")?;134135 Ok(Property { key, value })136 })137 .collect::<Result<Vec<_>>>()?;138139 <Pallet<T>>::set_collection_properties(self, &caller, properties)140 .map_err(dispatch_to_evm::<T>)141 }142143 /// Delete collection property.144 ///145 /// @param key Property key.146 #[solidity(hide)]147 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]148 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {149 let caller = T::CrossAccountId::from_eth(caller);150 let key = <Vec<u8>>::from(key)151 .try_into()152 .map_err(|_| "key too large")?;153154 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)155 }156157 /// Delete collection properties.158 ///159 /// @param keys Properties keys.160 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]161 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {162 let caller = T::CrossAccountId::from_eth(caller);163 let keys = keys164 .into_iter()165 .map(|key| {166 <Vec<u8>>::from(key)167 .try_into()168 .map_err(|_| Error::Revert("key too large".into()))169 })170 .collect::<Result<Vec<_>>>()?;171172 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)173 }174175 /// Get collection property.176 ///177 /// @dev Throws error if key not found.178 ///179 /// @param key Property key.180 /// @return bytes The property corresponding to the key.181 fn collection_property(&self, key: string) -> Result<bytes> {182 let key = <Vec<u8>>::from(key)183 .try_into()184 .map_err(|_| "key too large")?;185186 let props = CollectionProperties::<T>::get(self.id);187 let prop = props.get(&key).ok_or("key not found")?;188189 Ok(bytes(prop.to_vec()))190 }191192 /// Get collection properties.193 ///194 /// @param keys Properties keys. Empty keys for all propertyes.195 /// @return Vector of properties key/value pairs.196 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<eth::Property>> {197 let keys = keys198 .into_iter()199 .map(|key| {200 <Vec<u8>>::from(key)201 .try_into()202 .map_err(|_| Error::Revert("key too large".into()))203 })204 .collect::<Result<Vec<_>>>()?;205206 let properties = Pallet::<T>::filter_collection_properties(207 self.id,208 if keys.is_empty() { None } else { Some(keys) },209 )210 .map_err(dispatch_to_evm::<T>)?;211212 let properties = properties213 .into_iter()214 .map(|p| {215 let key =216 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;217 let value = bytes(p.value.to_vec());218 Ok(eth::Property { key, value })219 })220 .collect::<Result<Vec<_>>>()?;221 Ok(properties)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 Address of the sponsor from whose account funds will be debited for operations with the contract.229 #[solidity(hide)]230 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {231 self.consume_store_reads_and_writes(1, 1)?;232233 let caller = T::CrossAccountId::from_eth(caller);234235 let sponsor = T::CrossAccountId::from_eth(sponsor);236 self.set_sponsor(&caller, sponsor.as_sub().clone())237 .map_err(dispatch_to_evm::<T>)238 }239240 /// Set the sponsor of the collection.241 ///242 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.243 ///244 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.245 fn set_collection_sponsor_cross(246 &mut self,247 caller: caller,248 sponsor: eth::CrossAccount,249 ) -> Result<void> {250 self.consume_store_reads_and_writes(1, 1)?;251252 let caller = T::CrossAccountId::from_eth(caller);253254 let sponsor = sponsor.into_sub_cross_account::<T>()?;255 self.set_sponsor(&caller, sponsor.as_sub().clone())256 .map_err(dispatch_to_evm::<T>)257 }258259 /// Whether there is a pending sponsor.260 fn has_collection_pending_sponsor(&self) -> Result<bool> {261 Ok(matches!(262 self.collection.sponsorship,263 SponsorshipState::Unconfirmed(_)264 ))265 }266267 /// Collection sponsorship confirmation.268 ///269 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.270 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {271 self.consume_store_writes(1)?;272273 let caller = T::CrossAccountId::from_eth(caller);274 self.confirm_sponsorship(caller.as_sub())275 .map_err(dispatch_to_evm::<T>)276 }277278 /// Remove collection sponsor.279 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {280 self.consume_store_reads_and_writes(1, 1)?;281 let caller = T::CrossAccountId::from_eth(caller);282 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)283 }284285 /// Get current sponsor.286 ///287 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.288 fn collection_sponsor(&self) -> Result<eth::CrossAccount> {289 let sponsor = match self.collection.sponsorship.sponsor() {290 Some(sponsor) => sponsor,291 None => return Ok(Default::default()),292 };293294 Ok(eth::CrossAccount::from_sub::<T>(&sponsor))295 }296297 /// Get current collection limits.298 ///299 /// @return Array of collection limits300 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {301 let limits = &self.collection.limits;302303 Ok(vec![304 eth::CollectionLimit::from_opt_int(305 EvmCollectionLimits::AccountTokenOwnership,306 limits.account_token_ownership_limit,307 ),308 eth::CollectionLimit::from_opt_int(309 EvmCollectionLimits::SponsoredDataSize,310 limits.sponsored_data_size,311 ),312 limits313 .sponsored_data_rate_limit314 .and_then(|limit| {315 if let SponsoringRateLimit::Blocks(blocks) = limit {316 Some(eth::CollectionLimit::from_int(317 EvmCollectionLimits::SponsoredDataRateLimit,318 blocks,319 ))320 } else {321 None322 }323 })324 .unwrap_or(eth::CollectionLimit::from_int(325 EvmCollectionLimits::SponsoredDataRateLimit,326 Default::default(),327 )),328 eth::CollectionLimit::from_opt_int(EvmCollectionLimits::TokenLimit, limits.token_limit),329 eth::CollectionLimit::from_opt_int(330 EvmCollectionLimits::SponsorTransferTimeout,331 limits.sponsor_transfer_timeout,332 ),333 eth::CollectionLimit::from_opt_int(334 EvmCollectionLimits::SponsorApproveTimeout,335 limits.sponsor_approve_timeout,336 ),337 eth::CollectionLimit::from_opt_bool(338 EvmCollectionLimits::OwnerCanTransfer,339 limits.owner_can_transfer,340 ),341 eth::CollectionLimit::from_opt_bool(342 EvmCollectionLimits::OwnerCanDestroy,343 limits.owner_can_destroy,344 ),345 eth::CollectionLimit::from_opt_bool(346 EvmCollectionLimits::TransferEnabled,347 limits.transfers_enabled,348 ),349 ])350 }351352 /// Set limits for the collection.353 /// @dev Throws error if limit not found.354 /// @param limit Some limit.355 #[solidity(rename_selector = "setCollectionLimit")]356 fn set_collection_limit(357 &mut self,358 caller: caller,359 limit: eth::CollectionLimit,360 ) -> Result<void> {361 self.consume_store_reads_and_writes(1, 1)?;362363 let caller = T::CrossAccountId::from_eth(caller);364 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)365 }366367 /// Get contract address.368 fn contract_address(&self) -> Result<address> {369 Ok(crate::eth::collection_id_to_address(self.id))370 }371372 /// Add collection admin.373 /// @param newAdmin Cross account administrator address.374 fn add_collection_admin_cross(375 &mut self,376 caller: caller,377 new_admin: eth::CrossAccount,378 ) -> Result<void> {379 self.consume_store_reads_and_writes(2, 2)?;380381 let caller = T::CrossAccountId::from_eth(caller);382 let new_admin = new_admin.into_sub_cross_account::<T>()?;383 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;384 Ok(())385 }386387 /// Remove collection admin.388 /// @param admin Cross account administrator address.389 fn remove_collection_admin_cross(390 &mut self,391 caller: caller,392 admin: eth::CrossAccount,393 ) -> Result<void> {394 self.consume_store_reads_and_writes(2, 2)?;395396 let caller = T::CrossAccountId::from_eth(caller);397 let admin = admin.into_sub_cross_account::<T>()?;398 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;399 Ok(())400 }401402 /// Add collection admin.403 /// @param newAdmin Address of the added administrator.404 #[solidity(hide)]405 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {406 self.consume_store_reads_and_writes(2, 2)?;407408 let caller = T::CrossAccountId::from_eth(caller);409 let new_admin = T::CrossAccountId::from_eth(new_admin);410 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;411 Ok(())412 }413414 /// Remove collection admin.415 ///416 /// @param admin Address of the removed administrator.417 #[solidity(hide)]418 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {419 self.consume_store_reads_and_writes(2, 2)?;420421 let caller = T::CrossAccountId::from_eth(caller);422 let admin = T::CrossAccountId::from_eth(admin);423 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;424 Ok(())425 }426427 /// Toggle accessibility of collection nesting.428 ///429 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'430 #[solidity(rename_selector = "setCollectionNesting")]431 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {432 self.consume_store_reads_and_writes(1, 1)?;433434 let caller = T::CrossAccountId::from_eth(caller);435436 let mut permissions = self.collection.permissions.clone();437 let mut nesting = permissions.nesting().clone();438 nesting.token_owner = enable;439 nesting.restricted = None;440 permissions.nesting = Some(nesting);441442 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)443 }444445 /// Toggle accessibility of collection nesting.446 ///447 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'448 /// @param collections Addresses of collections that will be available for nesting.449 #[solidity(rename_selector = "setCollectionNesting")]450 fn set_nesting(451 &mut self,452 caller: caller,453 enable: bool,454 collections: Vec<address>,455 ) -> Result<void> {456 self.consume_store_reads_and_writes(1, 1)?;457458 if collections.is_empty() {459 return Err("no addresses provided".into());460 }461 let caller = T::CrossAccountId::from_eth(caller);462463 let mut permissions = self.collection.permissions.clone();464 match enable {465 false => {466 let mut nesting = permissions.nesting().clone();467 nesting.token_owner = false;468 nesting.restricted = None;469 permissions.nesting = Some(nesting);470 }471 true => {472 let mut bv = OwnerRestrictedSet::new();473 for i in collections {474 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {475 Error::Revert("Can't convert address into collection id".into())476 })?)477 .map_err(|_| "too many collections")?;478 }479 let mut nesting = permissions.nesting().clone();480 nesting.token_owner = true;481 nesting.restricted = Some(bv);482 permissions.nesting = Some(nesting);483 }484 };485486 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)487 }488489 /// Returns nesting for a collection490 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]491 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {492 let nesting = self.collection.permissions.nesting();493494 Ok(eth::CollectionNesting::new(495 nesting.token_owner,496 nesting497 .restricted498 .clone()499 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())500 .unwrap_or_default(),501 ))502 }503504 /// Returns permissions for a collection505 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {506 let nesting = self.collection.permissions.nesting();507 Ok(vec![508 eth::CollectionNestingPermission::new(509 eth::CollectionPermissionField::CollectionAdmin,510 nesting.collection_admin,511 ),512 eth::CollectionNestingPermission::new(513 eth::CollectionPermissionField::TokenOwner,514 nesting.token_owner,515 ),516 ])517 }518 /// Set the collection access method.519 /// @param mode Access mode520 /// 0 for Normal521 /// 1 for AllowList522 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {523 self.consume_store_reads_and_writes(1, 1)?;524525 let caller = T::CrossAccountId::from_eth(caller);526 let permissions = CollectionPermissions {527 access: Some(match mode {528 0 => AccessMode::Normal,529 1 => AccessMode::AllowList,530 _ => return Err("not supported access mode".into()),531 }),532 ..Default::default()533 };534 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)535 }536537 /// Checks that user allowed to operate with collection.538 ///539 /// @param user User address to check.540 fn allowlisted_cross(&self, user: eth::CrossAccount) -> Result<bool> {541 let user = user.into_sub_cross_account::<T>()?;542 Ok(Pallet::<T>::allowed(self.id, user))543 }544545 /// Add the user to the allowed list.546 ///547 /// @param user Address of a trusted user.548 #[solidity(hide)]549 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {550 self.consume_store_writes(1)?;551552 let caller = T::CrossAccountId::from_eth(caller);553 let user = T::CrossAccountId::from_eth(user);554 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;555 Ok(())556 }557558 /// Add user to allowed list.559 ///560 /// @param user User cross account address.561 fn add_to_collection_allow_list_cross(562 &mut self,563 caller: caller,564 user: eth::CrossAccount,565 ) -> Result<void> {566 self.consume_store_writes(1)?;567568 let caller = T::CrossAccountId::from_eth(caller);569 let user = user.into_sub_cross_account::<T>()?;570 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;571 Ok(())572 }573574 /// Remove the user from the allowed list.575 ///576 /// @param user Address of a removed user.577 #[solidity(hide)]578 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {579 self.consume_store_writes(1)?;580581 let caller = T::CrossAccountId::from_eth(caller);582 let user = T::CrossAccountId::from_eth(user);583 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;584 Ok(())585 }586587 /// Remove user from allowed list.588 ///589 /// @param user User cross account address.590 fn remove_from_collection_allow_list_cross(591 &mut self,592 caller: caller,593 user: eth::CrossAccount,594 ) -> Result<void> {595 self.consume_store_writes(1)?;596597 let caller = T::CrossAccountId::from_eth(caller);598 let user = user.into_sub_cross_account::<T>()?;599 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;600 Ok(())601 }602603 /// Switch permission for minting.604 ///605 /// @param mode Enable if "true".606 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {607 self.consume_store_reads_and_writes(1, 1)?;608609 let caller = T::CrossAccountId::from_eth(caller);610 let permissions = CollectionPermissions {611 mint_mode: Some(mode),612 ..Default::default()613 };614 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)615 }616617 /// Check that account is the owner or admin of the collection618 ///619 /// @param user account to verify620 /// @return "true" if account is the owner or admin621 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]622 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {623 let user = T::CrossAccountId::from_eth(user);624 Ok(self.is_owner_or_admin(&user))625 }626627 /// Check that account is the owner or admin of the collection628 ///629 /// @param user User cross account to verify630 /// @return "true" if account is the owner or admin631 fn is_owner_or_admin_cross(&self, user: eth::CrossAccount) -> Result<bool> {632 let user = user.into_sub_cross_account::<T>()?;633 Ok(self.is_owner_or_admin(&user))634 }635636 /// Returns collection type637 ///638 /// @return `Fungible` or `NFT` or `ReFungible`639 fn unique_collection_type(&self) -> Result<string> {640 let mode = match self.collection.mode {641 CollectionMode::Fungible(_) => "Fungible",642 CollectionMode::NFT => "NFT",643 CollectionMode::ReFungible => "ReFungible",644 };645 Ok(mode.into())646 }647648 /// Get collection owner.649 ///650 /// @return Tuble with sponsor address and his substrate mirror.651 /// If address is canonical then substrate mirror is zero and vice versa.652 fn collection_owner(&self) -> Result<eth::CrossAccount> {653 Ok(eth::CrossAccount::from_sub_cross_account::<T>(654 &T::CrossAccountId::from_sub(self.owner.clone()),655 ))656 }657658 /// Changes collection owner to another account659 ///660 /// @dev Owner can be changed only by current owner661 /// @param newOwner new owner account662 #[solidity(hide, rename_selector = "changeCollectionOwner")]663 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {664 self.consume_store_writes(1)?;665666 let caller = T::CrossAccountId::from_eth(caller);667 let new_owner = T::CrossAccountId::from_eth(new_owner);668 self.change_owner(caller, new_owner)669 .map_err(dispatch_to_evm::<T>)670 }671672 /// Get collection administrators673 ///674 /// @return Vector of tuples with admins address and his substrate mirror.675 /// If address is canonical then substrate mirror is zero and vice versa.676 fn collection_admins(&self) -> Result<Vec<eth::CrossAccount>> {677 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))678 .map(|(admin, _)| eth::CrossAccount::from_sub_cross_account::<T>(&admin))679 .collect();680 Ok(result)681 }682683 /// Changes collection owner to another account684 ///685 /// @dev Owner can be changed only by current owner686 /// @param newOwner new owner cross account687 fn change_collection_owner_cross(688 &mut self,689 caller: caller,690 new_owner: eth::CrossAccount,691 ) -> Result<void> {692 self.consume_store_writes(1)?;693694 let caller = T::CrossAccountId::from_eth(caller);695 let new_owner = new_owner.into_sub_cross_account::<T>()?;696 self.change_owner(caller, new_owner)697 .map_err(dispatch_to_evm::<T>)698 }699}700701/// Contains static property keys and values.702pub mod static_property {703 use evm_coder::{704 execution::{Result, Error},705 };706 use alloc::format;707708 const EXPECT_CONVERT_ERROR: &str = "length < limit";709710 /// Keys.711 pub mod key {712 use super::*;713714 /// Key "baseURI".715 pub fn base_uri() -> up_data_structs::PropertyKey {716 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)717 }718719 /// Key "url".720 pub fn url() -> up_data_structs::PropertyKey {721 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)722 }723724 /// Key "suffix".725 pub fn suffix() -> up_data_structs::PropertyKey {726 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)727 }728729 /// Key "parentNft".730 pub fn parent_nft() -> up_data_structs::PropertyKey {731 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)732 }733 }734735 /// Convert `byte` to [`PropertyKey`].736 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {737 bytes.to_vec().try_into().map_err(|_| {738 Error::Revert(format!(739 "Property key is too long. Max length is {}.",740 up_data_structs::PropertyKey::bound()741 ))742 })743 }744745 /// Convert `bytes` to [`PropertyValue`].746 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {747 bytes.to_vec().try_into().map_err(|_| {748 Error::Revert(format!(749 "Property key is too long. Max length is {}.",750 up_data_structs::PropertyKey::bound()751 ))752 })753 }754}pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -306,7 +306,7 @@
/// Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
#[derive(Default, Debug, Clone, Copy, AbiCoder)]
#[repr(u8)]
-pub enum CollectionPermissions {
+pub enum CollectionPermissionField {
/// Owner of token can nest tokens under it.
#[default]
TokenOwner,
@@ -431,3 +431,31 @@
Ok(perms)
}
}
+
+/// Nested collections.
+#[derive(Debug, Default, AbiCoder)]
+pub struct CollectionNesting {
+ token_owner: bool,
+ ids: Vec<uint256>,
+}
+
+impl CollectionNesting {
+ /// Create [`CollectionNesting`].
+ pub fn new(token_owner: bool, ids: Vec<uint256>) -> Self {
+ Self { token_owner, ids }
+ }
+}
+
+/// Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+#[derive(Debug, Default, AbiCoder)]
+pub struct CollectionNestingPermission {
+ field: CollectionPermissionField,
+ value: bool,
+}
+
+impl CollectionNestingPermission {
+ /// Create [`CollectionNestingPermission`].
+ pub fn new(field: CollectionPermissionField, value: bool) -> Self {
+ Self { field, value }
+ }
+}
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -257,19 +257,19 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() public view returns (Tuple33 memory) {
+ function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) {
require(false, stub_error);
dummy;
- return Tuple33(false, new uint256[](0));
+ return CollectionNesting(false, new uint256[](0));
}
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() public view returns (Tuple36[] memory) {
+ function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) {
require(false, stub_error);
dummy;
- return new Tuple36[](0);
+ return new CollectionNestingPermission[](0);
}
/// Set the collection access method.
@@ -443,24 +443,24 @@
uint256 sub;
}
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
+}
+
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple36 {
- CollectionPermissions field_0;
- bool field_1;
-}
-
-/// @dev anonymous struct
-struct Tuple33 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -469,6 +469,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -401,19 +401,19 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() public view returns (Tuple45 memory) {
+ function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) {
require(false, stub_error);
dummy;
- return Tuple45(false, new uint256[](0));
+ return CollectionNesting(false, new uint256[](0));
}
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() public view returns (Tuple48[] memory) {
+ function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) {
require(false, stub_error);
dummy;
- return new Tuple48[](0);
+ return new CollectionNestingPermission[](0);
}
/// Set the collection access method.
@@ -587,24 +587,24 @@
uint256 sub;
}
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
+}
+
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple48 {
- CollectionPermissions field_0;
- bool field_1;
-}
-
-/// @dev anonymous struct
-struct Tuple45 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -613,6 +613,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -401,19 +401,19 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() public view returns (Tuple44 memory) {
+ function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) {
require(false, stub_error);
dummy;
- return Tuple44(false, new uint256[](0));
+ return CollectionNesting(false, new uint256[](0));
}
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() public view returns (Tuple47[] memory) {
+ function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) {
require(false, stub_error);
dummy;
- return new Tuple47[](0);
+ return new CollectionNestingPermission[](0);
}
/// Set the collection access method.
@@ -587,24 +587,24 @@
uint256 sub;
}
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
+}
+
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple47 {
- CollectionPermissions field_0;
- bool field_1;
-}
-
-/// @dev anonymous struct
-struct Tuple44 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -613,6 +613,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;
tests/src/eth/abi/fungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -242,13 +242,13 @@
{
"components": [
{
- "internalType": "enum CollectionPermissions",
- "name": "field_0",
+ "internalType": "enum CollectionPermissionField",
+ "name": "field",
"type": "uint8"
},
- { "internalType": "bool", "name": "field_1", "type": "bool" }
+ { "internalType": "bool", "name": "value", "type": "bool" }
],
- "internalType": "struct Tuple36[]",
+ "internalType": "struct CollectionNestingPermission[]",
"name": "",
"type": "tuple[]"
}
@@ -262,14 +262,10 @@
"outputs": [
{
"components": [
- { "internalType": "bool", "name": "field_0", "type": "bool" },
- {
- "internalType": "uint256[]",
- "name": "field_1",
- "type": "uint256[]"
- }
+ { "internalType": "bool", "name": "token_owner", "type": "bool" },
+ { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }
],
- "internalType": "struct Tuple33",
+ "internalType": "struct CollectionNesting",
"name": "",
"type": "tuple"
}
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -272,13 +272,13 @@
{
"components": [
{
- "internalType": "enum CollectionPermissions",
- "name": "field_0",
+ "internalType": "enum CollectionPermissionField",
+ "name": "field",
"type": "uint8"
},
- { "internalType": "bool", "name": "field_1", "type": "bool" }
+ { "internalType": "bool", "name": "value", "type": "bool" }
],
- "internalType": "struct Tuple48[]",
+ "internalType": "struct CollectionNestingPermission[]",
"name": "",
"type": "tuple[]"
}
@@ -292,14 +292,10 @@
"outputs": [
{
"components": [
- { "internalType": "bool", "name": "field_0", "type": "bool" },
- {
- "internalType": "uint256[]",
- "name": "field_1",
- "type": "uint256[]"
- }
+ { "internalType": "bool", "name": "token_owner", "type": "bool" },
+ { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }
],
- "internalType": "struct Tuple45",
+ "internalType": "struct CollectionNesting",
"name": "",
"type": "tuple"
}
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -254,13 +254,13 @@
{
"components": [
{
- "internalType": "enum CollectionPermissions",
- "name": "field_0",
+ "internalType": "enum CollectionPermissionField",
+ "name": "field",
"type": "uint8"
},
- { "internalType": "bool", "name": "field_1", "type": "bool" }
+ { "internalType": "bool", "name": "value", "type": "bool" }
],
- "internalType": "struct Tuple47[]",
+ "internalType": "struct CollectionNestingPermission[]",
"name": "",
"type": "tuple[]"
}
@@ -274,14 +274,10 @@
"outputs": [
{
"components": [
- { "internalType": "bool", "name": "field_0", "type": "bool" },
- {
- "internalType": "uint256[]",
- "name": "field_1",
- "type": "uint256[]"
- }
+ { "internalType": "bool", "name": "token_owner", "type": "bool" },
+ { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" }
],
- "internalType": "struct Tuple44",
+ "internalType": "struct CollectionNesting",
"name": "",
"type": "tuple"
}
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -166,12 +166,12 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() external view returns (Tuple28 memory);
+ function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory);
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() external view returns (Tuple31[] memory);
+ function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory);
/// Set the collection access method.
/// @param mode Access mode
@@ -285,24 +285,24 @@
uint256 sub;
}
-/// @dev anonymous struct
-struct Tuple31 {
- CollectionPermissions field_0;
- bool field_1;
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
}
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple28 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -311,6 +311,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -268,12 +268,12 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() external view returns (Tuple38 memory);
+ function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory);
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() external view returns (Tuple41[] memory);
+ function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory);
/// Set the collection access method.
/// @param mode Access mode
@@ -387,24 +387,24 @@
uint256 sub;
}
-/// @dev anonymous struct
-struct Tuple41 {
- CollectionPermissions field_0;
- bool field_1;
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
}
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple38 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -413,6 +413,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -268,12 +268,12 @@
/// Returns nesting for a collection
/// @dev EVM selector for this function is: 0x22d25bfe,
/// or in textual repr: collectionNestingRestrictedCollectionIds()
- function collectionNestingRestrictedCollectionIds() external view returns (Tuple37 memory);
+ function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory);
/// Returns permissions for a collection
/// @dev EVM selector for this function is: 0x5b2eaf4b,
/// or in textual repr: collectionNestingPermissions()
- function collectionNestingPermissions() external view returns (Tuple40[] memory);
+ function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory);
/// Set the collection access method.
/// @param mode Access mode
@@ -387,24 +387,24 @@
uint256 sub;
}
-/// @dev anonymous struct
-struct Tuple40 {
- CollectionPermissions field_0;
- bool field_1;
+/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.
+struct CollectionNestingPermission {
+ CollectionPermissionField field;
+ bool value;
}
/// @dev Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.
-enum CollectionPermissions {
+enum CollectionPermissionField {
/// @dev Owner of token can nest tokens under it.
TokenOwner,
/// @dev Admin of token collection can nest tokens under token.
CollectionAdmin
}
-/// @dev anonymous struct
-struct Tuple37 {
- bool field_0;
- uint256[] field_1;
+/// @dev Nested collections.
+struct CollectionNesting {
+ bool token_owner;
+ uint256[] ids;
}
/// @dev [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
@@ -413,6 +413,7 @@
OptionUint value;
}
+/// @dev Ethereum representation of Optional value with uint256.
struct OptionUint {
bool status;
uint256 value;