12345678910111213141516171819use evm_coder::{20 solidity_interface, solidity, ToLog,21 types::*,22 execution::{Result, Error},23};24pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_std::vec::Vec;27use up_data_structs::{28 AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,29 SponsoringRateLimit, SponsorshipState,30};31use alloc::format;3233use crate::{34 Pallet, CollectionHandle, Config, CollectionProperties,35 eth::{36 convert_cross_account_to_uint256, convert_uint256_to_cross_account,37 convert_cross_account_to_tuple,38 },39};404142#[derive(ToLog)]43pub enum CollectionHelpersEvents {44 45 CollectionCreated {46 47 #[indexed]48 owner: address,4950 51 #[indexed]52 collection_id: address,53 },54}55565758pub trait CommonEvmHandler {59 const CODE: &'static [u8];6061 62 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;63}646566#[solidity_interface(name = Collection)]67impl<T: Config> CollectionHandle<T>68where69 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,70{71 72 73 74 75 fn set_collection_property(76 &mut self,77 caller: caller,78 key: string,79 value: bytes,80 ) -> Result<void> {81 self.consume_store_reads_and_writes(1, 1)?;8283 let caller = T::CrossAccountId::from_eth(caller);84 let key = <Vec<u8>>::from(key)85 .try_into()86 .map_err(|_| "key too large")?;87 let value = value.try_into().map_err(|_| "value too large")?;8889 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })90 .map_err(dispatch_to_evm::<T>)91 }9293 94 95 96 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {97 self.consume_store_reads_and_writes(1, 1)?;9899 let caller = T::CrossAccountId::from_eth(caller);100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too large")?;103104 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)105 }106107 108 109 110 111 112 113 fn collection_property(&self, key: string) -> Result<bytes> {114 let key = <Vec<u8>>::from(key)115 .try_into()116 .map_err(|_| "key too large")?;117118 let props = <CollectionProperties<T>>::get(self.id);119 let prop = props.get(&key).ok_or("key not found")?;120121 Ok(prop.to_vec())122 }123124 125 126 127 128 129 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {130 self.consume_store_reads_and_writes(1, 1)?;131132 check_is_owner_or_admin(caller, self)?;133134 let sponsor = T::CrossAccountId::from_eth(sponsor);135 self.set_sponsor(sponsor.as_sub().clone())136 .map_err(dispatch_to_evm::<T>)?;137 save(self)138 }139140 141 142 143 144 145 fn set_collection_sponsor_substrate(146 &mut self,147 caller: caller,148 sponsor: uint256,149 ) -> Result<void> {150 self.consume_store_reads_and_writes(1, 1)?;151152 check_is_owner_or_admin(caller, self)?;153154 let sponsor = convert_uint256_to_cross_account::<T>(sponsor);155 self.set_sponsor(sponsor.as_sub().clone())156 .map_err(dispatch_to_evm::<T>)?;157 save(self)158 }159160 161 fn has_collection_pending_sponsor(&self) -> Result<bool> {162 Ok(matches!(163 self.collection.sponsorship,164 SponsorshipState::Unconfirmed(_)165 ))166 }167168 169 170 171 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {172 self.consume_store_writes(1)?;173174 let caller = T::CrossAccountId::from_eth(caller);175 if !self176 .confirm_sponsorship(caller.as_sub())177 .map_err(dispatch_to_evm::<T>)?178 {179 return Err("caller is not set as sponsor".into());180 }181 save(self)182 }183184 185 fn remove_collection_sponsor(&mut self, caller: caller) -> Result<void> {186 self.consume_store_reads_and_writes(1, 1)?;187 check_is_owner_or_admin(caller, self)?;188 self.remove_sponsor().map_err(dispatch_to_evm::<T>)?;189 save(self)190 }191192 193 194 195 fn get_collection_sponsor(&self) -> Result<(address, uint256)> {196 let sponsor = match self.collection.sponsorship.sponsor() {197 Some(sponsor) => sponsor,198 None => return Ok(Default::default()),199 };200 let sponsor = T::CrossAccountId::from_sub(sponsor.clone());201 let result: (address, uint256) = if sponsor.is_canonical_substrate() {202 let sponsor = convert_cross_account_to_uint256::<T>(&sponsor);203 (Default::default(), sponsor)204 } else {205 let sponsor = *sponsor.as_eth();206 (sponsor, Default::default())207 };208 Ok(result)209 }210211 212 213 214 215 216 217 218 219 220 221 #[solidity(rename_selector = "setCollectionLimit")]222 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {223 self.consume_store_reads_and_writes(1, 1)?;224225 check_is_owner_or_admin(caller, self)?;226 let mut limits = self.limits.clone();227228 match limit.as_str() {229 "accountTokenOwnershipLimit" => {230 limits.account_token_ownership_limit = Some(value);231 }232 "sponsoredDataSize" => {233 limits.sponsored_data_size = Some(value);234 }235 "sponsoredDataRateLimit" => {236 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));237 }238 "tokenLimit" => {239 limits.token_limit = Some(value);240 }241 "sponsorTransferTimeout" => {242 limits.sponsor_transfer_timeout = Some(value);243 }244 "sponsorApproveTimeout" => {245 limits.sponsor_approve_timeout = Some(value);246 }247 _ => {248 return Err(Error::Revert(format!(249 "unknown integer limit \"{}\"",250 limit251 )))252 }253 }254 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)255 .map_err(dispatch_to_evm::<T>)?;256 save(self)257 }258259 260 261 262 263 264 265 266 #[solidity(rename_selector = "setCollectionLimit")]267 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {268 self.consume_store_reads_and_writes(1, 1)?;269270 check_is_owner_or_admin(caller, self)?;271 let mut limits = self.limits.clone();272273 match limit.as_str() {274 "ownerCanTransfer" => {275 limits.owner_can_transfer = Some(value);276 }277 "ownerCanDestroy" => {278 limits.owner_can_destroy = Some(value);279 }280 "transfersEnabled" => {281 limits.transfers_enabled = Some(value);282 }283 _ => {284 return Err(Error::Revert(format!(285 "unknown boolean limit \"{}\"",286 limit287 )))288 }289 }290 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)291 .map_err(dispatch_to_evm::<T>)?;292 save(self)293 }294295 296 fn contract_address(&self) -> Result<address> {297 Ok(crate::eth::collection_id_to_address(self.id))298 }299300 301 302 fn add_collection_admin_substrate(303 &mut self,304 caller: caller,305 new_admin: uint256,306 ) -> Result<void> {307 self.consume_store_writes(2)?;308309 let caller = T::CrossAccountId::from_eth(caller);310 let new_admin = convert_uint256_to_cross_account::<T>(new_admin);311 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;312 Ok(())313 }314315 316 317 fn remove_collection_admin_substrate(318 &mut self,319 caller: caller,320 admin: uint256,321 ) -> Result<void> {322 self.consume_store_writes(2)?;323324 let caller = T::CrossAccountId::from_eth(caller);325 let admin = convert_uint256_to_cross_account::<T>(admin);326 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;327 Ok(())328 }329330 331 332 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {333 self.consume_store_writes(2)?;334335 let caller = T::CrossAccountId::from_eth(caller);336 let new_admin = T::CrossAccountId::from_eth(new_admin);337 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;338 Ok(())339 }340341 342 343 344 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {345 self.consume_store_writes(2)?;346347 let caller = T::CrossAccountId::from_eth(caller);348 let admin = T::CrossAccountId::from_eth(admin);349 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;350 Ok(())351 }352353 354 355 356 #[solidity(rename_selector = "setCollectionNesting")]357 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {358 self.consume_store_reads_and_writes(1, 1)?;359360 check_is_owner_or_admin(caller, self)?;361362 let mut permissions = self.collection.permissions.clone();363 let mut nesting = permissions.nesting().clone();364 nesting.token_owner = enable;365 nesting.restricted = None;366 permissions.nesting = Some(nesting);367368 self.collection.permissions = <Pallet<T>>::clamp_permissions(369 self.collection.mode.clone(),370 &self.collection.permissions,371 permissions,372 )373 .map_err(dispatch_to_evm::<T>)?;374375 save(self)376 }377378 379 380 381 382 #[solidity(rename_selector = "setCollectionNesting")]383 fn set_nesting(384 &mut self,385 caller: caller,386 enable: bool,387 collections: Vec<address>,388 ) -> Result<void> {389 self.consume_store_reads_and_writes(1, 1)?;390391 if collections.is_empty() {392 return Err("no addresses provided".into());393 }394 check_is_owner_or_admin(caller, self)?;395396 let mut permissions = self.collection.permissions.clone();397 match enable {398 false => {399 let mut nesting = permissions.nesting().clone();400 nesting.token_owner = false;401 nesting.restricted = None;402 permissions.nesting = Some(nesting);403 }404 true => {405 let mut bv = OwnerRestrictedSet::new();406 for i in collections {407 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(408 "Can't convert address into collection id".into(),409 ))?)410 .map_err(|_| "too many collections")?;411 }412 let mut nesting = permissions.nesting().clone();413 nesting.token_owner = true;414 nesting.restricted = Some(bv);415 permissions.nesting = Some(nesting);416 }417 };418419 self.collection.permissions = <Pallet<T>>::clamp_permissions(420 self.collection.mode.clone(),421 &self.collection.permissions,422 permissions,423 )424 .map_err(dispatch_to_evm::<T>)?;425426 save(self)427 }428429 430 431 432 433 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {434 self.consume_store_reads_and_writes(1, 1)?;435436 check_is_owner_or_admin(caller, self)?;437 let permissions = CollectionPermissions {438 access: Some(match mode {439 0 => AccessMode::Normal,440 1 => AccessMode::AllowList,441 _ => return Err("not supported access mode".into()),442 }),443 ..Default::default()444 };445 self.collection.permissions = <Pallet<T>>::clamp_permissions(446 self.collection.mode.clone(),447 &self.collection.permissions,448 permissions,449 )450 .map_err(dispatch_to_evm::<T>)?;451452 save(self)453 }454455 456 457 458 fn allowed(&self, user: address) -> Result<bool> {459 Ok(Pallet::<T>::allowed(460 self.id,461 T::CrossAccountId::from_eth(user),462 ))463 }464465 466 467 468 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {469 self.consume_store_writes(1)?;470471 let caller = T::CrossAccountId::from_eth(caller);472 let user = T::CrossAccountId::from_eth(user);473 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;474 Ok(())475 }476477 478 479 480 fn add_to_collection_allow_list_substrate(481 &mut self,482 caller: caller,483 user: uint256,484 ) -> Result<void> {485 self.consume_store_writes(1)?;486487 let caller = T::CrossAccountId::from_eth(caller);488 let user = convert_uint256_to_cross_account::<T>(user);489 Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;490 Ok(())491 }492493 494 495 496 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {497 self.consume_store_writes(1)?;498499 let caller = T::CrossAccountId::from_eth(caller);500 let user = T::CrossAccountId::from_eth(user);501 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;502 Ok(())503 }504505 506 507 508 fn remove_from_collection_allow_list_substrate(509 &mut self,510 caller: caller,511 user: uint256,512 ) -> Result<void> {513 self.consume_store_writes(1)?;514515 let caller = T::CrossAccountId::from_eth(caller);516 let user = convert_uint256_to_cross_account::<T>(user);517 Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;518 Ok(())519 }520521 522 523 524 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {525 self.consume_store_reads_and_writes(1, 1)?;526527 check_is_owner_or_admin(caller, self)?;528 let permissions = CollectionPermissions {529 mint_mode: Some(mode),530 ..Default::default()531 };532 self.collection.permissions = <Pallet<T>>::clamp_permissions(533 self.collection.mode.clone(),534 &self.collection.permissions,535 permissions,536 )537 .map_err(dispatch_to_evm::<T>)?;538539 save(self)540 }541542 543 544 545 546 #[solidity(rename_selector = "isOwnerOrAdmin")]547 fn is_owner_or_admin_eth(&self, user: address) -> Result<bool> {548 let user = T::CrossAccountId::from_eth(user);549 Ok(self.is_owner_or_admin(&user))550 }551552 553 554 555 556 fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {557 let user = convert_uint256_to_cross_account::<T>(user);558 Ok(self.is_owner_or_admin(&user))559 }560561 562 563 564 fn unique_collection_type(&self) -> Result<string> {565 let mode = match self.collection.mode {566 CollectionMode::Fungible(_) => "Fungible",567 CollectionMode::NFT => "NFT",568 CollectionMode::ReFungible => "ReFungible",569 };570 Ok(mode.into())571 }572573 574 575 576 577 fn collection_owner(&self) -> Result<(address, uint256)> {578 Ok(convert_cross_account_to_tuple::<T>(579 &T::CrossAccountId::from_sub(self.owner.clone()),580 ))581 }582583 584 585 586 587 fn set_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {588 self.consume_store_writes(1)?;589590 let caller = T::CrossAccountId::from_eth(caller);591 let new_owner = T::CrossAccountId::from_eth(new_owner);592 self.set_owner_internal(caller, new_owner)593 .map_err(dispatch_to_evm::<T>)594 }595596 597 598 599 600 fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {601 self.consume_store_writes(1)?;602603 let caller = T::CrossAccountId::from_eth(caller);604 let new_owner = convert_uint256_to_cross_account::<T>(new_owner);605 self.set_owner_internal(caller, new_owner)606 .map_err(dispatch_to_evm::<T>)607 }608609 610 611 612 613 614 615 616}617618619620fn check_is_owner_or_admin<T: Config>(621 caller: caller,622 collection: &CollectionHandle<T>,623) -> Result<T::CrossAccountId> {624 let caller = T::CrossAccountId::from_eth(caller);625 collection626 .check_is_owner_or_admin(&caller)627 .map_err(dispatch_to_evm::<T>)?;628 Ok(caller)629}630631632633fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {634 collection635 .check_is_internal()636 .map_err(dispatch_to_evm::<T>)?;637 collection.save().map_err(dispatch_to_evm::<T>)?;638 Ok(())639}640641642pub mod static_property {643 use evm_coder::{644 execution::{Result, Error},645 };646 use alloc::format;647648 const EXPECT_CONVERT_ERROR: &str = "length < limit";649650 651 pub mod key {652 use super::*;653654 655 pub fn schema_name() -> up_data_structs::PropertyKey {656 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)657 }658659 660 pub fn base_uri() -> up_data_structs::PropertyKey {661 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)662 }663664 665 pub fn url() -> up_data_structs::PropertyKey {666 property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)667 }668669 670 pub fn suffix() -> up_data_structs::PropertyKey {671 property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)672 }673674 675 pub fn parent_nft() -> up_data_structs::PropertyKey {676 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)677 }678 }679680 681 pub mod value {682 use super::*;683684 685 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";686687 688 pub fn erc721() -> up_data_structs::PropertyValue {689 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)690 }691 }692693 694 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {695 bytes.to_vec().try_into().map_err(|_| {696 Error::Revert(format!(697 "Property key is too long. Max length is {}.",698 up_data_structs::PropertyKey::bound()699 ))700 })701 }702703 704 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {705 bytes.to_vec().try_into().map_err(|_| {706 Error::Revert(format!(707 "Property key is too long. Max length is {}.",708 up_data_structs::PropertyKey::bound()709 ))710 })711 }712}