1234567891011121314151617extern crate alloc;18use core::{19 char::{REPLACEMENT_CHARACTER, decode_utf16},20 convert::TryInto,21};22use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};23use frame_support::BoundedVec;24use up_data_structs::{25 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,26 CollectionPropertiesVec,27};28use pallet_evm_coder_substrate::dispatch_to_evm;29use sp_std::vec::Vec;30use pallet_common::{31 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, token_uri_key},32 CollectionHandle, CollectionPropertyPermissions,33};34use pallet_evm::{account::CrossAccountId, PrecompileHandle};35use pallet_evm_coder_substrate::call;36use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};3738use crate::{39 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,40 SelfWeightOf, weights::WeightInfo, TokenProperties,41};4243#[solidity_interface(name = "TokenProperties")]44impl<T: Config> NonfungibleHandle<T> {45 fn set_token_property_permission(46 &mut self,47 caller: caller,48 key: string,49 is_mutable: bool,50 collection_admin: bool,51 token_owner: bool,52 ) -> Result<()> {53 let caller = T::CrossAccountId::from_eth(caller);54 <Pallet<T>>::set_property_permission(55 self,56 &caller,57 PropertyKeyPermission {58 key: <Vec<u8>>::from(key)59 .try_into()60 .map_err(|_| "too long key")?,61 permission: PropertyPermission {62 mutable: is_mutable,63 collection_admin,64 token_owner,65 },66 },67 )68 .map_err(dispatch_to_evm::<T>)69 }7071 fn set_property(72 &mut self,73 caller: caller,74 token_id: uint256,75 key: string,76 value: bytes,77 ) -> Result<()> {78 let caller = T::CrossAccountId::from_eth(caller);79 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;80 let key = <Vec<u8>>::from(key)81 .try_into()82 .map_err(|_| "key too long")?;83 let value = value.try_into().map_err(|_| "value too long")?;8485 <Pallet<T>>::set_token_property(self, &caller, TokenId(token_id), Property { key, value })86 .map_err(dispatch_to_evm::<T>)87 }8889 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {90 let caller = T::CrossAccountId::from_eth(caller);91 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;92 let key = <Vec<u8>>::from(key)93 .try_into()94 .map_err(|_| "key too long")?;9596 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key)97 .map_err(dispatch_to_evm::<T>)98 }99100 101 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {102 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;103 let key = <Vec<u8>>::from(key)104 .try_into()105 .map_err(|_| "key too long")?;106107 let props = <TokenProperties<T>>::get((self.id, token_id));108 let prop = props.get(&key).ok_or("key not found")?;109110 Ok(prop.to_vec())111 }112}113114#[derive(ToLog)]115pub enum ERC721Events {116 Transfer {117 #[indexed]118 from: address,119 #[indexed]120 to: address,121 #[indexed]122 token_id: uint256,123 },124 Approval {125 #[indexed]126 owner: address,127 #[indexed]128 approved: address,129 #[indexed]130 token_id: uint256,131 },132 #[allow(dead_code)]133 ApprovalForAll {134 #[indexed]135 owner: address,136 #[indexed]137 operator: address,138 approved: bool,139 },140}141142#[derive(ToLog)]143pub enum ERC721MintableEvents {144 #[allow(dead_code)]145 MintingFinished {},146}147148#[solidity_interface(name = "ERC721Metadata")]149impl<T: Config> NonfungibleHandle<T> {150 fn name(&self) -> Result<string> {151 Ok(decode_utf16(self.name.iter().copied())152 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))153 .collect::<string>())154 }155156 fn symbol(&self) -> Result<string> {157 Ok(string::from_utf8_lossy(&self.token_prefix).into())158 }159160 161 #[solidity(rename_selector = "tokenURI")]162 fn token_uri(&self, token_id: uint256) -> Result<string> {163 let key = token_uri_key();164 if !has_token_permission::<T>(self.id, &key) {165 return Err("No tokenURI permission".into());166 }167168 self.consume_store_reads(1)?;169 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;170171 let properties = <TokenProperties<T>>::try_get((self.id, token_id))172 .map_err(|_| Error::Revert("Token properties not found".into()))?;173 if let Some(property) = properties.get(&key) {174 return Ok(string::from_utf8_lossy(property).into());175 }176177 Err("Property tokenURI not found".into())178 }179}180181#[solidity_interface(name = "ERC721Enumerable")]182impl<T: Config> NonfungibleHandle<T> {183 fn token_by_index(&self, index: uint256) -> Result<uint256> {184 Ok(index)185 }186187 188 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {189 190 Err("not implemented".into())191 }192193 fn total_supply(&self) -> Result<uint256> {194 self.consume_store_reads(1)?;195 Ok(<Pallet<T>>::total_supply(self).into())196 }197}198199#[solidity_interface(name = "ERC721", events(ERC721Events))]200impl<T: Config> NonfungibleHandle<T> {201 fn balance_of(&self, owner: address) -> Result<uint256> {202 self.consume_store_reads(1)?;203 let owner = T::CrossAccountId::from_eth(owner);204 let balance = <AccountBalance<T>>::get((self.id, owner));205 Ok(balance.into())206 }207 fn owner_of(&self, token_id: uint256) -> Result<address> {208 self.consume_store_reads(1)?;209 let token: TokenId = token_id.try_into()?;210 Ok(*<TokenData<T>>::get((self.id, token))211 .ok_or("token not found")?212 .owner213 .as_eth())214 }215 216 fn safe_transfer_from_with_data(217 &mut self,218 _from: address,219 _to: address,220 _token_id: uint256,221 _data: bytes,222 _value: value,223 ) -> Result<void> {224 225 Err("not implemented".into())226 }227 228 fn safe_transfer_from(229 &mut self,230 _from: address,231 _to: address,232 _token_id: uint256,233 _value: value,234 ) -> Result<void> {235 236 Err("not implemented".into())237 }238239 #[weight(<SelfWeightOf<T>>::transfer_from())]240 fn transfer_from(241 &mut self,242 caller: caller,243 from: address,244 to: address,245 token_id: uint256,246 _value: value,247 ) -> Result<void> {248 let caller = T::CrossAccountId::from_eth(caller);249 let from = T::CrossAccountId::from_eth(from);250 let to = T::CrossAccountId::from_eth(to);251 let token = token_id.try_into()?;252 let budget = self253 .recorder254 .weight_calls_budget(<StructureWeight<T>>::find_parent());255256 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)257 .map_err(dispatch_to_evm::<T>)?;258 Ok(())259 }260261 #[weight(<SelfWeightOf<T>>::approve())]262 fn approve(263 &mut self,264 caller: caller,265 approved: address,266 token_id: uint256,267 _value: value,268 ) -> Result<void> {269 let caller = T::CrossAccountId::from_eth(caller);270 let approved = T::CrossAccountId::from_eth(approved);271 let token = token_id.try_into()?;272 let budget = self273 .recorder274 .weight_calls_budget(<StructureWeight<T>>::find_parent());275276 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved), &budget)277 .map_err(dispatch_to_evm::<T>)?;278 Ok(())279 }280281 282 fn set_approval_for_all(283 &mut self,284 _caller: caller,285 _operator: address,286 _approved: bool,287 ) -> Result<void> {288 289 Err("not implemented".into())290 }291292 293 fn get_approved(&self, _token_id: uint256) -> Result<address> {294 295 Err("not implemented".into())296 }297298 299 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {300 301 Err("not implemented".into())302 }303}304305#[solidity_interface(name = "ERC721Burnable")]306impl<T: Config> NonfungibleHandle<T> {307 #[weight(<SelfWeightOf<T>>::burn_item())]308 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {309 let caller = T::CrossAccountId::from_eth(caller);310 let token = token_id.try_into()?;311312 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;313 Ok(())314 }315}316317#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]318impl<T: Config> NonfungibleHandle<T> {319 fn minting_finished(&self) -> Result<bool> {320 Ok(false)321 }322323 324 325 #[weight(<SelfWeightOf<T>>::create_item())]326 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {327 let caller = T::CrossAccountId::from_eth(caller);328 let to = T::CrossAccountId::from_eth(to);329 let token_id: u32 = token_id.try_into()?;330 let budget = self331 .recorder332 .weight_calls_budget(<StructureWeight<T>>::find_parent());333334 if <TokensMinted<T>>::get(self.id)335 .checked_add(1)336 .ok_or("item id overflow")?337 != token_id338 {339 return Err("item id should be next".into());340 }341342 <Pallet<T>>::create_item(343 self,344 &caller,345 CreateItemData::<T> {346 properties: BoundedVec::default(),347 owner: to,348 },349 &budget,350 )351 .map_err(dispatch_to_evm::<T>)?;352353 Ok(true)354 }355356 357 358 #[solidity(rename_selector = "mintWithTokenURI")]359 #[weight(<SelfWeightOf<T>>::create_item())]360 fn mint_with_token_uri(361 &mut self,362 caller: caller,363 to: address,364 token_id: uint256,365 token_uri: string,366 ) -> Result<bool> {367 let key = token_uri_key();368 let permission = get_token_permission::<T>(self.id, &key)?;369 if !permission.collection_admin {370 return Err("Operation is not allowed".into());371 }372373 let caller = T::CrossAccountId::from_eth(caller);374 let to = T::CrossAccountId::from_eth(to);375 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;376 let budget = self377 .recorder378 .weight_calls_budget(<StructureWeight<T>>::find_parent());379380 if <TokensMinted<T>>::get(self.id)381 .checked_add(1)382 .ok_or("item id overflow")?383 != token_id384 {385 return Err("item id should be next".into());386 }387388 let mut properties = CollectionPropertiesVec::default();389 properties390 .try_push(Property {391 key,392 value: token_uri393 .into_bytes()394 .try_into()395 .map_err(|_| "token uri is too long")?,396 })397 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;398399 <Pallet<T>>::create_item(400 self,401 &caller,402 CreateItemData::<T> {403 properties,404 owner: to,405 },406 &budget,407 )408 .map_err(dispatch_to_evm::<T>)?;409 Ok(true)410 }411412 413 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {414 Err("not implementable".into())415 }416}417418fn get_token_permission<T: Config>(419 collection_id: CollectionId,420 key: &PropertyKey,421) -> Result<PropertyPermission> {422 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)423 .map_err(|_| Error::Revert("No permissions for collection".into()))?;424 let a = token_property_permissions425 .get(key)426 .map(|p| p.clone())427 .ok_or_else(|| Error::Revert("No permission".into()))?;428 Ok(a)429}430431fn has_token_permission<T: Config>(collection_id: CollectionId, key: &PropertyKey) -> bool {432 if let Ok(token_property_permissions) =433 CollectionPropertyPermissions::<T>::try_get(collection_id)434 {435 return token_property_permissions.contains_key(key);436 }437438 false439}440441#[solidity_interface(name = "ERC721UniqueExtensions")]442impl<T: Config> NonfungibleHandle<T> {443 #[weight(<SelfWeightOf<T>>::transfer())]444 fn transfer(445 &mut self,446 caller: caller,447 to: address,448 token_id: uint256,449 _value: value,450 ) -> Result<void> {451 let caller = T::CrossAccountId::from_eth(caller);452 let to = T::CrossAccountId::from_eth(to);453 let token = token_id.try_into()?;454 let budget = self455 .recorder456 .weight_calls_budget(<StructureWeight<T>>::find_parent());457458 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;459 Ok(())460 }461462 #[weight(<SelfWeightOf<T>>::burn_from())]463 fn burn_from(464 &mut self,465 caller: caller,466 from: address,467 token_id: uint256,468 _value: value,469 ) -> Result<void> {470 let caller = T::CrossAccountId::from_eth(caller);471 let from = T::CrossAccountId::from_eth(from);472 let token = token_id.try_into()?;473 let budget = self474 .recorder475 .weight_calls_budget(<StructureWeight<T>>::find_parent());476477 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)478 .map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481482 fn next_token_id(&self) -> Result<uint256> {483 self.consume_store_reads(1)?;484 Ok(<TokensMinted<T>>::get(self.id)485 .checked_add(1)486 .ok_or("item id overflow")?487 .into())488 }489490 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]491 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {492 let caller = T::CrossAccountId::from_eth(caller);493 let to = T::CrossAccountId::from_eth(to);494 let mut expected_index = <TokensMinted<T>>::get(self.id)495 .checked_add(1)496 .ok_or("item id overflow")?;497 let budget = self498 .recorder499 .weight_calls_budget(<StructureWeight<T>>::find_parent());500501 let total_tokens = token_ids.len();502 for id in token_ids.into_iter() {503 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;504 if id != expected_index {505 return Err("item id should be next".into());506 }507 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;508 }509 let data = (0..total_tokens)510 .map(|_| CreateItemData::<T> {511 properties: BoundedVec::default(),512 owner: to.clone(),513 })514 .collect();515516 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)517 .map_err(dispatch_to_evm::<T>)?;518 Ok(true)519 }520521 #[solidity(rename_selector = "mintBulkWithTokenURI")]522 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]523 fn mint_bulk_with_token_uri(524 &mut self,525 caller: caller,526 to: address,527 tokens: Vec<(uint256, string)>,528 ) -> Result<bool> {529 let key = token_uri_key();530 let caller = T::CrossAccountId::from_eth(caller);531 let to = T::CrossAccountId::from_eth(to);532 let mut expected_index = <TokensMinted<T>>::get(self.id)533 .checked_add(1)534 .ok_or("item id overflow")?;535 let budget = self536 .recorder537 .weight_calls_budget(<StructureWeight<T>>::find_parent());538539 let mut data = Vec::with_capacity(tokens.len());540 for (id, token_uri) in tokens {541 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;542 if id != expected_index {543 return Err("item id should be next".into());544 }545 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;546547 let mut properties = CollectionPropertiesVec::default();548 properties549 .try_push(Property {550 key: key.clone(),551 value: token_uri552 .into_bytes()553 .try_into()554 .map_err(|_| "token uri is too long")?,555 })556 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;557558 data.push(CreateItemData::<T> {559 properties,560 owner: to.clone(),561 });562 }563564 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)565 .map_err(dispatch_to_evm::<T>)?;566 Ok(true)567 }568}569570#[solidity_interface(571 name = "UniqueNFT",572 is(573 ERC721,574 ERC721Metadata,575 ERC721Enumerable,576 ERC721UniqueExtensions,577 ERC721Mintable,578 ERC721Burnable,579 via("CollectionHandle<T>", common_mut, Collection),580 TokenProperties,581 )582)]583impl<T: Config> NonfungibleHandle<T> {}584585586generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);587generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);588589impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {590 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");591592 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {593 call::<T, UniqueNFTCall<T>, _, _>(handle, self)594 }595}