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<PropertyStruct>> {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(PropertyStruct { 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 308 309 310 #[solidity(rename_selector = "setCollectionLimit")]311 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {312 self.consume_store_reads_and_writes(1, 1)?;313314 let value = value315 .try_into()316 .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;317318 let convert_value_to_bool = || match value {319 0 => Ok(false),320 1 => Ok(true),321 _ => {322 return Err(Error::Revert(format!(323 "can't convert value to boolean \"{}\"",324 value325 )))326 }327 };328329 check_is_owner_or_admin(caller, self)?;330 let mut limits = self.limits.clone();331332 match limit.as_str() {333 "accountTokenOwnershipLimit" => {334 limits.account_token_ownership_limit = Some(value);335 }336 "sponsoredDataSize" => {337 limits.sponsored_data_size = Some(value);338 }339 "sponsoredDataRateLimit" => {340 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));341 }342 "tokenLimit" => {343 limits.token_limit = Some(value);344 }345 "sponsorTransferTimeout" => {346 limits.sponsor_transfer_timeout = Some(value);347 }348 "sponsorApproveTimeout" => {349 limits.sponsor_approve_timeout = Some(value);350 }351 "ownerCanTransfer" => {352 limits.owner_can_transfer = Some(convert_value_to_bool()?);353 }354 "ownerCanDestroy" => {355 limits.owner_can_destroy = Some(convert_value_to_bool()?);356 }357 "transfersEnabled" => {358 limits.transfers_enabled = Some(convert_value_to_bool()?);359 }360 _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),361 }362 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)363 .map_err(dispatch_to_evm::<T>)?;364 save(self)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: EthCrossAccount,378 ) -> Result<void> {379 self.consume_store_writes(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: EthCrossAccount,393 ) -> Result<void> {394 self.consume_store_writes(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_writes(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_writes(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 check_is_owner_or_admin(caller, self)?;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 self.collection.permissions = <Pallet<T>>::clamp_permissions(443 self.collection.mode.clone(),444 &self.collection.permissions,445 permissions,446 )447 .map_err(dispatch_to_evm::<T>)?;448449 save(self)450 }451452 453 454 455 456 #[solidity(rename_selector = "setCollectionNesting")]457 fn set_nesting(458 &mut self,459 caller: caller,460 enable: bool,461 collections: Vec<address>,462 ) -> Result<void> {463 self.consume_store_reads_and_writes(1, 1)?;464465 if collections.is_empty() {466 return Err("no addresses provided".into());467 }468 check_is_owner_or_admin(caller, self)?;469470 let mut permissions = self.collection.permissions.clone();471 match enable {472 false => {473 let mut nesting = permissions.nesting().clone();474 nesting.token_owner = false;475 nesting.restricted = None;476 permissions.nesting = Some(nesting);477 }478 true => {479 let mut bv = OwnerRestrictedSet::new();480 for i in collections {481 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {482 Error::Revert("Can't convert address into collection id".into())483 })?)484 .map_err(|_| "too many collections")?;485 }486 let mut nesting = permissions.nesting().clone();487 nesting.token_owner = true;488 nesting.restricted = Some(bv);489 permissions.nesting = Some(nesting);490 }491 };492493 self.collection.permissions = <Pallet<T>>::clamp_permissions(494 self.collection.mode.clone(),495 &self.collection.permissions,496 permissions,497 )498 .map_err(dispatch_to_evm::<T>)?;499500 save(self)501 }502503 504 505 506 507 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {508 self.consume_store_reads_and_writes(1, 1)?;509510 check_is_owner_or_admin(caller, self)?;511 let permissions = CollectionPermissions {512 access: Some(match mode {513 0 => AccessMode::Normal,514 1 => AccessMode::AllowList,515 _ => return Err("not supported access mode".into()),516 }),517 ..Default::default()518 };519 self.collection.permissions = <Pallet<T>>::clamp_permissions(520 self.collection.mode.clone(),521 &self.collection.permissions,522 permissions,523 )524 .map_err(dispatch_to_evm::<T>)?;525526 save(self)527 }528529 530 531 532 fn allowed(&self, user: address) -> Result<bool> {533 Ok(Pallet::<T>::allowed(534 self.id,535 T::CrossAccountId::from_eth(user),536 ))537 }538539 540 541 542 #[solidity(hide)]543 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {544 self.consume_store_writes(1)?;545546 let caller = T::CrossAccountId::from_eth(caller);547 let user = T::CrossAccountId::from_eth(user);548 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;549 Ok(())550 }551552 553 554 555 fn add_to_collection_allow_list_cross(556 &mut self,557 caller: caller,558 user: EthCrossAccount,559 ) -> Result<void> {560 self.consume_store_writes(1)?;561562 let caller = T::CrossAccountId::from_eth(caller);563 let user = user.into_sub_cross_account::<T>()?;564 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;565 Ok(())566 }567568 569 570 571 #[solidity(hide)]572 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {573 self.consume_store_writes(1)?;574575 let caller = T::CrossAccountId::from_eth(caller);576 let user = T::CrossAccountId::from_eth(user);577 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;578 Ok(())579 }580581 582 583 584 fn remove_from_collection_allow_list_cross(585 &mut self,586 caller: caller,587 user: EthCrossAccount,588 ) -> Result<void> {589 self.consume_store_writes(1)?;590591 let caller = T::CrossAccountId::from_eth(caller);592 let user = user.into_sub_cross_account::<T>()?;593 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;594 Ok(())595 }596597 598 599 600 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {601 self.consume_store_reads_and_writes(1, 1)?;602603 check_is_owner_or_admin(caller, self)?;604 let permissions = CollectionPermissions {605 mint_mode: Some(mode),606 ..Default::default()607 };608 self.collection.permissions = <Pallet<T>>::clamp_permissions(609 self.collection.mode.clone(),610 &self.collection.permissions,611 permissions,612 )613 .map_err(dispatch_to_evm::<T>)?;614615 save(self)616 }617618 619 620 621 622 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]623 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {624 let user = T::CrossAccountId::from_eth(user);625 Ok(self.is_owner_or_admin(&user))626 }627628 629 630 631 632 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {633 let user = user.into_sub_cross_account::<T>()?;634 Ok(self.is_owner_or_admin(&user))635 }636637 638 639 640 fn unique_collection_type(&self) -> Result<string> {641 let mode = match self.collection.mode {642 CollectionMode::Fungible(_) => "Fungible",643 CollectionMode::NFT => "NFT",644 CollectionMode::ReFungible => "ReFungible",645 };646 Ok(mode.into())647 }648649 650 651 652 653 fn collection_owner(&self) -> Result<EthCrossAccount> {654 Ok(EthCrossAccount::from_sub_cross_account::<T>(655 &T::CrossAccountId::from_sub(self.owner.clone()),656 ))657 }658659 660 661 662 663 #[solidity(hide, rename_selector = "changeCollectionOwner")]664 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {665 self.consume_store_writes(1)?;666667 let caller = T::CrossAccountId::from_eth(caller);668 let new_owner = T::CrossAccountId::from_eth(new_owner);669 self.set_owner_internal(caller, new_owner)670 .map_err(dispatch_to_evm::<T>)671 }672673 674 675 676 677 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {678 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))679 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))680 .collect();681 Ok(result)682 }683684 685 686 687 688 fn change_collection_owner_cross(689 &mut self,690 caller: caller,691 new_owner: EthCrossAccount,692 ) -> Result<void> {693 self.consume_store_writes(1)?;694695 let caller = T::CrossAccountId::from_eth(caller);696 let new_owner = new_owner.into_sub_cross_account::<T>()?;697 self.set_owner_internal(caller, new_owner)698 .map_err(dispatch_to_evm::<T>)699 }700}701702703704fn check_is_owner_or_admin<T: Config>(705 caller: caller,706 collection: &CollectionHandle<T>,707) -> Result<T::CrossAccountId> {708 let caller = T::CrossAccountId::from_eth(caller);709 collection710 .check_is_owner_or_admin(&caller)711 .map_err(dispatch_to_evm::<T>)?;712 Ok(caller)713}714715716717fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {718 collection719 .check_is_internal()720 .map_err(dispatch_to_evm::<T>)?;721 collection.save().map_err(dispatch_to_evm::<T>)?;722 Ok(())723}724725726pub mod static_property {727 use evm_coder::{728 execution::{Result, Error},729 };730 use alloc::format;731732 const EXPECT_CONVERT_ERROR: &str = "length < limit";733734 735 pub mod key {736 use super::*;737738 739 pub fn base_uri() -> up_data_structs::PropertyKey {740 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)741 }742743 744 pub fn url() -> up_data_structs::PropertyKey {745 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)746 }747748 749 pub fn suffix() -> up_data_structs::PropertyKey {750 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)751 }752753 754 pub fn parent_nft() -> up_data_structs::PropertyKey {755 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)756 }757 }758759 760 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {761 bytes.to_vec().try_into().map_err(|_| {762 Error::Revert(format!(763 "Property key is too long. Max length is {}.",764 up_data_structs::PropertyKey::bound()765 ))766 })767 }768769 770 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {771 bytes.to_vec().try_into().map_err(|_| {772 Error::Revert(format!(773 "Property key is too long. Max length is {}.",774 up_data_structs::PropertyKey::bound()775 ))776 })777 }778}