1use core::{2 char::{REPLACEMENT_CHARACTER, decode_utf16},3 convert::TryInto,4};5use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};6use frame_support::BoundedVec;7use nft_data_structs::TokenId;8use pallet_evm_coder_substrate::dispatch_to_evm;9use sp_core::{H160, U256};10use sp_std::{vec::Vec, vec};11use pallet_common::{12 account::CrossAccountId,13 erc::{CommonEvmHandler, PrecompileResult},14};15use pallet_evm_coder_substrate::call;16use pallet_common::erc::PrecompileOutput;1718use crate::{19 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,20 SelfWeightOf, weights::WeightInfo,21};2223#[derive(ToLog)]24pub enum ERC721Events {25 Transfer {26 #[indexed]27 from: address,28 #[indexed]29 to: address,30 #[indexed]31 token_id: uint256,32 },33 Approval {34 #[indexed]35 owner: address,36 #[indexed]37 approved: address,38 #[indexed]39 token_id: uint256,40 },41 #[allow(dead_code)]42 ApprovalForAll {43 #[indexed]44 owner: address,45 #[indexed]46 operator: address,47 approved: bool,48 },49}5051#[derive(ToLog)]52pub enum ERC721MintableEvents {53 #[allow(dead_code)]54 MintingFinished {},55}5657#[solidity_interface(name = "ERC721Metadata")]58impl<T: Config> NonfungibleHandle<T> {59 fn name(&self) -> Result<string> {60 Ok(decode_utf16(self.name.iter().copied())61 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))62 .collect::<string>())63 }64 fn symbol(&self) -> Result<string> {65 Ok(string::from_utf8_lossy(&self.token_prefix).into())66 }6768 69 #[solidity(rename_selector = "tokenURI")]70 fn token_uri(&self, token_id: uint256) -> Result<string> {71 self.consume_store_reads(1)?;72 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;73 Ok(string::from_utf8_lossy(74 &<TokenData<T>>::get((self.id, token_id))75 .ok_or("token not found")?76 .const_data,77 )78 .into())79 }80}8182#[solidity_interface(name = "ERC721Enumerable")]83impl<T: Config> NonfungibleHandle<T> {84 fn token_by_index(&self, index: uint256) -> Result<uint256> {85 Ok(index)86 }8788 89 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {90 91 Err("not implemented".into())92 }9394 fn total_supply(&self) -> Result<uint256> {95 self.consume_store_reads(1)?;96 Ok(<Pallet<T>>::total_supply(self).into())97 }98}99100#[solidity_interface(name = "ERC721", events(ERC721Events))]101impl<T: Config> NonfungibleHandle<T> {102 fn balance_of(&self, owner: address) -> Result<uint256> {103 self.consume_store_reads(1)?;104 let owner = T::CrossAccountId::from_eth(owner);105 let balance = <AccountBalance<T>>::get((self.id, owner));106 Ok(balance.into())107 }108 fn owner_of(&self, token_id: uint256) -> Result<address> {109 self.consume_store_reads(1)?;110 let token: TokenId = token_id.try_into()?;111 Ok(*<TokenData<T>>::get((self.id, token))112 .ok_or("token not found")?113 .owner114 .as_eth())115 }116 117 fn safe_transfer_from_with_data(118 &mut self,119 _from: address,120 _to: address,121 _token_id: uint256,122 _data: bytes,123 _value: value,124 ) -> Result<void> {125 126 Err("not implemented".into())127 }128 129 fn safe_transfer_from(130 &mut self,131 _from: address,132 _to: address,133 _token_id: uint256,134 _value: value,135 ) -> Result<void> {136 137 Err("not implemented".into())138 }139140 #[weight(<SelfWeightOf<T>>::transfer_from())]141 fn transfer_from(142 &mut self,143 caller: caller,144 from: address,145 to: address,146 token_id: uint256,147 _value: value,148 ) -> Result<void> {149 let caller = T::CrossAccountId::from_eth(caller);150 let from = T::CrossAccountId::from_eth(from);151 let to = T::CrossAccountId::from_eth(to);152 let token = token_id.try_into()?;153154 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token)155 .map_err(dispatch_to_evm::<T>)?;156 Ok(())157 }158159 #[weight(<SelfWeightOf<T>>::approve())]160 fn approve(161 &mut self,162 caller: caller,163 approved: address,164 token_id: uint256,165 _value: value,166 ) -> Result<void> {167 let caller = T::CrossAccountId::from_eth(caller);168 let approved = T::CrossAccountId::from_eth(approved);169 let token = token_id.try_into()?;170171 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))172 .map_err(dispatch_to_evm::<T>)?;173 Ok(())174 }175176 177 fn set_approval_for_all(178 &mut self,179 _caller: caller,180 _operator: address,181 _approved: bool,182 ) -> Result<void> {183 184 Err("not implemented".into())185 }186187 188 fn get_approved(&self, _token_id: uint256) -> Result<address> {189 190 Err("not implemented".into())191 }192193 194 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {195 196 Err("not implemented".into())197 }198}199200#[solidity_interface(name = "ERC721Burnable")]201impl<T: Config> NonfungibleHandle<T> {202 #[weight(<SelfWeightOf<T>>::burn_item())]203 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {204 let caller = T::CrossAccountId::from_eth(caller);205 let token = token_id.try_into()?;206207 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;208 Ok(())209 }210}211212#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]213impl<T: Config> NonfungibleHandle<T> {214 fn minting_finished(&self) -> Result<bool> {215 Ok(false)216 }217218 219 220 #[weight(<SelfWeightOf<T>>::create_item())]221 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {222 let caller = T::CrossAccountId::from_eth(caller);223 let to = T::CrossAccountId::from_eth(to);224 let token_id: u32 = token_id.try_into()?;225 if <TokensMinted<T>>::get(self.id)226 .checked_add(1)227 .ok_or("item id overflow")?228 != token_id229 {230 return Err("item id should be next".into());231 }232233 <Pallet<T>>::create_item(234 self,235 &caller,236 CreateItemData {237 const_data: BoundedVec::default(),238 variable_data: BoundedVec::default(),239 owner: to,240 },241 )242 .map_err(dispatch_to_evm::<T>)?;243244 Ok(true)245 }246247 248 249 #[solidity(rename_selector = "mintWithTokenURI")]250 #[weight(<SelfWeightOf<T>>::create_item())]251 fn mint_with_token_uri(252 &mut self,253 caller: caller,254 to: address,255 token_id: uint256,256 token_uri: string,257 ) -> Result<bool> {258 let caller = T::CrossAccountId::from_eth(caller);259 let to = T::CrossAccountId::from_eth(to);260 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;261 if <TokensMinted<T>>::get(self.id)262 .checked_add(1)263 .ok_or("item id overflow")?264 != token_id265 {266 return Err("item id should be next".into());267 }268269 <Pallet<T>>::create_item(270 self,271 &caller,272 CreateItemData {273 const_data: Vec::<u8>::from(token_uri)274 .try_into()275 .map_err(|_| "token uri is too long")?,276 variable_data: BoundedVec::default(),277 owner: to,278 },279 )280 .map_err(dispatch_to_evm::<T>)?;281 Ok(true)282 }283284 285 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {286 Err("not implementable".into())287 }288}289290#[solidity_interface(name = "ERC721UniqueExtensions")]291impl<T: Config> NonfungibleHandle<T> {292 #[weight(<SelfWeightOf<T>>::transfer())]293 fn transfer(294 &mut self,295 caller: caller,296 to: address,297 token_id: uint256,298 _value: value,299 ) -> Result<void> {300 let caller = T::CrossAccountId::from_eth(caller);301 let to = T::CrossAccountId::from_eth(to);302 let token = token_id.try_into()?;303304 <Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;305 Ok(())306 }307308 #[weight(<SelfWeightOf<T>>::burn_from())]309 fn burn_from(310 &mut self,311 caller: caller,312 from: address,313 token_id: uint256,314 _value: value,315 ) -> Result<void> {316 let caller = T::CrossAccountId::from_eth(caller);317 let from = T::CrossAccountId::from_eth(from);318 let token = token_id.try_into()?;319320 <Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;321 Ok(())322 }323324 fn next_token_id(&self) -> Result<uint256> {325 self.consume_store_reads(1)?;326 Ok(<TokensMinted<T>>::get(self.id)327 .checked_add(1)328 .ok_or("item id overflow")?329 .into())330 }331332 #[weight(<SelfWeightOf<T>>::set_variable_metadata(data.len() as u32))]333 fn set_variable_metadata(334 &mut self,335 caller: caller,336 token_id: uint256,337 data: bytes,338 ) -> Result<void> {339 let caller = T::CrossAccountId::from_eth(caller);340 let token = token_id.try_into()?;341342 <Pallet<T>>::set_variable_metadata(self, &caller, token, data)343 .map_err(dispatch_to_evm::<T>)?;344 Ok(())345 }346347 fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {348 self.consume_store_reads(1)?;349 let token: TokenId = token_id.try_into()?;350351 Ok(<TokenData<T>>::get((self.id, token))352 .ok_or("token not found")?353 .variable_data)354 }355356 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]357 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {358 let caller = T::CrossAccountId::from_eth(caller);359 let to = T::CrossAccountId::from_eth(to);360 let mut expected_index = <TokensMinted<T>>::get(self.id)361 .checked_add(1)362 .ok_or("item id overflow")?;363364 let total_tokens = token_ids.len();365 for id in token_ids.into_iter() {366 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;367 if id != expected_index {368 return Err("item id should be next".into());369 }370 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;371 }372 let data = (0..total_tokens)373 .map(|_| CreateItemData {374 const_data: BoundedVec::default(),375 variable_data: BoundedVec::default(),376 owner: to.clone(),377 })378 .collect();379380 <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;381 Ok(true)382 }383384 #[solidity(rename_selector = "mintBulkWithTokenURI")]385 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]386 fn mint_bulk_with_token_uri(387 &mut self,388 caller: caller,389 to: address,390 tokens: Vec<(uint256, string)>,391 ) -> Result<bool> {392 let caller = T::CrossAccountId::from_eth(caller);393 let to = T::CrossAccountId::from_eth(to);394 let mut expected_index = <TokensMinted<T>>::get(self.id)395 .checked_add(1)396 .ok_or("item id overflow")?;397398 let mut data = Vec::with_capacity(tokens.len());399 for (id, token_uri) in tokens {400 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;401 if id != expected_index {402 panic!("item id should be next ({}) but got {}", expected_index, id);403 }404 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;405406 data.push(CreateItemData {407 const_data: Vec::<u8>::from(token_uri)408 .try_into()409 .map_err(|_| "token uri is too long")?,410 variable_data: vec![].try_into().unwrap(),411 owner: to.clone(),412 });413 }414415 <Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;416 Ok(true)417 }418}419420#[solidity_interface(421 name = "UniqueNFT",422 is(423 ERC721,424 ERC721Metadata,425 ERC721Enumerable,426 ERC721UniqueExtensions,427 ERC721Mintable,428 ERC721Burnable,429 )430)]431impl<T: Config> NonfungibleHandle<T> {}432433434generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);435generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);436437impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {438 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");439440 fn call(self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileResult> {441 call::<T, UniqueNFTCall<T>, _>(*source, self, value, input)442 }443}