git.delta.rocks / unique-network / refs/commits / 65d98914224d

difftreelog

Merge pull request #188 from UniqueNetwork/feature/evm-collection-calls

kozyrevdev2021-08-31parents: #93b7b22 #874bac8.patch.diff
in: master
Test collection method calls from EVM

35 files changed

added.maintain/scripts/compile_stub.shdiffbeforeafterboth
--- /dev/null
+++ b/.maintain/scripts/compile_stub.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+set -eu
+
+dir=$PWD
+
+tmp=$(mktemp -d)
+cd $tmp
+cp $dir/$INPUT input.sol
+solcjs --optimize --bin input.sol
+
+mv input_sol_$(basename $OUTPUT .raw).bin out.bin
+xxd -r -p out.bin out.raw
+
+mv out.raw $dir/$OUTPUT
\ No newline at end of file
added.maintain/scripts/generate_api.shdiffbeforeafterboth
--- /dev/null
+++ b/.maintain/scripts/generate_api.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -eu
+
+tmp=$(mktemp)
+cargo test --package $PACKAGE -- $NAME --exact --nocapture --ignored | tee $tmp
+raw=$(mktemp --suffix .sol)
+sed -n '/=== SNIP START ===/, /=== SNIP END ===/{ /=== SNIP START ===/! { /=== SNIP END ===/! p } }' $tmp > $raw
+formatted=$(mktemp)
+prettier --use-tabs $raw > $formatted
+solhint --fix $formatted
+
+mv $formatted $OUTPUT
\ No newline at end of file
modifiedMakefilediffbeforeafterboth
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,28 @@
+.PHONY: _eth_codegen
+_eth_codegen:
+
+.PHONY: regenerate_solidity
+regenerate_solidity:
+	PACKAGE=pallet-nft NAME=eth::erc::fungible_iface OUTPUT=./tests/src/eth/api/UniqueFungible.sol ./.maintain/scripts/generate_api.sh
+	PACKAGE=pallet-nft NAME=eth::erc::nft_iface OUTPUT=./tests/src/eth/api/UniqueNFT.sol ./.maintain/scripts/generate_api.sh
+	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_iface OUTPUT=./tests/src/eth/api/ContractHelpers.sol ./.maintain/scripts/generate_api.sh
+
+	PACKAGE=pallet-nft NAME=eth::erc::fungible_impl OUTPUT=./pallets/nft/src/eth/stubs/UniqueFungible.sol ./.maintain/scripts/generate_api.sh
+	PACKAGE=pallet-nft NAME=eth::erc::nft_impl OUTPUT=./pallets/nft/src/eth/stubs/UniqueNFT.sol ./.maintain/scripts/generate_api.sh
+	PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_impl OUTPUT=./pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol ./.maintain/scripts/generate_api.sh
+
+NFT_EVM_STUBS=./pallets/nft/src/eth/stubs
+CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/
+
+$(NFT_EVM_STUBS)/UniqueFungible.raw: $(NFT_EVM_STUBS)/UniqueFungible.sol
+	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
+$(NFT_EVM_STUBS)/UniqueNFT.raw: $(NFT_EVM_STUBS)/UniqueNFT.sol
+	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
+$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw: $(CONTRACT_HELPERS_STUBS)/ContractHelpers.sol
+	INPUT=$< OUTPUT=$@ ./.maintain/scripts/compile_stub.sh
+
+evm_stubs: $(NFT_EVM_STUBS)/UniqueFungible.raw $(NFT_EVM_STUBS)/UniqueNFT.raw $(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw
+
 .PHONY: _bench
 _bench:
 	cargo run --release --features runtime-benchmarks -- \
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -34,8 +34,9 @@
 	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
 		let camel_name = &self.camel_name;
 		let ty = &self.ty;
+		let indexed = self.indexed;
 		quote! {
-			<NamedArgument<#ty>>::new(#camel_name)
+			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)
 		}
 	}
 }
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -61,6 +61,29 @@
 	fn call(&mut self, call: types::Msg<C>) -> execution::Result<AbiWriter>;
 }
 
+#[macro_export]
+macro_rules! generate_stubgen {
+	($name:ident, $decl:ident, $is_impl:literal) => {
+		#[test]
+		#[ignore]
+		fn $name() {
+			use sp_std::collections::btree_set::BTreeSet;
+			let mut out = BTreeSet::new();
+			$decl::generate_solidity_interface(&mut out, $is_impl);
+			println!("=== SNIP START ===");
+			println!("// SPDX-License-Identifier: OTHER");
+			println!("// This code is automatically generated");
+			println!();
+			println!("pragma solidity >=0.8.0 <0.9.0;");
+			println!();
+			for b in out {
+				println!("{}", b);
+			}
+			println!("=== SNIP END ===");
+		}
+	};
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -117,6 +117,41 @@
 	}
 }
 
+pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);
+
+impl<T> SolidityEventArgument<T> {
+	pub fn new(indexed: bool, name: &'static str) -> Self {
+		Self(indexed, name, Default::default())
+	}
+}
+
+impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
+	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		if !T::is_void() {
+			T::solidity_name(writer)?;
+			if self.0 {
+				write!(writer, " indexed")?;
+			}
+			write!(writer, " {}", self.1)
+		} else {
+			Ok(())
+		}
+	}
+	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		writeln!(writer, "\t\t{};", self.1)
+	}
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		T::solidity_default(writer)
+	}
+	fn len(&self) -> usize {
+		if T::is_void() {
+			0
+		} else {
+			1
+		}
+	}
+}
+
 impl SolidityArguments for () {
 	fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -1,5 +1,5 @@
 use core::marker::PhantomData;
-use evm_coder::{abi::AbiWriter, execution::Result, solidity_interface, types::*};
+use evm_coder::{abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*};
 use pallet_evm_coder_substrate::SubstrateRecorder;
 use pallet_evm::{ExitReason, ExitRevert, OnCreate, OnMethodCall, PrecompileOutput};
 use sp_core::H160;
@@ -14,76 +14,76 @@
 
 #[solidity_interface(name = "ContractHelpers")]
 impl<T: Config> ContractHelpers<T> {
-	fn contract_owner(&self, contract: address) -> Result<address> {
+	fn contract_owner(&self, contract_address: address) -> Result<address> {
 		self.0.consume_sload()?;
-		Ok(<Owner<T>>::get(contract))
+		Ok(<Owner<T>>::get(contract_address))
 	}
 
-	fn sponsoring_enabled(&self, contract: address) -> Result<bool> {
+	fn sponsoring_enabled(&self, contract_address: address) -> Result<bool> {
 		self.0.consume_sload()?;
-		Ok(<SelfSponsoring<T>>::get(contract))
+		Ok(<SelfSponsoring<T>>::get(contract_address))
 	}
 
 	fn toggle_sponsoring(
 		&mut self,
 		caller: caller,
-		contract: address,
+		contract_address: address,
 		enabled: bool,
 	) -> Result<void> {
 		self.0.consume_sload()?;
-		<Pallet<T>>::ensure_owner(contract, caller)?;
+		<Pallet<T>>::ensure_owner(contract_address, caller)?;
 		self.0.consume_sstore()?;
-		<Pallet<T>>::toggle_sponsoring(contract, enabled);
+		<Pallet<T>>::toggle_sponsoring(contract_address, enabled);
 		Ok(())
 	}
 
 	fn set_sponsoring_rate_limit(
 		&mut self,
 		caller: caller,
-		contract: address,
+		contract_address: address,
 		rate_limit: uint32,
 	) -> Result<void> {
 		self.0.consume_sload()?;
-		<Pallet<T>>::ensure_owner(contract, caller)?;
+		<Pallet<T>>::ensure_owner(contract_address, caller)?;
 		self.0.consume_sstore()?;
-		<Pallet<T>>::set_sponsoring_rate_limit(contract, rate_limit.into());
+		<Pallet<T>>::set_sponsoring_rate_limit(contract_address, rate_limit.into());
 		Ok(())
 	}
 
-	fn allowed(&self, contract: address, user: address) -> Result<bool> {
+	fn allowed(&self, contract_address: address, user: address) -> Result<bool> {
 		self.0.consume_sload()?;
-		Ok(<Pallet<T>>::allowed(contract, user, true))
+		Ok(<Pallet<T>>::allowed(contract_address, user, true))
 	}
 
-	fn allowlist_enabled(&self, contract: address) -> Result<bool> {
+	fn allowlist_enabled(&self, contract_address: address) -> Result<bool> {
 		self.0.consume_sload()?;
-		Ok(<AllowlistEnabled<T>>::get(contract))
+		Ok(<AllowlistEnabled<T>>::get(contract_address))
 	}
 
 	fn toggle_allowlist(
 		&mut self,
 		caller: caller,
-		contract: address,
+		contract_address: address,
 		enabled: bool,
 	) -> Result<void> {
 		self.0.consume_sload()?;
-		<Pallet<T>>::ensure_owner(contract, caller)?;
+		<Pallet<T>>::ensure_owner(contract_address, caller)?;
 		self.0.consume_sstore()?;
-		<Pallet<T>>::toggle_allowlist(contract, enabled);
+		<Pallet<T>>::toggle_allowlist(contract_address, enabled);
 		Ok(())
 	}
 
 	fn toggle_allowed(
 		&mut self,
 		caller: caller,
-		contract: address,
+		contract_address: address,
 		user: address,
 		allowed: bool,
 	) -> Result<void> {
 		self.0.consume_sload()?;
-		<Pallet<T>>::ensure_owner(contract, caller)?;
+		<Pallet<T>>::ensure_owner(contract_address, caller)?;
 		self.0.consume_sstore()?;
-		<Pallet<T>>::toggle_allowed(contract, user, allowed);
+		<Pallet<T>>::toggle_allowed(contract_address, user, allowed);
 		Ok(())
 	}
 }
@@ -130,7 +130,7 @@
 
 	fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {
 		(contract == &T::ContractAddress::get())
-			.then(|| include_bytes!("./stubs/ContractHelpers.bin").to_vec())
+			.then(|| include_bytes!("./stubs/ContractHelpers.raw").to_vec())
 	}
 }
 
@@ -162,3 +162,6 @@
 		None
 	}
 }
+
+generate_stubgen!(contract_helpers_impl, ContractHelpersCall, true);
+generate_stubgen!(contract_helpers_iface, ContractHelpersCall, false);
deletedpallets/evm-contract-helpers/src/stubs/ContractHelpers.bindiffbeforeafterboth

binary blob — no preview

addedpallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -1,40 +1,92 @@
-contract ContractHelpers {
-    uint8 _dummmy = 0;
-    address _dummy_addr = 0x0000000000000000000000000000000000000000;
-	string stub_error = "this contract does not exists, contract helpers are implemented on substrate chain side";
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
+
+contract ContractHelpers is Dummy {
+	function contractOwner(address contractAddress)
+		public
+		view
+		returns (address)
+	{
+		require(false, stub_error);
+		contractAddress;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	function sponsoringEnabled(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		contractAddress;
+		dummy;
+		return false;
+	}
+
+	function toggleSponsoring(address contractAddress, bool enabled) public {
+		require(false, stub_error);
+		contractAddress;
+		enabled;
+		dummy = 0;
+	}
+
+	function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
+		public
+	{
+		require(false, stub_error);
+		contractAddress;
+		rateLimit;
+		dummy = 0;
+	}
+
+	function allowed(address contractAddress, address user)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		contractAddress;
+		user;
+		dummy;
+		return false;
+	}
+
+	function allowlistEnabled(address contractAddress)
+		public
+		view
+		returns (bool)
+	{
+		require(false, stub_error);
+		contractAddress;
+		dummy;
+		return false;
+	}
 
-    function contractOwner(address contract_address) public view returns (address) {
-        require(false, stub_error);
-        contract_address;
-        return _dummy_addr;
-    }
-    
-    function sponsoringEnabled(address contract_address) public view returns (bool) {
-        require(false, stub_error);
-        contract_address;
-        _dummmy;
-        return false;
-    }
-    
-    function toggleSponsoring(address contract_address, bool enabled) public {
-        require(false, stub_error);
-        contract_address;
-        enabled;
-        _dummmy = 0;
-    }
-    
-    function toggleAllowlist(address contract_address, bool enabled) public {
-        require(false, stub_error);
-        contract_address;
-        enabled;
-        _dummmy = 0;
-    }
-    
-    function toggleAllowed(address contract_address, address user, bool allowed) public {
-        require(false, stub_error);
-        contract_address;
-        user;
-        allowed;
-        _dummmy = 0;
-    }
-}
\ No newline at end of file
+	function toggleAllowlist(address contractAddress, bool enabled) public {
+		require(false, stub_error);
+		contractAddress;
+		enabled;
+		dummy = 0;
+	}
+
+	function toggleAllowed(
+		address contractAddress,
+		address user,
+		bool allowed
+	) public {
+		require(false, stub_error);
+		contractAddress;
+		user;
+		allowed;
+		dummy = 0;
+	}
+}
modifiedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
before · pallets/nft/src/eth/erc.rs
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 crate::{6	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,7	ItemListIndex,8};9use frame_support::storage::{StorageMap, StorageDoubleMap};10use pallet_evm::AddressMapping;11use super::account::CrossAccountId;12use sp_std::{vec, vec::Vec};1314#[solidity_interface(name = "ERC165")]15impl<T: Config> CollectionHandle<T> {16	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {17		Ok(match self.mode {18			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),19			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),20			_ => false,21		})22	}23}2425#[solidity_interface(name = "InlineNameSymbol")]26impl<T: Config> CollectionHandle<T> {27	fn name(&self) -> Result<string> {28		Ok(decode_utf16(self.name.iter().copied())29			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))30			.collect::<string>())31	}3233	fn symbol(&self) -> Result<string> {34		Ok(string::from_utf8_lossy(&self.token_prefix).into())35	}36}3738#[solidity_interface(name = "InlineTotalSupply")]39impl<T: Config> CollectionHandle<T> {40	fn total_supply(&self) -> Result<uint256> {41		// TODO: we do not track total amount of all tokens42		Ok(0.into())43	}44}4546#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]47impl<T: Config> CollectionHandle<T> {48	#[solidity(rename_selector = "tokenURI")]49	fn token_uri(&self, token_id: uint256) -> Result<string> {50		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;51		Ok(string::from_utf8_lossy(52			&<NftItemList<T>>::get(self.id, token_id)53				.ok_or("token not found")?54				.const_data,55		)56		.into())57	}58}5960#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]61impl<T: Config> CollectionHandle<T> {62	fn token_by_index(&self, index: uint256) -> Result<uint256> {63		Ok(index)64	}6566	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {67		// TODO: Not implemetable68		Err("not implemented".into())69	}70}7172#[derive(ToLog)]73pub enum ERC721Events {74	Transfer {75		#[indexed]76		from: address,77		#[indexed]78		to: address,79		#[indexed]80		token_id: uint256,81	},82	Approval {83		#[indexed]84		owner: address,85		#[indexed]86		approved: address,87		#[indexed]88		token_id: uint256,89	},90	#[allow(dead_code)]91	ApprovalForAll {92		#[indexed]93		owner: address,94		#[indexed]95		operator: address,96		approved: bool,97	},98}99100#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]101impl<T: Config> CollectionHandle<T> {102	#[solidity(rename_selector = "balanceOf")]103	fn balance_of_nft(&self, owner: address) -> Result<uint256> {104		let owner = T::EvmAddressMapping::into_account_id(owner);105		let balance = <Balance<T>>::get(self.id, owner);106		Ok(balance.into())107	}108	fn owner_of(&self, token_id: uint256) -> Result<address> {109		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;111		Ok(*token.owner.as_eth())112	}113	fn safe_transfer_from_with_data(114		&mut self,115		_from: address,116		_to: address,117		_token_id: uint256,118		_data: bytes,119		_value: value,120	) -> Result<void> {121		// TODO: Not implemetable122		Err("not implemented".into())123	}124	fn safe_transfer_from(125		&mut self,126		_from: address,127		_to: address,128		_token_id: uint256,129		_value: value,130	) -> Result<void> {131		// TODO: Not implemetable132		Err("not implemented".into())133	}134135	fn transfer_from(136		&mut self,137		caller: caller,138		from: address,139		to: address,140		token_id: uint256,141		_value: value,142	) -> Result<void> {143		let caller = T::CrossAccountId::from_eth(caller);144		let from = T::CrossAccountId::from_eth(from);145		let to = T::CrossAccountId::from_eth(to);146		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;147148		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)149			.map_err(|_| "transferFrom error")?;150		Ok(())151	}152153	fn approve(154		&mut self,155		caller: caller,156		approved: address,157		token_id: uint256,158		_value: value,159	) -> Result<void> {160		let caller = T::CrossAccountId::from_eth(caller);161		let approved = T::CrossAccountId::from_eth(approved);162		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;163164		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)165			.map_err(|_| "approve internal")?;166		Ok(())167	}168169	fn set_approval_for_all(170		&mut self,171		_caller: caller,172		_operator: address,173		_approved: bool,174	) -> Result<void> {175		// TODO: Not implemetable176		Err("not implemented".into())177	}178179	fn get_approved(&self, _token_id: uint256) -> Result<address> {180		// TODO: Not implemetable181		Err("not implemented".into())182	}183184	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {185		// TODO: Not implemetable186		Err("not implemented".into())187	}188}189190#[solidity_interface(name = "ERC721Burnable")]191impl<T: Config> CollectionHandle<T> {192	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {193		let caller = T::CrossAccountId::from_eth(caller);194		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;195196		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;197		Ok(())198	}199}200201#[derive(ToLog)]202pub enum ERC721MintableEvents {203	#[allow(dead_code)]204	MintingFinished {},205}206207#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]208impl<T: Config> CollectionHandle<T> {209	fn minting_finished(&self) -> Result<bool> {210		Ok(false)211	}212213	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {214		let caller = T::CrossAccountId::from_eth(caller);215		let to = T::CrossAccountId::from_eth(to);216		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;217		if <ItemListIndex>::get(self.id)218			.checked_add(1)219			.ok_or("item id overflow")?220			!= token_id221		{222			return Err("item id should be next".into());223		}224225		<Module<T>>::create_item_internal(226			&caller,227			&self,228			&to,229			CreateItemData::NFT(CreateNftData {230				const_data: vec![].try_into().unwrap(),231				variable_data: vec![].try_into().unwrap(),232			}),233		)234		.map_err(|_| "mint error")?;235		Ok(true)236	}237238	#[solidity(rename_selector = "mintWithTokenURI")]239	fn mint_with_token_uri(240		&mut self,241		caller: caller,242		to: address,243		token_id: uint256,244		token_uri: string,245	) -> Result<bool> {246		let caller = T::CrossAccountId::from_eth(caller);247		let to = T::CrossAccountId::from_eth(to);248		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;249		if <ItemListIndex>::get(self.id)250			.checked_add(1)251			.ok_or("item id overflow")?252			!= token_id253		{254			return Err("item id should be next".into());255		}256257		<Module<T>>::create_item_internal(258			&caller,259			&self,260			&to,261			CreateItemData::NFT(CreateNftData {262				const_data: Vec::<u8>::from(token_uri)263					.try_into()264					.map_err(|_| "token uri is too long")?,265				variable_data: vec![].try_into().unwrap(),266			}),267		)268		.map_err(|_| "mint error")?;269		Ok(true)270	}271272	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {273		Err("not implementable".into())274	}275}276277#[solidity_interface(name = "ERC721UniqueExtensions")]278impl<T: Config> CollectionHandle<T> {279	#[solidity(rename_selector = "transfer")]280	fn transfer_nft(281		&mut self,282		caller: caller,283		to: address,284		token_id: uint256,285		_value: value,286	) -> Result<void> {287		let caller = T::CrossAccountId::from_eth(caller);288		let to = T::CrossAccountId::from_eth(to);289		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;290291		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)292			.map_err(|_| "transfer error")?;293		Ok(())294	}295296	fn next_token_id(&self) -> Result<uint256> {297		Ok(ItemListIndex::get(self.id)298			.checked_add(1)299			.ok_or("item id overflow")?300			.into())301	}302}303304#[solidity_interface(305	name = "UniqueNFT",306	is(307		ERC165,308		ERC721,309		ERC721Metadata,310		ERC721Enumerable,311		ERC721UniqueExtensions,312		ERC721Mintable,313		ERC721Burnable,314	)315)]316impl<T: Config> CollectionHandle<T> {}317318#[derive(ToLog)]319pub enum ERC20Events {320	Transfer {321		#[indexed]322		from: address,323		#[indexed]324		to: address,325		value: uint256,326	},327	Approval {328		#[indexed]329		owner: address,330		#[indexed]331		spender: address,332		value: uint256,333	},334}335336#[solidity_interface(337	name = "ERC20",338	inline_is(InlineNameSymbol, InlineTotalSupply),339	events(ERC20Events)340)]341impl<T: Config> CollectionHandle<T> {342	fn decimals(&self) -> Result<uint8> {343		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {344			*decimals345		} else {346			unreachable!()347		})348	}349	fn balance_of(&self, owner: address) -> Result<uint256> {350		let owner = T::EvmAddressMapping::into_account_id(owner);351		let balance = <Balance<T>>::get(self.id, owner);352		Ok(balance.into())353	}354	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {355		let caller = T::CrossAccountId::from_eth(caller);356		let to = T::CrossAccountId::from_eth(to);357		let amount = amount.try_into().map_err(|_| "amount overflow")?;358359		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)360			.map_err(|_| "transfer error")?;361		Ok(true)362	}363	#[solidity(rename_selector = "transferFrom")]364	fn transfer_from_fungible(365		&mut self,366		caller: caller,367		from: address,368		to: address,369		amount: uint256,370	) -> Result<bool> {371		let caller = T::CrossAccountId::from_eth(caller);372		let from = T::CrossAccountId::from_eth(from);373		let to = T::CrossAccountId::from_eth(to);374		let amount = amount.try_into().map_err(|_| "amount overflow")?;375376		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)377			.map_err(|_| "transferFrom error")?;378		Ok(true)379	}380	#[solidity(rename_selector = "approve")]381	fn approve_fungible(382		&mut self,383		caller: caller,384		spender: address,385		amount: uint256,386	) -> Result<bool> {387		let caller = T::CrossAccountId::from_eth(caller);388		let spender = T::CrossAccountId::from_eth(spender);389		let amount = amount.try_into().map_err(|_| "amount overflow")?;390391		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)392			.map_err(|_| "approve internal")?;393		Ok(true)394	}395	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {396		let owner = T::CrossAccountId::from_eth(owner);397		let spender = T::CrossAccountId::from_eth(spender);398399		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())400	}401}402403#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]404impl<T: Config> CollectionHandle<T> {}405406macro_rules! generate_code {407	($name:ident, $decl:ident, $is_impl:literal) => {408		#[test]409		#[ignore]410		fn $name() {411			use sp_std::collections::btree_set::BTreeSet;412			let mut out = BTreeSet::new();413			$decl::generate_solidity_interface(&mut out, $is_impl);414			println!("=== SNIP START ===");415			println!("// SPDX-License-Identifier: OTHER");416			println!("// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::{} --exact --nocapture --ignored`", stringify!(name));417			println!();418			println!("pragma solidity >=0.8.0 <0.9.0;");419			println!();420			for b in out {421				println!("{}", b);422			}423			println!("=== SNIP END ===");424		}425	};426}427428// Not a tests, but code generators429generate_code!(nft_impl, UniqueNFTCall, true);430generate_code!(nft_iface, UniqueNFTCall, false);431432generate_code!(fungible_impl, UniqueFungibleCall, true);433generate_code!(fungible_iface, UniqueFungibleCall, false);
after · pallets/nft/src/eth/erc.rs
1use core::char::{decode_utf16, REPLACEMENT_CHARACTER};2use evm_coder::{ToLog, execution::Result, generate_stubgen, solidity, solidity_interface, types::*};3use nft_data_structs::{CreateItemData, CreateNftData};4use core::convert::TryInto;5use crate::{6	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,7	ItemListIndex,8};9use frame_support::storage::{StorageMap, StorageDoubleMap};10use pallet_evm::AddressMapping;11use super::account::CrossAccountId;12use sp_std::{vec, vec::Vec};1314#[solidity_interface(name = "ERC165")]15impl<T: Config> CollectionHandle<T> {16	fn supports_interface(&self, interface_id: bytes4) -> Result<bool> {17		Ok(match self.mode {18			CollectionMode::Fungible(_) => UniqueFungibleCall::supports_interface(interface_id),19			CollectionMode::NFT => UniqueNFTCall::supports_interface(interface_id),20			_ => false,21		})22	}23}2425#[solidity_interface(name = "InlineNameSymbol")]26impl<T: Config> CollectionHandle<T> {27	fn name(&self) -> Result<string> {28		Ok(decode_utf16(self.name.iter().copied())29			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))30			.collect::<string>())31	}3233	fn symbol(&self) -> Result<string> {34		Ok(string::from_utf8_lossy(&self.token_prefix).into())35	}36}3738#[solidity_interface(name = "InlineTotalSupply")]39impl<T: Config> CollectionHandle<T> {40	fn total_supply(&self) -> Result<uint256> {41		// TODO: we do not track total amount of all tokens42		Ok(0.into())43	}44}4546#[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]47impl<T: Config> CollectionHandle<T> {48	#[solidity(rename_selector = "tokenURI")]49	fn token_uri(&self, token_id: uint256) -> Result<string> {50		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;51		Ok(string::from_utf8_lossy(52			&<NftItemList<T>>::get(self.id, token_id)53				.ok_or("token not found")?54				.const_data,55		)56		.into())57	}58}5960#[solidity_interface(name = "ERC721Enumerable", inline_is(InlineTotalSupply))]61impl<T: Config> CollectionHandle<T> {62	fn token_by_index(&self, index: uint256) -> Result<uint256> {63		Ok(index)64	}6566	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {67		// TODO: Not implemetable68		Err("not implemented".into())69	}70}7172#[derive(ToLog)]73pub enum ERC721Events {74	Transfer {75		#[indexed]76		from: address,77		#[indexed]78		to: address,79		#[indexed]80		token_id: uint256,81	},82	Approval {83		#[indexed]84		owner: address,85		#[indexed]86		approved: address,87		#[indexed]88		token_id: uint256,89	},90	#[allow(dead_code)]91	ApprovalForAll {92		#[indexed]93		owner: address,94		#[indexed]95		operator: address,96		approved: bool,97	},98}99100#[solidity_interface(name = "ERC721", is(ERC165), events(ERC721Events))]101impl<T: Config> CollectionHandle<T> {102	#[solidity(rename_selector = "balanceOf")]103	fn balance_of_nft(&self, owner: address) -> Result<uint256> {104		let owner = T::EvmAddressMapping::into_account_id(owner);105		let balance = <Balance<T>>::get(self.id, owner);106		Ok(balance.into())107	}108	fn owner_of(&self, token_id: uint256) -> Result<address> {109		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;110		let token = <NftItemList<T>>::get(self.id, token_id).ok_or("unknown token")?;111		Ok(*token.owner.as_eth())112	}113	fn safe_transfer_from_with_data(114		&mut self,115		_from: address,116		_to: address,117		_token_id: uint256,118		_data: bytes,119		_value: value,120	) -> Result<void> {121		// TODO: Not implemetable122		Err("not implemented".into())123	}124	fn safe_transfer_from(125		&mut self,126		_from: address,127		_to: address,128		_token_id: uint256,129		_value: value,130	) -> Result<void> {131		// TODO: Not implemetable132		Err("not implemented".into())133	}134135	fn transfer_from(136		&mut self,137		caller: caller,138		from: address,139		to: address,140		token_id: uint256,141		_value: value,142	) -> Result<void> {143		let caller = T::CrossAccountId::from_eth(caller);144		let from = T::CrossAccountId::from_eth(from);145		let to = T::CrossAccountId::from_eth(to);146		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;147148		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, token_id, 1)149			.map_err(|_| "transferFrom error")?;150		Ok(())151	}152153	fn approve(154		&mut self,155		caller: caller,156		approved: address,157		token_id: uint256,158		_value: value,159	) -> Result<void> {160		let caller = T::CrossAccountId::from_eth(caller);161		let approved = T::CrossAccountId::from_eth(approved);162		let token_id = token_id.try_into().map_err(|_| "token_id overflow")?;163164		<Module<T>>::approve_internal(&caller, &approved, self, token_id, 1)165			.map_err(|_| "approve internal")?;166		Ok(())167	}168169	fn set_approval_for_all(170		&mut self,171		_caller: caller,172		_operator: address,173		_approved: bool,174	) -> Result<void> {175		// TODO: Not implemetable176		Err("not implemented".into())177	}178179	fn get_approved(&self, _token_id: uint256) -> Result<address> {180		// TODO: Not implemetable181		Err("not implemented".into())182	}183184	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {185		// TODO: Not implemetable186		Err("not implemented".into())187	}188}189190#[solidity_interface(name = "ERC721Burnable")]191impl<T: Config> CollectionHandle<T> {192	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {193		let caller = T::CrossAccountId::from_eth(caller);194		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;195196		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;197		Ok(())198	}199}200201#[derive(ToLog)]202pub enum ERC721MintableEvents {203	#[allow(dead_code)]204	MintingFinished {},205}206207#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]208impl<T: Config> CollectionHandle<T> {209	fn minting_finished(&self) -> Result<bool> {210		Ok(false)211	}212213	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {214		let caller = T::CrossAccountId::from_eth(caller);215		let to = T::CrossAccountId::from_eth(to);216		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;217		if <ItemListIndex>::get(self.id)218			.checked_add(1)219			.ok_or("item id overflow")?220			!= token_id221		{222			return Err("item id should be next".into());223		}224225		<Module<T>>::create_item_internal(226			&caller,227			&self,228			&to,229			CreateItemData::NFT(CreateNftData {230				const_data: vec![].try_into().unwrap(),231				variable_data: vec![].try_into().unwrap(),232			}),233		)234		.map_err(|_| "mint error")?;235		Ok(true)236	}237238	#[solidity(rename_selector = "mintWithTokenURI")]239	fn mint_with_token_uri(240		&mut self,241		caller: caller,242		to: address,243		token_id: uint256,244		token_uri: string,245	) -> Result<bool> {246		let caller = T::CrossAccountId::from_eth(caller);247		let to = T::CrossAccountId::from_eth(to);248		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;249		if <ItemListIndex>::get(self.id)250			.checked_add(1)251			.ok_or("item id overflow")?252			!= token_id253		{254			return Err("item id should be next".into());255		}256257		<Module<T>>::create_item_internal(258			&caller,259			&self,260			&to,261			CreateItemData::NFT(CreateNftData {262				const_data: Vec::<u8>::from(token_uri)263					.try_into()264					.map_err(|_| "token uri is too long")?,265				variable_data: vec![].try_into().unwrap(),266			}),267		)268		.map_err(|_| "mint error")?;269		Ok(true)270	}271272	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {273		Err("not implementable".into())274	}275}276277#[solidity_interface(name = "ERC721UniqueExtensions")]278impl<T: Config> CollectionHandle<T> {279	#[solidity(rename_selector = "transfer")]280	fn transfer_nft(281		&mut self,282		caller: caller,283		to: address,284		token_id: uint256,285		_value: value,286	) -> Result<void> {287		let caller = T::CrossAccountId::from_eth(caller);288		let to = T::CrossAccountId::from_eth(to);289		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;290291		<Module<T>>::transfer_internal(&caller, &to, self, token_id, 1)292			.map_err(|_| "transfer error")?;293		Ok(())294	}295296	fn next_token_id(&self) -> Result<uint256> {297		Ok(ItemListIndex::get(self.id)298			.checked_add(1)299			.ok_or("item id overflow")?300			.into())301	}302}303304#[solidity_interface(305	name = "UniqueNFT",306	is(307		ERC165,308		ERC721,309		ERC721Metadata,310		ERC721Enumerable,311		ERC721UniqueExtensions,312		ERC721Mintable,313		ERC721Burnable,314	)315)]316impl<T: Config> CollectionHandle<T> {}317318#[derive(ToLog)]319pub enum ERC20Events {320	Transfer {321		#[indexed]322		from: address,323		#[indexed]324		to: address,325		value: uint256,326	},327	Approval {328		#[indexed]329		owner: address,330		#[indexed]331		spender: address,332		value: uint256,333	},334}335336#[solidity_interface(337	name = "ERC20",338	inline_is(InlineNameSymbol, InlineTotalSupply),339	events(ERC20Events)340)]341impl<T: Config> CollectionHandle<T> {342	fn decimals(&self) -> Result<uint8> {343		Ok(if let CollectionMode::Fungible(decimals) = &self.mode {344			*decimals345		} else {346			unreachable!()347		})348	}349	fn balance_of(&self, owner: address) -> Result<uint256> {350		let owner = T::EvmAddressMapping::into_account_id(owner);351		let balance = <Balance<T>>::get(self.id, owner);352		Ok(balance.into())353	}354	fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {355		let caller = T::CrossAccountId::from_eth(caller);356		let to = T::CrossAccountId::from_eth(to);357		let amount = amount.try_into().map_err(|_| "amount overflow")?;358359		<Module<T>>::transfer_internal(&caller, &to, self, 1, amount)360			.map_err(|_| "transfer error")?;361		Ok(true)362	}363	#[solidity(rename_selector = "transferFrom")]364	fn transfer_from_fungible(365		&mut self,366		caller: caller,367		from: address,368		to: address,369		amount: uint256,370	) -> Result<bool> {371		let caller = T::CrossAccountId::from_eth(caller);372		let from = T::CrossAccountId::from_eth(from);373		let to = T::CrossAccountId::from_eth(to);374		let amount = amount.try_into().map_err(|_| "amount overflow")?;375376		<Module<T>>::transfer_from_internal(&caller, &from, &to, self, 1, amount)377			.map_err(|_| "transferFrom error")?;378		Ok(true)379	}380	#[solidity(rename_selector = "approve")]381	fn approve_fungible(382		&mut self,383		caller: caller,384		spender: address,385		amount: uint256,386	) -> Result<bool> {387		let caller = T::CrossAccountId::from_eth(caller);388		let spender = T::CrossAccountId::from_eth(spender);389		let amount = amount.try_into().map_err(|_| "amount overflow")?;390391		<Module<T>>::approve_internal(&caller, &spender, self, 1, amount)392			.map_err(|_| "approve internal")?;393		Ok(true)394	}395	fn allowance(&self, owner: address, spender: address) -> Result<uint256> {396		let owner = T::CrossAccountId::from_eth(owner);397		let spender = T::CrossAccountId::from_eth(spender);398399		Ok(<Allowances<T>>::get(self.id, (1, owner.as_sub(), spender.as_sub())).into())400	}401}402403#[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]404impl<T: Config> CollectionHandle<T> {}405406// Not a tests, but code generators407generate_stubgen!(nft_impl, UniqueNFTCall, true);408generate_stubgen!(nft_iface, UniqueNFTCall, false);409410generate_stubgen!(fungible_impl, UniqueFungibleCall, true);411generate_stubgen!(fungible_iface, UniqueFungibleCall, false);
modifiedpallets/nft/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/mod.rs
+++ b/pallets/nft/src/eth/mod.rs
@@ -96,10 +96,14 @@
 			.and_then(<CollectionById<T>>::get)
 			.map(|collection| {
 				match collection.mode {
-					CollectionMode::NFT => include_bytes!("stubs/ERC721.bin") as &[u8],
-					CollectionMode::Fungible(_) => include_bytes!("stubs/ERC20.bin") as &[u8],
-					CollectionMode::ReFungible => include_bytes!("stubs/ERC1633.bin") as &[u8],
-					CollectionMode::Invalid => include_bytes!("stubs/Invalid.bin") as &[u8],
+					CollectionMode::NFT => include_bytes!("stubs/UniqueNFT.raw") as &[u8],
+					CollectionMode::Fungible(_) => {
+						include_bytes!("stubs/UniqueFungible.raw") as &[u8]
+					}
+					CollectionMode::ReFungible => {
+						include_bytes!("stubs/UniqueRefungible.raw") as &[u8]
+					}
+					CollectionMode::Invalid => include_bytes!("stubs/UniqueInvalid.raw") as &[u8],
 				}
 				.to_owned()
 			})
deletedpallets/nft/src/eth/stubs/ERC1633.bindiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC1633.bin
+++ /dev/null
@@ -1 +0,0 @@
-TODO
\ No newline at end of file
deletedpallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterboth

binary blob — no preview

deletedpallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC20.sol
+++ /dev/null
@@ -1,94 +0,0 @@
-// SPDX-License-Identifier: OTHER
-// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
-
-pragma solidity >=0.8.0 <0.9.0;
-
-// Common stubs holder
-contract Dummy {
-	uint8 dummy;
-	string stub_error = "this contract is implemented in native";
-}
-
-// Inline
-contract ERC20Events {
-	event Transfer(address from, address to, uint256 value);
-	event Approval(address owner, address spender, uint256 value);
-}
-
-// Inline
-contract InlineNameSymbol is Dummy {
-	function name() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-	function symbol() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-}
-
-// Inline
-contract InlineTotalSupply is Dummy {
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC165 is Dummy {
-	function supportsInterface(uint32 interfaceId) public view returns (bool) {
-		require(false, stub_error);
-		interfaceId;
-		dummy;
-		return false;
-	}
-}
-
-contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
-	function decimals() public view returns (uint8) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-	function balanceOf(address owner) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		dummy;
-		return 0;
-	}
-	function transfer(address to, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		to;
-		amount;
-		dummy = 0;
-		return false;
-	}
-	function transferFrom(address from, address to, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		from;
-		to;
-		amount;
-		dummy = 0;
-		return false;
-	}
-	function approve(address spender, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		spender;
-		amount;
-		dummy = 0;
-		return false;
-	}
-	function allowance(address owner, address spender) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		spender;
-		dummy;
-		return 0;
-	}
-}
-
-contract UniqueFungible is Dummy, ERC165, ERC20 {
-}
\ No newline at end of file
deletedpallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterboth

binary blob — no preview

deletedpallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC721.sol
+++ /dev/null
@@ -1,194 +0,0 @@
-// SPDX-License-Identifier: OTHER
-// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
-
-pragma solidity >=0.8.0 <0.9.0;
-
-// Common stubs holder
-contract Dummy {
-	uint8 dummy;
-	string stub_error = "this contract is implemented in native";
-}
-
-// Inline
-contract ERC721Events {
-	event Transfer(address from, address to, uint256 tokenId);
-	event Approval(address owner, address approved, uint256 tokenId);
-	event ApprovalForAll(address owner, address operator, bool approved);
-}
-
-// Inline
-contract ERC721MintableEvents {
-	event MintingFinished();
-}
-
-// Inline
-contract InlineNameSymbol is Dummy {
-	function name() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-	function symbol() public view returns (string memory) {
-		require(false, stub_error);
-		dummy;
-		return "";
-	}
-}
-
-// Inline
-contract InlineTotalSupply is Dummy {
-	function totalSupply() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC165 is Dummy {
-	function supportsInterface(uint32 interfaceId) public view returns (bool) {
-		require(false, stub_error);
-		interfaceId;
-		dummy;
-		return false;
-	}
-}
-
-contract ERC721 is Dummy, ERC165, ERC721Events {
-	function balanceOf(address owner) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		dummy;
-		return 0;
-	}
-	function ownerOf(uint256 tokenId) public view returns (address) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-	function safeTransferFromWithData(address from, address to, uint256 tokenId, bytes memory data) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		data;
-		dummy = 0;
-	}
-	function safeTransferFrom(address from, address to, uint256 tokenId) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		dummy = 0;
-	}
-	function transferFrom(address from, address to, uint256 tokenId) public {
-		require(false, stub_error);
-		from;
-		to;
-		tokenId;
-		dummy = 0;
-	}
-	function approve(address approved, uint256 tokenId) public {
-		require(false, stub_error);
-		approved;
-		tokenId;
-		dummy = 0;
-	}
-	function setApprovalForAll(address operator, bool approved) public {
-		require(false, stub_error);
-		operator;
-		approved;
-		dummy = 0;
-	}
-	function getApproved(uint256 tokenId) public view returns (address) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-	function isApprovedForAll(address owner, address operator) public view returns (address) {
-		require(false, stub_error);
-		owner;
-		operator;
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-}
-
-contract ERC721Burnable is Dummy {
-	function burn(uint256 tokenId) public {
-		require(false, stub_error);
-		tokenId;
-		dummy = 0;
-	}
-}
-
-contract ERC721Enumerable is Dummy, InlineTotalSupply {
-	function tokenByIndex(uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		index;
-		dummy;
-		return 0;
-	}
-	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
-		require(false, stub_error);
-		owner;
-		index;
-		dummy;
-		return 0;
-	}
-}
-
-contract ERC721Metadata is Dummy, InlineNameSymbol {
-	function tokenURI(uint256 tokenId) public view returns (string memory) {
-		require(false, stub_error);
-		tokenId;
-		dummy;
-		return "";
-	}
-}
-
-contract ERC721Mintable is Dummy, ERC721MintableEvents {
-	function mintingFinished() public view returns (bool) {
-		require(false, stub_error);
-		dummy;
-		return false;
-	}
-	function mint(address to, uint256 tokenId) public returns (bool) {
-		require(false, stub_error);
-		to;
-		tokenId;
-		dummy = 0;
-		return false;
-	}
-	function mintWithTokenURI(address to, uint256 tokenId, string memory tokenUri) public returns (bool) {
-		require(false, stub_error);
-		to;
-		tokenId;
-		tokenUri;
-		dummy = 0;
-		return false;
-	}
-	function finishMinting() public returns (bool) {
-		require(false, stub_error);
-		dummy = 0;
-		return false;
-	}
-}
-
-contract ERC721UniqueExtensions is Dummy {
-	function transfer(address to, uint256 tokenId) public {
-		require(false, stub_error);
-		to;
-		tokenId;
-		dummy = 0;
-	}
-	function nextTokenId() public view returns (uint256) {
-		require(false, stub_error);
-		dummy;
-		return 0;
-	}
-}
-
-contract UniqueNFT is Dummy, ERC165, ERC721, ERC721Metadata, ERC721Enumerable, ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable {
-}
\ No newline at end of file
deletedpallets/nft/src/eth/stubs/Invalid.bindiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/Invalid.bin
+++ /dev/null
@@ -1 +0,0 @@
-TODO
\ No newline at end of file
addedpallets/nft/src/eth/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

addedpallets/nft/src/eth/stubs/UniqueFungible.soldiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/eth/stubs/UniqueFungible.sol
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
+
+// Inline
+contract ERC20Events {
+	event Transfer(address indexed from, address indexed to, uint256 value);
+	event Approval(
+		address indexed owner,
+		address indexed spender,
+		uint256 value
+	);
+}
+
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
+
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
+
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
+		require(false, stub_error);
+		interfaceId;
+		dummy;
+		return false;
+	}
+}
+
+contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	function decimals() public view returns (uint8) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+
+	function transfer(address to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	function transferFrom(
+		address from,
+		address to,
+		uint256 amount
+	) public returns (bool) {
+		require(false, stub_error);
+		from;
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	function approve(address spender, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		spender;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	function allowance(address owner, address spender)
+		public
+		view
+		returns (uint256)
+	{
+		require(false, stub_error);
+		owner;
+		spender;
+		dummy;
+		return 0;
+	}
+}
+
+contract UniqueFungible is Dummy, ERC165, ERC20 {}
addedpallets/nft/src/eth/stubs/UniqueInvalid.rawdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/eth/stubs/UniqueInvalid.raw
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
addedpallets/nft/src/eth/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

addedpallets/nft/src/eth/stubs/UniqueNFT.soldiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/eth/stubs/UniqueNFT.sol
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
+
+// Inline
+contract ERC721Events {
+	event Transfer(
+		address indexed from,
+		address indexed to,
+		uint256 indexed tokenId
+	);
+	event Approval(
+		address indexed owner,
+		address indexed approved,
+		uint256 indexed tokenId
+	);
+	event ApprovalForAll(
+		address indexed owner,
+		address indexed operator,
+		bool approved
+	);
+}
+
+// Inline
+contract ERC721MintableEvents {
+	event MintingFinished();
+}
+
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
+
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
+
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
+		require(false, stub_error);
+		interfaceId;
+		dummy;
+		return false;
+	}
+}
+
+contract ERC721 is Dummy, ERC165, ERC721Events {
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+
+	function ownerOf(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	function safeTransferFromWithData(
+		address from,
+		address to,
+		uint256 tokenId,
+		bytes memory data
+	) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		data;
+		dummy = 0;
+	}
+
+	function safeTransferFrom(
+		address from,
+		address to,
+		uint256 tokenId
+	) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	function transferFrom(
+		address from,
+		address to,
+		uint256 tokenId
+	) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	function approve(address approved, uint256 tokenId) public {
+		require(false, stub_error);
+		approved;
+		tokenId;
+		dummy = 0;
+	}
+
+	function setApprovalForAll(address operator, bool approved) public {
+		require(false, stub_error);
+		operator;
+		approved;
+		dummy = 0;
+	}
+
+	function getApproved(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	function isApprovedForAll(address owner, address operator)
+		public
+		view
+		returns (address)
+	{
+		require(false, stub_error);
+		owner;
+		operator;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+}
+
+contract ERC721Burnable is Dummy {
+	function burn(uint256 tokenId) public {
+		require(false, stub_error);
+		tokenId;
+		dummy = 0;
+	}
+}
+
+contract ERC721Enumerable is Dummy, InlineTotalSupply {
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
+		return 0;
+	}
+
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		public
+		view
+		returns (uint256)
+	{
+		require(false, stub_error);
+		owner;
+		index;
+		dummy;
+		return 0;
+	}
+}
+
+contract ERC721Metadata is Dummy, InlineNameSymbol {
+	function tokenURI(uint256 tokenId) public view returns (string memory) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return "";
+	}
+}
+
+contract ERC721Mintable is Dummy, ERC721MintableEvents {
+	function mintingFinished() public view returns (bool) {
+		require(false, stub_error);
+		dummy;
+		return false;
+	}
+
+	function mint(address to, uint256 tokenId) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+		return false;
+	}
+
+	function mintWithTokenURI(
+		address to,
+		uint256 tokenId,
+		string memory tokenUri
+	) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		tokenUri;
+		dummy = 0;
+		return false;
+	}
+
+	function finishMinting() public returns (bool) {
+		require(false, stub_error);
+		dummy = 0;
+		return false;
+	}
+}
+
+contract ERC721UniqueExtensions is Dummy {
+	function transfer(address to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+
+	function nextTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
+
+contract UniqueNFT is
+	Dummy,
+	ERC165,
+	ERC721,
+	ERC721Metadata,
+	ERC721Enumerable,
+	ERC721UniqueExtensions,
+	ERC721Mintable,
+	ERC721Burnable
+{}
addedpallets/nft/src/eth/stubs/UniqueRefungible.rawdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nft/src/eth/stubs/UniqueRefungible.raw
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
addedtests/src/eth/api/ContractHelpers.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+interface ContractHelpers is Dummy {
+	function contractOwner(address contractAddress)
+		external
+		view
+		returns (address);
+
+	function sponsoringEnabled(address contractAddress)
+		external
+		view
+		returns (bool);
+
+	function toggleSponsoring(address contractAddress, bool enabled) external;
+
+	function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
+		external;
+
+	function allowed(address contractAddress, address user)
+		external
+		view
+		returns (bool);
+
+	function allowlistEnabled(address contractAddress)
+		external
+		view
+		returns (bool);
+
+	function toggleAllowlist(address contractAddress, bool enabled) external;
+
+	function toggleAllowed(
+		address contractAddress,
+		address user,
+		bool allowed
+	) external;
+}
addedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+// Inline
+interface ERC20Events {
+	event Transfer(address indexed from, address indexed to, uint256 value);
+	event Approval(
+		address indexed owner,
+		address indexed spender,
+		uint256 value
+	);
+}
+
+// Inline
+interface InlineNameSymbol is Dummy {
+	function name() external view returns (string memory);
+
+	function symbol() external view returns (string memory);
+}
+
+// Inline
+interface InlineTotalSupply is Dummy {
+	function totalSupply() external view returns (uint256);
+}
+
+interface ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) external view returns (bool);
+}
+
+interface ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	function decimals() external view returns (uint8);
+
+	function balanceOf(address owner) external view returns (uint256);
+
+	function transfer(address to, uint256 amount) external returns (bool);
+
+	function transferFrom(
+		address from,
+		address to,
+		uint256 amount
+	) external returns (bool);
+
+	function approve(address spender, uint256 amount) external returns (bool);
+
+	function allowance(address owner, address spender)
+		external
+		view
+		returns (uint256);
+}
+
+interface UniqueFungible is Dummy, ERC165, ERC20 {}
addedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+// Inline
+interface ERC721Events {
+	event Transfer(
+		address indexed from,
+		address indexed to,
+		uint256 indexed tokenId
+	);
+	event Approval(
+		address indexed owner,
+		address indexed approved,
+		uint256 indexed tokenId
+	);
+	event ApprovalForAll(
+		address indexed owner,
+		address indexed operator,
+		bool approved
+	);
+}
+
+// Inline
+interface ERC721MintableEvents {
+	event MintingFinished();
+}
+
+// Inline
+interface InlineNameSymbol is Dummy {
+	function name() external view returns (string memory);
+
+	function symbol() external view returns (string memory);
+}
+
+// Inline
+interface InlineTotalSupply is Dummy {
+	function totalSupply() external view returns (uint256);
+}
+
+interface ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) external view returns (bool);
+}
+
+interface ERC721 is Dummy, ERC165, ERC721Events {
+	function balanceOf(address owner) external view returns (uint256);
+
+	function ownerOf(uint256 tokenId) external view returns (address);
+
+	function safeTransferFromWithData(
+		address from,
+		address to,
+		uint256 tokenId,
+		bytes memory data
+	) external;
+
+	function safeTransferFrom(
+		address from,
+		address to,
+		uint256 tokenId
+	) external;
+
+	function transferFrom(
+		address from,
+		address to,
+		uint256 tokenId
+	) external;
+
+	function approve(address approved, uint256 tokenId) external;
+
+	function setApprovalForAll(address operator, bool approved) external;
+
+	function getApproved(uint256 tokenId) external view returns (address);
+
+	function isApprovedForAll(address owner, address operator)
+		external
+		view
+		returns (address);
+}
+
+interface ERC721Burnable is Dummy {
+	function burn(uint256 tokenId) external;
+}
+
+interface ERC721Enumerable is Dummy, InlineTotalSupply {
+	function tokenByIndex(uint256 index) external view returns (uint256);
+
+	function tokenOfOwnerByIndex(address owner, uint256 index)
+		external
+		view
+		returns (uint256);
+}
+
+interface ERC721Metadata is Dummy, InlineNameSymbol {
+	function tokenURI(uint256 tokenId) external view returns (string memory);
+}
+
+interface ERC721Mintable is Dummy, ERC721MintableEvents {
+	function mintingFinished() external view returns (bool);
+
+	function mint(address to, uint256 tokenId) external returns (bool);
+
+	function mintWithTokenURI(
+		address to,
+		uint256 tokenId,
+		string memory tokenUri
+	) external returns (bool);
+
+	function finishMinting() external returns (bool);
+}
+
+interface ERC721UniqueExtensions is Dummy {
+	function transfer(address to, uint256 tokenId) external;
+
+	function nextTokenId() external view returns (uint256);
+}
+
+interface UniqueNFT is
+	Dummy,
+	ERC165,
+	ERC721,
+	ERC721Metadata,
+	ERC721Enumerable,
+	ERC721UniqueExtensions,
+	ERC721Mintable,
+	ERC721Burnable
+{}
addedtests/src/eth/proxy/UniqueFungibleProxy.abidiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueFungibleProxy.abi
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address","name":"_proxied","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"interfaceId","type":"uint32"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
addedtests/src/eth/proxy/UniqueFungibleProxy.bindiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueFungibleProxy.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b5060405161098038038061098083398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b6108ed806100936000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c806370a082311161006657806370a082311461012757806395d89b411461013a578063a9059cbb14610142578063dd62ed3e14610155578063f4f4b5001461016857600080fd5b806306fdde03146100a3578063095ea7b3146100c157806318160ddd146100e457806323b872dd146100fa578063313ce5671461010d575b600080fd5b6100ab61017b565b6040516100b8919061083e565b60405180910390f35b6100d46100cf3660046106e3565b610200565b60405190151581526020016100b8565b6100ec61028f565b6040519081526020016100b8565b6100d46101083660046106a7565b610316565b6101156103ad565b60405160ff90911681526020016100b8565b6100ec610135366004610659565b610434565b6100ab6104b8565b6100d46101503660046106e3565b6104fc565b6100ec610163366004610674565b610536565b6100d46101763660046107f5565b6105bc565b60008054604080516306fdde0360e01b815290516060936001600160a01b03909316926306fdde039260048082019391829003018186803b1580156101bf57600080fd5b505afa1580156101d3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101fb919081019061072f565b905090565b6000805460405163095ea7b360e01b81526001600160a01b038581166004830152602482018590529091169063095ea7b3906044015b602060405180830381600087803b15801561025057600080fd5b505af1158015610264573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610288919061070d565b9392505050565b60008060009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b1580156102de57600080fd5b505afa1580156102f2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101fb91906107dc565b600080546040516323b872dd60e01b81526001600160a01b038681166004830152858116602483015260448201859052909116906323b872dd90606401602060405180830381600087803b15801561036d57600080fd5b505af1158015610381573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103a5919061070d565b949350505050565b60008060009054906101000a90046001600160a01b03166001600160a01b031663313ce5676040518163ffffffff1660e01b815260040160206040518083038186803b1580156103fc57600080fd5b505afa158015610410573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101fb919061081b565b600080546040516370a0823160e01b81526001600160a01b038481166004830152909116906370a082319060240160206040518083038186803b15801561047a57600080fd5b505afa15801561048e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b291906107dc565b92915050565b60008054604080516395d89b4160e01b815290516060936001600160a01b03909316926395d89b419260048082019391829003018186803b1580156101bf57600080fd5b6000805460405163a9059cbb60e01b81526001600160a01b038581166004830152602482018590529091169063a9059cbb90604401610236565b60008054604051636eb1769f60e11b81526001600160a01b03858116600483015284811660248301529091169063dd62ed3e9060440160206040518083038186803b15801561058457600080fd5b505afa158015610598573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061028891906107dc565b6000805460405162f4f4b560e81b815263ffffffff841660048201526001600160a01b039091169063f4f4b5009060240160206040518083038186803b15801561060557600080fd5b505afa158015610619573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b2919061070d565b80356001600160a01b038116811461065457600080fd5b919050565b60006020828403121561066b57600080fd5b6102888261063d565b6000806040838503121561068757600080fd5b6106908361063d565b915061069e6020840161063d565b90509250929050565b6000806000606084860312156106bc57600080fd5b6106c58461063d565b92506106d36020850161063d565b9150604084013590509250925092565b600080604083850312156106f657600080fd5b6106ff8361063d565b946020939093013593505050565b60006020828403121561071f57600080fd5b8151801515811461028857600080fd5b60006020828403121561074157600080fd5b815167ffffffffffffffff8082111561075957600080fd5b818401915084601f83011261076d57600080fd5b81518181111561077f5761077f6108a1565b604051601f8201601f19908116603f011681019083821181831017156107a7576107a76108a1565b816040528281528760208487010111156107c057600080fd5b6107d1836020830160208801610871565b979650505050505050565b6000602082840312156107ee57600080fd5b5051919050565b60006020828403121561080757600080fd5b813563ffffffff8116811461028857600080fd5b60006020828403121561082d57600080fd5b815160ff8116811461028857600080fd5b602081526000825180602084015261085d816040850160208701610871565b601f01601f19169190910160400192915050565b60005b8381101561088c578181015183820152602001610874565b8381111561089b576000848401525b50505050565b634e487b7160e01b600052604160045260246000fdfea2646970667358221220c05e4cb7ab439b86c0b6ac8a84eec6a230b592fa63d1ab2e3a7f1474c27338f364736f6c63430008070033
\ No newline at end of file
addedtests/src/eth/proxy/UniqueFungibleProxy.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueFungibleProxy.sol
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: OTHER
+
+pragma solidity >=0.8.0 <0.9.0;
+
+import "../api/UniqueFungible.sol";
+
+contract UniqueFungibleProxy is UniqueFungible {
+    UniqueFungible proxied;
+
+    constructor(address _proxied) UniqueFungible() {
+        proxied = UniqueFungible(_proxied);
+    }
+
+    function name() external view override returns (string memory) {
+        return proxied.name();
+    }
+
+    function symbol() external view override returns (string memory) {
+        return proxied.symbol();
+    }
+
+    function totalSupply() external view override returns (uint256) {
+        return proxied.totalSupply();
+    }
+
+    function supportsInterface(uint32 interfaceId)
+        external
+        view
+        override
+        returns (bool)
+    {
+        return proxied.supportsInterface(interfaceId);
+    }
+
+    function decimals() external view override returns (uint8) {
+        return proxied.decimals();
+    }
+
+    function balanceOf(address owner) external view override returns (uint256) {
+        return proxied.balanceOf(owner);
+    }
+
+    function transfer(address to, uint256 amount)
+        external
+        override
+        returns (bool)
+    {
+        return proxied.transfer(to, amount);
+    }
+
+    function transferFrom(
+        address from,
+        address to,
+        uint256 amount
+    ) external override returns (bool) {
+        return proxied.transferFrom(from, to, amount);
+    }
+
+    function approve(address spender, uint256 amount)
+        external
+        override
+        returns (bool)
+    {
+        return proxied.approve(spender, amount);
+    }
+
+    function allowance(address owner, address spender)
+        external
+        view
+        override
+        returns (uint256)
+    {
+        return proxied.allowance(owner, spender);
+    }
+}
addedtests/src/eth/proxy/UniqueNFTProxy.abidiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueNFTProxy.abi
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address","name":"_proxied","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[],"name":"MintingFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"approved","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"finishMinting","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"tokenUri","type":"string"}],"name":"mintWithTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintingFinished","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFromWithData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"interfaceId","type":"uint32"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
addedtests/src/eth/proxy/UniqueNFTProxy.bindiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueNFTProxy.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b506040516111e03803806111e083398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b61114d806100936000396000f3fe608060405234801561001057600080fd5b506004361061014d5760003560e01c806350bb4e7f116100c357806395d89b411161007c57806395d89b41146102a8578063a22cb465146102b0578063a9059cbb146102c3578063c87b56dd146102d6578063e985e9c5146102e9578063f4f4b500146102fc57600080fd5b806350bb4e7f1461024c57806360a116721461025f5780636352211e1461027257806370a082311461028557806375794a3c146102985780637d64bcb4146102a057600080fd5b806323b872dd1161011557806323b872dd146101da5780632f745c59146101ed57806340c10f191461020057806342842e0e1461021357806342966c68146102265780634f6ccce71461023957600080fd5b806305d2035b1461015257806306fdde031461016f578063081812fc14610184578063095ea7b3146101af57806318160ddd146101c4575b600080fd5b61015a61030f565b60405190151581526020015b60405180910390f35b61017761039b565b6040516101669190611043565b610197610192366004610f5b565b61041b565b6040516001600160a01b039091168152602001610166565b6101c26101bd366004610e2e565b61049f565b005b6101cc61050a565b604051908152602001610166565b6101c26101e8366004610d3f565b610591565b6101cc6101fb366004610e2e565b610605565b61015a61020e366004610e2e565b610691565b6101c2610221366004610d3f565b610718565b6101c2610234366004610f5b565b610759565b6101cc610247366004610f5b565b6107ba565b61015a61025a366004610e5a565b610838565b6101c261026d366004610d80565b6108c7565b610197610280366004610f5b565b610936565b6101cc610293366004610ccc565b610968565b6101cc61099b565b61015a6109ea565b610177610a4f565b6101c26102be366004610e00565b610a93565b6101c26102d1366004610e2e565b610acd565b6101776102e4366004610f5b565b610b06565b6101976102f7366004610d06565b610b87565b61015a61030a366004610f8d565b610c0d565b60008060009054906101000a90046001600160a01b03166001600160a01b03166305d2035b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561035e57600080fd5b505afa158015610372573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103969190610ec7565b905090565b60008054604080516306fdde0360e01b815290516060936001600160a01b03909316926306fdde039260048082019391829003018186803b1580156103df57600080fd5b505afa1580156103f3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103969190810190610ee4565b6000805460405163020604bf60e21b8152600481018490526001600160a01b039091169063081812fc906024015b60206040518083038186803b15801561046157600080fd5b505afa158015610475573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610ce9565b92915050565b60005460405163095ea7b360e01b81526001600160a01b038481166004830152602482018490529091169063095ea7b3906044015b600060405180830381600087803b1580156104ee57600080fd5b505af1158015610502573d6000803e3d6000fd5b505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561055957600080fd5b505afa15801561056d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103969190610f74565b6000546040516323b872dd60e01b81526001600160a01b038581166004830152848116602483015260448201849052909116906323b872dd906064015b600060405180830381600087803b1580156105e857600080fd5b505af11580156105fc573d6000803e3d6000fd5b50505050505050565b60008054604051632f745c5960e01b81526001600160a01b0385811660048301526024820185905290911690632f745c599060440160206040518083038186803b15801561065257600080fd5b505afa158015610666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610f74565b9392505050565b600080546040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052909116906340c10f1990604401602060405180830381600087803b1580156106e057600080fd5b505af11580156106f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610ec7565b600054604051632142170760e11b81526001600160a01b038581166004830152848116602483015260448201849052909116906342842e0e906064016105ce565b600054604051630852cd8d60e31b8152600481018390526001600160a01b03909116906342966c6890602401600060405180830381600087803b15801561079f57600080fd5b505af11580156107b3573d6000803e3d6000fd5b5050505050565b60008054604051634f6ccce760e01b8152600481018490526001600160a01b0390911690634f6ccce7906024015b60206040518083038186803b15801561080057600080fd5b505afa158015610814573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610f74565b600080546040516350bb4e7f60e01b81526001600160a01b03909116906350bb4e7f9061086d9087908790879060040161101c565b602060405180830381600087803b15801561088757600080fd5b505af115801561089b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108bf9190610ec7565b949350505050565b6000546040516330508b3960e11b81526001600160a01b03909116906360a11672906108fd908790879087908790600401610fdf565b600060405180830381600087803b15801561091757600080fd5b505af115801561092b573d6000803e3d6000fd5b505050505b50505050565b600080546040516331a9108f60e11b8152600481018490526001600160a01b0390911690636352211e90602401610449565b600080546040516370a0823160e01b81526001600160a01b038481166004830152909116906370a08231906024016107e8565b60008060009054906101000a90046001600160a01b03166001600160a01b03166375794a3c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561055957600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b0316637d64bcb46040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610a3b57600080fd5b505af1158015610372573d6000803e3d6000fd5b60008054604080516395d89b4160e01b815290516060936001600160a01b03909316926395d89b419260048082019391829003018186803b1580156103df57600080fd5b60005460405163a22cb46560e01b81526001600160a01b03848116600483015283151560248301529091169063a22cb465906044016104d4565b60005460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016104d4565b60005460405163c87b56dd60e01b8152600481018390526060916001600160a01b03169063c87b56dd9060240160006040518083038186803b158015610b4b57600080fd5b505afa158015610b5f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526104999190810190610ee4565b6000805460405163e985e9c560e01b81526001600160a01b03858116600483015284811660248301529091169063e985e9c59060440160206040518083038186803b158015610bd557600080fd5b505afa158015610be9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610ce9565b6000805460405162f4f4b560e81b815263ffffffff841660048201526001600160a01b039091169063f4f4b5009060240160206040518083038186803b158015610c5657600080fd5b505afa158015610c6a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610ec7565b6000610ca1610c9c84611087565b611056565b9050828152838383011115610cb557600080fd5b828260208301376000602084830101529392505050565b600060208284031215610cde57600080fd5b813561068a816110f1565b600060208284031215610cfb57600080fd5b815161068a816110f1565b60008060408385031215610d1957600080fd5b8235610d24816110f1565b91506020830135610d34816110f1565b809150509250929050565b600080600060608486031215610d5457600080fd5b8335610d5f816110f1565b92506020840135610d6f816110f1565b929592945050506040919091013590565b60008060008060808587031215610d9657600080fd5b8435610da1816110f1565b93506020850135610db1816110f1565b925060408501359150606085013567ffffffffffffffff811115610dd457600080fd5b8501601f81018713610de557600080fd5b610df487823560208401610c8e565b91505092959194509250565b60008060408385031215610e1357600080fd5b8235610e1e816110f1565b91506020830135610d3481611109565b60008060408385031215610e4157600080fd5b8235610e4c816110f1565b946020939093013593505050565b600080600060608486031215610e6f57600080fd5b8335610e7a816110f1565b925060208401359150604084013567ffffffffffffffff811115610e9d57600080fd5b8401601f81018613610eae57600080fd5b610ebd86823560208401610c8e565b9150509250925092565b600060208284031215610ed957600080fd5b815161068a81611109565b600060208284031215610ef657600080fd5b815167ffffffffffffffff811115610f0d57600080fd5b8201601f81018413610f1e57600080fd5b8051610f2c610c9c82611087565b818152856020838501011115610f4157600080fd5b610f528260208301602086016110af565b95945050505050565b600060208284031215610f6d57600080fd5b5035919050565b600060208284031215610f8657600080fd5b5051919050565b600060208284031215610f9f57600080fd5b813563ffffffff8116811461068a57600080fd5b60008151808452610fcb8160208601602086016110af565b601f01601f19169290920160200192915050565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061101290830184610fb3565b9695505050505050565b60018060a01b0384168152826020820152606060408201526000610f526060830184610fb3565b60208152600061068a6020830184610fb3565b604051601f8201601f1916810167ffffffffffffffff8111828210171561107f5761107f6110db565b604052919050565b600067ffffffffffffffff8211156110a1576110a16110db565b50601f01601f191660200190565b60005b838110156110ca5781810151838201526020016110b2565b838111156109305750506000910152565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b038116811461110657600080fd5b50565b801515811461110657600080fdfea2646970667358221220cd50bb20b48b73eddb390585af8699a01cd5f49b79ab50aa532df41df639f36764736f6c63430008070033
\ No newline at end of file
addedtests/src/eth/proxy/UniqueNFTProxy.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/UniqueNFTProxy.sol
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: OTHER
+
+pragma solidity >=0.8.0 <0.9.0;
+
+import "../api/UniqueNFT.sol";
+
+contract UniqueNFTProxy is UniqueNFT {
+    UniqueNFT proxied;
+
+    constructor(address _proxied) UniqueNFT() {
+        proxied = UniqueNFT(_proxied);
+    }
+
+    function name() external view override returns (string memory) {
+        return proxied.name();
+    }
+
+    function symbol() external view override returns (string memory) {
+        return proxied.symbol();
+    }
+
+    function totalSupply() external view override returns (uint256) {
+        return proxied.totalSupply();
+    }
+
+    function balanceOf(address owner) external view override returns (uint256) {
+        return proxied.balanceOf(owner);
+    }
+
+    function ownerOf(uint256 tokenId) external view override returns (address) {
+        return proxied.ownerOf(tokenId);
+    }
+
+    function safeTransferFromWithData(
+        address from,
+        address to,
+        uint256 tokenId,
+        bytes memory data
+    ) external override {
+        return proxied.safeTransferFromWithData(from, to, tokenId, data);
+    }
+
+    function safeTransferFrom(
+        address from,
+        address to,
+        uint256 tokenId
+    ) external override {
+        return proxied.safeTransferFrom(from, to, tokenId);
+    }
+
+    function transferFrom(
+        address from,
+        address to,
+        uint256 tokenId
+    ) external override {
+        return proxied.transferFrom(from, to, tokenId);
+    }
+
+    function approve(address approved, uint256 tokenId) external override {
+        return proxied.approve(approved, tokenId);
+    }
+
+    function setApprovalForAll(address operator, bool approved)
+        external
+        override
+    {
+        return proxied.setApprovalForAll(operator, approved);
+    }
+
+    function getApproved(uint256 tokenId)
+        external
+        view
+        override
+        returns (address)
+    {
+        return proxied.getApproved(tokenId);
+    }
+
+    function isApprovedForAll(address owner, address operator)
+        external
+        view
+        override
+        returns (address)
+    {
+        return proxied.isApprovedForAll(owner, operator);
+    }
+
+    function burn(uint256 tokenId) external override {
+        return proxied.burn(tokenId);
+    }
+
+    function tokenByIndex(uint256 index)
+        external
+        view
+        override
+        returns (uint256)
+    {
+        return proxied.tokenByIndex(index);
+    }
+
+    function tokenOfOwnerByIndex(address owner, uint256 index)
+        external
+        view
+        override
+        returns (uint256)
+    {
+        return proxied.tokenOfOwnerByIndex(owner, index);
+    }
+
+    function tokenURI(uint256 tokenId)
+        external
+        view
+        override
+        returns (string memory)
+    {
+        return proxied.tokenURI(tokenId);
+    }
+
+    function mintingFinished() external view override returns (bool) {
+        return proxied.mintingFinished();
+    }
+
+    function mint(address to, uint256 tokenId)
+        external
+        override
+        returns (bool)
+    {
+        return proxied.mint(to, tokenId);
+    }
+
+    function mintWithTokenURI(
+        address to,
+        uint256 tokenId,
+        string memory tokenUri
+    ) external override returns (bool) {
+        return proxied.mintWithTokenURI(to, tokenId, tokenUri);
+    }
+
+    function finishMinting() external override returns (bool) {
+        return proxied.finishMinting();
+    }
+
+    function transfer(address to, uint256 tokenId) external override {
+        return proxied.transfer(to, tokenId);
+    }
+
+    function nextTokenId() external view override returns (uint256) {
+        return proxied.nextTokenId();
+    }
+
+    function supportsInterface(uint32 interfaceId)
+        external
+        view
+        override
+        returns (bool)
+    {
+        return proxied.supportsInterface(interfaceId);
+    }
+}
addedtests/src/eth/proxy/fungibleProxy.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/fungibleProxy.test.ts
@@ -0,0 +1,194 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import privateKey from '../../substrate/privateKey';
+import { createCollectionExpectSuccess, createFungibleItemExpectSuccess } from '../../util/helpers';
+import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents } from '../util/helpers';
+import fungibleAbi from '../fungibleAbi.json';
+import { expect } from 'chai';
+import { ApiPromise } from '@polkadot/api';
+import Web3 from 'web3';
+import { readFile } from 'fs/promises';
+
+async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any) {
+  // Proxy owner has no special privilegies, we don't need to reuse them
+  const owner = await createEthAccountWithBalance(api, web3);
+  const Proxy = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueFungibleProxy.abi`)).toString()), undefined, {
+    from: owner,
+    ...GAS_ARGS,
+  });
+  const proxy = await Proxy.deploy({ data: (await readFile(`${__dirname}/UniqueFungibleProxy.bin`)).toString(), arguments: [wrapped.options.address] }).send({ from: owner });
+  return proxy;
+}
+
+describe('Fungible (Via EVM proxy): Information getting', () => {
+  itWeb3('totalSupply', async ({ api, web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: { type: 'Fungible', decimalPoints: 0 },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+  
+    await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { substrate: alice.address });
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const totalSupply = await contract.methods.totalSupply().call();
+
+    // FIXME: always equals to 0, because this method is not implemented
+    expect(totalSupply).to.equal('0');
+  });
+
+  itWeb3('balanceOf', async ({ api, web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: { type: 'Fungible', decimalPoints: 0 },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: caller });
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const balance = await contract.methods.balanceOf(caller).call();
+
+    expect(balance).to.equal('200');
+  });
+});
+
+describe('Fungible (Via EVM proxy): Plain calls', () => {
+  itWeb3('Can perform approve()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: { type: 'Fungible', decimalPoints: 0 },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const spender = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: contract.options.address });
+
+    {
+      const result = await contract.methods.approve(spender, 100).send({from: caller});
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Approval',
+          args: {
+            owner: contract.options.address,
+            spender,
+            value: '100',
+          },
+        },
+      ]);
+    }
+
+    {
+      const allowance = await contract.methods.allowance(contract.options.address, spender).call();
+      expect(+allowance).to.equal(100);
+    }
+  });
+
+  itWeb3('Can perform transferFrom()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: { type: 'Fungible', decimalPoints: 0 },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3);
+
+    await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: owner });
+
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const evmCollection = new web3.eth.Contract(fungibleAbi as any, address, { from: caller, ...GAS_ARGS });
+    const contract = await proxyWrap(api, web3, evmCollection);
+
+    await evmCollection.methods.approve(contract.options.address, 100).send({from: owner});
+
+    {
+      const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: caller});
+      const events = normalizeEvents(result.events);
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: owner,
+            to: receiver,
+            value: '49',
+          },
+        },
+        {
+          address,
+          event: 'Approval',
+          args: {
+            owner,
+            spender: contract.options.address,
+            value: '51',
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(49);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(owner).call();
+      expect(+balance).to.equal(151);
+    }
+  });
+
+  itWeb3('Can perform transfer()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      name: 'token name',
+      mode: { type: 'Fungible', decimalPoints: 0 },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const receiver = await createEthAccountWithBalance(api, web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: contract.options.address });
+
+    {
+      const result = await contract.methods.transfer(receiver, 50).send({ from: caller});
+      const events = normalizeEvents(result.events);
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: contract.options.address,
+            to: receiver,
+            value: '50',
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(contract.options.address).call();
+      expect(+balance).to.equal(150);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(50);
+    }
+  });
+});
addedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -0,0 +1,270 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import privateKey from '../../substrate/privateKey';
+import { createCollectionExpectSuccess, createItemExpectSuccess } from '../../util/helpers';
+import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents } from '../util/helpers';
+import nonFungibleAbi from '../nonFungibleAbi.json';
+import { expect } from 'chai';
+import waitNewBlocks from '../../substrate/wait-new-blocks';
+import { submitTransactionAsync } from '../../substrate/substrate-api';
+import Web3 from 'web3';
+import { readFile } from 'fs/promises';
+import { ApiPromise } from '@polkadot/api';
+
+async function proxyWrap(api: ApiPromise, web3: Web3, wrapped: any) {
+  // Proxy owner has no special privilegies, we don't need to reuse them
+  const owner = await createEthAccountWithBalance(api, web3);
+  const Proxy = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueNFTProxy.abi`)).toString()), undefined, {
+    from: owner,
+    ...GAS_ARGS,
+  });
+  const proxy = await Proxy.deploy({ data: (await readFile(`${__dirname}/UniqueNFTProxy.bin`)).toString(), arguments: [wrapped.options.address] }).send({ from: owner });
+  return proxy;
+}
+
+describe('NFT (Via EVM proxy): Information getting', () => {
+  itWeb3('totalSupply', async ({ api, web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    await createItemExpectSuccess(alice, collection, 'NFT', { substrate: alice.address });
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const totalSupply = await contract.methods.totalSupply().call();
+
+    // FIXME: always equals to 0, because this method is not implemented
+    expect(totalSupply).to.equal('0');
+  });
+
+  itWeb3('balanceOf', async ({ api, web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const caller = await createEthAccountWithBalance(api, web3);
+    await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+    await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+    await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const balance = await contract.methods.balanceOf(caller).call();
+
+    expect(balance).to.equal('3');
+  });
+
+  itWeb3('ownerOf', async ({ api, web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const caller = await createEthAccountWithBalance(api, web3);
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const owner = await contract.methods.ownerOf(tokenId).call();
+
+    expect(owner).to.equal(caller);
+  });
+});
+
+describe('NFT (Via EVM proxy): Plain calls', () => {
+  itWeb3('Can perform mint()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, { from: caller, ...GAS_ARGS }));
+
+    const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: contract.options.address });
+    await submitTransactionAsync(alice, changeAdminTx);
+
+    {
+      const nextTokenId = await contract.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      console.log('Before mint');
+      const result = await contract.methods.mintWithTokenURI(
+        receiver,
+        nextTokenId,
+        'Test URI',
+      ).send({ from: caller });
+      console.log('After mint');
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
+        },
+      ]);
+
+      await waitNewBlocks(api, 1);
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+    }
+  });
+
+  itWeb3('Can perform burn()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: {type: 'NFT'},
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: contract.options.address });
+
+    const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: contract.options.address });
+    await submitTransactionAsync(alice, changeAdminTx);
+
+    {
+      const result = await contract.methods.burn(tokenId).send({ from: caller });
+      const events = normalizeEvents(result.events);
+      
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: contract.options.address,
+            to: '0x0000000000000000000000000000000000000000',
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+  });
+
+  itWeb3('Can perform approve()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const spender = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address));
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: contract.options.address });
+
+    {
+      const result = await contract.methods.approve(spender, tokenId).send({ from: caller, gas: '0x1000000', gasPrice: '0x01' });
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Approval',
+          args: {
+            owner: contract.options.address,
+            approved: spender,
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+  });
+
+  itWeb3('Can perform transferFrom()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const owner = await createEthAccountWithBalance(api, web3);
+
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, address, { from: caller, ...GAS_ARGS });
+    const contract = await proxyWrap(api, web3, evmCollection);
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+    await evmCollection.methods.approve(contract.options.address, tokenId).send({ from: owner });
+
+    {
+      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({ from: caller });
+      const events = normalizeEvents(result.events);
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: owner,
+            to: receiver,
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(contract.options.address).call();
+      expect(+balance).to.equal(0);
+    }
+  });
+
+  itWeb3('Can perform transfer()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}));
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: contract.options.address });
+
+    {
+      const result = await contract.methods.transfer(receiver, tokenId).send({ from: caller });
+      await waitNewBlocks(api, 1);
+      const events = normalizeEvents(result.events);
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: contract.options.address,
+            to: receiver,
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(contract.options.address).call();
+      expect(+balance).to.equal(0);
+    }
+
+    {
+      const balance = await contract.methods.balanceOf(receiver).call();
+      expect(+balance).to.equal(1);
+    }
+  });
+});