git.delta.rocks / unique-network / refs/commits / d612b7eed2eb

difftreelog

refactor switch to macro-based ERC impls

Yaroslav Bolyukin2021-06-02parent: #1a0920c.patch.diff
in: master

2 files changed

addedpallets/nft/src/eth/erc_impl.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/eth/erc_impl.rs
@@ -0,0 +1,283 @@
+use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
+use evm_coder::{
+	abi::{AbiWriter, StringError},
+	types::*,
+};
+use core::convert::TryInto;
+use alloc::format;
+use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList};
+use frame_support::storage::StorageDoubleMap;
+use pallet_evm::AddressMapping;
+use super::erc::*;
+use super::account::CrossAccountId;
+
+type Result<T> = core::result::Result<T, StringError>;
+
+impl<T: Config> InlineNameSymbol for CollectionHandle<T> {
+	type Error = StringError;
+
+	fn name(&self) -> Result<string> {
+		Ok(decode_utf16(self.name.iter().copied())
+			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
+			.collect::<string>())
+	}
+
+	fn symbol(&self) -> Result<string> {
+		Ok(string::from_utf8_lossy(&self.token_prefix).into())
+	}
+}
+
+impl<T: Config> InlineTotalSupply for CollectionHandle<T> {
+	type Error = StringError;
+
+	fn total_supply(&self) -> Result<uint256> {
+		// TODO: we do not track total amount of all tokens
+		Ok(0.into())
+	}
+}
+
+impl<T: Config> ERC721Metadata for CollectionHandle<T> {
+	type Error = StringError;
+
+	fn token_uri(&self, token_id: uint256) -> Result<string> {
+		// TODO: We should standartize url prefix, maybe via offchain schema?
+		Ok(format!("unique.network/{}/{}", self.id, token_id))
+	}
+
+	fn call_inline_name_symbol(&mut self, c: Msg<InlineNameSymbolCall>) -> Result<AbiWriter> {
+		<Self as InlineNameSymbol>::call(self, c)
+	}
+}
+
+impl<T: Config> ERC721Enumerable for CollectionHandle<T> {
+	type Error = StringError;
+
+	fn token_by_index(&self, index: uint256) -> Result<uint256> {
+		Ok(index)
+	}
+
+	fn token_of_owner_by_index(&self, owner: address, index: uint256) -> Result<uint256> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {
+		<Self as InlineTotalSupply>::call(self, c)
+	}
+}
+
+impl<T: Config> ERC721 for CollectionHandle<T> {
+	type Error = StringError;
+
+	fn balance_of(&self, owner: address) -> Result<uint256> {
+		let owner = T::EvmAddressMapping::into_account_id(owner);
+		let balance = <Balance<T>>::get(self.id, owner);
+		Ok(balance.into())
+	}
+	fn owner_of(&self, token_id: uint256) -> Result<address> {
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;
+		Ok(token.owner.as_eth().clone())
+	}
+	fn safe_transfer_from_with_data(
+		&mut self,
+		_from: address,
+		_to: address,
+		_token_id: uint256,
+		_data: bytes,
+		_value: value,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+	fn safe_transfer_from(
+		&mut self,
+		_from: address,
+		_to: address,
+		_token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn transfer_from(
+		&mut self,
+		caller: caller,
+		from: address,
+		to: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;
+
+		<Module<T>>::transfer_from_internal(&caller, &from, &to, &self, token_id, 1)
+			.map_err(|_| "transferFrom error")?;
+		Ok(())
+	}
+
+	fn approve(
+		&mut self,
+		caller: caller,
+		approved: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let approved = T::CrossAccountId::from_eth(approved);
+		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;
+
+		<Module<T>>::approve_internal(&caller, &approved, &self, token_id, 1)
+			.map_err(|_| "approve internal")?;
+		Ok(())
+	}
+
+	fn set_approval_for_all(
+		&mut self,
+		_caller: caller,
+		_operator: address,
+		_approved: bool,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn get_approved(&self, _token_id: uint256) -> Result<address> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+		let ERC165Call::SupportsInterface { interface_id } = c.call;
+		Ok(evm_coder::abi_encode!(bool(
+			&ERC721Call::supports_interface(interface_id)
+		)))
+	}
+}
+
+impl<T: Config> ERC721UniqueExtensions for CollectionHandle<T> {
+	type Error = StringError;
+	fn transfer(
+		&mut self,
+		caller: caller,
+		to: address,
+		token_id: uint256,
+		value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;
+
+		<Module<T>>::transfer_internal(&caller, &to, &self, token_id, 1)
+			.map_err(|_| "transfer error")?;
+		Ok(())
+	}
+}
+
+impl<T: Config> UniqueNFT for CollectionHandle<T> {
+	type Error = StringError;
+	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+		let ERC165Call::SupportsInterface { interface_id } = c.call;
+		Ok(evm_coder::abi_encode!(bool(
+			&UniqueNFTCall::supports_interface(interface_id)
+		)))
+	}
+	fn call_erc721(&mut self, c: Msg<ERC721Call>) -> Result<AbiWriter> {
+		<Self as ERC721>::call(self, c)
+	}
+	fn call_erc721_metadata(&mut self, c: Msg<ERC721MetadataCall>) -> Result<AbiWriter> {
+		<Self as ERC721Metadata>::call(self, c)
+	}
+	fn call_erc721_enumerable(&mut self, c: Msg<ERC721EnumerableCall>) -> Result<AbiWriter> {
+		<Self as ERC721Enumerable>::call(self, c)
+	}
+	fn call_erc721_unique_extensions(
+		&mut self,
+		c: Msg<ERC721UniqueExtensionsCall>,
+	) -> Result<AbiWriter> {
+		<Self as ERC721UniqueExtensions>::call(self, c)
+	}
+}
+
+impl<T: Config> ERC20 for CollectionHandle<T> {
+	type Error = StringError;
+	fn decimals(&self) -> Result<uint8> {
+		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {
+			*decimals
+		} else {
+			unreachable!()
+		})
+	}
+	fn balance_of(&self, owner: address) -> Result<uint256> {
+		let owner = T::EvmAddressMapping::into_account_id(owner);
+		let balance = <Balance<T>>::get(self.id, owner);
+		Ok(balance.into())
+	}
+	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Module<T>>::transfer_internal(&caller, &to, &self, 1, amount)
+			.map_err(|_| "transfer error")?;
+		Ok(true)
+	}
+	fn transfer_from(
+		&mut self,
+		caller: caller,
+		from: address,
+		to: address,
+		amount: uint256,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Module<T>>::transfer_from_internal(&caller, &from, &to, &self, 1, amount)
+			.map_err(|_| "transferFrom error")?;
+		Ok(true)
+	}
+	fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let spender = T::CrossAccountId::from_eth(spender);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+		<Module<T>>::approve_internal(&caller, &spender, &self, 1, amount)
+			.map_err(|_| "approve internal")?;
+		Ok(true)
+	}
+	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {
+		let owner = T::CrossAccountId::from_eth(owner);
+		let spender = T::CrossAccountId::from_eth(spender);
+
+		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())
+	}
+	fn call_inline_name_symbol(&mut self, c: Msg<InlineNameSymbolCall>) -> Result<AbiWriter> {
+		<Self as InlineNameSymbol>::call(self, c)
+	}
+	fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {
+		<Self as InlineTotalSupply>::call(self, c)
+	}
+}
+
+impl<T: Config> UniqueFungible for CollectionHandle<T> {
+	type Error = StringError;
+	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {
+		let ERC165Call::SupportsInterface { interface_id } = c.call;
+		Ok(evm_coder::abi_encode!(bool(
+			&UniqueNFTCall::supports_interface(interface_id)
+		)))
+	}
+	fn call_erc20(&mut self, c: Msg<ERC20Call>) -> Result<AbiWriter> {
+		<Self as ERC20>::call(self, c)
+	}
+}
modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
before · pallets/nft/src/eth/mod.rs
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>);1920// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection21// TODO: Unhardcode prefix22const 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 &eth[0..16] != ETH_ACCOUNT_PREFIX {26		return None;27	}28	let mut id_bytes = [0; 4];29	id_bytes.copy_from_slice(&eth[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(&ETH_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			// TODO: weight45			PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)46		}47		Err(Some(s)) => {48			sp_io::storage::rollback_transaction();49			// Error(string)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		// function name() external view returns (string memory)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		// function symbol() external view returns (string memory)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		// function decimals() external view returns (uint8 decimals)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		// function totalSupply() external view returns (uint256)93		fn_selector!(totalSupply()) if erc20 || erc721 => {94			// TODO: can't be implemented, as we don't track total amount of fungibles95			crate::abi_encode!(uint256(0))96		}97		// function balanceOf(address account) external view returns (uint256)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		// function ownerOf(uint256 tokenId) external view returns (address)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		// function transfer(address recipient, uint256 amount) external returns (bool) {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		// function allowance(address owner, address spender) external view returns (uint256)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		// function approve(address spender, uint256 amount) external returns (bool)156		// FIXME: All current implementations resets amount to specified value, ours - adds it157		// FIXME: Our implementation doesn't handle resets (approve with zero amount)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		// function approve(address approved, uint256 tokenId) external payable174		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		// function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)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		// function transferFrom(address from, address to, uint256 tokenId) external payable208		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		// function supportsInterface(bytes4 interfaceID) public pure returns (bool)227		fn_selector!(supportsInterface(bytes4)) => {228			crate::abi_decode!(input, interface_id: uint32);229			let supports = match interface_id {230				// ERC165231				0x01ffc9a7 => true,232				// ERC20233				0x36372b07 if erc20 => true,234				// ERC721235				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));280// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);281282pub 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}296297298// TODO: This function is slow, and output can be memoized299pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {300	let contract = collection_id_to_address(collection_id);301302	// TODO: Make it work without native runtime by forking ethereum_tx_sign, and303	// switching to pure-rust implementation of secp256k1304	#[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			// zero selector, this transaction always have same sender, so all data should be acquired from logs313			data: Vec::from([0, 0, 0, 0]),314		}.sign(315			// TODO: move to pallet config316			// 0xF70631E55faff9f3FD3681545aa6c724226a3853317			// 9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a318			&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}
after · pallets/nft/src/eth/mod.rs
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>);1920// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection21// TODO: Unhardcode prefix22const 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 &eth[0..16] != ETH_ACCOUNT_PREFIX {26		return None;27	}28	let mut id_bytes = [0; 4];29	id_bytes.copy_from_slice(&eth[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(&ETH_ACCOUNT_PREFIX);35	out[16..20].copy_from_slice(&u32::to_be_bytes(id));36	H160(out)37}3839fn call_internal<T: Config>(40	collection: &mut CollectionHandle<T>,41	caller: caller,42	method_id: u32,43	mut input: AbiReader,44	value: U256,45) -> Result<Option<AbiWriter>, evm_coder::abi::StringError> {46	match collection.mode.clone() {47		CollectionMode::Fungible(_) => {48			#[cfg(feature = "std")]49			{50				println!("Parse fungible call {:x}", method_id);51			}52			let call = match UniqueFungibleCall::parse(method_id, &mut input)? {53				Some(v) => v,54				None => {55					#[cfg(feature = "std")]56					{57						println!("Method not found");58					}59					return Ok(None);60				}61			};62			#[cfg(feature = "std")]63			{64				dbg!(&call);65			}66			Ok(Some(<CollectionHandle<T> as UniqueFungible>::call(67				collection,68				Msg {69					call,70					caller,71					value,72				},73			)?))74		}75		CollectionMode::NFT => {76			let call = match UniqueNFTCall::parse(method_id, &mut input)? {77				Some(v) => v,78				None => return Ok(None),79			};80			Ok(Some(<CollectionHandle<T> as UniqueNFT>::call(81				collection,82				Msg {83					call,84					caller,85					value,86				},87			)?))88		}89		_ => {90			return Err(StringError::from(91				"erc calls only supported to fungible and nft collections for now",92			)93			.into())94		}95	}96}9798impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {99	fn is_reserved(target: &H160) -> bool {100		map_eth_to_id(target).is_some()101	}102	fn is_used(target: &H160) -> bool {103		map_eth_to_id(target)104			.map(<CollectionById<T>>::contains_key)105			.unwrap_or(false)106	}107	fn get_code(target: &H160) -> Option<Vec<u8>> {108		map_eth_to_id(&target)109			.and_then(<CollectionById<T>>::get)110			.map(|collection| {111				match collection.mode {112					CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],113					CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],114					CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],115					CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],116				}.to_owned()117			})118	}119	fn call(120		source: &H160,121		target: &H160,122		gas_limit: u64,123		input: &[u8],124		value: U256,125	) -> Option<PrecompileOutput> {126		let mut collection = map_eth_to_id(&target)127			.and_then(|id| <CollectionHandle<T>>::get_with_gas_limit(id, gas_limit))?;128		let (method_id, input) = AbiReader::new_call(input).unwrap();129		let result = call_internal(&mut collection, *source, method_id, input, value);130		let cost = gas_limit - collection.gas_left();131		let logs = collection.logs.retrieve_logs();132		match result {133			Ok(Some(v)) => Some(PrecompileOutput {134				exit_status: ExitReason::Succeed(ExitSucceed::Returned),135				cost,136				logs,137				output: v.finish(),138			}),139			Ok(None) => None,140			Err(e) => Some(PrecompileOutput {141				exit_status: ExitReason::Revert(ExitRevert::Reverted),142				cost: 0,143				logs: Default::default(),144				output: AbiWriter::from(e).finish(),145			}),146		}147	}148}149150// TODO: This function is slow, and output can be memoized151pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {152	let contract = collection_id_to_address(collection_id);153154	// TODO: Make it work without native runtime by forking ethereum_tx_sign, and155	// switching to pure-rust implementation of secp256k1156	#[cfg(feature = "std")]157	{158		let signed = ethereum_tx_sign::RawTransaction {159			nonce: 0.into(),160			to: Some(contract.0.into()),161			value: 0.into(),162			gas_price: 0.into(),163			gas: 0.into(),164			// zero selector, this transaction always have same sender, so all data should be acquired from logs165			data: Vec::from([0, 0, 0, 0]),166		}.sign(167			// TODO: move to pallet config168			// 0xF70631E55faff9f3FD3681545aa6c724226a3853169			// 9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a170			&hex_literal::hex!("9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a").into(),171			&chain_id172		);173		rlp::decode::<ethereum::Transaction>(&signed).expect("transaction is just created, it can't be broken")174	}175	#[cfg(not(feature = "std"))]176	{177		panic!("transaction generation not yet supported by wasm runtime")178	}179}