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
after · pallets/nft/src/eth/erc_impl.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{3	abi::{AbiWriter, StringError},4	types::*,5};6use core::convert::TryInto;7use alloc::format;8use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList};9use frame_support::storage::StorageDoubleMap;10use pallet_evm::AddressMapping;11use super::erc::*;12use super::account::CrossAccountId;1314type Result<T> = core::result::Result<T, StringError>;1516impl<T: Config> InlineNameSymbol for CollectionHandle<T> {17	type Error = StringError;1819	fn name(&self) -> Result<string> {20		Ok(decode_utf16(self.name.iter().copied())21			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))22			.collect::<string>())23	}2425	fn symbol(&self) -> Result<string> {26		Ok(string::from_utf8_lossy(&self.token_prefix).into())27	}28}2930impl<T: Config> InlineTotalSupply for CollectionHandle<T> {31	type Error = StringError;3233	fn total_supply(&self) -> Result<uint256> {34		// TODO: we do not track total amount of all tokens35		Ok(0.into())36	}37}3839impl<T: Config> ERC721Metadata for CollectionHandle<T> {40	type Error = StringError;4142	fn token_uri(&self, token_id: uint256) -> Result<string> {43		// TODO: We should standartize url prefix, maybe via offchain schema?44		Ok(format!("unique.network/{}/{}", self.id, token_id))45	}4647	fn call_inline_name_symbol(&mut self, c: Msg<InlineNameSymbolCall>) -> Result<AbiWriter> {48		<Self as InlineNameSymbol>::call(self, c)49	}50}5152impl<T: Config> ERC721Enumerable for CollectionHandle<T> {53	type Error = StringError;5455	fn token_by_index(&self, index: uint256) -> Result<uint256> {56		Ok(index)57	}5859	fn token_of_owner_by_index(&self, owner: address, index: uint256) -> Result<uint256> {60		// TODO: Not implemetable61		Err("not implemented".into())62	}6364	fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {65		<Self as InlineTotalSupply>::call(self, c)66	}67}6869impl<T: Config> ERC721 for CollectionHandle<T> {70	type Error = StringError;7172	fn balance_of(&self, owner: address) -> Result<uint256> {73		let owner = T::EvmAddressMapping::into_account_id(owner);74		let balance = <Balance<T>>::get(self.id, owner);75		Ok(balance.into())76	}77	fn owner_of(&self, token_id: uint256) -> Result<address> {78		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;79		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;80		Ok(token.owner.as_eth().clone())81	}82	fn safe_transfer_from_with_data(83		&mut self,84		_from: address,85		_to: address,86		_token_id: uint256,87		_data: bytes,88		_value: value,89	) -> Result<void> {90		// TODO: Not implemetable91		Err("not implemented".into())92	}93	fn safe_transfer_from(94		&mut self,95		_from: address,96		_to: address,97		_token_id: uint256,98		_value: value,99	) -> Result<void> {100		// TODO: Not implemetable101		Err("not implemented".into())102	}103104	fn transfer_from(105		&mut self,106		caller: caller,107		from: address,108		to: address,109		token_id: uint256,110		_value: value,111	) -> Result<void> {112		let caller = T::CrossAccountId::from_eth(caller);113		let from = T::CrossAccountId::from_eth(from);114		let to = T::CrossAccountId::from_eth(to);115		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;116117		<Module<T>>::transfer_from_internal(&caller, &from, &to, &self, token_id, 1)118			.map_err(|_| "transferFrom error")?;119		Ok(())120	}121122	fn approve(123		&mut self,124		caller: caller,125		approved: address,126		token_id: uint256,127		_value: value,128	) -> Result<void> {129		let caller = T::CrossAccountId::from_eth(caller);130		let approved = T::CrossAccountId::from_eth(approved);131		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;132133		<Module<T>>::approve_internal(&caller, &approved, &self, token_id, 1)134			.map_err(|_| "approve internal")?;135		Ok(())136	}137138	fn set_approval_for_all(139		&mut self,140		_caller: caller,141		_operator: address,142		_approved: bool,143	) -> Result<void> {144		// TODO: Not implemetable145		Err("not implemented".into())146	}147148	fn get_approved(&self, _token_id: uint256) -> Result<address> {149		// TODO: Not implemetable150		Err("not implemented".into())151	}152153	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {154		// TODO: Not implemetable155		Err("not implemented".into())156	}157158	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {159		let ERC165Call::SupportsInterface { interface_id } = c.call;160		Ok(evm_coder::abi_encode!(bool(161			&ERC721Call::supports_interface(interface_id)162		)))163	}164}165166impl<T: Config> ERC721UniqueExtensions for CollectionHandle<T> {167	type Error = StringError;168	fn transfer(169		&mut self,170		caller: caller,171		to: address,172		token_id: uint256,173		value: value,174	) -> Result<void> {175		let caller = T::CrossAccountId::from_eth(caller);176		let to = T::CrossAccountId::from_eth(to);177		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;178179		<Module<T>>::transfer_internal(&caller, &to, &self, token_id, 1)180			.map_err(|_| "transfer error")?;181		Ok(())182	}183}184185impl<T: Config> UniqueNFT for CollectionHandle<T> {186	type Error = StringError;187	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {188		let ERC165Call::SupportsInterface { interface_id } = c.call;189		Ok(evm_coder::abi_encode!(bool(190			&UniqueNFTCall::supports_interface(interface_id)191		)))192	}193	fn call_erc721(&mut self, c: Msg<ERC721Call>) -> Result<AbiWriter> {194		<Self as ERC721>::call(self, c)195	}196	fn call_erc721_metadata(&mut self, c: Msg<ERC721MetadataCall>) -> Result<AbiWriter> {197		<Self as ERC721Metadata>::call(self, c)198	}199	fn call_erc721_enumerable(&mut self, c: Msg<ERC721EnumerableCall>) -> Result<AbiWriter> {200		<Self as ERC721Enumerable>::call(self, c)201	}202	fn call_erc721_unique_extensions(203		&mut self,204		c: Msg<ERC721UniqueExtensionsCall>,205	) -> Result<AbiWriter> {206		<Self as ERC721UniqueExtensions>::call(self, c)207	}208}209210impl<T: Config> ERC20 for CollectionHandle<T> {211	type Error = StringError;212	fn decimals(&self) -> Result<uint8> {213		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {214			*decimals215		} else {216			unreachable!()217		})218	}219	fn balance_of(&self, owner: address) -> Result<uint256> {220		let owner = T::EvmAddressMapping::into_account_id(owner);221		let balance = <Balance<T>>::get(self.id, owner);222		Ok(balance.into())223	}224	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {225		let caller = T::CrossAccountId::from_eth(caller);226		let to = T::CrossAccountId::from_eth(to);227		let amount = amount.try_into().map_err(|_| "amount overflow")?;228229		<Module<T>>::transfer_internal(&caller, &to, &self, 1, amount)230			.map_err(|_| "transfer error")?;231		Ok(true)232	}233	fn transfer_from(234		&mut self,235		caller: caller,236		from: address,237		to: address,238		amount: uint256,239	) -> Result<bool> {240		let caller = T::CrossAccountId::from_eth(caller);241		let from = T::CrossAccountId::from_eth(from);242		let to = T::CrossAccountId::from_eth(to);243		let amount = amount.try_into().map_err(|_| "amount overflow")?;244245		<Module<T>>::transfer_from_internal(&caller, &from, &to, &self, 1, amount)246			.map_err(|_| "transferFrom error")?;247		Ok(true)248	}249	fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {250		let caller = T::CrossAccountId::from_eth(caller);251		let spender = T::CrossAccountId::from_eth(spender);252		let amount = amount.try_into().map_err(|_| "amount overflow")?;253254		<Module<T>>::approve_internal(&caller, &spender, &self, 1, amount)255			.map_err(|_| "approve internal")?;256		Ok(true)257	}258	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {259		let owner = T::CrossAccountId::from_eth(owner);260		let spender = T::CrossAccountId::from_eth(spender);261262		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())263	}264	fn call_inline_name_symbol(&mut self, c: Msg<InlineNameSymbolCall>) -> Result<AbiWriter> {265		<Self as InlineNameSymbol>::call(self, c)266	}267	fn call_inline_total_supply(&mut self, c: Msg<InlineTotalSupplyCall>) -> Result<AbiWriter> {268		<Self as InlineTotalSupply>::call(self, c)269	}270}271272impl<T: Config> UniqueFungible for CollectionHandle<T> {273	type Error = StringError;274	fn call_erc165(&mut self, c: Msg<ERC165Call>) -> Result<AbiWriter> {275		let ERC165Call::SupportsInterface { interface_id } = c.call;276		Ok(evm_coder::abi_encode!(bool(277			&UniqueNFTCall::supports_interface(interface_id)278		)))279	}280	fn call_erc20(&mut self, c: Msg<ERC20Call>) -> Result<AbiWriter> {281		<Self as ERC20>::call(self, c)282	}283}
modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/mod.rs
+++ b/pallets/nft/src/eth/mod.rs
@@ -36,209 +36,63 @@
 	H160(out)
 }
 
-fn result_to_output(result: Result<AbiWriter, Option<&'static str>>, logs: Vec<PrecompileLog>) -> PrecompileOutput {
-	sp_io::storage::start_transaction();
-	match result {
-		Ok(result) => {
-			sp_io::storage::commit_transaction();
-			// TODO: weight
-			PrecompileOutput(ExitReason::Succeed(ExitSucceed::Returned), result.finish(), 0, logs)
-		}
-		Err(Some(s)) => {
-			sp_io::storage::rollback_transaction();
-			// Error(string)
-			let mut out = AbiWriter::new_call(0x08c379a0);
-			out.string(&s);
-			PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), out.finish(), 0, Vec::new())
-		}
-		Err(None) => {
-			sp_io::storage::rollback_transaction();
-			PrecompileOutput(ExitReason::Revert(ExitRevert::Reverted), Vec::new(), 0, Vec::new())
-		}
-	}
-}
-
-fn call_internal<T: Config>(sender: H160, collection: &CollectionHandle<T>, method_id: u32, mut input: AbiReader) -> Result<AbiWriter, Option<&'static str>> {
-	let erc20 = matches!(collection.mode, CollectionMode::Fungible(_));
-	let erc721 = matches!(collection.mode, CollectionMode::NFT);
-
-	Ok(match method_id {
-		// function name() external view returns (string memory)
-		fn_selector!(name()) => {
-			let name = collection.name.iter()
-				.map(|&e| e.try_into().ok() as Option<u8>)
-				.collect::<Option<Vec<u8>>>()
-				.ok_or(Some("non-ascii name"))?;
-
-			crate::abi_encode!(memory(&name))
-		}
-		// function symbol() external view returns (string memory)
-		fn_selector!(symbol()) => {
-			let name = collection.token_prefix.iter()
-				.map(|&e| e.is_ascii_uppercase().then(|| e))
-				.collect::<Option<Vec<u8>>>()
-				.ok_or(Some("non-uppercase prefix"))?;
-
-			crate::abi_encode!(memory(&name))
-		}
-		// function decimals() external view returns (uint8 decimals)
-		fn_selector!(decimals()) if erc20 => {
-			if let CollectionMode::Fungible(decimals) = &collection.mode {
-				crate::abi_encode!(uint8(*decimals))
-			} else {
-				unreachable!()
+fn call_internal<T: Config>(
+	collection: &mut CollectionHandle<T>,
+	caller: caller,
+	method_id: u32,
+	mut input: AbiReader,
+	value: U256,
+) -> Result<Option<AbiWriter>, evm_coder::abi::StringError> {
+	match collection.mode.clone() {
+		CollectionMode::Fungible(_) => {
+			#[cfg(feature = "std")]
+			{
+				println!("Parse fungible call {:x}", method_id);
 			}
-		}
-		// function totalSupply() external view returns (uint256)
-		fn_selector!(totalSupply()) if erc20 || erc721 => {
-			// TODO: can't be implemented, as we don't track total amount of fungibles
-			crate::abi_encode!(uint256(0))
-		}
-		// function balanceOf(address account) external view returns (uint256)
-		fn_selector!(balanceOf(address)) if erc20 || erc721 => {
-			crate::abi_decode!(input, account: address);
-			let account = T::EvmAddressMapping::into_account_id(account);
-			let balance = <Balance<T>>::get(collection.id, account);
-			crate::abi_encode!(uint256(balance))
+			let call = match UniqueFungibleCall::parse(method_id, &mut input)? {
+				Some(v) => v,
+				None => {
+					#[cfg(feature = "std")]
+					{
+						println!("Method not found");
+					}
+					return Ok(None);
+				}
+			};
+			#[cfg(feature = "std")]
+			{
+				dbg!(&call);
+			}
+			Ok(Some(<CollectionHandle<T> as UniqueFungible>::call(
+				collection,
+				Msg {
+					call,
+					caller,
+					value,
+				},
+			)?))
 		}
-		// function ownerOf(uint256 tokenId) external view returns (address)
-		fn_selector!(ownerOf(uint256)) if erc721 => {
-			crate::abi_decode!(input, token_id: uint256);
-			let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;
-
-			let token = <NftItemList<T>>::get(collection.id, token_id).ok_or("unknown token")?;
-
-			crate::abi_encode!(address(token.owner.as_eth().clone()))
+		CollectionMode::NFT => {
+			let call = match UniqueNFTCall::parse(method_id, &mut input)? {
+				Some(v) => v,
+				None => return Ok(None),
+			};
+			Ok(Some(<CollectionHandle<T> as UniqueNFT>::call(
+				collection,
+				Msg {
+					call,
+					caller,
+					value,
+				},
+			)?))
 		}
-		// function transfer(address recipient, uint256 amount) external returns (bool) {
-		fn_selector!(transfer(address, uint256)) if erc20 => {
-			crate::abi_decode!(input, recipient: address, amount: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let recipient = T::CrossAccountId::from_eth(recipient);
-
-			<Module<T>>::transfer_internal(
-				&sender,
-				&recipient,
-				&collection,
-				1,
-				amount,
-			).map_err(|_| "transfer error")?;
-
-			crate::abi_encode!(bool(true))
+		_ => {
+			return Err(StringError::from(
+				"erc calls only supported to fungible and nft collections for now",
+			)
+			.into())
 		}
-
-		fn_selector!(transfer(address, uint256)) if erc721 => {
-			crate::abi_decode!(input, recipient: address, token_id: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let recipient = T::CrossAccountId::from_eth(recipient);
-			let token_id: u32 = token_id.try_into().map_err(|_| "bad token id")?;
-
-			<Module<T>>::transfer_internal(
-				&sender,
-				&recipient,
-				&collection,
-				token_id,
-				1,
-			).map_err(|_| "transfer error")?;
-
-			crate::abi_encode!(bool(true))
-		}
-
-		// function allowance(address owner, address spender) external view returns (uint256)
-		fn_selector!(allowance(address, address)) if erc20 => {
-			crate::abi_decode!(input, owner: address, spender: address);
-			let owner = T::EvmAddressMapping::into_account_id(owner);
-			let spender = T::EvmAddressMapping::into_account_id(spender);
-			let allowance = <Allowances<T>>::get(collection.id, (1, &owner, &spender));
-			crate::abi_encode!(uint256(allowance))
-		}
-		// function approve(address spender, uint256 amount) external returns (bool)
-		// FIXME: All current implementations resets amount to specified value, ours - adds it
-		// FIXME: Our implementation doesn't handle resets (approve with zero amount)
-		fn_selector!(approve(address, uint256)) if erc20 => {
-			crate::abi_decode!(input, spender: address, amount: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let spender = T::CrossAccountId::from_eth(spender);
-
-			<Module<T>>::approve_internal(
-				&sender,
-				&spender,
-				&collection,
-				1,
-				amount,
-			).map_err(|_| "approve error")?;
-
-			crate::abi_encode!(bool(true))
-		}
-		// function approve(address approved, uint256 tokenId) external payable
-		fn_selector!(approve(address, uint256)) if erc721 => {
-			crate::abi_decode!(input, approved: address, token_id: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let approved = T::CrossAccountId::from_eth(approved);
-			let token_id = token_id.try_into().map_err(|_| "bad token id")?;
-
-			<Module<T>>::approve_internal(
-				&sender,
-				&approved,
-				&collection,
-				token_id,
-				1,
-			).map_err(|_| "approve error")?;
-			crate::abi_encode!()
-		}
-		// function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)
-		fn_selector!(transferFrom(address, address, uint256)) if erc20 => {
-			crate::abi_decode!(input, from: address, recipient: address, amount: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let from = T::CrossAccountId::from_eth(from);
-			let recipient = T::CrossAccountId::from_eth(recipient);
-
-			<Module<T>>::transfer_from_internal(
-				&sender,
-				&from,
-				&recipient,
-				&collection,
-				1,
-				amount,
-			).map_err(|_| "transfer_from error")?;
-
-			crate::abi_encode!(bool(true))
-		}
-		// function transferFrom(address from, address to, uint256 tokenId) external payable
-		fn_selector!(transferFrom(address, address, uint256)) if erc721 => {
-			crate::abi_decode!(input, from: address, recipient: address, token_id: uint256);
-			let sender = T::CrossAccountId::from_eth(sender);
-			let from = T::CrossAccountId::from_eth(from);
-			let recipient = T::CrossAccountId::from_eth(recipient);
-			let token_id = token_id.try_into().map_err(|_| "bad token id")?;
-
-			<Module<T>>::transfer_from_internal(
-				&sender,
-				&from,
-				&recipient,
-				&collection,
-				token_id,
-				1,
-			).map_err(|_| "transfer_from error")?;
-
-			crate::abi_encode!()
-		}
-		// function supportsInterface(bytes4 interfaceID) public pure returns (bool)
-		fn_selector!(supportsInterface(bytes4)) => {
-			crate::abi_decode!(input, interface_id: uint32);
-			let supports = match interface_id {
-				// ERC165
-				0x01ffc9a7 => true,
-				// ERC20
-				0x36372b07 if erc20 => true,
-				// ERC721
-				0x80ac58cd if erc721 => true,
-				_ => false,
-			};
-			crate::abi_encode!(bool(supports))
-		}
-		_ => return Err(None)
-	})
+	}
 }
 
 impl<T: Config> pallet_evm::OnMethodCall<T> for NftErcSupport<T> {
@@ -265,35 +119,33 @@
 	fn call(
 		source: &H160,
 		target: &H160,
+		gas_limit: u64,
 		input: &[u8],
+		value: U256,
 	) -> Option<PrecompileOutput> {
-		let collection = map_eth_to_id(&target)
-			.and_then(<CollectionHandle<T>>::get)?;
+		let mut collection = map_eth_to_id(&target)
+			.and_then(|id| <CollectionHandle<T>>::get_with_gas_limit(id, gas_limit))?;
 		let (method_id, input) = AbiReader::new_call(input).unwrap();
-		let result = call_internal(*source, &collection, method_id, input);
-		Some(result_to_output(result, collection.logs.retrieve_logs_for_contract(*target)))
+		let result = call_internal(&mut collection, *source, method_id, input, value);
+		let cost = gas_limit - collection.gas_left();
+		let logs = collection.logs.retrieve_logs();
+		match result {
+			Ok(Some(v)) => Some(PrecompileOutput {
+				exit_status: ExitReason::Succeed(ExitSucceed::Returned),
+				cost,
+				logs,
+				output: v.finish(),
+			}),
+			Ok(None) => None,
+			Err(e) => Some(PrecompileOutput {
+				exit_status: ExitReason::Revert(ExitRevert::Reverted),
+				cost: 0,
+				logs: Default::default(),
+				output: AbiWriter::from(e).finish(),
+			}),
+		}
 	}
 }
-
-pub const TRANSFER_NFT_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));
-pub const APPROVAL_NFT_TOPIC: H256 = event_topic!(Approval(address, address, uint256));
-// TODO: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
-
-pub const TRANSFER_FUNGIBLE_TOPIC: H256 = event_topic!(Transfer(address, address, uint256));
-pub const APPROVAL_FUNGIBLE_TOPIC: H256 = event_topic!(Approval(address, address, uint256));
-
-pub fn address_to_topic(address: &H160) -> H256 {
-	let mut output = [0; 32];
-	output[12..32].copy_from_slice(&address.0);
-	H256(output)
-}
-
-pub fn u32_to_topic(id: u32) -> H256 {
-	let mut output = [0; 32];
-	output[28..32].copy_from_slice(&id.to_be_bytes());
-	H256(output)
-}
-
 
 // TODO: This function is slow, and output can be memoized
 pub fn generate_transaction(collection_id: u32, chain_id: u64) -> ethereum::Transaction {