12345678910111213141516171819pub use pallet_evm::{20 account::CrossAccountId, PrecompileHandle, PrecompileOutput, PrecompileResult,21};22use pallet_evm_coder_substrate::{23 abi::AbiType,24 dispatch_to_evm,25 execution::{Error, PreDispatch, Result},26 frontier_contract, solidity_interface,27 types::*,28 ToLog,29};30use sp_std::{vec, vec::Vec};31use up_data_structs::{32 CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,33 SponsorshipState,34};3536use crate::{37 eth, weights::WeightInfo, CollectionHandle, CollectionProperties, Config, Pallet, SelfWeightOf,38};3940frontier_contract! {41 macro_rules! CollectionHandle_result {...}42 impl<T: Config> Contract for CollectionHandle<T> {...}43}444546#[derive(ToLog)]47pub enum CollectionHelpersEvents {48 49 CollectionCreated {50 51 #[indexed]52 owner: Address,5354 55 #[indexed]56 collection_id: Address,57 },58 59 CollectionDestroyed {60 61 #[indexed]62 collection_id: Address,63 },64 65 CollectionChanged {66 67 #[indexed]68 collection_id: Address,69 },70}71727374pub trait CommonEvmHandler {75 76 const CODE: &'static [u8];7778 79 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;80}818283#[solidity_interface(name = Collection, enum(derive(PreDispatch)), enum_attr(weight))]84impl<T: Config> CollectionHandle<T>85where86 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,87{88 89 90 91 92 #[solidity(hide)]93 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]94 fn set_collection_property(&mut self, caller: Caller, key: String, value: Bytes) -> Result<()> {95 let caller = T::CrossAccountId::from_eth(caller);96 let key = <Vec<u8>>::from(key)97 .try_into()98 .map_err(|_| "key too large")?;99 let value = value.0.try_into().map_err(|_| "value too large")?;100101 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })102 .map_err(dispatch_to_evm::<T>)103 }104105 106 107 108 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]109 fn set_collection_properties(110 &mut self,111 caller: Caller,112 properties: Vec<eth::Property>,113 ) -> Result<()> {114 let caller = T::CrossAccountId::from_eth(caller);115116 let properties = properties117 .into_iter()118 .map(eth::Property::try_into)119 .collect::<Result<Vec<_>>>()?;120121 <Pallet<T>>::set_collection_properties(self, &caller, properties.into_iter())122 .map_err(dispatch_to_evm::<T>)123 }124125 126 127 128 #[solidity(hide)]129 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]130 fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {131 let caller = T::CrossAccountId::from_eth(caller);132 let key = <Vec<u8>>::from(key)133 .try_into()134 .map_err(|_| "key too large")?;135136 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)137 }138139 140 141 142 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]143 fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {144 let caller = T::CrossAccountId::from_eth(caller);145 let keys = keys146 .into_iter()147 .map(|key| {148 <Vec<u8>>::from(key)149 .try_into()150 .map_err(|_| Error::Revert("key too large".into()))151 })152 .collect::<Result<Vec<_>>>()?;153154 <Pallet<T>>::delete_collection_properties(self, &caller, keys.into_iter())155 .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<eth::Property>> {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(Property::try_into)198 .collect::<Result<Vec<_>>>()?;199 Ok(properties)200 }201202 203 204 205 206 207 #[solidity(hide)]208 fn set_collection_sponsor(&mut self, caller: Caller, sponsor: Address) -> Result<()> {209 self.consume_store_reads_and_writes(1, 1)?;210211 let caller = T::CrossAccountId::from_eth(caller);212213 let sponsor = T::CrossAccountId::from_eth(sponsor);214 self.set_sponsor(&caller, sponsor.as_sub().clone())215 .map_err(dispatch_to_evm::<T>)216 }217218 219 220 221 222 223 fn set_collection_sponsor_cross(224 &mut self,225 caller: Caller,226 sponsor: eth::CrossAddress,227 ) -> Result<()> {228 self.consume_store_reads_and_writes(1, 1)?;229230 let caller = T::CrossAccountId::from_eth(caller);231232 let sponsor = sponsor.into_sub_cross_account::<T>()?;233 self.set_sponsor(&caller, sponsor.as_sub().clone())234 .map_err(dispatch_to_evm::<T>)235 }236237 238 fn has_collection_pending_sponsor(&self) -> Result<bool> {239 Ok(matches!(240 self.collection.sponsorship,241 SponsorshipState::Unconfirmed(_)242 ))243 }244245 246 247 248 fn confirm_collection_sponsorship(&mut self, caller: Caller) -> Result<()> {249 self.consume_store_writes(1)?;250251 let caller = T::CrossAccountId::from_eth(caller);252 self.confirm_sponsorship(caller.as_sub())253 .map_err(dispatch_to_evm::<T>)254 }255256 257 fn remove_collection_sponsor(&mut self, caller: Caller) -> Result<()> {258 self.consume_store_reads_and_writes(1, 1)?;259 let caller = T::CrossAccountId::from_eth(caller);260 self.remove_sponsor(&caller).map_err(dispatch_to_evm::<T>)261 }262263 264 265 266 fn collection_sponsor(&self) -> Result<eth::CrossAddress> {267 let sponsor = match self.collection.sponsorship.sponsor() {268 Some(sponsor) => sponsor,269 None => return Ok(Default::default()),270 };271272 Ok(eth::CrossAddress::from_sub::<T>(sponsor))273 }274275 276 277 278 fn collection_limits(&self) -> Result<Vec<eth::CollectionLimit>> {279 let limits = &self.collection.limits;280281 Ok(vec![282 eth::CollectionLimit::new(283 eth::CollectionLimitField::AccountTokenOwnership,284 limits.account_token_ownership_limit,285 ),286 eth::CollectionLimit::new(287 eth::CollectionLimitField::SponsoredDataSize,288 limits.sponsored_data_size,289 ),290 limits291 .sponsored_data_rate_limit292 .and_then(|limit| {293 if let SponsoringRateLimit::Blocks(blocks) = limit {294 Some(eth::CollectionLimit::new(295 eth::CollectionLimitField::SponsoredDataRateLimit,296 Some(blocks),297 ))298 } else {299 None300 }301 })302 .unwrap_or_else(|| {303 eth::CollectionLimit::new(304 eth::CollectionLimitField::SponsoredDataRateLimit,305 Default::default(),306 )307 }),308 eth::CollectionLimit::new(eth::CollectionLimitField::TokenLimit, limits.token_limit),309 eth::CollectionLimit::new(310 eth::CollectionLimitField::SponsorTransferTimeout,311 limits.sponsor_transfer_timeout,312 ),313 eth::CollectionLimit::new(314 eth::CollectionLimitField::SponsorApproveTimeout,315 limits.sponsor_approve_timeout,316 ),317 eth::CollectionLimit::new(318 eth::CollectionLimitField::OwnerCanTransfer,319 limits.owner_can_transfer.map(u32::from),320 ),321 eth::CollectionLimit::new(322 eth::CollectionLimitField::OwnerCanDestroy,323 limits.owner_can_destroy.map(u32::from),324 ),325 eth::CollectionLimit::new(326 eth::CollectionLimitField::TransferEnabled,327 limits.transfers_enabled.map(u32::from),328 ),329 ])330 }331332 333 334 335 #[solidity(rename_selector = "setCollectionLimit")]336 fn set_collection_limit(&mut self, caller: Caller, limit: eth::CollectionLimit) -> Result<()> {337 self.consume_store_reads_and_writes(1, 1)?;338339 if !limit.has_value() {340 return Err(Error::Revert("user can't disable limits".into()));341 }342343 let caller = T::CrossAccountId::from_eth(caller);344 <Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)345 }346347 348 fn contract_address(&self) -> Result<Address> {349 Ok(crate::eth::collection_id_to_address(self.id))350 }351352 353 354 fn add_collection_admin_cross(355 &mut self,356 caller: Caller,357 new_admin: eth::CrossAddress,358 ) -> Result<()> {359 self.consume_store_reads_and_writes(2, 2)?;360361 let caller = T::CrossAccountId::from_eth(caller);362 let new_admin = new_admin.into_sub_cross_account::<T>()?;363 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;364 Ok(())365 }366367 368 369 fn remove_collection_admin_cross(370 &mut self,371 caller: Caller,372 admin: eth::CrossAddress,373 ) -> Result<()> {374 self.consume_store_reads_and_writes(2, 2)?;375376 let caller = T::CrossAccountId::from_eth(caller);377 let admin = admin.into_sub_cross_account::<T>()?;378 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;379 Ok(())380 }381382 383 384 #[solidity(hide)]385 fn add_collection_admin(&mut self, caller: Caller, new_admin: Address) -> Result<()> {386 self.consume_store_reads_and_writes(2, 2)?;387388 let caller = T::CrossAccountId::from_eth(caller);389 let new_admin = T::CrossAccountId::from_eth(new_admin);390 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;391 Ok(())392 }393394 395 396 397 #[solidity(hide)]398 fn remove_collection_admin(&mut self, caller: Caller, admin: Address) -> Result<()> {399 self.consume_store_reads_and_writes(2, 2)?;400401 let caller = T::CrossAccountId::from_eth(caller);402 let admin = T::CrossAccountId::from_eth(admin);403 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;404 Ok(())405 }406407 #[solidity(rename_selector = "setCollectionNesting")]408 fn set_nesting(409 &mut self,410 caller: Caller,411 collection_nesting_and_permissions: eth::CollectionNestingAndPermission,412 ) -> Result<()> {413 self.consume_store_reads_and_writes(1, 1)?;414415 let caller = T::CrossAccountId::from_eth(caller);416417 let mut permissions = self.collection.permissions.clone();418 let mut nesting = permissions.nesting().clone();419420 let bv = if !collection_nesting_and_permissions.restricted.is_empty() {421 let mut bv = OwnerRestrictedSet::new();422 for address in collection_nesting_and_permissions.restricted.iter() {423 bv.try_insert(crate::eth::map_eth_to_id(address).ok_or_else(|| {424 Error::Revert("Can't convert address into collection id".into())425 })?)426 .map_err(|_| "too many collections")?;427 }428 Some(bv)429 } else {430 None431 };432433 nesting.token_owner = collection_nesting_and_permissions.token_owner;434 nesting.collection_admin = collection_nesting_and_permissions.collection_admin;435 nesting.restricted = bv;436 permissions.nesting = Some(nesting);437438 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)439 }440441 442 443 444 #[solidity(hide, rename_selector = "setCollectionNesting")]445 fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> {446 self.consume_store_reads_and_writes(1, 1)?;447448 let caller = T::CrossAccountId::from_eth(caller);449450 let mut permissions = self.collection.permissions.clone();451 let mut nesting = permissions.nesting().clone();452 nesting.token_owner = enable;453 nesting.restricted = None;454 permissions.nesting = Some(nesting);455456 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)457 }458459 460 461 462 463 #[solidity(hide, rename_selector = "setCollectionNesting")]464 fn set_nesting_collection_ids(465 &mut self,466 caller: Caller,467 enable: bool,468 collections: Vec<Address>,469 ) -> Result<()> {470 self.consume_store_reads_and_writes(1, 1)?;471472 if collections.is_empty() {473 return Err("no addresses provided".into());474 }475 let caller = T::CrossAccountId::from_eth(caller);476477 let mut permissions = self.collection.permissions.clone();478 match enable {479 false => {480 let mut nesting = permissions.nesting().clone();481 nesting.token_owner = false;482 nesting.restricted = None;483 permissions.nesting = Some(nesting);484 }485 true => {486 let mut bv = OwnerRestrictedSet::new();487 for i in collections {488 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {489 Error::Revert("Can't convert address into collection id".into())490 })?)491 .map_err(|_| "too many collections")?;492 }493 let mut nesting = permissions.nesting().clone();494 nesting.token_owner = true;495 nesting.restricted = Some(bv);496 permissions.nesting = Some(nesting);497 }498 };499500 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)501 }502503 #[solidity(rename_selector = "collectionNesting")]504 fn collection_nesting(&self) -> Result<eth::CollectionNestingAndPermission> {505 let nesting = self.collection.permissions.nesting();506507 Ok(eth::CollectionNestingAndPermission::new(508 nesting.token_owner,509 nesting.collection_admin,510 nesting511 .restricted512 .clone()513 .map(|b| {514 b.0.into_inner()515 .iter()516 .map(|id| crate::eth::collection_id_to_address(id.0.into()))517 .collect()518 })519 .unwrap_or_default(),520 ))521 }522523 524 #[solidity(hide, rename_selector = "collectionNestingRestrictedCollectionIds")]525 fn collection_nesting_restricted_ids(&self) -> Result<eth::CollectionNesting> {526 let nesting = self.collection.permissions.nesting();527528 Ok(eth::CollectionNesting::new(529 nesting.token_owner,530 nesting531 .restricted532 .clone()533 .map(|b| b.0.into_inner().iter().map(|id| id.0.into()).collect())534 .unwrap_or_default(),535 ))536 }537538 539 #[solidity(hide)]540 fn collection_nesting_permissions(&self) -> Result<Vec<eth::CollectionNestingPermission>> {541 let nesting = self.collection.permissions.nesting();542 Ok(vec![543 eth::CollectionNestingPermission::new(544 eth::CollectionPermissionField::CollectionAdmin,545 nesting.collection_admin,546 ),547 eth::CollectionNestingPermission::new(548 eth::CollectionPermissionField::TokenOwner,549 nesting.token_owner,550 ),551 ])552 }553 554 555 fn set_collection_access(&mut self, caller: Caller, mode: eth::AccessMode) -> Result<()> {556 self.consume_store_reads_and_writes(1, 1)?;557558 let caller = T::CrossAccountId::from_eth(caller);559 let permissions = CollectionPermissions {560 access: Some(mode.into()),561 ..Default::default()562 };563 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)564 }565566 567 568 569 fn allowlisted_cross(&self, user: eth::CrossAddress) -> Result<bool> {570 let user = user.into_sub_cross_account::<T>()?;571 Ok(Pallet::<T>::allowed(self.id, user))572 }573574 575 576 577 #[solidity(hide)]578 fn add_to_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {579 self.consume_store_writes(1)?;580581 let caller = T::CrossAccountId::from_eth(caller);582 let user = T::CrossAccountId::from_eth(user);583 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;584 Ok(())585 }586587 588 589 590 fn add_to_collection_allow_list_cross(591 &mut self,592 caller: Caller,593 user: eth::CrossAddress,594 ) -> Result<()> {595 self.consume_store_writes(1)?;596597 let caller = T::CrossAccountId::from_eth(caller);598 let user = user.into_sub_cross_account::<T>()?;599 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;600 Ok(())601 }602603 604 605 606 #[solidity(hide)]607 fn remove_from_collection_allow_list(&mut self, caller: Caller, user: Address) -> Result<()> {608 self.consume_store_writes(1)?;609610 let caller = T::CrossAccountId::from_eth(caller);611 let user = T::CrossAccountId::from_eth(user);612 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;613 Ok(())614 }615616 617 618 619 fn remove_from_collection_allow_list_cross(620 &mut self,621 caller: Caller,622 user: eth::CrossAddress,623 ) -> Result<()> {624 self.consume_store_writes(1)?;625626 let caller = T::CrossAccountId::from_eth(caller);627 let user = user.into_sub_cross_account::<T>()?;628 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;629 Ok(())630 }631632 633 634 635 fn set_collection_mint_mode(&mut self, caller: Caller, mode: bool) -> Result<()> {636 self.consume_store_reads_and_writes(1, 1)?;637638 let caller = T::CrossAccountId::from_eth(caller);639 let permissions = CollectionPermissions {640 mint_mode: Some(mode),641 ..Default::default()642 };643 <Pallet<T>>::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::<T>)644 }645646 647 648 649 650 #[solidity(hide, rename_selector = "isOwnerOrAdmin")]651 fn is_owner_or_admin_eth(&self, user: Address) -> Result<bool> {652 let user = T::CrossAccountId::from_eth(user);653 Ok(self.is_owner_or_admin(&user))654 }655656 657 658 659 660 fn is_owner_or_admin_cross(&self, user: eth::CrossAddress) -> Result<bool> {661 let user = user.into_sub_cross_account::<T>()?;662 Ok(self.is_owner_or_admin(&user))663 }664665 666 667 668 fn unique_collection_type(&self) -> Result<String> {669 let mode = match self.collection.mode {670 CollectionMode::Fungible(_) => "Fungible",671 CollectionMode::NFT => "NFT",672 CollectionMode::ReFungible => "ReFungible",673 };674 Ok(mode.into())675 }676677 678 679 680 681 fn collection_owner(&self) -> Result<eth::CrossAddress> {682 Ok(eth::CrossAddress::from_sub_cross_account::<T>(683 &T::CrossAccountId::from_sub(self.owner.clone()),684 ))685 }686687 688 689 690 691 #[solidity(hide, rename_selector = "changeCollectionOwner")]692 fn set_owner(&mut self, caller: Caller, new_owner: Address) -> Result<()> {693 self.consume_store_writes(1)?;694695 let caller = T::CrossAccountId::from_eth(caller);696 let new_owner = T::CrossAccountId::from_eth(new_owner);697 self.change_owner(caller, new_owner)698 .map_err(dispatch_to_evm::<T>)699 }700701 702 703 704 705 fn collection_admins(&self) -> Result<Vec<eth::CrossAddress>> {706 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))707 .map(|(admin, _)| eth::CrossAddress::from_sub_cross_account::<T>(&admin))708 .collect();709 Ok(result)710 }711712 713 714 715 716 fn change_collection_owner_cross(717 &mut self,718 caller: Caller,719 new_owner: eth::CrossAddress,720 ) -> Result<()> {721 self.consume_store_writes(1)?;722723 let caller = T::CrossAccountId::from_eth(caller);724 let new_owner = new_owner.into_sub_cross_account::<T>()?;725 self.change_owner(caller, new_owner)726 .map_err(dispatch_to_evm::<T>)727 }728}729730731pub mod static_property {732 use alloc::format;733734 use pallet_evm_coder_substrate::execution::{Error, Result};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}