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::{CollectionLimitField as EvmCollectionLimits, self},38 weights::WeightInfo,39};404142#[derive(ToLog)]43pub enum CollectionHelpersEvents {44 45 CollectionCreated {46 47 #[indexed]48 owner: address,4950 51 #[indexed]52 collection_id: address,53 },54 55 CollectionDestroyed {56 57 #[indexed]58 collection_id: address,59 },60 61 CollectionChanged {62 63 #[indexed]64 collection_id: address,65 },6667 68 TokenChanged {69 70 #[indexed]71 collection_id: address,72 73 token_id: uint256,74 },75}76777879pub trait CommonEvmHandler {80 81 const CODE: &'static [u8];8283 84 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;85}868788#[solidity_interface(name = Collection)]89impl<T: Config> CollectionHandle<T>90where91 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,92{93 94 95 96 97 #[solidity(hide)]98 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]99 fn set_collection_property(100 &mut self,101 caller: caller,102 key: string,103 value: bytes,104 ) -> Result<void> {105 let caller = T::CrossAccountId::from_eth(caller);106 let key = <Vec<u8>>::from(key)107 .try_into()108 .map_err(|_| "key too large")?;109 let value = value.0.try_into().map_err(|_| "value too large")?;110111 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })112 .map_err(dispatch_to_evm::<T>)113 }114115 116 117 118 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]119 fn set_collection_properties(120 &mut self,121 caller: caller,122 properties: Vec<eth::Property>,123 ) -> Result<void> {124 let caller = T::CrossAccountId::from_eth(caller);125126 let properties = properties127 .into_iter()128 .map(|eth::Property { key, value }| {129 let key = <Vec<u8>>::from(key)130 .try_into()131 .map_err(|_| "key too large")?;132133 let value = value.0.try_into().map_err(|_| "value too large")?;134135 Ok(Property { key, value })136 })137 .collect::<Result<Vec<_>>>()?;138139 <Pallet<T>>::set_collection_properties(self, &caller, properties)140 .map_err(dispatch_to_evm::<T>)141 }142143 144 145 146 #[solidity(hide)]147 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]148 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {149 let caller = T::CrossAccountId::from_eth(caller);150 let key = <Vec<u8>>::from(key)151 .try_into()152 .map_err(|_| "key too large")?;153154 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)155 }156157 158 159 160 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]161 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {162 let caller = T::CrossAccountId::from_eth(caller);163 let keys = keys164 .into_iter()165 .map(|key| {166 <Vec<u8>>::from(key)167 .try_into()168 .map_err(|_| Error::Revert("key too large".into()))169 })170 .collect::<Result<Vec<_>>>()?;171172 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)173 }174175 176 177 178 179 180 181 fn collection_property(&self, key: string) -> Result<bytes> {182 let key = <Vec<u8>>::from(key)183 .try_into()184 .map_err(|_| "key too large")?;185186 let props = CollectionProperties::<T>::get(self.id);187 let prop = props.get(&key).ok_or("key not found")?;188189 Ok(bytes(prop.to_vec()))190 }191192 193 194 195 196 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<eth::Property>> {197 let keys = keys198 .into_iter()199 .map(|key| {200 <Vec<u8>>::from(key)201 .try_into()202 .map_err(|_| Error::Revert("key too large".into()))203 })204 .collect::<Result<Vec<_>>>()?;205206 let properties = Pallet::<T>::filter_collection_properties(207 self.id,208 if keys.is_empty() { None } else { Some(keys) },209 )210 .map_err(dispatch_to_evm::<T>)?;211212 let properties = properties213 .into_iter()214 .map(|p| {215 let key =216 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;217 let value = bytes(p.value.to_vec());218 Ok(eth::Property { key, value })219 })220 .collect::<Result<Vec<_>>>()?;221 Ok(properties)222 }223224 225 226 227 228 229 #[solidity(hide)]230 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {231 self.consume_store_reads_and_writes(1, 1)?;232233 let caller = T::CrossAccountId::from_eth(caller);234235 let sponsor = T::CrossAccountId::from_eth(sponsor);236 self.set_sponsor(&caller, sponsor.as_sub().clone())237 .map_err(dispatch_to_evm::<T>)238 }239240 241 242 243 244 245 fn set_collection_sponsor_cross(246 &mut self,247 caller: caller,248 sponsor: eth::CrossAccount,249 ) -> Result<void> {250 self.consume_store_reads_and_writes(1, 1)?;251252 let caller = T::CrossAccountId::from_eth(caller);253254 let sponsor = sponsor.into_sub_cross_account::<T>()?;255 self.set_sponsor(&caller, sponsor.as_sub().clone())256 .map_err(dispatch_to_evm::<T>)257 }258259 260 fn has_collection_pending_sponsor(&self) -> Result<bool> {261 Ok(matches!(262 self.collection.sponsorship,263 SponsorshipState::Unconfirmed(_)264 ))265 }266267 268 269 270 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {271 self.consume_store_writes(1)?;272273 let caller = T::CrossAccountId::from_eth(caller);274 self.confirm_sponsorship(caller.as_sub())275 .map_err(dispatch_to_evm::<T>)276 }277278 279 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {280 self.consume_store_reads_and_writes(1, 1)?;281 let caller = T::CrossAccountId::from_eth(caller);282 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)283 }284285 286 287 288 fn collection_sponsor(&self) -> Result<eth::CrossAccount> {289 let sponsor = match self.collection.sponsorship.sponsor() {290 Some(sponsor) => sponsor,291 None => return Ok(Default::default()),292 };293294 Ok(eth::CrossAccount::from_sub::<T>(&sponsor))295 }296297 298 299 300 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {301 let limits = &self.collection.limits;302303 Ok(vec![304 eth::CollectionLimit::from_opt_int(305 EvmCollectionLimits::AccountTokenOwnership,306 limits.account_token_ownership_limit,307 ),308 eth::CollectionLimit::from_opt_int(309 EvmCollectionLimits::SponsoredDataSize,310 limits.sponsored_data_size,311 ),312 limits313 .sponsored_data_rate_limit314 .and_then(|limit| {315 if let SponsoringRateLimit::Blocks(blocks) = limit {316 Some(eth::CollectionLimit::from_int(317 EvmCollectionLimits::SponsoredDataRateLimit,318 blocks,319 ))320 } else {321 None322 }323 })324 .unwrap_or(eth::CollectionLimit::from_int(325 EvmCollectionLimits::SponsoredDataRateLimit,326 Default::default(),327 )),328 eth::CollectionLimit::from_opt_int(EvmCollectionLimits::TokenLimit, limits.token_limit),329 eth::CollectionLimit::from_opt_int(330 EvmCollectionLimits::SponsorTransferTimeout,331 limits.sponsor_transfer_timeout,332 ),333 eth::CollectionLimit::from_opt_int(334 EvmCollectionLimits::SponsorApproveTimeout,335 limits.sponsor_approve_timeout,336 ),337 eth::CollectionLimit::from_opt_bool(338 EvmCollectionLimits::OwnerCanTransfer,339 limits.owner_can_transfer,340 ),341 eth::CollectionLimit::from_opt_bool(342 EvmCollectionLimits::OwnerCanDestroy,343 limits.owner_can_destroy,344 ),345 eth::CollectionLimit::from_opt_bool(346 EvmCollectionLimits::TransferEnabled,347 limits.transfers_enabled,348 ),349 ])350 }351352 353 354 355 #[solidity(rename_selector = "setCollectionLimit")]356 fn set_collection_limit(357 &mut self,358 caller: caller,359 limit: eth::CollectionLimit,360 ) -> Result<void> {361 self.consume_store_reads_and_writes(1, 1)?;362363 let caller = T::CrossAccountId::from_eth(caller);364 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)365 }366367 368 fn contract_address(&self) -> Result<address> {369 Ok(crate::eth::collection_id_to_address(self.id))370 }371372 373 374 fn add_collection_admin_cross(375 &mut self,376 caller: caller,377 new_admin: eth::CrossAccount,378 ) -> Result<void> {379 self.consume_store_reads_and_writes(2, 2)?;380381 let caller = T::CrossAccountId::from_eth(caller);382 let new_admin = new_admin.into_sub_cross_account::<T>()?;383 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;384 Ok(())385 }386387 388 389 fn remove_collection_admin_cross(390 &mut self,391 caller: caller,392 admin: eth::CrossAccount,393 ) -> Result<void> {394 self.consume_store_reads_and_writes(2, 2)?;395396 let caller = T::CrossAccountId::from_eth(caller);397 let admin = admin.into_sub_cross_account::<T>()?;398 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;399 Ok(())400 }401402 403 404 #[solidity(hide)]405 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {406 self.consume_store_reads_and_writes(2, 2)?;407408 let caller = T::CrossAccountId::from_eth(caller);409 let new_admin = T::CrossAccountId::from_eth(new_admin);410 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;411 Ok(())412 }413414 415 416 417 #[solidity(hide)]418 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {419 self.consume_store_reads_and_writes(2, 2)?;420421 let caller = T::CrossAccountId::from_eth(caller);422 let admin = T::CrossAccountId::from_eth(admin);423 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;424 Ok(())425 }426427 428 429 430 #[solidity(rename_selector = "setCollectionNesting")]431 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {432 self.consume_store_reads_and_writes(1, 1)?;433434 let caller = T::CrossAccountId::from_eth(caller);435436 let mut permissions = self.collection.permissions.clone();437 let mut nesting = permissions.nesting().clone();438 nesting.token_owner = enable;439 nesting.restricted = None;440 permissions.nesting = Some(nesting);441442 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)443 }444445 446 447 448 449 #[solidity(rename_selector = "setCollectionNesting")]450 fn set_nesting(451 &mut self,452 caller: caller,453 enable: bool,454 collections: Vec<address>,455 ) -> Result<void> {456 self.consume_store_reads_and_writes(1, 1)?;457458 if collections.is_empty() {459 return Err("no addresses provided".into());460 }461 let caller = T::CrossAccountId::from_eth(caller);462463 let mut permissions = self.collection.permissions.clone();464 match enable {465 false => {466 let mut nesting = permissions.nesting().clone();467 nesting.token_owner = false;468 nesting.restricted = None;469 permissions.nesting = Some(nesting);470 }471 true => {472 let mut bv = OwnerRestrictedSet::new();473 for i in collections {474 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {475 Error::Revert("Can't convert address into collection id".into())476 })?)477 .map_err(|_| "too many collections")?;478 }479 let mut nesting = permissions.nesting().clone();480 nesting.token_owner = true;481 nesting.restricted = Some(bv);482 permissions.nesting = Some(nesting);483 }484 };485486 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)487 }488489 490 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]491 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {492 let nesting = self.collection.permissions.nesting();493494 Ok(eth::CollectionNesting::new(495 nesting.token_owner,496 nesting497 .restricted498 .clone()499 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())500 .unwrap_or_default(),501 ))502 }503504 505 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {506 let nesting = self.collection.permissions.nesting();507 Ok(vec![508 eth::CollectionNestingPermission::new(509 eth::CollectionPermissionField::CollectionAdmin,510 nesting.collection_admin,511 ),512 eth::CollectionNestingPermission::new(513 eth::CollectionPermissionField::TokenOwner,514 nesting.token_owner,515 ),516 ])517 }518 519 520 521 522 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {523 self.consume_store_reads_and_writes(1, 1)?;524525 let caller = T::CrossAccountId::from_eth(caller);526 let permissions = CollectionPermissions {527 access: Some(match mode {528 0 => AccessMode::Normal,529 1 => AccessMode::AllowList,530 _ => return Err("not supported access mode".into()),531 }),532 ..Default::default()533 };534 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)535 }536537 538 539 540 fn allowlisted_cross(&self, user: eth::CrossAccount) -> Result<bool> {541 let user = user.into_sub_cross_account::<T>()?;542 Ok(Pallet::<T>::allowed(self.id, user))543 }544545 546 547 548 #[solidity(hide)]549 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {550 self.consume_store_writes(1)?;551552 let caller = T::CrossAccountId::from_eth(caller);553 let user = T::CrossAccountId::from_eth(user);554 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;555 Ok(())556 }557558 559 560 561 fn add_to_collection_allow_list_cross(562 &mut self,563 caller: caller,564 user: eth::CrossAccount,565 ) -> Result<void> {566 self.consume_store_writes(1)?;567568 let caller = T::CrossAccountId::from_eth(caller);569 let user = user.into_sub_cross_account::<T>()?;570 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;571 Ok(())572 }573574 575 576 577 #[solidity(hide)]578 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {579 self.consume_store_writes(1)?;580581 let caller = T::CrossAccountId::from_eth(caller);582 let user = T::CrossAccountId::from_eth(user);583 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;584 Ok(())585 }586587 588 589 590 fn remove_from_collection_allow_list_cross(591 &mut self,592 caller: caller,593 user: eth::CrossAccount,594 ) -> Result<void> {595 self.consume_store_writes(1)?;596597 let caller = T::CrossAccountId::from_eth(caller);598 let user = user.into_sub_cross_account::<T>()?;599 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;600 Ok(())601 }602603 604 605 606 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {607 self.consume_store_reads_and_writes(1, 1)?;608609 let caller = T::CrossAccountId::from_eth(caller);610 let permissions = CollectionPermissions {611 mint_mode: Some(mode),612 ..Default::default()613 };614 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)615 }616617 618 619 620 621 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]622 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {623 let user = T::CrossAccountId::from_eth(user);624 Ok(self.is_owner_or_admin(&user))625 }626627 628 629 630 631 fn is_owner_or_admin_cross(&self, user: eth::CrossAccount) -> Result<bool> {632 let user = user.into_sub_cross_account::<T>()?;633 Ok(self.is_owner_or_admin(&user))634 }635636 637 638 639 fn unique_collection_type(&self) -> Result<string> {640 let mode = match self.collection.mode {641 CollectionMode::Fungible(_) => "Fungible",642 CollectionMode::NFT => "NFT",643 CollectionMode::ReFungible => "ReFungible",644 };645 Ok(mode.into())646 }647648 649 650 651 652 fn collection_owner(&self) -> Result<eth::CrossAccount> {653 Ok(eth::CrossAccount::from_sub_cross_account::<T>(654 &T::CrossAccountId::from_sub(self.owner.clone()),655 ))656 }657658 659 660 661 662 #[solidity(hide, rename_selector = "changeCollectionOwner")]663 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {664 self.consume_store_writes(1)?;665666 let caller = T::CrossAccountId::from_eth(caller);667 let new_owner = T::CrossAccountId::from_eth(new_owner);668 self.change_owner(caller, new_owner)669 .map_err(dispatch_to_evm::<T>)670 }671672 673 674 675 676 fn collection_admins(&self) -> Result<Vec<eth::CrossAccount>> {677 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))678 .map(|(admin, _)| eth::CrossAccount::from_sub_cross_account::<T>(&admin))679 .collect();680 Ok(result)681 }682683 684 685 686 687 fn change_collection_owner_cross(688 &mut self,689 caller: caller,690 new_owner: eth::CrossAccount,691 ) -> Result<void> {692 self.consume_store_writes(1)?;693694 let caller = T::CrossAccountId::from_eth(caller);695 let new_owner = new_owner.into_sub_cross_account::<T>()?;696 self.change_owner(caller, new_owner)697 .map_err(dispatch_to_evm::<T>)698 }699}700701702pub mod static_property {703 use evm_coder::{704 execution::{Result, Error},705 };706 use alloc::format;707708 const EXPECT_CONVERT_ERROR: &str = "length < limit";709710 711 pub mod key {712 use super::*;713714 715 pub fn base_uri() -> up_data_structs::PropertyKey {716 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)717 }718719 720 pub fn url() -> up_data_structs::PropertyKey {721 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)722 }723724 725 pub fn suffix() -> up_data_structs::PropertyKey {726 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)727 }728729 730 pub fn parent_nft() -> up_data_structs::PropertyKey {731 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)732 }733 }734735 736 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {737 bytes.to_vec().try_into().map_err(|_| {738 Error::Revert(format!(739 "Property key is too long. Max length is {}.",740 up_data_structs::PropertyKey::bound()741 ))742 })743 }744745 746 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {747 bytes.to_vec().try_into().map_err(|_| {748 Error::Revert(format!(749 "Property key is too long. Max length is {}.",750 up_data_structs::PropertyKey::bound()751 ))752 })753 }754}