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,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 68 const CODE: &'static [u8];6970 71 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;72}737475#[solidity_interface(name = Collection)]76impl<T: Config> CollectionHandle<T>77where78 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,79{80 81 82 83 84 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]85 fn set_collection_property(86 &mut self,87 caller: caller,88 key: string,89 value: bytes,90 ) -> Result<void> {91 let caller = T::CrossAccountId::from_eth(caller);92 let key = <Vec<u8>>::from(key)93 .try_into()94 .map_err(|_| "key too large")?;95 let value = value.0.try_into().map_err(|_| "value too large")?;9697 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })98 .map_err(dispatch_to_evm::<T>)99 }100101 102 103 104 #[weight(<SelfWeightOf<T>>::set_collection_properties(properties.len() as u32))]105 fn set_collection_properties(106 &mut self,107 caller: caller,108 properties: Vec<(string, bytes)>,109 ) -> Result<void> {110 let caller = T::CrossAccountId::from_eth(caller);111112 let properties = properties113 .into_iter()114 .map(|(key, value)| {115 let key = <Vec<u8>>::from(key)116 .try_into()117 .map_err(|_| "key too large")?;118119 let value = value.0.try_into().map_err(|_| "value too large")?;120121 Ok(Property { key, value })122 })123 .collect::<Result<Vec<_>>>()?;124125 <Pallet<T>>::set_collection_properties(self, &caller, properties)126 .map_err(dispatch_to_evm::<T>)127 }128129 130 131 132 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]133 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {134 let caller = T::CrossAccountId::from_eth(caller);135 let key = <Vec<u8>>::from(key)136 .try_into()137 .map_err(|_| "key too large")?;138139 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)140 }141142 143 144 145 #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]146 fn delete_collection_properties(&mut self, caller: caller, keys: Vec<string>) -> Result<()> {147 let caller = T::CrossAccountId::from_eth(caller);148 let keys = keys149 .into_iter()150 .map(|key| {151 <Vec<u8>>::from(key)152 .try_into()153 .map_err(|_| Error::Revert("key too large".into()))154 })155 .collect::<Result<Vec<_>>>()?;156157 <Pallet<T>>::delete_collection_properties(self, &caller, keys).map_err(dispatch_to_evm::<T>)158 }159160 161 162 163 164 165 166 fn collection_property(&self, key: string) -> Result<bytes> {167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too large")?;170171 let props = CollectionProperties::<T>::get(self.id);172 let prop = props.get(&key).ok_or("key not found")?;173174 Ok(bytes(prop.to_vec()))175 }176177 178 179 180 181 fn collection_properties(&self, keys: Vec<string>) -> Result<Vec<(string, bytes)>> {182 let keys = keys183 .into_iter()184 .map(|key| {185 <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| Error::Revert("key too large".into()))188 })189 .collect::<Result<Vec<_>>>()?;190191 let properties = Pallet::<T>::filter_collection_properties(192 self.id,193 if keys.is_empty() { None } else { Some(keys) },194 )195 .map_err(dispatch_to_evm::<T>)?;196197 let properties = properties198 .into_iter()199 .map(|p| {200 let key =201 string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?;202 let value = bytes(p.value.to_vec());203 Ok((key, value))204 })205 .collect::<Result<Vec<_>>>()?;206 Ok(properties)207 }208209 210 211 212 213 214 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {215 self.consume_store_reads_and_writes(1, 1)?;216217 check_is_owner_or_admin(caller, self)?;218219 let sponsor = T::CrossAccountId::from_eth(sponsor);220 self.set_sponsor(sponsor.as_sub().clone())221 .map_err(dispatch_to_evm::<T>)?;222 save(self)223 }224225 226 227 228 229 230 fn set_collection_sponsor_cross(231 &mut self,232 caller: caller,233 sponsor: (address, uint256),234 ) -> Result<void> {235 self.consume_store_reads_and_writes(1, 1)?;236237 check_is_owner_or_admin(caller, self)?;238239 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;240 self.set_sponsor(sponsor.as_sub().clone())241 .map_err(dispatch_to_evm::<T>)?;242 save(self)243 }244245 246 fn has_collection_pending_sponsor(&self) -> Result<bool> {247 Ok(matches!(248 self.collection.sponsorship,249 SponsorshipState::Unconfirmed(_)250 ))251 }252253 254 255 256 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {257 self.consume_store_writes(1)?;258259 let caller = T::CrossAccountId::from_eth(caller);260 if !self261 .confirm_sponsorship(caller.as_sub())262 .map_err(dispatch_to_evm::<T>)?263 {264 return Err("caller is not set as sponsor".into());265 }266 save(self)267 }268269 270 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {271 self.consume_store_reads_and_writes(1, 1)?;272 check_is_owner_or_admin(caller, self)?;273 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;274 save(self)275 }276277 278 279 280 fn collection_sponsor(&self) -> Result<(address, uint256)> {281 let sponsor = match self.collection.sponsorship.sponsor() {282 Some(sponsor) => sponsor,283 None => return Ok(Default::default()),284 };285 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());286 let result: (address, uint256) = if sponsor.is_canonical_substrate() {287 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);288 (Default::default(), sponsor)289 } else {290 let sponsor = *sponsor.as_eth();291 (sponsor, Default::default())292 };293 Ok(result)294 }295296 297 298 299 300 301 302 303 304 305 306 #[solidity(rename_selector = "setCollectionLimit")]307 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {308 self.consume_store_reads_and_writes(1, 1)?;309310 check_is_owner_or_admin(caller, self)?;311 let mut limits = self.limits.clone();312313 match limit.as_str() {314 "accountTokenOwnershipLimit" => {315 limits.account_token_ownership_limit = Some(value);316 }317 "sponsoredDataSize" => {318 limits.sponsored_data_size = Some(value);319 }320 "sponsoredDataRateLimit" => {321 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));322 }323 "tokenLimit" => {324 limits.token_limit = Some(value);325 }326 "sponsorTransferTimeout" => {327 limits.sponsor_transfer_timeout = Some(value);328 }329 "sponsorApproveTimeout" => {330 limits.sponsor_approve_timeout = Some(value);331 }332 _ => {333 return Err(Error::Revert(format!(334 "unknown integer 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 346 347 348 349 350 351 #[solidity(rename_selector = "setCollectionLimit")]352 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {353 self.consume_store_reads_and_writes(1, 1)?;354355 check_is_owner_or_admin(caller, self)?;356 let mut limits = self.limits.clone();357358 match limit.as_str() {359 "ownerCanTransfer" => {360 limits.owner_can_transfer = Some(value);361 }362 "ownerCanDestroy" => {363 limits.owner_can_destroy = Some(value);364 }365 "transfersEnabled" => {366 limits.transfers_enabled = Some(value);367 }368 _ => {369 return Err(Error::Revert(format!(370 "unknown boolean limit \"{}\"",371 limit372 )))373 }374 }375 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)376 .map_err(dispatch_to_evm::<T>)?;377 save(self)378 }379380 381 fn contract_address(&self) -> Result<address> {382 Ok(crate::eth::collection_id_to_address(self.id))383 }384385 386 387 fn add_collection_admin_cross(388 &mut self,389 caller: caller,390 new_admin: (address, uint256),391 ) -> Result<void> {392 self.consume_store_writes(2)?;393394 let caller = T::CrossAccountId::from_eth(caller);395 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;396 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;397 Ok(())398 }399400 401 402 fn remove_collection_admin_cross(403 &mut self,404 caller: caller,405 admin: (address, uint256),406 ) -> Result<void> {407 self.consume_store_writes(2)?;408409 let caller = T::CrossAccountId::from_eth(caller);410 let admin = convert_tuple_to_cross_account::<T>(admin)?;411 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;412 Ok(())413 }414415 416 417 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {418 self.consume_store_writes(2)?;419420 let caller = T::CrossAccountId::from_eth(caller);421 let new_admin = T::CrossAccountId::from_eth(new_admin);422 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;423 Ok(())424 }425426 427 428 429 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {430 self.consume_store_writes(2)?;431432 let caller = T::CrossAccountId::from_eth(caller);433 let admin = T::CrossAccountId::from_eth(admin);434 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;435 Ok(())436 }437438 439 440 441 #[solidity(rename_selector = "setCollectionNesting")]442 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {443 self.consume_store_reads_and_writes(1, 1)?;444445 check_is_owner_or_admin(caller, self)?;446447 let mut permissions = self.collection.permissions.clone();448 let mut nesting = permissions.nesting().clone();449 nesting.token_owner = enable;450 nesting.restricted = None;451 permissions.nesting = Some(nesting);452453 self.collection.permissions = <Pallet<T>>::clamp_permissions(454 self.collection.mode.clone(),455 &self.collection.permissions,456 permissions,457 )458 .map_err(dispatch_to_evm::<T>)?;459460 save(self)461 }462463 464 465 466 467 #[solidity(rename_selector = "setCollectionNesting")]468 fn set_nesting(469 &mut self,470 caller: caller,471 enable: bool,472 collections: Vec<address>,473 ) -> Result<void> {474 self.consume_store_reads_and_writes(1, 1)?;475476 if collections.is_empty() {477 return Err("no addresses provided".into());478 }479 check_is_owner_or_admin(caller, self)?;480481 let mut permissions = self.collection.permissions.clone();482 match enable {483 false => {484 let mut nesting = permissions.nesting().clone();485 nesting.token_owner = false;486 nesting.restricted = None;487 permissions.nesting = Some(nesting);488 }489 true => {490 let mut bv = OwnerRestrictedSet::new();491 for i in collections {492 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {493 Error::Revert("Can't convert address into collection id".into())494 })?)495 .map_err(|_| "too many collections")?;496 }497 let mut nesting = permissions.nesting().clone();498 nesting.token_owner = true;499 nesting.restricted = Some(bv);500 permissions.nesting = Some(nesting);501 }502 };503504 self.collection.permissions = <Pallet<T>>::clamp_permissions(505 self.collection.mode.clone(),506 &self.collection.permissions,507 permissions,508 )509 .map_err(dispatch_to_evm::<T>)?;510511 save(self)512 }513514 515 516 517 518 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {519 self.consume_store_reads_and_writes(1, 1)?;520521 check_is_owner_or_admin(caller, self)?;522 let permissions = CollectionPermissions {523 access: Some(match mode {524 0 => AccessMode::Normal,525 1 => AccessMode::AllowList,526 _ => return Err("not supported access mode".into()),527 }),528 ..Default::default()529 };530 self.collection.permissions = <Pallet<T>>::clamp_permissions(531 self.collection.mode.clone(),532 &self.collection.permissions,533 permissions,534 )535 .map_err(dispatch_to_evm::<T>)?;536537 save(self)538 }539540 541 542 543 fn allowed(&self, user: address) -> Result<bool> {544 Ok(Pallet::<T>::allowed(545 self.id,546 T::CrossAccountId::from_eth(user),547 ))548 }549550 551 552 553 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {554 self.consume_store_writes(1)?;555556 let caller = T::CrossAccountId::from_eth(caller);557 let user = T::CrossAccountId::from_eth(user);558 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;559 Ok(())560 }561562 563 564 565 fn add_to_collection_allow_list_cross(566 &mut self,567 caller: caller,568 user: (address, uint256),569 ) -> Result<void> {570 self.consume_store_writes(1)?;571572 let caller = T::CrossAccountId::from_eth(caller);573 let user = convert_tuple_to_cross_account::<T>(user)?;574 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;575 Ok(())576 }577578 579 580 581 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {582 self.consume_store_writes(1)?;583584 let caller = T::CrossAccountId::from_eth(caller);585 let user = T::CrossAccountId::from_eth(user);586 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;587 Ok(())588 }589590 591 592 593 fn remove_from_collection_allow_list_cross(594 &mut self,595 caller: caller,596 user: (address, uint256),597 ) -> Result<void> {598 self.consume_store_writes(1)?;599600 let caller = T::CrossAccountId::from_eth(caller);601 let user = convert_tuple_to_cross_account::<T>(user)?;602 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;603 Ok(())604 }605606 607 608 609 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {610 self.consume_store_reads_and_writes(1, 1)?;611612 check_is_owner_or_admin(caller, self)?;613 let permissions = CollectionPermissions {614 mint_mode: Some(mode),615 ..Default::default()616 };617 self.collection.permissions = <Pallet<T>>::clamp_permissions(618 self.collection.mode.clone(),619 &self.collection.permissions,620 permissions,621 )622 .map_err(dispatch_to_evm::<T>)?;623624 save(self)625 }626627 628 629 630 631 #[solidity(rename_selector = "isOwnerOrAdmin")]632 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {633 let user = T::CrossAccountId::from_eth(user);634 Ok(self.is_owner_or_admin(&user))635 }636637 638 639 640 641 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {642 let user = convert_tuple_to_cross_account::<T>(user)?;643 Ok(self.is_owner_or_admin(&user))644 }645646 647 648 649 fn unique_collection_type(&self) -> Result<string> {650 let mode = match self.collection.mode {651 CollectionMode::Fungible(_) => "Fungible",652 CollectionMode::NFT => "NFT",653 CollectionMode::ReFungible => "ReFungible",654 };655 Ok(mode.into())656 }657658 659 660 661 662 fn collection_owner(&self) -> Result<(address, uint256)> {663 Ok(convert_cross_account_to_tuple::<T>(664 &T::CrossAccountId::from_sub(self.owner.clone()),665 ))666 }667668 669 670 671 672 #[solidity(rename_selector = "changeCollectionOwner")]673 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {674 self.consume_store_writes(1)?;675676 let caller = T::CrossAccountId::from_eth(caller);677 let new_owner = T::CrossAccountId::from_eth(new_owner);678 self.set_owner_internal(caller, new_owner)679 .map_err(dispatch_to_evm::<T>)680 }681682 683 684 685 686 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {687 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))688 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))689 .collect();690 Ok(result)691 }692693 694 695 696 697 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {698 self.consume_store_writes(1)?;699700 let caller = T::CrossAccountId::from_eth(caller);701 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;702 self.set_owner_internal(caller, new_owner)703 .map_err(dispatch_to_evm::<T>)704 }705}706707708709fn check_is_owner_or_admin<T: Config>(710 caller: caller,711 collection: &CollectionHandle<T>,712) -> Result<T::CrossAccountId> {713 let caller = T::CrossAccountId::from_eth(caller);714 collection715 .check_is_owner_or_admin(&caller)716 .map_err(dispatch_to_evm::<T>)?;717 Ok(caller)718}719720721722fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {723 collection724 .check_is_internal()725 .map_err(dispatch_to_evm::<T>)?;726 collection.save().map_err(dispatch_to_evm::<T>)?;727 Ok(())728}729730731pub mod static_property {732 use evm_coder::{733 execution::{Result, Error},734 };735 use alloc::format;736737 const EXPECT_CONVERT_ERROR: &str = "length < limit";738739 740 pub mod key {741 use super::*;742743 744 pub fn base_uri() -> up_data_structs::PropertyKey {745 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)746 }747748 749 pub fn url() -> up_data_structs::PropertyKey {750 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)751 }752753 754 pub fn suffix() -> up_data_structs::PropertyKey {755 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)756 }757758 759 pub fn parent_nft() -> up_data_structs::PropertyKey {760 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)761 }762 }763764 765 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {766 bytes.to_vec().try_into().map_err(|_| {767 Error::Revert(format!(768 "Property key is too long. Max length is {}.",769 up_data_structs::PropertyKey::bound()770 ))771 })772 }773774 775 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {776 bytes.to_vec().try_into().map_err(|_| {777 Error::Revert(format!(778 "Property key is too long. Max length is {}.",779 up_data_structs::PropertyKey::bound()780 ))781 })782 }783}