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 },6768 69 TokenChanged {70 71 #[indexed]72 collection_id: address,73 74 token_id: uint256,75 },76}77787980pub trait CommonEvmHandler {81 82 const CODE: &'static [u8];8384 85 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;86}878889#[solidity_interface(name = Collection)]90impl<T: Config> CollectionHandle<T>91where92 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,93{94 95 96 97 98 #[solidity(hide)]99 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]100 fn set_collection_property(101 &mut self,102 caller: caller,103 key: string,104 value: bytes,105 ) -> Result<void> {106 let caller = T::CrossAccountId::from_eth(caller);107 let key = <Vec<u8>>::from(key)108 .try_into()109 .map_err(|_| "key too large")?;110 let value = value.0.try_into().map_err(|_| "value too large")?;111112 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })113 .map_err(dispatch_to_evm::<T>)114 }115116 117 118 119 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]120 fn set_collection_properties(121 &mut self,122 caller: caller,123 properties: Vec<PropertyStruct>,124 ) -> Result<void> {125 let caller = T::CrossAccountId::from_eth(caller);126127 let properties = properties128 .into_iter()129 .map(|PropertyStruct { key, value }| {130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too large")?;133134 let value = value.0.try_into().map_err(|_| "value too large")?;135136 Ok(Property { key, value })137 })138 .collect::<Result<Vec<_>>>()?;139140 <Pallet<T>>::set_collection_properties(self, &caller, properties)141 .map_err(dispatch_to_evm::<T>)142 }143144 145 146 147 #[solidity(hide)]148 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]149 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {150 let caller = T::CrossAccountId::from_eth(caller);151 let key = <Vec<u8>>::from(key)152 .try_into()153 .map_err(|_| "key too large")?;154155 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)156 }157158 159 160 161 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]162 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {163 let caller = T::CrossAccountId::from_eth(caller);164 let keys = keys165 .into_iter()166 .map(|key| {167 <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| Error::Revert("key too large".into()))170 })171 .collect::<Result<Vec<_>>>()?;172173 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)174 }175176 177 178 179 180 181 182 fn collection_property(&self, key: string) -> Result<bytes> {183 let key = <Vec<u8>>::from(key)184 .try_into()185 .map_err(|_| "key too large")?;186187 let props = CollectionProperties::<T>::get(self.id);188 let prop = props.get(&key).ok_or("key not found")?;189190 Ok(bytes(prop.to_vec()))191 }192193 194 195 196 197 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {198 let keys = keys199 .into_iter()200 .map(|key| {201 <Vec<u8>>::from(key)202 .try_into()203 .map_err(|_| Error::Revert("key too large".into()))204 })205 .collect::<Result<Vec<_>>>()?;206207 let properties = Pallet::<T>::filter_collection_properties(208 self.id,209 if keys.is_empty() { None } else { Some(keys) },210 )211 .map_err(dispatch_to_evm::<T>)?;212213 let properties = properties214 .into_iter()215 .map(|p| {216 let key =217 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;218 let value = bytes(p.value.to_vec());219 Ok(PropertyStruct { key, value })220 })221 .collect::<Result<Vec<_>>>()?;222 Ok(properties)223 }224225 226 227 228 229 230 #[solidity(hide)]231 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {232 self.consume_store_reads_and_writes(1, 1)?;233234 let caller = T::CrossAccountId::from_eth(caller);235236 let sponsor = T::CrossAccountId::from_eth(sponsor);237 self.set_sponsor(&caller, sponsor.as_sub().clone())238 .map_err(dispatch_to_evm::<T>)239 }240241 242 243 244 245 246 fn set_collection_sponsor_cross(247 &mut self,248 caller: caller,249 sponsor: EthCrossAccount,250 ) -> Result<void> {251 self.consume_store_reads_and_writes(1, 1)?;252253 let caller = T::CrossAccountId::from_eth(caller);254255 let sponsor = sponsor.into_sub_cross_account::<T>()?;256 self.set_sponsor(&caller, sponsor.as_sub().clone())257 .map_err(dispatch_to_evm::<T>)258 }259260 261 fn has_collection_pending_sponsor(&self) -> Result<bool> {262 Ok(matches!(263 self.collection.sponsorship,264 SponsorshipState::Unconfirmed(_)265 ))266 }267268 269 270 271 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {272 self.consume_store_writes(1)?;273274 let caller = T::CrossAccountId::from_eth(caller);275 self.confirm_sponsorship(caller.as_sub())276 .map_err(dispatch_to_evm::<T>)277 }278279 280 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {281 self.consume_store_reads_and_writes(1, 1)?;282 let caller = T::CrossAccountId::from_eth(caller);283 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)284 }285286 287 288 289 fn collection_sponsor(&self) -> Result<(address, uint256)> {290 let sponsor = match self.collection.sponsorship.sponsor() {291 Some(sponsor) => sponsor,292 None => return Ok(Default::default()),293 };294 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());295 let result: (address, uint256) = if sponsor.is_canonical_substrate() {296 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);297 (Default::default(), sponsor)298 } else {299 let sponsor = *sponsor.as_eth();300 (sponsor, Default::default())301 };302 Ok(result)303 }304305 306 307 308 309 310 311 312 313 314 315 316 317 318 #[solidity(rename_selector = "setCollectionLimit")]319 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint256) -> Result<void> {320 self.consume_store_reads_and_writes(1, 1)?;321322 let value = value323 .try_into()324 .map_err(|_| Error::Revert(format!("can't convert value to u32 \"{}\"", value)))?;325326 let convert_value_to_bool = || match value {327 0 => Ok(false),328 1 => Ok(true),329 _ => {330 return Err(Error::Revert(format!(331 "can't convert value to boolean \"{}\"",332 value333 )))334 }335 };336337 let mut limits = self.limits.clone();338339 match limit.as_str() {340 "accountTokenOwnershipLimit" => {341 limits.account_token_ownership_limit = Some(value);342 }343 "sponsoredDataSize" => {344 limits.sponsored_data_size = Some(value);345 }346 "sponsoredDataRateLimit" => {347 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));348 }349 "tokenLimit" => {350 limits.token_limit = Some(value);351 }352 "sponsorTransferTimeout" => {353 limits.sponsor_transfer_timeout = Some(value);354 }355 "sponsorApproveTimeout" => {356 limits.sponsor_approve_timeout = Some(value);357 }358 "ownerCanTransfer" => {359 limits.owner_can_transfer = Some(convert_value_to_bool()?);360 }361 "ownerCanDestroy" => {362 limits.owner_can_destroy = Some(convert_value_to_bool()?);363 }364 "transfersEnabled" => {365 limits.transfers_enabled = Some(convert_value_to_bool()?);366 }367 _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))),368 }369370 let caller = T::CrossAccountId::from_eth(caller);371 <Pallet<T>>::update_limits(&caller, self, limits).map_err(dispatch_to_evm::<T>)372 }373374 375 fn contract_address(&self) -> Result<address> {376 Ok(crate::eth::collection_id_to_address(self.id))377 }378379 380 381 fn add_collection_admin_cross(382 &mut self,383 caller: caller,384 new_admin: EthCrossAccount,385 ) -> Result<void> {386 self.consume_store_reads_and_writes(2, 2)?;387388 let caller = T::CrossAccountId::from_eth(caller);389 let new_admin = new_admin.into_sub_cross_account::<T>()?;390 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;391 Ok(())392 }393394 395 396 fn remove_collection_admin_cross(397 &mut self,398 caller: caller,399 admin: EthCrossAccount,400 ) -> Result<void> {401 self.consume_store_reads_and_writes(2, 2)?;402403 let caller = T::CrossAccountId::from_eth(caller);404 let admin = admin.into_sub_cross_account::<T>()?;405 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;406 Ok(())407 }408409 410 411 #[solidity(hide)]412 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {413 self.consume_store_reads_and_writes(2, 2)?;414415 let caller = T::CrossAccountId::from_eth(caller);416 let new_admin = T::CrossAccountId::from_eth(new_admin);417 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;418 Ok(())419 }420421 422 423 424 #[solidity(hide)]425 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {426 self.consume_store_reads_and_writes(2, 2)?;427428 let caller = T::CrossAccountId::from_eth(caller);429 let admin = T::CrossAccountId::from_eth(admin);430 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;431 Ok(())432 }433434 435 436 437 #[solidity(rename_selector = "setCollectionNesting")]438 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {439 self.consume_store_reads_and_writes(1, 1)?;440441 let caller = T::CrossAccountId::from_eth(caller);442443 let mut permissions = self.collection.permissions.clone();444 let mut nesting = permissions.nesting().clone();445 nesting.token_owner = enable;446 nesting.restricted = None;447 permissions.nesting = Some(nesting);448449 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)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 let caller = T::CrossAccountId::from_eth(caller);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 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)494 }495496 497 498 499 500 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {501 self.consume_store_reads_and_writes(1, 1)?;502503 let caller = T::CrossAccountId::from_eth(caller);504 let permissions = CollectionPermissions {505 access: Some(match mode {506 0 => AccessMode::Normal,507 1 => AccessMode::AllowList,508 _ => return Err("not supported access mode".into()),509 }),510 ..Default::default()511 };512 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)513 }514515 516 517 518 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {519 let user = user.into_sub_cross_account::<T>()?;520 Ok(Pallet::<T>::allowed(self.id, user))521 }522523 524 525 526 #[solidity(hide)]527 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {528 self.consume_store_writes(1)?;529530 let caller = T::CrossAccountId::from_eth(caller);531 let user = T::CrossAccountId::from_eth(user);532 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;533 Ok(())534 }535536 537 538 539 fn add_to_collection_allow_list_cross(540 &mut self,541 caller: caller,542 user: EthCrossAccount,543 ) -> Result<void> {544 self.consume_store_writes(1)?;545546 let caller = T::CrossAccountId::from_eth(caller);547 let user = user.into_sub_cross_account::<T>()?;548 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;549 Ok(())550 }551552 553 554 555 #[solidity(hide)]556 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {557 self.consume_store_writes(1)?;558559 let caller = T::CrossAccountId::from_eth(caller);560 let user = T::CrossAccountId::from_eth(user);561 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;562 Ok(())563 }564565 566 567 568 fn remove_from_collection_allow_list_cross(569 &mut self,570 caller: caller,571 user: EthCrossAccount,572 ) -> Result<void> {573 self.consume_store_writes(1)?;574575 let caller = T::CrossAccountId::from_eth(caller);576 let user = user.into_sub_cross_account::<T>()?;577 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;578 Ok(())579 }580581 582 583 584 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {585 self.consume_store_reads_and_writes(1, 1)?;586587 let caller = T::CrossAccountId::from_eth(caller);588 let permissions = CollectionPermissions {589 mint_mode: Some(mode),590 ..Default::default()591 };592 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)593 }594595 596 597 598 599 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]600 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {601 let user = T::CrossAccountId::from_eth(user);602 Ok(self.is_owner_or_admin(&user))603 }604605 606 607 608 609 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {610 let user = user.into_sub_cross_account::<T>()?;611 Ok(self.is_owner_or_admin(&user))612 }613614 615 616 617 fn unique_collection_type(&self) -> Result<string> {618 let mode = match self.collection.mode {619 CollectionMode::Fungible(_) => "Fungible",620 CollectionMode::NFT => "NFT",621 CollectionMode::ReFungible => "ReFungible",622 };623 Ok(mode.into())624 }625626 627 628 629 630 fn collection_owner(&self) -> Result<EthCrossAccount> {631 Ok(EthCrossAccount::from_sub_cross_account::<T>(632 &T::CrossAccountId::from_sub(self.owner.clone()),633 ))634 }635636 637 638 639 640 #[solidity(hide, rename_selector = "changeCollectionOwner")]641 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {642 self.consume_store_writes(1)?;643644 let caller = T::CrossAccountId::from_eth(caller);645 let new_owner = T::CrossAccountId::from_eth(new_owner);646 self.change_owner(caller, new_owner)647 .map_err(dispatch_to_evm::<T>)648 }649650 651 652 653 654 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {655 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))656 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))657 .collect();658 Ok(result)659 }660661 662 663 664 665 fn change_collection_owner_cross(666 &mut self,667 caller: caller,668 new_owner: EthCrossAccount,669 ) -> Result<void> {670 self.consume_store_writes(1)?;671672 let caller = T::CrossAccountId::from_eth(caller);673 let new_owner = new_owner.into_sub_cross_account::<T>()?;674 self.change_owner(caller, new_owner)675 .map_err(dispatch_to_evm::<T>)676 }677}678679680681fn check_is_owner_or_admin<T: Config>(682 caller: caller,683 collection: &CollectionHandle<T>,684) -> Result<T::CrossAccountId> {685 let caller = T::CrossAccountId::from_eth(caller);686 collection687 .check_is_owner_or_admin(&caller)688 .map_err(dispatch_to_evm::<T>)?;689 Ok(caller)690}691692693694fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {695 collection696 .check_is_internal()697 .map_err(dispatch_to_evm::<T>)?;698 collection.save().map_err(dispatch_to_evm::<T>)?;699 Ok(())700}701702703pub mod static_property {704 use evm_coder::{705 execution::{Result, Error},706 };707 use alloc::format;708709 const EXPECT_CONVERT_ERROR: &str = "length < limit";710711 712 pub mod key {713 use super::*;714715 716 pub fn base_uri() -> up_data_structs::PropertyKey {717 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)718 }719720 721 pub fn url() -> up_data_structs::PropertyKey {722 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)723 }724725 726 pub fn suffix() -> up_data_structs::PropertyKey {727 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)728 }729730 731 pub fn parent_nft() -> up_data_structs::PropertyKey {732 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)733 }734 }735736 737 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {738 bytes.to_vec().try_into().map_err(|_| {739 Error::Revert(format!(740 "Property key is too long. Max length is {}.",741 up_data_structs::PropertyKey::bound()742 ))743 })744 }745746 747 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {748 bytes.to_vec().try_into().map_err(|_| {749 Error::Revert(format!(750 "Property key is too long. Max length is {}.",751 up_data_structs::PropertyKey::bound()752 ))753 })754 }755}