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, PropertyKey,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 for (key, value) in properties.into_iter() {110 self.set_collection_property(caller, key, value)?;111 }112 Ok(())113 }114115 116 117 118 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]119 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {120 self.consume_store_reads_and_writes(1, 1)?;121122 let caller = T::CrossAccountId::from_eth(caller);123 let key = <Vec<u8>>::from(key)124 .try_into()125 .map_err(|_| "key too large")?;126127 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)128 }129130 131 132 133 134 135 136 fn collection_property(&self, key: string) -> Result<bytes> {137 let key = <Vec<u8>>::from(key)138 .try_into()139 .map_err(|_| "key too large")?;140141 let props = CollectionProperties::<T>::get(self.id);142 let prop = props.get(&key).ok_or("key not found")?;143144 Ok(bytes(prop.to_vec()))145 }146147 148 149 150 151 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {152 let mut keys_ = Vec::<PropertyKey>::with_capacity(keys.len());153 for key in keys {154 keys_.push(155 <Vec<u8>>::from(key)156 .try_into()157 .map_err(|_| Error::Revert("key too large".into()))?,158 )159 }160 let properties = Pallet::<T>::filter_collection_properties(self.id, Some(keys_))161 .map_err(dispatch_to_evm::<T>)?;162163 let mut properties_ = Vec::<(string, bytes)>::with_capacity(properties.len());164 for p in properties {165 let key =166 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;167 let value = bytes(p.value.to_vec());168 properties_.push((key, value));169 }170 Ok(properties_)171 }172173 174 175 176 177 178 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {179 self.consume_store_reads_and_writes(1, 1)?;180181 check_is_owner_or_admin(caller, self)?;182183 let sponsor = T::CrossAccountId::from_eth(sponsor);184 self.set_sponsor(sponsor.as_sub().clone())185 .map_err(dispatch_to_evm::<T>)?;186 save(self)187 }188189 190 191 192 193 194 fn set_collection_sponsor_cross(195 &mut self,196 caller: caller,197 sponsor: (address, uint256),198 ) -> Result<void> {199 self.consume_store_reads_and_writes(1, 1)?;200201 check_is_owner_or_admin(caller, self)?;202203 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;204 self.set_sponsor(sponsor.as_sub().clone())205 .map_err(dispatch_to_evm::<T>)?;206 save(self)207 }208209 210 fn has_collection_pending_sponsor(&self) -> Result<bool> {211 Ok(matches!(212 self.collection.sponsorship,213 SponsorshipState::Unconfirmed(_)214 ))215 }216217 218 219 220 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {221 self.consume_store_writes(1)?;222223 let caller = T::CrossAccountId::from_eth(caller);224 if !self225 .confirm_sponsorship(caller.as_sub())226 .map_err(dispatch_to_evm::<T>)?227 {228 return Err("caller is not set as sponsor".into());229 }230 save(self)231 }232233 234 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {235 self.consume_store_reads_and_writes(1, 1)?;236 check_is_owner_or_admin(caller, self)?;237 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;238 save(self)239 }240241 242 243 244 fn collection_sponsor(&self) -> Result<(address, uint256)> {245 let sponsor = match self.collection.sponsorship.sponsor() {246 Some(sponsor) => sponsor,247 None => return Ok(Default::default()),248 };249 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());250 let result: (address, uint256) = if sponsor.is_canonical_substrate() {251 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);252 (Default::default(), sponsor)253 } else {254 let sponsor = *sponsor.as_eth();255 (sponsor, Default::default())256 };257 Ok(result)258 }259260 261 262 263 264 265 266 267 268 269 270 #[solidity(rename_selector = "setCollectionLimit")]271 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {272 self.consume_store_reads_and_writes(1, 1)?;273274 check_is_owner_or_admin(caller, self)?;275 let mut limits = self.limits.clone();276277 match limit.as_str() {278 "accountTokenOwnershipLimit" => {279 limits.account_token_ownership_limit = Some(value);280 }281 "sponsoredDataSize" => {282 limits.sponsored_data_size = Some(value);283 }284 "sponsoredDataRateLimit" => {285 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));286 }287 "tokenLimit" => {288 limits.token_limit = Some(value);289 }290 "sponsorTransferTimeout" => {291 limits.sponsor_transfer_timeout = Some(value);292 }293 "sponsorApproveTimeout" => {294 limits.sponsor_approve_timeout = Some(value);295 }296 _ => {297 return Err(Error::Revert(format!(298 "unknown integer limit \"{}\"",299 limit300 )))301 }302 }303 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)304 .map_err(dispatch_to_evm::<T>)?;305 save(self)306 }307308 309 310 311 312 313 314 315 #[solidity(rename_selector = "setCollectionLimit")]316 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {317 self.consume_store_reads_and_writes(1, 1)?;318319 check_is_owner_or_admin(caller, self)?;320 let mut limits = self.limits.clone();321322 match limit.as_str() {323 "ownerCanTransfer" => {324 limits.owner_can_transfer = Some(value);325 }326 "ownerCanDestroy" => {327 limits.owner_can_destroy = Some(value);328 }329 "transfersEnabled" => {330 limits.transfers_enabled = Some(value);331 }332 _ => {333 return Err(Error::Revert(format!(334 "unknown boolean limit \"{}\"",335 limit336 )))337 }338 }339 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)340 .map_err(dispatch_to_evm::<T>)?;341 save(self)342 }343344 345 fn contract_address(&self) -> Result<address> {346 Ok(crate::eth::collection_id_to_address(self.id))347 }348349 350 351 fn add_collection_admin_cross(352 &mut self,353 caller: caller,354 new_admin: (address, uint256),355 ) -> Result<void> {356 self.consume_store_writes(2)?;357358 let caller = T::CrossAccountId::from_eth(caller);359 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;360 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;361 Ok(())362 }363364 365 366 fn remove_collection_admin_cross(367 &mut self,368 caller: caller,369 admin: (address, uint256),370 ) -> Result<void> {371 self.consume_store_writes(2)?;372373 let caller = T::CrossAccountId::from_eth(caller);374 let admin = convert_tuple_to_cross_account::<T>(admin)?;375 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;376 Ok(())377 }378379 380 381 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {382 self.consume_store_writes(2)?;383384 let caller = T::CrossAccountId::from_eth(caller);385 let new_admin = T::CrossAccountId::from_eth(new_admin);386 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;387 Ok(())388 }389390 391 392 393 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {394 self.consume_store_writes(2)?;395396 let caller = T::CrossAccountId::from_eth(caller);397 let admin = T::CrossAccountId::from_eth(admin);398 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;399 Ok(())400 }401402 403 404 405 #[solidity(rename_selector = "setCollectionNesting")]406 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {407 self.consume_store_reads_and_writes(1, 1)?;408409 check_is_owner_or_admin(caller, self)?;410411 let mut permissions = self.collection.permissions.clone();412 let mut nesting = permissions.nesting().clone();413 nesting.token_owner = enable;414 nesting.restricted = None;415 permissions.nesting = Some(nesting);416417 self.collection.permissions = <Pallet<T>>::clamp_permissions(418 self.collection.mode.clone(),419 &self.collection.permissions,420 permissions,421 )422 .map_err(dispatch_to_evm::<T>)?;423424 save(self)425 }426427 428 429 430 431 #[solidity(rename_selector = "setCollectionNesting")]432 fn set_nesting(433 &mut self,434 caller: caller,435 enable: bool,436 collections: Vec<address>,437 ) -> Result<void> {438 self.consume_store_reads_and_writes(1, 1)?;439440 if collections.is_empty() {441 return Err("no addresses provided".into());442 }443 check_is_owner_or_admin(caller, self)?;444445 let mut permissions = self.collection.permissions.clone();446 match enable {447 false => {448 let mut nesting = permissions.nesting().clone();449 nesting.token_owner = false;450 nesting.restricted = None;451 permissions.nesting = Some(nesting);452 }453 true => {454 let mut bv = OwnerRestrictedSet::new();455 for i in collections {456 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {457 Error::Revert("Can't convert address into collection id".into())458 })?)459 .map_err(|_| "too many collections")?;460 }461 let mut nesting = permissions.nesting().clone();462 nesting.token_owner = true;463 nesting.restricted = Some(bv);464 permissions.nesting = Some(nesting);465 }466 };467468 self.collection.permissions = <Pallet<T>>::clamp_permissions(469 self.collection.mode.clone(),470 &self.collection.permissions,471 permissions,472 )473 .map_err(dispatch_to_evm::<T>)?;474475 save(self)476 }477478 479 480 481 482 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {483 self.consume_store_reads_and_writes(1, 1)?;484485 check_is_owner_or_admin(caller, self)?;486 let permissions = CollectionPermissions {487 access: Some(match mode {488 0 => AccessMode::Normal,489 1 => AccessMode::AllowList,490 _ => return Err("not supported access mode".into()),491 }),492 ..Default::default()493 };494 self.collection.permissions = <Pallet<T>>::clamp_permissions(495 self.collection.mode.clone(),496 &self.collection.permissions,497 permissions,498 )499 .map_err(dispatch_to_evm::<T>)?;500501 save(self)502 }503504 505 506 507 fn allowed(&self, user: address) -> Result<bool> {508 Ok(Pallet::<T>::allowed(509 self.id,510 T::CrossAccountId::from_eth(user),511 ))512 }513514 515 516 517 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {518 self.consume_store_writes(1)?;519520 let caller = T::CrossAccountId::from_eth(caller);521 let user = T::CrossAccountId::from_eth(user);522 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;523 Ok(())524 }525526 527 528 529 fn add_to_collection_allow_list_cross(530 &mut self,531 caller: caller,532 user: (address, uint256),533 ) -> Result<void> {534 self.consume_store_writes(1)?;535536 let caller = T::CrossAccountId::from_eth(caller);537 let user = convert_tuple_to_cross_account::<T>(user)?;538 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;539 Ok(())540 }541542 543 544 545 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {546 self.consume_store_writes(1)?;547548 let caller = T::CrossAccountId::from_eth(caller);549 let user = T::CrossAccountId::from_eth(user);550 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;551 Ok(())552 }553554 555 556 557 fn remove_from_collection_allow_list_cross(558 &mut self,559 caller: caller,560 user: (address, uint256),561 ) -> Result<void> {562 self.consume_store_writes(1)?;563564 let caller = T::CrossAccountId::from_eth(caller);565 let user = convert_tuple_to_cross_account::<T>(user)?;566 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;567 Ok(())568 }569570 571 572 573 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {574 self.consume_store_reads_and_writes(1, 1)?;575576 check_is_owner_or_admin(caller, self)?;577 let permissions = CollectionPermissions {578 mint_mode: Some(mode),579 ..Default::default()580 };581 self.collection.permissions = <Pallet<T>>::clamp_permissions(582 self.collection.mode.clone(),583 &self.collection.permissions,584 permissions,585 )586 .map_err(dispatch_to_evm::<T>)?;587588 save(self)589 }590591 592 593 594 595 #[solidity(rename_selector = "isOwnerOrAdmin")]596 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {597 let user = T::CrossAccountId::from_eth(user);598 Ok(self.is_owner_or_admin(&user))599 }600601 602 603 604 605 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {606 let user = convert_tuple_to_cross_account::<T>(user)?;607 Ok(self.is_owner_or_admin(&user))608 }609610 611 612 613 fn unique_collection_type(&self) -> Result<string> {614 let mode = match self.collection.mode {615 CollectionMode::Fungible(_) => "Fungible",616 CollectionMode::NFT => "NFT",617 CollectionMode::ReFungible => "ReFungible",618 };619 Ok(mode.into())620 }621622 623 624 625 626 fn collection_owner(&self) -> Result<(address, uint256)> {627 Ok(convert_cross_account_to_tuple::<T>(628 &T::CrossAccountId::from_sub(self.owner.clone()),629 ))630 }631632 633 634 635 636 #[solidity(rename_selector = "changeCollectionOwner")]637 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {638 self.consume_store_writes(1)?;639640 let caller = T::CrossAccountId::from_eth(caller);641 let new_owner = T::CrossAccountId::from_eth(new_owner);642 self.set_owner_internal(caller, new_owner)643 .map_err(dispatch_to_evm::<T>)644 }645646 647 648 649 650 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {651 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))652 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))653 .collect();654 Ok(result)655 }656657 658 659 660 661 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {662 self.consume_store_writes(1)?;663664 let caller = T::CrossAccountId::from_eth(caller);665 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;666 self.set_owner_internal(caller, new_owner)667 .map_err(dispatch_to_evm::<T>)668 }669}670671672673fn check_is_owner_or_admin<T: Config>(674 caller: caller,675 collection: &CollectionHandle<T>,676) -> Result<T::CrossAccountId> {677 let caller = T::CrossAccountId::from_eth(caller);678 collection679 .check_is_owner_or_admin(&caller)680 .map_err(dispatch_to_evm::<T>)?;681 Ok(caller)682}683684685686fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {687 collection688 .check_is_internal()689 .map_err(dispatch_to_evm::<T>)?;690 collection.save().map_err(dispatch_to_evm::<T>)?;691 Ok(())692}693694695pub mod static_property {696 use evm_coder::{697 execution::{Result, Error},698 };699 use alloc::format;700701 const EXPECT_CONVERT_ERROR: &str = "length < limit";702703 704 pub mod key {705 use super::*;706707 708 pub fn base_uri() -> up_data_structs::PropertyKey {709 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)710 }711712 713 pub fn url() -> up_data_structs::PropertyKey {714 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)715 }716717 718 pub fn suffix() -> up_data_structs::PropertyKey {719 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)720 }721722 723 pub fn parent_nft() -> up_data_structs::PropertyKey {724 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)725 }726 }727728 729 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {730 bytes.to_vec().try_into().map_err(|_| {731 Error::Revert(format!(732 "Property key is too long. Max length is {}.",733 up_data_structs::PropertyKey::bound()734 ))735 })736 }737738 739 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {740 bytes.to_vec().try_into().map_err(|_| {741 Error::Revert(format!(742 "Property key is too long. Max length is {}.",743 up_data_structs::PropertyKey::bound()744 ))745 })746 }747}