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 CollectionPermissions as EvmPermissions, CollectionLimitField as EvmCollectionLimits, self,39 },40 weights::WeightInfo,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 },6869 70 TokenChanged {71 72 #[indexed]73 collection_id: address,74 75 token_id: uint256,76 },77}78798081pub trait CommonEvmHandler {82 83 const CODE: &'static [u8];8485 86 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;87}888990#[solidity_interface(name = Collection)]91impl<T: Config> CollectionHandle<T>92where93 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,94{95 96 97 98 99 #[solidity(hide)]100 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]101 fn set_collection_property(102 &mut self,103 caller: caller,104 key: string,105 value: bytes,106 ) -> Result<void> {107 let caller = T::CrossAccountId::from_eth(caller);108 let key = <Vec<u8>>::from(key)109 .try_into()110 .map_err(|_| "key too large")?;111 let value = value.0.try_into().map_err(|_| "value too large")?;112113 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })114 .map_err(dispatch_to_evm::<T>)115 }116117 118 119 120 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]121 fn set_collection_properties(122 &mut self,123 caller: caller,124 properties: Vec<eth::Property>,125 ) -> Result<void> {126 let caller = T::CrossAccountId::from_eth(caller);127128 let properties = properties129 .into_iter()130 .map(|eth::Property { key, value }| {131 let key = <Vec<u8>>::from(key)132 .try_into()133 .map_err(|_| "key too large")?;134135 let value = value.0.try_into().map_err(|_| "value too large")?;136137 Ok(Property { key, value })138 })139 .collect::<Result<Vec<_>>>()?;140141 <Pallet<T>>::set_collection_properties(self, &caller, properties)142 .map_err(dispatch_to_evm::<T>)143 }144145 146 147 148 #[solidity(hide)]149 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]150 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {151 let caller = T::CrossAccountId::from_eth(caller);152 let key = <Vec<u8>>::from(key)153 .try_into()154 .map_err(|_| "key too large")?;155156 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)157 }158159 160 161 162 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]163 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let keys = keys166 .into_iter()167 .map(|key| {168 <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| Error::Revert("key too large".into()))171 })172 .collect::<Result<Vec<_>>>()?;173174 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)175 }176177 178 179 180 181 182 183 fn collection_property(&self, key: string) -> Result<bytes> {184 let key = <Vec<u8>>::from(key)185 .try_into()186 .map_err(|_| "key too large")?;187188 let props = CollectionProperties::<T>::get(self.id);189 let prop = props.get(&key).ok_or("key not found")?;190191 Ok(bytes(prop.to_vec()))192 }193194 195 196 197 198 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<eth::Property>> {199 let keys = keys200 .into_iter()201 .map(|key| {202 <Vec<u8>>::from(key)203 .try_into()204 .map_err(|_| Error::Revert("key too large".into()))205 })206 .collect::<Result<Vec<_>>>()?;207208 let properties = Pallet::<T>::filter_collection_properties(209 self.id,210 if keys.is_empty() { None } else { Some(keys) },211 )212 .map_err(dispatch_to_evm::<T>)?;213214 let properties = properties215 .into_iter()216 .map(|p| {217 let key =218 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;219 let value = bytes(p.value.to_vec());220 Ok(eth::Property { key, value })221 })222 .collect::<Result<Vec<_>>>()?;223 Ok(properties)224 }225226 227 228 229 230 231 #[solidity(hide)]232 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {233 self.consume_store_reads_and_writes(1, 1)?;234235 let caller = T::CrossAccountId::from_eth(caller);236237 let sponsor = T::CrossAccountId::from_eth(sponsor);238 self.set_sponsor(&caller, sponsor.as_sub().clone())239 .map_err(dispatch_to_evm::<T>)240 }241242 243 244 245 246 247 fn set_collection_sponsor_cross(248 &mut self,249 caller: caller,250 sponsor: eth::CrossAccount,251 ) -> Result<void> {252 self.consume_store_reads_and_writes(1, 1)?;253254 let caller = T::CrossAccountId::from_eth(caller);255256 let sponsor = sponsor.into_sub_cross_account::<T>()?;257 self.set_sponsor(&caller, sponsor.as_sub().clone())258 .map_err(dispatch_to_evm::<T>)259 }260261 262 fn has_collection_pending_sponsor(&self) -> Result<bool> {263 Ok(matches!(264 self.collection.sponsorship,265 SponsorshipState::Unconfirmed(_)266 ))267 }268269 270 271 272 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {273 self.consume_store_writes(1)?;274275 let caller = T::CrossAccountId::from_eth(caller);276 self.confirm_sponsorship(caller.as_sub())277 .map_err(dispatch_to_evm::<T>)278 }279280 281 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {282 self.consume_store_reads_and_writes(1, 1)?;283 let caller = T::CrossAccountId::from_eth(caller);284 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)285 }286287 288 289 290 fn collection_sponsor(&self) -> Result<eth::CrossAccount> {291 let sponsor = match self.collection.sponsorship.sponsor() {292 Some(sponsor) => sponsor,293 None => return Ok(Default::default()),294 };295296 Ok(eth::CrossAccount::from_sub::<T>(&sponsor))297 }298299 300 301 302 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {303 let limits = &self.collection.limits;304305 Ok(vec![306 eth::CollectionLimit::from_opt_int(307 EvmCollectionLimits::AccountTokenOwnership,308 limits.account_token_ownership_limit,309 ),310 eth::CollectionLimit::from_opt_int(311 EvmCollectionLimits::SponsoredDataSize,312 limits.sponsored_data_size,313 ),314 limits315 .sponsored_data_rate_limit316 .and_then(|limit| {317 if let SponsoringRateLimit::Blocks(blocks) = limit {318 Some(eth::CollectionLimit::from_int(319 EvmCollectionLimits::SponsoredDataRateLimit,320 blocks,321 ))322 } else {323 None324 }325 })326 .unwrap_or(eth::CollectionLimit::from_int(327 EvmCollectionLimits::SponsoredDataRateLimit,328 Default::default(),329 )),330 eth::CollectionLimit::from_opt_int(EvmCollectionLimits::TokenLimit, limits.token_limit),331 eth::CollectionLimit::from_opt_int(332 EvmCollectionLimits::SponsorTransferTimeout,333 limits.sponsor_transfer_timeout,334 ),335 eth::CollectionLimit::from_opt_int(336 EvmCollectionLimits::SponsorApproveTimeout,337 limits.sponsor_approve_timeout,338 ),339 eth::CollectionLimit::from_opt_bool(340 EvmCollectionLimits::OwnerCanTransfer,341 limits.owner_can_transfer,342 ),343 eth::CollectionLimit::from_opt_bool(344 EvmCollectionLimits::OwnerCanDestroy,345 limits.owner_can_destroy,346 ),347 eth::CollectionLimit::from_opt_bool(348 EvmCollectionLimits::TransferEnabled,349 limits.transfers_enabled,350 ),351 ])352 }353354 355 356 357 #[solidity(rename_selector = "setCollectionLimit")]358 fn set_collection_limit(359 &mut self,360 caller: caller,361 limit: eth::CollectionLimit,362 ) -> Result<void> {363 self.consume_store_reads_and_writes(1, 1)?;364365 let caller = T::CrossAccountId::from_eth(caller);366 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)367 }368369 370 fn contract_address(&self) -> Result<address> {371 Ok(crate::eth::collection_id_to_address(self.id))372 }373374 375 376 fn add_collection_admin_cross(377 &mut self,378 caller: caller,379 new_admin: eth::CrossAccount,380 ) -> Result<void> {381 self.consume_store_reads_and_writes(2, 2)?;382383 let caller = T::CrossAccountId::from_eth(caller);384 let new_admin = new_admin.into_sub_cross_account::<T>()?;385 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;386 Ok(())387 }388389 390 391 fn remove_collection_admin_cross(392 &mut self,393 caller: caller,394 admin: eth::CrossAccount,395 ) -> Result<void> {396 self.consume_store_reads_and_writes(2, 2)?;397398 let caller = T::CrossAccountId::from_eth(caller);399 let admin = admin.into_sub_cross_account::<T>()?;400 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;401 Ok(())402 }403404 405 406 #[solidity(hide)]407 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {408 self.consume_store_reads_and_writes(2, 2)?;409410 let caller = T::CrossAccountId::from_eth(caller);411 let new_admin = T::CrossAccountId::from_eth(new_admin);412 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;413 Ok(())414 }415416 417 418 419 #[solidity(hide)]420 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {421 self.consume_store_reads_and_writes(2, 2)?;422423 let caller = T::CrossAccountId::from_eth(caller);424 let admin = T::CrossAccountId::from_eth(admin);425 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;426 Ok(())427 }428429 430 431 432 #[solidity(rename_selector = "setCollectionNesting")]433 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {434 self.consume_store_reads_and_writes(1, 1)?;435436 let caller = T::CrossAccountId::from_eth(caller);437438 let mut permissions = self.collection.permissions.clone();439 let mut nesting = permissions.nesting().clone();440 nesting.token_owner = enable;441 nesting.restricted = None;442 permissions.nesting = Some(nesting);443444 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)445 }446447 448 449 450 451 #[solidity(rename_selector = "setCollectionNesting")]452 fn set_nesting(453 &mut self,454 caller: caller,455 enable: bool,456 collections: Vec<address>,457 ) -> Result<void> {458 self.consume_store_reads_and_writes(1, 1)?;459460 if collections.is_empty() {461 return Err("no addresses provided".into());462 }463 let caller = T::CrossAccountId::from_eth(caller);464465 let mut permissions = self.collection.permissions.clone();466 match enable {467 false => {468 let mut nesting = permissions.nesting().clone();469 nesting.token_owner = false;470 nesting.restricted = None;471 permissions.nesting = Some(nesting);472 }473 true => {474 let mut bv = OwnerRestrictedSet::new();475 for i in collections {476 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {477 Error::Revert("Can't convert address into collection id".into())478 })?)479 .map_err(|_| "too many collections")?;480 }481 let mut nesting = permissions.nesting().clone();482 nesting.token_owner = true;483 nesting.restricted = Some(bv);484 permissions.nesting = Some(nesting);485 }486 };487488 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)489 }490491 492 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]493 fn collection_nesting_restricted_ids(&self) -> Result<(bool, Vec<uint256>)> {494 let nesting = self.collection.permissions.nesting();495496 Ok((497 nesting.token_owner,498 nesting499 .restricted500 .clone()501 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())502 .unwrap_or_default(),503 ))504 }505506 507 fn collection_nesting_permissions(&self) -> Result<Vec<(EvmPermissions, bool)>> {508 let nesting = self.collection.permissions.nesting();509 Ok(vec![510 (EvmPermissions::CollectionAdmin, nesting.collection_admin),511 (EvmPermissions::TokenOwner, nesting.token_owner),512 ])513 }514 515 516 517 518 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {519 self.consume_store_reads_and_writes(1, 1)?;520521 let caller = T::CrossAccountId::from_eth(caller);522 let permissions = CollectionPermissions {523 access: Some(match mode {524 0 => AccessMode::Normal,525 1 => AccessMode::AllowList,526 _ => return Err("not supported access mode".into()),527 }),528 ..Default::default()529 };530 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)531 }532533 534 535 536 fn allowlisted_cross(&self, user: eth::CrossAccount) -> Result<bool> {537 let user = user.into_sub_cross_account::<T>()?;538 Ok(Pallet::<T>::allowed(self.id, user))539 }540541 542 543 544 #[solidity(hide)]545 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {546 self.consume_store_writes(1)?;547548 let caller = T::CrossAccountId::from_eth(caller);549 let user = T::CrossAccountId::from_eth(user);550 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;551 Ok(())552 }553554 555 556 557 fn add_to_collection_allow_list_cross(558 &mut self,559 caller: caller,560 user: eth::CrossAccount,561 ) -> Result<void> {562 self.consume_store_writes(1)?;563564 let caller = T::CrossAccountId::from_eth(caller);565 let user = user.into_sub_cross_account::<T>()?;566 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;567 Ok(())568 }569570 571 572 573 #[solidity(hide)]574 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {575 self.consume_store_writes(1)?;576577 let caller = T::CrossAccountId::from_eth(caller);578 let user = T::CrossAccountId::from_eth(user);579 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;580 Ok(())581 }582583 584 585 586 fn remove_from_collection_allow_list_cross(587 &mut self,588 caller: caller,589 user: eth::CrossAccount,590 ) -> Result<void> {591 self.consume_store_writes(1)?;592593 let caller = T::CrossAccountId::from_eth(caller);594 let user = user.into_sub_cross_account::<T>()?;595 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;596 Ok(())597 }598599 600 601 602 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {603 self.consume_store_reads_and_writes(1, 1)?;604605 let caller = T::CrossAccountId::from_eth(caller);606 let permissions = CollectionPermissions {607 mint_mode: Some(mode),608 ..Default::default()609 };610 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)611 }612613 614 615 616 617 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]618 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {619 let user = T::CrossAccountId::from_eth(user);620 Ok(self.is_owner_or_admin(&user))621 }622623 624 625 626 627 fn is_owner_or_admin_cross(&self, user: eth::CrossAccount) -> Result<bool> {628 let user = user.into_sub_cross_account::<T>()?;629 Ok(self.is_owner_or_admin(&user))630 }631632 633 634 635 fn unique_collection_type(&self) -> Result<string> {636 let mode = match self.collection.mode {637 CollectionMode::Fungible(_) => "Fungible",638 CollectionMode::NFT => "NFT",639 CollectionMode::ReFungible => "ReFungible",640 };641 Ok(mode.into())642 }643644 645 646 647 648 fn collection_owner(&self) -> Result<eth::CrossAccount> {649 Ok(eth::CrossAccount::from_sub_cross_account::<T>(650 &T::CrossAccountId::from_sub(self.owner.clone()),651 ))652 }653654 655 656 657 658 #[solidity(hide, rename_selector = "changeCollectionOwner")]659 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {660 self.consume_store_writes(1)?;661662 let caller = T::CrossAccountId::from_eth(caller);663 let new_owner = T::CrossAccountId::from_eth(new_owner);664 self.change_owner(caller, new_owner)665 .map_err(dispatch_to_evm::<T>)666 }667668 669 670 671 672 fn collection_admins(&self) -> Result<Vec<eth::CrossAccount>> {673 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))674 .map(|(admin, _)| eth::CrossAccount::from_sub_cross_account::<T>(&admin))675 .collect();676 Ok(result)677 }678679 680 681 682 683 fn change_collection_owner_cross(684 &mut self,685 caller: caller,686 new_owner: eth::CrossAccount,687 ) -> Result<void> {688 self.consume_store_writes(1)?;689690 let caller = T::CrossAccountId::from_eth(caller);691 let new_owner = new_owner.into_sub_cross_account::<T>()?;692 self.change_owner(caller, new_owner)693 .map_err(dispatch_to_evm::<T>)694 }695}696697698pub mod static_property {699 use evm_coder::{700 execution::{Result, Error},701 };702 use alloc::format;703704 const EXPECT_CONVERT_ERROR: &str = "length < limit";705706 707 pub mod key {708 use super::*;709710 711 pub fn base_uri() -> up_data_structs::PropertyKey {712 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)713 }714715 716 pub fn url() -> up_data_structs::PropertyKey {717 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)718 }719720 721 pub fn suffix() -> up_data_structs::PropertyKey {722 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)723 }724725 726 pub fn parent_nft() -> up_data_structs::PropertyKey {727 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)728 }729 }730731 732 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {733 bytes.to_vec().try_into().map_err(|_| {734 Error::Revert(format!(735 "Property key is too long. Max length is {}.",736 up_data_structs::PropertyKey::bound()737 ))738 })739 }740741 742 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {743 bytes.to_vec().try_into().map_err(|_| {744 Error::Revert(format!(745 "Property key is too long. Max length is {}.",746 up_data_structs::PropertyKey::bound()747 ))748 })749 }750}