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 fn verify_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {424 let user = convert_substrate_address_to_cross_account_id::<T>(user);425 Ok(self426 .check_is_owner_or_admin(&user)427 .map(|_| true)428 .unwrap_or(false))429 }430431 432 433 434 fn unique_collection_type(&mut self) -> Result<string> {435 let mode = match self.collection.mode {436 CollectionMode::Fungible(_) => "Fungible",437 CollectionMode::NFT => "NFT",438 CollectionMode::ReFungible => "ReFungible",439 };440 Ok(mode.into())441 }442443 444 445 446 447 fn change_owner(&mut self, caller: caller, new_owner: address) -> Result<void> {448 let caller = T::CrossAccountId::from_eth(caller);449 let new_owner = T::CrossAccountId::from_eth(new_owner);450 self.change_owner_internal(caller, new_owner)451 .map_err(dispatch_to_evm::<T>)452 }453454 455 456 457 458 fn change_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {459 let caller = T::CrossAccountId::from_eth(caller);460 let new_owner = convert_substrate_address_to_cross_account_id::<T>(new_owner);461 self.change_owner_internal(caller, new_owner)462 .map_err(dispatch_to_evm::<T>)463 }464}465466fn convert_substrate_address_to_cross_account_id<T: Config>(address: uint256) -> T::CrossAccountId467where468 T::AccountId: From<[u8; 32]>,469{470 let mut address_arr: [u8; 32] = Default::default();471 address.to_big_endian(&mut address_arr);472 let account_id = T::AccountId::from(address_arr);473 T::CrossAccountId::from_sub(account_id)474}475476fn check_is_owner_or_admin<T: Config>(477 caller: caller,478 collection: &CollectionHandle<T>,479) -> Result<T::CrossAccountId> {480 let caller = T::CrossAccountId::from_eth(caller);481 collection482 .check_is_owner_or_admin(&caller)483 .map_err(dispatch_to_evm::<T>)?;484 Ok(caller)485}486487fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {488 489 collection.consume_store_writes(1)?;490 collection491 .check_is_internal()492 .map_err(dispatch_to_evm::<T>)?;493 collection.save().map_err(dispatch_to_evm::<T>)?;494 Ok(())495}496497498pub mod static_property {499 use evm_coder::{500 execution::{Result, Error},501 };502 use alloc::format;503504 const EXPECT_CONVERT_ERROR: &str = "length < limit";505506 507 pub mod key {508 use super::*;509510 511 pub fn schema_name() -> up_data_structs::PropertyKey {512 property_key_from_bytes(b"schemaName").expect(EXPECT_CONVERT_ERROR)513 }514515 516 pub fn base_uri() -> up_data_structs::PropertyKey {517 property_key_from_bytes(b"baseURI").expect(EXPECT_CONVERT_ERROR)518 }519520 521 pub fn url() -> up_data_structs::PropertyKey {522 property_key_from_bytes(b"url").expect(EXPECT_CONVERT_ERROR)523 }524525 526 pub fn suffix() -> up_data_structs::PropertyKey {527 property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR)528 }529530 531 pub fn parent_nft() -> up_data_structs::PropertyKey {532 property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR)533 }534 }535536 537 pub mod value {538 use super::*;539540 541 pub const ERC721_METADATA: &[u8] = b"ERC721Metadata";542543 544 pub fn erc721() -> up_data_structs::PropertyValue {545 property_value_from_bytes(ERC721_METADATA).expect(EXPECT_CONVERT_ERROR)546 }547 }548549 550 pub fn property_key_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyKey> {551 bytes.to_vec().try_into().map_err(|_| {552 Error::Revert(format!(553 "Property key is too long. Max length is {}.",554 up_data_structs::PropertyKey::bound()555 ))556 })557 }558559 560 pub fn property_value_from_bytes(bytes: &[u8]) -> Result<up_data_structs::PropertyValue> {561 bytes.to_vec().try_into().map_err(|_| {562 Error::Revert(format!(563 "Property key is too long. Max length is {}.",564 up_data_structs::PropertyKey::bound()565 ))566 })567 }568}