12345678910111213141516171819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use pallet_evm_coder_substrate::{21 abi::AbiType,22 solidity_interface, ToLog,23 types::*,24 execution::{Result, Error, PreDispatch},25 frontier_contract,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,31 SponsorshipState,32};3334use crate::{35 Pallet, CollectionHandle, Config, CollectionProperties, eth, SelfWeightOf, weights::WeightInfo,36};3738frontier_contract! {39 macro_rules! CollectionHandle_result {...}40 impl<T: Config> Contract for CollectionHandle<T> {...}41}424344#[derive(ToLog)]45pub enum CollectionHelpersEvents {46 47 CollectionCreated {48 49 #[indexed]50 owner: Address,5152 53 #[indexed]54 collection_id: Address,55 },56 57 CollectionDestroyed {58 59 #[indexed]60 collection_id: Address,61 },62 63 CollectionChanged {64 65 #[indexed]66 collection_id: Address,67 },68}69707172pub trait CommonEvmHandler {73 74 const CODE: &'static [u8];7576 77 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;78}798081#[solidity_interface(name = Collection, enum(derive(PreDispatch)), enum_attr(weight))]82impl<T: Config> CollectionHandle<T>83where84 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,85{86 87 88 89 90 #[solidity(hide)]91 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]92 fn set_collection_property(&mut self, caller: Caller, key: String, value: Bytes) -> Result<()> {93 let caller = T::CrossAccountId::from_eth(caller);94 let key = <Vec<u8>>::from(key)95 .try_into()96 .map_err(|_| "key too large")?;97 let value = value.0.try_into().map_err(|_| "value too large")?;9899 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })100 .map_err(dispatch_to_evm::<T>)101 }102103 104 105 106 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]107 fn set_collection_properties(108 &mut self,109 caller: Caller,110 properties: Vec<eth::Property>,111 ) -> Result<()> {112 let caller = T::CrossAccountId::from_eth(caller);113114 let properties = properties115 .into_iter()116 .map(eth::Property::try_into)117 .collect::<Result<Vec<_>>>()?;118119 <Pallet<T>>::set_collection_properties(self, &caller, properties.into_iter())120 .map_err(dispatch_to_evm::<T>)121 }122123 124 125 126 #[solidity(hide)]127 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]128 fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {129 let caller = T::CrossAccountId::from_eth(caller);130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too large")?;133134 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)135 }136137 138 139 140 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]141 fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {142 let caller = T::CrossAccountId::from_eth(caller);143 let keys = keys144 .into_iter()145 .map(|key| {146 <Vec<u8>>::from(key)147 .try_into()148 .map_err(|_| Error::Revert("key too large".into()))149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::delete_collection_properties(self, &caller, keys.into_iter())153 .map_err(dispatch_to_evm::<T>)154 }155156 157 158 159 160 161 162 fn collection_property(&self, key: String) -> Result<Bytes> {163 let key = <Vec<u8>>::from(key)164 .try_into()165 .map_err(|_| "key too large")?;166167 let props = CollectionProperties::<T>::get(self.id);168 let prop = props.get(&key).ok_or("key not found")?;169170 Ok(Bytes(prop.to_vec()))171 }172173 174 175 176 177 fn collection_properties(&self, keys: Vec<String>) -> Result<Vec<eth::Property>> {178 let keys = keys179 .into_iter()180 .map(|key| {181 <Vec<u8>>::from(key)182 .try_into()183 .map_err(|_| Error::Revert("key too large".into()))184 })185 .collect::<Result<Vec<_>>>()?;186187 let properties = Pallet::<T>::filter_collection_properties(188 self.id,189 if keys.is_empty() { None } else { Some(keys) },190 )191 .map_err(dispatch_to_evm::<T>)?;192193 let properties = properties194 .into_iter()195 .map(Property::try_into)196 .collect::<Result<Vec<_>>>()?;197 Ok(properties)198 }199200 201 202 203 204 205 #[solidity(hide)]206 fn set_collection_sponsor(&mut self, caller: Caller, sponsor: Address) -> Result<()> {207 self.consume_store_reads_and_writes(1, 1)?;208209 let caller = T::CrossAccountId::from_eth(caller);210211 let sponsor = T::CrossAccountId::from_eth(sponsor);212 self.set_sponsor(&caller, sponsor.as_sub().clone())213 .map_err(dispatch_to_evm::<T>)214 }215216 217 218 219 220 221 fn set_collection_sponsor_cross(222 &mut self,223 caller: Caller,224 sponsor: eth::CrossAddress,225 ) -> Result<()> {226 self.consume_store_reads_and_writes(1, 1)?;227228 let caller = T::CrossAccountId::from_eth(caller);229230 let sponsor = sponsor.into_sub_cross_account::<T>()?;231 self.set_sponsor(&caller, sponsor.as_sub().clone())232 .map_err(dispatch_to_evm::<T>)233 }234235 236 fn has_collection_pending_sponsor(&self) -> Result<bool> {237 Ok(matches!(238 self.collection.sponsorship,239 SponsorshipState::Unconfirmed(_)240 ))241 }242243 244 245 246 fn confirm_collection_sponsorship(&mut self, caller: Caller) -> Result<()> {247 self.consume_store_writes(1)?;248249 let caller = T::CrossAccountId::from_eth(caller);250 self.confirm_sponsorship(caller.as_sub())251 .map_err(dispatch_to_evm::<T>)252 }253254 255 fn remove_collection_sponsor(&mut self, caller: Caller) -> Result<()> {256 self.consume_store_reads_and_writes(1, 1)?;257 let caller = T::CrossAccountId::from_eth(caller);258 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)259 }260261 262 263 264 fn collection_sponsor(&self) -> Result<eth::CrossAddress> {265 let sponsor = match self.collection.sponsorship.sponsor() {266 Some(sponsor) => sponsor,267 None => return Ok(Default::default()),268 };269270 Ok(eth::CrossAddress::from_sub::<T>(sponsor))271 }272273 274 275 276 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {277 let limits = &self.collection.limits;278279 Ok(vec![280 eth::CollectionLimit::new(281 eth::CollectionLimitField::AccountTokenOwnership,282 limits.account_token_ownership_limit,283 ),284 eth::CollectionLimit::new(285 eth::CollectionLimitField::SponsoredDataSize,286 limits.sponsored_data_size,287 ),288 limits289 .sponsored_data_rate_limit290 .and_then(|limit| {291 if let SponsoringRateLimit::Blocks(blocks) = limit {292 Some(eth::CollectionLimit::new(293 eth::CollectionLimitField::SponsoredDataRateLimit,294 Some(blocks),295 ))296 } else {297 None298 }299 })300 .unwrap_or_else(|| {301 eth::CollectionLimit::new(302 eth::CollectionLimitField::SponsoredDataRateLimit,303 Default::default(),304 )305 }),306 eth::CollectionLimit::new(eth::CollectionLimitField::TokenLimit, limits.token_limit),307 eth::CollectionLimit::new(308 eth::CollectionLimitField::SponsorTransferTimeout,309 limits.sponsor_transfer_timeout,310 ),311 eth::CollectionLimit::new(312 eth::CollectionLimitField::SponsorApproveTimeout,313 limits.sponsor_approve_timeout,314 ),315 eth::CollectionLimit::new(316 eth::CollectionLimitField::OwnerCanTransfer,317 limits.owner_can_transfer.map(u32::from),318 ),319 eth::CollectionLimit::new(320 eth::CollectionLimitField::OwnerCanDestroy,321 limits.owner_can_destroy.map(u32::from),322 ),323 eth::CollectionLimit::new(324 eth::CollectionLimitField::TransferEnabled,325 limits.transfers_enabled.map(u32::from),326 ),327 ])328 }329330 331 332 333 #[solidity(rename_selector = "setCollectionLimit")]334 fn set_collection_limit(&mut self, caller: Caller, limit: eth::CollectionLimit) -> Result<()> {335 self.consume_store_reads_and_writes(1, 1)?;336337 if !limit.has_value() {338 return Err(Error::Revert("user can't disable limits".into()));339 }340341 let caller = T::CrossAccountId::from_eth(caller);342 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)343 }344345 346 fn contract_address(&self) -> Result<Address> {347 Ok(crate::eth::collection_id_to_address(self.id))348 }349350 351 352 fn add_collection_admin_cross(353 &mut self,354 caller: Caller,355 new_admin: eth::CrossAddress,356 ) -> Result<()> {357 self.consume_store_reads_and_writes(2, 2)?;358359 let caller = T::CrossAccountId::from_eth(caller);360 let new_admin = new_admin.into_sub_cross_account::<T>()?;361 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;362 Ok(())363 }364365 366 367 fn remove_collection_admin_cross(368 &mut self,369 caller: Caller,370 admin: eth::CrossAddress,371 ) -> Result<()> {372 self.consume_store_reads_and_writes(2, 2)?;373374 let caller = T::CrossAccountId::from_eth(caller);375 let admin = admin.into_sub_cross_account::<T>()?;376 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;377 Ok(())378 }379380 381 382 #[solidity(hide)]383 fn add_collection_admin(&mut self, caller: Caller, new_admin: Address) -> Result<()> {384 self.consume_store_reads_and_writes(2, 2)?;385386 let caller = T::CrossAccountId::from_eth(caller);387 let new_admin = T::CrossAccountId::from_eth(new_admin);388 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;389 Ok(())390 }391392 393 394 395 #[solidity(hide)]396 fn remove_collection_admin(&mut self, caller: Caller, admin: Address) -> Result<()> {397 self.consume_store_reads_and_writes(2, 2)?;398399 let caller = T::CrossAccountId::from_eth(caller);400 let admin = T::CrossAccountId::from_eth(admin);401 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;402 Ok(())403 }404405 #[solidity(rename_selector = "setCollectionNesting")]406 fn set_nesting(407 &mut self,408 caller: Caller,409 collection_nesting_and_permissions: eth::CollectionNestingAndPermission,410 ) -> Result<()> {411 self.consume_store_reads_and_writes(1, 1)?;412413 let caller = T::CrossAccountId::from_eth(caller);414415 let mut permissions = self.collection.permissions.clone();416 let mut nesting = permissions.nesting().clone();417418 let bv = if !collection_nesting_and_permissions.restricted.is_empty() {419 let mut bv = OwnerRestrictedSet::new();420 for address in collection_nesting_and_permissions.restricted.iter() {421 bv.try_insert(crate::eth::map_eth_to_id(address).ok_or_else(|| {422 Error::Revert("Can't convert address into collection id".into())423 })?)424 .map_err(|_| "too many collections")?;425 }426 Some(bv)427 } else {428 None429 };430431 nesting.token_owner = collection_nesting_and_permissions.token_owner;432 nesting.collection_admin = collection_nesting_and_permissions.collection_admin;433 nesting.restricted = bv;434 permissions.nesting = Some(nesting);435436 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)437 }438439 440 441 442 #[solidity(hide, rename_selector = "setCollectionNesting")]443 fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> {444 self.consume_store_reads_and_writes(1, 1)?;445446 let caller = T::CrossAccountId::from_eth(caller);447448 let mut permissions = self.collection.permissions.clone();449 let mut nesting = permissions.nesting().clone();450 nesting.token_owner = enable;451 nesting.restricted = None;452 permissions.nesting = Some(nesting);453454 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)455 }456457 458 459 460 461 #[solidity(hide, rename_selector = "setCollectionNesting")]462 fn set_nesting_collection_ids(463 &mut self,464 caller: Caller,465 enable: bool,466 collections: Vec<Address>,467 ) -> Result<()> {468 self.consume_store_reads_and_writes(1, 1)?;469470 if collections.is_empty() {471 return Err("no addresses provided".into());472 }473 let caller = T::CrossAccountId::from_eth(caller);474475 let mut permissions = self.collection.permissions.clone();476 match enable {477 false => {478 let mut nesting = permissions.nesting().clone();479 nesting.token_owner = false;480 nesting.restricted = None;481 permissions.nesting = Some(nesting);482 }483 true => {484 let mut bv = OwnerRestrictedSet::new();485 for i in collections {486 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {487 Error::Revert("Can't convert address into collection id".into())488 })?)489 .map_err(|_| "too many collections")?;490 }491 let mut nesting = permissions.nesting().clone();492 nesting.token_owner = true;493 nesting.restricted = Some(bv);494 permissions.nesting = Some(nesting);495 }496 };497498 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)499 }500501 #[solidity(rename_selector = "collectionNesting")]502 fn collection_nesting(&self) -> Result<eth::CollectionNestingAndPermission> {503 let nesting = self.collection.permissions.nesting();504505 Ok(eth::CollectionNestingAndPermission::new(506 nesting.token_owner,507 nesting.collection_admin,508 nesting509 .restricted510 .clone()511 .map(|b| {512 b.0.into_inner()513 .iter()514 .map(|id| crate::eth::collection_id_to_address(id.0.into()))515 .collect()516 })517 .unwrap_or_default(),518 ))519 }520521 522 #[solidity(hide, rename_selector = "collectionNestingRestrictedCollectionIds")]523 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {524 let nesting = self.collection.permissions.nesting();525526 Ok(eth::CollectionNesting::new(527 nesting.token_owner,528 nesting529 .restricted530 .clone()531 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())532 .unwrap_or_default(),533 ))534 }535536 537 #[solidity(hide)]538 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {539 let nesting = self.collection.permissions.nesting();540 Ok(vec![541 eth::CollectionNestingPermission::new(542 eth::CollectionPermissionField::CollectionAdmin,543 nesting.collection_admin,544 ),545 eth::CollectionNestingPermission::new(546 eth::CollectionPermissionField::TokenOwner,547 nesting.token_owner,548 ),549 ])550 }551 552 553 fn set_collection_access(&mut self, caller: Caller, mode: eth::AccessMode) -> Result<()> {554 self.consume_store_reads_and_writes(1, 1)?;555556 let caller = T::CrossAccountId::from_eth(caller);557 let permissions = CollectionPermissions {558 access: Some(mode.into()),559 ..Default::default()560 };561 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)562 }563564 565 566 567 fn allowlisted_cross(&self, user: eth::CrossAddress) -> Result<bool> {568 let user = user.into_sub_cross_account::<T>()?;569 Ok(Pallet::<T>::allowed(self.id, user))570 }571572 573 574 575 #[solidity(hide)]576 fn add_to_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {577 self.consume_store_writes(1)?;578579 let caller = T::CrossAccountId::from_eth(caller);580 let user = T::CrossAccountId::from_eth(user);581 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;582 Ok(())583 }584585 586 587 588 fn add_to_collection_allow_list_cross(589 &mut self,590 caller: Caller,591 user: eth::CrossAddress,592 ) -> Result<()> {593 self.consume_store_writes(1)?;594595 let caller = T::CrossAccountId::from_eth(caller);596 let user = user.into_sub_cross_account::<T>()?;597 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;598 Ok(())599 }600601 602 603 604 #[solidity(hide)]605 fn remove_from_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {606 self.consume_store_writes(1)?;607608 let caller = T::CrossAccountId::from_eth(caller);609 let user = T::CrossAccountId::from_eth(user);610 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;611 Ok(())612 }613614 615 616 617 fn remove_from_collection_allow_list_cross(618 &mut self,619 caller: Caller,620 user: eth::CrossAddress,621 ) -> Result<()> {622 self.consume_store_writes(1)?;623624 let caller = T::CrossAccountId::from_eth(caller);625 let user = user.into_sub_cross_account::<T>()?;626 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;627 Ok(())628 }629630 631 632 633 fn set_collection_mint_mode(&mut self, caller: Caller, mode: bool) -> Result<()> {634 self.consume_store_reads_and_writes(1, 1)?;635636 let caller = T::CrossAccountId::from_eth(caller);637 let permissions = CollectionPermissions {638 mint_mode: Some(mode),639 ..Default::default()640 };641 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)642 }643644 645 646 647 648 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]649 fn is_owner_or_admin_eth(&self, user: Address) -> Result<bool> {650 let user = T::CrossAccountId::from_eth(user);651 Ok(self.is_owner_or_admin(&user))652 }653654 655 656 657 658 fn is_owner_or_admin_cross(&self, user: eth::CrossAddress) -> Result<bool> {659 let user = user.into_sub_cross_account::<T>()?;660 Ok(self.is_owner_or_admin(&user))661 }662663 664 665 666 fn unique_collection_type(&self) -> Result<String> {667 let mode = match self.collection.mode {668 CollectionMode::Fungible(_) => "Fungible",669 CollectionMode::NFT => "NFT",670 CollectionMode::ReFungible => "ReFungible",671 };672 Ok(mode.into())673 }674675 676 677 678 679 fn collection_owner(&self) -> Result<eth::CrossAddress> {680 Ok(eth::CrossAddress::from_sub_cross_account::<T>(681 &T::CrossAccountId::from_sub(self.owner.clone()),682 ))683 }684685 686 687 688 689 #[solidity(hide, rename_selector = "changeCollectionOwner")]690 fn set_owner(&mut self, caller: Caller, new_owner: Address) -> Result<()> {691 self.consume_store_writes(1)?;692693 let caller = T::CrossAccountId::from_eth(caller);694 let new_owner = T::CrossAccountId::from_eth(new_owner);695 self.change_owner(caller, new_owner)696 .map_err(dispatch_to_evm::<T>)697 }698699 700 701 702 703 fn collection_admins(&self) -> Result<Vec<eth::CrossAddress>> {704 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))705 .map(|(admin, _)| eth::CrossAddress::from_sub_cross_account::<T>(&admin))706 .collect();707 Ok(result)708 }709710 711 712 713 714 fn change_collection_owner_cross(715 &mut self,716 caller: Caller,717 new_owner: eth::CrossAddress,718 ) -> Result<()> {719 self.consume_store_writes(1)?;720721 let caller = T::CrossAccountId::from_eth(caller);722 let new_owner = new_owner.into_sub_cross_account::<T>()?;723 self.change_owner(caller, new_owner)724 .map_err(dispatch_to_evm::<T>)725 }726}727728729pub mod static_property {730 use pallet_evm_coder_substrate::{731 execution::{Result, Error},732 };733 use alloc::format;734735 const EXPECT_CONVERT_ERROR: &str = "length < limit";736737 738 pub mod key {739 use super::*;740741 742 pub fn base_uri() -> up_data_structs::PropertyKey {743 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)744 }745746 747 pub fn url() -> up_data_structs::PropertyKey {748 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)749 }750751 752 pub fn suffix() -> up_data_structs::PropertyKey {753 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)754 }755756 757 pub fn parent_nft() -> up_data_structs::PropertyKey {758 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)759 }760 }761762 763 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {764 bytes.to_vec().try_into().map_err(|_| {765 Error::Revert(format!(766 "Property key is too long. Max length is {}.",767 up_data_structs::PropertyKey::bound()768 ))769 })770 }771772 773 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {774 bytes.to_vec().try_into().map_err(|_| {775 Error::Revert(format!(776 "Property key is too long. Max length is {}.",777 up_data_structs::PropertyKey::bound()778 ))779 })780 }781}