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 #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")]498 fn collection_nesting_restricted_ids(&self) -> Result<(bool, Vec<uint256>)> {499 let nesting = self.collection.permissions.nesting();500501 Ok((502 nesting.token_owner,503 nesting504 .restricted505 .clone()506 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())507 .unwrap_or_default(),508 ))509 }510511 512 513 514 515 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {516 self.consume_store_reads_and_writes(1, 1)?;517518 let caller = T::CrossAccountId::from_eth(caller);519 let permissions = CollectionPermissions {520 access: Some(match mode {521 0 => AccessMode::Normal,522 1 => AccessMode::AllowList,523 _ => return Err("not supported access mode".into()),524 }),525 ..Default::default()526 };527 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)528 }529530 531 532 533 fn allowlisted_cross(&self, user: EthCrossAccount) -> Result<bool> {534 let user = user.into_sub_cross_account::<T>()?;535 Ok(Pallet::<T>::allowed(self.id, user))536 }537538 539 540 541 #[solidity(hide)]542 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {543 self.consume_store_writes(1)?;544545 let caller = T::CrossAccountId::from_eth(caller);546 let user = T::CrossAccountId::from_eth(user);547 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;548 Ok(())549 }550551 552 553 554 fn add_to_collection_allow_list_cross(555 &mut self,556 caller: caller,557 user: EthCrossAccount,558 ) -> Result<void> {559 self.consume_store_writes(1)?;560561 let caller = T::CrossAccountId::from_eth(caller);562 let user = user.into_sub_cross_account::<T>()?;563 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;564 Ok(())565 }566567 568 569 570 #[solidity(hide)]571 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {572 self.consume_store_writes(1)?;573574 let caller = T::CrossAccountId::from_eth(caller);575 let user = T::CrossAccountId::from_eth(user);576 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;577 Ok(())578 }579580 581 582 583 fn remove_from_collection_allow_list_cross(584 &mut self,585 caller: caller,586 user: EthCrossAccount,587 ) -> Result<void> {588 self.consume_store_writes(1)?;589590 let caller = T::CrossAccountId::from_eth(caller);591 let user = user.into_sub_cross_account::<T>()?;592 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;593 Ok(())594 }595596 597 598 599 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {600 self.consume_store_reads_and_writes(1, 1)?;601602 let caller = T::CrossAccountId::from_eth(caller);603 let permissions = CollectionPermissions {604 mint_mode: Some(mode),605 ..Default::default()606 };607 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)608 }609610 611 612 613 614 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]615 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {616 let user = T::CrossAccountId::from_eth(user);617 Ok(self.is_owner_or_admin(&user))618 }619620 621 622 623 624 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {625 let user = user.into_sub_cross_account::<T>()?;626 Ok(self.is_owner_or_admin(&user))627 }628629 630 631 632 fn unique_collection_type(&self) -> Result<string> {633 let mode = match self.collection.mode {634 CollectionMode::Fungible(_) => "Fungible",635 CollectionMode::NFT => "NFT",636 CollectionMode::ReFungible => "ReFungible",637 };638 Ok(mode.into())639 }640641 642 643 644 645 fn collection_owner(&self) -> Result<EthCrossAccount> {646 Ok(EthCrossAccount::from_sub_cross_account::<T>(647 &T::CrossAccountId::from_sub(self.owner.clone()),648 ))649 }650651 652 653 654 655 #[solidity(hide, rename_selector = "changeCollectionOwner")]656 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {657 self.consume_store_writes(1)?;658659 let caller = T::CrossAccountId::from_eth(caller);660 let new_owner = T::CrossAccountId::from_eth(new_owner);661 self.change_owner(caller, new_owner)662 .map_err(dispatch_to_evm::<T>)663 }664665 666 667 668 669 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {670 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))671 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))672 .collect();673 Ok(result)674 }675676 677 678 679 680 fn change_collection_owner_cross(681 &mut self,682 caller: caller,683 new_owner: EthCrossAccount,684 ) -> Result<void> {685 self.consume_store_writes(1)?;686687 let caller = T::CrossAccountId::from_eth(caller);688 let new_owner = new_owner.into_sub_cross_account::<T>()?;689 self.change_owner(caller, new_owner)690 .map_err(dispatch_to_evm::<T>)691 }692}693694695696fn check_is_owner_or_admin<T: Config>(697 caller: caller,698 collection: &CollectionHandle<T>,699) -> Result<T::CrossAccountId> {700 let caller = T::CrossAccountId::from_eth(caller);701 collection702 .check_is_owner_or_admin(&caller)703 .map_err(dispatch_to_evm::<T>)?;704 Ok(caller)705}706707708709fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {710 collection711 .check_is_internal()712 .map_err(dispatch_to_evm::<T>)?;713 collection.save().map_err(dispatch_to_evm::<T>)?;714 Ok(())715}716717718pub mod static_property {719 use evm_coder::{720 execution::{Result, Error},721 };722 use alloc::format;723724 const EXPECT_CONVERT_ERROR: &str = "length < limit";725726 727 pub mod key {728 use super::*;729730 731 pub fn base_uri() -> up_data_structs::PropertyKey {732 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)733 }734735 736 pub fn url() -> up_data_structs::PropertyKey {737 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)738 }739740 741 pub fn suffix() -> up_data_structs::PropertyKey {742 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)743 }744745 746 pub fn parent_nft() -> up_data_structs::PropertyKey {747 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)748 }749 }750751 752 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {753 bytes.to_vec().try_into().map_err(|_| {754 Error::Revert(format!(755 "Property key is too long. Max length is {}.",756 up_data_structs::PropertyKey::bound()757 ))758 })759 }760761 762 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {763 bytes.to_vec().try_into().map_err(|_| {764 Error::Revert(format!(765 "Property key is too long. Max length is {}.",766 up_data_structs::PropertyKey::bound()767 ))768 })769 }770}