1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use alloc::format;6use crate::{7 Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,8 ItemListIndex,9};10use frame_support::storage::{StorageMap, StorageDoubleMap};11use pallet_evm::AddressMapping;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 fn token_uri(&self, token_id: uint256) -> Result<string> {50 51 Ok(format!("unique.network/{}/{}", self.id, token_id))52 }53}5455#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]56impl<T: Config> CollectionHandle<T> {57 fn token_by_index(&self, index: uint256) -> Result<uint256> {58 Ok(index)59 }6061 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {62 63 Err("not implemented".into())64 }65}6667#[derive(ToLog)]68pub enum ERC721Events {69 Transfer {70 #[indexed]71 from: address,72 #[indexed]73 to: address,74 #[indexed]75 token_id: uint256,76 },77 Approval {78 #[indexed]79 owner: address,80 #[indexed]81 approved: address,82 #[indexed]83 token_id: uint256,84 },85 #[allow(dead_code)]86 ApprovalForAll {87 #[indexed]88 owner: address,89 #[indexed]90 operator: address,91 approved: bool,92 },93}9495#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]96impl<T: Config> CollectionHandle<T> {97 #[solidity(rename_selector = "balanceOf")]98 fn balance_of_nft(&self, owner: address) -> Result<uint256> {99 let owner = T::EvmAddressMapping::into_account_id(owner);100 let balance = <Balance<T>>::get(self.id, owner);101 Ok(balance.into())102 }103 fn owner_of(&self, token_id: uint256) -> Result<address> {104 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;105 let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;106 Ok(*token.owner.as_eth())107 }108 fn safe_transfer_from_with_data(109 &mut self,110 _from: address,111 _to: address,112 _token_id: uint256,113 _data: bytes,114 _value: value,115 ) -> Result<void> {116 117 Err("not implemented".into())118 }119 fn safe_transfer_from(120 &mut self,121 _from: address,122 _to: address,123 _token_id: uint256,124 _value: value,125 ) -> Result<void> {126 127 Err("not implemented".into())128 }129130 fn transfer_from(131 &mut self,132 caller: caller,133 from: address,134 to: address,135 token_id: uint256,136 _value: value,137 ) -> Result<void> {138 let caller = T::CrossAccountId::from_eth(caller);139 let from = T::CrossAccountId::from_eth(from);140 let to = T::CrossAccountId::from_eth(to);141 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;142143 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)144 .map_err(|_| "transferFrom error")?;145 Ok(())146 }147148 fn approve(149 &mut self,150 caller: caller,151 approved: address,152 token_id: uint256,153 _value: value,154 ) -> Result<void> {155 let caller = T::CrossAccountId::from_eth(caller);156 let approved = T::CrossAccountId::from_eth(approved);157 let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;158159 <Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)160 .map_err(|_| "approve internal")?;161 Ok(())162 }163164 fn set_approval_for_all(165 &mut self,166 _caller: caller,167 _operator: address,168 _approved: bool,169 ) -> Result<void> {170 171 Err("not implemented".into())172 }173174 fn get_approved(&self, _token_id: uint256) -> Result<address> {175 176 Err("not implemented".into())177 }178179 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {180 181 Err("not implemented".into())182 }183}184185#[solidity_interface(name = "ERC721Burnable")]186impl<T: Config> CollectionHandle<T> {187 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {188 let caller = T::CrossAccountId::from_eth(caller);189 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;190191 <Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;192 Ok(())193 }194}195196#[derive(ToLog)]197pub enum ERC721MintableEvents {198 #[allow(dead_code)]199 MintingFinished {},200}201202#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]203impl<T: Config> CollectionHandle<T> {204 fn minting_finished(&self) -> Result<bool> {205 Ok(false)206 }207208 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {209 let caller = T::CrossAccountId::from_eth(caller);210 let to = T::CrossAccountId::from_eth(to);211 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;212 if <ItemListIndex>::get(self.id)213 .checked_add(1)214 .ok_or("item id overflow")?215 != token_id216 {217 return Err("item id should be next".into());218 }219220 <Module<T>>::create_item_internal(221 &caller,222 &self,223 &to,224 CreateItemData::NFT(CreateNftData {225 const_data: vec![],226 variable_data: vec![],227 }),228 )229 .map_err(|_| "mint error")?;230 Ok(true)231 }232233 #[solidity(rename_selector = "mintWithTokenURI")]234 fn mint_with_token_uri(235 &mut self,236 caller: caller,237 to: address,238 token_id: uint256,239 token_uri: string,240 ) -> Result<bool> {241 let caller = T::CrossAccountId::from_eth(caller);242 let to = T::CrossAccountId::from_eth(to);243 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;244 if <ItemListIndex>::get(self.id)245 .checked_add(1)246 .ok_or("item id overflow")?247 != token_id248 {249 return Err("item id should be next".into());250 }251252 <Module<T>>::create_item_internal(253 &caller,254 &self,255 &to,256 CreateItemData::NFT(CreateNftData {257 const_data: token_uri.into(),258 variable_data: vec![],259 }),260 )261 .map_err(|_| "mint error")?;262 Ok(true)263 }264265 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {266 Err("not implementable".into())267 }268}269270#[solidity_interface(name = "ERC721UniqueExtensions")]271impl<T: Config> CollectionHandle<T> {272 #[solidity(rename_selector = "transfer")]273 fn transfer_nft(274 &mut self,275 caller: caller,276 to: address,277 token_id: uint256,278 _value: value,279 ) -> Result<void> {280 let caller = T::CrossAccountId::from_eth(caller);281 let to = T::CrossAccountId::from_eth(to);282 let token_id = token_id.try_into().map_err(|_| "amount overflow")?;283284 <Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)285 .map_err(|_| "transfer error")?;286 Ok(())287 }288289 fn next_token_id(&self) -> Result<uint256> {290 Ok(ItemListIndex::get(self.id)291 .checked_add(1)292 .ok_or("item id overflow")?293 .into())294 }295}296297#[solidity_interface(298 name = "UniqueNFT",299 is(300 ERC165,301 ERC721,302 ERC721Metadata,303 ERC721Enumerable,304 ERC721UniqueExtensions,305 ERC721Mintable,306 ERC721Burnable,307 )308)]309impl<T: Config> CollectionHandle<T> {}310311#[derive(ToLog)]312pub enum ERC20Events {313 Transfer {314 #[indexed]315 from: address,316 #[indexed]317 to: address,318 value: uint256,319 },320 Approval {321 #[indexed]322 owner: address,323 #[indexed]324 spender: address,325 value: uint256,326 },327}328329#[solidity_interface(330 name = "ERC20",331 inline_is(InlineNameSymbol, InlineTotalSupply),332 events(ERC20Events)333)]334impl<T: Config> CollectionHandle<T> {335 fn decimals(&self) -> Result<uint8> {336 Ok(if let CollectionMode::Fungible(decimals) = &self.mode {337 *decimals338 } else {339 unreachable!()340 })341 }342 fn balance_of(&self, owner: address) -> Result<uint256> {343 let owner = T::EvmAddressMapping::into_account_id(owner);344 let balance = <Balance<T>>::get(self.id, owner);345 Ok(balance.into())346 }347 fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {348 let caller = T::CrossAccountId::from_eth(caller);349 let to = T::CrossAccountId::from_eth(to);350 let amount = amount.try_into().map_err(|_| "amount overflow")?;351352 <Module<T>>::transfer_internal(&caller, &to, self, 1, amount)353 .map_err(|_| "transfer error")?;354 Ok(true)355 }356 #[solidity(rename_selector = "transferFrom")]357 fn transfer_from_fungible(358 &mut self,359 caller: caller,360 from: address,361 to: address,362 amount: uint256,363 ) -> Result<bool> {364 let caller = T::CrossAccountId::from_eth(caller);365 let from = T::CrossAccountId::from_eth(from);366 let to = T::CrossAccountId::from_eth(to);367 let amount = amount.try_into().map_err(|_| "amount overflow")?;368369 <Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)370 .map_err(|_| "transferFrom error")?;371 Ok(true)372 }373 #[solidity(rename_selector = "approve")]374 fn approve_fungible(375 &mut self,376 caller: caller,377 spender: address,378 amount: uint256,379 ) -> Result<bool> {380 let caller = T::CrossAccountId::from_eth(caller);381 let spender = T::CrossAccountId::from_eth(spender);382 let amount = amount.try_into().map_err(|_| "amount overflow")?;383384 <Module<T>>::approve_internal(&caller, &spender, self, 1, amount)385 .map_err(|_| "approve internal")?;386 Ok(true)387 }388 fn allowance(&self, owner: address, spender: address) -> Result<uint256> {389 let owner = T::CrossAccountId::from_eth(owner);390 let spender = T::CrossAccountId::from_eth(spender);391392 Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())393 }394}395396#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]397impl<T: Config> CollectionHandle<T> {}398399macro_rules! generate_code {400 ($name:ident, $decl:ident, $is_impl:literal) => {401 #[test]402 #[ignore]403 fn $name() {404 use sp_std::collections::btree_set::BTreeSet;405 let mut out = BTreeSet::new();406 $decl::generate_solidity_interface(&mut out, $is_impl);407 println!("=== SNIP START ===");408 println!("// SPDX-License-Identifier: OTHER");409 println!("// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::{} --exact --nocapture --ignored`", stringify!(name));410 println!();411 println!("pragma solidity >=0.8.0 <0.9.0;");412 println!();413 for b in out {414 println!("{}", b);415 }416 println!("=== SNIP END ===");417 }418 };419}420421422generate_code!(nft_impl, UniqueNFTCall, true);423generate_code!(nft_iface, UniqueNFTCall, false);424425generate_code!(fungible_impl, UniqueFungibleCall, true);426generate_code!(fungible_iface, UniqueFungibleCall, false);