12345678910111213141516171819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23 weight,24 custom_signature::{FunctionName, FunctionSignature},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::{39 convert_cross_account_to_uint256, convert_cross_account_to_tuple,40 convert_tuple_to_cross_account,41 },42 weights::WeightInfo,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}65666768pub trait CommonEvmHandler {69 const CODE: &'static [u8];7071 72 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;73}747576#[solidity_interface(name = Collection)]77impl<T: Config> CollectionHandle<T>78where79 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,80{81 82 83 84 85 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]86 fn set_collection_property(87 &mut self,88 caller: caller,89 key: string,90 value: bytes,91 ) -> Result<void> {92 let caller = T::CrossAccountId::from_eth(caller);93 let key = <Vec<u8>>::from(key)94 .try_into()95 .map_err(|_| "key too large")?;96 let value = value.0.try_into().map_err(|_| "value too large")?;9798 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })99 .map_err(dispatch_to_evm::<T>)100 }101102 103 104 105 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]106 fn set_collection_properties(107 &mut self,108 caller: caller,109 properties: Vec<(string, bytes)>,110 ) -> Result<void> {111 let caller = T::CrossAccountId::from_eth(caller);112113 let properties = properties114 .into_iter()115 .map(|(key, value)| {116 let key = <Vec<u8>>::from(key)117 .try_into()118 .map_err(|_| "key too large")?;119120 let value = value.0.try_into().map_err(|_| "value too large")?;121122 Ok(Property { key, value })123 })124 .collect::<Result<Vec<_>>>()?;125126 <Pallet<T>>::set_collection_properties(self, &caller, properties)127 .map_err(dispatch_to_evm::<T>)128 }129130 131 132 133 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]134 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let key = <Vec<u8>>::from(key)137 .try_into()138 .map_err(|_| "key too large")?;139140 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)141 }142143 144 145 146 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]147 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {148 let caller = T::CrossAccountId::from_eth(caller);149 let keys = keys150 .into_iter()151 .map(|key| {152 <Vec<u8>>::from(key)153 .try_into()154 .map_err(|_| Error::Revert("key too large".into()))155 })156 .collect::<Result<Vec<_>>>()?;157158 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)159 }160161 162 163 164 165 166 167 fn collection_property(&self, key: string) -> Result<bytes> {168 let key = <Vec<u8>>::from(key)169 .try_into()170 .map_err(|_| "key too large")?;171172 let props = CollectionProperties::<T>::get(self.id);173 let prop = props.get(&key).ok_or("key not found")?;174175 Ok(bytes(prop.to_vec()))176 }177178 179 180 181 182 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {183 let keys = keys184 .into_iter()185 .map(|key| {186 <Vec<u8>>::from(key)187 .try_into()188 .map_err(|_| Error::Revert("key too large".into()))189 })190 .collect::<Result<Vec<_>>>()?;191192 let properties = Pallet::<T>::filter_collection_properties(193 self.id,194 if keys.is_empty() { None } else { Some(keys) },195 )196 .map_err(dispatch_to_evm::<T>)?;197198 let properties = properties199 .into_iter()200 .map(|p| {201 let key =202 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;203 let value = bytes(p.value.to_vec());204 Ok((key, value))205 })206 .collect::<Result<Vec<_>>>()?;207 Ok(properties)208 }209210 211 212 213 214 215 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {216 self.consume_store_reads_and_writes(1, 1)?;217218 check_is_owner_or_admin(caller, self)?;219220 let sponsor = T::CrossAccountId::from_eth(sponsor);221 self.set_sponsor(sponsor.as_sub().clone())222 .map_err(dispatch_to_evm::<T>)?;223 save(self)224 }225226 227 228 229 230 231 fn set_collection_sponsor_cross(232 &mut self,233 caller: caller,234 sponsor: (address, uint256),235 ) -> Result<void> {236 self.consume_store_reads_and_writes(1, 1)?;237238 check_is_owner_or_admin(caller, self)?;239240 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;241 self.set_sponsor(sponsor.as_sub().clone())242 .map_err(dispatch_to_evm::<T>)?;243 save(self)244 }245246 247 fn has_collection_pending_sponsor(&self) -> Result<bool> {248 Ok(matches!(249 self.collection.sponsorship,250 SponsorshipState::Unconfirmed(_)251 ))252 }253254 255 256 257 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {258 self.consume_store_writes(1)?;259260 let caller = T::CrossAccountId::from_eth(caller);261 if !self262 .confirm_sponsorship(caller.as_sub())263 .map_err(dispatch_to_evm::<T>)?264 {265 return Err("caller is not set as sponsor".into());266 }267 save(self)268 }269270 271 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {272 self.consume_store_reads_and_writes(1, 1)?;273 check_is_owner_or_admin(caller, self)?;274 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;275 save(self)276 }277278 279 280 281 fn collection_sponsor(&self) -> Result<(address, uint256)> {282 let sponsor = match self.collection.sponsorship.sponsor() {283 Some(sponsor) => sponsor,284 None => return Ok(Default::default()),285 };286 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());287 let result: (address, uint256) = if sponsor.is_canonical_substrate() {288 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);289 (Default::default(), sponsor)290 } else {291 let sponsor = *sponsor.as_eth();292 (sponsor, Default::default())293 };294 Ok(result)295 }296297 298 299 300 301 302 303 304 305 306 307 #[solidity(rename_selector = "setCollectionLimit")]308 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {309 self.consume_store_reads_and_writes(1, 1)?;310311 check_is_owner_or_admin(caller, self)?;312 let mut limits = self.limits.clone();313314 match limit.as_str() {315 "accountTokenOwnershipLimit" => {316 limits.account_token_ownership_limit = Some(value);317 }318 "sponsoredDataSize" => {319 limits.sponsored_data_size = Some(value);320 }321 "sponsoredDataRateLimit" => {322 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));323 }324 "tokenLimit" => {325 limits.token_limit = Some(value);326 }327 "sponsorTransferTimeout" => {328 limits.sponsor_transfer_timeout = Some(value);329 }330 "sponsorApproveTimeout" => {331 limits.sponsor_approve_timeout = Some(value);332 }333 _ => {334 return Err(Error::Revert(format!(335 "unknown integer limit \"{}\"",336 limit337 )))338 }339 }340 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)341 .map_err(dispatch_to_evm::<T>)?;342 save(self)343 }344345 346 347 348 349 350 351 352 #[solidity(rename_selector = "setCollectionLimit")]353 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {354 self.consume_store_reads_and_writes(1, 1)?;355356 check_is_owner_or_admin(caller, self)?;357 let mut limits = self.limits.clone();358359 match limit.as_str() {360 "ownerCanTransfer" => {361 limits.owner_can_transfer = Some(value);362 }363 "ownerCanDestroy" => {364 limits.owner_can_destroy = Some(value);365 }366 "transfersEnabled" => {367 limits.transfers_enabled = Some(value);368 }369 _ => {370 return Err(Error::Revert(format!(371 "unknown boolean limit \"{}\"",372 limit373 )))374 }375 }376 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)377 .map_err(dispatch_to_evm::<T>)?;378 save(self)379 }380381 382 fn contract_address(&self) -> Result<address> {383 Ok(crate::eth::collection_id_to_address(self.id))384 }385386 387 388 fn add_collection_admin_cross(389 &mut self,390 caller: caller,391 new_admin: (address, uint256),392 ) -> Result<void> {393 self.consume_store_writes(2)?;394395 let caller = T::CrossAccountId::from_eth(caller);396 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;397 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;398 Ok(())399 }400401 402 403 fn remove_collection_admin_cross(404 &mut self,405 caller: caller,406 admin: (address, uint256),407 ) -> Result<void> {408 self.consume_store_writes(2)?;409410 let caller = T::CrossAccountId::from_eth(caller);411 let admin = convert_tuple_to_cross_account::<T>(admin)?;412 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;413 Ok(())414 }415416 417 418 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {419 self.consume_store_writes(2)?;420421 let caller = T::CrossAccountId::from_eth(caller);422 let new_admin = T::CrossAccountId::from_eth(new_admin);423 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;424 Ok(())425 }426427 428 429 430 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {431 self.consume_store_writes(2)?;432433 let caller = T::CrossAccountId::from_eth(caller);434 let admin = T::CrossAccountId::from_eth(admin);435 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;436 Ok(())437 }438439 440 441 442 #[solidity(rename_selector = "setCollectionNesting")]443 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {444 self.consume_store_reads_and_writes(1, 1)?;445446 check_is_owner_or_admin(caller, self)?;447448 let mut permissions = self.collection.permissions.clone();449 let mut nesting = permissions.nesting().clone();450 nesting.token_owner = enable;451 nesting.restricted = None;452 permissions.nesting = Some(nesting);453454 self.collection.permissions = <Pallet<T>>::clamp_permissions(455 self.collection.mode.clone(),456 &self.collection.permissions,457 permissions,458 )459 .map_err(dispatch_to_evm::<T>)?;460461 save(self)462 }463464 465 466 467 468 #[solidity(rename_selector = "setCollectionNesting")]469 fn set_nesting(470 &mut self,471 caller: caller,472 enable: bool,473 collections: Vec<address>,474 ) -> Result<void> {475 self.consume_store_reads_and_writes(1, 1)?;476477 if collections.is_empty() {478 return Err("no addresses provided".into());479 }480 check_is_owner_or_admin(caller, self)?;481482 let mut permissions = self.collection.permissions.clone();483 match enable {484 false => {485 let mut nesting = permissions.nesting().clone();486 nesting.token_owner = false;487 nesting.restricted = None;488 permissions.nesting = Some(nesting);489 }490 true => {491 let mut bv = OwnerRestrictedSet::new();492 for i in collections {493 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {494 Error::Revert("Can't convert address into collection id".into())495 })?)496 .map_err(|_| "too many collections")?;497 }498 let mut nesting = permissions.nesting().clone();499 nesting.token_owner = true;500 nesting.restricted = Some(bv);501 permissions.nesting = Some(nesting);502 }503 };504505 self.collection.permissions = <Pallet<T>>::clamp_permissions(506 self.collection.mode.clone(),507 &self.collection.permissions,508 permissions,509 )510 .map_err(dispatch_to_evm::<T>)?;511512 save(self)513 }514515 516 517 518 519 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {520 self.consume_store_reads_and_writes(1, 1)?;521522 check_is_owner_or_admin(caller, self)?;523 let permissions = CollectionPermissions {524 access: Some(match mode {525 0 => AccessMode::Normal,526 1 => AccessMode::AllowList,527 _ => return Err("not supported access mode".into()),528 }),529 ..Default::default()530 };531 self.collection.permissions = <Pallet<T>>::clamp_permissions(532 self.collection.mode.clone(),533 &self.collection.permissions,534 permissions,535 )536 .map_err(dispatch_to_evm::<T>)?;537538 save(self)539 }540541 542 543 544 fn allowed(&self, user: address) -> Result<bool> {545 Ok(Pallet::<T>::allowed(546 self.id,547 T::CrossAccountId::from_eth(user),548 ))549 }550551 552 553 554 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {555 self.consume_store_writes(1)?;556557 let caller = T::CrossAccountId::from_eth(caller);558 let user = T::CrossAccountId::from_eth(user);559 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;560 Ok(())561 }562563 564 565 566 fn add_to_collection_allow_list_cross(567 &mut self,568 caller: caller,569 user: (address, uint256),570 ) -> Result<void> {571 self.consume_store_writes(1)?;572573 let caller = T::CrossAccountId::from_eth(caller);574 let user = convert_tuple_to_cross_account::<T>(user)?;575 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;576 Ok(())577 }578579 580 581 582 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {583 self.consume_store_writes(1)?;584585 let caller = T::CrossAccountId::from_eth(caller);586 let user = T::CrossAccountId::from_eth(user);587 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;588 Ok(())589 }590591 592 593 594 fn remove_from_collection_allow_list_cross(595 &mut self,596 caller: caller,597 user: (address, uint256),598 ) -> Result<void> {599 self.consume_store_writes(1)?;600601 let caller = T::CrossAccountId::from_eth(caller);602 let user = convert_tuple_to_cross_account::<T>(user)?;603 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;604 Ok(())605 }606607 608 609 610 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {611 self.consume_store_reads_and_writes(1, 1)?;612613 check_is_owner_or_admin(caller, self)?;614 let permissions = CollectionPermissions {615 mint_mode: Some(mode),616 ..Default::default()617 };618 self.collection.permissions = <Pallet<T>>::clamp_permissions(619 self.collection.mode.clone(),620 &self.collection.permissions,621 permissions,622 )623 .map_err(dispatch_to_evm::<T>)?;624625 save(self)626 }627628 629 630 631 632 #[solidity(rename_selector = "isOwnerOrAdmin")]633 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {634 let user = T::CrossAccountId::from_eth(user);635 Ok(self.is_owner_or_admin(&user))636 }637638 639 640 641 642 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {643 let user = convert_tuple_to_cross_account::<T>(user)?;644 Ok(self.is_owner_or_admin(&user))645 }646647 648 649 650 fn unique_collection_type(&self) -> Result<string> {651 let mode = match self.collection.mode {652 CollectionMode::Fungible(_) => "Fungible",653 CollectionMode::NFT => "NFT",654 CollectionMode::ReFungible => "ReFungible",655 };656 Ok(mode.into())657 }658659 660 661 662 663 fn collection_owner(&self) -> Result<(address, uint256)> {664 Ok(convert_cross_account_to_tuple::<T>(665 &T::CrossAccountId::from_sub(self.owner.clone()),666 ))667 }668669 670 671 672 673 #[solidity(rename_selector = "changeCollectionOwner")]674 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {675 self.consume_store_writes(1)?;676677 let caller = T::CrossAccountId::from_eth(caller);678 let new_owner = T::CrossAccountId::from_eth(new_owner);679 self.set_owner_internal(caller, new_owner)680 .map_err(dispatch_to_evm::<T>)681 }682683 684 685 686 687 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {688 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))689 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))690 .collect();691 Ok(result)692 }693694 695 696 697 698 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {699 self.consume_store_writes(1)?;700701 let caller = T::CrossAccountId::from_eth(caller);702 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;703 self.set_owner_internal(caller, new_owner)704 .map_err(dispatch_to_evm::<T>)705 }706}707708709710fn check_is_owner_or_admin<T: Config>(711 caller: caller,712 collection: &CollectionHandle<T>,713) -> Result<T::CrossAccountId> {714 let caller = T::CrossAccountId::from_eth(caller);715 collection716 .check_is_owner_or_admin(&caller)717 .map_err(dispatch_to_evm::<T>)?;718 Ok(caller)719}720721722723fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {724 collection725 .check_is_internal()726 .map_err(dispatch_to_evm::<T>)?;727 collection.save().map_err(dispatch_to_evm::<T>)?;728 Ok(())729}730731732pub mod static_property {733 use evm_coder::{734 execution::{Result, Error},735 };736 use alloc::format;737738 const EXPECT_CONVERT_ERROR: &str = "length < limit";739740 741 pub mod key {742 use super::*;743744 745 pub fn base_uri() -> up_data_structs::PropertyKey {746 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)747 }748749 750 pub fn url() -> up_data_structs::PropertyKey {751 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)752 }753754 755 pub fn suffix() -> up_data_structs::PropertyKey {756 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)757 }758759 760 pub fn parent_nft() -> up_data_structs::PropertyKey {761 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)762 }763 }764765 766 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {767 bytes.to_vec().try_into().map_err(|_| {768 Error::Revert(format!(769 "Property key is too long. Max length is {}.",770 up_data_structs::PropertyKey::bound()771 ))772 })773 }774775 776 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {777 bytes.to_vec().try_into().map_err(|_| {778 Error::Revert(format!(779 "Property key is too long. Max length is {}.",780 up_data_structs::PropertyKey::bound()781 ))782 })783 }784}