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 406 407 408 #[solidity(rename_selector = "setCollectionNesting")]409 fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> {410 self.consume_store_reads_and_writes(1, 1)?;411412 let caller = T::CrossAccountId::from_eth(caller);413414 let mut permissions = self.collection.permissions.clone();415 let mut nesting = permissions.nesting().clone();416 nesting.token_owner = enable;417 nesting.restricted = None;418 permissions.nesting = Some(nesting);419420 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)421 }422423 424 425 426 427 #[solidity(rename_selector = "setCollectionNesting")]428 fn set_nesting(429 &mut self,430 caller: Caller,431 enable: bool,432 collections: Vec<Address>,433 ) -> Result<()> {434 self.consume_store_reads_and_writes(1, 1)?;435436 if collections.is_empty() {437 return Err("no addresses provided".into());438 }439 let caller = T::CrossAccountId::from_eth(caller);440441 let mut permissions = self.collection.permissions.clone();442 match enable {443 false => {444 let mut nesting = permissions.nesting().clone();445 nesting.token_owner = false;446 nesting.restricted = None;447 permissions.nesting = Some(nesting);448 }449 true => {450 let mut bv = OwnerRestrictedSet::new();451 for i in collections {452 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {453 Error::Revert("Can't convert address into collection id".into())454 })?)455 .map_err(|_| "too many collections")?;456 }457 let mut nesting = permissions.nesting().clone();458 nesting.token_owner = true;459 nesting.restricted = Some(bv);460 permissions.nesting = Some(nesting);461 }462 };463464 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)465 }466467 468 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]469 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {470 let nesting = self.collection.permissions.nesting();471472 Ok(eth::CollectionNesting::new(473 nesting.token_owner,474 nesting475 .restricted476 .clone()477 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())478 .unwrap_or_default(),479 ))480 }481482 483 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {484 let nesting = self.collection.permissions.nesting();485 Ok(vec![486 eth::CollectionNestingPermission::new(487 eth::CollectionPermissionField::CollectionAdmin,488 nesting.collection_admin,489 ),490 eth::CollectionNestingPermission::new(491 eth::CollectionPermissionField::TokenOwner,492 nesting.token_owner,493 ),494 ])495 }496 497 498 fn set_collection_access(&mut self, caller: Caller, mode: eth::AccessMode) -> Result<()> {499 self.consume_store_reads_and_writes(1, 1)?;500501 let caller = T::CrossAccountId::from_eth(caller);502 let permissions = CollectionPermissions {503 access: Some(mode.into()),504 ..Default::default()505 };506 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)507 }508509 510 511 512 fn allowlisted_cross(&self, user: eth::CrossAddress) -> Result<bool> {513 let user = user.into_sub_cross_account::<T>()?;514 Ok(Pallet::<T>::allowed(self.id, user))515 }516517 518 519 520 #[solidity(hide)]521 fn add_to_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {522 self.consume_store_writes(1)?;523524 let caller = T::CrossAccountId::from_eth(caller);525 let user = T::CrossAccountId::from_eth(user);526 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;527 Ok(())528 }529530 531 532 533 fn add_to_collection_allow_list_cross(534 &mut self,535 caller: Caller,536 user: eth::CrossAddress,537 ) -> Result<()> {538 self.consume_store_writes(1)?;539540 let caller = T::CrossAccountId::from_eth(caller);541 let user = user.into_sub_cross_account::<T>()?;542 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;543 Ok(())544 }545546 547 548 549 #[solidity(hide)]550 fn remove_from_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {551 self.consume_store_writes(1)?;552553 let caller = T::CrossAccountId::from_eth(caller);554 let user = T::CrossAccountId::from_eth(user);555 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558559 560 561 562 fn remove_from_collection_allow_list_cross(563 &mut self,564 caller: Caller,565 user: eth::CrossAddress,566 ) -> Result<()> {567 self.consume_store_writes(1)?;568569 let caller = T::CrossAccountId::from_eth(caller);570 let user = user.into_sub_cross_account::<T>()?;571 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;572 Ok(())573 }574575 576 577 578 fn set_collection_mint_mode(&mut self, caller: Caller, mode: bool) -> Result<()> {579 self.consume_store_reads_and_writes(1, 1)?;580581 let caller = T::CrossAccountId::from_eth(caller);582 let permissions = CollectionPermissions {583 mint_mode: Some(mode),584 ..Default::default()585 };586 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)587 }588589 590 591 592 593 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]594 fn is_owner_or_admin_eth(&self, user: Address) -> Result<bool> {595 let user = T::CrossAccountId::from_eth(user);596 Ok(self.is_owner_or_admin(&user))597 }598599 600 601 602 603 fn is_owner_or_admin_cross(&self, user: eth::CrossAddress) -> Result<bool> {604 let user = user.into_sub_cross_account::<T>()?;605 Ok(self.is_owner_or_admin(&user))606 }607608 609 610 611 fn unique_collection_type(&self) -> Result<String> {612 let mode = match self.collection.mode {613 CollectionMode::Fungible(_) => "Fungible",614 CollectionMode::NFT => "NFT",615 CollectionMode::ReFungible => "ReFungible",616 };617 Ok(mode.into())618 }619620 621 622 623 624 fn collection_owner(&self) -> Result<eth::CrossAddress> {625 Ok(eth::CrossAddress::from_sub_cross_account::<T>(626 &T::CrossAccountId::from_sub(self.owner.clone()),627 ))628 }629630 631 632 633 634 #[solidity(hide, rename_selector = "changeCollectionOwner")]635 fn set_owner(&mut self, caller: Caller, new_owner: Address) -> Result<()> {636 self.consume_store_writes(1)?;637638 let caller = T::CrossAccountId::from_eth(caller);639 let new_owner = T::CrossAccountId::from_eth(new_owner);640 self.change_owner(caller, new_owner)641 .map_err(dispatch_to_evm::<T>)642 }643644 645 646 647 648 fn collection_admins(&self) -> Result<Vec<eth::CrossAddress>> {649 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))650 .map(|(admin, _)| eth::CrossAddress::from_sub_cross_account::<T>(&admin))651 .collect();652 Ok(result)653 }654655 656 657 658 659 fn change_collection_owner_cross(660 &mut self,661 caller: Caller,662 new_owner: eth::CrossAddress,663 ) -> Result<()> {664 self.consume_store_writes(1)?;665666 let caller = T::CrossAccountId::from_eth(caller);667 let new_owner = new_owner.into_sub_cross_account::<T>()?;668 self.change_owner(caller, new_owner)669 .map_err(dispatch_to_evm::<T>)670 }671}672673674pub mod static_property {675 use pallet_evm_coder_substrate::{676 execution::{Result, Error},677 };678 use alloc::format;679680 const EXPECT_CONVERT_ERROR: &str = "length < limit";681682 683 pub mod key {684 use super::*;685686 687 pub fn base_uri() -> up_data_structs::PropertyKey {688 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)689 }690691 692 pub fn url() -> up_data_structs::PropertyKey {693 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)694 }695696 697 pub fn suffix() -> up_data_structs::PropertyKey {698 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)699 }700701 702 pub fn parent_nft() -> up_data_structs::PropertyKey {703 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)704 }705 }706707 708 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {709 bytes.to_vec().try_into().map_err(|_| {710 Error::Revert(format!(711 "Property key is too long. Max length is {}.",712 up_data_structs::PropertyKey::bound()713 ))714 })715 }716717 718 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {719 bytes.to_vec().try_into().map_err(|_| {720 Error::Revert(format!(721 "Property key is too long. Max length is {}.",722 up_data_structs::PropertyKey::bound()723 ))724 })725 }726}