difftreelog
refactor AbiRead
in: master
4 files changed
crates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -403,8 +403,9 @@
fn expand_parse(&self) -> proc_macro2::TokenStream {
assert!(!self.is_special());
let name = &self.name;
+ let ty = &self.ty;
quote! {
- #name: reader.abi_read()?
+ #name: <#ty>::abi_read(reader)?
}
}
crates/evm-coder/src/abi.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -342,14 +342,12 @@
}
}
-/// [`AbiReader`] implements reading of many types, but it should
-/// be limited to types defined in spec
-///
-/// As this trait can't be made sealed,
-/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`
-pub trait AbiRead<T> {
+/// [`AbiReader`] implements reading of many types.
+pub trait AbiRead {
/// Read item from current position, advanding decoder
- fn abi_read(&mut self) -> Result<T>;
+ fn abi_read(reader: &mut AbiReader) -> Result<Self>
+ where
+ Self: Sized;
}
macro_rules! impl_abi_readable {
@@ -363,9 +361,9 @@
ABI_ALIGNMENT
}
}
- impl AbiRead<$ty> for AbiReader<'_> {
- fn abi_read(&mut self) -> Result<$ty> {
- self.$method()
+ impl AbiRead for $ty {
+ fn abi_read(reader: &mut AbiReader) -> Result<$ty> {
+ reader.$method()
}
}
};
@@ -389,9 +387,9 @@
ABI_ALIGNMENT
}
}
-impl AbiRead<bytes> for AbiReader<'_> {
- fn abi_read(&mut self) -> Result<bytes> {
- Ok(bytes(self.bytes()?))
+impl AbiRead for bytes {
+ fn abi_read(reader: &mut AbiReader) -> Result<bytes> {
+ Ok(bytes(reader.bytes()?))
}
}
@@ -405,17 +403,14 @@
impl sealed::CanBePlacedInVec for H160 {}
impl sealed::CanBePlacedInVec for EthCrossAccount {}
-impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>
-where
- Self: AbiRead<R>,
-{
- fn abi_read(&mut self) -> Result<Vec<R>> {
- let mut sub = self.subresult(None)?;
+impl<R: AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<R> {
+ fn abi_read(reader: &mut AbiReader) -> Result<Vec<R>> {
+ let mut sub = reader.subresult(None)?;
let size = sub.uint32()? as usize;
sub.subresult_offset = sub.offset;
let mut out = Vec::with_capacity(size);
for _ in 0..size {
- out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);
+ out.push(<R>::abi_read(&mut sub)?);
}
Ok(out)
}
@@ -435,16 +430,16 @@
}
}
-impl AbiRead<EthCrossAccount> for AbiReader<'_> {
- fn abi_read(&mut self) -> Result<EthCrossAccount> {
+impl AbiRead for EthCrossAccount {
+ fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
let size = if !EthCrossAccount::is_dynamic() {
Some(<EthCrossAccount as TypeHelper>::size())
} else {
None
};
- let mut subresult = self.subresult(size)?;
- let eth = <Self as AbiRead<address>>::abi_read(&mut subresult)?;
- let sub = <Self as AbiRead<uint256>>::abi_read(&mut subresult)?;
+ let mut subresult = reader.subresult(size)?;
+ let eth = <address>::abi_read(&mut subresult)?;
+ let sub = <uint256>::abi_read(&mut subresult)?;
Ok(EthCrossAccount { eth, sub })
}
@@ -479,18 +474,16 @@
impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
- impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>
+ impl<$($ident),+> AbiRead for ($($ident,)+)
where
- $(
- Self: AbiRead<$ident>,
- )+
+ $($ident: AbiRead,)+
($($ident,)+): TypeHelper,
{
- fn abi_read(&mut self) -> Result<($($ident,)+)> {
+ fn abi_read(reader: &mut AbiReader) -> Result<($($ident,)+)> {
let size = if !<($($ident,)+)>::is_dynamic() { Some(<($($ident,)+)>::size()) } else { None };
- let mut subresult = self.subresult(size)?;
+ let mut subresult = reader.subresult(size)?;
Ok((
- $(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+
+ $(<$ident>::abi_read(&mut subresult)?,)+
))
}
}
@@ -683,7 +676,7 @@
let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
assert_eq!(call, u32::to_be_bytes(function_identifier));
- let data = <AbiReader<'_> as AbiRead<$type>>::abi_read(&mut decoder).unwrap();
+ let data = <$type>::abi_read(&mut decoder).unwrap();
assert_eq!(data, decoded_data);
let mut writer = AbiWriter::new_call(function_identifier);
@@ -889,8 +882,7 @@
let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
assert_eq!(call, u32::to_be_bytes(decoded_data.0));
let address = decoder.address().unwrap();
- let data =
- <AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();
+ let data = <Vec<(uint256, string)>>::abi_read(&mut decoder).unwrap();
assert_eq!(data, decoded_data.1);
let mut writer = AbiWriter::new_call(decoded_data.0);
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -341,7 +341,7 @@
return Ok(None);
}
Ok(Some(Self::SupportsInterface {
- interface_id: input.abi_read()?,
+ interface_id: types::bytes4::abi_read(input)?,
}))
}
}
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.1819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23 weight,24 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},25 make_signature,26};27pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use up_data_structs::{31 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,32 SponsoringRateLimit, SponsorshipState,33};34use alloc::format;3536use crate::{37 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,38 eth::{convert_cross_account_to_uint256, convert_tuple_to_cross_account},39 weights::WeightInfo,40};4142/// Events for ethereum collection helper.43#[derive(ToLog)]44pub enum CollectionHelpersEvents {45 /// The collection has been created.46 CollectionCreated {47 /// Collection owner.48 #[indexed]49 owner: address,5051 /// Collection ID.52 #[indexed]53 collection_id: address,54 },55 /// The collection has been destroyed.56 CollectionDestroyed {57 /// Collection ID.58 #[indexed]59 collection_id: address,60 },61}6263/// Does not always represent a full collection, for RFT it is either64/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).65pub trait CommonEvmHandler {66 const CODE: &'static [u8];6768 /// Call precompiled handle.69 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;70}7172/// @title A contract that allows you to work with collections.73#[solidity_interface(name = Collection)]74impl<T: Config> CollectionHandle<T>75where76 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,77{78 /// Set collection property.79 ///80 /// @param key Property key.81 /// @param value Propery value.82 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]83 fn set_collection_property(84 &mut self,85 caller: caller,86 key: string,87 value: bytes,88 ) -> Result<void> {89 let caller = T::CrossAccountId::from_eth(caller);90 let key = <Vec<u8>>::from(key)91 .try_into()92 .map_err(|_| "key too large")?;93 let value = value.0.try_into().map_err(|_| "value too large")?;9495 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })96 .map_err(dispatch_to_evm::<T>)97 }9899 /// Set collection properties.100 ///101 /// @param properties Vector of properties key/value pair.102 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]103 fn set_collection_properties(104 &mut self,105 caller: caller,106 properties: Vec<(string, bytes)>,107 ) -> Result<void> {108 let caller = T::CrossAccountId::from_eth(caller);109110 let properties = properties111 .into_iter()112 .map(|(key, value)| {113 let key = <Vec<u8>>::from(key)114 .try_into()115 .map_err(|_| "key too large")?;116117 let value = value.0.try_into().map_err(|_| "value too large")?;118119 Ok(Property { key, value })120 })121 .collect::<Result<Vec<_>>>()?;122123 <Pallet<T>>::set_collection_properties(self, &caller, properties)124 .map_err(dispatch_to_evm::<T>)125 }126127 /// Delete collection property.128 ///129 /// @param key Property key.130 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]131 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let key = <Vec<u8>>::from(key)134 .try_into()135 .map_err(|_| "key too large")?;136137 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)138 }139140 /// Delete collection properties.141 ///142 /// @param keys Properties keys.143 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]144 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {145 let caller = T::CrossAccountId::from_eth(caller);146 let keys = keys147 .into_iter()148 .map(|key| {149 <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| Error::Revert("key too large".into()))152 })153 .collect::<Result<Vec<_>>>()?;154155 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)156 }157158 /// Get collection property.159 ///160 /// @dev Throws error if key not found.161 ///162 /// @param key Property key.163 /// @return bytes The property corresponding to the key.164 fn collection_property(&self, key: string) -> Result<bytes> {165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too large")?;168169 let props = CollectionProperties::<T>::get(self.id);170 let prop = props.get(&key).ok_or("key not found")?;171172 Ok(bytes(prop.to_vec()))173 }174175 /// Get collection properties.176 ///177 /// @param keys Properties keys. Empty keys for all propertyes.178 /// @return Vector of properties key/value pairs.179 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {180 let keys = keys181 .into_iter()182 .map(|key| {183 <Vec<u8>>::from(key)184 .try_into()185 .map_err(|_| Error::Revert("key too large".into()))186 })187 .collect::<Result<Vec<_>>>()?;188189 let properties = Pallet::<T>::filter_collection_properties(190 self.id,191 if keys.is_empty() { None } else { Some(keys) },192 )193 .map_err(dispatch_to_evm::<T>)?;194195 let properties = properties196 .into_iter()197 .map(|p| {198 let key =199 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;200 let value = bytes(p.value.to_vec());201 Ok((key, value))202 })203 .collect::<Result<Vec<_>>>()?;204 Ok(properties)205 }206207 /// Set the sponsor of the collection.208 ///209 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.210 ///211 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.212 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {213 self.consume_store_reads_and_writes(1, 1)?;214215 check_is_owner_or_admin(caller, self)?;216217 let sponsor = T::CrossAccountId::from_eth(sponsor);218 self.set_sponsor(sponsor.as_sub().clone())219 .map_err(dispatch_to_evm::<T>)?;220 save(self)221 }222223 /// Set the sponsor of the collection.224 ///225 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.226 ///227 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.228 fn set_collection_sponsor_cross(229 &mut self,230 caller: caller,231 sponsor: EthCrossAccount,232 ) -> Result<void> {233 self.consume_store_reads_and_writes(1, 1)?;234235 check_is_owner_or_admin(caller, self)?;236237 let sponsor = sponsor.into_sub_cross_account::<T>()?;238 self.set_sponsor(sponsor.as_sub().clone())239 .map_err(dispatch_to_evm::<T>)?;240 save(self)241 }242243 /// Whether there is a pending sponsor.244 fn has_collection_pending_sponsor(&self) -> Result<bool> {245 Ok(matches!(246 self.collection.sponsorship,247 SponsorshipState::Unconfirmed(_)248 ))249 }250251 /// Collection sponsorship confirmation.252 ///253 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.254 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {255 self.consume_store_writes(1)?;256257 let caller = T::CrossAccountId::from_eth(caller);258 if !self259 .confirm_sponsorship(caller.as_sub())260 .map_err(dispatch_to_evm::<T>)?261 {262 return Err("caller is not set as sponsor".into());263 }264 save(self)265 }266267 /// Remove collection sponsor.268 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {269 self.consume_store_reads_and_writes(1, 1)?;270 check_is_owner_or_admin(caller, self)?;271 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;272 save(self)273 }274275 /// Get current sponsor.276 ///277 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.278 fn collection_sponsor(&self) -> Result<(address, uint256)> {279 let sponsor = match self.collection.sponsorship.sponsor() {280 Some(sponsor) => sponsor,281 None => return Ok(Default::default()),282 };283 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());284 let result: (address, uint256) = if sponsor.is_canonical_substrate() {285 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);286 (Default::default(), sponsor)287 } else {288 let sponsor = *sponsor.as_eth();289 (sponsor, Default::default())290 };291 Ok(result)292 }293294 /// Set limits for the collection.295 /// @dev Throws error if limit not found.296 /// @param limit Name of the limit. Valid names:297 /// "accountTokenOwnershipLimit",298 /// "sponsoredDataSize",299 /// "sponsoredDataRateLimit",300 /// "tokenLimit",301 /// "sponsorTransferTimeout",302 /// "sponsorApproveTimeout"303 /// @param value Value of the limit.304 #[solidity(rename_selector = "setCollectionLimit")]305 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {306 self.consume_store_reads_and_writes(1, 1)?;307308 check_is_owner_or_admin(caller, self)?;309 let mut limits = self.limits.clone();310311 match limit.as_str() {312 "accountTokenOwnershipLimit" => {313 limits.account_token_ownership_limit = Some(value);314 }315 "sponsoredDataSize" => {316 limits.sponsored_data_size = Some(value);317 }318 "sponsoredDataRateLimit" => {319 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));320 }321 "tokenLimit" => {322 limits.token_limit = Some(value);323 }324 "sponsorTransferTimeout" => {325 limits.sponsor_transfer_timeout = Some(value);326 }327 "sponsorApproveTimeout" => {328 limits.sponsor_approve_timeout = Some(value);329 }330 _ => {331 return Err(Error::Revert(format!(332 "unknown integer limit \"{}\"",333 limit334 )))335 }336 }337 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)338 .map_err(dispatch_to_evm::<T>)?;339 save(self)340 }341342 /// Set limits for the collection.343 /// @dev Throws error if limit not found.344 /// @param limit Name of the limit. Valid names:345 /// "ownerCanTransfer",346 /// "ownerCanDestroy",347 /// "transfersEnabled"348 /// @param value Value of the limit.349 #[solidity(rename_selector = "setCollectionLimit")]350 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {351 self.consume_store_reads_and_writes(1, 1)?;352353 check_is_owner_or_admin(caller, self)?;354 let mut limits = self.limits.clone();355356 match limit.as_str() {357 "ownerCanTransfer" => {358 limits.owner_can_transfer = Some(value);359 }360 "ownerCanDestroy" => {361 limits.owner_can_destroy = Some(value);362 }363 "transfersEnabled" => {364 limits.transfers_enabled = Some(value);365 }366 _ => {367 return Err(Error::Revert(format!(368 "unknown boolean limit \"{}\"",369 limit370 )))371 }372 }373 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)374 .map_err(dispatch_to_evm::<T>)?;375 save(self)376 }377378 /// Get contract address.379 fn contract_address(&self) -> Result<address> {380 Ok(crate::eth::collection_id_to_address(self.id))381 }382383 /// Add collection admin.384 /// @param newAdmin Cross account administrator address.385 fn add_collection_admin_cross(386 &mut self,387 caller: caller,388 new_admin: EthCrossAccount,389 ) -> Result<void> {390 self.consume_store_writes(2)?;391392 let caller = T::CrossAccountId::from_eth(caller);393 let new_admin = new_admin.into_sub_cross_account::<T>()?;394 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;395 Ok(())396 }397398 /// Remove collection admin.399 /// @param admin Cross account administrator address.400 fn remove_collection_admin_cross(401 &mut self,402 caller: caller,403 admin: EthCrossAccount,404 ) -> Result<void> {405 self.consume_store_writes(2)?;406407 let caller = T::CrossAccountId::from_eth(caller);408 let admin = admin.into_sub_cross_account::<T>()?;409 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;410 Ok(())411 }412413 /// Add collection admin.414 /// @param newAdmin Address of the added administrator.415 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {416 self.consume_store_writes(2)?;417418 let caller = T::CrossAccountId::from_eth(caller);419 let new_admin = T::CrossAccountId::from_eth(new_admin);420 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;421 Ok(())422 }423424 /// Remove collection admin.425 ///426 /// @param admin Address of the removed administrator.427 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {428 self.consume_store_writes(2)?;429430 let caller = T::CrossAccountId::from_eth(caller);431 let admin = T::CrossAccountId::from_eth(admin);432 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;433 Ok(())434 }435436 /// Toggle accessibility of collection nesting.437 ///438 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'439 #[solidity(rename_selector = "setCollectionNesting")]440 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {441 self.consume_store_reads_and_writes(1, 1)?;442443 check_is_owner_or_admin(caller, self)?;444445 let mut permissions = self.collection.permissions.clone();446 let mut nesting = permissions.nesting().clone();447 nesting.token_owner = enable;448 nesting.restricted = None;449 permissions.nesting = Some(nesting);450451 self.collection.permissions = <Pallet<T>>::clamp_permissions(452 self.collection.mode.clone(),453 &self.collection.permissions,454 permissions,455 )456 .map_err(dispatch_to_evm::<T>)?;457458 save(self)459 }460461 /// Toggle accessibility of collection nesting.462 ///463 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'464 /// @param collections Addresses of collections that will be available for nesting.465 #[solidity(rename_selector = "setCollectionNesting")]466 fn set_nesting(467 &mut self,468 caller: caller,469 enable: bool,470 collections: Vec<address>,471 ) -> Result<void> {472 self.consume_store_reads_and_writes(1, 1)?;473474 if collections.is_empty() {475 return Err("no addresses provided".into());476 }477 check_is_owner_or_admin(caller, self)?;478479 let mut permissions = self.collection.permissions.clone();480 match enable {481 false => {482 let mut nesting = permissions.nesting().clone();483 nesting.token_owner = false;484 nesting.restricted = None;485 permissions.nesting = Some(nesting);486 }487 true => {488 let mut bv = OwnerRestrictedSet::new();489 for i in collections {490 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {491 Error::Revert("Can't convert address into collection id".into())492 })?)493 .map_err(|_| "too many collections")?;494 }495 let mut nesting = permissions.nesting().clone();496 nesting.token_owner = true;497 nesting.restricted = Some(bv);498 permissions.nesting = Some(nesting);499 }500 };501502 self.collection.permissions = <Pallet<T>>::clamp_permissions(503 self.collection.mode.clone(),504 &self.collection.permissions,505 permissions,506 )507 .map_err(dispatch_to_evm::<T>)?;508509 save(self)510 }511512 /// Set the collection access method.513 /// @param mode Access mode514 /// 0 for Normal515 /// 1 for AllowList516 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {517 self.consume_store_reads_and_writes(1, 1)?;518519 check_is_owner_or_admin(caller, self)?;520 let permissions = CollectionPermissions {521 access: Some(match mode {522 0 => AccessMode::Normal,523 1 => AccessMode::AllowList,524 _ => return Err("not supported access mode".into()),525 }),526 ..Default::default()527 };528 self.collection.permissions = <Pallet<T>>::clamp_permissions(529 self.collection.mode.clone(),530 &self.collection.permissions,531 permissions,532 )533 .map_err(dispatch_to_evm::<T>)?;534535 save(self)536 }537538 /// Checks that user allowed to operate with collection.539 ///540 /// @param user User address to check.541 fn allowed(&self, user: address) -> Result<bool> {542 Ok(Pallet::<T>::allowed(543 self.id,544 T::CrossAccountId::from_eth(user),545 ))546 }547548 /// Add the user to the allowed list.549 ///550 /// @param user Address of a trusted user.551 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {552 self.consume_store_writes(1)?;553554 let caller = T::CrossAccountId::from_eth(caller);555 let user = T::CrossAccountId::from_eth(user);556 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;557 Ok(())558 }559560 /// Add user to allowed list.561 ///562 /// @param user User cross account address.563 fn add_to_collection_allow_list_cross(564 &mut self,565 caller: caller,566 user: EthCrossAccount,567 ) -> Result<void> {568 self.consume_store_writes(1)?;569570 let caller = T::CrossAccountId::from_eth(caller);571 let user = user.into_sub_cross_account::<T>()?;572 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;573 Ok(())574 }575576 /// Remove the user from the allowed list.577 ///578 /// @param user Address of a removed user.579 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {580 self.consume_store_writes(1)?;581582 let caller = T::CrossAccountId::from_eth(caller);583 let user = T::CrossAccountId::from_eth(user);584 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;585 Ok(())586 }587588 /// Remove user from allowed list.589 ///590 /// @param user User cross account address.591 fn remove_from_collection_allow_list_cross(592 &mut self,593 caller: caller,594 user: EthCrossAccount,595 ) -> Result<void> {596 self.consume_store_writes(1)?;597598 let caller = T::CrossAccountId::from_eth(caller);599 let user = user.into_sub_cross_account::<T>()?;600 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;601 Ok(())602 }603604 /// Switch permission for minting.605 ///606 /// @param mode Enable if "true".607 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {608 self.consume_store_reads_and_writes(1, 1)?;609610 check_is_owner_or_admin(caller, self)?;611 let permissions = CollectionPermissions {612 mint_mode: Some(mode),613 ..Default::default()614 };615 self.collection.permissions = <Pallet<T>>::clamp_permissions(616 self.collection.mode.clone(),617 &self.collection.permissions,618 permissions,619 )620 .map_err(dispatch_to_evm::<T>)?;621622 save(self)623 }624625 /// Check that account is the owner or admin of the collection626 ///627 /// @param user account to verify628 /// @return "true" if account is the owner or admin629 #[solidity(rename_selector = "isOwnerOrAdmin")]630 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {631 let user = T::CrossAccountId::from_eth(user);632 Ok(self.is_owner_or_admin(&user))633 }634635 /// Check that account is the owner or admin of the collection636 ///637 /// @param user User cross account to verify638 /// @return "true" if account is the owner or admin639 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {640 let user = user.into_sub_cross_account::<T>()?;641 Ok(self.is_owner_or_admin(&user))642 }643644 /// Returns collection type645 ///646 /// @return `Fungible` or `NFT` or `ReFungible`647 fn unique_collection_type(&self) -> Result<string> {648 let mode = match self.collection.mode {649 CollectionMode::Fungible(_) => "Fungible",650 CollectionMode::NFT => "NFT",651 CollectionMode::ReFungible => "ReFungible",652 };653 Ok(mode.into())654 }655656 /// Get collection owner.657 ///658 /// @return Tuble with sponsor address and his substrate mirror.659 /// If address is canonical then substrate mirror is zero and vice versa.660 fn collection_owner(&self) -> Result<EthCrossAccount> {661 Ok(EthCrossAccount::from_sub_cross_account::<T>(662 &T::CrossAccountId::from_sub(self.owner.clone()),663 ))664 }665666 /// Changes collection owner to another account667 ///668 /// @dev Owner can be changed only by current owner669 /// @param newOwner new owner account670 #[solidity(rename_selector = "changeCollectionOwner")]671 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {672 self.consume_store_writes(1)?;673674 let caller = T::CrossAccountId::from_eth(caller);675 let new_owner = T::CrossAccountId::from_eth(new_owner);676 self.set_owner_internal(caller, new_owner)677 .map_err(dispatch_to_evm::<T>)678 }679680 /// Get collection administrators681 ///682 /// @return Vector of tuples with admins address and his substrate mirror.683 /// If address is canonical then substrate mirror is zero and vice versa.684 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {685 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))686 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))687 .collect();688 Ok(result)689 }690691 /// Changes collection owner to another account692 ///693 /// @dev Owner can be changed only by current owner694 /// @param newOwner new owner cross account695 fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {696 self.consume_store_writes(1)?;697698 let caller = T::CrossAccountId::from_eth(caller);699 let new_owner = new_owner.into_sub_cross_account::<T>()?;700 self.set_owner_internal(caller, new_owner)701 .map_err(dispatch_to_evm::<T>)702 }703}704705/// ### Note706/// Do not forget to add: `self.consume_store_reads(1)?;`707fn check_is_owner_or_admin<T: Config>(708 caller: caller,709 collection: &CollectionHandle<T>,710) -> Result<T::CrossAccountId> {711 let caller = T::CrossAccountId::from_eth(caller);712 collection713 .check_is_owner_or_admin(&caller)714 .map_err(dispatch_to_evm::<T>)?;715 Ok(caller)716}717718/// ### Note719/// Do not forget to add: `self.consume_store_writes(1)?;`720fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {721 collection722 .check_is_internal()723 .map_err(dispatch_to_evm::<T>)?;724 collection.save().map_err(dispatch_to_evm::<T>)?;725 Ok(())726}727728/// Contains static property keys and values.729pub mod static_property {730 use evm_coder::{731 execution::{Result, Error},732 };733 use alloc::format;734735 const EXPECT_CONVERT_ERROR: &str = "length < limit";736737 /// Keys.738 pub mod key {739 use super::*;740741 /// Key "baseURI".742 pub fn base_uri() -> up_data_structs::PropertyKey {743 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)744 }745746 /// Key "url".747 pub fn url() -> up_data_structs::PropertyKey {748 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)749 }750751 /// Key "suffix".752 pub fn suffix() -> up_data_structs::PropertyKey {753 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)754 }755756 /// Key "parentNft".757 pub fn parent_nft() -> up_data_structs::PropertyKey {758 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)759 }760 }761762 /// Convert `byte` to [`PropertyKey`].763 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {764 bytes.to_vec().try_into().map_err(|_| {765 Error::Revert(format!(766 "Property key is too long. Max length is {}.",767 up_data_structs::PropertyKey::bound()768 ))769 })770 }771772 /// Convert `bytes` to [`PropertyValue`].773 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {774 bytes.to_vec().try_into().map_err(|_| {775 Error::Revert(format!(776 "Property key is too long. Max length is {}.",777 up_data_structs::PropertyKey::bound()778 ))779 })780 }781}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.1819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23 weight,24 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},25 make_signature,26};27pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use up_data_structs::{31 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,32 SponsoringRateLimit, SponsorshipState,33};34use alloc::format;3536use crate::{37 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,38 eth::convert_cross_account_to_uint256, 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}6162/// Does not always represent a full collection, for RFT it is either63/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).64pub trait CommonEvmHandler {65 const CODE: &'static [u8];6667 /// Call precompiled handle.68 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;69}7071/// @title A contract that allows you to work with collections.72#[solidity_interface(name = Collection)]73impl<T: Config> CollectionHandle<T>74where75 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,76{77 /// Set collection property.78 ///79 /// @param key Property key.80 /// @param value Propery value.81 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]82 fn set_collection_property(83 &mut self,84 caller: caller,85 key: string,86 value: bytes,87 ) -> Result<void> {88 let caller = T::CrossAccountId::from_eth(caller);89 let key = <Vec<u8>>::from(key)90 .try_into()91 .map_err(|_| "key too large")?;92 let value = value.0.try_into().map_err(|_| "value too large")?;9394 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })95 .map_err(dispatch_to_evm::<T>)96 }9798 /// Set collection properties.99 ///100 /// @param properties Vector of properties key/value pair.101 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]102 fn set_collection_properties(103 &mut self,104 caller: caller,105 properties: Vec<(string, bytes)>,106 ) -> Result<void> {107 let caller = T::CrossAccountId::from_eth(caller);108109 let properties = properties110 .into_iter()111 .map(|(key, value)| {112 let key = <Vec<u8>>::from(key)113 .try_into()114 .map_err(|_| "key too large")?;115116 let value = value.0.try_into().map_err(|_| "value too large")?;117118 Ok(Property { key, value })119 })120 .collect::<Result<Vec<_>>>()?;121122 <Pallet<T>>::set_collection_properties(self, &caller, properties)123 .map_err(dispatch_to_evm::<T>)124 }125126 /// Delete collection property.127 ///128 /// @param key Property key.129 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]130 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {131 let caller = T::CrossAccountId::from_eth(caller);132 let key = <Vec<u8>>::from(key)133 .try_into()134 .map_err(|_| "key too large")?;135136 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)137 }138139 /// Delete collection properties.140 ///141 /// @param keys Properties keys.142 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]143 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {144 let caller = T::CrossAccountId::from_eth(caller);145 let keys = keys146 .into_iter()147 .map(|key| {148 <Vec<u8>>::from(key)149 .try_into()150 .map_err(|_| Error::Revert("key too large".into()))151 })152 .collect::<Result<Vec<_>>>()?;153154 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)155 }156157 /// Get collection property.158 ///159 /// @dev Throws error if key not found.160 ///161 /// @param key Property key.162 /// @return bytes The property corresponding to the key.163 fn collection_property(&self, key: string) -> Result<bytes> {164 let key = <Vec<u8>>::from(key)165 .try_into()166 .map_err(|_| "key too large")?;167168 let props = CollectionProperties::<T>::get(self.id);169 let prop = props.get(&key).ok_or("key not found")?;170171 Ok(bytes(prop.to_vec()))172 }173174 /// Get collection properties.175 ///176 /// @param keys Properties keys. Empty keys for all propertyes.177 /// @return Vector of properties key/value pairs.178 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {179 let keys = keys180 .into_iter()181 .map(|key| {182 <Vec<u8>>::from(key)183 .try_into()184 .map_err(|_| Error::Revert("key too large".into()))185 })186 .collect::<Result<Vec<_>>>()?;187188 let properties = Pallet::<T>::filter_collection_properties(189 self.id,190 if keys.is_empty() { None } else { Some(keys) },191 )192 .map_err(dispatch_to_evm::<T>)?;193194 let properties = properties195 .into_iter()196 .map(|p| {197 let key =198 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;199 let value = bytes(p.value.to_vec());200 Ok((key, value))201 })202 .collect::<Result<Vec<_>>>()?;203 Ok(properties)204 }205206 /// Set the sponsor of the collection.207 ///208 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.209 ///210 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.211 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {212 self.consume_store_reads_and_writes(1, 1)?;213214 check_is_owner_or_admin(caller, self)?;215216 let sponsor = T::CrossAccountId::from_eth(sponsor);217 self.set_sponsor(sponsor.as_sub().clone())218 .map_err(dispatch_to_evm::<T>)?;219 save(self)220 }221222 /// Set the sponsor of the collection.223 ///224 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.225 ///226 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.227 fn set_collection_sponsor_cross(228 &mut self,229 caller: caller,230 sponsor: EthCrossAccount,231 ) -> Result<void> {232 self.consume_store_reads_and_writes(1, 1)?;233234 check_is_owner_or_admin(caller, self)?;235236 let sponsor = sponsor.into_sub_cross_account::<T>()?;237 self.set_sponsor(sponsor.as_sub().clone())238 .map_err(dispatch_to_evm::<T>)?;239 save(self)240 }241242 /// Whether there is a pending sponsor.243 fn has_collection_pending_sponsor(&self) -> Result<bool> {244 Ok(matches!(245 self.collection.sponsorship,246 SponsorshipState::Unconfirmed(_)247 ))248 }249250 /// Collection sponsorship confirmation.251 ///252 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.253 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {254 self.consume_store_writes(1)?;255256 let caller = T::CrossAccountId::from_eth(caller);257 if !self258 .confirm_sponsorship(caller.as_sub())259 .map_err(dispatch_to_evm::<T>)?260 {261 return Err("caller is not set as sponsor".into());262 }263 save(self)264 }265266 /// Remove collection sponsor.267 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {268 self.consume_store_reads_and_writes(1, 1)?;269 check_is_owner_or_admin(caller, self)?;270 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;271 save(self)272 }273274 /// Get current sponsor.275 ///276 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.277 fn collection_sponsor(&self) -> Result<(address, uint256)> {278 let sponsor = match self.collection.sponsorship.sponsor() {279 Some(sponsor) => sponsor,280 None => return Ok(Default::default()),281 };282 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());283 let result: (address, uint256) = if sponsor.is_canonical_substrate() {284 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);285 (Default::default(), sponsor)286 } else {287 let sponsor = *sponsor.as_eth();288 (sponsor, Default::default())289 };290 Ok(result)291 }292293 /// Set limits for the collection.294 /// @dev Throws error if limit not found.295 /// @param limit Name of the limit. Valid names:296 /// "accountTokenOwnershipLimit",297 /// "sponsoredDataSize",298 /// "sponsoredDataRateLimit",299 /// "tokenLimit",300 /// "sponsorTransferTimeout",301 /// "sponsorApproveTimeout"302 /// @param value Value of the limit.303 #[solidity(rename_selector = "setCollectionLimit")]304 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {305 self.consume_store_reads_and_writes(1, 1)?;306307 check_is_owner_or_admin(caller, self)?;308 let mut limits = self.limits.clone();309310 match limit.as_str() {311 "accountTokenOwnershipLimit" => {312 limits.account_token_ownership_limit = Some(value);313 }314 "sponsoredDataSize" => {315 limits.sponsored_data_size = Some(value);316 }317 "sponsoredDataRateLimit" => {318 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));319 }320 "tokenLimit" => {321 limits.token_limit = Some(value);322 }323 "sponsorTransferTimeout" => {324 limits.sponsor_transfer_timeout = Some(value);325 }326 "sponsorApproveTimeout" => {327 limits.sponsor_approve_timeout = Some(value);328 }329 _ => {330 return Err(Error::Revert(format!(331 "unknown integer limit \"{}\"",332 limit333 )))334 }335 }336 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)337 .map_err(dispatch_to_evm::<T>)?;338 save(self)339 }340341 /// Set limits for the collection.342 /// @dev Throws error if limit not found.343 /// @param limit Name of the limit. Valid names:344 /// "ownerCanTransfer",345 /// "ownerCanDestroy",346 /// "transfersEnabled"347 /// @param value Value of the limit.348 #[solidity(rename_selector = "setCollectionLimit")]349 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {350 self.consume_store_reads_and_writes(1, 1)?;351352 check_is_owner_or_admin(caller, self)?;353 let mut limits = self.limits.clone();354355 match limit.as_str() {356 "ownerCanTransfer" => {357 limits.owner_can_transfer = Some(value);358 }359 "ownerCanDestroy" => {360 limits.owner_can_destroy = Some(value);361 }362 "transfersEnabled" => {363 limits.transfers_enabled = Some(value);364 }365 _ => {366 return Err(Error::Revert(format!(367 "unknown boolean limit \"{}\"",368 limit369 )))370 }371 }372 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)373 .map_err(dispatch_to_evm::<T>)?;374 save(self)375 }376377 /// Get contract address.378 fn contract_address(&self) -> Result<address> {379 Ok(crate::eth::collection_id_to_address(self.id))380 }381382 /// Add collection admin.383 /// @param newAdmin Cross account administrator address.384 fn add_collection_admin_cross(385 &mut self,386 caller: caller,387 new_admin: EthCrossAccount,388 ) -> Result<void> {389 self.consume_store_writes(2)?;390391 let caller = T::CrossAccountId::from_eth(caller);392 let new_admin = new_admin.into_sub_cross_account::<T>()?;393 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;394 Ok(())395 }396397 /// Remove collection admin.398 /// @param admin Cross account administrator address.399 fn remove_collection_admin_cross(400 &mut self,401 caller: caller,402 admin: EthCrossAccount,403 ) -> Result<void> {404 self.consume_store_writes(2)?;405406 let caller = T::CrossAccountId::from_eth(caller);407 let admin = admin.into_sub_cross_account::<T>()?;408 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;409 Ok(())410 }411412 /// Add collection admin.413 /// @param newAdmin Address of the added administrator.414 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {415 self.consume_store_writes(2)?;416417 let caller = T::CrossAccountId::from_eth(caller);418 let new_admin = T::CrossAccountId::from_eth(new_admin);419 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;420 Ok(())421 }422423 /// Remove collection admin.424 ///425 /// @param admin Address of the removed administrator.426 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {427 self.consume_store_writes(2)?;428429 let caller = T::CrossAccountId::from_eth(caller);430 let admin = T::CrossAccountId::from_eth(admin);431 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;432 Ok(())433 }434435 /// Toggle accessibility of collection nesting.436 ///437 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'438 #[solidity(rename_selector = "setCollectionNesting")]439 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {440 self.consume_store_reads_and_writes(1, 1)?;441442 check_is_owner_or_admin(caller, self)?;443444 let mut permissions = self.collection.permissions.clone();445 let mut nesting = permissions.nesting().clone();446 nesting.token_owner = enable;447 nesting.restricted = None;448 permissions.nesting = Some(nesting);449450 self.collection.permissions = <Pallet<T>>::clamp_permissions(451 self.collection.mode.clone(),452 &self.collection.permissions,453 permissions,454 )455 .map_err(dispatch_to_evm::<T>)?;456457 save(self)458 }459460 /// Toggle accessibility of collection nesting.461 ///462 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'463 /// @param collections Addresses of collections that will be available for nesting.464 #[solidity(rename_selector = "setCollectionNesting")]465 fn set_nesting(466 &mut self,467 caller: caller,468 enable: bool,469 collections: Vec<address>,470 ) -> Result<void> {471 self.consume_store_reads_and_writes(1, 1)?;472473 if collections.is_empty() {474 return Err("no addresses provided".into());475 }476 check_is_owner_or_admin(caller, self)?;477478 let mut permissions = self.collection.permissions.clone();479 match enable {480 false => {481 let mut nesting = permissions.nesting().clone();482 nesting.token_owner = false;483 nesting.restricted = None;484 permissions.nesting = Some(nesting);485 }486 true => {487 let mut bv = OwnerRestrictedSet::new();488 for i in collections {489 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {490 Error::Revert("Can't convert address into collection id".into())491 })?)492 .map_err(|_| "too many collections")?;493 }494 let mut nesting = permissions.nesting().clone();495 nesting.token_owner = true;496 nesting.restricted = Some(bv);497 permissions.nesting = Some(nesting);498 }499 };500501 self.collection.permissions = <Pallet<T>>::clamp_permissions(502 self.collection.mode.clone(),503 &self.collection.permissions,504 permissions,505 )506 .map_err(dispatch_to_evm::<T>)?;507508 save(self)509 }510511 /// Set the collection access method.512 /// @param mode Access mode513 /// 0 for Normal514 /// 1 for AllowList515 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {516 self.consume_store_reads_and_writes(1, 1)?;517518 check_is_owner_or_admin(caller, self)?;519 let permissions = CollectionPermissions {520 access: Some(match mode {521 0 => AccessMode::Normal,522 1 => AccessMode::AllowList,523 _ => return Err("not supported access mode".into()),524 }),525 ..Default::default()526 };527 self.collection.permissions = <Pallet<T>>::clamp_permissions(528 self.collection.mode.clone(),529 &self.collection.permissions,530 permissions,531 )532 .map_err(dispatch_to_evm::<T>)?;533534 save(self)535 }536537 /// Checks that user allowed to operate with collection.538 ///539 /// @param user User address to check.540 fn allowed(&self, user: address) -> Result<bool> {541 Ok(Pallet::<T>::allowed(542 self.id,543 T::CrossAccountId::from_eth(user),544 ))545 }546547 /// Add the user to the allowed list.548 ///549 /// @param user Address of a trusted user.550 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {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, true).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558559 /// Add user to allowed list.560 ///561 /// @param user User cross account address.562 fn add_to_collection_allow_list_cross(563 &mut self,564 caller: caller,565 user: EthCrossAccount,566 ) -> Result<void> {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, true).map_err(dispatch_to_evm::<T>)?;572 Ok(())573 }574575 /// Remove the user from the allowed list.576 ///577 /// @param user Address of a removed user.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: EthCrossAccount,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 check_is_owner_or_admin(caller, self)?;610 let permissions = CollectionPermissions {611 mint_mode: Some(mode),612 ..Default::default()613 };614 self.collection.permissions = <Pallet<T>>::clamp_permissions(615 self.collection.mode.clone(),616 &self.collection.permissions,617 permissions,618 )619 .map_err(dispatch_to_evm::<T>)?;620621 save(self)622 }623624 /// Check that account is the owner or admin of the collection625 ///626 /// @param user account to verify627 /// @return "true" if account is the owner or admin628 #[solidity(rename_selector = "isOwnerOrAdmin")]629 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {630 let user = T::CrossAccountId::from_eth(user);631 Ok(self.is_owner_or_admin(&user))632 }633634 /// Check that account is the owner or admin of the collection635 ///636 /// @param user User cross account to verify637 /// @return "true" if account is the owner or admin638 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {639 let user = user.into_sub_cross_account::<T>()?;640 Ok(self.is_owner_or_admin(&user))641 }642643 /// Returns collection type644 ///645 /// @return `Fungible` or `NFT` or `ReFungible`646 fn unique_collection_type(&self) -> Result<string> {647 let mode = match self.collection.mode {648 CollectionMode::Fungible(_) => "Fungible",649 CollectionMode::NFT => "NFT",650 CollectionMode::ReFungible => "ReFungible",651 };652 Ok(mode.into())653 }654655 /// Get collection owner.656 ///657 /// @return Tuble with sponsor address and his substrate mirror.658 /// If address is canonical then substrate mirror is zero and vice versa.659 fn collection_owner(&self) -> Result<EthCrossAccount> {660 Ok(EthCrossAccount::from_sub_cross_account::<T>(661 &T::CrossAccountId::from_sub(self.owner.clone()),662 ))663 }664665 /// Changes collection owner to another account666 ///667 /// @dev Owner can be changed only by current owner668 /// @param newOwner new owner account669 #[solidity(rename_selector = "changeCollectionOwner")]670 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {671 self.consume_store_writes(1)?;672673 let caller = T::CrossAccountId::from_eth(caller);674 let new_owner = T::CrossAccountId::from_eth(new_owner);675 self.set_owner_internal(caller, new_owner)676 .map_err(dispatch_to_evm::<T>)677 }678679 /// Get collection administrators680 ///681 /// @return Vector of tuples with admins address and his substrate mirror.682 /// If address is canonical then substrate mirror is zero and vice versa.683 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {684 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))685 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))686 .collect();687 Ok(result)688 }689690 /// Changes collection owner to another account691 ///692 /// @dev Owner can be changed only by current owner693 /// @param newOwner new owner cross account694 fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {695 self.consume_store_writes(1)?;696697 let caller = T::CrossAccountId::from_eth(caller);698 let new_owner = new_owner.into_sub_cross_account::<T>()?;699 self.set_owner_internal(caller, new_owner)700 .map_err(dispatch_to_evm::<T>)701 }702}703704/// ### Note705/// Do not forget to add: `self.consume_store_reads(1)?;`706fn check_is_owner_or_admin<T: Config>(707 caller: caller,708 collection: &CollectionHandle<T>,709) -> Result<T::CrossAccountId> {710 let caller = T::CrossAccountId::from_eth(caller);711 collection712 .check_is_owner_or_admin(&caller)713 .map_err(dispatch_to_evm::<T>)?;714 Ok(caller)715}716717/// ### Note718/// Do not forget to add: `self.consume_store_writes(1)?;`719fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {720 collection721 .check_is_internal()722 .map_err(dispatch_to_evm::<T>)?;723 collection.save().map_err(dispatch_to_evm::<T>)?;724 Ok(())725}726727/// Contains static property keys and values.728pub mod static_property {729 use evm_coder::{730 execution::{Result, Error},731 };732 use alloc::format;733734 const EXPECT_CONVERT_ERROR: &str = "length < limit";735736 /// Keys.737 pub mod key {738 use super::*;739740 /// Key "baseURI".741 pub fn base_uri() -> up_data_structs::PropertyKey {742 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)743 }744745 /// Key "url".746 pub fn url() -> up_data_structs::PropertyKey {747 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)748 }749750 /// Key "suffix".751 pub fn suffix() -> up_data_structs::PropertyKey {752 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)753 }754755 /// Key "parentNft".756 pub fn parent_nft() -> up_data_structs::PropertyKey {757 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)758 }759 }760761 /// Convert `byte` to [`PropertyKey`].762 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {763 bytes.to_vec().try_into().map_err(|_| {764 Error::Revert(format!(765 "Property key is too long. Max length is {}.",766 up_data_structs::PropertyKey::bound()767 ))768 })769 }770771 /// Convert `bytes` to [`PropertyValue`].772 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {773 bytes.to_vec().try_into().map_err(|_| {774 Error::Revert(format!(775 "Property key is too long. Max length is {}.",776 up_data_structs::PropertyKey::bound()777 ))778 })779 }780}