difftreelog
refactor Make implementations of Abi* for EthCrossAccount via AbiCoder macro
in: master
9 files changed
.maintain/scripts/generate_abi.shdiffbeforeafterboth--- a/.maintain/scripts/generate_abi.sh
+++ b/.maintain/scripts/generate_abi.sh
@@ -4,6 +4,7 @@
dir=$PWD
tmp=$(mktemp -d)
+echo "Tmp file: $tmp/input.sol"
cd $tmp
cp $dir/$INPUT input.sol
solcjs --abi -p input.sol
crates/evm-coder/src/abi/impls.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi/impls.rs
+++ b/crates/evm-coder/src/abi/impls.rs
@@ -120,42 +120,6 @@
}
}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
-
-impl AbiType for EthCrossAccount {
- const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)"));
-
- fn is_dynamic() -> bool {
- address::is_dynamic() || uint256::is_dynamic()
- }
-
- fn size() -> usize {
- <address as AbiType>::size() + <uint256 as AbiType>::size()
- }
-}
-
-impl AbiRead for EthCrossAccount {
- fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
- let size = if !EthCrossAccount::is_dynamic() {
- Some(<EthCrossAccount as AbiType>::size())
- } else {
- None
- };
- let mut subresult = reader.subresult(size)?;
- let eth = <address>::abi_read(&mut subresult)?;
- let sub = <uint256>::abi_read(&mut subresult)?;
-
- Ok(EthCrossAccount { eth, sub })
- }
-}
-
-impl AbiWrite for EthCrossAccount {
- fn abi_write(&self, writer: &mut AbiWriter) {
- self.eth.abi_write(writer);
- self.sub.abi_write(writer);
- }
-}
-
impl sealed::CanBePlacedInVec for Property {}
impl AbiType for Property {
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -93,6 +93,7 @@
pub use evm_coder_procedural::solidity;
/// See [`solidity_interface`]
pub use evm_coder_procedural::weight;
+pub use evm_coder_procedural::AbiCoder;
pub use sha3_const;
/// Derives [`ToLog`] for enum
@@ -119,7 +120,6 @@
#[cfg(not(feature = "std"))]
use alloc::{vec::Vec};
- use pallet_evm::account::CrossAccountId;
use primitive_types::{U256, H160, H256};
pub type address = H160;
@@ -185,73 +185,7 @@
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
- }
- }
-
- #[derive(Debug, Default)]
- pub struct EthCrossAccount {
- pub(crate) eth: address,
- pub(crate) sub: uint256,
- }
-
- impl EthCrossAccount {
- pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
- where
- T: pallet_evm::Config,
- T::AccountId: AsRef<[u8; 32]>,
- {
- if cross_account_id.is_canonical_substrate() {
- Self {
- eth: Default::default(),
- sub: convert_cross_account_to_uint256::<T>(cross_account_id),
- }
- } else {
- Self {
- eth: *cross_account_id.as_eth(),
- sub: Default::default(),
- }
- }
- }
-
- pub fn into_sub_cross_account<T>(&self) -> crate::execution::Result<T::CrossAccountId>
- where
- T: pallet_evm::Config,
- T::AccountId: From<[u8; 32]>,
- {
- if self.eth == Default::default() && self.sub == Default::default() {
- Err("All fields of cross account is zeroed".into())
- } else if self.eth == Default::default() {
- Ok(convert_uint256_to_cross_account::<T>(self.sub))
- } else if self.sub == Default::default() {
- Ok(T::CrossAccountId::from_eth(self.eth))
- } else {
- Err("All fields of cross account is non zeroed".into())
- }
}
- }
-
- /// Convert `CrossAccountId` to `uint256`.
- pub fn convert_cross_account_to_uint256<T: pallet_evm::Config>(
- from: &T::CrossAccountId,
- ) -> uint256
- where
- T::AccountId: AsRef<[u8; 32]>,
- {
- let slice = from.as_sub().as_ref();
- uint256::from_big_endian(slice)
- }
-
- /// Convert `uint256` to `CrossAccountId`.
- pub fn convert_uint256_to_cross_account<T: pallet_evm::Config>(
- from: uint256,
- ) -> T::CrossAccountId
- where
- T::AccountId: From<[u8; 32]>,
- {
- let mut new_admin_arr = [0_u8; 32];
- from.to_big_endian(&mut new_admin_arr);
- let account_id = T::AccountId::from(new_admin_arr);
- T::CrossAccountId::from_sub(account_id)
}
#[derive(Debug, Default)]
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -145,7 +145,7 @@
}
}
-mod sealed {
+pub mod sealed {
/// Not every type should be directly placed in vec.
/// Vec encoding is not memory efficient, as every item will be padded
/// to 32 bytes.
@@ -156,7 +156,6 @@
impl sealed::CanBePlacedInVec for uint256 {}
impl sealed::CanBePlacedInVec for string {}
impl sealed::CanBePlacedInVec for address {}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
impl sealed::CanBePlacedInVec for Property {}
impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
@@ -171,61 +170,6 @@
write!(writer, "new ")?;
T::solidity_name(writer, tc)?;
write!(writer, "[](0)")
- }
-}
-
-impl SolidityTupleType for EthCrossAccount {
- fn names(tc: &TypeCollector) -> Vec<string> {
- let mut collected = Vec::with_capacity(Self::len());
- {
- let mut out = string::new();
- address::solidity_name(&mut out, tc).expect("no fmt error");
- collected.push(out);
- }
- {
- let mut out = string::new();
- uint256::solidity_name(&mut out, tc).expect("no fmt error");
- collected.push(out);
- }
- collected
- }
-
- fn len() -> usize {
- 2
- }
-}
-
-impl SolidityTypeName for EthCrossAccount {
- fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}", tc.collect_struct::<Self>())
- }
-
- fn is_simple() -> bool {
- false
- }
-
- fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}(", tc.collect_struct::<Self>())?;
- address::solidity_default(writer, tc)?;
- write!(writer, ",")?;
- uint256::solidity_default(writer, tc)?;
- write!(writer, ")")
- }
-}
-
-impl StructCollect for EthCrossAccount {
- fn name() -> String {
- "EthCrossAccount".into()
- }
-
- fn declaration() -> String {
- let mut str = String::new();
- writeln!(str, "/// @dev Cross account struct").unwrap();
- writeln!(str, "struct {} {{", Self::name()).unwrap();
- writeln!(str, "\taddress eth;").unwrap();
- writeln!(str, "\tuint256 sub;").unwrap();
- writeln!(str, "}}").unwrap();
- str
}
}
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 abi::AbiType,21 solidity_interface, solidity, ToLog,22 types::*,23 types::Property as PropertyStruct,24 execution::{Result, Error},25 weight,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 /// Raw compiled binary code of the contract stub66 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 #[solidity(hide)]83 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]84 fn set_collection_property(85 &mut self,86 caller: caller,87 key: string,88 value: bytes,89 ) -> Result<void> {90 let caller = T::CrossAccountId::from_eth(caller);91 let key = <Vec<u8>>::from(key)92 .try_into()93 .map_err(|_| "key too large")?;94 let value = value.0.try_into().map_err(|_| "value too large")?;9596 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })97 .map_err(dispatch_to_evm::<T>)98 }99100 /// Set collection properties.101 ///102 /// @param properties Vector of properties key/value pair.103 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]104 fn set_collection_properties(105 &mut self,106 caller: caller,107 properties: Vec<PropertyStruct>,108 ) -> Result<void> {109 let caller = T::CrossAccountId::from_eth(caller);110111 let properties = properties112 .into_iter()113 .map(|PropertyStruct { key, value }| {114 let key = <Vec<u8>>::from(key)115 .try_into()116 .map_err(|_| "key too large")?;117118 let value = value.0.try_into().map_err(|_| "value too large")?;119120 Ok(Property { key, value })121 })122 .collect::<Result<Vec<_>>>()?;123124 <Pallet<T>>::set_collection_properties(self, &caller, properties)125 .map_err(dispatch_to_evm::<T>)126 }127128 /// Delete collection property.129 ///130 /// @param key Property key.131 #[solidity(hide)]132 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]133 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {134 let caller = T::CrossAccountId::from_eth(caller);135 let key = <Vec<u8>>::from(key)136 .try_into()137 .map_err(|_| "key too large")?;138139 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)140 }141142 /// Delete collection properties.143 ///144 /// @param keys Properties keys.145 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]146 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {147 let caller = T::CrossAccountId::from_eth(caller);148 let keys = keys149 .into_iter()150 .map(|key| {151 <Vec<u8>>::from(key)152 .try_into()153 .map_err(|_| Error::Revert("key too large".into()))154 })155 .collect::<Result<Vec<_>>>()?;156157 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)158 }159160 /// Get collection property.161 ///162 /// @dev Throws error if key not found.163 ///164 /// @param key Property key.165 /// @return bytes The property corresponding to the key.166 fn collection_property(&self, key: string) -> Result<bytes> {167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too large")?;170171 let props = CollectionProperties::<T>::get(self.id);172 let prop = props.get(&key).ok_or("key not found")?;173174 Ok(bytes(prop.to_vec()))175 }176177 /// Get collection properties.178 ///179 /// @param keys Properties keys. Empty keys for all propertyes.180 /// @return Vector of properties key/value pairs.181 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {182 let keys = keys183 .into_iter()184 .map(|key| {185 <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| Error::Revert("key too large".into()))188 })189 .collect::<Result<Vec<_>>>()?;190191 let properties = Pallet::<T>::filter_collection_properties(192 self.id,193 if keys.is_empty() { None } else { Some(keys) },194 )195 .map_err(dispatch_to_evm::<T>)?;196197 let properties = properties198 .into_iter()199 .map(|p| {200 let key =201 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;202 let value = bytes(p.value.to_vec());203 Ok(PropertyStruct { key, value })204 })205 .collect::<Result<Vec<_>>>()?;206 Ok(properties)207 }208209 /// Set the sponsor of the collection.210 ///211 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.212 ///213 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.214 #[solidity(hide)]215 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {216 self.consume_store_reads_and_writes(1, 1)?;217218 check_is_owner_or_admin(caller, self)?;219220 let sponsor = T::CrossAccountId::from_eth(sponsor);221 self.set_sponsor(sponsor.as_sub().clone())222 .map_err(dispatch_to_evm::<T>)?;223 save(self)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 Cross account address of the sponsor from whose account funds will be debited for operations with the contract.231 fn set_collection_sponsor_cross(232 &mut self,233 caller: caller,234 sponsor: EthCrossAccount,235 ) -> Result<void> {236 self.consume_store_reads_and_writes(1, 1)?;237238 check_is_owner_or_admin(caller, self)?;239240 let sponsor = sponsor.into_sub_cross_account::<T>()?;241 self.set_sponsor(sponsor.as_sub().clone())242 .map_err(dispatch_to_evm::<T>)?;243 save(self)244 }245246 /// Whether there is a pending sponsor.247 fn has_collection_pending_sponsor(&self) -> Result<bool> {248 Ok(matches!(249 self.collection.sponsorship,250 SponsorshipState::Unconfirmed(_)251 ))252 }253254 /// Collection sponsorship confirmation.255 ///256 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.257 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {258 self.consume_store_writes(1)?;259260 let caller = T::CrossAccountId::from_eth(caller);261 if !self262 .confirm_sponsorship(caller.as_sub())263 .map_err(dispatch_to_evm::<T>)?264 {265 return Err("caller is not set as sponsor".into());266 }267 save(self)268 }269270 /// Remove collection sponsor.271 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272 self.consume_store_reads_and_writes(1, 1)?;273 check_is_owner_or_admin(caller, self)?;274 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;275 save(self)276 }277278 /// Get current sponsor.279 ///280 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.281 fn collection_sponsor(&self) -> Result<(address, uint256)> {282 let sponsor = match self.collection.sponsorship.sponsor() {283 Some(sponsor) => sponsor,284 None => return Ok(Default::default()),285 };286 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());287 let result: (address, uint256) = if sponsor.is_canonical_substrate() {288 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);289 (Default::default(), sponsor)290 } else {291 let sponsor = *sponsor.as_eth();292 (sponsor, Default::default())293 };294 Ok(result)295 }296297 /// Set limits for the collection.298 /// @dev Throws error if limit not found.299 /// @param limit Name of the limit. Valid names:300 /// "accountTokenOwnershipLimit",301 /// "sponsoredDataSize",302 /// "sponsoredDataRateLimit",303 /// "tokenLimit",304 /// "sponsorTransferTimeout",305 /// "sponsorApproveTimeout"306 /// "ownerCanTransfer",307 /// "ownerCanDestroy",308 /// "transfersEnabled"309 /// @param value Value of the limit.310 #[solidity(rename_selector = "setCollectionLimit")]311 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {312 self.consume_store_reads_and_writes(1, 1)?;313314 let value = value315 .try_into()316 .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;317318 let convert_value_to_bool = || match value {319 0 => Ok(false),320 1 => Ok(true),321 _ => {322 return Err(Error::Revert(format!(323 "can't convert value to boolean \"{}\"",324 value325 )))326 }327 };328329 check_is_owner_or_admin(caller, self)?;330 let mut limits = self.limits.clone();331332 match limit.as_str() {333 "accountTokenOwnershipLimit" => {334 limits.account_token_ownership_limit = Some(value);335 }336 "sponsoredDataSize" => {337 limits.sponsored_data_size = Some(value);338 }339 "sponsoredDataRateLimit" => {340 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));341 }342 "tokenLimit" => {343 limits.token_limit = Some(value);344 }345 "sponsorTransferTimeout" => {346 limits.sponsor_transfer_timeout = Some(value);347 }348 "sponsorApproveTimeout" => {349 limits.sponsor_approve_timeout = Some(value);350 }351 "ownerCanTransfer" => {352 limits.owner_can_transfer = Some(convert_value_to_bool()?);353 }354 "ownerCanDestroy" => {355 limits.owner_can_destroy = Some(convert_value_to_bool()?);356 }357 "transfersEnabled" => {358 limits.transfers_enabled = Some(convert_value_to_bool()?);359 }360 _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),361 }362 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)363 .map_err(dispatch_to_evm::<T>)?;364 save(self)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: EthCrossAccount,378 ) -> Result<void> {379 self.consume_store_writes(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: EthCrossAccount,393 ) -> Result<void> {394 self.consume_store_writes(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_writes(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_writes(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 check_is_owner_or_admin(caller, self)?;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 self.collection.permissions = <Pallet<T>>::clamp_permissions(443 self.collection.mode.clone(),444 &self.collection.permissions,445 permissions,446 )447 .map_err(dispatch_to_evm::<T>)?;448449 save(self)450 }451452 /// Toggle accessibility of collection nesting.453 ///454 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'455 /// @param collections Addresses of collections that will be available for nesting.456 #[solidity(rename_selector = "setCollectionNesting")]457 fn set_nesting(458 &mut self,459 caller: caller,460 enable: bool,461 collections: Vec<address>,462 ) -> Result<void> {463 self.consume_store_reads_and_writes(1, 1)?;464465 if collections.is_empty() {466 return Err("no addresses provided".into());467 }468 check_is_owner_or_admin(caller, self)?;469470 let mut permissions = self.collection.permissions.clone();471 match enable {472 false => {473 let mut nesting = permissions.nesting().clone();474 nesting.token_owner = false;475 nesting.restricted = None;476 permissions.nesting = Some(nesting);477 }478 true => {479 let mut bv = OwnerRestrictedSet::new();480 for i in collections {481 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {482 Error::Revert("Can't convert address into collection id".into())483 })?)484 .map_err(|_| "too many collections")?;485 }486 let mut nesting = permissions.nesting().clone();487 nesting.token_owner = true;488 nesting.restricted = Some(bv);489 permissions.nesting = Some(nesting);490 }491 };492493 self.collection.permissions = <Pallet<T>>::clamp_permissions(494 self.collection.mode.clone(),495 &self.collection.permissions,496 permissions,497 )498 .map_err(dispatch_to_evm::<T>)?;499500 save(self)501 }502503 /// Set the collection access method.504 /// @param mode Access mode505 /// 0 for Normal506 /// 1 for AllowList507 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {508 self.consume_store_reads_and_writes(1, 1)?;509510 check_is_owner_or_admin(caller, self)?;511 let permissions = CollectionPermissions {512 access: Some(match mode {513 0 => AccessMode::Normal,514 1 => AccessMode::AllowList,515 _ => return Err("not supported access mode".into()),516 }),517 ..Default::default()518 };519 self.collection.permissions = <Pallet<T>>::clamp_permissions(520 self.collection.mode.clone(),521 &self.collection.permissions,522 permissions,523 )524 .map_err(dispatch_to_evm::<T>)?;525526 save(self)527 }528529 /// Checks that user allowed to operate with collection.530 ///531 /// @param user User address to check.532 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {533 let user = user.into_sub_cross_account::<T>()?;534 Ok(Pallet::<T>::allowed(self.id, user))535 }536537 /// Add the user to the allowed list.538 ///539 /// @param user Address of a trusted user.540 #[solidity(hide)]541 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {542 self.consume_store_writes(1)?;543544 let caller = T::CrossAccountId::from_eth(caller);545 let user = T::CrossAccountId::from_eth(user);546 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;547 Ok(())548 }549550 /// Add user to allowed list.551 ///552 /// @param user User cross account address.553 fn add_to_collection_allow_list_cross(554 &mut self,555 caller: caller,556 user: EthCrossAccount,557 ) -> Result<void> {558 self.consume_store_writes(1)?;559560 let caller = T::CrossAccountId::from_eth(caller);561 let user = user.into_sub_cross_account::<T>()?;562 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;563 Ok(())564 }565566 /// Remove the user from the allowed list.567 ///568 /// @param user Address of a removed user.569 #[solidity(hide)]570 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {571 self.consume_store_writes(1)?;572573 let caller = T::CrossAccountId::from_eth(caller);574 let user = T::CrossAccountId::from_eth(user);575 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;576 Ok(())577 }578579 /// Remove user from allowed list.580 ///581 /// @param user User cross account address.582 fn remove_from_collection_allow_list_cross(583 &mut self,584 caller: caller,585 user: EthCrossAccount,586 ) -> Result<void> {587 self.consume_store_writes(1)?;588589 let caller = T::CrossAccountId::from_eth(caller);590 let user = user.into_sub_cross_account::<T>()?;591 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;592 Ok(())593 }594595 /// Switch permission for minting.596 ///597 /// @param mode Enable if "true".598 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {599 self.consume_store_reads_and_writes(1, 1)?;600601 check_is_owner_or_admin(caller, self)?;602 let permissions = CollectionPermissions {603 mint_mode: Some(mode),604 ..Default::default()605 };606 self.collection.permissions = <Pallet<T>>::clamp_permissions(607 self.collection.mode.clone(),608 &self.collection.permissions,609 permissions,610 )611 .map_err(dispatch_to_evm::<T>)?;612613 save(self)614 }615616 /// Check that account is the owner or admin of the collection617 ///618 /// @param user account to verify619 /// @return "true" if account is the owner or admin620 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]621 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {622 let user = T::CrossAccountId::from_eth(user);623 Ok(self.is_owner_or_admin(&user))624 }625626 /// Check that account is the owner or admin of the collection627 ///628 /// @param user User cross account to verify629 /// @return "true" if account is the owner or admin630 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {631 let user = user.into_sub_cross_account::<T>()?;632 Ok(self.is_owner_or_admin(&user))633 }634635 /// Returns collection type636 ///637 /// @return `Fungible` or `NFT` or `ReFungible`638 fn unique_collection_type(&self) -> Result<string> {639 let mode = match self.collection.mode {640 CollectionMode::Fungible(_) => "Fungible",641 CollectionMode::NFT => "NFT",642 CollectionMode::ReFungible => "ReFungible",643 };644 Ok(mode.into())645 }646647 /// Get collection owner.648 ///649 /// @return Tuble with sponsor address and his substrate mirror.650 /// If address is canonical then substrate mirror is zero and vice versa.651 fn collection_owner(&self) -> Result<EthCrossAccount> {652 Ok(EthCrossAccount::from_sub_cross_account::<T>(653 &T::CrossAccountId::from_sub(self.owner.clone()),654 ))655 }656657 /// Changes collection owner to another account658 ///659 /// @dev Owner can be changed only by current owner660 /// @param newOwner new owner account661 #[solidity(hide, rename_selector = "changeCollectionOwner")]662 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {663 self.consume_store_writes(1)?;664665 let caller = T::CrossAccountId::from_eth(caller);666 let new_owner = T::CrossAccountId::from_eth(new_owner);667 self.set_owner_internal(caller, new_owner)668 .map_err(dispatch_to_evm::<T>)669 }670671 /// Get collection administrators672 ///673 /// @return Vector of tuples with admins address and his substrate mirror.674 /// If address is canonical then substrate mirror is zero and vice versa.675 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {676 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))677 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))678 .collect();679 Ok(result)680 }681682 /// Changes collection owner to another account683 ///684 /// @dev Owner can be changed only by current owner685 /// @param newOwner new owner cross account686 fn change_collection_owner_cross(687 &mut self,688 caller: caller,689 new_owner: EthCrossAccount,690 ) -> Result<void> {691 self.consume_store_writes(1)?;692693 let caller = T::CrossAccountId::from_eth(caller);694 let new_owner = new_owner.into_sub_cross_account::<T>()?;695 self.set_owner_internal(caller, new_owner)696 .map_err(dispatch_to_evm::<T>)697 }698}699700/// ### Note701/// Do not forget to add: `self.consume_store_reads(1)?;`702fn check_is_owner_or_admin<T: Config>(703 caller: caller,704 collection: &CollectionHandle<T>,705) -> Result<T::CrossAccountId> {706 let caller = T::CrossAccountId::from_eth(caller);707 collection708 .check_is_owner_or_admin(&caller)709 .map_err(dispatch_to_evm::<T>)?;710 Ok(caller)711}712713/// ### Note714/// Do not forget to add: `self.consume_store_writes(1)?;`715fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {716 collection717 .check_is_internal()718 .map_err(dispatch_to_evm::<T>)?;719 collection.save().map_err(dispatch_to_evm::<T>)?;720 Ok(())721}722723/// Contains static property keys and values.724pub mod static_property {725 use evm_coder::{726 execution::{Result, Error},727 };728 use alloc::format;729730 const EXPECT_CONVERT_ERROR: &str = "length < limit";731732 /// Keys.733 pub mod key {734 use super::*;735736 /// Key "baseURI".737 pub fn base_uri() -> up_data_structs::PropertyKey {738 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)739 }740741 /// Key "url".742 pub fn url() -> up_data_structs::PropertyKey {743 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)744 }745746 /// Key "suffix".747 pub fn suffix() -> up_data_structs::PropertyKey {748 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)749 }750751 /// Key "parentNft".752 pub fn parent_nft() -> up_data_structs::PropertyKey {753 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)754 }755 }756757 /// Convert `byte` to [`PropertyKey`].758 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {759 bytes.to_vec().try_into().map_err(|_| {760 Error::Revert(format!(761 "Property key is too long. Max length is {}.",762 up_data_structs::PropertyKey::bound()763 ))764 })765 }766767 /// Convert `bytes` to [`PropertyValue`].768 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {769 bytes.to_vec().try_into().map_err(|_| {770 Error::Revert(format!(771 "Property key is too long. Max length is {}.",772 up_data_structs::PropertyKey::bound()773 ))774 })775 }776}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 types::Property as PropertyStruct,25 execution::{Result, Error},26 weight,27};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::{EthCrossAccount, convert_cross_account_to_uint256},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 /// Raw compiled binary code of the contract stub67 const CODE: &'static [u8];6869 /// Call precompiled handle.70 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;71}7273/// @title A contract that allows you to work with collections.74#[solidity_interface(name = Collection)]75impl<T: Config> CollectionHandle<T>76where77 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,78{79 /// Set collection property.80 ///81 /// @param key Property key.82 /// @param value Propery value.83 #[solidity(hide)]84 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]85 fn set_collection_property(86 &mut self,87 caller: caller,88 key: string,89 value: bytes,90 ) -> Result<void> {91 let caller = T::CrossAccountId::from_eth(caller);92 let key = <Vec<u8>>::from(key)93 .try_into()94 .map_err(|_| "key too large")?;95 let value = value.0.try_into().map_err(|_| "value too large")?;9697 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })98 .map_err(dispatch_to_evm::<T>)99 }100101 /// Set collection properties.102 ///103 /// @param properties Vector of properties key/value pair.104 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]105 fn set_collection_properties(106 &mut self,107 caller: caller,108 properties: Vec<PropertyStruct>,109 ) -> Result<void> {110 let caller = T::CrossAccountId::from_eth(caller);111112 let properties = properties113 .into_iter()114 .map(|PropertyStruct { key, value }| {115 let key = <Vec<u8>>::from(key)116 .try_into()117 .map_err(|_| "key too large")?;118119 let value = value.0.try_into().map_err(|_| "value too large")?;120121 Ok(Property { key, value })122 })123 .collect::<Result<Vec<_>>>()?;124125 <Pallet<T>>::set_collection_properties(self, &caller, properties)126 .map_err(dispatch_to_evm::<T>)127 }128129 /// Delete collection property.130 ///131 /// @param key Property key.132 #[solidity(hide)]133 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]134 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let key = <Vec<u8>>::from(key)137 .try_into()138 .map_err(|_| "key too large")?;139140 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)141 }142143 /// Delete collection properties.144 ///145 /// @param keys Properties keys.146 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]147 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {148 let caller = T::CrossAccountId::from_eth(caller);149 let keys = keys150 .into_iter()151 .map(|key| {152 <Vec<u8>>::from(key)153 .try_into()154 .map_err(|_| Error::Revert("key too large".into()))155 })156 .collect::<Result<Vec<_>>>()?;157158 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)159 }160161 /// Get collection property.162 ///163 /// @dev Throws error if key not found.164 ///165 /// @param key Property key.166 /// @return bytes The property corresponding to the key.167 fn collection_property(&self, key: string) -> Result<bytes> {168 let key = <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| "key too large")?;171172 let props = CollectionProperties::<T>::get(self.id);173 let prop = props.get(&key).ok_or("key not found")?;174175 Ok(bytes(prop.to_vec()))176 }177178 /// Get collection properties.179 ///180 /// @param keys Properties keys. Empty keys for all propertyes.181 /// @return Vector of properties key/value pairs.182 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {183 let keys = keys184 .into_iter()185 .map(|key| {186 <Vec<u8>>::from(key)187 .try_into()188 .map_err(|_| Error::Revert("key too large".into()))189 })190 .collect::<Result<Vec<_>>>()?;191192 let properties = Pallet::<T>::filter_collection_properties(193 self.id,194 if keys.is_empty() { None } else { Some(keys) },195 )196 .map_err(dispatch_to_evm::<T>)?;197198 let properties = properties199 .into_iter()200 .map(|p| {201 let key =202 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;203 let value = bytes(p.value.to_vec());204 Ok(PropertyStruct { key, value })205 })206 .collect::<Result<Vec<_>>>()?;207 Ok(properties)208 }209210 /// Set the sponsor of the collection.211 ///212 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.213 ///214 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.215 #[solidity(hide)]216 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {217 self.consume_store_reads_and_writes(1, 1)?;218219 check_is_owner_or_admin(caller, self)?;220221 let sponsor = T::CrossAccountId::from_eth(sponsor);222 self.set_sponsor(sponsor.as_sub().clone())223 .map_err(dispatch_to_evm::<T>)?;224 save(self)225 }226227 /// Set the sponsor of the collection.228 ///229 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.230 ///231 /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.232 fn set_collection_sponsor_cross(233 &mut self,234 caller: caller,235 sponsor: EthCrossAccount,236 ) -> Result<void> {237 self.consume_store_reads_and_writes(1, 1)?;238239 check_is_owner_or_admin(caller, self)?;240241 let sponsor = sponsor.into_sub_cross_account::<T>()?;242 self.set_sponsor(sponsor.as_sub().clone())243 .map_err(dispatch_to_evm::<T>)?;244 save(self)245 }246247 /// Whether there is a pending sponsor.248 fn has_collection_pending_sponsor(&self) -> Result<bool> {249 Ok(matches!(250 self.collection.sponsorship,251 SponsorshipState::Unconfirmed(_)252 ))253 }254255 /// Collection sponsorship confirmation.256 ///257 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.258 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {259 self.consume_store_writes(1)?;260261 let caller = T::CrossAccountId::from_eth(caller);262 if !self263 .confirm_sponsorship(caller.as_sub())264 .map_err(dispatch_to_evm::<T>)?265 {266 return Err("caller is not set as sponsor".into());267 }268 save(self)269 }270271 /// Remove collection sponsor.272 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {273 self.consume_store_reads_and_writes(1, 1)?;274 check_is_owner_or_admin(caller, self)?;275 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;276 save(self)277 }278279 /// Get current sponsor.280 ///281 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.282 fn collection_sponsor(&self) -> Result<(address, uint256)> {283 let sponsor = match self.collection.sponsorship.sponsor() {284 Some(sponsor) => sponsor,285 None => return Ok(Default::default()),286 };287 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());288 let result: (address, uint256) = if sponsor.is_canonical_substrate() {289 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);290 (Default::default(), sponsor)291 } else {292 let sponsor = *sponsor.as_eth();293 (sponsor, Default::default())294 };295 Ok(result)296 }297298 /// Set limits for the collection.299 /// @dev Throws error if limit not found.300 /// @param limit Name of the limit. Valid names:301 /// "accountTokenOwnershipLimit",302 /// "sponsoredDataSize",303 /// "sponsoredDataRateLimit",304 /// "tokenLimit",305 /// "sponsorTransferTimeout",306 /// "sponsorApproveTimeout"307 /// "ownerCanTransfer",308 /// "ownerCanDestroy",309 /// "transfersEnabled"310 /// @param value Value of the limit.311 #[solidity(rename_selector = "setCollectionLimit")]312 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {313 self.consume_store_reads_and_writes(1, 1)?;314315 let value = value316 .try_into()317 .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;318319 let convert_value_to_bool = || match value {320 0 => Ok(false),321 1 => Ok(true),322 _ => {323 return Err(Error::Revert(format!(324 "can't convert value to boolean \"{}\"",325 value326 )))327 }328 };329330 check_is_owner_or_admin(caller, self)?;331 let mut limits = self.limits.clone();332333 match limit.as_str() {334 "accountTokenOwnershipLimit" => {335 limits.account_token_ownership_limit = Some(value);336 }337 "sponsoredDataSize" => {338 limits.sponsored_data_size = Some(value);339 }340 "sponsoredDataRateLimit" => {341 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));342 }343 "tokenLimit" => {344 limits.token_limit = Some(value);345 }346 "sponsorTransferTimeout" => {347 limits.sponsor_transfer_timeout = Some(value);348 }349 "sponsorApproveTimeout" => {350 limits.sponsor_approve_timeout = Some(value);351 }352 "ownerCanTransfer" => {353 limits.owner_can_transfer = Some(convert_value_to_bool()?);354 }355 "ownerCanDestroy" => {356 limits.owner_can_destroy = Some(convert_value_to_bool()?);357 }358 "transfersEnabled" => {359 limits.transfers_enabled = Some(convert_value_to_bool()?);360 }361 _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),362 }363 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)364 .map_err(dispatch_to_evm::<T>)?;365 save(self)366 }367368 /// Get contract address.369 fn contract_address(&self) -> Result<address> {370 Ok(crate::eth::collection_id_to_address(self.id))371 }372373 /// Add collection admin.374 /// @param newAdmin Cross account administrator address.375 fn add_collection_admin_cross(376 &mut self,377 caller: caller,378 new_admin: EthCrossAccount,379 ) -> Result<void> {380 self.consume_store_writes(2)?;381382 let caller = T::CrossAccountId::from_eth(caller);383 let new_admin = new_admin.into_sub_cross_account::<T>()?;384 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;385 Ok(())386 }387388 /// Remove collection admin.389 /// @param admin Cross account administrator address.390 fn remove_collection_admin_cross(391 &mut self,392 caller: caller,393 admin: EthCrossAccount,394 ) -> Result<void> {395 self.consume_store_writes(2)?;396397 let caller = T::CrossAccountId::from_eth(caller);398 let admin = admin.into_sub_cross_account::<T>()?;399 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;400 Ok(())401 }402403 /// Add collection admin.404 /// @param newAdmin Address of the added administrator.405 #[solidity(hide)]406 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {407 self.consume_store_writes(2)?;408409 let caller = T::CrossAccountId::from_eth(caller);410 let new_admin = T::CrossAccountId::from_eth(new_admin);411 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;412 Ok(())413 }414415 /// Remove collection admin.416 ///417 /// @param admin Address of the removed administrator.418 #[solidity(hide)]419 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {420 self.consume_store_writes(2)?;421422 let caller = T::CrossAccountId::from_eth(caller);423 let admin = T::CrossAccountId::from_eth(admin);424 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;425 Ok(())426 }427428 /// Toggle accessibility of collection nesting.429 ///430 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'431 #[solidity(rename_selector = "setCollectionNesting")]432 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {433 self.consume_store_reads_and_writes(1, 1)?;434435 check_is_owner_or_admin(caller, self)?;436437 let mut permissions = self.collection.permissions.clone();438 let mut nesting = permissions.nesting().clone();439 nesting.token_owner = enable;440 nesting.restricted = None;441 permissions.nesting = Some(nesting);442443 self.collection.permissions = <Pallet<T>>::clamp_permissions(444 self.collection.mode.clone(),445 &self.collection.permissions,446 permissions,447 )448 .map_err(dispatch_to_evm::<T>)?;449450 save(self)451 }452453 /// Toggle accessibility of collection nesting.454 ///455 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'456 /// @param collections Addresses of collections that will be available for nesting.457 #[solidity(rename_selector = "setCollectionNesting")]458 fn set_nesting(459 &mut self,460 caller: caller,461 enable: bool,462 collections: Vec<address>,463 ) -> Result<void> {464 self.consume_store_reads_and_writes(1, 1)?;465466 if collections.is_empty() {467 return Err("no addresses provided".into());468 }469 check_is_owner_or_admin(caller, self)?;470471 let mut permissions = self.collection.permissions.clone();472 match enable {473 false => {474 let mut nesting = permissions.nesting().clone();475 nesting.token_owner = false;476 nesting.restricted = None;477 permissions.nesting = Some(nesting);478 }479 true => {480 let mut bv = OwnerRestrictedSet::new();481 for i in collections {482 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {483 Error::Revert("Can't convert address into collection id".into())484 })?)485 .map_err(|_| "too many collections")?;486 }487 let mut nesting = permissions.nesting().clone();488 nesting.token_owner = true;489 nesting.restricted = Some(bv);490 permissions.nesting = Some(nesting);491 }492 };493494 self.collection.permissions = <Pallet<T>>::clamp_permissions(495 self.collection.mode.clone(),496 &self.collection.permissions,497 permissions,498 )499 .map_err(dispatch_to_evm::<T>)?;500501 save(self)502 }503504 /// Set the collection access method.505 /// @param mode Access mode506 /// 0 for Normal507 /// 1 for AllowList508 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {509 self.consume_store_reads_and_writes(1, 1)?;510511 check_is_owner_or_admin(caller, self)?;512 let permissions = CollectionPermissions {513 access: Some(match mode {514 0 => AccessMode::Normal,515 1 => AccessMode::AllowList,516 _ => return Err("not supported access mode".into()),517 }),518 ..Default::default()519 };520 self.collection.permissions = <Pallet<T>>::clamp_permissions(521 self.collection.mode.clone(),522 &self.collection.permissions,523 permissions,524 )525 .map_err(dispatch_to_evm::<T>)?;526527 save(self)528 }529530 /// Checks that user allowed to operate with collection.531 ///532 /// @param user User address to check.533 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {534 let user = user.into_sub_cross_account::<T>()?;535 Ok(Pallet::<T>::allowed(self.id, user))536 }537538 /// Add the user to the allowed list.539 ///540 /// @param user Address of a trusted user.541 #[solidity(hide)]542 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {543 self.consume_store_writes(1)?;544545 let caller = T::CrossAccountId::from_eth(caller);546 let user = T::CrossAccountId::from_eth(user);547 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;548 Ok(())549 }550551 /// Add user to allowed list.552 ///553 /// @param user User cross account address.554 fn add_to_collection_allow_list_cross(555 &mut self,556 caller: caller,557 user: EthCrossAccount,558 ) -> Result<void> {559 self.consume_store_writes(1)?;560561 let caller = T::CrossAccountId::from_eth(caller);562 let user = user.into_sub_cross_account::<T>()?;563 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;564 Ok(())565 }566567 /// Remove the user from the allowed list.568 ///569 /// @param user Address of a removed user.570 #[solidity(hide)]571 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {572 self.consume_store_writes(1)?;573574 let caller = T::CrossAccountId::from_eth(caller);575 let user = T::CrossAccountId::from_eth(user);576 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;577 Ok(())578 }579580 /// Remove user from allowed list.581 ///582 /// @param user User cross account address.583 fn remove_from_collection_allow_list_cross(584 &mut self,585 caller: caller,586 user: EthCrossAccount,587 ) -> Result<void> {588 self.consume_store_writes(1)?;589590 let caller = T::CrossAccountId::from_eth(caller);591 let user = user.into_sub_cross_account::<T>()?;592 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;593 Ok(())594 }595596 /// Switch permission for minting.597 ///598 /// @param mode Enable if "true".599 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {600 self.consume_store_reads_and_writes(1, 1)?;601602 check_is_owner_or_admin(caller, self)?;603 let permissions = CollectionPermissions {604 mint_mode: Some(mode),605 ..Default::default()606 };607 self.collection.permissions = <Pallet<T>>::clamp_permissions(608 self.collection.mode.clone(),609 &self.collection.permissions,610 permissions,611 )612 .map_err(dispatch_to_evm::<T>)?;613614 save(self)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: EthCrossAccount) -> 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<EthCrossAccount> {653 Ok(EthCrossAccount::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.set_owner_internal(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<EthCrossAccount>> {677 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))678 .map(|(admin, _)| EthCrossAccount::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: EthCrossAccount,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.set_owner_internal(caller, new_owner)697 .map_err(dispatch_to_evm::<T>)698 }699}700701/// ### Note702/// Do not forget to add: `self.consume_store_reads(1)?;`703fn check_is_owner_or_admin<T: Config>(704 caller: caller,705 collection: &CollectionHandle<T>,706) -> Result<T::CrossAccountId> {707 let caller = T::CrossAccountId::from_eth(caller);708 collection709 .check_is_owner_or_admin(&caller)710 .map_err(dispatch_to_evm::<T>)?;711 Ok(caller)712}713714/// ### Note715/// Do not forget to add: `self.consume_store_writes(1)?;`716fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {717 collection718 .check_is_internal()719 .map_err(dispatch_to_evm::<T>)?;720 collection.save().map_err(dispatch_to_evm::<T>)?;721 Ok(())722}723724/// Contains static property keys and values.725pub mod static_property {726 use evm_coder::{727 execution::{Result, Error},728 };729 use alloc::format;730731 const EXPECT_CONVERT_ERROR: &str = "length < limit";732733 /// Keys.734 pub mod key {735 use super::*;736737 /// Key "baseURI".738 pub fn base_uri() -> up_data_structs::PropertyKey {739 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)740 }741742 /// Key "url".743 pub fn url() -> up_data_structs::PropertyKey {744 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)745 }746747 /// Key "suffix".748 pub fn suffix() -> up_data_structs::PropertyKey {749 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)750 }751752 /// Key "parentNft".753 pub fn parent_nft() -> up_data_structs::PropertyKey {754 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)755 }756 }757758 /// Convert `byte` to [`PropertyKey`].759 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {760 bytes.to_vec().try_into().map_err(|_| {761 Error::Revert(format!(762 "Property key is too long. Max length is {}.",763 up_data_structs::PropertyKey::bound()764 ))765 })766 }767768 /// Convert `bytes` to [`PropertyValue`].769 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {770 bytes.to_vec().try_into().map_err(|_| {771 Error::Revert(format!(772 "Property key is too long. Max length is {}.",773 up_data_structs::PropertyKey::bound()774 ))775 })776 }777}pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,7 +16,10 @@
//! The module contains a number of functions for converting and checking ethereum identifiers.
-use evm_coder::types::{uint256, address};
+use evm_coder::{
+ AbiCoder,
+ types::{uint256, address},
+};
pub use pallet_evm::{Config, account::CrossAccountId};
use sp_core::H160;
use up_data_structs::CollectionId;
@@ -109,3 +112,111 @@
Err("All fields of cross account is non zeroed".into())
}
}
+
+#[derive(Debug, Default, AbiCoder)]
+pub struct EthCrossAccount {
+ pub(crate) eth: address,
+ pub(crate) sub: uint256,
+}
+
+impl EthCrossAccount {
+ pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
+ where
+ T: pallet_evm::account::Config,
+ T::AccountId: AsRef<[u8; 32]>,
+ {
+ if cross_account_id.is_canonical_substrate() {
+ Self {
+ eth: Default::default(),
+ sub: convert_cross_account_to_uint256::<T>(cross_account_id),
+ }
+ } else {
+ Self {
+ eth: *cross_account_id.as_eth(),
+ sub: Default::default(),
+ }
+ }
+ }
+
+ pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
+ where
+ T: pallet_evm::account::Config,
+ T::AccountId: From<[u8; 32]>,
+ {
+ if self.eth == Default::default() && self.sub == Default::default() {
+ Err("All fields of cross account is zeroed".into())
+ } else if self.eth == Default::default() {
+ Ok(convert_uint256_to_cross_account::<T>(self.sub))
+ } else if self.sub == Default::default() {
+ Ok(T::CrossAccountId::from_eth(self.eth))
+ } else {
+ Err("All fields of cross account is non zeroed".into())
+ }
+ }
+}
+
+impl ::evm_coder::solidity::sealed::CanBePlacedInVec for EthCrossAccount {}
+impl ::evm_coder::solidity::SolidityTupleType for EthCrossAccount {
+ fn names(tc: &::evm_coder::solidity::TypeCollector) -> Vec<String> {
+ let mut collected =
+ Vec::with_capacity(<Self as ::evm_coder::solidity::SolidityTupleType>::len());
+ {
+ let mut out = String::new();
+ <address as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+ .expect("no fmt error");
+ collected.push(out);
+ }
+ {
+ let mut out = String::new();
+ <uint256 as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+ .expect("no fmt error");
+ collected.push(out);
+ }
+ collected
+ }
+
+ fn len() -> usize {
+ 2
+ }
+}
+impl ::evm_coder::solidity::SolidityTypeName for EthCrossAccount {
+ fn solidity_name(
+ writer: &mut impl ::core::fmt::Write,
+ tc: &::evm_coder::solidity::TypeCollector,
+ ) -> ::core::fmt::Result {
+ write!(writer, "{}", tc.collect_struct::<Self>())
+ }
+
+ fn is_simple() -> bool {
+ false
+ }
+
+ fn solidity_default(
+ writer: &mut impl ::core::fmt::Write,
+ tc: &::evm_coder::solidity::TypeCollector,
+ ) -> ::core::fmt::Result {
+ write!(writer, "{}(", tc.collect_struct::<Self>())?;
+ address::solidity_default(writer, tc)?;
+ write!(writer, ",")?;
+ uint256::solidity_default(writer, tc)?;
+ write!(writer, ")")
+ }
+}
+
+impl ::evm_coder::solidity::StructCollect for EthCrossAccount {
+ fn name() -> String {
+ "EthCrossAccount".into()
+ }
+
+ fn declaration() -> String {
+ use std::fmt::Write;
+
+ let mut str = String::new();
+ writeln!(str, "/// @dev Cross account struct").unwrap();
+ writeln!(str, "struct {} {{", Self::name()).unwrap();
+ writeln!(str, "\taddress eth;").unwrap();
+ writeln!(str, "\tuint256 sub;").unwrap();
+ writeln!(str, "}}").unwrap();
+ str
+ }
+}
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -24,12 +24,15 @@
weight,
};
use up_data_structs::CollectionMode;
-use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
+use pallet_common::{
+ CollectionHandle,
+ erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+ eth::EthCrossAccount,
+};
use sp_std::vec::Vec;
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm};
use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use pallet_common::{CollectionHandle, erc::CollectionCall};
use sp_core::Get;
use crate::{
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -36,8 +36,9 @@
use pallet_evm_coder_substrate::dispatch_to_evm;
use sp_std::vec::Vec;
use pallet_common::{
+ CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
- CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
+ eth::EthCrossAccount,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::call;
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -33,6 +33,7 @@
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions,
erc::{CommonEvmHandler, CollectionCall, static_property::key},
+ eth::EthCrossAccount,
CommonCollectionOperations,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};