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 let caller = T::CrossAccountId::from_eth(caller);121 let key = <Vec<u8>>::from(key)122 .try_into()123 .map_err(|_| "key too large")?;124125 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)126 }127128 129 130 131 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]132 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let keys = keys135 .into_iter()136 .map(|key| {137 <Vec<u8>>::from(key)138 .try_into()139 .map_err(|_| Error::Revert("key too large".into()))140 })141 .collect::<Result<Vec<_>>>()?;142143 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)144 }145146 147 148 149 150 151 152 fn collection_property(&self, key: string) -> Result<bytes> {153 let key = <Vec<u8>>::from(key)154 .try_into()155 .map_err(|_| "key too large")?;156157 let props = CollectionProperties::<T>::get(self.id);158 let prop = props.get(&key).ok_or("key not found")?;159160 Ok(bytes(prop.to_vec()))161 }162163 164 165 166 167 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {168 let keys = keys169 .into_iter()170 .map(|key| {171 <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| Error::Revert("key too large".into()))174 })175 .collect::<Result<Vec<_>>>()?;176177 let properties = Pallet::<T>::filter_collection_properties(178 self.id,179 if keys.is_empty() { None } else { Some(keys) },180 )181 .map_err(dispatch_to_evm::<T>)?;182183 let properties = properties184 .into_iter()185 .map(|p| {186 let key =187 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;188 let value = bytes(p.value.to_vec());189 Ok((key, value))190 })191 .collect::<Result<Vec<_>>>()?;192 Ok(properties)193 }194195 196 197 198 199 200 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {201 self.consume_store_reads_and_writes(1, 1)?;202203 check_is_owner_or_admin(caller, self)?;204205 let sponsor = T::CrossAccountId::from_eth(sponsor);206 self.set_sponsor(sponsor.as_sub().clone())207 .map_err(dispatch_to_evm::<T>)?;208 save(self)209 }210211 212 213 214 215 216 fn set_collection_sponsor_cross(217 &mut self,218 caller: caller,219 sponsor: (address, uint256),220 ) -> Result<void> {221 self.consume_store_reads_and_writes(1, 1)?;222223 check_is_owner_or_admin(caller, self)?;224225 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;226 self.set_sponsor(sponsor.as_sub().clone())227 .map_err(dispatch_to_evm::<T>)?;228 save(self)229 }230231 232 fn has_collection_pending_sponsor(&self) -> Result<bool> {233 Ok(matches!(234 self.collection.sponsorship,235 SponsorshipState::Unconfirmed(_)236 ))237 }238239 240 241 242 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {243 self.consume_store_writes(1)?;244245 let caller = T::CrossAccountId::from_eth(caller);246 if !self247 .confirm_sponsorship(caller.as_sub())248 .map_err(dispatch_to_evm::<T>)?249 {250 return Err("caller is not set as sponsor".into());251 }252 save(self)253 }254255 256 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {257 self.consume_store_reads_and_writes(1, 1)?;258 check_is_owner_or_admin(caller, self)?;259 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;260 save(self)261 }262263 264 265 266 fn collection_sponsor(&self) -> Result<(address, uint256)> {267 let sponsor = match self.collection.sponsorship.sponsor() {268 Some(sponsor) => sponsor,269 None => return Ok(Default::default()),270 };271 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());272 let result: (address, uint256) = if sponsor.is_canonical_substrate() {273 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);274 (Default::default(), sponsor)275 } else {276 let sponsor = *sponsor.as_eth();277 (sponsor, Default::default())278 };279 Ok(result)280 }281282 283 284 285 286 287 288 289 290 291 292 #[solidity(rename_selector = "setCollectionLimit")]293 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {294 self.consume_store_reads_and_writes(1, 1)?;295296 check_is_owner_or_admin(caller, self)?;297 let mut limits = self.limits.clone();298299 match limit.as_str() {300 "accountTokenOwnershipLimit" => {301 limits.account_token_ownership_limit = Some(value);302 }303 "sponsoredDataSize" => {304 limits.sponsored_data_size = Some(value);305 }306 "sponsoredDataRateLimit" => {307 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));308 }309 "tokenLimit" => {310 limits.token_limit = Some(value);311 }312 "sponsorTransferTimeout" => {313 limits.sponsor_transfer_timeout = Some(value);314 }315 "sponsorApproveTimeout" => {316 limits.sponsor_approve_timeout = Some(value);317 }318 _ => {319 return Err(Error::Revert(format!(320 "unknown integer limit \"{}\"",321 limit322 )))323 }324 }325 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)326 .map_err(dispatch_to_evm::<T>)?;327 save(self)328 }329330 331 332 333 334 335 336 337 #[solidity(rename_selector = "setCollectionLimit")]338 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {339 self.consume_store_reads_and_writes(1, 1)?;340341 check_is_owner_or_admin(caller, self)?;342 let mut limits = self.limits.clone();343344 match limit.as_str() {345 "ownerCanTransfer" => {346 limits.owner_can_transfer = Some(value);347 }348 "ownerCanDestroy" => {349 limits.owner_can_destroy = Some(value);350 }351 "transfersEnabled" => {352 limits.transfers_enabled = Some(value);353 }354 _ => {355 return Err(Error::Revert(format!(356 "unknown boolean limit \"{}\"",357 limit358 )))359 }360 }361 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)362 .map_err(dispatch_to_evm::<T>)?;363 save(self)364 }365366 367 fn contract_address(&self) -> Result<address> {368 Ok(crate::eth::collection_id_to_address(self.id))369 }370371 372 373 fn add_collection_admin_cross(374 &mut self,375 caller: caller,376 new_admin: (address, uint256),377 ) -> Result<void> {378 self.consume_store_writes(2)?;379380 let caller = T::CrossAccountId::from_eth(caller);381 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;382 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;383 Ok(())384 }385386 387 388 fn remove_collection_admin_cross(389 &mut self,390 caller: caller,391 admin: (address, uint256),392 ) -> Result<void> {393 self.consume_store_writes(2)?;394395 let caller = T::CrossAccountId::from_eth(caller);396 let admin = convert_tuple_to_cross_account::<T>(admin)?;397 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;398 Ok(())399 }400401 402 403 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {404 self.consume_store_writes(2)?;405406 let caller = T::CrossAccountId::from_eth(caller);407 let new_admin = T::CrossAccountId::from_eth(new_admin);408 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;409 Ok(())410 }411412 413 414 415 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {416 self.consume_store_writes(2)?;417418 let caller = T::CrossAccountId::from_eth(caller);419 let admin = T::CrossAccountId::from_eth(admin);420 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;421 Ok(())422 }423424 425 426 427 #[solidity(rename_selector = "setCollectionNesting")]428 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {429 self.consume_store_reads_and_writes(1, 1)?;430431 check_is_owner_or_admin(caller, self)?;432433 let mut permissions = self.collection.permissions.clone();434 let mut nesting = permissions.nesting().clone();435 nesting.token_owner = enable;436 nesting.restricted = None;437 permissions.nesting = Some(nesting);438439 self.collection.permissions = <Pallet<T>>::clamp_permissions(440 self.collection.mode.clone(),441 &self.collection.permissions,442 permissions,443 )444 .map_err(dispatch_to_evm::<T>)?;445446 save(self)447 }448449 450 451 452 453 #[solidity(rename_selector = "setCollectionNesting")]454 fn set_nesting(455 &mut self,456 caller: caller,457 enable: bool,458 collections: Vec<address>,459 ) -> Result<void> {460 self.consume_store_reads_and_writes(1, 1)?;461462 if collections.is_empty() {463 return Err("no addresses provided".into());464 }465 check_is_owner_or_admin(caller, self)?;466467 let mut permissions = self.collection.permissions.clone();468 match enable {469 false => {470 let mut nesting = permissions.nesting().clone();471 nesting.token_owner = false;472 nesting.restricted = None;473 permissions.nesting = Some(nesting);474 }475 true => {476 let mut bv = OwnerRestrictedSet::new();477 for i in collections {478 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {479 Error::Revert("Can't convert address into collection id".into())480 })?)481 .map_err(|_| "too many collections")?;482 }483 let mut nesting = permissions.nesting().clone();484 nesting.token_owner = true;485 nesting.restricted = Some(bv);486 permissions.nesting = Some(nesting);487 }488 };489490 self.collection.permissions = <Pallet<T>>::clamp_permissions(491 self.collection.mode.clone(),492 &self.collection.permissions,493 permissions,494 )495 .map_err(dispatch_to_evm::<T>)?;496497 save(self)498 }499500 501 502 503 504 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {505 self.consume_store_reads_and_writes(1, 1)?;506507 check_is_owner_or_admin(caller, self)?;508 let permissions = CollectionPermissions {509 access: Some(match mode {510 0 => AccessMode::Normal,511 1 => AccessMode::AllowList,512 _ => return Err("not supported access mode".into()),513 }),514 ..Default::default()515 };516 self.collection.permissions = <Pallet<T>>::clamp_permissions(517 self.collection.mode.clone(),518 &self.collection.permissions,519 permissions,520 )521 .map_err(dispatch_to_evm::<T>)?;522523 save(self)524 }525526 527 528 529 fn allowed(&self, user: address) -> Result<bool> {530 Ok(Pallet::<T>::allowed(531 self.id,532 T::CrossAccountId::from_eth(user),533 ))534 }535536 537 538 539 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {540 self.consume_store_writes(1)?;541542 let caller = T::CrossAccountId::from_eth(caller);543 let user = T::CrossAccountId::from_eth(user);544 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;545 Ok(())546 }547548 549 550 551 fn add_to_collection_allow_list_cross(552 &mut self,553 caller: caller,554 user: (address, uint256),555 ) -> Result<void> {556 self.consume_store_writes(1)?;557558 let caller = T::CrossAccountId::from_eth(caller);559 let user = convert_tuple_to_cross_account::<T>(user)?;560 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;561 Ok(())562 }563564 565 566 567 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {568 self.consume_store_writes(1)?;569570 let caller = T::CrossAccountId::from_eth(caller);571 let user = T::CrossAccountId::from_eth(user);572 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;573 Ok(())574 }575576 577 578 579 fn remove_from_collection_allow_list_cross(580 &mut self,581 caller: caller,582 user: (address, uint256),583 ) -> Result<void> {584 self.consume_store_writes(1)?;585586 let caller = T::CrossAccountId::from_eth(caller);587 let user = convert_tuple_to_cross_account::<T>(user)?;588 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;589 Ok(())590 }591592 593 594 595 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {596 self.consume_store_reads_and_writes(1, 1)?;597598 check_is_owner_or_admin(caller, self)?;599 let permissions = CollectionPermissions {600 mint_mode: Some(mode),601 ..Default::default()602 };603 self.collection.permissions = <Pallet<T>>::clamp_permissions(604 self.collection.mode.clone(),605 &self.collection.permissions,606 permissions,607 )608 .map_err(dispatch_to_evm::<T>)?;609610 save(self)611 }612613 614 615 616 617 #[solidity(rename_selector = "isOwnerOrAdmin")]618 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {619 let user = T::CrossAccountId::from_eth(user);620 Ok(self.is_owner_or_admin(&user))621 }622623 624 625 626 627 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {628 let user = convert_tuple_to_cross_account::<T>(user)?;629 Ok(self.is_owner_or_admin(&user))630 }631632 633 634 635 fn unique_collection_type(&self) -> Result<string> {636 let mode = match self.collection.mode {637 CollectionMode::Fungible(_) => "Fungible",638 CollectionMode::NFT => "NFT",639 CollectionMode::ReFungible => "ReFungible",640 };641 Ok(mode.into())642 }643644 645 646 647 648 fn collection_owner(&self) -> Result<(address, uint256)> {649 Ok(convert_cross_account_to_tuple::<T>(650 &T::CrossAccountId::from_sub(self.owner.clone()),651 ))652 }653654 655 656 657 658 #[solidity(rename_selector = "changeCollectionOwner")]659 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {660 self.consume_store_writes(1)?;661662 let caller = T::CrossAccountId::from_eth(caller);663 let new_owner = T::CrossAccountId::from_eth(new_owner);664 self.set_owner_internal(caller, new_owner)665 .map_err(dispatch_to_evm::<T>)666 }667668 669 670 671 672 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {673 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))674 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))675 .collect();676 Ok(result)677 }678679 680 681 682 683 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {684 self.consume_store_writes(1)?;685686 let caller = T::CrossAccountId::from_eth(caller);687 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;688 self.set_owner_internal(caller, new_owner)689 .map_err(dispatch_to_evm::<T>)690 }691}692693694695fn check_is_owner_or_admin<T: Config>(696 caller: caller,697 collection: &CollectionHandle<T>,698) -> Result<T::CrossAccountId> {699 let caller = T::CrossAccountId::from_eth(caller);700 collection701 .check_is_owner_or_admin(&caller)702 .map_err(dispatch_to_evm::<T>)?;703 Ok(caller)704}705706707708fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {709 collection710 .check_is_internal()711 .map_err(dispatch_to_evm::<T>)?;712 collection.save().map_err(dispatch_to_evm::<T>)?;713 Ok(())714}715716717pub mod static_property {718 use evm_coder::{719 execution::{Result, Error},720 };721 use alloc::format;722723 const EXPECT_CONVERT_ERROR: &str = "length < limit";724725 726 pub mod key {727 use super::*;728729 730 pub fn base_uri() -> up_data_structs::PropertyKey {731 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)732 }733734 735 pub fn url() -> up_data_structs::PropertyKey {736 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)737 }738739 740 pub fn suffix() -> up_data_structs::PropertyKey {741 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)742 }743744 745 pub fn parent_nft() -> up_data_structs::PropertyKey {746 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)747 }748 }749750 751 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {752 bytes.to_vec().try_into().map_err(|_| {753 Error::Revert(format!(754 "Property key is too long. Max length is {}.",755 up_data_structs::PropertyKey::bound()756 ))757 })758 }759760 761 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {762 bytes.to_vec().try_into().map_err(|_| {763 Error::Revert(format!(764 "Property key is too long. Max length is {}.",765 up_data_structs::PropertyKey::bound()766 ))767 })768 }769}