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_uint256_to_cross_account,38 convert_cross_account_to_tuple,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}57585960pub trait CommonEvmHandler {61 const CODE: &'static [u8];6263 64 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;65}666768#[solidity_interface(name = Collection)]69impl<T: Config> CollectionHandle<T>70where71 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,72{73 74 75 76 77 #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]78 fn set_collection_property(79 &mut self,80 caller: caller,81 key: string,82 value: bytes,83 ) -> Result<void> {84 let caller = T::CrossAccountId::from_eth(caller);85 let key = <Vec<u8>>::from(key)86 .try_into()87 .map_err(|_| "key too large")?;88 let value = value.try_into().map_err(|_| "value too large")?;8990 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })91 .map_err(dispatch_to_evm::<T>)92 }9394 95 96 97 #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]98 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {99 self.consume_store_reads_and_writes(1, 1)?;100101 let caller = T::CrossAccountId::from_eth(caller);102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too large")?;105106 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)107 }108109 110 111 112 113 114 115 fn collection_property(&self, key: string) -> Result<bytes> {116 let key = <Vec<u8>>::from(key)117 .try_into()118 .map_err(|_| "key too large")?;119120 let props = <CollectionProperties<T>>::get(self.id);121 let prop = props.get(&key).ok_or("key not found")?;122123 Ok(prop.to_vec())124 }125126 127 128 129 130 131 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {132 self.consume_store_reads_and_writes(1, 1)?;133134 check_is_owner_or_admin(caller, self)?;135136 let sponsor = T::CrossAccountId::from_eth(sponsor);137 self.set_sponsor(sponsor.as_sub().clone())138 .map_err(dispatch_to_evm::<T>)?;139 save(self)140 }141142 143 144 145 146 147 148 149 150 151 152 153 154155 156157 158 159 160 161 162163 164 fn has_collection_pending_sponsor(&self) -> Result<bool> {165 Ok(matches!(166 self.collection.sponsorship,167 SponsorshipState::Unconfirmed(_)168 ))169 }170171 172 173 174 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {175 self.consume_store_writes(1)?;176177 let caller = T::CrossAccountId::from_eth(caller);178 if !self179 .confirm_sponsorship(caller.as_sub())180 .map_err(dispatch_to_evm::<T>)?181 {182 return Err("caller is not set as sponsor".into());183 }184 save(self)185 }186187 188 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {189 self.consume_store_reads_and_writes(1, 1)?;190 check_is_owner_or_admin(caller, self)?;191 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;192 save(self)193 }194195 196 197 198 fn collection_sponsor(&self) -> Result<(address, uint256)> {199 let sponsor = match self.collection.sponsorship.sponsor() {200 Some(sponsor) => sponsor,201 None => return Ok(Default::default()),202 };203 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());204 let result: (address, uint256) = if sponsor.is_canonical_substrate() {205 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);206 (Default::default(), sponsor)207 } else {208 let sponsor = *sponsor.as_eth();209 (sponsor, Default::default())210 };211 Ok(result)212 }213214 215 216 217 218 219 220 221 222 223 224 #[solidity(rename_selector = "setCollectionLimit")]225 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {226 self.consume_store_reads_and_writes(1, 1)?;227228 check_is_owner_or_admin(caller, self)?;229 let mut limits = self.limits.clone();230231 match limit.as_str() {232 "accountTokenOwnershipLimit" => {233 limits.account_token_ownership_limit = Some(value);234 }235 "sponsoredDataSize" => {236 limits.sponsored_data_size = Some(value);237 }238 "sponsoredDataRateLimit" => {239 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));240 }241 "tokenLimit" => {242 limits.token_limit = Some(value);243 }244 "sponsorTransferTimeout" => {245 limits.sponsor_transfer_timeout = Some(value);246 }247 "sponsorApproveTimeout" => {248 limits.sponsor_approve_timeout = Some(value);249 }250 _ => {251 return Err(Error::Revert(format!(252 "unknown integer limit \"{}\"",253 limit254 )))255 }256 }257 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)258 .map_err(dispatch_to_evm::<T>)?;259 save(self)260 }261262 263 264 265 266 267 268 269 #[solidity(rename_selector = "setCollectionLimit")]270 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {271 self.consume_store_reads_and_writes(1, 1)?;272273 check_is_owner_or_admin(caller, self)?;274 let mut limits = self.limits.clone();275276 match limit.as_str() {277 "ownerCanTransfer" => {278 limits.owner_can_transfer = Some(value);279 }280 "ownerCanDestroy" => {281 limits.owner_can_destroy = Some(value);282 }283 "transfersEnabled" => {284 limits.transfers_enabled = Some(value);285 }286 _ => {287 return Err(Error::Revert(format!(288 "unknown boolean limit \"{}\"",289 limit290 )))291 }292 }293 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)294 .map_err(dispatch_to_evm::<T>)?;295 save(self)296 }297298 299 fn contract_address(&self) -> Result<address> {300 Ok(crate::eth::collection_id_to_address(self.id))301 }302303 304 305 306 307 308 309 310 311 312313 314 315 316 317 318319 320 321 322 323 324 325 326 327 328329 330 331 332 333 334335 336 337 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {338 self.consume_store_writes(2)?;339340 let caller = T::CrossAccountId::from_eth(caller);341 let new_admin = T::CrossAccountId::from_eth(new_admin);342 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;343 Ok(())344 }345346 347 348 349 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {350 self.consume_store_writes(2)?;351352 let caller = T::CrossAccountId::from_eth(caller);353 let admin = T::CrossAccountId::from_eth(admin);354 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;355 Ok(())356 }357358 359 360 361 #[solidity(rename_selector = "setCollectionNesting")]362 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {363 self.consume_store_reads_and_writes(1, 1)?;364365 check_is_owner_or_admin(caller, self)?;366367 let mut permissions = self.collection.permissions.clone();368 let mut nesting = permissions.nesting().clone();369 nesting.token_owner = enable;370 nesting.restricted = None;371 permissions.nesting = Some(nesting);372373 self.collection.permissions = <Pallet<T>>::clamp_permissions(374 self.collection.mode.clone(),375 &self.collection.permissions,376 permissions,377 )378 .map_err(dispatch_to_evm::<T>)?;379380 save(self)381 }382383 384 385 386 387 #[solidity(rename_selector = "setCollectionNesting")]388 fn set_nesting(389 &mut self,390 caller: caller,391 enable: bool,392 collections: Vec<address>,393 ) -> Result<void> {394 self.consume_store_reads_and_writes(1, 1)?;395396 if collections.is_empty() {397 return Err("no addresses provided".into());398 }399 check_is_owner_or_admin(caller, self)?;400401 let mut permissions = self.collection.permissions.clone();402 match enable {403 false => {404 let mut nesting = permissions.nesting().clone();405 nesting.token_owner = false;406 nesting.restricted = None;407 permissions.nesting = Some(nesting);408 }409 true => {410 let mut bv = OwnerRestrictedSet::new();411 for i in collections {412 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {413 Error::Revert("Can't convert address into collection id".into())414 })?)415 .map_err(|_| "too many collections")?;416 }417 let mut nesting = permissions.nesting().clone();418 nesting.token_owner = true;419 nesting.restricted = Some(bv);420 permissions.nesting = Some(nesting);421 }422 };423424 self.collection.permissions = <Pallet<T>>::clamp_permissions(425 self.collection.mode.clone(),426 &self.collection.permissions,427 permissions,428 )429 .map_err(dispatch_to_evm::<T>)?;430431 save(self)432 }433434 435 436 437 438 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {439 self.consume_store_reads_and_writes(1, 1)?;440441 check_is_owner_or_admin(caller, self)?;442 let permissions = CollectionPermissions {443 access: Some(match mode {444 0 => AccessMode::Normal,445 1 => AccessMode::AllowList,446 _ => return Err("not supported access mode".into()),447 }),448 ..Default::default()449 };450 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 fn allowed(&self, user: address) -> Result<bool> {464 Ok(Pallet::<T>::allowed(465 self.id,466 T::CrossAccountId::from_eth(user),467 ))468 }469470 471 472 473 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {474 self.consume_store_writes(1)?;475476 let caller = T::CrossAccountId::from_eth(caller);477 let user = T::CrossAccountId::from_eth(user);478 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481482 483 484 485 486 487 488 489 490 491 492493 494 495 496 497 498499 500 501 502 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {503 self.consume_store_writes(1)?;504505 let caller = T::CrossAccountId::from_eth(caller);506 let user = T::CrossAccountId::from_eth(user);507 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;508 Ok(())509 }510511 512 513 514 515 516 517 518 519 520 521522 523 524 525 526 527528 529 530 531 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {532 self.consume_store_reads_and_writes(1, 1)?;533534 check_is_owner_or_admin(caller, self)?;535 let permissions = CollectionPermissions {536 mint_mode: Some(mode),537 ..Default::default()538 };539 self.collection.permissions = <Pallet<T>>::clamp_permissions(540 self.collection.mode.clone(),541 &self.collection.permissions,542 permissions,543 )544 .map_err(dispatch_to_evm::<T>)?;545546 save(self)547 }548549 550 551 552 553 #[solidity(rename_selector = "isOwnerOrAdmin")]554 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {555 let user = T::CrossAccountId::from_eth(user);556 Ok(self.is_owner_or_admin(&user))557 }558559 560 561 562 563 564 565 566 567 568569 570 571 572 fn unique_collection_type(&self) -> Result<string> {573 let mode = match self.collection.mode {574 CollectionMode::Fungible(_) => "Fungible",575 CollectionMode::NFT => "NFT",576 CollectionMode::ReFungible => "ReFungible",577 };578 Ok(mode.into())579 }580581 582 583 584 585 fn collection_owner(&self) -> Result<(address, uint256)> {586 Ok(convert_cross_account_to_tuple::<T>(587 &T::CrossAccountId::from_sub(self.owner.clone()),588 ))589 }590591 592 593 594 595 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {596 self.consume_store_writes(1)?;597598 let caller = T::CrossAccountId::from_eth(caller);599 let new_owner = T::CrossAccountId::from_eth(new_owner);600 self.set_owner_internal(caller, new_owner)601 .map_err(dispatch_to_evm::<T>)602 }603604 605 606 607 608 609 610 611612 613 614 615 616 617618 619 620 621 622 623 624 625}626627628629fn check_is_owner_or_admin<T: Config>(630 caller: caller,631 collection: &CollectionHandle<T>,632) -> Result<T::CrossAccountId> {633 let caller = T::CrossAccountId::from_eth(caller);634 collection635 .check_is_owner_or_admin(&caller)636 .map_err(dispatch_to_evm::<T>)?;637 Ok(caller)638}639640641642fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {643 collection644 .check_is_internal()645 .map_err(dispatch_to_evm::<T>)?;646 collection.save().map_err(dispatch_to_evm::<T>)?;647 Ok(())648}649650651pub mod static_property {652 use evm_coder::{653 execution::{Result, Error},654 };655 use alloc::format;656657 const EXPECT_CONVERT_ERROR: &str = "length < limit";658659 660 pub mod key {661 use super::*;662663 664 pub fn schema_name() -> up_data_structs::PropertyKey {665 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)666 }667668 669 pub fn schema_version() -> up_data_structs::PropertyKey {670 property_key_from_bytes(b"schemaVersion").expect(EXPECT_CONVERT_ERROR)671 }672673 674 pub fn base_uri() -> up_data_structs::PropertyKey {675 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)676 }677678 679 pub fn url() -> up_data_structs::PropertyKey {680 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)681 }682683 684 pub fn suffix() -> up_data_structs::PropertyKey {685 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)686 }687688 689 pub fn parent_nft() -> up_data_structs::PropertyKey {690 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)691 }692693 694 pub fn erc721_metadata() -> up_data_structs::PropertyKey {695 property_key_from_bytes(b"ERC721Metadata").expect(EXPECT_CONVERT_ERROR)696 }697 }698699 700 pub mod value {701 use super::*;702703 704 pub const SCHEMA_VERSION: &[u8] = b"1.0.0";705706 707 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";708709 710 pub const ERC721_METADATA_SUPPORTED: &[u8] = b"1";711712 713 pub fn erc721() -> up_data_structs::PropertyValue {714 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)715 }716717 718 pub fn schema_version() -> up_data_structs::PropertyValue {719 property_value_from_bytes(SCHEMA_VERSION).expect(EXPECT_CONVERT_ERROR)720 }721722 723 pub fn erc721_metadata_supported() -> up_data_structs::PropertyValue {724 property_value_from_bytes(ERC721_METADATA_SUPPORTED).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}