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 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>>::delete_collection_properties(1))]104 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {105 self.consume_store_reads_and_writes(1, 1)?;106107 let caller = T::CrossAccountId::from_eth(caller);108 let key = <Vec<u8>>::from(key)109 .try_into()110 .map_err(|_| "key too large")?;111112 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)113 }114115 116 117 118 119 120 121 fn collection_property(&self, key: string) -> Result<bytes> {122 let key = <Vec<u8>>::from(key)123 .try_into()124 .map_err(|_| "key too large")?;125126 let props = <CollectionProperties<T>>::get(self.id);127 let prop = props.get(&key).ok_or("key not found")?;128129 Ok(bytes(prop.to_vec()))130 }131132 133 134 135 136 137 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {138 self.consume_store_reads_and_writes(1, 1)?;139140 check_is_owner_or_admin(caller, self)?;141142 let sponsor = T::CrossAccountId::from_eth(sponsor);143 self.set_sponsor(sponsor.as_sub().clone())144 .map_err(dispatch_to_evm::<T>)?;145 save(self)146 }147148 149 150 151 152 153 fn set_collection_sponsor_cross(154 &mut self,155 caller: caller,156 sponsor: (address, uint256),157 ) -> Result<void> {158 self.consume_store_reads_and_writes(1, 1)?;159160 check_is_owner_or_admin(caller, self)?;161162 let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;163 self.set_sponsor(sponsor.as_sub().clone())164 .map_err(dispatch_to_evm::<T>)?;165 save(self)166 }167168 169 fn has_collection_pending_sponsor(&self) -> Result<bool> {170 Ok(matches!(171 self.collection.sponsorship,172 SponsorshipState::Unconfirmed(_)173 ))174 }175176 177 178 179 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {180 self.consume_store_writes(1)?;181182 let caller = T::CrossAccountId::from_eth(caller);183 if !self184 .confirm_sponsorship(caller.as_sub())185 .map_err(dispatch_to_evm::<T>)?186 {187 return Err("caller is not set as sponsor".into());188 }189 save(self)190 }191192 193 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {194 self.consume_store_reads_and_writes(1, 1)?;195 check_is_owner_or_admin(caller, self)?;196 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;197 save(self)198 }199200 201 202 203 fn collection_sponsor(&self) -> Result<(address, uint256)> {204 let sponsor = match self.collection.sponsorship.sponsor() {205 Some(sponsor) => sponsor,206 None => return Ok(Default::default()),207 };208 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());209 let result: (address, uint256) = if sponsor.is_canonical_substrate() {210 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);211 (Default::default(), sponsor)212 } else {213 let sponsor = *sponsor.as_eth();214 (sponsor, Default::default())215 };216 Ok(result)217 }218219 220 221 222 223 224 225 226 227 228 229 #[solidity(rename_selector = "setCollectionLimit")]230 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {231 self.consume_store_reads_and_writes(1, 1)?;232233 check_is_owner_or_admin(caller, self)?;234 let mut limits = self.limits.clone();235236 match limit.as_str() {237 "accountTokenOwnershipLimit" => {238 limits.account_token_ownership_limit = Some(value);239 }240 "sponsoredDataSize" => {241 limits.sponsored_data_size = Some(value);242 }243 "sponsoredDataRateLimit" => {244 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));245 }246 "tokenLimit" => {247 limits.token_limit = Some(value);248 }249 "sponsorTransferTimeout" => {250 limits.sponsor_transfer_timeout = Some(value);251 }252 "sponsorApproveTimeout" => {253 limits.sponsor_approve_timeout = Some(value);254 }255 _ => {256 return Err(Error::Revert(format!(257 "unknown integer limit \"{}\"",258 limit259 )))260 }261 }262 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)263 .map_err(dispatch_to_evm::<T>)?;264 save(self)265 }266267 268 269 270 271 272 273 274 #[solidity(rename_selector = "setCollectionLimit")]275 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {276 self.consume_store_reads_and_writes(1, 1)?;277278 check_is_owner_or_admin(caller, self)?;279 let mut limits = self.limits.clone();280281 match limit.as_str() {282 "ownerCanTransfer" => {283 limits.owner_can_transfer = Some(value);284 }285 "ownerCanDestroy" => {286 limits.owner_can_destroy = Some(value);287 }288 "transfersEnabled" => {289 limits.transfers_enabled = Some(value);290 }291 _ => {292 return Err(Error::Revert(format!(293 "unknown boolean limit \"{}\"",294 limit295 )))296 }297 }298 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)299 .map_err(dispatch_to_evm::<T>)?;300 save(self)301 }302303 304 fn contract_address(&self) -> Result<address> {305 Ok(crate::eth::collection_id_to_address(self.id))306 }307308 309 310 fn add_collection_admin_cross(311 &mut self,312 caller: caller,313 new_admin: (address, uint256),314 ) -> Result<void> {315 self.consume_store_writes(2)?;316317 let caller = T::CrossAccountId::from_eth(caller);318 let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;319 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;320 Ok(())321 }322323 324 325 fn remove_collection_admin_cross(326 &mut self,327 caller: caller,328 admin: (address, uint256),329 ) -> Result<void> {330 self.consume_store_writes(2)?;331332 let caller = T::CrossAccountId::from_eth(caller);333 let admin = convert_tuple_to_cross_account::<T>(admin)?;334 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;335 Ok(())336 }337338 339 340 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {341 self.consume_store_writes(2)?;342343 let caller = T::CrossAccountId::from_eth(caller);344 let new_admin = T::CrossAccountId::from_eth(new_admin);345 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;346 Ok(())347 }348349 350 351 352 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {353 self.consume_store_writes(2)?;354355 let caller = T::CrossAccountId::from_eth(caller);356 let admin = T::CrossAccountId::from_eth(admin);357 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;358 Ok(())359 }360361 362 363 364 #[solidity(rename_selector = "setCollectionNesting")]365 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {366 self.consume_store_reads_and_writes(1, 1)?;367368 check_is_owner_or_admin(caller, self)?;369370 let mut permissions = self.collection.permissions.clone();371 let mut nesting = permissions.nesting().clone();372 nesting.token_owner = enable;373 nesting.restricted = None;374 permissions.nesting = Some(nesting);375376 self.collection.permissions = <Pallet<T>>::clamp_permissions(377 self.collection.mode.clone(),378 &self.collection.permissions,379 permissions,380 )381 .map_err(dispatch_to_evm::<T>)?;382383 save(self)384 }385386 387 388 389 390 #[solidity(rename_selector = "setCollectionNesting")]391 fn set_nesting(392 &mut self,393 caller: caller,394 enable: bool,395 collections: Vec<address>,396 ) -> Result<void> {397 self.consume_store_reads_and_writes(1, 1)?;398399 if collections.is_empty() {400 return Err("no addresses provided".into());401 }402 check_is_owner_or_admin(caller, self)?;403404 let mut permissions = self.collection.permissions.clone();405 match enable {406 false => {407 let mut nesting = permissions.nesting().clone();408 nesting.token_owner = false;409 nesting.restricted = None;410 permissions.nesting = Some(nesting);411 }412 true => {413 let mut bv = OwnerRestrictedSet::new();414 for i in collections {415 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| {416 Error::Revert("Can't convert address into collection id".into())417 })?)418 .map_err(|_| "too many collections")?;419 }420 let mut nesting = permissions.nesting().clone();421 nesting.token_owner = true;422 nesting.restricted = Some(bv);423 permissions.nesting = Some(nesting);424 }425 };426427 self.collection.permissions = <Pallet<T>>::clamp_permissions(428 self.collection.mode.clone(),429 &self.collection.permissions,430 permissions,431 )432 .map_err(dispatch_to_evm::<T>)?;433434 save(self)435 }436437 438 439 440 441 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {442 self.consume_store_reads_and_writes(1, 1)?;443444 check_is_owner_or_admin(caller, self)?;445 let permissions = CollectionPermissions {446 access: Some(match mode {447 0 => AccessMode::Normal,448 1 => AccessMode::AllowList,449 _ => return Err("not supported access mode".into()),450 }),451 ..Default::default()452 };453 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 fn allowed(&self, user: address) -> Result<bool> {467 Ok(Pallet::<T>::allowed(468 self.id,469 T::CrossAccountId::from_eth(user),470 ))471 }472473 474 475 476 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {477 self.consume_store_writes(1)?;478479 let caller = T::CrossAccountId::from_eth(caller);480 let user = T::CrossAccountId::from_eth(user);481 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;482 Ok(())483 }484485 486 487 488 fn add_to_collection_allow_list_cross(489 &mut self,490 caller: caller,491 user: (address, uint256),492 ) -> Result<void> {493 self.consume_store_writes(1)?;494495 let caller = T::CrossAccountId::from_eth(caller);496 let user = convert_tuple_to_cross_account::<T>(user)?;497 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;498 Ok(())499 }500501 502 503 504 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {505 self.consume_store_writes(1)?;506507 let caller = T::CrossAccountId::from_eth(caller);508 let user = T::CrossAccountId::from_eth(user);509 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;510 Ok(())511 }512513 514 515 516 fn remove_from_collection_allow_list_cross(517 &mut self,518 caller: caller,519 user: (address, uint256),520 ) -> Result<void> {521 self.consume_store_writes(1)?;522523 let caller = T::CrossAccountId::from_eth(caller);524 let user = convert_tuple_to_cross_account::<T>(user)?;525 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;526 Ok(())527 }528529 530 531 532 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {533 self.consume_store_reads_and_writes(1, 1)?;534535 check_is_owner_or_admin(caller, self)?;536 let permissions = CollectionPermissions {537 mint_mode: Some(mode),538 ..Default::default()539 };540 self.collection.permissions = <Pallet<T>>::clamp_permissions(541 self.collection.mode.clone(),542 &self.collection.permissions,543 permissions,544 )545 .map_err(dispatch_to_evm::<T>)?;546547 save(self)548 }549550 551 552 553 554 #[solidity(rename_selector = "isOwnerOrAdmin")]555 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {556 let user = T::CrossAccountId::from_eth(user);557 Ok(self.is_owner_or_admin(&user))558 }559560 561 562 563 564 fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {565 let user = convert_tuple_to_cross_account::<T>(user)?;566 Ok(self.is_owner_or_admin(&user))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 fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {610 let result = crate::IsAdmin::<T>::iter_prefix((self.id,))611 .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))612 .collect();613 Ok(result)614 }615616 617 618 619 620 fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {621 self.consume_store_writes(1)?;622623 let caller = T::CrossAccountId::from_eth(caller);624 let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;625 self.set_owner_internal(caller, new_owner)626 .map_err(dispatch_to_evm::<T>)627 }628}629630631632fn check_is_owner_or_admin<T: Config>(633 caller: caller,634 collection: &CollectionHandle<T>,635) -> Result<T::CrossAccountId> {636 let caller = T::CrossAccountId::from_eth(caller);637 collection638 .check_is_owner_or_admin(&caller)639 .map_err(dispatch_to_evm::<T>)?;640 Ok(caller)641}642643644645fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {646 collection647 .check_is_internal()648 .map_err(dispatch_to_evm::<T>)?;649 collection.save().map_err(dispatch_to_evm::<T>)?;650 Ok(())651}652653654pub mod static_property {655 use evm_coder::{656 execution::{Result, Error},657 };658 use alloc::format;659660 const EXPECT_CONVERT_ERROR: &str = "length < limit";661662 663 pub mod key {664 use super::*;665666 667 pub fn base_uri() -> up_data_structs::PropertyKey {668 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)669 }670671 672 pub fn url() -> up_data_structs::PropertyKey {673 property_key_from_bytes(b"URI").expect(EXPECT_CONVERT_ERROR)674 }675676 677 pub fn suffix() -> up_data_structs::PropertyKey {678 property_key_from_bytes(b"URISuffix").expect(EXPECT_CONVERT_ERROR)679 }680681 682 pub fn parent_nft() -> up_data_structs::PropertyKey {683 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)684 }685 }686687 688 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {689 bytes.to_vec().try_into().map_err(|_| {690 Error::Revert(format!(691 "Property key is too long. Max length is {}.",692 up_data_structs::PropertyKey::bound()693 ))694 })695 }696697 698 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {699 bytes.to_vec().try_into().map_err(|_| {700 Error::Revert(format!(701 "Property key is too long. Max length is {}.",702 up_data_structs::PropertyKey::bound()703 ))704 })705 }706}