12345678910111213141516171819use evm_coder::{20 abi::AbiType,21 solidity_interface, solidity, ToLog,22 types::*,23 types::Property as PropertyStruct,24 execution::{Result, Error},25 weight,26};27pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};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::convert_cross_account_to_uint256, 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}61626364pub trait CommonEvmHandler {65 66 const CODE: &'static [u8];6768 69 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;70}717273#[solidity_interface(name = Collection)]74impl<T: Config> CollectionHandle<T>75where76 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,77{78 79 80 81 82 #[solidity(hide)]83 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]84 fn set_collection_property(85 &mut self,86 caller: caller,87 key: string,88 value: bytes,89 ) -> Result<void> {90 let caller = T::CrossAccountId::from_eth(caller);91 let key = <Vec<u8>>::from(key)92 .try_into()93 .map_err(|_| "key too large")?;94 let value = value.0.try_into().map_err(|_| "value too large")?;9596 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })97 .map_err(dispatch_to_evm::<T>)98 }99100 101 102 103 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]104 fn set_collection_properties(105 &mut self,106 caller: caller,107 properties: Vec<PropertyStruct>,108 ) -> Result<void> {109 let caller = T::CrossAccountId::from_eth(caller);110111 let properties = properties112 .into_iter()113 .map(|PropertyStruct { key, value }| {114 let key = <Vec<u8>>::from(key)115 .try_into()116 .map_err(|_| "key too large")?;117118 let value = value.0.try_into().map_err(|_| "value too large")?;119120 Ok(Property { key, value })121 })122 .collect::<Result<Vec<_>>>()?;123124 <Pallet<T>>::set_collection_properties(self, &caller, properties)125 .map_err(dispatch_to_evm::<T>)126 }127128 129 130 131 #[solidity(hide)]132 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]133 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {134 let caller = T::CrossAccountId::from_eth(caller);135 let key = <Vec<u8>>::from(key)136 .try_into()137 .map_err(|_| "key too large")?;138139 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)140 }141142 143 144 145 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]146 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {147 let caller = T::CrossAccountId::from_eth(caller);148 let keys = keys149 .into_iter()150 .map(|key| {151 <Vec<u8>>::from(key)152 .try_into()153 .map_err(|_| Error::Revert("key too large".into()))154 })155 .collect::<Result<Vec<_>>>()?;156157 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)158 }159160 161 162 163 164 165 166 fn collection_property(&self, key: string) -> Result<bytes> {167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too large")?;170171 let props = CollectionProperties::<T>::get(self.id);172 let prop = props.get(&key).ok_or("key not found")?;173174 Ok(bytes(prop.to_vec()))175 }176177 178 179 180 181 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {182 let keys = keys183 .into_iter()184 .map(|key| {185 <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| Error::Revert("key too large".into()))188 })189 .collect::<Result<Vec<_>>>()?;190191 let properties = Pallet::<T>::filter_collection_properties(192 self.id,193 if keys.is_empty() { None } else { Some(keys) },194 )195 .map_err(dispatch_to_evm::<T>)?;196197 let properties = properties198 .into_iter()199 .map(|p| {200 let key =201 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;202 let value = bytes(p.value.to_vec());203 Ok((key, value))204 })205 .collect::<Result<Vec<_>>>()?;206 Ok(properties)207 }208209 210 211 212 213 214 #[solidity(hide)]215 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {216 self.consume_store_reads_and_writes(1, 1)?;217218 check_is_owner_or_admin(caller, self)?;219220 let sponsor = T::CrossAccountId::from_eth(sponsor);221 self.set_sponsor(sponsor.as_sub().clone())222 .map_err(dispatch_to_evm::<T>)?;223 save(self)224 }225226 227 228 229 230 231 fn set_collection_sponsor_cross(232 &mut self,233 caller: caller,234 sponsor: EthCrossAccount,235 ) -> Result<void> {236 self.consume_store_reads_and_writes(1, 1)?;237238 check_is_owner_or_admin(caller, self)?;239240 let sponsor = sponsor.into_sub_cross_account::<T>()?;241 self.set_sponsor(sponsor.as_sub().clone())242 .map_err(dispatch_to_evm::<T>)?;243 save(self)244 }245246 247 fn has_collection_pending_sponsor(&self) -> Result<bool> {248 Ok(matches!(249 self.collection.sponsorship,250 SponsorshipState::Unconfirmed(_)251 ))252 }253254 255 256 257 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {258 self.consume_store_writes(1)?;259260 let caller = T::CrossAccountId::from_eth(caller);261 if !self262 .confirm_sponsorship(caller.as_sub())263 .map_err(dispatch_to_evm::<T>)?264 {265 return Err("caller is not set as sponsor".into());266 }267 save(self)268 }269270 271 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272 self.consume_store_reads_and_writes(1, 1)?;273 check_is_owner_or_admin(caller, self)?;274 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;275 save(self)276 }277278 279 280 281 fn collection_sponsor(&self) -> Result<(address, uint256)> {282 let sponsor = match self.collection.sponsorship.sponsor() {283 Some(sponsor) => sponsor,284 None => return Ok(Default::default()),285 };286 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());287 let result: (address, uint256) = if sponsor.is_canonical_substrate() {288 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);289 (Default::default(), sponsor)290 } else {291 let sponsor = *sponsor.as_eth();292 (sponsor, Default::default())293 };294 Ok(result)295 }296297 298 299 300 301 302 303 304 305 306 307 #[solidity(rename_selector = "setCollectionLimit")]308 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {309 self.consume_store_reads_and_writes(1, 1)?;310311 check_is_owner_or_admin(caller, self)?;312 let mut limits = self.limits.clone();313314 match limit.as_str() {315 "accountTokenOwnershipLimit" => {316 limits.account_token_ownership_limit = Some(value);317 }318 "sponsoredDataSize" => {319 limits.sponsored_data_size = Some(value);320 }321 "sponsoredDataRateLimit" => {322 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));323 }324 "tokenLimit" => {325 limits.token_limit = Some(value);326 }327 "sponsorTransferTimeout" => {328 limits.sponsor_transfer_timeout = Some(value);329 }330 "sponsorApproveTimeout" => {331 limits.sponsor_approve_timeout = Some(value);332 }333 _ => {334 return Err(Error::Revert(format!(335 "unknown integer limit \"{}\"",336 limit337 )))338 }339 }340 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)341 .map_err(dispatch_to_evm::<T>)?;342 save(self)343 }344345 346 347 348 349 350 351 352 #[solidity(rename_selector = "setCollectionLimit")]353 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {354 self.consume_store_reads_and_writes(1, 1)?;355356 check_is_owner_or_admin(caller, self)?;357 let mut limits = self.limits.clone();358359 match limit.as_str() {360 "ownerCanTransfer" => {361 limits.owner_can_transfer = Some(value);362 }363 "ownerCanDestroy" => {364 limits.owner_can_destroy = Some(value);365 }366 "transfersEnabled" => {367 limits.transfers_enabled = Some(value);368 }369 _ => {370 return Err(Error::Revert(format!(371 "unknown boolean limit \"{}\"",372 limit373 )))374 }375 }376 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)377 .map_err(dispatch_to_evm::<T>)?;378 save(self)379 }380381 382 fn contract_address(&self) -> Result<address> {383 Ok(crate::eth::collection_id_to_address(self.id))384 }385386 387 388 fn add_collection_admin_cross(389 &mut self,390 caller: caller,391 new_admin: EthCrossAccount,392 ) -> Result<void> {393 self.consume_store_writes(2)?;394395 let caller = T::CrossAccountId::from_eth(caller);396 let new_admin = new_admin.into_sub_cross_account::<T>()?;397 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;398 Ok(())399 }400401 402 403 fn remove_collection_admin_cross(404 &mut self,405 caller: caller,406 admin: EthCrossAccount,407 ) -> Result<void> {408 self.consume_store_writes(2)?;409410 let caller = T::CrossAccountId::from_eth(caller);411 let admin = admin.into_sub_cross_account::<T>()?;412 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;413 Ok(())414 }415416 417 418 #[solidity(hide)]419 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {420 self.consume_store_writes(2)?;421422 let caller = T::CrossAccountId::from_eth(caller);423 let new_admin = T::CrossAccountId::from_eth(new_admin);424 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;425 Ok(())426 }427428 429 430 431 #[solidity(hide)]432 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {433 self.consume_store_writes(2)?;434435 let caller = T::CrossAccountId::from_eth(caller);436 let admin = T::CrossAccountId::from_eth(admin);437 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;438 Ok(())439 }440441 442 443 444 #[solidity(rename_selector = "setCollectionNesting")]445 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {446 self.consume_store_reads_and_writes(1, 1)?;447448 check_is_owner_or_admin(caller, self)?;449450 let mut permissions = self.collection.permissions.clone();451 let mut nesting = permissions.nesting().clone();452 nesting.token_owner = enable;453 nesting.restricted = None;454 permissions.nesting = Some(nesting);455456 self.collection.permissions = <Pallet<T>>::clamp_permissions(457 self.collection.mode.clone(),458 &self.collection.permissions,459 permissions,460 )461 .map_err(dispatch_to_evm::<T>)?;462463 save(self)464 }465466 467 468 469 470 #[solidity(rename_selector = "setCollectionNesting")]471 fn set_nesting(472 &mut self,473 caller: caller,474 enable: bool,475 collections: Vec<address>,476 ) -> Result<void> {477 self.consume_store_reads_and_writes(1, 1)?;478479 if collections.is_empty() {480 return Err("no addresses provided".into());481 }482 check_is_owner_or_admin(caller, self)?;483484 let mut permissions = self.collection.permissions.clone();485 match enable {486 false => {487 let mut nesting = permissions.nesting().clone();488 nesting.token_owner = false;489 nesting.restricted = None;490 permissions.nesting = Some(nesting);491 }492 true => {493 let mut bv = OwnerRestrictedSet::new();494 for i in collections {495 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {496 Error::Revert("Can't convert address into collection id".into())497 })?)498 .map_err(|_| "too many collections")?;499 }500 let mut nesting = permissions.nesting().clone();501 nesting.token_owner = true;502 nesting.restricted = Some(bv);503 permissions.nesting = Some(nesting);504 }505 };506507 self.collection.permissions = <Pallet<T>>::clamp_permissions(508 self.collection.mode.clone(),509 &self.collection.permissions,510 permissions,511 )512 .map_err(dispatch_to_evm::<T>)?;513514 save(self)515 }516517 518 519 520 521 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {522 self.consume_store_reads_and_writes(1, 1)?;523524 check_is_owner_or_admin(caller, self)?;525 let permissions = CollectionPermissions {526 access: Some(match mode {527 0 => AccessMode::Normal,528 1 => AccessMode::AllowList,529 _ => return Err("not supported access mode".into()),530 }),531 ..Default::default()532 };533 self.collection.permissions = <Pallet<T>>::clamp_permissions(534 self.collection.mode.clone(),535 &self.collection.permissions,536 permissions,537 )538 .map_err(dispatch_to_evm::<T>)?;539540 save(self)541 }542543 544 545 546 fn allowed(&self, user: address) -> Result<bool> {547 Ok(Pallet::<T>::allowed(548 self.id,549 T::CrossAccountId::from_eth(user),550 ))551 }552553 554 555 556 #[solidity(hide)]557 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {558 self.consume_store_writes(1)?;559560 let caller = T::CrossAccountId::from_eth(caller);561 let user = T::CrossAccountId::from_eth(user);562 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;563 Ok(())564 }565566 567 568 569 fn add_to_collection_allow_list_cross(570 &mut self,571 caller: caller,572 user: EthCrossAccount,573 ) -> Result<void> {574 self.consume_store_writes(1)?;575576 let caller = T::CrossAccountId::from_eth(caller);577 let user = user.into_sub_cross_account::<T>()?;578 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;579 Ok(())580 }581582 583 584 585 #[solidity(hide)]586 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {587 self.consume_store_writes(1)?;588589 let caller = T::CrossAccountId::from_eth(caller);590 let user = T::CrossAccountId::from_eth(user);591 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;592 Ok(())593 }594595 596 597 598 fn remove_from_collection_allow_list_cross(599 &mut self,600 caller: caller,601 user: EthCrossAccount,602 ) -> Result<void> {603 self.consume_store_writes(1)?;604605 let caller = T::CrossAccountId::from_eth(caller);606 let user = user.into_sub_cross_account::<T>()?;607 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;608 Ok(())609 }610611 612 613 614 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {615 self.consume_store_reads_and_writes(1, 1)?;616617 check_is_owner_or_admin(caller, self)?;618 let permissions = CollectionPermissions {619 mint_mode: Some(mode),620 ..Default::default()621 };622 self.collection.permissions = <Pallet<T>>::clamp_permissions(623 self.collection.mode.clone(),624 &self.collection.permissions,625 permissions,626 )627 .map_err(dispatch_to_evm::<T>)?;628629 save(self)630 }631632 633 634 635 636 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]637 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {638 let user = T::CrossAccountId::from_eth(user);639 Ok(self.is_owner_or_admin(&user))640 }641642 643 644 645 646 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {647 let user = user.into_sub_cross_account::<T>()?;648 Ok(self.is_owner_or_admin(&user))649 }650651 652 653 654 fn unique_collection_type(&self) -> Result<string> {655 let mode = match self.collection.mode {656 CollectionMode::Fungible(_) => "Fungible",657 CollectionMode::NFT => "NFT",658 CollectionMode::ReFungible => "ReFungible",659 };660 Ok(mode.into())661 }662663 664 665 666 667 fn collection_owner(&self) -> Result<EthCrossAccount> {668 Ok(EthCrossAccount::from_sub_cross_account::<T>(669 &T::CrossAccountId::from_sub(self.owner.clone()),670 ))671 }672673 674 675 676 677 #[solidity(hide, rename_selector = "changeCollectionOwner")]678 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {679 self.consume_store_writes(1)?;680681 let caller = T::CrossAccountId::from_eth(caller);682 let new_owner = T::CrossAccountId::from_eth(new_owner);683 self.set_owner_internal(caller, new_owner)684 .map_err(dispatch_to_evm::<T>)685 }686687 688 689 690 691 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {692 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))693 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))694 .collect();695 Ok(result)696 }697698 699 700 701 702 fn change_collection_owner_cross(703 &mut self,704 caller: caller,705 new_owner: EthCrossAccount,706 ) -> Result<void> {707 self.consume_store_writes(1)?;708709 let caller = T::CrossAccountId::from_eth(caller);710 let new_owner = new_owner.into_sub_cross_account::<T>()?;711 self.set_owner_internal(caller, new_owner)712 .map_err(dispatch_to_evm::<T>)713 }714}715716717718fn check_is_owner_or_admin<T: Config>(719 caller: caller,720 collection: &CollectionHandle<T>,721) -> Result<T::CrossAccountId> {722 let caller = T::CrossAccountId::from_eth(caller);723 collection724 .check_is_owner_or_admin(&caller)725 .map_err(dispatch_to_evm::<T>)?;726 Ok(caller)727}728729730731fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {732 collection733 .check_is_internal()734 .map_err(dispatch_to_evm::<T>)?;735 collection.save().map_err(dispatch_to_evm::<T>)?;736 Ok(())737}738739740pub mod static_property {741 use evm_coder::{742 execution::{Result, Error},743 };744 use alloc::format;745746 const EXPECT_CONVERT_ERROR: &str = "length < limit";747748 749 pub mod key {750 use super::*;751752 753 pub fn base_uri() -> up_data_structs::PropertyKey {754 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)755 }756757 758 pub fn url() -> up_data_structs::PropertyKey {759 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)760 }761762 763 pub fn suffix() -> up_data_structs::PropertyKey {764 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)765 }766767 768 pub fn parent_nft() -> up_data_structs::PropertyKey {769 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)770 }771 }772773 774 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {775 bytes.to_vec().try_into().map_err(|_| {776 Error::Revert(format!(777 "Property key is too long. Max length is {}.",778 up_data_structs::PropertyKey::bound()779 ))780 })781 }782783 784 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {785 bytes.to_vec().try_into().map_err(|_| {786 Error::Revert(format!(787 "Property key is too long. Max length is {}.",788 up_data_structs::PropertyKey::bound()789 ))790 })791 }792}