1pub mod account;2use account::CrossAccountId;3pub mod abi;4use abi::{AbiReader, AbiWriter};5pub mod log;67use sp_std::borrow::ToOwned;8use sp_std::vec::Vec;9use sp_std::convert::TryInto;1011use codec::{Decode, Encode};12use pallet_evm::{AddressMapping, PrecompileLog, PrecompileOutput, ExitReason, ExitRevert, ExitSucceed};13use sp_core::{H160, H256};14use frame_support::storage::{StorageMap, StorageDoubleMap};1516use crate::{Allowances, NftItemList, Module, Balance, Config, CollectionById, CollectionHandle, CollectionId, CollectionMode};1718pub struct NftErcSupport<T: Config>(core::marker::PhantomData<T>);19202122const ETH_ACCOUNT_PREFIX: [u8; 16] = [0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e];2324fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {25 if ð[0..16] != ETH_ACCOUNT_PREFIX {26 return None;27 }28 let mut id_bytes = [0; 4];29 id_bytes.copy_from_slice(ð[16..20]);30 Some(u32::from_be_bytes(id_bytes))31}32pub fn collection_id_to_address(id: u32) -> H160 {33 let mut out = [0; 20];34 out[0..16].copy_from_slice(Ð_ACCOUNT_PREFIX);35 out[16..20].copy_from_slice(&u32::to_be_bytes(id));36 H160(out)37}3839fn result_to_output(result: Result<AbiWriter, Option<&'static str>>, logs: Vec<PrecompileLog>) -> PrecompileOutput {40 sp_io::storage::start_transaction();41 match result {42 Ok(result) => {43 sp_io::storage::commit_transaction();44 45 PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)46 }47 Err(Some(s)) => {48 sp_io::storage::rollback_transaction();49 50 let mut out = AbiWriter::new_call(0x08c379a0);51 out.string(&s);52 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new())53 }54 Err(None) => {55 sp_io::storage::rollback_transaction();56 PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new())57 }58 }59}6061fn call_internal<T: Config>(sender: H160, collection: &CollectionHandle<T>, method_id: u32, mut input: AbiReader) -> Result<AbiWriter, Option<&'static str>> {62 let erc20 = matches!(collection.mode, CollectionMode::Fungible(_));63 let erc721 = matches!(collection.mode, CollectionMode::NFT);6465 Ok(match method_id {66 67 fn_selector!(name()) => {68 let name = collection.name.iter()69 .map(|&e| e.try_into().ok() as Option<u8>)70 .collect::<Option<Vec<u8>>>()71 .ok_or(Some("non-ascii name"))?;7273 crate::abi_encode!(memory(&name))74 }75 76 fn_selector!(symbol()) => {77 let name = collection.token_prefix.iter()78 .map(|&e| e.is_ascii_uppercase().then(|| e))79 .collect::<Option<Vec<u8>>>()80 .ok_or(Some("non-uppercase prefix"))?;8182 crate::abi_encode!(memory(&name))83 }84 85 fn_selector!(decimals()) if erc20 => {86 if let CollectionMode::Fungible(decimals) = &collection.mode {87 crate::abi_encode!(uint8(*decimals))88 } else {89 unreachable!()90 }91 }92 93 fn_selector!(totalSupply()) if erc20 || erc721 => {94 95 crate::abi_encode!(uint256(0))96 }97 98 fn_selector!(balanceOf(address)) if erc20 || erc721 => {99 crate::abi_decode!(input, account: address);100 let account = T::EvmAddressMapping::into_account_id(account);101 let balance = <Balance<T>>::get(collection.id, account);102 crate::abi_encode!(uint256(balance))103 }104 105 fn_selector!(ownerOf(uint256)) if erc721 => {106 crate::abi_decode!(input, token_id: uint256);107 let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;108109 let token = <NftItemList<T>>::get(collection.id, token_id).ok_or("unknown token")?;110111 crate::abi_encode!(address(token.owner.as_eth().clone()))112 }113 114 fn_selector!(transfer(address, uint256)) if erc20 => {115 crate::abi_decode!(input, recipient: address, amount: uint256);116 let sender = T::CrossAccountId::from_eth(sender);117 let recipient = T::CrossAccountId::from_eth(recipient);118119 <Module<T>>::transfer_internal(120 &sender,121 &recipient,122 &collection,123 1,124 amount,125 ).map_err(|_| "transfer error")?;126127 crate::abi_encode!(bool(true))128 }129130 fn_selector!(transfer(address, uint256)) if erc721 => {131 crate::abi_decode!(input, recipient: address, token_id: uint256);132 let sender = T::CrossAccountId::from_eth(sender);133 let recipient = T::CrossAccountId::from_eth(recipient);134 let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;135136 <Module<T>>::transfer_internal(137 &sender,138 &recipient,139 &collection,140 token_id,141 1,142 ).map_err(|_| "transfer error")?;143144 crate::abi_encode!(bool(true))145 }146147 148 fn_selector!(allowance(address, address)) if erc20 => {149 crate::abi_decode!(input, owner: address, spender: address);150 let owner = T::EvmAddressMapping::into_account_id(owner);151 let spender = T::EvmAddressMapping::into_account_id(spender);152 let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));153 crate::abi_encode!(uint256(allowance))154 }155 156 157 158 fn_selector!(approve(address, uint256)) if erc20 => {159 crate::abi_decode!(input, spender: address, amount: uint256);160 let sender = T::CrossAccountId::from_eth(sender);161 let spender = T::CrossAccountId::from_eth(spender);162163 <Module<T>>::approve_internal(164 &sender,165 &spender,166 &collection,167 1,168 amount,169 ).map_err(|_| "approve error")?;170171 crate::abi_encode!(bool(true))172 }173 174 fn_selector!(approve(address, uint256)) if erc721 => {175 crate::abi_decode!(input, approved: address, token_id: uint256);176 let sender = T::CrossAccountId::from_eth(sender);177 let approved = T::CrossAccountId::from_eth(approved);178 let token_id = token_id.try_into().map_err(|_| "bad token id")?;179180 <Module<T>>::approve_internal(181 &sender,182 &approved,183 &collection,184 token_id,185 1,186 ).map_err(|_| "approve error")?;187 crate::abi_encode!()188 }189 190 fn_selector!(transferFrom(address, address, uint256)) if erc20 => {191 crate::abi_decode!(input, from: address, recipient: address, amount: uint256);192 let sender = T::CrossAccountId::from_eth(sender);193 let from = T::CrossAccountId::from_eth(from);194 let recipient = T::CrossAccountId::from_eth(recipient);195196 <Module<T>>::transfer_from_internal(197 &sender,198 &from,199 &recipient,200 &collection,201 1,202 amount,203 ).map_err(|_| "transfer_from error")?;204205 crate::abi_encode!(bool(true))206 }207 208 fn_selector!(transferFrom(address, address, uint256)) if erc721 => {209 crate::abi_decode!(input, from: address, recipient: address, token_id: uint256);210 let sender = T::CrossAccountId::from_eth(sender);211 let from = T::CrossAccountId::from_eth(from);212 let recipient = T::CrossAccountId::from_eth(recipient);213 let token_id = token_id.try_into().map_err(|_| "bad token id")?;214215 <Module<T>>::transfer_from_internal(216 &sender,217 &from,218 &recipient,219 &collection,220 token_id,221 1,222 ).map_err(|_| "transfer_from error")?;223224 crate::abi_encode!()225 }226 227 fn_selector!(supportsInterface(bytes4)) => {228 crate::abi_decode!(input, interface_id: uint32);229 let supports = match interface_id {230 231 0x01ffc9a7 => true,232 233 0x36372b07 if erc20 => true,234 235 0x80ac58cd if erc721 => true,236 _ => false,237 };238 crate::abi_encode!(bool(supports))239 }240 _ => return Err(None)241 })242}243244impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {245 fn is_reserved(target: &H160) -> bool {246 map_eth_to_id(target).is_some()247 }248 fn is_used(target: &H160) -> bool {249 map_eth_to_id(target)250 .map(<CollectionById<T>>::contains_key)251 .unwrap_or(false)252 }253 fn get_code(target: &H160) -> Option<Vec<u8>> {254 map_eth_to_id(&target)255 .and_then(<CollectionById<T>>::get)256 .map(|collection| {257 match collection.mode {258 CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],259 CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],260 CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],261 CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],262 }.to_owned()263 })264 }265 fn call(266 source: &H160,267 target: &H160,268 input: &[u8],269 ) -> Option<PrecompileOutput> {270 let collection = map_eth_to_id(&target)271 .and_then(<CollectionHandle<T>>::get)?;272 let (method_id, input) = AbiReader::new_call(input).unwrap();273 let result = call_internal(*source, &collection, method_id, input);274 Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))275 }276}277278pub const TRANSFER_NFT_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));279pub const APPROVAL_NFT_TOPIC: H256 = event_topic!(Approval(address, address, uint256));280281282pub const TRANSFER_FUNGIBLE_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));283pub const APPROVAL_FUNGIBLE_TOPIC: H256 = event_topic!(Approval(address, address, uint256));284285pub fn address_to_topic(address: &H160) -> H256 {286 let mut output = [0; 32];287 output[12..32].copy_from_slice(&address.0);288 H256(output)289}290291pub fn u32_to_topic(id: u32) -> H256 {292 let mut output = [0; 32];293 output[28..32].copy_from_slice(&id.to_be_bytes());294 H256(output)295}296297298299pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {300 let contract = collection_id_to_address(collection_id);301302 303 304 #[cfg(feature = "std")]305 {306 let signed = ethereum_tx_sign::RawTransaction {307 nonce: 0.into(),308 to: Some(contract.0.into()),309 value: 0.into(),310 gas_price: 0.into(),311 gas: 0.into(),312 313 data: Vec::from([0, 0, 0, 0]),314 }.sign(315 316 317 318 &hex_literal::hex!("9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a").into(),319 &chain_id320 );321 rlp::decode::<ethereum::Transaction>(&signed).expect("transaction is just created, it can't be broken")322 }323 #[cfg(not(feature = "std"))]324 {325 panic!("transaction generation not yet supported by wasm runtime")326 }327}