12345678910111213141516171819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23 weight,24 custom_signature::{SignatureUnit, FunctionSignature, SignaturePreferences},25 make_signature,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, convert_tuple_to_cross_account},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}62636465pub trait CommonEvmHandler {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 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]83 fn set_collection_property(84 &mut self,85 caller: caller,86 key: string,87 value: bytes,88 ) -> Result<void> {89 let caller = T::CrossAccountId::from_eth(caller);90 let key = <Vec<u8>>::from(key)91 .try_into()92 .map_err(|_| "key too large")?;93 let value = value.0.try_into().map_err(|_| "value too large")?;9495 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })96 .map_err(dispatch_to_evm::<T>)97 }9899 100 101 102 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]103 fn set_collection_properties(104 &mut self,105 caller: caller,106 properties: Vec<(string, bytes)>,107 ) -> Result<void> {108 let caller = T::CrossAccountId::from_eth(caller);109110 let properties = properties111 .into_iter()112 .map(|(key, value)| {113 let key = <Vec<u8>>::from(key)114 .try_into()115 .map_err(|_| "key too large")?;116117 let value = value.0.try_into().map_err(|_| "value too large")?;118119 Ok(Property { key, value })120 })121 .collect::<Result<Vec<_>>>()?;122123 <Pallet<T>>::set_collection_properties(self, &caller, properties)124 .map_err(dispatch_to_evm::<T>)125 }126127 128 129 130 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]131 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let key = <Vec<u8>>::from(key)134 .try_into()135 .map_err(|_| "key too large")?;136137 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)138 }139140 141 142 143 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]144 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {145 let caller = T::CrossAccountId::from_eth(caller);146 let keys = keys147 .into_iter()148 .map(|key| {149 <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| Error::Revert("key too large".into()))152 })153 .collect::<Result<Vec<_>>>()?;154155 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)156 }157158 159 160 161 162 163 164 fn collection_property(&self, key: string) -> Result<bytes> {165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too large")?;168169 let props = CollectionProperties::<T>::get(self.id);170 let prop = props.get(&key).ok_or("key not found")?;171172 Ok(bytes(prop.to_vec()))173 }174175 176 177 178 179 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {180 let keys = keys181 .into_iter()182 .map(|key| {183 <Vec<u8>>::from(key)184 .try_into()185 .map_err(|_| Error::Revert("key too large".into()))186 })187 .collect::<Result<Vec<_>>>()?;188189 let properties = Pallet::<T>::filter_collection_properties(190 self.id,191 if keys.is_empty() { None } else { Some(keys) },192 )193 .map_err(dispatch_to_evm::<T>)?;194195 let properties = properties196 .into_iter()197 .map(|p| {198 let key =199 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;200 let value = bytes(p.value.to_vec());201 Ok((key, value))202 })203 .collect::<Result<Vec<_>>>()?;204 Ok(properties)205 }206207 208 209 210 211 212 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {213 self.consume_store_reads_and_writes(1, 1)?;214215 check_is_owner_or_admin(caller, self)?;216217 let sponsor = T::CrossAccountId::from_eth(sponsor);218 self.set_sponsor(sponsor.as_sub().clone())219 .map_err(dispatch_to_evm::<T>)?;220 save(self)221 }222223 224 225 226 227 228 fn set_collection_sponsor_cross(229 &mut self,230 caller: caller,231 sponsor: EthCrossAccount,232 ) -> Result<void> {233 self.consume_store_reads_and_writes(1, 1)?;234235 check_is_owner_or_admin(caller, self)?;236237 let sponsor = sponsor.into_sub_cross_account::<T>()?;238 self.set_sponsor(sponsor.as_sub().clone())239 .map_err(dispatch_to_evm::<T>)?;240 save(self)241 }242243 244 fn has_collection_pending_sponsor(&self) -> Result<bool> {245 Ok(matches!(246 self.collection.sponsorship,247 SponsorshipState::Unconfirmed(_)248 ))249 }250251 252 253 254 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {255 self.consume_store_writes(1)?;256257 let caller = T::CrossAccountId::from_eth(caller);258 if !self259 .confirm_sponsorship(caller.as_sub())260 .map_err(dispatch_to_evm::<T>)?261 {262 return Err("caller is not set as sponsor".into());263 }264 save(self)265 }266267 268 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {269 self.consume_store_reads_and_writes(1, 1)?;270 check_is_owner_or_admin(caller, self)?;271 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;272 save(self)273 }274275 276 277 278 fn collection_sponsor(&self) -> Result<(address, uint256)> {279 let sponsor = match self.collection.sponsorship.sponsor() {280 Some(sponsor) => sponsor,281 None => return Ok(Default::default()),282 };283 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());284 let result: (address, uint256) = if sponsor.is_canonical_substrate() {285 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);286 (Default::default(), sponsor)287 } else {288 let sponsor = *sponsor.as_eth();289 (sponsor, Default::default())290 };291 Ok(result)292 }293294 295 296 297 298 299 300 301 302 303 304 #[solidity(rename_selector = "setCollectionLimit")]305 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {306 self.consume_store_reads_and_writes(1, 1)?;307308 check_is_owner_or_admin(caller, self)?;309 let mut limits = self.limits.clone();310311 match limit.as_str() {312 "accountTokenOwnershipLimit" => {313 limits.account_token_ownership_limit = Some(value);314 }315 "sponsoredDataSize" => {316 limits.sponsored_data_size = Some(value);317 }318 "sponsoredDataRateLimit" => {319 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));320 }321 "tokenLimit" => {322 limits.token_limit = Some(value);323 }324 "sponsorTransferTimeout" => {325 limits.sponsor_transfer_timeout = Some(value);326 }327 "sponsorApproveTimeout" => {328 limits.sponsor_approve_timeout = Some(value);329 }330 _ => {331 return Err(Error::Revert(format!(332 "unknown integer limit \"{}\"",333 limit334 )))335 }336 }337 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)338 .map_err(dispatch_to_evm::<T>)?;339 save(self)340 }341342 343 344 345 346 347 348 349 #[solidity(rename_selector = "setCollectionLimit")]350 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {351 self.consume_store_reads_and_writes(1, 1)?;352353 check_is_owner_or_admin(caller, self)?;354 let mut limits = self.limits.clone();355356 match limit.as_str() {357 "ownerCanTransfer" => {358 limits.owner_can_transfer = Some(value);359 }360 "ownerCanDestroy" => {361 limits.owner_can_destroy = Some(value);362 }363 "transfersEnabled" => {364 limits.transfers_enabled = Some(value);365 }366 _ => {367 return Err(Error::Revert(format!(368 "unknown boolean limit \"{}\"",369 limit370 )))371 }372 }373 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)374 .map_err(dispatch_to_evm::<T>)?;375 save(self)376 }377378 379 fn contract_address(&self) -> Result<address> {380 Ok(crate::eth::collection_id_to_address(self.id))381 }382383 384 385 fn add_collection_admin_cross(386 &mut self,387 caller: caller,388 new_admin: EthCrossAccount,389 ) -> Result<void> {390 self.consume_store_writes(2)?;391392 let caller = T::CrossAccountId::from_eth(caller);393 let new_admin = new_admin.into_sub_cross_account::<T>()?;394 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;395 Ok(())396 }397398 399 400 fn remove_collection_admin_cross(401 &mut self,402 caller: caller,403 admin: EthCrossAccount,404 ) -> Result<void> {405 self.consume_store_writes(2)?;406407 let caller = T::CrossAccountId::from_eth(caller);408 let admin = admin.into_sub_cross_account::<T>()?;409 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;410 Ok(())411 }412413 414 415 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {416 self.consume_store_writes(2)?;417418 let caller = T::CrossAccountId::from_eth(caller);419 let new_admin = T::CrossAccountId::from_eth(new_admin);420 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;421 Ok(())422 }423424 425 426 427 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {428 self.consume_store_writes(2)?;429430 let caller = T::CrossAccountId::from_eth(caller);431 let admin = T::CrossAccountId::from_eth(admin);432 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;433 Ok(())434 }435436 437 438 439 #[solidity(rename_selector = "setCollectionNesting")]440 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {441 self.consume_store_reads_and_writes(1, 1)?;442443 check_is_owner_or_admin(caller, self)?;444445 let mut permissions = self.collection.permissions.clone();446 let mut nesting = permissions.nesting().clone();447 nesting.token_owner = enable;448 nesting.restricted = None;449 permissions.nesting = Some(nesting);450451 self.collection.permissions = <Pallet<T>>::clamp_permissions(452 self.collection.mode.clone(),453 &self.collection.permissions,454 permissions,455 )456 .map_err(dispatch_to_evm::<T>)?;457458 save(self)459 }460461 462 463 464 465 #[solidity(rename_selector = "setCollectionNesting")]466 fn set_nesting(467 &mut self,468 caller: caller,469 enable: bool,470 collections: Vec<address>,471 ) -> Result<void> {472 self.consume_store_reads_and_writes(1, 1)?;473474 if collections.is_empty() {475 return Err("no addresses provided".into());476 }477 check_is_owner_or_admin(caller, self)?;478479 let mut permissions = self.collection.permissions.clone();480 match enable {481 false => {482 let mut nesting = permissions.nesting().clone();483 nesting.token_owner = false;484 nesting.restricted = None;485 permissions.nesting = Some(nesting);486 }487 true => {488 let mut bv = OwnerRestrictedSet::new();489 for i in collections {490 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {491 Error::Revert("Can't convert address into collection id".into())492 })?)493 .map_err(|_| "too many collections")?;494 }495 let mut nesting = permissions.nesting().clone();496 nesting.token_owner = true;497 nesting.restricted = Some(bv);498 permissions.nesting = Some(nesting);499 }500 };501502 self.collection.permissions = <Pallet<T>>::clamp_permissions(503 self.collection.mode.clone(),504 &self.collection.permissions,505 permissions,506 )507 .map_err(dispatch_to_evm::<T>)?;508509 save(self)510 }511512 513 514 515 516 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {517 self.consume_store_reads_and_writes(1, 1)?;518519 check_is_owner_or_admin(caller, self)?;520 let permissions = CollectionPermissions {521 access: Some(match mode {522 0 => AccessMode::Normal,523 1 => AccessMode::AllowList,524 _ => return Err("not supported access mode".into()),525 }),526 ..Default::default()527 };528 self.collection.permissions = <Pallet<T>>::clamp_permissions(529 self.collection.mode.clone(),530 &self.collection.permissions,531 permissions,532 )533 .map_err(dispatch_to_evm::<T>)?;534535 save(self)536 }537538 539 540 541 fn allowed(&self, user: address) -> Result<bool> {542 Ok(Pallet::<T>::allowed(543 self.id,544 T::CrossAccountId::from_eth(user),545 ))546 }547548 549 550 551 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {552 self.consume_store_writes(1)?;553554 let caller = T::CrossAccountId::from_eth(caller);555 let user = T::CrossAccountId::from_eth(user);556 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;557 Ok(())558 }559560 561 562 563 fn add_to_collection_allow_list_cross(564 &mut self,565 caller: caller,566 user: EthCrossAccount,567 ) -> Result<void> {568 self.consume_store_writes(1)?;569570 let caller = T::CrossAccountId::from_eth(caller);571 let user = user.into_sub_cross_account::<T>()?;572 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;573 Ok(())574 }575576 577 578 579 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {580 self.consume_store_writes(1)?;581582 let caller = T::CrossAccountId::from_eth(caller);583 let user = T::CrossAccountId::from_eth(user);584 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;585 Ok(())586 }587588 589 590 591 fn remove_from_collection_allow_list_cross(592 &mut self,593 caller: caller,594 user: EthCrossAccount,595 ) -> Result<void> {596 self.consume_store_writes(1)?;597598 let caller = T::CrossAccountId::from_eth(caller);599 let user = user.into_sub_cross_account::<T>()?;600 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;601 Ok(())602 }603604 605 606 607 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {608 self.consume_store_reads_and_writes(1, 1)?;609610 check_is_owner_or_admin(caller, self)?;611 let permissions = CollectionPermissions {612 mint_mode: Some(mode),613 ..Default::default()614 };615 self.collection.permissions = <Pallet<T>>::clamp_permissions(616 self.collection.mode.clone(),617 &self.collection.permissions,618 permissions,619 )620 .map_err(dispatch_to_evm::<T>)?;621622 save(self)623 }624625 626 627 628 629 #[solidity(rename_selector = "isOwnerOrAdmin")]630 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {631 let user = T::CrossAccountId::from_eth(user);632 Ok(self.is_owner_or_admin(&user))633 }634635 636 637 638 639 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {640 let user = user.into_sub_cross_account::<T>()?;641 Ok(self.is_owner_or_admin(&user))642 }643644 645 646 647 fn unique_collection_type(&self) -> Result<string> {648 let mode = match self.collection.mode {649 CollectionMode::Fungible(_) => "Fungible",650 CollectionMode::NFT => "NFT",651 CollectionMode::ReFungible => "ReFungible",652 };653 Ok(mode.into())654 }655656 657 658 659 660 fn collection_owner(&self) -> Result<EthCrossAccount> {661 Ok(EthCrossAccount::from_sub_cross_account::<T>(662 &T::CrossAccountId::from_sub(self.owner.clone()),663 ))664 }665666 667 668 669 670 #[solidity(rename_selector = "changeCollectionOwner")]671 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {672 self.consume_store_writes(1)?;673674 let caller = T::CrossAccountId::from_eth(caller);675 let new_owner = T::CrossAccountId::from_eth(new_owner);676 self.set_owner_internal(caller, new_owner)677 .map_err(dispatch_to_evm::<T>)678 }679680 681 682 683 684 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {685 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))686 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))687 .collect();688 Ok(result)689 }690691 692 693 694 695 fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {696 self.consume_store_writes(1)?;697698 let caller = T::CrossAccountId::from_eth(caller);699 let new_owner = new_owner.into_sub_cross_account::<T>()?;700 self.set_owner_internal(caller, new_owner)701 .map_err(dispatch_to_evm::<T>)702 }703}704705706707fn check_is_owner_or_admin<T: Config>(708 caller: caller,709 collection: &CollectionHandle<T>,710) -> Result<T::CrossAccountId> {711 let caller = T::CrossAccountId::from_eth(caller);712 collection713 .check_is_owner_or_admin(&caller)714 .map_err(dispatch_to_evm::<T>)?;715 Ok(caller)716}717718719720fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {721 collection722 .check_is_internal()723 .map_err(dispatch_to_evm::<T>)?;724 collection.save().map_err(dispatch_to_evm::<T>)?;725 Ok(())726}727728729pub mod static_property {730 use evm_coder::{731 execution::{Result, Error},732 };733 use alloc::format;734735 const EXPECT_CONVERT_ERROR: &str = "length < limit";736737 738 pub mod key {739 use super::*;740741 742 pub fn base_uri() -> up_data_structs::PropertyKey {743 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)744 }745746 747 pub fn url() -> up_data_structs::PropertyKey {748 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)749 }750751 752 pub fn suffix() -> up_data_structs::PropertyKey {753 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)754 }755756 757 pub fn parent_nft() -> up_data_structs::PropertyKey {758 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)759 }760 }761762 763 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {764 bytes.to_vec().try_into().map_err(|_| {765 Error::Revert(format!(766 "Property key is too long. Max length is {}.",767 up_data_structs::PropertyKey::bound()768 ))769 })770 }771772 773 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {774 bytes.to_vec().try_into().map_err(|_| {775 Error::Revert(format!(776 "Property key is too long. Max length is {}.",777 up_data_structs::PropertyKey::bound()778 ))779 })780 }781}