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 #[solidity(rename_selector = "changeCollectionOwner")]596 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {597 self.consume_store_writes(1)?;598599 let caller = T::CrossAccountId::from_eth(caller);600 let new_owner = T::CrossAccountId::from_eth(new_owner);601 self.set_owner_internal(caller, new_owner)602 .map_err(dispatch_to_evm::<T>)603 }604605 606 607 608 609 610 611 612613 614 615 616 617 618619 620 621 622 623 624 625 626}627628629630fn check_is_owner_or_admin<T: Config>(631 caller: caller,632 collection: &CollectionHandle<T>,633) -> Result<T::CrossAccountId> {634 let caller = T::CrossAccountId::from_eth(caller);635 collection636 .check_is_owner_or_admin(&caller)637 .map_err(dispatch_to_evm::<T>)?;638 Ok(caller)639}640641642643fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {644 collection645 .check_is_internal()646 .map_err(dispatch_to_evm::<T>)?;647 collection.save().map_err(dispatch_to_evm::<T>)?;648 Ok(())649}650651652pub mod static_property {653 use evm_coder::{654 execution::{Result, Error},655 };656 use alloc::format;657658 const EXPECT_CONVERT_ERROR: &str = "length < limit";659660 661 pub mod key {662 use super::*;663664 665 pub fn base_uri() -> up_data_structs::PropertyKey {666 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)667 }668669 670 pub fn url() -> up_data_structs::PropertyKey {671 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)672 }673674 675 pub fn suffix() -> up_data_structs::PropertyKey {676 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)677 }678679 680 pub fn parent_nft() -> up_data_structs::PropertyKey {681 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)682 }683 }684685 686 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {687 bytes.to_vec().try_into().map_err(|_| {688 Error::Revert(format!(689 "Property key is too long. Max length is {}.",690 up_data_structs::PropertyKey::bound()691 ))692 })693 }694695 696 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {697 bytes.to_vec().try_into().map_err(|_| {698 Error::Revert(format!(699 "Property key is too long. Max length is {}.",700 up_data_structs::PropertyKey::bound()701 ))702 })703 }704}