12345678910111213141516171819use evm_coder::{20 abi::AbiType,21 solidity_interface, solidity, ToLog,22 types::*,23 execution::{Result, Error},24 weight,25};26pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};27use pallet_evm_coder_substrate::dispatch_to_evm;28use sp_std::vec::Vec;29use up_data_structs::{30 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,31 SponsoringRateLimit, SponsorshipState,32};33use alloc::format;3435use crate::{36 Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,37 eth::convert_cross_account_to_uint256, weights::WeightInfo,38};394041#[derive(ToLog)]42pub enum CollectionHelpersEvents {43 44 CollectionCreated {45 46 #[indexed]47 owner: address,4849 50 #[indexed]51 collection_id: address,52 },53 54 CollectionDestroyed {55 56 #[indexed]57 collection_id: address,58 },59}60616263pub trait CommonEvmHandler {64 65 const CODE: &'static [u8];6667 68 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;69}707172#[solidity_interface(name = Collection)]73impl<T: Config> CollectionHandle<T>74where75 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,76{77 78 79 80 81 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]82 fn set_collection_property(83 &mut self,84 caller: caller,85 key: string,86 value: bytes,87 ) -> Result<void> {88 let caller = T::CrossAccountId::from_eth(caller);89 let key = <Vec<u8>>::from(key)90 .try_into()91 .map_err(|_| "key too large")?;92 let value = value.0.try_into().map_err(|_| "value too large")?;9394 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })95 .map_err(dispatch_to_evm::<T>)96 }9798 99 100 101 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]102 fn set_collection_properties(103 &mut self,104 caller: caller,105 properties: Vec<(string, bytes)>,106 ) -> Result<void> {107 let caller = T::CrossAccountId::from_eth(caller);108109 let properties = properties110 .into_iter()111 .map(|(key, value)| {112 let key = <Vec<u8>>::from(key)113 .try_into()114 .map_err(|_| "key too large")?;115116 let value = value.0.try_into().map_err(|_| "value too large")?;117118 Ok(Property { key, value })119 })120 .collect::<Result<Vec<_>>>()?;121122 <Pallet<T>>::set_collection_properties(self, &caller, properties)123 .map_err(dispatch_to_evm::<T>)124 }125126 127 128 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).map_err(dispatch_to_evm::<T>)155 }156157 158 159 160 161 162 163 fn collection_property(&self, key: string) -> Result<bytes> {164 let key = <Vec<u8>>::from(key)165 .try_into()166 .map_err(|_| "key too large")?;167168 let props = CollectionProperties::<T>::get(self.id);169 let prop = props.get(&key).ok_or("key not found")?;170171 Ok(bytes(prop.to_vec()))172 }173174 175 176 177 178 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {179 let keys = keys180 .into_iter()181 .map(|key| {182 <Vec<u8>>::from(key)183 .try_into()184 .map_err(|_| Error::Revert("key too large".into()))185 })186 .collect::<Result<Vec<_>>>()?;187188 let properties = Pallet::<T>::filter_collection_properties(189 self.id,190 if keys.is_empty() { None } else { Some(keys) },191 )192 .map_err(dispatch_to_evm::<T>)?;193194 let properties = properties195 .into_iter()196 .map(|p| {197 let key =198 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;199 let value = bytes(p.value.to_vec());200 Ok((key, value))201 })202 .collect::<Result<Vec<_>>>()?;203 Ok(properties)204 }205206 207 208 209 210 211 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {212 self.consume_store_reads_and_writes(1, 1)?;213214 check_is_owner_or_admin(caller, self)?;215216 let sponsor = T::CrossAccountId::from_eth(sponsor);217 self.set_sponsor(sponsor.as_sub().clone())218 .map_err(dispatch_to_evm::<T>)?;219 save(self)220 }221222 223 224 225 226 227 fn set_collection_sponsor_cross(228 &mut self,229 caller: caller,230 sponsor: EthCrossAccount,231 ) -> Result<void> {232 self.consume_store_reads_and_writes(1, 1)?;233234 check_is_owner_or_admin(caller, self)?;235236 let sponsor = sponsor.into_sub_cross_account::<T>()?;237 self.set_sponsor(sponsor.as_sub().clone())238 .map_err(dispatch_to_evm::<T>)?;239 save(self)240 }241242 243 fn has_collection_pending_sponsor(&self) -> Result<bool> {244 Ok(matches!(245 self.collection.sponsorship,246 SponsorshipState::Unconfirmed(_)247 ))248 }249250 251 252 253 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {254 self.consume_store_writes(1)?;255256 let caller = T::CrossAccountId::from_eth(caller);257 if !self258 .confirm_sponsorship(caller.as_sub())259 .map_err(dispatch_to_evm::<T>)?260 {261 return Err("caller is not set as sponsor".into());262 }263 save(self)264 }265266 267 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {268 self.consume_store_reads_and_writes(1, 1)?;269 check_is_owner_or_admin(caller, self)?;270 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;271 save(self)272 }273274 275 276 277 fn collection_sponsor(&self) -> Result<(address, uint256)> {278 let sponsor = match self.collection.sponsorship.sponsor() {279 Some(sponsor) => sponsor,280 None => return Ok(Default::default()),281 };282 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());283 let result: (address, uint256) = if sponsor.is_canonical_substrate() {284 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);285 (Default::default(), sponsor)286 } else {287 let sponsor = *sponsor.as_eth();288 (sponsor, Default::default())289 };290 Ok(result)291 }292293 294 295 296 297 298 299 300 301 302 303 #[solidity(rename_selector = "setCollectionLimit")]304 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {305 self.consume_store_reads_and_writes(1, 1)?;306307 check_is_owner_or_admin(caller, self)?;308 let mut limits = self.limits.clone();309310 match limit.as_str() {311 "accountTokenOwnershipLimit" => {312 limits.account_token_ownership_limit = Some(value);313 }314 "sponsoredDataSize" => {315 limits.sponsored_data_size = Some(value);316 }317 "sponsoredDataRateLimit" => {318 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));319 }320 "tokenLimit" => {321 limits.token_limit = Some(value);322 }323 "sponsorTransferTimeout" => {324 limits.sponsor_transfer_timeout = Some(value);325 }326 "sponsorApproveTimeout" => {327 limits.sponsor_approve_timeout = Some(value);328 }329 _ => {330 return Err(Error::Revert(format!(331 "unknown integer limit \"{}\"",332 limit333 )))334 }335 }336 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)337 .map_err(dispatch_to_evm::<T>)?;338 save(self)339 }340341 342 343 344 345 346 347 348 #[solidity(rename_selector = "setCollectionLimit")]349 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {350 self.consume_store_reads_and_writes(1, 1)?;351352 check_is_owner_or_admin(caller, self)?;353 let mut limits = self.limits.clone();354355 match limit.as_str() {356 "ownerCanTransfer" => {357 limits.owner_can_transfer = Some(value);358 }359 "ownerCanDestroy" => {360 limits.owner_can_destroy = Some(value);361 }362 "transfersEnabled" => {363 limits.transfers_enabled = Some(value);364 }365 _ => {366 return Err(Error::Revert(format!(367 "unknown boolean limit \"{}\"",368 limit369 )))370 }371 }372 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)373 .map_err(dispatch_to_evm::<T>)?;374 save(self)375 }376377 378 fn contract_address(&self) -> Result<address> {379 Ok(crate::eth::collection_id_to_address(self.id))380 }381382 383 384 fn add_collection_admin_cross(385 &mut self,386 caller: caller,387 new_admin: EthCrossAccount,388 ) -> Result<void> {389 self.consume_store_writes(2)?;390391 let caller = T::CrossAccountId::from_eth(caller);392 let new_admin = new_admin.into_sub_cross_account::<T>()?;393 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;394 Ok(())395 }396397 398 399 fn remove_collection_admin_cross(400 &mut self,401 caller: caller,402 admin: EthCrossAccount,403 ) -> Result<void> {404 self.consume_store_writes(2)?;405406 let caller = T::CrossAccountId::from_eth(caller);407 let admin = admin.into_sub_cross_account::<T>()?;408 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;409 Ok(())410 }411412 413 414 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {415 self.consume_store_writes(2)?;416417 let caller = T::CrossAccountId::from_eth(caller);418 let new_admin = T::CrossAccountId::from_eth(new_admin);419 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;420 Ok(())421 }422423 424 425 426 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {427 self.consume_store_writes(2)?;428429 let caller = T::CrossAccountId::from_eth(caller);430 let admin = T::CrossAccountId::from_eth(admin);431 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;432 Ok(())433 }434435 436 437 438 #[solidity(rename_selector = "setCollectionNesting")]439 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {440 self.consume_store_reads_and_writes(1, 1)?;441442 check_is_owner_or_admin(caller, self)?;443444 let mut permissions = self.collection.permissions.clone();445 let mut nesting = permissions.nesting().clone();446 nesting.token_owner = enable;447 nesting.restricted = None;448 permissions.nesting = Some(nesting);449450 self.collection.permissions = <Pallet<T>>::clamp_permissions(451 self.collection.mode.clone(),452 &self.collection.permissions,453 permissions,454 )455 .map_err(dispatch_to_evm::<T>)?;456457 save(self)458 }459460 461 462 463 464 #[solidity(rename_selector = "setCollectionNesting")]465 fn set_nesting(466 &mut self,467 caller: caller,468 enable: bool,469 collections: Vec<address>,470 ) -> Result<void> {471 self.consume_store_reads_and_writes(1, 1)?;472473 if collections.is_empty() {474 return Err("no addresses provided".into());475 }476 check_is_owner_or_admin(caller, self)?;477478 let mut permissions = self.collection.permissions.clone();479 match enable {480 false => {481 let mut nesting = permissions.nesting().clone();482 nesting.token_owner = false;483 nesting.restricted = None;484 permissions.nesting = Some(nesting);485 }486 true => {487 let mut bv = OwnerRestrictedSet::new();488 for i in collections {489 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {490 Error::Revert("Can't convert address into collection id".into())491 })?)492 .map_err(|_| "too many collections")?;493 }494 let mut nesting = permissions.nesting().clone();495 nesting.token_owner = true;496 nesting.restricted = Some(bv);497 permissions.nesting = Some(nesting);498 }499 };500501 self.collection.permissions = <Pallet<T>>::clamp_permissions(502 self.collection.mode.clone(),503 &self.collection.permissions,504 permissions,505 )506 .map_err(dispatch_to_evm::<T>)?;507508 save(self)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 check_is_owner_or_admin(caller, self)?;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 self.collection.permissions = <Pallet<T>>::clamp_permissions(528 self.collection.mode.clone(),529 &self.collection.permissions,530 permissions,531 )532 .map_err(dispatch_to_evm::<T>)?;533534 save(self)535 }536537 538 539 540 fn allowed(&self, user: address) -> Result<bool> {541 Ok(Pallet::<T>::allowed(542 self.id,543 T::CrossAccountId::from_eth(user),544 ))545 }546547 548 549 550 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {551 self.consume_store_writes(1)?;552553 let caller = T::CrossAccountId::from_eth(caller);554 let user = T::CrossAccountId::from_eth(user);555 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558559 560 561 562 fn add_to_collection_allow_list_cross(563 &mut self,564 caller: caller,565 user: EthCrossAccount,566 ) -> Result<void> {567 self.consume_store_writes(1)?;568569 let caller = T::CrossAccountId::from_eth(caller);570 let user = user.into_sub_cross_account::<T>()?;571 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;572 Ok(())573 }574575 576 577 578 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {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, false).map_err(dispatch_to_evm::<T>)?;584 Ok(())585 }586587 588 589 590 fn remove_from_collection_allow_list_cross(591 &mut self,592 caller: caller,593 user: EthCrossAccount,594 ) -> Result<void> {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, false).map_err(dispatch_to_evm::<T>)?;600 Ok(())601 }602603 604 605 606 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {607 self.consume_store_reads_and_writes(1, 1)?;608609 check_is_owner_or_admin(caller, self)?;610 let permissions = CollectionPermissions {611 mint_mode: Some(mode),612 ..Default::default()613 };614 self.collection.permissions = <Pallet<T>>::clamp_permissions(615 self.collection.mode.clone(),616 &self.collection.permissions,617 permissions,618 )619 .map_err(dispatch_to_evm::<T>)?;620621 save(self)622 }623624 625 626 627 628 #[solidity(rename_selector = "isOwnerOrAdmin")]629 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {630 let user = T::CrossAccountId::from_eth(user);631 Ok(self.is_owner_or_admin(&user))632 }633634 635 636 637 638 fn is_owner_or_admin_cross(&self, user: EthCrossAccount) -> Result<bool> {639 let user = user.into_sub_cross_account::<T>()?;640 Ok(self.is_owner_or_admin(&user))641 }642643 644 645 646 fn unique_collection_type(&self) -> Result<string> {647 let mode = match self.collection.mode {648 CollectionMode::Fungible(_) => "Fungible",649 CollectionMode::NFT => "NFT",650 CollectionMode::ReFungible => "ReFungible",651 };652 Ok(mode.into())653 }654655 656 657 658 659 fn collection_owner(&self) -> Result<EthCrossAccount> {660 Ok(EthCrossAccount::from_sub_cross_account::<T>(661 &T::CrossAccountId::from_sub(self.owner.clone()),662 ))663 }664665 666 667 668 669 #[solidity(rename_selector = "changeCollectionOwner")]670 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {671 self.consume_store_writes(1)?;672673 let caller = T::CrossAccountId::from_eth(caller);674 let new_owner = T::CrossAccountId::from_eth(new_owner);675 self.set_owner_internal(caller, new_owner)676 .map_err(dispatch_to_evm::<T>)677 }678679 680 681 682 683 fn collection_admins(&self) -> Result<Vec<EthCrossAccount>> {684 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))685 .map(|(admin, _)| EthCrossAccount::from_sub_cross_account::<T>(&admin))686 .collect();687 Ok(result)688 }689690 691 692 693 694 fn set_owner_cross(&mut self, caller: caller, new_owner: EthCrossAccount) -> Result<void> {695 self.consume_store_writes(1)?;696697 let caller = T::CrossAccountId::from_eth(caller);698 let new_owner = new_owner.into_sub_cross_account::<T>()?;699 self.set_owner_internal(caller, new_owner)700 .map_err(dispatch_to_evm::<T>)701 }702}703704705706fn check_is_owner_or_admin<T: Config>(707 caller: caller,708 collection: &CollectionHandle<T>,709) -> Result<T::CrossAccountId> {710 let caller = T::CrossAccountId::from_eth(caller);711 collection712 .check_is_owner_or_admin(&caller)713 .map_err(dispatch_to_evm::<T>)?;714 Ok(caller)715}716717718719fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {720 collection721 .check_is_internal()722 .map_err(dispatch_to_evm::<T>)?;723 collection.save().map_err(dispatch_to_evm::<T>)?;724 Ok(())725}726727728pub mod static_property {729 use evm_coder::{730 execution::{Result, Error},731 };732 use alloc::format;733734 const EXPECT_CONVERT_ERROR: &str = "length < limit";735736 737 pub mod key {738 use super::*;739740 741 pub fn base_uri() -> up_data_structs::PropertyKey {742 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)743 }744745 746 pub fn url() -> up_data_structs::PropertyKey {747 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)748 }749750 751 pub fn suffix() -> up_data_structs::PropertyKey {752 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)753 }754755 756 pub fn parent_nft() -> up_data_structs::PropertyKey {757 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)758 }759 }760761 762 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {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 }770771 772 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {773 bytes.to_vec().try_into().map_err(|_| {774 Error::Revert(format!(775 "Property key is too long. Max length is {}.",776 up_data_structs::PropertyKey::bound()777 ))778 })779 }780}