git.delta.rocks / unique-network / refs/commits / 2d71e7a206db

difftreelog

refactor use new selector macros

Yaroslav Bolyukin2021-05-31parent: #6d1d1e8.patch.diff
in: master

1 file changed

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		0x06fdde03 => {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"))?;72			73			crate::abi_encode!(memory(&name))74		}75		// function symbol() external view returns (string memory)76		0x95d89b41 => {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"))?;81			82			crate::abi_encode!(memory(&name))83		}84		// function decimals() external view returns (uint8 decimals)85		0x313ce567 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		0x18160ddd 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		0x70a08231 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		0x6352211e 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		0xa9059cbb 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		}		129		// function transfer(address recipient, uint256 token) external returns (bool) {130		0xa9059cbb 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		}146		// function allowance(address owner, address spender) external view returns (uint256)147		0xdd62ed3e if erc20 => {148			crate::abi_decode!(input, owner: address, spender: address);149			let owner = T::EvmAddressMapping::into_account_id(owner);150			let spender = T::EvmAddressMapping::into_account_id(spender);151			let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));152			crate::abi_encode!(uint256(allowance))153		}154		// function approve(address spender, uint256 amount) external returns (bool)155		// FIXME: All current implementations resets amount to specified value, ours - adds it156		// FIXME: Our implementation doesn't handle resets (approve with zero amount)157		0x095ea7b3 if erc20 => {158			crate::abi_decode!(input, spender: address, amount: uint256);159			let sender = T::CrossAccountId::from_eth(sender);160			let spender = T::CrossAccountId::from_eth(spender);161162			<Module<T>>::approve_internal(163				&sender,164				&spender,165				&collection,166				1,167				amount,168			).map_err(|_| "approve error")?;169170			crate::abi_encode!(bool(true))171		}172		// function approve(address approved, uint256 tokenId) external payable173		0x095ea7b3 if erc721 => {174			crate::abi_decode!(input, approved: address, token_id: uint256);175			let sender = T::CrossAccountId::from_eth(sender);176			let approved = T::CrossAccountId::from_eth(approved);177			let token_id = token_id.try_into().map_err(|_| "bad token id")?;178179			<Module<T>>::approve_internal(180				&sender,181				&approved,182				&collection,183				token_id,184				1,185			).map_err(|_| "approve error")?;186			crate::abi_encode!()187		}188		// function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)189		0x23b872dd if erc20 => {190			crate::abi_decode!(input, from: address, recipient: address, amount: uint256);191			let sender = T::CrossAccountId::from_eth(sender);192			let from = T::CrossAccountId::from_eth(from);193			let recipient = T::CrossAccountId::from_eth(recipient);194195			<Module<T>>::transfer_from_internal(196				&sender,197				&from,198				&recipient,199				&collection,200				1,201				amount,202			).map_err(|_| "transfer_from error")?;203204			crate::abi_encode!(bool(true))205		}206		// function transferFrom(address from, address to, uint256 tokenId) external payable207		0x23b872dd if erc721 => {208			crate::abi_decode!(input, from: address, recipient: address, token_id: uint256);209			let sender = T::CrossAccountId::from_eth(sender);210			let from = T::CrossAccountId::from_eth(from);211			let recipient = T::CrossAccountId::from_eth(recipient);212			let token_id = token_id.try_into().map_err(|_| "bad token id")?;213214			<Module<T>>::transfer_from_internal(215				&sender,216				&from,217				&recipient,218				&collection,219				token_id,220				1,221			).map_err(|_| "transfer_from error")?;222223			crate::abi_encode!()224		}225		// function supportsInterface(bytes4 interfaceID) public pure returns (bool)226		0x01ffc9a7 => {227			crate::abi_decode!(input, interface_id: uint32);228			let supports = match interface_id {229				// ERC165230				0x01ffc9a7 => true,231				// ERC20232				0x36372b07 if erc20 => true,233				// ERC721234				0x80ac58cd if erc721 => true,235				_ => false,236			};237			crate::abi_encode!(bool(supports))238		}239		_ => return Err(None)240	})241}242243impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {244	fn is_reserved(target: &H160) -> bool {245		map_eth_to_id(target).is_some()246	}247	fn is_used(target: &H160) -> bool {248		map_eth_to_id(target)249			.map(<CollectionById<T>>::contains_key)250			.unwrap_or(false)251	}252	fn get_code(target: &H160) -> Option<Vec<u8>> {253		map_eth_to_id(&target)254			.and_then(<CollectionById<T>>::get)255			.map(|collection| {256				match collection.mode {257					CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],258					CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],259					CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],260					CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],261				}.to_owned()262			})263	}264	fn call(265		source: &H160,266		target: &H160,267		input: &[u8],268	) -> Option<PrecompileOutput> {269		let collection = map_eth_to_id(&target)270			.and_then(<CollectionHandle<T>>::get)?;271		let (method_id, input) = AbiReader::new_call(input).unwrap();272		let result = call_internal(*source, &collection, method_id, input);273		Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))274	}275}276277/// event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);278pub const TRANSFER_NFT_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));279/// event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);280pub const APPROVAL_NFT_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));281// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);282283/// event Transfer(address indexed from, address indexed to, uint256 amount);284pub const TRANSFER_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"));285/// event Approval(address indexed owner, address indexed approved, uint256 amount);286pub const APPROVAL_FUNGIBLE_TOPIC: H256 = H256(hex_literal::hex!("8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"));287288pub fn address_to_topic(address: &H160) -> H256 {289	let mut output = [0; 32];290	output[12..32].copy_from_slice(&address.0);291	H256(output)292}293294pub fn u32_to_topic(id: u32) -> H256 {295	let mut output = [0; 32];296	output[28..32].copy_from_slice(&id.to_be_bytes());297	H256(output)298}299300301// TODO: This function is slow, and output can be memoized302pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {303	let contract = collection_id_to_address(collection_id);304305	// TODO: Make it work without native runtime by forking ethereum_tx_sign, and306	// switching to pure-rust implementation of secp256k1307	#[cfg(feature = "std")]308	{309		let signed = ethereum_tx_sign::RawTransaction {310			nonce: 0.into(),311			to: Some(contract.0.into()),312			value: 0.into(),313			gas_price: 0.into(),314			gas: 0.into(),315			// zero selector, this transaction always have same sender, so all data should be acquired from logs316			data: Vec::from([0, 0, 0, 0]),317		}.sign(318			// TODO: move to pallet config319			// 0xF70631E55faff9f3FD3681545aa6c724226a3853320			// 9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a321			&hex_literal::hex!("9dbaef9b3ebc00e53f67c6a77bcfbf2c4f2aebe4d70d94af4f2df01744b7a91a").into(),322			&chain_id323		);324		rlp::decode::<ethereum::Transaction>(&signed).expect("transaction is just created, it can't be broken")325	}326	#[cfg(not(feature = "std"))]327	{328		panic!("transaction generation not yet supported by wasm runtime")329	}330}