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,30};31use alloc::format;3233use crate::{Pallet, CollectionHandle, Config, CollectionProperties};343536#[derive(ToLog)]37pub enum CollectionHelpersEvents {38 39 CollectionCreated {40 41 #[indexed]42 owner: address,4344 45 #[indexed]46 collection_id: address,47 },48}49505152pub trait CommonEvmHandler {53 const CODE: &'static [u8];5455 56 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;57}585960#[solidity_interface(name = "Collection")]61impl<T: Config> CollectionHandle<T>62where63 T::AccountId: From<[u8; 32]>,64{65 66 67 68 69 fn set_collection_property(70 &mut self,71 caller: caller,72 key: string,73 value: bytes,74 ) -> Result<void> {75 let caller = T::CrossAccountId::from_eth(caller);76 let key = <Vec<u8>>::from(key)77 .try_into()78 .map_err(|_| "key too large")?;79 let value = value.try_into().map_err(|_| "value too large")?;8081 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })82 .map_err(dispatch_to_evm::<T>)83 }8485 86 87 88 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {89 let caller = T::CrossAccountId::from_eth(caller);90 let key = <Vec<u8>>::from(key)91 .try_into()92 .map_err(|_| "key too large")?;9394 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)95 }9697 98 99 100 101 102 103 fn collection_property(&self, key: string) -> Result<bytes> {104 let key = <Vec<u8>>::from(key)105 .try_into()106 .map_err(|_| "key too large")?;107108 let props = <CollectionProperties<T>>::get(self.id);109 let prop = props.get(&key).ok_or("key not found")?;110111 Ok(prop.to_vec())112 }113114 115 116 117 118 119 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {120 check_is_owner_or_admin(caller, self)?;121122 let sponsor = T::CrossAccountId::from_eth(sponsor);123 self.set_sponsor(sponsor.as_sub().clone())124 .map_err(dispatch_to_evm::<T>)?;125 save(self)126 }127128 129 130 131 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {132 let caller = T::CrossAccountId::from_eth(caller);133 if !self134 .confirm_sponsorship(caller.as_sub())135 .map_err(dispatch_to_evm::<T>)?136 {137 return Err("caller is not set as sponsor".into());138 }139 save(self)140 }141142 143 144 145 146 147 148 149 150 151 152 #[solidity(rename_selector = "setCollectionLimit")]153 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {154 check_is_owner_or_admin(caller, self)?;155 let mut limits = self.limits.clone();156157 match limit.as_str() {158 "accountTokenOwnershipLimit" => {159 limits.account_token_ownership_limit = Some(value);160 }161 "sponsoredDataSize" => {162 limits.sponsored_data_size = Some(value);163 }164 "sponsoredDataRateLimit" => {165 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));166 }167 "tokenLimit" => {168 limits.token_limit = Some(value);169 }170 "sponsorTransferTimeout" => {171 limits.sponsor_transfer_timeout = Some(value);172 }173 "sponsorApproveTimeout" => {174 limits.sponsor_approve_timeout = Some(value);175 }176 _ => {177 return Err(Error::Revert(format!(178 "unknown integer limit \"{}\"",179 limit180 )))181 }182 }183 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)184 .map_err(dispatch_to_evm::<T>)?;185 save(self)186 }187188 189 190 191 192 193 194 195 #[solidity(rename_selector = "setCollectionLimit")]196 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {197 check_is_owner_or_admin(caller, self)?;198 let mut limits = self.limits.clone();199200 match limit.as_str() {201 "ownerCanTransfer" => {202 limits.owner_can_transfer = Some(value);203 }204 "ownerCanDestroy" => {205 limits.owner_can_destroy = Some(value);206 }207 "transfersEnabled" => {208 limits.transfers_enabled = Some(value);209 }210 _ => {211 return Err(Error::Revert(format!(212 "unknown boolean limit \"{}\"",213 limit214 )))215 }216 }217 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)218 .map_err(dispatch_to_evm::<T>)?;219 save(self)220 }221222 223 fn contract_address(&self, _caller: caller) -> Result<address> {224 Ok(crate::eth::collection_id_to_address(self.id))225 }226227 228 229 fn add_collection_admin_substrate(230 &mut self,231 caller: caller,232 new_admin: uint256,233 ) -> Result<void> {234 let caller = T::CrossAccountId::from_eth(caller);235 let new_admin = convert_substrate_address_to_cross_account_id::<T>(new_admin);236 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;237 Ok(())238 }239240 241 242 fn remove_collection_admin_substrate(243 &mut self,244 caller: caller,245 admin: uint256,246 ) -> Result<void> {247 let caller = T::CrossAccountId::from_eth(caller);248 let admin = convert_substrate_address_to_cross_account_id::<T>(admin);249 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;250 Ok(())251 }252253 254 255 fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {256 let caller = T::CrossAccountId::from_eth(caller);257 let new_admin = T::CrossAccountId::from_eth(new_admin);258 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;259 Ok(())260 }261262 263 264 265 fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {266 let caller = T::CrossAccountId::from_eth(caller);267 let admin = T::CrossAccountId::from_eth(admin);268 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;269 Ok(())270 }271272 273 274 275 #[solidity(rename_selector = "setCollectionNesting")]276 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {277 check_is_owner_or_admin(caller, self)?;278279 let mut permissions = self.collection.permissions.clone();280 let mut nesting = permissions.nesting().clone();281 nesting.token_owner = enable;282 nesting.restricted = None;283 permissions.nesting = Some(nesting);284285 self.collection.permissions = <Pallet<T>>::clamp_permissions(286 self.collection.mode.clone(),287 &self.collection.permissions,288 permissions,289 )290 .map_err(dispatch_to_evm::<T>)?;291292 save(self)293 }294295 296 297 298 299 #[solidity(rename_selector = "setCollectionNesting")]300 fn set_nesting(301 &mut self,302 caller: caller,303 enable: bool,304 collections: Vec<address>,305 ) -> Result<void> {306 if collections.is_empty() {307 return Err("no addresses provided".into());308 }309 check_is_owner_or_admin(caller, self)?;310311 let mut permissions = self.collection.permissions.clone();312 match enable {313 false => {314 let mut nesting = permissions.nesting().clone();315 nesting.token_owner = false;316 nesting.restricted = None;317 permissions.nesting = Some(nesting);318 }319 true => {320 let mut bv = OwnerRestrictedSet::new();321 for i in collections {322 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(323 "Can't convert address into collection id".into(),324 ))?)325 .map_err(|_| "too many collections")?;326 }327 let mut nesting = permissions.nesting().clone();328 nesting.token_owner = true;329 nesting.restricted = Some(bv);330 permissions.nesting = Some(nesting);331 }332 };333334 self.collection.permissions = <Pallet<T>>::clamp_permissions(335 self.collection.mode.clone(),336 &self.collection.permissions,337 permissions,338 )339 .map_err(dispatch_to_evm::<T>)?;340341 save(self)342 }343344 345 346 347 348 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {349 check_is_owner_or_admin(caller, self)?;350 let permissions = CollectionPermissions {351 access: Some(match mode {352 0 => AccessMode::Normal,353 1 => AccessMode::AllowList,354 _ => return Err("not supported access mode".into()),355 }),356 ..Default::default()357 };358 self.collection.permissions = <Pallet<T>>::clamp_permissions(359 self.collection.mode.clone(),360 &self.collection.permissions,361 permissions,362 )363 .map_err(dispatch_to_evm::<T>)?;364365 save(self)366 }367368 369 370 371 fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {372 let caller = T::CrossAccountId::from_eth(caller);373 let user = T::CrossAccountId::from_eth(user);374 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;375 Ok(())376 }377378 379 380 381 fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {382 let caller = T::CrossAccountId::from_eth(caller);383 let user = T::CrossAccountId::from_eth(user);384 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;385 Ok(())386 }387388 389 390 391 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {392 check_is_owner_or_admin(caller, self)?;393 let permissions = CollectionPermissions {394 mint_mode: Some(mode),395 ..Default::default()396 };397 self.collection.permissions = <Pallet<T>>::clamp_permissions(398 self.collection.mode.clone(),399 &self.collection.permissions,400 permissions,401 )402 .map_err(dispatch_to_evm::<T>)?;403404 save(self)405 }406407 408 409 410 411 fn verify_owner_or_admin(&self, user: address) -> Result<bool> {412 let user = T::CrossAccountId::from_eth(user);413 Ok(self414 .check_is_owner_or_admin(&user)415 .map(|_| true)416 .unwrap_or(false))417 }418419 420 421 422 423 #[solidity(rename_selector = "verifyOwnerOrAdmin")]424 fn verify_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {425 let user = convert_substrate_address_to_cross_account_id::<T>(user);426 Ok(self427 .check_is_owner_or_admin(&user)428 .map(|_| true)429 .unwrap_or(false))430 }431432 433 434 435 fn unique_collection_type(&mut self) -> Result<string> {436 let mode = match self.collection.mode {437 CollectionMode::Fungible(_) => "Fungible",438 CollectionMode::NFT => "NFT",439 CollectionMode::ReFungible => "ReFungible",440 };441 Ok(mode.into())442 }443444 445 446 447 448 fn change_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {449 let caller = T::CrossAccountId::from_eth(caller);450 let new_owner = T::CrossAccountId::from_eth(new_owner);451 self.change_owner_internal(caller, new_owner)452 .map_err(dispatch_to_evm::<T>)453 }454455 456 457 458 459 #[solidity(rename_selector = "changeOwner")]460 fn change_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {461 let caller = T::CrossAccountId::from_eth(caller);462 let new_owner = convert_substrate_address_to_cross_account_id::<T>(new_owner);463 self.change_owner_internal(caller, new_owner)464 .map_err(dispatch_to_evm::<T>)465 }466}467468fn convert_substrate_address_to_cross_account_id<T: Config>(address: uint256) -> T::CrossAccountId469where470 T::AccountId: From<[u8; 32]>,471{472 let mut address_arr: [u8; 32] = Default::default();473 address.to_big_endian(&mut address_arr);474 let account_id = T::AccountId::from(address_arr);475 T::CrossAccountId::from_sub(account_id)476}477478fn check_is_owner_or_admin<T: Config>(479 caller: caller,480 collection: &CollectionHandle<T>,481) -> Result<T::CrossAccountId> {482 let caller = T::CrossAccountId::from_eth(caller);483 collection484 .check_is_owner_or_admin(&caller)485 .map_err(dispatch_to_evm::<T>)?;486 Ok(caller)487}488489fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {490 491 collection.consume_store_writes(1)?;492 collection493 .check_is_internal()494 .map_err(dispatch_to_evm::<T>)?;495 collection.save().map_err(dispatch_to_evm::<T>)?;496 Ok(())497}498499500pub mod static_property {501 use evm_coder::{502 execution::{Result, Error},503 };504 use alloc::format;505506 const EXPECT_CONVERT_ERROR: &str = "length < limit";507508 509 pub mod key {510 use super::*;511512 513 pub fn schema_name() -> up_data_structs::PropertyKey {514 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)515 }516517 518 pub fn base_uri() -> up_data_structs::PropertyKey {519 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)520 }521522 523 pub fn url() -> up_data_structs::PropertyKey {524 property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)525 }526527 528 pub fn suffix() -> up_data_structs::PropertyKey {529 property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)530 }531532 533 pub fn parent_nft() -> up_data_structs::PropertyKey {534 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)535 }536 }537538 539 pub mod value {540 use super::*;541542 543 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";544545 546 pub fn erc721() -> up_data_structs::PropertyValue {547 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)548 }549 }550551 552 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {553 bytes.to_vec().try_into().map_err(|_| {554 Error::Revert(format!(555 "Property key is too long. Max length is {}.",556 up_data_structs::PropertyKey::bound()557 ))558 })559 }560561 562 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {563 bytes.to_vec().try_into().map_err(|_| {564 Error::Revert(format!(565 "Property key is too long. Max length is {}.",566 up_data_structs::PropertyKey::bound()567 ))568 })569 }570}