difftreelog
fix refactor weght calculation.
in: master
1 file changed
pallets/common/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! This module contains the implementation of pallet methods for evm.1819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23};24pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_std::vec::Vec;27use up_data_structs::{28 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,29 SponsoringRateLimit, SponsorshipState,30};31use alloc::format;3233use crate::{34 Pallet, CollectionHandle, Config, CollectionProperties,35 eth::{36 convert_cross_account_to_uint256, convert_uint256_to_cross_account,37 convert_cross_account_to_tuple,38 },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}5556/// Does not always represent a full collection, for RFT it is either57/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).58pub trait CommonEvmHandler {59 const CODE: &'static [u8];6061 /// Call precompiled handle.62 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;63}6465/// @title A contract that allows you to work with collections.66#[solidity_interface(name = Collection)]67impl<T: Config> CollectionHandle<T>68where69 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,70{71 /// Set collection property.72 ///73 /// @param key Property key.74 /// @param value Propery value.75 fn set_collection_property(76 &mut self,77 caller: caller,78 key: string,79 value: bytes,80 ) -> Result<void> {81 self.consume_store_reads_and_writes(1, 1)?;8283 let caller = T::CrossAccountId::from_eth(caller);84 let key = <Vec<u8>>::from(key)85 .try_into()86 .map_err(|_| "key too large")?;87 let value = value.try_into().map_err(|_| "value too large")?;8889 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })90 .map_err(dispatch_to_evm::<T>)91 }9293 /// Delete collection property.94 ///95 /// @param key Property key.96 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {97 self.consume_store_reads_and_writes(1, 1)?;9899 let caller = T::CrossAccountId::from_eth(caller);100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too large")?;103104 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)105 }106107 /// Get collection property.108 ///109 /// @dev Throws error if key not found.110 ///111 /// @param key Property key.112 /// @return bytes The property corresponding to the key.113 fn collection_property(&self, key: string) -> Result<bytes> {114 let key = <Vec<u8>>::from(key)115 .try_into()116 .map_err(|_| "key too large")?;117118 let props = <CollectionProperties<T>>::get(self.id);119 let prop = props.get(&key).ok_or("key not found")?;120121 Ok(prop.to_vec())122 }123124 /// Set the sponsor of the collection.125 ///126 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.127 ///128 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.129 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {130 self.consume_store_reads_and_writes(1, 1)?;131132 check_is_owner_or_admin(caller, self)?;133134 let sponsor = T::CrossAccountId::from_eth(sponsor);135 self.set_sponsor(sponsor.as_sub().clone())136 .map_err(dispatch_to_evm::<T>)?;137 save(self)138 }139140 /// Set the substrate sponsor of the collection.141 ///142 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.143 ///144 /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.145 fn set_collection_sponsor_substrate(146 &mut self,147 caller: caller,148 sponsor: uint256,149 ) -> Result<void> {150 self.consume_store_reads_and_writes(1, 1)?;151152 check_is_owner_or_admin(caller, self)?;153154 let sponsor = convert_uint256_to_cross_account::<T>(sponsor);155 self.set_sponsor(sponsor.as_sub().clone())156 .map_err(dispatch_to_evm::<T>)?;157 save(self)158 }159160 /// Whether there is a pending sponsor.161 fn has_collection_pending_sponsor(&self) -> Result<bool> {162 Ok(matches!(163 self.collection.sponsorship,164 SponsorshipState::Unconfirmed(_)165 ))166 }167168 /// Collection sponsorship confirmation.169 ///170 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.171 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {172 self.consume_store_writes(1)?;173174 let caller = T::CrossAccountId::from_eth(caller);175 if !self176 .confirm_sponsorship(caller.as_sub())177 .map_err(dispatch_to_evm::<T>)?178 {179 return Err("caller is not set as sponsor".into());180 }181 save(self)182 }183184 /// Remove collection sponsor.185 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {186 self.consume_store_reads_and_writes(1, 1)?;187 check_is_owner_or_admin(caller, self)?;188 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;189 save(self)190 }191192 /// Get current sponsor.193 ///194 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.195 fn get_collection_sponsor(&self) -> Result<(address, uint256)> {196 let sponsor = match self.collection.sponsorship.sponsor() {197 Some(sponsor) => sponsor,198 None => return Ok(Default::default()),199 };200 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());201 let result: (address, uint256) = if sponsor.is_canonical_substrate() {202 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);203 (Default::default(), sponsor)204 } else {205 let sponsor = *sponsor.as_eth();206 (sponsor, Default::default())207 };208 Ok(result)209 }210211 /// Set limits for the collection.212 /// @dev Throws error if limit not found.213 /// @param limit Name of the limit. Valid names:214 /// "accountTokenOwnershipLimit",215 /// "sponsoredDataSize",216 /// "sponsoredDataRateLimit",217 /// "tokenLimit",218 /// "sponsorTransferTimeout",219 /// "sponsorApproveTimeout"220 /// @param value Value of the limit.221 #[solidity(rename_selector = "setCollectionLimit")]222 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {223 self.consume_store_reads_and_writes(1, 1)?;224225 check_is_owner_or_admin(caller, self)?;226 let mut limits = self.limits.clone();227228 match limit.as_str() {229 "accountTokenOwnershipLimit" => {230 limits.account_token_ownership_limit = Some(value);231 }232 "sponsoredDataSize" => {233 limits.sponsored_data_size = Some(value);234 }235 "sponsoredDataRateLimit" => {236 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));237 }238 "tokenLimit" => {239 limits.token_limit = Some(value);240 }241 "sponsorTransferTimeout" => {242 limits.sponsor_transfer_timeout = Some(value);243 }244 "sponsorApproveTimeout" => {245 limits.sponsor_approve_timeout = Some(value);246 }247 _ => {248 return Err(Error::Revert(format!(249 "unknown integer limit \"{}\"",250 limit251 )))252 }253 }254 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)255 .map_err(dispatch_to_evm::<T>)?;256 save(self)257 }258259 /// Set limits for the collection.260 /// @dev Throws error if limit not found.261 /// @param limit Name of the limit. Valid names:262 /// "ownerCanTransfer",263 /// "ownerCanDestroy",264 /// "transfersEnabled"265 /// @param value Value of the limit.266 #[solidity(rename_selector = "setCollectionLimit")]267 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {268 self.consume_store_reads_and_writes(1, 1)?;269270 check_is_owner_or_admin(caller, self)?;271 let mut limits = self.limits.clone();272273 match limit.as_str() {274 "ownerCanTransfer" => {275 limits.owner_can_transfer = Some(value);276 }277 "ownerCanDestroy" => {278 limits.owner_can_destroy = Some(value);279 }280 "transfersEnabled" => {281 limits.transfers_enabled = Some(value);282 }283 _ => {284 return Err(Error::Revert(format!(285 "unknown boolean limit \"{}\"",286 limit287 )))288 }289 }290 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)291 .map_err(dispatch_to_evm::<T>)?;292 save(self)293 }294295 /// Get contract address.296 fn contract_address(&self) -> Result<address> {297 Ok(crate::eth::collection_id_to_address(self.id))298 }299300 /// Add collection admin by substrate address.301 /// @param newAdmin Substrate administrator address.302 fn add_collection_admin_substrate(303 &mut self,304 caller: caller,305 new_admin: uint256,306 ) -> Result<void> {307 self.consume_store_writes(2)?;308309 let caller = T::CrossAccountId::from_eth(caller);310 let new_admin = convert_uint256_to_cross_account::<T>(new_admin);311 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;312 Ok(())313 }314315 /// Remove collection admin by substrate address.316 /// @param admin Substrate administrator address.317 fn remove_collection_admin_substrate(318 &mut self,319 caller: caller,320 admin: uint256,321 ) -> Result<void> {322 self.consume_store_writes(2)?;323324 let caller = T::CrossAccountId::from_eth(caller);325 let admin = convert_uint256_to_cross_account::<T>(admin);326 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;327 Ok(())328 }329330 /// Add collection admin.331 /// @param newAdmin Address of the added administrator.332 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {333 self.consume_store_writes(2)?;334335 let caller = T::CrossAccountId::from_eth(caller);336 let new_admin = T::CrossAccountId::from_eth(new_admin);337 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;338 Ok(())339 }340341 /// Remove collection admin.342 ///343 /// @param admin Address of the removed administrator.344 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {345 self.consume_store_writes(2)?;346347 let caller = T::CrossAccountId::from_eth(caller);348 let admin = T::CrossAccountId::from_eth(admin);349 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;350 Ok(())351 }352353 /// Toggle accessibility of collection nesting.354 ///355 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'356 #[solidity(rename_selector = "setCollectionNesting")]357 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {358 self.consume_store_reads_and_writes(1, 1)?;359360 check_is_owner_or_admin(caller, self)?;361362 let mut permissions = self.collection.permissions.clone();363 let mut nesting = permissions.nesting().clone();364 nesting.token_owner = enable;365 nesting.restricted = None;366 permissions.nesting = Some(nesting);367368 self.collection.permissions = <Pallet<T>>::clamp_permissions(369 self.collection.mode.clone(),370 &self.collection.permissions,371 permissions,372 )373 .map_err(dispatch_to_evm::<T>)?;374375 save(self)376 }377378 /// Toggle accessibility of collection nesting.379 ///380 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'381 /// @param collections Addresses of collections that will be available for nesting.382 #[solidity(rename_selector = "setCollectionNesting")]383 fn set_nesting(384 &mut self,385 caller: caller,386 enable: bool,387 collections: Vec<address>,388 ) -> Result<void> {389 self.consume_store_reads_and_writes(1, 1)?;390391 if collections.is_empty() {392 return Err("no addresses provided".into());393 }394 check_is_owner_or_admin(caller, self)?;395396 let mut permissions = self.collection.permissions.clone();397 match enable {398 false => {399 let mut nesting = permissions.nesting().clone();400 nesting.token_owner = false;401 nesting.restricted = None;402 permissions.nesting = Some(nesting);403 }404 true => {405 let mut bv = OwnerRestrictedSet::new();406 for i in collections {407 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(408 "Can't convert address into collection id".into(),409 ))?)410 .map_err(|_| "too many collections")?;411 }412 let mut nesting = permissions.nesting().clone();413 nesting.token_owner = true;414 nesting.restricted = Some(bv);415 permissions.nesting = Some(nesting);416 }417 };418419 self.collection.permissions = <Pallet<T>>::clamp_permissions(420 self.collection.mode.clone(),421 &self.collection.permissions,422 permissions,423 )424 .map_err(dispatch_to_evm::<T>)?;425426 save(self)427 }428429 /// Set the collection access method.430 /// @param mode Access mode431 /// 0 for Normal432 /// 1 for AllowList433 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {434 self.consume_store_reads_and_writes(1, 1)?;435436 check_is_owner_or_admin(caller, self)?;437 let permissions = CollectionPermissions {438 access: Some(match mode {439 0 => AccessMode::Normal,440 1 => AccessMode::AllowList,441 _ => return Err("not supported access mode".into()),442 }),443 ..Default::default()444 };445 self.collection.permissions = <Pallet<T>>::clamp_permissions(446 self.collection.mode.clone(),447 &self.collection.permissions,448 permissions,449 )450 .map_err(dispatch_to_evm::<T>)?;451452 save(self)453 }454455 /// Checks that user allowed to operate with collection.456 ///457 /// @param user User address to check.458 fn allowed(&self, user: address) -> Result<bool> {459 Ok(Pallet::<T>::allowed(460 self.id,461 T::CrossAccountId::from_eth(user),462 ))463 }464465 /// Add the user to the allowed list.466 ///467 /// @param user Address of a trusted user.468 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {469 self.consume_store_writes(1)?;470471 let caller = T::CrossAccountId::from_eth(caller);472 let user = T::CrossAccountId::from_eth(user);473 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;474 Ok(())475 }476477 /// Add substrate user to allowed list.478 ///479 /// @param user User substrate address.480 fn add_to_collection_allow_list_substrate(481 &mut self,482 caller: caller,483 user: uint256,484 ) -> Result<void> {485 self.consume_store_writes(1)?;486487 let caller = T::CrossAccountId::from_eth(caller);488 let user = convert_uint256_to_cross_account::<T>(user);489 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;490 Ok(())491 }492493 /// Remove the user from the allowed list.494 ///495 /// @param user Address of a removed user.496 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {497 self.consume_store_writes(1)?;498499 let caller = T::CrossAccountId::from_eth(caller);500 let user = T::CrossAccountId::from_eth(user);501 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;502 Ok(())503 }504505 /// Remove substrate user from allowed list.506 ///507 /// @param user User substrate address.508 fn remove_from_collection_allow_list_substrate(509 &mut self,510 caller: caller,511 user: uint256,512 ) -> Result<void> {513 self.consume_store_writes(1)?;514515 let caller = T::CrossAccountId::from_eth(caller);516 let user = convert_uint256_to_cross_account::<T>(user);517 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;518 Ok(())519 }520521 /// Switch permission for minting.522 ///523 /// @param mode Enable if "true".524 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {525 self.consume_store_reads_and_writes(1, 1)?;526527 check_is_owner_or_admin(caller, self)?;528 let permissions = CollectionPermissions {529 mint_mode: Some(mode),530 ..Default::default()531 };532 self.collection.permissions = <Pallet<T>>::clamp_permissions(533 self.collection.mode.clone(),534 &self.collection.permissions,535 permissions,536 )537 .map_err(dispatch_to_evm::<T>)?;538539 save(self)540 }541542 /// Check that account is the owner or admin of the collection543 ///544 /// @param user account to verify545 /// @return "true" if account is the owner or admin546 #[solidity(rename_selector = "isOwnerOrAdmin")]547 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {548 let user = T::CrossAccountId::from_eth(user);549 Ok(self.is_owner_or_admin(&user))550 }551552 /// Check that substrate account is the owner or admin of the collection553 ///554 /// @param user account to verify555 /// @return "true" if account is the owner or admin556 fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {557 let user = convert_uint256_to_cross_account::<T>(user);558 Ok(self.is_owner_or_admin(&user))559 }560561 /// Returns collection type562 ///563 /// @return `Fungible` or `NFT` or `ReFungible`564 fn unique_collection_type(&self) -> Result<string> {565 let mode = match self.collection.mode {566 CollectionMode::Fungible(_) => "Fungible",567 CollectionMode::NFT => "NFT",568 CollectionMode::ReFungible => "ReFungible",569 };570 Ok(mode.into())571 }572573 /// Get collection owner.574 ///575 /// @return Tuble with sponsor address and his substrate mirror.576 /// If address is canonical then substrate mirror is zero and vice versa.577 fn collection_owner(&self) -> Result<(address, uint256)> {578 Ok(convert_cross_account_to_tuple::<T>(579 &T::CrossAccountId::from_sub(self.owner.clone()),580 ))581 }582583 /// Changes collection owner to another account584 ///585 /// @dev Owner can be changed only by current owner586 /// @param newOwner new owner account587 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {588 self.consume_store_writes(1)?;589590 let caller = T::CrossAccountId::from_eth(caller);591 let new_owner = T::CrossAccountId::from_eth(new_owner);592 self.set_owner_internal(caller, new_owner)593 .map_err(dispatch_to_evm::<T>)594 }595596 /// Changes collection owner to another substrate account597 ///598 /// @dev Owner can be changed only by current owner599 /// @param newOwner new owner substrate account600 fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {601 self.consume_store_writes(1)?;602603 let caller = T::CrossAccountId::from_eth(caller);604 let new_owner = convert_uint256_to_cross_account::<T>(new_owner);605 self.set_owner_internal(caller, new_owner)606 .map_err(dispatch_to_evm::<T>)607 }608609 // TODO: need implement AbiWriter for &Vec<T>610 // fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {611 // let result = pallet_common::IsAdmin::<T>::iter_prefix((self.id,))612 // .map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::<T>(&admin))613 // .collect();614 // Ok(result)615 // }616}617618/// ### Note619/// Do not forget to add: `self.consume_store_reads(1)?;`620fn check_is_owner_or_admin<T: Config>(621 caller: caller,622 collection: &CollectionHandle<T>,623) -> Result<T::CrossAccountId> {624 let caller = T::CrossAccountId::from_eth(caller);625 collection626 .check_is_owner_or_admin(&caller)627 .map_err(dispatch_to_evm::<T>)?;628 Ok(caller)629}630631/// ### Note632/// Do not forget to add: `self.consume_store_writes(1)?;`633fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {634 collection635 .check_is_internal()636 .map_err(dispatch_to_evm::<T>)?;637 collection.save().map_err(dispatch_to_evm::<T>)?;638 Ok(())639}640641/// Contains static property keys and values.642pub mod static_property {643 use evm_coder::{644 execution::{Result, Error},645 };646 use alloc::format;647648 const EXPECT_CONVERT_ERROR: &str = "length < limit";649650 /// Keys.651 pub mod key {652 use super::*;653654 /// Key "schemaName".655 pub fn schema_name() -> up_data_structs::PropertyKey {656 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)657 }658659 /// Key "baseURI".660 pub fn base_uri() -> up_data_structs::PropertyKey {661 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)662 }663664 /// Key "url".665 pub fn url() -> up_data_structs::PropertyKey {666 property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)667 }668669 /// Key "suffix".670 pub fn suffix() -> up_data_structs::PropertyKey {671 property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)672 }673674 /// Key "parentNft".675 pub fn parent_nft() -> up_data_structs::PropertyKey {676 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)677 }678 }679680 /// Values.681 pub mod value {682 use super::*;683684 /// Value "ERC721Metadata".685 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";686687 /// Value for [`ERC721_METADATA`].688 pub fn erc721() -> up_data_structs::PropertyValue {689 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)690 }691 }692693 /// Convert `byte` to [`PropertyKey`].694 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {695 bytes.to_vec().try_into().map_err(|_| {696 Error::Revert(format!(697 "Property key is too long. Max length is {}.",698 up_data_structs::PropertyKey::bound()699 ))700 })701 }702703 /// Convert `bytes` to [`PropertyValue`].704 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {705 bytes.to_vec().try_into().map_err(|_| {706 Error::Revert(format!(707 "Property key is too long. Max length is {}.",708 up_data_structs::PropertyKey::bound()709 ))710 })711 }712}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};25pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};26use pallet_evm_coder_substrate::dispatch_to_evm;27use sp_std::vec::Vec;28use up_data_structs::{29 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,30 SponsoringRateLimit, SponsorshipState,31};32use alloc::format;3334use crate::{35 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,36 eth::{37 convert_cross_account_to_uint256, convert_uint256_to_cross_account,38 convert_cross_account_to_tuple,39 },40 weights::WeightInfo,41};4243/// Events for ethereum collection helper.44#[derive(ToLog)]45pub enum CollectionHelpersEvents {46 /// The collection has been created.47 CollectionCreated {48 /// Collection owner.49 #[indexed]50 owner: address,5152 /// Collection ID.53 #[indexed]54 collection_id: address,55 },56}5758/// Does not always represent a full collection, for RFT it is either59/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).60pub trait CommonEvmHandler {61 const CODE: &'static [u8];6263 /// Call precompiled handle.64 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;65}6667/// @title A contract that allows you to work with collections.68#[solidity_interface(name = Collection)]69impl<T: Config> CollectionHandle<T>70where71 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,72{73 /// Set collection property.74 ///75 /// @param key Property key.76 /// @param value Propery value.77 #[weight(<SelfWeightOf<T>>::set_collection_properties(1_u32))]78 fn set_collection_property(79 &mut self,80 caller: caller,81 key: string,82 value: bytes,83 ) -> Result<void> {84 let caller = T::CrossAccountId::from_eth(caller);85 let key = <Vec<u8>>::from(key)86 .try_into()87 .map_err(|_| "key too large")?;88 let value = value.try_into().map_err(|_| "value too large")?;8990 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })91 .map_err(dispatch_to_evm::<T>)92 }9394 /// Delete collection property.95 ///96 /// @param key Property key.97 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1_u32))]98 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {99 self.consume_store_reads_and_writes(1, 1)?;100101 let caller = T::CrossAccountId::from_eth(caller);102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too large")?;105106 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)107 }108109 /// Get collection property.110 ///111 /// @dev Throws error if key not found.112 ///113 /// @param key Property key.114 /// @return bytes The property corresponding to the key.115 fn collection_property(&self, key: string) -> Result<bytes> {116 let key = <Vec<u8>>::from(key)117 .try_into()118 .map_err(|_| "key too large")?;119120 let props = <CollectionProperties<T>>::get(self.id);121 let prop = props.get(&key).ok_or("key not found")?;122123 Ok(prop.to_vec())124 }125126 /// Set the sponsor of the collection.127 ///128 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.129 ///130 /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.131 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {132 self.consume_store_reads_and_writes(1, 1)?;133134 check_is_owner_or_admin(caller, self)?;135136 let sponsor = T::CrossAccountId::from_eth(sponsor);137 self.set_sponsor(sponsor.as_sub().clone())138 .map_err(dispatch_to_evm::<T>)?;139 save(self)140 }141142 /// Set the substrate sponsor of the collection.143 ///144 /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.145 ///146 /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.147 fn set_collection_sponsor_substrate(148 &mut self,149 caller: caller,150 sponsor: uint256,151 ) -> Result<void> {152 self.consume_store_reads_and_writes(1, 1)?;153154 check_is_owner_or_admin(caller, self)?;155156 let sponsor = convert_uint256_to_cross_account::<T>(sponsor);157 self.set_sponsor(sponsor.as_sub().clone())158 .map_err(dispatch_to_evm::<T>)?;159 save(self)160 }161162 /// Whether there is a pending sponsor.163 fn has_collection_pending_sponsor(&self) -> Result<bool> {164 Ok(matches!(165 self.collection.sponsorship,166 SponsorshipState::Unconfirmed(_)167 ))168 }169170 /// Collection sponsorship confirmation.171 ///172 /// @dev After setting the sponsor for the collection, it must be confirmed with this function.173 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {174 self.consume_store_writes(1)?;175176 let caller = T::CrossAccountId::from_eth(caller);177 if !self178 .confirm_sponsorship(caller.as_sub())179 .map_err(dispatch_to_evm::<T>)?180 {181 return Err("caller is not set as sponsor".into());182 }183 save(self)184 }185186 /// Remove collection sponsor.187 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {188 self.consume_store_reads_and_writes(1, 1)?;189 check_is_owner_or_admin(caller, self)?;190 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;191 save(self)192 }193194 /// Get current sponsor.195 ///196 /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.197 fn get_collection_sponsor(&self) -> Result<(address, uint256)> {198 let sponsor = match self.collection.sponsorship.sponsor() {199 Some(sponsor) => sponsor,200 None => return Ok(Default::default()),201 };202 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());203 let result: (address, uint256) = if sponsor.is_canonical_substrate() {204 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);205 (Default::default(), sponsor)206 } else {207 let sponsor = *sponsor.as_eth();208 (sponsor, Default::default())209 };210 Ok(result)211 }212213 /// Set limits for the collection.214 /// @dev Throws error if limit not found.215 /// @param limit Name of the limit. Valid names:216 /// "accountTokenOwnershipLimit",217 /// "sponsoredDataSize",218 /// "sponsoredDataRateLimit",219 /// "tokenLimit",220 /// "sponsorTransferTimeout",221 /// "sponsorApproveTimeout"222 /// @param value Value of the limit.223 #[solidity(rename_selector = "setCollectionLimit")]224 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {225 self.consume_store_reads_and_writes(1, 1)?;226227 check_is_owner_or_admin(caller, self)?;228 let mut limits = self.limits.clone();229230 match limit.as_str() {231 "accountTokenOwnershipLimit" => {232 limits.account_token_ownership_limit = Some(value);233 }234 "sponsoredDataSize" => {235 limits.sponsored_data_size = Some(value);236 }237 "sponsoredDataRateLimit" => {238 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));239 }240 "tokenLimit" => {241 limits.token_limit = Some(value);242 }243 "sponsorTransferTimeout" => {244 limits.sponsor_transfer_timeout = Some(value);245 }246 "sponsorApproveTimeout" => {247 limits.sponsor_approve_timeout = Some(value);248 }249 _ => {250 return Err(Error::Revert(format!(251 "unknown integer limit \"{}\"",252 limit253 )))254 }255 }256 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)257 .map_err(dispatch_to_evm::<T>)?;258 save(self)259 }260261 /// Set limits for the collection.262 /// @dev Throws error if limit not found.263 /// @param limit Name of the limit. Valid names:264 /// "ownerCanTransfer",265 /// "ownerCanDestroy",266 /// "transfersEnabled"267 /// @param value Value of the limit.268 #[solidity(rename_selector = "setCollectionLimit")]269 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {270 self.consume_store_reads_and_writes(1, 1)?;271272 check_is_owner_or_admin(caller, self)?;273 let mut limits = self.limits.clone();274275 match limit.as_str() {276 "ownerCanTransfer" => {277 limits.owner_can_transfer = Some(value);278 }279 "ownerCanDestroy" => {280 limits.owner_can_destroy = Some(value);281 }282 "transfersEnabled" => {283 limits.transfers_enabled = Some(value);284 }285 _ => {286 return Err(Error::Revert(format!(287 "unknown boolean limit \"{}\"",288 limit289 )))290 }291 }292 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)293 .map_err(dispatch_to_evm::<T>)?;294 save(self)295 }296297 /// Get contract address.298 fn contract_address(&self) -> Result<address> {299 Ok(crate::eth::collection_id_to_address(self.id))300 }301302 /// Add collection admin by substrate address.303 /// @param newAdmin Substrate administrator address.304 fn add_collection_admin_substrate(305 &mut self,306 caller: caller,307 new_admin: uint256,308 ) -> Result<void> {309 self.consume_store_writes(2)?;310311 let caller = T::CrossAccountId::from_eth(caller);312 let new_admin = convert_uint256_to_cross_account::<T>(new_admin);313 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;314 Ok(())315 }316317 /// Remove collection admin by substrate address.318 /// @param admin Substrate administrator address.319 fn remove_collection_admin_substrate(320 &mut self,321 caller: caller,322 admin: uint256,323 ) -> Result<void> {324 self.consume_store_writes(2)?;325326 let caller = T::CrossAccountId::from_eth(caller);327 let admin = convert_uint256_to_cross_account::<T>(admin);328 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;329 Ok(())330 }331332 /// Add collection admin.333 /// @param newAdmin Address of the added administrator.334 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {335 self.consume_store_writes(2)?;336337 let caller = T::CrossAccountId::from_eth(caller);338 let new_admin = T::CrossAccountId::from_eth(new_admin);339 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;340 Ok(())341 }342343 /// Remove collection admin.344 ///345 /// @param admin Address of the removed administrator.346 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {347 self.consume_store_writes(2)?;348349 let caller = T::CrossAccountId::from_eth(caller);350 let admin = T::CrossAccountId::from_eth(admin);351 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;352 Ok(())353 }354355 /// Toggle accessibility of collection nesting.356 ///357 /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'358 #[solidity(rename_selector = "setCollectionNesting")]359 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {360 self.consume_store_reads_and_writes(1, 1)?;361362 check_is_owner_or_admin(caller, self)?;363364 let mut permissions = self.collection.permissions.clone();365 let mut nesting = permissions.nesting().clone();366 nesting.token_owner = enable;367 nesting.restricted = None;368 permissions.nesting = Some(nesting);369370 self.collection.permissions = <Pallet<T>>::clamp_permissions(371 self.collection.mode.clone(),372 &self.collection.permissions,373 permissions,374 )375 .map_err(dispatch_to_evm::<T>)?;376377 save(self)378 }379380 /// Toggle accessibility of collection nesting.381 ///382 /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'383 /// @param collections Addresses of collections that will be available for nesting.384 #[solidity(rename_selector = "setCollectionNesting")]385 fn set_nesting(386 &mut self,387 caller: caller,388 enable: bool,389 collections: Vec<address>,390 ) -> Result<void> {391 self.consume_store_reads_and_writes(1, 1)?;392393 if collections.is_empty() {394 return Err("no addresses provided".into());395 }396 check_is_owner_or_admin(caller, self)?;397398 let mut permissions = self.collection.permissions.clone();399 match enable {400 false => {401 let mut nesting = permissions.nesting().clone();402 nesting.token_owner = false;403 nesting.restricted = None;404 permissions.nesting = Some(nesting);405 }406 true => {407 let mut bv = OwnerRestrictedSet::new();408 for i in collections {409 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(410 "Can't convert address into collection id".into(),411 ))?)412 .map_err(|_| "too many collections")?;413 }414 let mut nesting = permissions.nesting().clone();415 nesting.token_owner = true;416 nesting.restricted = Some(bv);417 permissions.nesting = Some(nesting);418 }419 };420421 self.collection.permissions = <Pallet<T>>::clamp_permissions(422 self.collection.mode.clone(),423 &self.collection.permissions,424 permissions,425 )426 .map_err(dispatch_to_evm::<T>)?;427428 save(self)429 }430431 /// Set the collection access method.432 /// @param mode Access mode433 /// 0 for Normal434 /// 1 for AllowList435 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {436 self.consume_store_reads_and_writes(1, 1)?;437438 check_is_owner_or_admin(caller, self)?;439 let permissions = CollectionPermissions {440 access: Some(match mode {441 0 => AccessMode::Normal,442 1 => AccessMode::AllowList,443 _ => return Err("not supported access mode".into()),444 }),445 ..Default::default()446 };447 self.collection.permissions = <Pallet<T>>::clamp_permissions(448 self.collection.mode.clone(),449 &self.collection.permissions,450 permissions,451 )452 .map_err(dispatch_to_evm::<T>)?;453454 save(self)455 }456457 /// Checks that user allowed to operate with collection.458 ///459 /// @param user User address to check.460 fn allowed(&self, user: address) -> Result<bool> {461 Ok(Pallet::<T>::allowed(462 self.id,463 T::CrossAccountId::from_eth(user),464 ))465 }466467 /// Add the user to the allowed list.468 ///469 /// @param user Address of a trusted user.470 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {471 self.consume_store_writes(1)?;472473 let caller = T::CrossAccountId::from_eth(caller);474 let user = T::CrossAccountId::from_eth(user);475 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;476 Ok(())477 }478479 /// Add substrate user to allowed list.480 ///481 /// @param user User substrate address.482 fn add_to_collection_allow_list_substrate(483 &mut self,484 caller: caller,485 user: uint256,486 ) -> Result<void> {487 self.consume_store_writes(1)?;488489 let caller = T::CrossAccountId::from_eth(caller);490 let user = convert_uint256_to_cross_account::<T>(user);491 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;492 Ok(())493 }494495 /// Remove the user from the allowed list.496 ///497 /// @param user Address of a removed user.498 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {499 self.consume_store_writes(1)?;500501 let caller = T::CrossAccountId::from_eth(caller);502 let user = T::CrossAccountId::from_eth(user);503 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;504 Ok(())505 }506507 /// Remove substrate user from allowed list.508 ///509 /// @param user User substrate address.510 fn remove_from_collection_allow_list_substrate(511 &mut self,512 caller: caller,513 user: uint256,514 ) -> Result<void> {515 self.consume_store_writes(1)?;516517 let caller = T::CrossAccountId::from_eth(caller);518 let user = convert_uint256_to_cross_account::<T>(user);519 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;520 Ok(())521 }522523 /// Switch permission for minting.524 ///525 /// @param mode Enable if "true".526 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {527 self.consume_store_reads_and_writes(1, 1)?;528529 check_is_owner_or_admin(caller, self)?;530 let permissions = CollectionPermissions {531 mint_mode: Some(mode),532 ..Default::default()533 };534 self.collection.permissions = <Pallet<T>>::clamp_permissions(535 self.collection.mode.clone(),536 &self.collection.permissions,537 permissions,538 )539 .map_err(dispatch_to_evm::<T>)?;540541 save(self)542 }543544 /// Check that account is the owner or admin of the collection545 ///546 /// @param user account to verify547 /// @return "true" if account is the owner or admin548 #[solidity(rename_selector = "isOwnerOrAdmin")]549 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {550 let user = T::CrossAccountId::from_eth(user);551 Ok(self.is_owner_or_admin(&user))552 }553554 /// Check that substrate account is the owner or admin of the collection555 ///556 /// @param user account to verify557 /// @return "true" if account is the owner or admin558 fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {559 let user = convert_uint256_to_cross_account::<T>(user);560 Ok(self.is_owner_or_admin(&user))561 }562563 /// Returns collection type564 ///565 /// @return `Fungible` or `NFT` or `ReFungible`566 fn unique_collection_type(&self) -> Result<string> {567 let mode = match self.collection.mode {568 CollectionMode::Fungible(_) => "Fungible",569 CollectionMode::NFT => "NFT",570 CollectionMode::ReFungible => "ReFungible",571 };572 Ok(mode.into())573 }574575 /// Get collection owner.576 ///577 /// @return Tuble with sponsor address and his substrate mirror.578 /// If address is canonical then substrate mirror is zero and vice versa.579 fn collection_owner(&self) -> Result<(address, uint256)> {580 Ok(convert_cross_account_to_tuple::<T>(581 &T::CrossAccountId::from_sub(self.owner.clone()),582 ))583 }584585 /// Changes collection owner to another account586 ///587 /// @dev Owner can be changed only by current owner588 /// @param newOwner new owner account589 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {590 self.consume_store_writes(1)?;591592 let caller = T::CrossAccountId::from_eth(caller);593 let new_owner = T::CrossAccountId::from_eth(new_owner);594 self.set_owner_internal(caller, new_owner)595 .map_err(dispatch_to_evm::<T>)596 }597598 /// Changes collection owner to another substrate account599 ///600 /// @dev Owner can be changed only by current owner601 /// @param newOwner new owner substrate account602 fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {603 self.consume_store_writes(1)?;604605 let caller = T::CrossAccountId::from_eth(caller);606 let new_owner = convert_uint256_to_cross_account::<T>(new_owner);607 self.set_owner_internal(caller, new_owner)608 .map_err(dispatch_to_evm::<T>)609 }610611 // TODO: need implement AbiWriter for &Vec<T>612 // fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {613 // let result = pallet_common::IsAdmin::<T>::iter_prefix((self.id,))614 // .map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::<T>(&admin))615 // .collect();616 // Ok(result)617 // }618}619620/// ### Note621/// Do not forget to add: `self.consume_store_reads(1)?;`622fn check_is_owner_or_admin<T: Config>(623 caller: caller,624 collection: &CollectionHandle<T>,625) -> Result<T::CrossAccountId> {626 let caller = T::CrossAccountId::from_eth(caller);627 collection628 .check_is_owner_or_admin(&caller)629 .map_err(dispatch_to_evm::<T>)?;630 Ok(caller)631}632633/// ### Note634/// Do not forget to add: `self.consume_store_writes(1)?;`635fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {636 collection637 .check_is_internal()638 .map_err(dispatch_to_evm::<T>)?;639 collection.save().map_err(dispatch_to_evm::<T>)?;640 Ok(())641}642643/// Contains static property keys and values.644pub mod static_property {645 use evm_coder::{646 execution::{Result, Error},647 };648 use alloc::format;649650 const EXPECT_CONVERT_ERROR: &str = "length < limit";651652 /// Keys.653 pub mod key {654 use super::*;655656 /// Key "schemaName".657 pub fn schema_name() -> up_data_structs::PropertyKey {658 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)659 }660661 /// Key "baseURI".662 pub fn base_uri() -> up_data_structs::PropertyKey {663 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)664 }665666 /// Key "url".667 pub fn url() -> up_data_structs::PropertyKey {668 property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)669 }670671 /// Key "suffix".672 pub fn suffix() -> up_data_structs::PropertyKey {673 property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)674 }675676 /// Key "parentNft".677 pub fn parent_nft() -> up_data_structs::PropertyKey {678 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)679 }680 }681682 /// Values.683 pub mod value {684 use super::*;685686 /// Value "ERC721Metadata".687 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";688689 /// Value for [`ERC721_METADATA`].690 pub fn erc721() -> up_data_structs::PropertyValue {691 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)692 }693 }694695 /// Convert `byte` to [`PropertyKey`].696 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {697 bytes.to_vec().try_into().map_err(|_| {698 Error::Revert(format!(699 "Property key is too long. Max length is {}.",700 up_data_structs::PropertyKey::bound()701 ))702 })703 }704705 /// Convert `bytes` to [`PropertyValue`].706 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {707 bytes.to_vec().try_into().map_err(|_| {708 Error::Revert(format!(709 "Property key is too long. Max length is {}.",710 up_data_structs::PropertyKey::bound()711 ))712 })713 }714}