git.delta.rocks / unique-network / refs/commits / 0902e70cd1b8

difftreelog

CORE-317 Fix ERC165 support interface

Trubnikov Sergey2022-04-11parent: #219f5f0.patch.diff
in: master

7 files changed

modifiedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -57,7 +57,7 @@
 	fn expand_interface_id(&self) -> proc_macro2::TokenStream {
 		let pascal_call_name = &self.pascal_call_name;
 		quote! {
-			interface_id ^= #pascal_call_name::interface_id();
+			interface_id ^= u32::from_be_bytes(#pascal_call_name::interface_id());
 		}
 	}
 
@@ -540,18 +540,18 @@
 
 	fn expand_const(&self) -> proc_macro2::TokenStream {
 		let screaming_name = &self.screaming_name;
-		let selector = self.selector;
+		let selector = u32::to_be_bytes(self.selector);
 		let selector_str = &self.selector_str;
 		quote! {
 			#[doc = #selector_str]
-			const #screaming_name: u32 = #selector;
+			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
 		}
 	}
 
 	fn expand_interface_id(&self) -> proc_macro2::TokenStream {
 		let screaming_name = &self.screaming_name;
 		quote! {
-			interface_id ^= Self::#screaming_name;
+			interface_id ^= u32::from_be_bytes(Self::#screaming_name);
 		}
 	}
 
@@ -831,14 +831,14 @@
 				#(
 					#consts
 				)*
-				pub fn interface_id() -> u32 {
+				pub fn interface_id() -> ::evm_coder::types::bytes4 {
 					let mut interface_id = 0;
 					#(#interface_id)*
 					#(#inline_interface_id)*
-					interface_id
+					u32::to_be_bytes(interface_id)
 				}
-				pub fn supports_interface(interface_id: u32) -> bool {
-					interface_id != 0xffffff && (
+				pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool {
+					interface_id != u32::to_be_bytes(0xffffff) && (
 						interface_id == ::evm_coder::ERC165Call::INTERFACE_ID ||
 						interface_id == Self::interface_id()
 						#(
@@ -884,7 +884,7 @@
 				}
 			}
 			impl #gen_ref ::evm_coder::Call for #call_name #gen_ref {
-				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {
+				fn parse(method_id: ::evm_coder::types::bytes4, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {
 					use ::evm_coder::abi::AbiRead;
 					match method_id {
 						::evm_coder::ERC165Call::INTERFACE_ID => return Ok(
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
before · crates/evm-coder-macros/src/to_log.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use inflector::cases;18use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};19use std::fmt::Write;20use quote::quote;2122use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};2324struct EventField {25	name: Ident,26	camel_name: String,27	ty: Ident,28	indexed: bool,29}3031impl EventField {32	fn try_from(field: &Field) -> syn::Result<Self> {33		let name = field.ident.as_ref().unwrap();34		let ty = parse_ident_from_type(&field.ty, false)?;35		let mut indexed = false;36		for attr in &field.attrs {37			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {38				if ident == "indexed" {39					indexed = true;40				}41			}42		}43		Ok(Self {44			name: name.to_owned(),45			camel_name: cases::camelcase::to_camel_case(&name.to_string()),46			ty: ty.to_owned(),47			indexed,48		})49	}50	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {51		let camel_name = &self.camel_name;52		let ty = &self.ty;53		let indexed = self.indexed;54		quote! {55			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)56		}57	}58}5960struct Event {61	name: Ident,62	name_screaming: Ident,63	fields: Vec<EventField>,64	selector: [u8; 32],65	selector_str: String,66}6768impl Event {69	fn try_from(variant: &Variant) -> syn::Result<Self> {70		let name = &variant.ident;71		let name_screaming = snake_ident_to_screaming(name);7273		let named = match &variant.fields {74			Fields::Named(named) => named,75			_ => {76				return Err(syn::Error::new(77					variant.fields.span(),78					"expected named fields",79				))80			}81		};82		let mut fields = Vec::new();83		for field in &named.named {84			fields.push(EventField::try_from(field)?);85		}86		if fields.iter().filter(|f| f.indexed).count() > 3 {87			return Err(syn::Error::new(88				variant.fields.span(),89				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"90			));91		}92		let mut selector_str = format!("{}(", name);93		for (i, arg) in fields.iter().enumerate() {94			if i != 0 {95				write!(selector_str, ",").unwrap();96			}97			write!(selector_str, "{}", arg.ty).unwrap();98		}99		selector_str.push(')');100		let selector = crate::event_selector_str(&selector_str);101102		Ok(Self {103			name: name.to_owned(),104			name_screaming,105			fields,106			selector,107			selector_str,108		})109	}110111	fn expand_serializers(&self) -> proc_macro2::TokenStream {112		let name = &self.name;113		let name_screaming = &self.name_screaming;114		let fields = self.fields.iter().map(|f| &f.name);115116		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);117		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);118119		quote! {120			Self::#name {#(121				#fields,122			)*} => {123				topics.push(topic::from(Self::#name_screaming));124				#(125					topics.push(#indexed.to_topic());126				)*127				#(128					#plain.abi_write(&mut writer);129				)*130			}131		}132	}133134	fn expand_consts(&self) -> proc_macro2::TokenStream {135		let name_screaming = &self.name_screaming;136		let selector_str = &self.selector_str;137		let selector = &self.selector;138139		quote! {140			#[doc = #selector_str]141			const #name_screaming: [u8; 32] = [#(142				#selector,143			)*];144		}145	}146147	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {148		let name = self.name.to_string();149		let args = self.fields.iter().map(EventField::expand_solidity_argument);150		quote! {151			SolidityEvent {152				name: #name,153				args: (154					#(155						#args,156					)*157				),158			}159		}160	}161}162163pub struct Events {164	name: Ident,165	events: Vec<Event>,166}167168impl Events {169	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {170		let name = &data.ident;171		let en = match &data.data {172			Data::Enum(en) => en,173			_ => return Err(syn::Error::new(data.span(), "expected enum")),174		};175		let mut events = Vec::new();176		for variant in &en.variants {177			events.push(Event::try_from(variant)?);178		}179		Ok(Self {180			name: name.to_owned(),181			events,182		})183	}184	pub fn expand(&self) -> proc_macro2::TokenStream {185		let name = &self.name;186187		let consts = self.events.iter().map(Event::expand_consts);188		let serializers = self.events.iter().map(Event::expand_serializers);189		let solidity_name = self.name.to_string();190		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);191192		quote! {193			impl #name {194				#(195					#consts196				)*197198				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {199					use evm_coder::solidity::*;200					use core::fmt::Write;201					let interface = SolidityInterface {202						selector: 0,203						name: #solidity_name,204						is: &[],205						functions: (#(206							#solidity_functions,207						)*),208					};209					let mut out = string::new();210					out.push_str("// Inline\n");211					let _ = interface.format(is_impl, &mut out, tc);212					tc.collect(out);213				}214			}215216			#[automatically_derived]217			impl ::evm_coder::events::ToLog for #name {218				fn to_log(&self, contract: address) -> ::ethereum::Log {219					use ::evm_coder::events::ToTopic;220					use ::evm_coder::abi::AbiWrite;221					let mut writer = ::evm_coder::abi::AbiWriter::new();222					let mut topics = Vec::new();223					match self {224						#(225							#serializers,226						)*227					}228					::ethereum::Log {229						address: contract,230						topics,231						data: writer.finish(),232					}233				}234			}235		}236	}237}
after · crates/evm-coder-macros/src/to_log.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use inflector::cases;18use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};19use std::fmt::Write;20use quote::quote;2122use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};2324struct EventField {25	name: Ident,26	camel_name: String,27	ty: Ident,28	indexed: bool,29}3031impl EventField {32	fn try_from(field: &Field) -> syn::Result<Self> {33		let name = field.ident.as_ref().unwrap();34		let ty = parse_ident_from_type(&field.ty, false)?;35		let mut indexed = false;36		for attr in &field.attrs {37			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {38				if ident == "indexed" {39					indexed = true;40				}41			}42		}43		Ok(Self {44			name: name.to_owned(),45			camel_name: cases::camelcase::to_camel_case(&name.to_string()),46			ty: ty.to_owned(),47			indexed,48		})49	}50	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {51		let camel_name = &self.camel_name;52		let ty = &self.ty;53		let indexed = self.indexed;54		quote! {55			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)56		}57	}58}5960struct Event {61	name: Ident,62	name_screaming: Ident,63	fields: Vec<EventField>,64	selector: [u8; 32],65	selector_str: String,66}6768impl Event {69	fn try_from(variant: &Variant) -> syn::Result<Self> {70		let name = &variant.ident;71		let name_screaming = snake_ident_to_screaming(name);7273		let named = match &variant.fields {74			Fields::Named(named) => named,75			_ => {76				return Err(syn::Error::new(77					variant.fields.span(),78					"expected named fields",79				))80			}81		};82		let mut fields = Vec::new();83		for field in &named.named {84			fields.push(EventField::try_from(field)?);85		}86		if fields.iter().filter(|f| f.indexed).count() > 3 {87			return Err(syn::Error::new(88				variant.fields.span(),89				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"90			));91		}92		let mut selector_str = format!("{}(", name);93		for (i, arg) in fields.iter().enumerate() {94			if i != 0 {95				write!(selector_str, ",").unwrap();96			}97			write!(selector_str, "{}", arg.ty).unwrap();98		}99		selector_str.push(')');100		let selector = crate::event_selector_str(&selector_str);101102		Ok(Self {103			name: name.to_owned(),104			name_screaming,105			fields,106			selector,107			selector_str,108		})109	}110111	fn expand_serializers(&self) -> proc_macro2::TokenStream {112		let name = &self.name;113		let name_screaming = &self.name_screaming;114		let fields = self.fields.iter().map(|f| &f.name);115116		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);117		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);118119		quote! {120			Self::#name {#(121				#fields,122			)*} => {123				topics.push(topic::from(Self::#name_screaming));124				#(125					topics.push(#indexed.to_topic());126				)*127				#(128					#plain.abi_write(&mut writer);129				)*130			}131		}132	}133134	fn expand_consts(&self) -> proc_macro2::TokenStream {135		let name_screaming = &self.name_screaming;136		let selector_str = &self.selector_str;137		let selector = &self.selector;138139		quote! {140			#[doc = #selector_str]141			const #name_screaming: [u8; 32] = [#(142				#selector,143			)*];144		}145	}146147	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {148		let name = self.name.to_string();149		let args = self.fields.iter().map(EventField::expand_solidity_argument);150		quote! {151			SolidityEvent {152				name: #name,153				args: (154					#(155						#args,156					)*157				),158			}159		}160	}161}162163pub struct Events {164	name: Ident,165	events: Vec<Event>,166}167168impl Events {169	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {170		let name = &data.ident;171		let en = match &data.data {172			Data::Enum(en) => en,173			_ => return Err(syn::Error::new(data.span(), "expected enum")),174		};175		let mut events = Vec::new();176		for variant in &en.variants {177			events.push(Event::try_from(variant)?);178		}179		Ok(Self {180			name: name.to_owned(),181			events,182		})183	}184	pub fn expand(&self) -> proc_macro2::TokenStream {185		let name = &self.name;186187		let consts = self.events.iter().map(Event::expand_consts);188		let serializers = self.events.iter().map(Event::expand_serializers);189		let solidity_name = self.name.to_string();190		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);191192		quote! {193			impl #name {194				#(195					#consts196				)*197198				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {199					use evm_coder::solidity::*;200					use core::fmt::Write;201					let interface = SolidityInterface {202						selector: [0; 4],203						name: #solidity_name,204						is: &[],205						functions: (#(206							#solidity_functions,207						)*),208					};209					let mut out = string::new();210					out.push_str("// Inline\n");211					let _ = interface.format(is_impl, &mut out, tc);212					tc.collect(out);213				}214			}215216			#[automatically_derived]217			impl ::evm_coder::events::ToLog for #name {218				fn to_log(&self, contract: address) -> ::ethereum::Log {219					use ::evm_coder::events::ToTopic;220					use ::evm_coder::abi::AbiWrite;221					let mut writer = ::evm_coder::abi::AbiWriter::new();222					let mut topics = Vec::new();223					match self {224						#(225							#serializers,226						)*227					}228					::ethereum::Log {229						address: contract,230						topics,231						data: writer.finish(),232					}233				}234			}235		}236	}237}
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -26,7 +26,7 @@
 
 use crate::{
 	execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},
-	types::string,
+	types::{string, self},
 };
 use crate::execution::Result;
 
@@ -46,7 +46,7 @@
 			offset: 0,
 		}
 	}
-	pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {
+	pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {
 		if buf.len() < 4 {
 			return Err(Error::Error(ExitError::OutOfOffset));
 		}
@@ -54,7 +54,7 @@
 		method_id.copy_from_slice(&buf[0..4]);
 
 		Ok((
-			u32::from_be_bytes(method_id),
+			method_id,
 			Self {
 				buf,
 				subresult_offset: 4,
@@ -63,25 +63,50 @@
 		))
 	}
 
-	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {
-		if self.buf.len() - self.offset < ABI_ALIGNMENT {
+	fn read_pad<const S: usize>(buf: &[u8], offset: usize, pad_start: usize, pad_size: usize, block_start: usize, block_size: usize) -> Result<[u8; S]> {
+		if buf.len() - offset < ABI_ALIGNMENT {
 			return Err(Error::Error(ExitError::OutOfOffset));
 		}
 		let mut block = [0; S];
 		// Verify padding is empty
-		if !self.buf[self.offset..self.offset + ABI_ALIGNMENT - S]
+		if !buf[pad_start..pad_size]
 			.iter()
 			.all(|&v| v == 0)
 		{
 			return Err(Error::Error(ExitError::InvalidRange));
 		}
 		block.copy_from_slice(
-			&self.buf[self.offset + ABI_ALIGNMENT - S..self.offset + ABI_ALIGNMENT],
+			&buf[block_start..block_size],
 		);
+		Ok(block)
+	}
+
+	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {
+		let offset = self.offset;
 		self.offset += ABI_ALIGNMENT;
-		Ok(block)
+		Self::read_pad(
+			self.buf,
+			offset,
+			offset, 
+			offset + ABI_ALIGNMENT - S,
+			offset + ABI_ALIGNMENT - S,
+			offset + ABI_ALIGNMENT
+		)
 	}
 
+	fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {
+		let offset = self.offset;
+		self.offset += ABI_ALIGNMENT;
+		Self::read_pad(
+			self.buf,
+			offset,
+			offset + S, 
+			offset + ABI_ALIGNMENT,
+			offset,
+			offset + S
+		)
+	}
+	
 	pub fn address(&mut self) -> Result<H160> {
 		Ok(H160(self.read_padleft()?))
 	}
@@ -96,7 +121,7 @@
 	}
 
 	pub fn bytes4(&mut self) -> Result<[u8; 4]> {
-		self.read_padleft()
+		self.read_padright()
 	}
 
 	pub fn bytes(&mut self) -> Result<Vec<u8>> {
@@ -268,6 +293,7 @@
 impl_abi_readable!(u64, uint64);
 impl_abi_readable!(u128, uint128);
 impl_abi_readable!(U256, uint256);
+impl_abi_readable!([u8; 4], bytes4);
 impl_abi_readable!(H160, address);
 impl_abi_readable!(Vec<u8>, bytes);
 impl_abi_readable!(bool, bool);
@@ -466,7 +492,7 @@
 			"
 		))
 		.unwrap();
-		assert_eq!(call, 0x50bb4e7f);
+		assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));
 		assert_eq!(
 			format!("{:?}", decoder.address().unwrap()),
 			"0xad2c0954693c2b5404b7e50967d3481bea432374"
@@ -505,7 +531,7 @@
 			"
 		))
 		.unwrap();
-		assert_eq!(call, 0x36543006);
+		assert_eq!(call, u32::to_be_bytes(0x36543006));
 		let _ = decoder.address().unwrap();
 		let data =
 			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -44,7 +44,7 @@
 	pub type uint128 = u128;
 	pub type uint256 = U256;
 
-	pub type bytes4 = u32;
+	pub type bytes4 = [u8; 4];
 
 	pub type topic = H256;
 
@@ -71,7 +71,7 @@
 }
 
 pub trait Call: Sized {
-	fn parse(selector: u32, input: &mut AbiReader) -> execution::Result<Option<Self>>;
+	fn parse(selector: types::bytes4, input: &mut AbiReader) -> execution::Result<Option<Self>>;
 }
 
 pub type Weight = u64;
@@ -93,11 +93,11 @@
 }
 
 impl ERC165Call {
-	pub const INTERFACE_ID: types::bytes4 = 0x01ffc9a7;
+	pub const INTERFACE_ID: types::bytes4 = u32::to_be_bytes(0x01ffc9a7);
 }
 
 impl Call for ERC165Call {
-	fn parse(selector: u32, input: &mut AbiReader) -> execution::Result<Option<Self>> {
+	fn parse(selector: types::bytes4, input: &mut AbiReader) -> execution::Result<Option<Self>> {
 		if selector != Self::INTERFACE_ID {
 			return Ok(None);
 		}
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -103,6 +103,7 @@
 	uint64 => "uint64" true = "0",
 	uint128 => "uint128" true = "0",
 	uint256 => "uint256" true = "0",
+	bytes4 => "bytes4" true = "bytes4(0)",
 	address => "address" true = "0x0000000000000000000000000000000000000000",
 	string => "string" false = "\"\"",
 	bytes => "bytes" false = "hex\"\"",
@@ -473,7 +474,7 @@
 }
 
 pub struct SolidityInterface<F: SolidityFunctions> {
-	pub selector: u32,
+	pub selector: bytes4,
 	pub name: &'static str,
 	pub is: &'static [&'static str],
 	pub functions: F,
@@ -486,8 +487,9 @@
 		out: &mut impl fmt::Write,
 		tc: &TypeCollector,
 	) -> fmt::Result {
-		if self.selector != 0 {
-			writeln!(out, "// Selector: {:0>8x}", self.selector)?;
+		const ZERO_BYTES: [u8; 4] = [0; 4];
+		if self.selector != ZERO_BYTES {
+			writeln!(out, "// Selector: {:0>8x}", u32::from_be_bytes(self.selector))?;
 		}
 		if is_impl {
 			write!(out, "contract ")?;
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -14,11 +14,13 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, deployFlipper, ethBalanceViaSub, GAS_ARGS, itWeb3, recordEthFee} from './util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, deployFlipper, ethBalanceViaSub, GAS_ARGS, itWeb3, recordEthFee, usingWeb3} from './util/helpers';
 import {expect} from 'chai';
 import {createCollectionExpectSuccess, createItemExpectSuccess, UNIQUE} from '../util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import privateKey from '../substrate/privateKey';
+import {Contract} from 'web3-eth-contract';
+import Web3 from 'web3';
 
 describe('Contract calls', () => {
   itWeb3('Call of simple contract fee is less than 0.2 UNQ', async ({web3, api}) => {
@@ -59,3 +61,53 @@
     expect(Math.abs(fee - expectedFee)).to.be.lessThan(tolerance);
   });
 });
+
+describe('ERC165 tests', async () => {
+  // https://eips.ethereum.org/EIPS/eip-165
+
+  let collection: number;
+  let minter: string;
+
+  function contract(web3: Web3): Contract {
+    return new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collection), {from: minter, ...GAS_ARGS});
+  }
+
+  before(async () => {
+    await usingWeb3 (async (web3) => {
+      collection = await createCollectionExpectSuccess();
+      minter = createEthAccount(web3);
+    });
+  });
+  
+  itWeb3('interfaceID == 0xffffffff always false', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0xffffffff').call()).to.be.false;
+  });
+
+  itWeb3('ERC721 support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x58800161').call()).to.be.true;
+  });
+
+  itWeb3('ERC721Metadata support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x5b5e139f').call()).to.be.true;
+  });
+
+  itWeb3('ERC721Mintable support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x68ccfe89').call()).to.be.true;
+  });
+
+  itWeb3('ERC721Enumerable support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x780e9d63').call()).to.be.true;
+  });
+
+  itWeb3('ERC721UniqueExtensions support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0xe562194d').call()).to.be.true;
+  });
+
+  itWeb3('ERC721Burnable support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x42966c68').call()).to.be.true;
+  });
+
+  itWeb3('ERC165 support', async ({web3}) => {
+    expect(await contract(web3).methods.supportsInterface('0x01ffc9a7').call()).to.be.true;
+  });
+});
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -466,9 +466,9 @@
     {
         "inputs": [
             {
-                "internalType": "uint32",
+                "internalType": "bytes4",
                 "name": "interfaceId",
-                "type": "uint32"
+                "type": "bytes4"
             }
         ],
         "name": "supportsInterface",