12345678910111213141516171819pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};20use evm_coder::{21 abi::AbiType,22 solidity_interface, solidity, ToLog,23 types::*,24 types::Property as PropertyStruct,25 execution::{Result, Error},26 weight,27};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use up_data_structs::{31 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,32 SponsoringRateLimit, SponsorshipState,33};34use alloc::format;3536use crate::{37 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,38 eth::{EthCrossAccount, convert_cross_account_to_uint256},39 weights::WeightInfo,40};414243#[derive(ToLog)]44pub enum CollectionHelpersEvents {45 46 CollectionCreated {47 48 #[indexed]49 owner: address,5051 52 #[indexed]53 collection_id: address,54 },55 56 CollectionDestroyed {57 58 #[indexed]59 collection_id: address,60 },61 62 CollectionChanged {63 64 #[indexed]65 collection_id: address,66 },67}68697071pub trait CommonEvmHandler {72 73 const CODE: &'static [u8];7475 76 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;77}787980#[solidity_interface(name = Collection)]81impl<T: Config> CollectionHandle<T>82where83 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,84{85 86 87 88 89 #[solidity(hide)]90 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]91 fn set_collection_property(92 &mut self,93 caller: caller,94 key: string,95 value: bytes,96 ) -> Result<void> {97 let caller = T::CrossAccountId::from_eth(caller);98 let key = <Vec<u8>>::from(key)99 .try_into()100 .map_err(|_| "key too large")?;101 let value = value.0.try_into().map_err(|_| "value too large")?;102103 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })104 .map_err(dispatch_to_evm::<T>)105 }106107 108 109 110 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]111 fn set_collection_properties(112 &mut self,113 caller: caller,114 properties: Vec<PropertyStruct>,115 ) -> Result<void> {116 let caller = T::CrossAccountId::from_eth(caller);117118 let properties = properties119 .into_iter()120 .map(|PropertyStruct { key, value }| {121 let key = <Vec<u8>>::from(key)122 .try_into()123 .map_err(|_| "key too large")?;124125 let value = value.0.try_into().map_err(|_| "value too large")?;126127 Ok(Property { key, value })128 })129 .collect::<Result<Vec<_>>>()?;130131 <Pallet<T>>::set_collection_properties(self, &caller, properties)132 .map_err(dispatch_to_evm::<T>)133 }134135 136 137 138 #[solidity(hide)]139 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]140 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {141 let caller = T::CrossAccountId::from_eth(caller);142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too large")?;145146 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)147 }148149 150 151 152 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]153 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {154 let caller = T::CrossAccountId::from_eth(caller);155 let keys = keys156 .into_iter()157 .map(|key| {158 <Vec<u8>>::from(key)159 .try_into()160 .map_err(|_| Error::Revert("key too large".into()))161 })162 .collect::<Result<Vec<_>>>()?;163164 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)165 }166167 168 169 170 171 172 173 fn collection_property(&self, key: string) -> Result<bytes> {174 let key = <Vec<u8>>::from(key)175 .try_into()176 .map_err(|_| "key too large")?;177178 let props = CollectionProperties::<T>::get(self.id);179 let prop = props.get(&key).ok_or("key not found")?;180181 Ok(bytes(prop.to_vec()))182 }183184 185 186 187 188 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {189 let keys = keys190 .into_iter()191 .map(|key| {192 <Vec<u8>>::from(key)193 .try_into()194 .map_err(|_| Error::Revert("key too large".into()))195 })196 .collect::<Result<Vec<_>>>()?;197198 let properties = Pallet::<T>::filter_collection_properties(199 self.id,200 if keys.is_empty() { None } else { Some(keys) },201 )202 .map_err(dispatch_to_evm::<T>)?;203204 let properties = properties205 .into_iter()206 .map(|p| {207 let key =208 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;209 let value = bytes(p.value.to_vec());210 Ok(PropertyStruct { key, value })211 })212 .collect::<Result<Vec<_>>>()?;213 Ok(properties)214 }215216 217 218 219 220 221 #[solidity(hide)]222 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {223 self.consume_store_reads_and_writes(1, 1)?;224225 let caller = T::CrossAccountId::from_eth(caller);226227 let sponsor = T::CrossAccountId::from_eth(sponsor);228 self.set_sponsor(&caller, sponsor.as_sub().clone())229 .map_err(dispatch_to_evm::<T>)230 }231232 233 234 235 236 237 fn set_collection_sponsor_cross(238 &mut self,239 caller: caller,240 sponsor: EthCrossAccount,241 ) -> Result<void> {242 self.consume_store_reads_and_writes(1, 1)?;243244 let caller = T::CrossAccountId::from_eth(caller);245246 let sponsor = sponsor.into_sub_cross_account::<T>()?;247 self.set_sponsor(&caller, sponsor.as_sub().clone())248 .map_err(dispatch_to_evm::<T>)249 }250251 252 fn has_collection_pending_sponsor(&self) -> Result<bool> {253 Ok(matches!(254 self.collection.sponsorship,255 SponsorshipState::Unconfirmed(_)256 ))257 }258259 260 261 262 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {263 self.consume_store_writes(1)?;264265 let caller = T::CrossAccountId::from_eth(caller);266 self.confirm_sponsorship(caller.as_sub())267 .map_err(dispatch_to_evm::<T>)268 }269270 271 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272 self.consume_store_reads_and_writes(1, 1)?;273 let caller = T::CrossAccountId::from_eth(caller);274 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)275 }276277 278 279 280 fn collection_sponsor(&self) -> Result<(address, uint256)> {281 let sponsor = match self.collection.sponsorship.sponsor() {282 Some(sponsor) => sponsor,283 None => return Ok(Default::default()),284 };285 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());286 let result: (address, uint256) = if sponsor.is_canonical_substrate() {287 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);288 (Default::default(), sponsor)289 } else {290 let sponsor = *sponsor.as_eth();291 (sponsor, Default::default())292 };293 Ok(result)294 }295296 297 298 299 300 301 302 303 304 305 306 307 308 309 #[solidity(rename_selector = "setCollectionLimit")]310 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {311 self.consume_store_reads_and_writes(1, 1)?;312313 let value = value314 .try_into()315 .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;316317 let convert_value_to_bool = || match value {318 0 => Ok(false),319 1 => Ok(true),320 _ => {321 return Err(Error::Revert(format!(322 "can't convert value to boolean \"{}\"",323 value324 )))325 }326 };327328 let mut limits = self.limits.clone();329330 match limit.as_str() {331 "accountTokenOwnershipLimit" => {332 limits.account_token_ownership_limit = Some(value);333 }334 "sponsoredDataSize" => {335 limits.sponsored_data_size = Some(value);336 }337 "sponsoredDataRateLimit" => {338 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));339 }340 "tokenLimit" => {341 limits.token_limit = Some(value);342 }343 "sponsorTransferTimeout" => {344 limits.sponsor_transfer_timeout = Some(value);345 }346 "sponsorApproveTimeout" => {347 limits.sponsor_approve_timeout = Some(value);348 }349 "ownerCanTransfer" => {350 limits.owner_can_transfer = Some(convert_value_to_bool()?);351 }352 "ownerCanDestroy" => {353 limits.owner_can_destroy = Some(convert_value_to_bool()?);354 }355 "transfersEnabled" => {356 limits.transfers_enabled = Some(convert_value_to_bool()?);357 }358 _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),359 }360361 let caller = T::CrossAccountId::from_eth(caller);362 <Pallet<T>>::update_limits(&caller, self, limits).map_err(dispatch_to_evm::<T>)363 }364365 366 fn contract_address(&self) -> Result<address> {367 Ok(crate::eth::collection_id_to_address(self.id))368 }369370 371 372 fn add_collection_admin_cross(373 &mut self,374 caller: caller,375 new_admin: EthCrossAccount,376 ) -> Result<void> {377 self.consume_store_reads_and_writes(2, 2)?;378379 let caller = T::CrossAccountId::from_eth(caller);380 let new_admin = new_admin.into_sub_cross_account::<T>()?;381 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;382 Ok(())383 }384385 386 387 fn remove_collection_admin_cross(388 &mut self,389 caller: caller,390 admin: EthCrossAccount,391 ) -> Result<void> {392 self.consume_store_reads_and_writes(2, 2)?;393394 let caller = T::CrossAccountId::from_eth(caller);395 let admin = admin.into_sub_cross_account::<T>()?;396 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;397 Ok(())398 }399400 401 402 #[solidity(hide)]403 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {404 self.consume_store_reads_and_writes(2, 2)?;405406 let caller = T::CrossAccountId::from_eth(caller);407 let new_admin = T::CrossAccountId::from_eth(new_admin);408 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;409 Ok(())410 }411412 413 414 415 #[solidity(hide)]416 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {417 self.consume_store_reads_and_writes(2, 2)?;418419 let caller = T::CrossAccountId::from_eth(caller);420 let admin = T::CrossAccountId::from_eth(admin);421 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;422 Ok(())423 }424425 426 427 428 #[solidity(rename_selector = "setCollectionNesting")]429 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {430 self.consume_store_reads_and_writes(1, 1)?;431432 let caller = T::CrossAccountId::from_eth(caller);433434 let mut permissions = self.collection.permissions.clone();435 let mut nesting = permissions.nesting().clone();436 nesting.token_owner = enable;437 nesting.restricted = None;438 permissions.nesting = Some(nesting);439440 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)441 }442443 444 445 446 447 #[solidity(rename_selector = "setCollectionNesting")]448 fn set_nesting(449 &mut self,450 caller: caller,451 enable: bool,452 collections: Vec<address>,453 ) -> Result<void> {454 self.consume_store_reads_and_writes(1, 1)?;455456 if collections.is_empty() {457 return Err("no addresses provided".into());458 }459 let caller = T::CrossAccountId::from_eth(caller);460461 let mut permissions = self.collection.permissions.clone();462 match enable {463 false => {464 let mut nesting = permissions.nesting().clone();465 nesting.token_owner = false;466 nesting.restricted = None;467 permissions.nesting = Some(nesting);468 }469 true => {470 let mut bv = OwnerRestrictedSet::new();471 for i in collections {472 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {473 Error::Revert("Can't convert address into collection id".into())474 })?)475 .map_err(|_| "too many collections")?;476 }477 let mut nesting = permissions.nesting().clone();478 nesting.token_owner = true;479 nesting.restricted = Some(bv);480 permissions.nesting = Some(nesting);481 }482 };483484 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)485 }486487 488 489 490 491 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {492 self.consume_store_reads_and_writes(1, 1)?;493494 let caller = T::CrossAccountId::from_eth(caller);495 let permissions = CollectionPermissions {496 access: Some(match mode {497 0 => AccessMode::Normal,498 1 => AccessMode::AllowList,499 _ => return Err("not supported access mode".into()),500 }),501 ..Default::default()502 };503 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)504 }505506 507 508 509 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {510 let user = user.into_sub_cross_account::<T>()?;511 Ok(Pallet::<T>::allowed(self.id, user))512 }513514 515 516 517 #[solidity(hide)]518 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {519 self.consume_store_writes(1)?;520521 let caller = T::CrossAccountId::from_eth(caller);522 let user = T::CrossAccountId::from_eth(user);523 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;524 Ok(())525 }526527 528 529 530 fn add_to_collection_allow_list_cross(531 &mut self,532 caller: caller,533 user: EthCrossAccount,534 ) -> Result<void> {535 self.consume_store_writes(1)?;536537 let caller = T::CrossAccountId::from_eth(caller);538 let user = user.into_sub_cross_account::<T>()?;539 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;540 Ok(())541 }542543 544 545 546 #[solidity(hide)]547 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {548 self.consume_store_writes(1)?;549550 let caller = T::CrossAccountId::from_eth(caller);551 let user = T::CrossAccountId::from_eth(user);552 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;553 Ok(())554 }555556 557 558 559 fn remove_from_collection_allow_list_cross(560 &mut self,561 caller: caller,562 user: EthCrossAccount,563 ) -> Result<void> {564 self.consume_store_writes(1)?;565566 let caller = T::CrossAccountId::from_eth(caller);567 let user = user.into_sub_cross_account::<T>()?;568 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;569 Ok(())570 }571572 573 574 575 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {576 self.consume_store_reads_and_writes(1, 1)?;577578 let caller = T::CrossAccountId::from_eth(caller);579 let permissions = CollectionPermissions {580 mint_mode: Some(mode),581 ..Default::default()582 };583 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)584 }585586 587 588 589 590 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]591 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {592 let user = T::CrossAccountId::from_eth(user);593 Ok(self.is_owner_or_admin(&user))594 }595596 597 598 599 600 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {601 let user = user.into_sub_cross_account::<T>()?;602 Ok(self.is_owner_or_admin(&user))603 }604605 606 607 608 fn unique_collection_type(&self) -> Result<string> {609 let mode = match self.collection.mode {610 CollectionMode::Fungible(_) => "Fungible",611 CollectionMode::NFT => "NFT",612 CollectionMode::ReFungible => "ReFungible",613 };614 Ok(mode.into())615 }616617 618 619 620 621 fn collection_owner(&self) -> Result<EthCrossAccount> {622 Ok(EthCrossAccount::from_sub_cross_account::<T>(623 &T::CrossAccountId::from_sub(self.owner.clone()),624 ))625 }626627 628 629 630 631 #[solidity(hide, rename_selector = "changeCollectionOwner")]632 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {633 self.consume_store_writes(1)?;634635 let caller = T::CrossAccountId::from_eth(caller);636 let new_owner = T::CrossAccountId::from_eth(new_owner);637 self.change_owner(caller, new_owner)638 .map_err(dispatch_to_evm::<T>)639 }640641 642 643 644 645 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {646 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))647 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))648 .collect();649 Ok(result)650 }651652 653 654 655 656 fn change_collection_owner_cross(657 &mut self,658 caller: caller,659 new_owner: EthCrossAccount,660 ) -> Result<void> {661 self.consume_store_writes(1)?;662663 let caller = T::CrossAccountId::from_eth(caller);664 let new_owner = new_owner.into_sub_cross_account::<T>()?;665 self.change_owner(caller, new_owner)666 .map_err(dispatch_to_evm::<T>)667 }668}669670671672fn check_is_owner_or_admin<T: Config>(673 caller: caller,674 collection: &CollectionHandle<T>,675) -> Result<T::CrossAccountId> {676 let caller = T::CrossAccountId::from_eth(caller);677 collection678 .check_is_owner_or_admin(&caller)679 .map_err(dispatch_to_evm::<T>)?;680 Ok(caller)681}682683684685fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {686 collection687 .check_is_internal()688 .map_err(dispatch_to_evm::<T>)?;689 collection.save().map_err(dispatch_to_evm::<T>)?;690 Ok(())691}692693694pub mod static_property {695 use evm_coder::{696 execution::{Result, Error},697 };698 use alloc::format;699700 const EXPECT_CONVERT_ERROR: &str = "length < limit";701702 703 pub mod key {704 use super::*;705706 707 pub fn base_uri() -> up_data_structs::PropertyKey {708 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)709 }710711 712 pub fn url() -> up_data_structs::PropertyKey {713 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)714 }715716 717 pub fn suffix() -> up_data_structs::PropertyKey {718 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)719 }720721 722 pub fn parent_nft() -> up_data_structs::PropertyKey {723 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)724 }725 }726727 728 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {729 bytes.to_vec().try_into().map_err(|_| {730 Error::Revert(format!(731 "Property key is too long. Max length is {}.",732 up_data_structs::PropertyKey::bound()733 ))734 })735 }736737 738 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {739 bytes.to_vec().try_into().map_err(|_| {740 Error::Revert(format!(741 "Property key is too long. Max length is {}.",742 up_data_structs::PropertyKey::bound()743 ))744 })745 }746}