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 Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode, CollectionPermissions,29};30use alloc::format;3132use crate::{Pallet, CollectionHandle, Config, CollectionProperties};333435#[derive(ToLog)]36pub enum CollectionHelpersEvents {37 38 CollectionCreated {39 40 #[indexed]41 owner: address,4243 44 #[indexed]45 collection_id: address,46 },47}48495051pub trait CommonEvmHandler {52 const CODE: &'static [u8];5354 55 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;56}575859#[solidity_interface(name = "Collection")]60impl<T: Config> CollectionHandle<T>61where62 T::AccountId: From<[u8; 32]>,63{64 65 66 67 68 fn set_collection_property(69 &mut self,70 caller: caller,71 key: string,72 value: bytes,73 ) -> Result<void> {74 let caller = T::CrossAccountId::from_eth(caller);75 let key = <Vec<u8>>::from(key)76 .try_into()77 .map_err(|_| "key too large")?;78 let value = value.try_into().map_err(|_| "value too large")?;7980 <Pallet<T>>::set_collection_property(self, &caller, Property { key, value })81 .map_err(dispatch_to_evm::<T>)82 }8384 85 86 87 fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {88 let caller = T::CrossAccountId::from_eth(caller);89 let key = <Vec<u8>>::from(key)90 .try_into()91 .map_err(|_| "key too large")?;9293 <Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)94 }9596 97 98 99 100 101 102 fn collection_property(&self, key: string) -> Result<bytes> {103 let key = <Vec<u8>>::from(key)104 .try_into()105 .map_err(|_| "key too large")?;106107 let props = <CollectionProperties<T>>::get(self.id);108 let prop = props.get(&key).ok_or("key not found")?;109110 Ok(prop.to_vec())111 }112113 114 115 116 117 118 fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {119 check_is_owner_or_admin(caller, self)?;120121 let sponsor = T::CrossAccountId::from_eth(sponsor);122 self.set_sponsor(sponsor.as_sub().clone())123 .map_err(dispatch_to_evm::<T>)?;124 save(self)125 }126127 128 129 130 fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {131 let caller = T::CrossAccountId::from_eth(caller);132 if !self133 .confirm_sponsorship(caller.as_sub())134 .map_err(dispatch_to_evm::<T>)?135 {136 return Err("caller is not set as sponsor".into());137 }138 save(self)139 }140141 142 143 144 145 146 147 148 149 150 151 #[solidity(rename_selector = "setCollectionLimit")]152 fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {153 check_is_owner_or_admin(caller, self)?;154 let mut limits = self.limits.clone();155156 match limit.as_str() {157 "accountTokenOwnershipLimit" => {158 limits.account_token_ownership_limit = Some(value);159 }160 "sponsoredDataSize" => {161 limits.sponsored_data_size = Some(value);162 }163 "sponsoredDataRateLimit" => {164 limits.sponsored_data_rate_limit = Some(SponsoringRateLimit::Blocks(value));165 }166 "tokenLimit" => {167 limits.token_limit = Some(value);168 }169 "sponsorTransferTimeout" => {170 limits.sponsor_transfer_timeout = Some(value);171 }172 "sponsorApproveTimeout" => {173 limits.sponsor_approve_timeout = Some(value);174 }175 _ => {176 return Err(Error::Revert(format!(177 "unknown integer limit \"{}\"",178 limit179 )))180 }181 }182 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)183 .map_err(dispatch_to_evm::<T>)?;184 save(self)185 }186187 188 189 190 191 192 193 194 #[solidity(rename_selector = "setCollectionLimit")]195 fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {196 check_is_owner_or_admin(caller, self)?;197 let mut limits = self.limits.clone();198199 match limit.as_str() {200 "ownerCanTransfer" => {201 limits.owner_can_transfer = Some(value);202 }203 "ownerCanDestroy" => {204 limits.owner_can_destroy = Some(value);205 }206 "transfersEnabled" => {207 limits.transfers_enabled = Some(value);208 }209 _ => {210 return Err(Error::Revert(format!(211 "unknown boolean limit \"{}\"",212 limit213 )))214 }215 }216 self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)217 .map_err(dispatch_to_evm::<T>)?;218 save(self)219 }220221 222 fn contract_address(&self, _caller: caller) -> Result<address> {223 Ok(crate::eth::collection_id_to_address(self.id))224 }225226 227 228 fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {229 let caller = T::CrossAccountId::from_eth(caller);230 let mut new_admin_arr: [u8; 32] = Default::default();231 new_admin.to_big_endian(&mut new_admin_arr);232 let account_id = T::AccountId::from(new_admin_arr);233 let new_admin = T::CrossAccountId::from_sub(account_id);234 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;235 Ok(())236 }237238 239 240 fn remove_collection_admin_substrate(&self, caller: caller, admin: uint256) -> Result<void> {241 let caller = T::CrossAccountId::from_eth(caller);242 let mut admin_arr: [u8; 32] = Default::default();243 admin.to_big_endian(&mut admin_arr);244 let account_id = T::AccountId::from(admin_arr);245 let admin = T::CrossAccountId::from_sub(account_id);246 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;247 Ok(())248 }249250 251 252 fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result<void> {253 let caller = T::CrossAccountId::from_eth(caller);254 let new_admin = T::CrossAccountId::from_eth(new_admin);255 <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;256 Ok(())257 }258259 260 261 262 fn remove_collection_admin(&self, caller: caller, admin: address) -> Result<void> {263 let caller = T::CrossAccountId::from_eth(caller);264 let admin = T::CrossAccountId::from_eth(admin);265 <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;266 Ok(())267 }268269 270 271 272 #[solidity(rename_selector = "setCollectionNesting")]273 fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {274 check_is_owner_or_admin(caller, self)?;275276 let mut permissions = self.collection.permissions.clone();277 let mut nesting = permissions.nesting().clone();278 nesting.token_owner = enable;279 nesting.restricted = None;280 permissions.nesting = Some(nesting);281282 self.collection.permissions = <Pallet<T>>::clamp_permissions(283 self.collection.mode.clone(),284 &self.collection.permissions,285 permissions,286 )287 .map_err(dispatch_to_evm::<T>)?;288289 save(self)290 }291292 293 294 295 296 #[solidity(rename_selector = "setCollectionNesting")]297 fn set_nesting(298 &mut self,299 caller: caller,300 enable: bool,301 collections: Vec<address>,302 ) -> Result<void> {303 if collections.is_empty() {304 return Err("no addresses provided".into());305 }306 check_is_owner_or_admin(caller, self)?;307308 let mut permissions = self.collection.permissions.clone();309 match enable {310 false => {311 let mut nesting = permissions.nesting().clone();312 nesting.token_owner = false;313 nesting.restricted = None;314 permissions.nesting = Some(nesting);315 }316 true => {317 let mut bv = OwnerRestrictedSet::new();318 for i in collections {319 bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert(320 "Can't convert address into collection id".into(),321 ))?)322 .map_err(|_| "too many collections")?;323 }324 let mut nesting = permissions.nesting().clone();325 nesting.token_owner = true;326 nesting.restricted = Some(bv);327 permissions.nesting = Some(nesting);328 }329 };330331 self.collection.permissions = <Pallet<T>>::clamp_permissions(332 self.collection.mode.clone(),333 &self.collection.permissions,334 permissions,335 )336 .map_err(dispatch_to_evm::<T>)?;337338 save(self)339 }340341 342 343 344 345 fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {346 check_is_owner_or_admin(caller, self)?;347 let permissions = CollectionPermissions {348 access: Some(match mode {349 0 => AccessMode::Normal,350 1 => AccessMode::AllowList,351 _ => return Err("not supported access mode".into()),352 }),353 ..Default::default()354 };355 self.collection.permissions = <Pallet<T>>::clamp_permissions(356 self.collection.mode.clone(),357 &self.collection.permissions,358 permissions,359 )360 .map_err(dispatch_to_evm::<T>)?;361362 save(self)363 }364365 366 367 368 fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {369 let caller = T::CrossAccountId::from_eth(caller);370 let user = T::CrossAccountId::from_eth(user);371 <Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;372 Ok(())373 }374375 376 377 378 fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {379 let caller = T::CrossAccountId::from_eth(caller);380 let user = T::CrossAccountId::from_eth(user);381 <Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;382 Ok(())383 }384385 386 387 388 fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {389 check_is_owner_or_admin(caller, self)?;390 let permissions = CollectionPermissions {391 mint_mode: Some(mode),392 ..Default::default()393 };394 self.collection.permissions = <Pallet<T>>::clamp_permissions(395 self.collection.mode.clone(),396 &self.collection.permissions,397 permissions,398 )399 .map_err(dispatch_to_evm::<T>)?;400401 save(self)402 }403}404405fn check_is_owner_or_admin<T: Config>(406 caller: caller,407 collection: &CollectionHandle<T>,408) -> Result<T::CrossAccountId> {409 let caller = T::CrossAccountId::from_eth(caller);410 collection411 .check_is_owner_or_admin(&caller)412 .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;413 Ok(caller)414}415416fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {417 418 collection419 .check_is_internal()420 .map_err(dispatch_to_evm::<T>)?;421 <crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());422 Ok(())423}424425426pub fn token_uri_key() -> up_data_structs::PropertyKey {427 b"tokenURI"428 .to_vec()429 .try_into()430 .expect("length < limit; qed")431}