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::{TokenId, SchemaVersion, PropertyPermission, PropertyKeyPermission, Property};25use pallet_evm_coder_substrate::dispatch_to_evm;26use sp_core::{H160, U256};27use sp_std::vec::Vec;28use pallet_common::{29 erc::{CommonEvmHandler, PrecompileResult, CollectionPropertiesCall},30 CollectionHandle,31};32use pallet_evm::account::CrossAccountId;33use pallet_evm_coder_substrate::call;34use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};3536use crate::{37 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,38 SelfWeightOf, weights::WeightInfo, TokenProperties,39};4041#[solidity_interface(name = "TokenProperties")]42impl<T: Config> NonfungibleHandle<T> {43 fn set_token_property_permission(44 &mut self,45 caller: caller,46 key: string,47 is_mutable: bool,48 collection_admin: bool,49 token_owner: bool,50 ) -> Result<()> {51 let caller = T::CrossAccountId::from_eth(caller);52 <Pallet<T>>::set_property_permission(53 self,54 &caller,55 PropertyKeyPermission {56 key: <Vec<u8>>::from(key)57 .try_into()58 .map_err(|_| "too long key")?,59 permission: PropertyPermission {60 mutable: is_mutable,61 collection_admin,62 token_owner,63 },64 },65 )66 .map_err(dispatch_to_evm::<T>)67 }6869 fn set_property(70 &mut self,71 caller: caller,72 token_id: uint256,73 key: string,74 value: bytes,75 ) -> Result<()> {76 let caller = T::CrossAccountId::from_eth(caller);77 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;78 let key = <Vec<u8>>::from(key)79 .try_into()80 .map_err(|_| "key too long")?;81 let value = value.try_into().map_err(|_| "value too long")?;8283 <Pallet<T>>::set_token_property(self, &caller, TokenId(token_id), Property { key, value })84 .map_err(dispatch_to_evm::<T>)85 }8687 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {88 let caller = T::CrossAccountId::from_eth(caller);89 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;90 let key = <Vec<u8>>::from(key)91 .try_into()92 .map_err(|_| "key too long")?;9394 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key)95 .map_err(dispatch_to_evm::<T>)96 }9798 99 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {100 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;101 let key = <Vec<u8>>::from(key)102 .try_into()103 .map_err(|_| "key too long")?;104105 let props = <TokenProperties<T>>::get((self.id, token_id));106 let prop = props.get(&key).ok_or("key not found")?;107108 Ok(prop.to_vec())109 }110}111112fn error_unsupported_schema_version() -> Error {113 alloc::format!(114 "Unsupported schema version! Support only {:?}",115 SchemaVersion::ImageURL116 )117 .as_str()118 .into()119}120121#[derive(ToLog)]122pub enum ERC721Events {123 Transfer {124 #[indexed]125 from: address,126 #[indexed]127 to: address,128 #[indexed]129 token_id: uint256,130 },131 Approval {132 #[indexed]133 owner: address,134 #[indexed]135 approved: address,136 #[indexed]137 token_id: uint256,138 },139 #[allow(dead_code)]140 ApprovalForAll {141 #[indexed]142 owner: address,143 #[indexed]144 operator: address,145 approved: bool,146 },147}148149#[derive(ToLog)]150pub enum ERC721MintableEvents {151 #[allow(dead_code)]152 MintingFinished {},153}154155#[solidity_interface(name = "ERC721Metadata")]156impl<T: Config> NonfungibleHandle<T> {157 fn name(&self) -> Result<string> {158 Ok(decode_utf16(self.name.iter().copied())159 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))160 .collect::<string>())161 }162163 fn symbol(&self) -> Result<string> {164 Ok(string::from_utf8_lossy(&self.token_prefix).into())165 }166167 168 #[solidity(rename_selector = "tokenURI")]169 fn token_uri(&self, token_id: uint256) -> Result<string> {170 if !matches!(self.schema_version, SchemaVersion::ImageURL) {171 return Err(error_unsupported_schema_version());172 }173174 self.consume_store_reads(1)?;175 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;176 Ok(string::from_utf8_lossy(177 &<TokenData<T>>::get((self.id, token_id))178 .ok_or("token not found")?179 .const_data,180 )181 .into())182 }183}184185#[solidity_interface(name = "ERC721Enumerable")]186impl<T: Config> NonfungibleHandle<T> {187 fn token_by_index(&self, index: uint256) -> Result<uint256> {188 Ok(index)189 }190191 192 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {193 194 Err("not implemented".into())195 }196197 fn total_supply(&self) -> Result<uint256> {198 self.consume_store_reads(1)?;199 Ok(<Pallet<T>>::total_supply(self).into())200 }201}202203#[solidity_interface(name = "ERC721", events(ERC721Events))]204impl<T: Config> NonfungibleHandle<T> {205 fn balance_of(&self, owner: address) -> Result<uint256> {206 self.consume_store_reads(1)?;207 let owner = T::CrossAccountId::from_eth(owner);208 let balance = <AccountBalance<T>>::get((self.id, owner));209 Ok(balance.into())210 }211 fn owner_of(&self, token_id: uint256) -> Result<address> {212 self.consume_store_reads(1)?;213 let token: TokenId = token_id.try_into()?;214 Ok(*<TokenData<T>>::get((self.id, token))215 .ok_or("token not found")?216 .owner217 .as_eth())218 }219 220 fn safe_transfer_from_with_data(221 &mut self,222 _from: address,223 _to: address,224 _token_id: uint256,225 _data: bytes,226 _value: value,227 ) -> Result<void> {228 229 Err("not implemented".into())230 }231 232 fn safe_transfer_from(233 &mut self,234 _from: address,235 _to: address,236 _token_id: uint256,237 _value: value,238 ) -> Result<void> {239 240 Err("not implemented".into())241 }242243 #[weight(<SelfWeightOf<T>>::transfer_from())]244 fn transfer_from(245 &mut self,246 caller: caller,247 from: address,248 to: address,249 token_id: uint256,250 _value: value,251 ) -> Result<void> {252 let caller = T::CrossAccountId::from_eth(caller);253 let from = T::CrossAccountId::from_eth(from);254 let to = T::CrossAccountId::from_eth(to);255 let token = token_id.try_into()?;256 let budget = self257 .recorder258 .weight_calls_budget(<StructureWeight<T>>::find_parent());259260 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)261 .map_err(dispatch_to_evm::<T>)?;262 Ok(())263 }264265 #[weight(<SelfWeightOf<T>>::approve())]266 fn approve(267 &mut self,268 caller: caller,269 approved: address,270 token_id: uint256,271 _value: value,272 ) -> Result<void> {273 let caller = T::CrossAccountId::from_eth(caller);274 let approved = T::CrossAccountId::from_eth(approved);275 let token = token_id.try_into()?;276277 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))278 .map_err(dispatch_to_evm::<T>)?;279 Ok(())280 }281282 283 fn set_approval_for_all(284 &mut self,285 _caller: caller,286 _operator: address,287 _approved: bool,288 ) -> Result<void> {289 290 Err("not implemented".into())291 }292293 294 fn get_approved(&self, _token_id: uint256) -> Result<address> {295 296 Err("not implemented".into())297 }298299 300 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {301 302 Err("not implemented".into())303 }304}305306#[solidity_interface(name = "ERC721Burnable")]307impl<T: Config> NonfungibleHandle<T> {308 #[weight(<SelfWeightOf<T>>::burn_item())]309 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {310 let caller = T::CrossAccountId::from_eth(caller);311 let token = token_id.try_into()?;312313 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;314 Ok(())315 }316}317318#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]319impl<T: Config> NonfungibleHandle<T> {320 fn minting_finished(&self) -> Result<bool> {321 Ok(false)322 }323324 325 326 #[weight(<SelfWeightOf<T>>::create_item())]327 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {328 let caller = T::CrossAccountId::from_eth(caller);329 let to = T::CrossAccountId::from_eth(to);330 let token_id: u32 = token_id.try_into()?;331 let budget = self332 .recorder333 .weight_calls_budget(<StructureWeight<T>>::find_parent());334335 if <TokensMinted<T>>::get(self.id)336 .checked_add(1)337 .ok_or("item id overflow")?338 != token_id339 {340 return Err("item id should be next".into());341 }342343 <Pallet<T>>::create_item(344 self,345 &caller,346 CreateItemData::<T> {347 const_data: BoundedVec::default(),348 properties: BoundedVec::default(),349 owner: to,350 },351 &budget,352 )353 .map_err(dispatch_to_evm::<T>)?;354355 Ok(true)356 }357358 359 360 #[solidity(rename_selector = "mintWithTokenURI")]361 #[weight(<SelfWeightOf<T>>::create_item())]362 fn mint_with_token_uri(363 &mut self,364 caller: caller,365 to: address,366 token_id: uint256,367 token_uri: string,368 ) -> Result<bool> {369 if !matches!(self.schema_version, SchemaVersion::ImageURL) {370 return Err(error_unsupported_schema_version());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 <Pallet<T>>::create_item(389 self,390 &caller,391 CreateItemData::<T> {392 const_data: Vec::<u8>::from(token_uri)393 .try_into()394 .map_err(|_| "token uri is too long")?,395 properties: BoundedVec::default(),396 owner: to,397 },398 &budget,399 )400 .map_err(dispatch_to_evm::<T>)?;401 Ok(true)402 }403404 405 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {406 Err("not implementable".into())407 }408}409410#[solidity_interface(name = "ERC721UniqueExtensions")]411impl<T: Config> NonfungibleHandle<T> {412 #[weight(<SelfWeightOf<T>>::transfer())]413 fn transfer(414 &mut self,415 caller: caller,416 to: address,417 token_id: uint256,418 _value: value,419 ) -> Result<void> {420 let caller = T::CrossAccountId::from_eth(caller);421 let to = T::CrossAccountId::from_eth(to);422 let token = token_id.try_into()?;423 let budget = self424 .recorder425 .weight_calls_budget(<StructureWeight<T>>::find_parent());426427 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;428 Ok(())429 }430431 #[weight(<SelfWeightOf<T>>::burn_from())]432 fn burn_from(433 &mut self,434 caller: caller,435 from: address,436 token_id: uint256,437 _value: value,438 ) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let from = T::CrossAccountId::from_eth(from);441 let token = token_id.try_into()?;442 let budget = self443 .recorder444 .weight_calls_budget(<StructureWeight<T>>::find_parent());445446 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)447 .map_err(dispatch_to_evm::<T>)?;448 Ok(())449 }450451 fn next_token_id(&self) -> Result<uint256> {452 self.consume_store_reads(1)?;453 Ok(<TokensMinted<T>>::get(self.id)454 .checked_add(1)455 .ok_or("item id overflow")?456 .into())457 }458459 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]460 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {461 let caller = T::CrossAccountId::from_eth(caller);462 let to = T::CrossAccountId::from_eth(to);463 let mut expected_index = <TokensMinted<T>>::get(self.id)464 .checked_add(1)465 .ok_or("item id overflow")?;466 let budget = self467 .recorder468 .weight_calls_budget(<StructureWeight<T>>::find_parent());469470 let total_tokens = token_ids.len();471 for id in token_ids.into_iter() {472 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;473 if id != expected_index {474 return Err("item id should be next".into());475 }476 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;477 }478 let data = (0..total_tokens)479 .map(|_| CreateItemData::<T> {480 const_data: BoundedVec::default(),481 properties: BoundedVec::default(),482 owner: to.clone(),483 })484 .collect();485486 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)487 .map_err(dispatch_to_evm::<T>)?;488 Ok(true)489 }490491 #[solidity(rename_selector = "mintBulkWithTokenURI")]492 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]493 fn mint_bulk_with_token_uri(494 &mut self,495 caller: caller,496 to: address,497 tokens: Vec<(uint256, string)>,498 ) -> Result<bool> {499 if !matches!(self.schema_version, SchemaVersion::ImageURL) {500 return Err(error_unsupported_schema_version());501 }502503 let caller = T::CrossAccountId::from_eth(caller);504 let to = T::CrossAccountId::from_eth(to);505 let mut expected_index = <TokensMinted<T>>::get(self.id)506 .checked_add(1)507 .ok_or("item id overflow")?;508 let budget = self509 .recorder510 .weight_calls_budget(<StructureWeight<T>>::find_parent());511512 let mut data = Vec::with_capacity(tokens.len());513 for (id, token_uri) in tokens {514 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;515 if id != expected_index {516 return Err("item id should be next".into());517 }518 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;519520 data.push(CreateItemData::<T> {521 const_data: Vec::<u8>::from(token_uri)522 .try_into()523 .map_err(|_| "token uri is too long")?,524 properties: BoundedVec::default(),525 owner: to.clone(),526 });527 }528529 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)530 .map_err(dispatch_to_evm::<T>)?;531 Ok(true)532 }533}534535#[solidity_interface(536 name = "UniqueNFT",537 is(538 ERC721,539 ERC721Metadata,540 ERC721Enumerable,541 ERC721UniqueExtensions,542 ERC721Mintable,543 ERC721Burnable,544 via("CollectionHandle<T>", common_mut, CollectionProperties),545 TokenProperties,546 )547)]548impl<T: Config> NonfungibleHandle<T> {}549550551generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);552generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);553554impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {555 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");556557 fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult> {558 call::<T, UniqueNFTCall<T>, _>(*source, self, value, input)559 }560}