1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use crate::{6 Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,7 ItemListIndex,8};9use frame_support::storage::{StorageMap, StorageDoubleMap};10use pallet_evm::AddressMapping;11use pallet_evm_coder_substrate::dispatch_to_evm;12use super::account::CrossAccountId;13use sp_std::{vec, vec::Vec};1415#[solidity_interface(name = "ERC165")]16impl<T: Config> CollectionHandle<T> {17 fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {18 Ok(match self.mode {19 CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),20 CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),21 _ => false,22 })23 }24}2526#[solidity_interface(name = "InlineNameSymbol")]27impl<T: Config> CollectionHandle<T> {28 fn name(&self) -> Result<string> {29 Ok(decode_utf16(self.name.iter().copied())30 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))31 .collect::<string>())32 }3334 fn symbol(&self) -> Result<string> {35 Ok(string::from_utf8_lossy(&self.token_prefix).into())36 }37}3839#[solidity_interface(name = "InlineTotalSupply")]40impl<T: Config> CollectionHandle<T> {41 fn total_supply(&self) -> Result<uint256> {42 43 Ok(0.into())44 }45}4647#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]48impl<T: Config> CollectionHandle<T> {49 #[solidity(rename_selector = "tokenURI")]50 fn token_uri(&self, token_id: uint256) -> Result<string> {51 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;52 Ok(string::from_utf8_lossy(53 &<NftItemList<T>>::get(self.id, token_id)54 .ok_or("token not found")?55 .const_data,56 )57 .into())58 }59}6061#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]62impl<T: Config> CollectionHandle<T> {63 fn token_by_index(&self, index: uint256) -> Result<uint256> {64 Ok(index)65 }6667 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {68 69 Err("not implemented".into())70 }71}7273#[derive(ToLog)]74pub enum ERC721Events {75 Transfer {76 #[indexed]77 from: address,78 #[indexed]79 to: address,80 #[indexed]81 token_id: uint256,82 },83 Approval {84 #[indexed]85 owner: address,86 #[indexed]87 approved: address,88 #[indexed]89 token_id: uint256,90 },91 #[allow(dead_code)]92 ApprovalForAll {93 #[indexed]94 owner: address,95 #[indexed]96 operator: address,97 approved: bool,98 },99}100101#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]102impl<T: Config> CollectionHandle<T> {103 #[solidity(rename_selector = "balanceOf")]104 fn balance_of_nft(&self, owner: address) -> Result<uint256> {105 let owner = T::EvmAddressMapping::into_account_id(owner);106 let balance = <Balance<T>>::get(self.id, owner);107 Ok(balance.into())108 }109 fn owner_of(&self, token_id: uint256) -> Result<address> {110 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;111 let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;112 Ok(*token.owner.as_eth())113 }114 fn safe_transfer_from_with_data(115 &mut self,116 _from: address,117 _to: address,118 _token_id: uint256,119 _data: bytes,120 _value: value,121 ) -> Result<void> {122 123 Err("not implemented".into())124 }125 fn safe_transfer_from(126 &mut self,127 _from: address,128 _to: address,129 _token_id: uint256,130 _value: value,131 ) -> Result<void> {132 133 Err("not implemented".into())134 }135136 fn transfer_from(137 &mut self,138 caller: caller,139 from: address,140 to: address,141 token_id: uint256,142 _value: value,143 ) -> Result<void> {144 let caller = T::CrossAccountId::from_eth(caller);145 let from = T::CrossAccountId::from_eth(from);146 let to = T::CrossAccountId::from_eth(to);147 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;148149 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)150 .map_err(|_| "transferFrom error")?;151 Ok(())152 }153154 fn approve(155 &mut self,156 caller: caller,157 approved: address,158 token_id: uint256,159 _value: value,160 ) -> Result<void> {161 let caller = T::CrossAccountId::from_eth(caller);162 let approved = T::CrossAccountId::from_eth(approved);163 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;164165 <Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)166 .map_err(|_| "approve internal")?;167 Ok(())168 }169170 fn set_approval_for_all(171 &mut self,172 _caller: caller,173 _operator: address,174 _approved: bool,175 ) -> Result<void> {176 177 Err("not implemented".into())178 }179180 fn get_approved(&self, _token_id: uint256) -> Result<address> {181 182 Err("not implemented".into())183 }184185 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {186 187 Err("not implemented".into())188 }189}190191#[solidity_interface(name = "ERC721Burnable")]192impl<T: Config> CollectionHandle<T> {193 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {194 let caller = T::CrossAccountId::from_eth(caller);195 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;196197 <Module<T>>::burn_item_internal(&caller, &self, token_id, 1, true)198 .map_err(|_| "burn error")?;199 Ok(())200 }201}202203#[derive(ToLog)]204pub enum ERC721MintableEvents {205 #[allow(dead_code)]206 MintingFinished {},207}208209#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]210impl<T: Config> CollectionHandle<T> {211 fn minting_finished(&self) -> Result<bool> {212 Ok(false)213 }214215 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {216 let caller = T::CrossAccountId::from_eth(caller);217 let to = T::CrossAccountId::from_eth(to);218 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;219 if <ItemListIndex>::get(self.id)220 .checked_add(1)221 .ok_or("item id overflow")?222 != token_id223 {224 return Err("item id should be next".into());225 }226227 <Module<T>>::create_item_internal(228 &caller,229 &self,230 &to,231 CreateItemData::NFT(CreateNftData {232 const_data: vec![].try_into().unwrap(),233 variable_data: vec![].try_into().unwrap(),234 }),235 )236 .map_err(|_| "mint error")?;237 Ok(true)238 }239240 #[solidity(rename_selector = "mintWithTokenURI")]241 fn mint_with_token_uri(242 &mut self,243 caller: caller,244 to: address,245 token_id: uint256,246 token_uri: string,247 ) -> Result<bool> {248 let caller = T::CrossAccountId::from_eth(caller);249 let to = T::CrossAccountId::from_eth(to);250 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;251 if <ItemListIndex>::get(self.id)252 .checked_add(1)253 .ok_or("item id overflow")?254 != token_id255 {256 return Err("item id should be next".into());257 }258259 <Module<T>>::create_item_internal(260 &caller,261 &self,262 &to,263 CreateItemData::NFT(CreateNftData {264 const_data: Vec::<u8>::from(token_uri)265 .try_into()266 .map_err(|_| "token uri is too long")?,267 variable_data: vec![].try_into().unwrap(),268 }),269 )270 .map_err(|_| "mint error")?;271 Ok(true)272 }273274 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {275 Err("not implementable".into())276 }277}278279#[solidity_interface(name = "ERC721UniqueExtensions")]280impl<T: Config> CollectionHandle<T> {281 #[solidity(rename_selector = "transfer")]282 fn transfer_nft(283 &mut self,284 caller: caller,285 to: address,286 token_id: uint256,287 _value: value,288 ) -> Result<void> {289 let caller = T::CrossAccountId::from_eth(caller);290 let to = T::CrossAccountId::from_eth(to);291 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;292293 <Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)294 .map_err(|_| "transfer error")?;295 Ok(())296 }297298 fn next_token_id(&self) -> Result<uint256> {299 Ok(ItemListIndex::get(self.id)300 .checked_add(1)301 .ok_or("item id overflow")?302 .into())303 }304305 fn set_variable_metadata(306 &mut self,307 caller: caller,308 token_id: uint256,309 data: bytes,310 ) -> Result<void> {311 let caller = T::CrossAccountId::from_eth(caller);312 let token_id = token_id.try_into().map_err(|_| "token id overflow")?;313314 <Module<T>>::set_variable_meta_data_internal(&caller, self, token_id, data)315 .map_err(dispatch_to_evm::<T>)?;316 Ok(())317 }318319 fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {320 let token_id = token_id.try_into().map_err(|_| "token id overflow")?;321322 <Module<T>>::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::<T>)323 }324325 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {326 let caller = T::CrossAccountId::from_eth(caller);327 let to = T::CrossAccountId::from_eth(to);328 let mut expected_index = <ItemListIndex>::get(self.id)329 .checked_add(1)330 .ok_or("item id overflow")?;331332 let total_tokens = token_ids.len();333 for id in token_ids.into_iter() {334 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;335 if id != expected_index {336 return Err("item id should be next".into());337 }338 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;339 }340341 let data = (0..total_tokens)342 .map(|_| {343 CreateItemData::NFT(CreateNftData {344 const_data: vec![].try_into().unwrap(),345 variable_data: vec![].try_into().unwrap(),346 })347 })348 .collect();349350 <Module<T>>::create_multiple_items_internal(&caller, self, &to, data)351 .map_err(dispatch_to_evm::<T>)?;352 Ok(true)353 }354355 #[solidity(rename_selector = "mintBulkWithTokenURI")]356 fn mint_bulk_with_token_uri(357 &mut self,358 caller: caller,359 to: address,360 tokens: Vec<(uint256, string)>,361 ) -> Result<bool> {362 let caller = T::CrossAccountId::from_eth(caller);363 let to = T::CrossAccountId::from_eth(to);364 let mut expected_index = <ItemListIndex>::get(self.id)365 .checked_add(1)366 .ok_or("item id overflow")?;367368 let mut data = Vec::with_capacity(tokens.len());369 for (id, token_uri) in tokens {370 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;371 if id != expected_index {372 panic!("item id should be next ({}) but got {}", expected_index, id);373 }374 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;375376 data.push(CreateItemData::NFT(CreateNftData {377 const_data: Vec::<u8>::from(token_uri)378 .try_into()379 .map_err(|_| "token uri is too long")?,380 variable_data: vec![].try_into().unwrap(),381 }));382 }383384 <Module<T>>::create_multiple_items_internal(&caller, self, &to, data)385 .map_err(dispatch_to_evm::<T>)?;386 Ok(true)387 }388}389390#[solidity_interface(391 name = "UniqueNFT",392 is(393 ERC165,394 ERC721,395 ERC721Metadata,396 ERC721Enumerable,397 ERC721UniqueExtensions,398 ERC721Mintable,399 ERC721Burnable,400 )401)]402impl<T: Config> CollectionHandle<T> {}403404#[derive(ToLog)]405pub enum ERC20Events {406 Transfer {407 #[indexed]408 from: address,409 #[indexed]410 to: address,411 value: uint256,412 },413 Approval {414 #[indexed]415 owner: address,416 #[indexed]417 spender: address,418 value: uint256,419 },420}421422#[solidity_interface(423 name = "ERC20",424 inline_is(InlineNameSymbol, InlineTotalSupply),425 events(ERC20Events)426)]427impl<T: Config> CollectionHandle<T> {428 fn decimals(&self) -> Result<uint8> {429 Ok(if let CollectionMode::Fungible(decimals) = &self.mode {430 *decimals431 } else {432 unreachable!()433 })434 }435 fn balance_of(&self, owner: address) -> Result<uint256> {436 let owner = T::EvmAddressMapping::into_account_id(owner);437 let balance = <Balance<T>>::get(self.id, owner);438 Ok(balance.into())439 }440 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {441 let caller = T::CrossAccountId::from_eth(caller);442 let to = T::CrossAccountId::from_eth(to);443 let amount = amount.try_into().map_err(|_| "amount overflow")?;444445 <Module<T>>::transfer_internal(&caller, &to, self, 1, amount)446 .map_err(|_| "transfer error")?;447 Ok(true)448 }449 #[solidity(rename_selector = "transferFrom")]450 fn transfer_from_fungible(451 &mut self,452 caller: caller,453 from: address,454 to: address,455 amount: uint256,456 ) -> Result<bool> {457 let caller = T::CrossAccountId::from_eth(caller);458 let from = T::CrossAccountId::from_eth(from);459 let to = T::CrossAccountId::from_eth(to);460 let amount = amount.try_into().map_err(|_| "amount overflow")?;461462 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)463 .map_err(|_| "transferFrom error")?;464 Ok(true)465 }466 #[solidity(rename_selector = "approve")]467 fn approve_fungible(468 &mut self,469 caller: caller,470 spender: address,471 amount: uint256,472 ) -> Result<bool> {473 let caller = T::CrossAccountId::from_eth(caller);474 let spender = T::CrossAccountId::from_eth(spender);475 let amount = amount.try_into().map_err(|_| "amount overflow")?;476477 <Module<T>>::approve_internal(&caller, &spender, self, 1, amount)478 .map_err(|_| "approve internal")?;479 Ok(true)480 }481 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {482 let owner = T::CrossAccountId::from_eth(owner);483 let spender = T::CrossAccountId::from_eth(spender);484485 Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())486 }487}488489#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]490impl<T: Config> CollectionHandle<T> {}491492493generate_stubgen!(nft_impl, UniqueNFTCall, true);494generate_stubgen!(nft_iface, UniqueNFTCall, false);495496generate_stubgen!(fungible_impl, UniqueFungibleCall, true);497generate_stubgen!(fungible_iface, UniqueFungibleCall, false);