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 allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {533 let user = user.into_sub_cross_account::<T>()?;534 Ok(Pallet::<T>::allowed(self.id, user))535 }536537 538 539 540 #[solidity(hide)]541 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {542 self.consume_store_writes(1)?;543544 let caller = T::CrossAccountId::from_eth(caller);545 let user = T::CrossAccountId::from_eth(user);546 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;547 Ok(())548 }549550 551 552 553 fn add_to_collection_allow_list_cross(554 &mut self,555 caller: caller,556 user: EthCrossAccount,557 ) -> Result<void> {558 self.consume_store_writes(1)?;559560 let caller = T::CrossAccountId::from_eth(caller);561 let user = user.into_sub_cross_account::<T>()?;562 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;563 Ok(())564 }565566 567 568 569 #[solidity(hide)]570 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {571 self.consume_store_writes(1)?;572573 let caller = T::CrossAccountId::from_eth(caller);574 let user = T::CrossAccountId::from_eth(user);575 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;576 Ok(())577 }578579 580 581 582 fn remove_from_collection_allow_list_cross(583 &mut self,584 caller: caller,585 user: EthCrossAccount,586 ) -> Result<void> {587 self.consume_store_writes(1)?;588589 let caller = T::CrossAccountId::from_eth(caller);590 let user = user.into_sub_cross_account::<T>()?;591 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;592 Ok(())593 }594595 596 597 598 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {599 self.consume_store_reads_and_writes(1, 1)?;600601 check_is_owner_or_admin(caller, self)?;602 let permissions = CollectionPermissions {603 mint_mode: Some(mode),604 ..Default::default()605 };606 self.collection.permissions = <Pallet<T>>::clamp_permissions(607 self.collection.mode.clone(),608 &self.collection.permissions,609 permissions,610 )611 .map_err(dispatch_to_evm::<T>)?;612613 save(self)614 }615616 617 618 619 620 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]621 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {622 let user = T::CrossAccountId::from_eth(user);623 Ok(self.is_owner_or_admin(&user))624 }625626 627 628 629 630 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {631 let user = user.into_sub_cross_account::<T>()?;632 Ok(self.is_owner_or_admin(&user))633 }634635 636 637 638 fn unique_collection_type(&self) -> Result<string> {639 let mode = match self.collection.mode {640 CollectionMode::Fungible(_) => "Fungible",641 CollectionMode::NFT => "NFT",642 CollectionMode::ReFungible => "ReFungible",643 };644 Ok(mode.into())645 }646647 648 649 650 651 fn collection_owner(&self) -> Result<EthCrossAccount> {652 Ok(EthCrossAccount::from_sub_cross_account::<T>(653 &T::CrossAccountId::from_sub(self.owner.clone()),654 ))655 }656657 658 659 660 661 #[solidity(hide, rename_selector = "changeCollectionOwner")]662 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {663 self.consume_store_writes(1)?;664665 let caller = T::CrossAccountId::from_eth(caller);666 let new_owner = T::CrossAccountId::from_eth(new_owner);667 self.set_owner_internal(caller, new_owner)668 .map_err(dispatch_to_evm::<T>)669 }670671 672 673 674 675 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {676 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))677 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))678 .collect();679 Ok(result)680 }681682 683 684 685 686 fn change_collection_owner_cross(687 &mut self,688 caller: caller,689 new_owner: EthCrossAccount,690 ) -> Result<void> {691 self.consume_store_writes(1)?;692693 let caller = T::CrossAccountId::from_eth(caller);694 let new_owner = new_owner.into_sub_cross_account::<T>()?;695 self.set_owner_internal(caller, new_owner)696 .map_err(dispatch_to_evm::<T>)697 }698}699700701702fn check_is_owner_or_admin<T: Config>(703 caller: caller,704 collection: &CollectionHandle<T>,705) -> Result<T::CrossAccountId> {706 let caller = T::CrossAccountId::from_eth(caller);707 collection708 .check_is_owner_or_admin(&caller)709 .map_err(dispatch_to_evm::<T>)?;710 Ok(caller)711}712713714715fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {716 collection717 .check_is_internal()718 .map_err(dispatch_to_evm::<T>)?;719 collection.save().map_err(dispatch_to_evm::<T>)?;720 Ok(())721}722723724pub mod static_property {725 use evm_coder::{726 execution::{Result, Error},727 };728 use alloc::format;729730 const EXPECT_CONVERT_ERROR: &str = "length < limit";731732 733 pub mod key {734 use super::*;735736 737 pub fn base_uri() -> up_data_structs::PropertyKey {738 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)739 }740741 742 pub fn url() -> up_data_structs::PropertyKey {743 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)744 }745746 747 pub fn suffix() -> up_data_structs::PropertyKey {748 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)749 }750751 752 pub fn parent_nft() -> up_data_structs::PropertyKey {753 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)754 }755 }756757 758 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {759 bytes.to_vec().try_into().map_err(|_| {760 Error::Revert(format!(761 "Property key is too long. Max length is {}.",762 up_data_structs::PropertyKey::bound()763 ))764 })765 }766767 768 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {769 bytes.to_vec().try_into().map_err(|_| {770 Error::Revert(format!(771 "Property key is too long. Max length is {}.",772 up_data_structs::PropertyKey::bound()773 ))774 })775 }776}