12345678910111213141516171819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use evm_coder::{21 abi::AbiType,22 solidity_interface, solidity, ToLog,23 types::*,24 execution::{Result, Error},25 weight,26};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::{vec, vec::Vec};29use up_data_structs::{30 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,31 SponsoringRateLimit, SponsorshipState,32};33use alloc::format;3435use crate::{36 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,37 eth::{38 Property as PropertyStruct, EthCrossAccount, CollectionPermissions as EvmPermissions,39 CollectionLimitField as EvmCollectionLimits, self,40 },41 weights::WeightInfo,42};434445#[derive(ToLog)]46pub enum CollectionHelpersEvents {47 48 CollectionCreated {49 50 #[indexed]51 owner: address,5253 54 #[indexed]55 collection_id: address,56 },57 58 CollectionDestroyed {59 60 #[indexed]61 collection_id: address,62 },63 64 CollectionChanged {65 66 #[indexed]67 collection_id: address,68 },6970 71 TokenChanged {72 73 #[indexed]74 collection_id: address,75 76 token_id: uint256,77 },78}79808182pub trait CommonEvmHandler {83 84 const CODE: &'static [u8];8586 87 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;88}899091#[solidity_interface(name = Collection)]92impl<T: Config> CollectionHandle<T>93where94 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,95{96 97 98 99 100 #[solidity(hide)]101 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]102 fn set_collection_property(103 &mut self,104 caller: caller,105 key: string,106 value: bytes,107 ) -> Result<void> {108 let caller = T::CrossAccountId::from_eth(caller);109 let key = <Vec<u8>>::from(key)110 .try_into()111 .map_err(|_| "key too large")?;112 let value = value.0.try_into().map_err(|_| "value too large")?;113114 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })115 .map_err(dispatch_to_evm::<T>)116 }117118 119 120 121 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]122 fn set_collection_properties(123 &mut self,124 caller: caller,125 properties: Vec<PropertyStruct>,126 ) -> Result<void> {127 let caller = T::CrossAccountId::from_eth(caller);128129 let properties = properties130 .into_iter()131 .map(|PropertyStruct { key, value }| {132 let key = <Vec<u8>>::from(key)133 .try_into()134 .map_err(|_| "key too large")?;135136 let value = value.0.try_into().map_err(|_| "value too large")?;137138 Ok(Property { key, value })139 })140 .collect::<Result<Vec<_>>>()?;141142 <Pallet<T>>::set_collection_properties(self, &caller, properties)143 .map_err(dispatch_to_evm::<T>)144 }145146 147 148 149 #[solidity(hide)]150 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]151 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {152 let caller = T::CrossAccountId::from_eth(caller);153 let key = <Vec<u8>>::from(key)154 .try_into()155 .map_err(|_| "key too large")?;156157 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)158 }159160 161 162 163 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]164 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {165 let caller = T::CrossAccountId::from_eth(caller);166 let keys = keys167 .into_iter()168 .map(|key| {169 <Vec<u8>>::from(key)170 .try_into()171 .map_err(|_| Error::Revert("key too large".into()))172 })173 .collect::<Result<Vec<_>>>()?;174175 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)176 }177178 179 180 181 182 183 184 fn collection_property(&self, key: string) -> Result<bytes> {185 let key = <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| "key too large")?;188189 let props = CollectionProperties::<T>::get(self.id);190 let prop = props.get(&key).ok_or("key not found")?;191192 Ok(bytes(prop.to_vec()))193 }194195 196 197 198 199 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {200 let keys = keys201 .into_iter()202 .map(|key| {203 <Vec<u8>>::from(key)204 .try_into()205 .map_err(|_| Error::Revert("key too large".into()))206 })207 .collect::<Result<Vec<_>>>()?;208209 let properties = Pallet::<T>::filter_collection_properties(210 self.id,211 if keys.is_empty() { None } else { Some(keys) },212 )213 .map_err(dispatch_to_evm::<T>)?;214215 let properties = properties216 .into_iter()217 .map(|p| {218 let key =219 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;220 let value = bytes(p.value.to_vec());221 Ok(PropertyStruct { key, value })222 })223 .collect::<Result<Vec<_>>>()?;224 Ok(properties)225 }226227 228 229 230 231 232 #[solidity(hide)]233 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {234 self.consume_store_reads_and_writes(1, 1)?;235236 let caller = T::CrossAccountId::from_eth(caller);237238 let sponsor = T::CrossAccountId::from_eth(sponsor);239 self.set_sponsor(&caller, sponsor.as_sub().clone())240 .map_err(dispatch_to_evm::<T>)241 }242243 244 245 246 247 248 fn set_collection_sponsor_cross(249 &mut self,250 caller: caller,251 sponsor: EthCrossAccount,252 ) -> Result<void> {253 self.consume_store_reads_and_writes(1, 1)?;254255 let caller = T::CrossAccountId::from_eth(caller);256257 let sponsor = sponsor.into_sub_cross_account::<T>()?;258 self.set_sponsor(&caller, sponsor.as_sub().clone())259 .map_err(dispatch_to_evm::<T>)260 }261262 263 fn has_collection_pending_sponsor(&self) -> Result<bool> {264 Ok(matches!(265 self.collection.sponsorship,266 SponsorshipState::Unconfirmed(_)267 ))268 }269270 271 272 273 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {274 self.consume_store_writes(1)?;275276 let caller = T::CrossAccountId::from_eth(caller);277 self.confirm_sponsorship(caller.as_sub())278 .map_err(dispatch_to_evm::<T>)279 }280281 282 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {283 self.consume_store_reads_and_writes(1, 1)?;284 let caller = T::CrossAccountId::from_eth(caller);285 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)286 }287288 289 290 291 fn collection_sponsor(&self) -> Result<EthCrossAccount> {292 let sponsor = match self.collection.sponsorship.sponsor() {293 Some(sponsor) => sponsor,294 None => return Ok(Default::default()),295 };296297 Ok(EthCrossAccount::from_sub::<T>(&sponsor))298 }299300 301 302 303 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {304 let limits = &self.collection.limits;305306 Ok(vec![307 eth::CollectionLimit::from_opt_int(308 EvmCollectionLimits::AccountTokenOwnership,309 limits.account_token_ownership_limit,310 ),311 eth::CollectionLimit::from_opt_int(312 EvmCollectionLimits::SponsoredDataSize,313 limits.sponsored_data_size,314 ),315 limits316 .sponsored_data_rate_limit317 .and_then(|limit| {318 if let SponsoringRateLimit::Blocks(blocks) = limit {319 Some(eth::CollectionLimit::from_int(320 EvmCollectionLimits::SponsoredDataRateLimit,321 blocks,322 ))323 } else {324 None325 }326 })327 .unwrap_or(eth::CollectionLimit::from_int(328 EvmCollectionLimits::SponsoredDataRateLimit,329 Default::default(),330 )),331 eth::CollectionLimit::from_opt_int(EvmCollectionLimits::TokenLimit, limits.token_limit),332 eth::CollectionLimit::from_opt_int(333 EvmCollectionLimits::SponsorTransferTimeout,334 limits.sponsor_transfer_timeout,335 ),336 eth::CollectionLimit::from_opt_int(337 EvmCollectionLimits::SponsorApproveTimeout,338 limits.sponsor_approve_timeout,339 ),340 eth::CollectionLimit::from_opt_bool(341 EvmCollectionLimits::OwnerCanTransfer,342 limits.owner_can_transfer,343 ),344 eth::CollectionLimit::from_opt_bool(345 EvmCollectionLimits::OwnerCanDestroy,346 limits.owner_can_destroy,347 ),348 eth::CollectionLimit::from_opt_bool(349 EvmCollectionLimits::TransferEnabled,350 limits.transfers_enabled,351 ),352 ])353 }354355 356 357 358 #[solidity(rename_selector = "setCollectionLimit")]359 fn set_collection_limit(360 &mut self,361 caller: caller,362 limit: eth::CollectionLimit,363 ) -> Result<void> {364 self.consume_store_reads_and_writes(1, 1)?;365366 let caller = T::CrossAccountId::from_eth(caller);367 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)368 }369370 371 fn contract_address(&self) -> Result<address> {372 Ok(crate::eth::collection_id_to_address(self.id))373 }374375 376 377 fn add_collection_admin_cross(378 &mut self,379 caller: caller,380 new_admin: EthCrossAccount,381 ) -> Result<void> {382 self.consume_store_reads_and_writes(2, 2)?;383384 let caller = T::CrossAccountId::from_eth(caller);385 let new_admin = new_admin.into_sub_cross_account::<T>()?;386 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;387 Ok(())388 }389390 391 392 fn remove_collection_admin_cross(393 &mut self,394 caller: caller,395 admin: EthCrossAccount,396 ) -> Result<void> {397 self.consume_store_reads_and_writes(2, 2)?;398399 let caller = T::CrossAccountId::from_eth(caller);400 let admin = admin.into_sub_cross_account::<T>()?;401 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;402 Ok(())403 }404405 406 407 #[solidity(hide)]408 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {409 self.consume_store_reads_and_writes(2, 2)?;410411 let caller = T::CrossAccountId::from_eth(caller);412 let new_admin = T::CrossAccountId::from_eth(new_admin);413 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;414 Ok(())415 }416417 418 419 420 #[solidity(hide)]421 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {422 self.consume_store_reads_and_writes(2, 2)?;423424 let caller = T::CrossAccountId::from_eth(caller);425 let admin = T::CrossAccountId::from_eth(admin);426 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;427 Ok(())428 }429430 431 432 433 #[solidity(rename_selector = "setCollectionNesting")]434 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {435 self.consume_store_reads_and_writes(1, 1)?;436437 let caller = T::CrossAccountId::from_eth(caller);438439 let mut permissions = self.collection.permissions.clone();440 let mut nesting = permissions.nesting().clone();441 nesting.token_owner = enable;442 nesting.restricted = None;443 permissions.nesting = Some(nesting);444445 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)446 }447448 449 450 451 452 #[solidity(rename_selector = "setCollectionNesting")]453 fn set_nesting(454 &mut self,455 caller: caller,456 enable: bool,457 collections: Vec<address>,458 ) -> Result<void> {459 self.consume_store_reads_and_writes(1, 1)?;460461 if collections.is_empty() {462 return Err("no addresses provided".into());463 }464 let caller = T::CrossAccountId::from_eth(caller);465466 let mut permissions = self.collection.permissions.clone();467 match enable {468 false => {469 let mut nesting = permissions.nesting().clone();470 nesting.token_owner = false;471 nesting.restricted = None;472 permissions.nesting = Some(nesting);473 }474 true => {475 let mut bv = OwnerRestrictedSet::new();476 for i in collections {477 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {478 Error::Revert("Can't convert address into collection id".into())479 })?)480 .map_err(|_| "too many collections")?;481 }482 let mut nesting = permissions.nesting().clone();483 nesting.token_owner = true;484 nesting.restricted = Some(bv);485 permissions.nesting = Some(nesting);486 }487 };488489 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)490 }491492 493 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]494 fn collection_nesting_restricted_ids(&self) -> Result<(bool, Vec<uint256>)> {495 let nesting = self.collection.permissions.nesting();496497 Ok((498 nesting.token_owner,499 nesting500 .restricted501 .clone()502 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())503 .unwrap_or_default(),504 ))505 }506507 508 fn collection_nesting_permissions(&self) -> Result<Vec<(EvmPermissions, bool)>> {509 let nesting = self.collection.permissions.nesting();510 Ok(vec![511 (EvmPermissions::CollectionAdmin, nesting.collection_admin),512 (EvmPermissions::TokenOwner, nesting.token_owner),513 ])514 }515 516 517 518 519 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {520 self.consume_store_reads_and_writes(1, 1)?;521522 let caller = T::CrossAccountId::from_eth(caller);523 let permissions = CollectionPermissions {524 access: Some(match mode {525 0 => AccessMode::Normal,526 1 => AccessMode::AllowList,527 _ => return Err("not supported access mode".into()),528 }),529 ..Default::default()530 };531 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)532 }533534 535 536 537 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {538 let user = user.into_sub_cross_account::<T>()?;539 Ok(Pallet::<T>::allowed(self.id, user))540 }541542 543 544 545 #[solidity(hide)]546 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {547 self.consume_store_writes(1)?;548549 let caller = T::CrossAccountId::from_eth(caller);550 let user = T::CrossAccountId::from_eth(user);551 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;552 Ok(())553 }554555 556 557 558 fn add_to_collection_allow_list_cross(559 &mut self,560 caller: caller,561 user: EthCrossAccount,562 ) -> Result<void> {563 self.consume_store_writes(1)?;564565 let caller = T::CrossAccountId::from_eth(caller);566 let user = user.into_sub_cross_account::<T>()?;567 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;568 Ok(())569 }570571 572 573 574 #[solidity(hide)]575 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {576 self.consume_store_writes(1)?;577578 let caller = T::CrossAccountId::from_eth(caller);579 let user = T::CrossAccountId::from_eth(user);580 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;581 Ok(())582 }583584 585 586 587 fn remove_from_collection_allow_list_cross(588 &mut self,589 caller: caller,590 user: EthCrossAccount,591 ) -> Result<void> {592 self.consume_store_writes(1)?;593594 let caller = T::CrossAccountId::from_eth(caller);595 let user = user.into_sub_cross_account::<T>()?;596 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;597 Ok(())598 }599600 601 602 603 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {604 self.consume_store_reads_and_writes(1, 1)?;605606 let caller = T::CrossAccountId::from_eth(caller);607 let permissions = CollectionPermissions {608 mint_mode: Some(mode),609 ..Default::default()610 };611 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)612 }613614 615 616 617 618 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]619 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {620 let user = T::CrossAccountId::from_eth(user);621 Ok(self.is_owner_or_admin(&user))622 }623624 625 626 627 628 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {629 let user = user.into_sub_cross_account::<T>()?;630 Ok(self.is_owner_or_admin(&user))631 }632633 634 635 636 fn unique_collection_type(&self) -> Result<string> {637 let mode = match self.collection.mode {638 CollectionMode::Fungible(_) => "Fungible",639 CollectionMode::NFT => "NFT",640 CollectionMode::ReFungible => "ReFungible",641 };642 Ok(mode.into())643 }644645 646 647 648 649 fn collection_owner(&self) -> Result<EthCrossAccount> {650 Ok(EthCrossAccount::from_sub_cross_account::<T>(651 &T::CrossAccountId::from_sub(self.owner.clone()),652 ))653 }654655 656 657 658 659 #[solidity(hide, rename_selector = "changeCollectionOwner")]660 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {661 self.consume_store_writes(1)?;662663 let caller = T::CrossAccountId::from_eth(caller);664 let new_owner = T::CrossAccountId::from_eth(new_owner);665 self.change_owner(caller, new_owner)666 .map_err(dispatch_to_evm::<T>)667 }668669 670 671 672 673 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {674 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))675 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))676 .collect();677 Ok(result)678 }679680 681 682 683 684 fn change_collection_owner_cross(685 &mut self,686 caller: caller,687 new_owner: EthCrossAccount,688 ) -> Result<void> {689 self.consume_store_writes(1)?;690691 let caller = T::CrossAccountId::from_eth(caller);692 let new_owner = new_owner.into_sub_cross_account::<T>()?;693 self.change_owner(caller, new_owner)694 .map_err(dispatch_to_evm::<T>)695 }696}697698699700fn check_is_owner_or_admin<T: Config>(701 caller: caller,702 collection: &CollectionHandle<T>,703) -> Result<T::CrossAccountId> {704 let caller = T::CrossAccountId::from_eth(caller);705 collection706 .check_is_owner_or_admin(&caller)707 .map_err(dispatch_to_evm::<T>)?;708 Ok(caller)709}710711712713fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {714 collection715 .check_is_internal()716 .map_err(dispatch_to_evm::<T>)?;717 collection.save().map_err(dispatch_to_evm::<T>)?;718 Ok(())719}720721722pub mod static_property {723 use evm_coder::{724 execution::{Result, Error},725 };726 use alloc::format;727728 const EXPECT_CONVERT_ERROR: &str = "length < limit";729730 731 pub mod key {732 use super::*;733734 735 pub fn base_uri() -> up_data_structs::PropertyKey {736 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)737 }738739 740 pub fn url() -> up_data_structs::PropertyKey {741 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)742 }743744 745 pub fn suffix() -> up_data_structs::PropertyKey {746 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)747 }748749 750 pub fn parent_nft() -> up_data_structs::PropertyKey {751 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)752 }753 }754755 756 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {757 bytes.to_vec().try_into().map_err(|_| {758 Error::Revert(format!(759 "Property key is too long. Max length is {}.",760 up_data_structs::PropertyKey::bound()761 ))762 })763 }764765 766 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {767 bytes.to_vec().try_into().map_err(|_| {768 Error::Revert(format!(769 "Property key is too long. Max length is {}.",770 up_data_structs::PropertyKey::bound()771 ))772 })773 }774}