12345678910111213141516171819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23 weight,24};25pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};26use pallet_evm_coder_substrate::dispatch_to_evm;27use sp_std::vec::Vec;28use up_data_structs::{29 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,30 SponsoringRateLimit, SponsorshipState,31};32use alloc::format;3334use crate::{35 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,36 eth::{37 convert_cross_account_to_uint256, convert_cross_account_to_tuple,38 convert_tuple_to_cross_account,39 },40 weights::WeightInfo,41};424344#[derive(ToLog)]45pub enum CollectionHelpersEvents {46 47 CollectionCreated {48 49 #[indexed]50 owner: address,5152 53 #[indexed]54 collection_id: address,55 },56 57 CollectionDestroyed {58 59 #[indexed]60 collection_id: address,61 },62}63646566pub trait CommonEvmHandler {67 const CODE: &'static [u8];6869 70 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;71}727374#[solidity_interface(name = Collection)]75impl<T: Config> CollectionHandle<T>76where77 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,78{79 80 81 82 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<(string, bytes)>,108 ) -> Result<void> {109 let caller = T::CrossAccountId::from_eth(caller);110111 let properties = properties112 .into_iter()113 .map(|(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 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]132 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let key = <Vec<u8>>::from(key)135 .try_into()136 .map_err(|_| "key too large")?;137138 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)139 }140141 142 143 144 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]145 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {146 let caller = T::CrossAccountId::from_eth(caller);147 let keys = keys148 .into_iter()149 .map(|key| {150 <Vec<u8>>::from(key)151 .try_into()152 .map_err(|_| Error::Revert("key too large".into()))153 })154 .collect::<Result<Vec<_>>>()?;155156 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)157 }158159 160 161 162 163 164 165 fn collection_property(&self, key: string) -> Result<bytes> {166 let key = <Vec<u8>>::from(key)167 .try_into()168 .map_err(|_| "key too large")?;169170 let props = CollectionProperties::<T>::get(self.id);171 let prop = props.get(&key).ok_or("key not found")?;172173 Ok(bytes(prop.to_vec()))174 }175176 177 178 179 180 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {181 let keys = keys182 .into_iter()183 .map(|key| {184 <Vec<u8>>::from(key)185 .try_into()186 .map_err(|_| Error::Revert("key too large".into()))187 })188 .collect::<Result<Vec<_>>>()?;189190 let properties = Pallet::<T>::filter_collection_properties(191 self.id,192 if keys.is_empty() { None } else { Some(keys) },193 )194 .map_err(dispatch_to_evm::<T>)?;195196 let properties = properties197 .into_iter()198 .map(|p| {199 let key =200 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;201 let value = bytes(p.value.to_vec());202 Ok((key, value))203 })204 .collect::<Result<Vec<_>>>()?;205 Ok(properties)206 }207208 209 210 211 212 213 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {214 self.consume_store_reads_and_writes(1, 1)?;215216 check_is_owner_or_admin(caller, self)?;217218 let sponsor = T::CrossAccountId::from_eth(sponsor);219 self.set_sponsor(sponsor.as_sub().clone())220 .map_err(dispatch_to_evm::<T>)?;221 save(self)222 }223224 225 226 227 228 229 fn set_collection_sponsor_cross(230 &mut self,231 caller: caller,232 sponsor: (address, uint256),233 ) -> Result<void> {234 self.consume_store_reads_and_writes(1, 1)?;235236 check_is_owner_or_admin(caller, self)?;237238 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;239 self.set_sponsor(sponsor.as_sub().clone())240 .map_err(dispatch_to_evm::<T>)?;241 save(self)242 }243244 245 fn has_collection_pending_sponsor(&self) -> Result<bool> {246 Ok(matches!(247 self.collection.sponsorship,248 SponsorshipState::Unconfirmed(_)249 ))250 }251252 253 254 255 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {256 self.consume_store_writes(1)?;257258 let caller = T::CrossAccountId::from_eth(caller);259 if !self260 .confirm_sponsorship(caller.as_sub())261 .map_err(dispatch_to_evm::<T>)?262 {263 return Err("caller is not set as sponsor".into());264 }265 save(self)266 }267268 269 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {270 self.consume_store_reads_and_writes(1, 1)?;271 check_is_owner_or_admin(caller, self)?;272 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;273 save(self)274 }275276 277 278 279 fn collection_sponsor(&self) -> Result<(address, uint256)> {280 let sponsor = match self.collection.sponsorship.sponsor() {281 Some(sponsor) => sponsor,282 None => return Ok(Default::default()),283 };284 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());285 let result: (address, uint256) = if sponsor.is_canonical_substrate() {286 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);287 (Default::default(), sponsor)288 } else {289 let sponsor = *sponsor.as_eth();290 (sponsor, Default::default())291 };292 Ok(result)293 }294295 296 297 298 299 300 301 302 303 304 305 #[solidity(rename_selector = "setCollectionLimit")]306 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {307 self.consume_store_reads_and_writes(1, 1)?;308309 check_is_owner_or_admin(caller, self)?;310 let mut limits = self.limits.clone();311312 match limit.as_str() {313 "accountTokenOwnershipLimit" => {314 limits.account_token_ownership_limit = Some(value);315 }316 "sponsoredDataSize" => {317 limits.sponsored_data_size = Some(value);318 }319 "sponsoredDataRateLimit" => {320 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));321 }322 "tokenLimit" => {323 limits.token_limit = Some(value);324 }325 "sponsorTransferTimeout" => {326 limits.sponsor_transfer_timeout = Some(value);327 }328 "sponsorApproveTimeout" => {329 limits.sponsor_approve_timeout = Some(value);330 }331 _ => {332 return Err(Error::Revert(format!(333 "unknown integer limit \"{}\"",334 limit335 )))336 }337 }338 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)339 .map_err(dispatch_to_evm::<T>)?;340 save(self)341 }342343 344 345 346 347 348 349 350 #[solidity(rename_selector = "setCollectionLimit")]351 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {352 self.consume_store_reads_and_writes(1, 1)?;353354 check_is_owner_or_admin(caller, self)?;355 let mut limits = self.limits.clone();356357 match limit.as_str() {358 "ownerCanTransfer" => {359 limits.owner_can_transfer = Some(value);360 }361 "ownerCanDestroy" => {362 limits.owner_can_destroy = Some(value);363 }364 "transfersEnabled" => {365 limits.transfers_enabled = Some(value);366 }367 _ => {368 return Err(Error::Revert(format!(369 "unknown boolean limit \"{}\"",370 limit371 )))372 }373 }374 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)375 .map_err(dispatch_to_evm::<T>)?;376 save(self)377 }378379 380 fn contract_address(&self) -> Result<address> {381 Ok(crate::eth::collection_id_to_address(self.id))382 }383384 385 386 fn add_collection_admin_cross(387 &mut self,388 caller: caller,389 new_admin: (address, uint256),390 ) -> Result<void> {391 self.consume_store_writes(2)?;392393 let caller = T::CrossAccountId::from_eth(caller);394 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;395 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;396 Ok(())397 }398399 400 401 fn remove_collection_admin_cross(402 &mut self,403 caller: caller,404 admin: (address, uint256),405 ) -> Result<void> {406 self.consume_store_writes(2)?;407408 let caller = T::CrossAccountId::from_eth(caller);409 let admin = convert_tuple_to_cross_account::<T>(admin)?;410 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;411 Ok(())412 }413414 415 416 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {417 self.consume_store_writes(2)?;418419 let caller = T::CrossAccountId::from_eth(caller);420 let new_admin = T::CrossAccountId::from_eth(new_admin);421 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;422 Ok(())423 }424425 426 427 428 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {429 self.consume_store_writes(2)?;430431 let caller = T::CrossAccountId::from_eth(caller);432 let admin = T::CrossAccountId::from_eth(admin);433 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;434 Ok(())435 }436437 438 439 440 #[solidity(rename_selector = "setCollectionNesting")]441 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {442 self.consume_store_reads_and_writes(1, 1)?;443444 check_is_owner_or_admin(caller, self)?;445446 let mut permissions = self.collection.permissions.clone();447 let mut nesting = permissions.nesting().clone();448 nesting.token_owner = enable;449 nesting.restricted = None;450 permissions.nesting = Some(nesting);451452 self.collection.permissions = <Pallet<T>>::clamp_permissions(453 self.collection.mode.clone(),454 &self.collection.permissions,455 permissions,456 )457 .map_err(dispatch_to_evm::<T>)?;458459 save(self)460 }461462 463 464 465 466 #[solidity(rename_selector = "setCollectionNesting")]467 fn set_nesting(468 &mut self,469 caller: caller,470 enable: bool,471 collections: Vec<address>,472 ) -> Result<void> {473 self.consume_store_reads_and_writes(1, 1)?;474475 if collections.is_empty() {476 return Err("no addresses provided".into());477 }478 check_is_owner_or_admin(caller, self)?;479480 let mut permissions = self.collection.permissions.clone();481 match enable {482 false => {483 let mut nesting = permissions.nesting().clone();484 nesting.token_owner = false;485 nesting.restricted = None;486 permissions.nesting = Some(nesting);487 }488 true => {489 let mut bv = OwnerRestrictedSet::new();490 for i in collections {491 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {492 Error::Revert("Can't convert address into collection id".into())493 })?)494 .map_err(|_| "too many collections")?;495 }496 let mut nesting = permissions.nesting().clone();497 nesting.token_owner = true;498 nesting.restricted = Some(bv);499 permissions.nesting = Some(nesting);500 }501 };502503 self.collection.permissions = <Pallet<T>>::clamp_permissions(504 self.collection.mode.clone(),505 &self.collection.permissions,506 permissions,507 )508 .map_err(dispatch_to_evm::<T>)?;509510 save(self)511 }512513 514 515 516 517 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {518 self.consume_store_reads_and_writes(1, 1)?;519520 check_is_owner_or_admin(caller, self)?;521 let permissions = CollectionPermissions {522 access: Some(match mode {523 0 => AccessMode::Normal,524 1 => AccessMode::AllowList,525 _ => return Err("not supported access mode".into()),526 }),527 ..Default::default()528 };529 self.collection.permissions = <Pallet<T>>::clamp_permissions(530 self.collection.mode.clone(),531 &self.collection.permissions,532 permissions,533 )534 .map_err(dispatch_to_evm::<T>)?;535536 save(self)537 }538539 540 541 542 fn allowed(&self, user: address) -> Result<bool> {543 Ok(Pallet::<T>::allowed(544 self.id,545 T::CrossAccountId::from_eth(user),546 ))547 }548549 550 551 552 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {553 self.consume_store_writes(1)?;554555 let caller = T::CrossAccountId::from_eth(caller);556 let user = T::CrossAccountId::from_eth(user);557 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;558 Ok(())559 }560561 562 563 564 fn add_to_collection_allow_list_cross(565 &mut self,566 caller: caller,567 user: (address, uint256),568 ) -> Result<void> {569 self.consume_store_writes(1)?;570571 let caller = T::CrossAccountId::from_eth(caller);572 let user = convert_tuple_to_cross_account::<T>(user)?;573 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;574 Ok(())575 }576577 578 579 580 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {581 self.consume_store_writes(1)?;582583 let caller = T::CrossAccountId::from_eth(caller);584 let user = T::CrossAccountId::from_eth(user);585 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;586 Ok(())587 }588589 590 591 592 fn remove_from_collection_allow_list_cross(593 &mut self,594 caller: caller,595 user: (address, uint256),596 ) -> Result<void> {597 self.consume_store_writes(1)?;598599 let caller = T::CrossAccountId::from_eth(caller);600 let user = convert_tuple_to_cross_account::<T>(user)?;601 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;602 Ok(())603 }604605 606 607 608 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {609 self.consume_store_reads_and_writes(1, 1)?;610611 check_is_owner_or_admin(caller, self)?;612 let permissions = CollectionPermissions {613 mint_mode: Some(mode),614 ..Default::default()615 };616 self.collection.permissions = <Pallet<T>>::clamp_permissions(617 self.collection.mode.clone(),618 &self.collection.permissions,619 permissions,620 )621 .map_err(dispatch_to_evm::<T>)?;622623 save(self)624 }625626 627 628 629 630 #[solidity(rename_selector = "isOwnerOrAdmin")]631 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {632 let user = T::CrossAccountId::from_eth(user);633 Ok(self.is_owner_or_admin(&user))634 }635636 637 638 639 640 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {641 let user = convert_tuple_to_cross_account::<T>(user)?;642 Ok(self.is_owner_or_admin(&user))643 }644645 646 647 648 fn unique_collection_type(&self) -> Result<string> {649 let mode = match self.collection.mode {650 CollectionMode::Fungible(_) => "Fungible",651 CollectionMode::NFT => "NFT",652 CollectionMode::ReFungible => "ReFungible",653 };654 Ok(mode.into())655 }656657 658 659 660 661 fn collection_owner(&self) -> Result<(address, uint256)> {662 Ok(convert_cross_account_to_tuple::<T>(663 &T::CrossAccountId::from_sub(self.owner.clone()),664 ))665 }666667 668 669 670 671 #[solidity(rename_selector = "changeCollectionOwner")]672 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {673 self.consume_store_writes(1)?;674675 let caller = T::CrossAccountId::from_eth(caller);676 let new_owner = T::CrossAccountId::from_eth(new_owner);677 self.set_owner_internal(caller, new_owner)678 .map_err(dispatch_to_evm::<T>)679 }680681 682 683 684 685 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {686 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))687 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))688 .collect();689 Ok(result)690 }691692 693 694 695 696 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {697 self.consume_store_writes(1)?;698699 let caller = T::CrossAccountId::from_eth(caller);700 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;701 self.set_owner_internal(caller, new_owner)702 .map_err(dispatch_to_evm::<T>)703 }704}705706707708fn check_is_owner_or_admin<T: Config>(709 caller: caller,710 collection: &CollectionHandle<T>,711) -> Result<T::CrossAccountId> {712 let caller = T::CrossAccountId::from_eth(caller);713 collection714 .check_is_owner_or_admin(&caller)715 .map_err(dispatch_to_evm::<T>)?;716 Ok(caller)717}718719720721fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {722 collection723 .check_is_internal()724 .map_err(dispatch_to_evm::<T>)?;725 collection.save().map_err(dispatch_to_evm::<T>)?;726 Ok(())727}728729730pub mod static_property {731 use evm_coder::{732 execution::{Result, Error},733 };734 use alloc::format;735736 const EXPECT_CONVERT_ERROR: &str = "length < limit";737738 739 pub mod key {740 use super::*;741742 743 pub fn base_uri() -> up_data_structs::PropertyKey {744 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)745 }746747 748 pub fn url() -> up_data_structs::PropertyKey {749 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)750 }751752 753 pub fn suffix() -> up_data_structs::PropertyKey {754 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)755 }756757 758 pub fn parent_nft() -> up_data_structs::PropertyKey {759 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)760 }761 }762763 764 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {765 bytes.to_vec().try_into().map_err(|_| {766 Error::Revert(format!(767 "Property key is too long. Max length is {}.",768 up_data_structs::PropertyKey::bound()769 ))770 })771 }772773 774 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {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 }782}