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
--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -199,7 +199,7 @@
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
-						selector: 0,
+						selector: [0; 4],
 						name: #solidity_name,
 						is: &[],
 						functions: (#(
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
before · crates/evm-coder/src/abi.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/>.1617//! TODO: I misunterstood therminology, abi IS rlp encoded, so18//! this module should be replaced with rlp crate1920#![allow(dead_code)]2122#[cfg(not(feature = "std"))]23use alloc::vec::Vec;24use evm_core::ExitError;25use primitive_types::{H160, U256};2627use crate::{28	execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},29	types::string,30};31use crate::execution::Result;3233const ABI_ALIGNMENT: usize = 32;3435#[derive(Clone)]36pub struct AbiReader<'i> {37	buf: &'i [u8],38	subresult_offset: usize,39	offset: usize,40}41impl<'i> AbiReader<'i> {42	pub fn new(buf: &'i [u8]) -> Self {43		Self {44			buf,45			subresult_offset: 0,46			offset: 0,47		}48	}49	pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {50		if buf.len() < 4 {51			return Err(Error::Error(ExitError::OutOfOffset));52		}53		let mut method_id = [0; 4];54		method_id.copy_from_slice(&buf[0..4]);5556		Ok((57			u32::from_be_bytes(method_id),58			Self {59				buf,60				subresult_offset: 4,61				offset: 4,62			},63		))64	}6566	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {67		if self.buf.len() - self.offset < ABI_ALIGNMENT {68			return Err(Error::Error(ExitError::OutOfOffset));69		}70		let mut block = [0; S];71		// Verify padding is empty72		if !self.buf[self.offset..self.offset + ABI_ALIGNMENT - S]73			.iter()74			.all(|&v| v == 0)75		{76			return Err(Error::Error(ExitError::InvalidRange));77		}78		block.copy_from_slice(79			&self.buf[self.offset + ABI_ALIGNMENT - S..self.offset + ABI_ALIGNMENT],80		);81		self.offset += ABI_ALIGNMENT;82		Ok(block)83	}8485	pub fn address(&mut self) -> Result<H160> {86		Ok(H160(self.read_padleft()?))87	}8889	pub fn bool(&mut self) -> Result<bool> {90		let data: [u8; 1] = self.read_padleft()?;91		match data[0] {92			0 => Ok(false),93			1 => Ok(true),94			_ => Err(Error::Error(ExitError::InvalidRange)),95		}96	}9798	pub fn bytes4(&mut self) -> Result<[u8; 4]> {99		self.read_padleft()100	}101102	pub fn bytes(&mut self) -> Result<Vec<u8>> {103		let mut subresult = self.subresult()?;104		let length = subresult.read_usize()?;105		if subresult.buf.len() <= subresult.offset + length {106			return Err(Error::Error(ExitError::OutOfOffset));107		}108		Ok(subresult.buf[subresult.offset..subresult.offset + length].into())109	}110	pub fn string(&mut self) -> Result<string> {111		string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))112	}113114	pub fn uint8(&mut self) -> Result<u8> {115		Ok(self.read_padleft::<1>()?[0])116	}117118	pub fn uint32(&mut self) -> Result<u32> {119		Ok(u32::from_be_bytes(self.read_padleft()?))120	}121122	pub fn uint128(&mut self) -> Result<u128> {123		Ok(u128::from_be_bytes(self.read_padleft()?))124	}125126	pub fn uint256(&mut self) -> Result<U256> {127		let buf: [u8; 32] = self.read_padleft()?;128		Ok(U256::from_big_endian(&buf))129	}130131	pub fn uint64(&mut self) -> Result<u64> {132		Ok(u64::from_be_bytes(self.read_padleft()?))133	}134135	pub fn read_usize(&mut self) -> Result<usize> {136		Ok(usize::from_be_bytes(self.read_padleft()?))137	}138139	fn subresult(&mut self) -> Result<AbiReader<'i>> {140		let offset = self.read_usize()?;141		if offset + self.subresult_offset > self.buf.len() {142			return Err(Error::Error(ExitError::InvalidRange));143		}144		Ok(AbiReader {145			buf: self.buf,146			subresult_offset: offset + self.subresult_offset,147			offset: offset + self.subresult_offset,148		})149	}150151	pub fn is_finished(&self) -> bool {152		self.buf.len() == self.offset153	}154}155156#[derive(Default)]157pub struct AbiWriter {158	static_part: Vec<u8>,159	dynamic_part: Vec<(usize, AbiWriter)>,160}161impl AbiWriter {162	pub fn new() -> Self {163		Self::default()164	}165	pub fn new_call(method_id: u32) -> Self {166		let mut val = Self::new();167		val.static_part.extend(&method_id.to_be_bytes());168		val169	}170171	fn write_padleft(&mut self, block: &[u8]) {172		assert!(block.len() <= ABI_ALIGNMENT);173		self.static_part174			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);175		self.static_part.extend(block);176	}177178	fn write_padright(&mut self, bytes: &[u8]) {179		assert!(bytes.len() <= ABI_ALIGNMENT);180		self.static_part.extend(bytes);181		self.static_part182			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);183	}184185	pub fn address(&mut self, address: &H160) {186		self.write_padleft(&address.0)187	}188189	pub fn bool(&mut self, value: &bool) {190		self.write_padleft(&[if *value { 1 } else { 0 }])191	}192193	pub fn uint8(&mut self, value: &u8) {194		self.write_padleft(&[*value])195	}196197	pub fn uint32(&mut self, value: &u32) {198		self.write_padleft(&u32::to_be_bytes(*value))199	}200201	pub fn uint128(&mut self, value: &u128) {202		self.write_padleft(&u128::to_be_bytes(*value))203	}204205	pub fn uint256(&mut self, value: &U256) {206		let mut out = [0; 32];207		value.to_big_endian(&mut out);208		self.write_padleft(&out)209	}210211	pub fn write_usize(&mut self, value: &usize) {212		self.write_padleft(&usize::to_be_bytes(*value))213	}214215	pub fn write_subresult(&mut self, result: Self) {216		self.dynamic_part.push((self.static_part.len(), result));217		// Empty block, to be filled later218		self.write_padleft(&[]);219	}220221	pub fn memory(&mut self, value: &[u8]) {222		let mut sub = Self::new();223		sub.write_usize(&value.len());224		for chunk in value.chunks(ABI_ALIGNMENT) {225			sub.write_padright(chunk);226		}227		self.write_subresult(sub);228	}229230	pub fn string(&mut self, value: &str) {231		self.memory(value.as_bytes())232	}233234	pub fn bytes(&mut self, value: &[u8]) {235		self.memory(value)236	}237238	pub fn finish(mut self) -> Vec<u8> {239		for (static_offset, part) in self.dynamic_part {240			let part_offset = self.static_part.len();241242			let encoded_dynamic_offset = usize::to_be_bytes(part_offset);243			self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()244				..static_offset + ABI_ALIGNMENT]245				.copy_from_slice(&encoded_dynamic_offset);246			self.static_part.extend(part.finish())247		}248		self.static_part249	}250}251252pub trait AbiRead<T> {253	fn abi_read(&mut self) -> Result<T>;254}255256macro_rules! impl_abi_readable {257	($ty:ty, $method:ident) => {258		impl AbiRead<$ty> for AbiReader<'_> {259			fn abi_read(&mut self) -> Result<$ty> {260				self.$method()261			}262		}263	};264}265266impl_abi_readable!(u8, uint8);267impl_abi_readable!(u32, uint32);268impl_abi_readable!(u64, uint64);269impl_abi_readable!(u128, uint128);270impl_abi_readable!(U256, uint256);271impl_abi_readable!(H160, address);272impl_abi_readable!(Vec<u8>, bytes);273impl_abi_readable!(bool, bool);274impl_abi_readable!(string, string);275276mod sealed {277	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead278	pub trait CanBePlacedInVec {}279}280281impl sealed::CanBePlacedInVec for U256 {}282impl sealed::CanBePlacedInVec for string {}283impl sealed::CanBePlacedInVec for H160 {}284285impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>286where287	Self: AbiRead<R>,288{289	fn abi_read(&mut self) -> Result<Vec<R>> {290		let mut sub = self.subresult()?;291		let size = sub.read_usize()?;292		sub.subresult_offset = sub.offset;293		let mut out = Vec::with_capacity(size);294		for _ in 0..size {295			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);296		}297		Ok(out)298	}299}300301macro_rules! impl_tuples {302	($($ident:ident)+) => {303		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}304		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>305		where306			$(Self: AbiRead<$ident>),+307		{308			fn abi_read(&mut self) -> Result<($($ident,)+)> {309				let mut subresult = self.subresult()?;310				Ok((311					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+312				))313			}314		}315	};316}317318impl_tuples! {A}319impl_tuples! {A B}320impl_tuples! {A B C}321impl_tuples! {A B C D}322impl_tuples! {A B C D E}323impl_tuples! {A B C D E F}324impl_tuples! {A B C D E F G}325impl_tuples! {A B C D E F G H}326impl_tuples! {A B C D E F G H I}327impl_tuples! {A B C D E F G H I J}328329pub trait AbiWrite {330	fn abi_write(&self, writer: &mut AbiWriter);331	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {332		let mut writer = AbiWriter::new();333		self.abi_write(&mut writer);334		Ok(writer.into())335	}336}337338impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {339	// this particular AbiWrite implementation should be split to another trait,340	// which only implements [`to_result`]341	//342	// But due to lack of specialization feature in stable Rust, we can't have343	// blanket impl of this trait `for T where T: AbiWrite`, so here we abusing344	// default trait methods for it345	fn abi_write(&self, _writer: &mut AbiWriter) {346		debug_assert!(false, "shouldn't be called, see comment")347	}348	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {349		match self {350			Ok(v) => Ok(WithPostDispatchInfo {351				post_info: v.post_info.clone(),352				data: {353					let mut out = AbiWriter::new();354					v.data.abi_write(&mut out);355					out356				},357			}),358			Err(e) => Err(e.clone()),359		}360	}361}362363macro_rules! impl_abi_writeable {364	($ty:ty, $method:ident) => {365		impl AbiWrite for $ty {366			fn abi_write(&self, writer: &mut AbiWriter) {367				writer.$method(&self)368			}369		}370	};371}372373impl_abi_writeable!(u8, uint8);374impl_abi_writeable!(u32, uint32);375impl_abi_writeable!(u128, uint128);376impl_abi_writeable!(U256, uint256);377impl_abi_writeable!(H160, address);378impl_abi_writeable!(bool, bool);379impl_abi_writeable!(&str, string);380impl AbiWrite for &string {381	fn abi_write(&self, writer: &mut AbiWriter) {382		writer.string(self)383	}384}385impl AbiWrite for &Vec<u8> {386	fn abi_write(&self, writer: &mut AbiWriter) {387		writer.bytes(self)388	}389}390391impl AbiWrite for () {392	fn abi_write(&self, _writer: &mut AbiWriter) {}393}394395#[macro_export]396macro_rules! abi_decode {397	($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {398		$(399			let $name = $reader.$typ()?;400		)+401	}402}403#[macro_export]404macro_rules! abi_encode {405	($($typ:ident($value:expr)),* $(,)?) => {{406		#[allow(unused_mut)]407		let mut writer = ::evm_coder::abi::AbiWriter::new();408		$(409			writer.$typ($value);410		)*411		writer412	}};413	(call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{414		#[allow(unused_mut)]415		let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);416		$(417			writer.$typ($value);418		)*419		writer420	}}421}422423#[cfg(test)]424pub mod test {425	use crate::{426		abi::AbiRead,427		types::{string, uint256},428	};429430	use super::{AbiReader, AbiWriter};431	use hex_literal::hex;432433	#[test]434	fn dynamic_after_static() {435		let mut encoder = AbiWriter::new();436		encoder.bool(&true);437		encoder.string("test");438		let encoded = encoder.finish();439440		let mut encoder = AbiWriter::new();441		encoder.bool(&true);442		// Offset to subresult443		encoder.uint32(&(32 * 2));444		// Len of "test"445		encoder.uint32(&4);446		encoder.write_padright(&[b't', b'e', b's', b't']);447		let alternative_encoded = encoder.finish();448449		assert_eq!(encoded, alternative_encoded);450451		let mut decoder = AbiReader::new(&encoded);452		assert_eq!(decoder.bool().unwrap(), true);453		assert_eq!(decoder.string().unwrap(), "test");454	}455456	#[test]457	fn mint_sample() {458		let (call, mut decoder) = AbiReader::new_call(&hex!(459			"460				50bb4e7f461				000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374462				0000000000000000000000000000000000000000000000000000000000000001463				0000000000000000000000000000000000000000000000000000000000000060464				0000000000000000000000000000000000000000000000000000000000000008465				5465737420555249000000000000000000000000000000000000000000000000466			"467		))468		.unwrap();469		assert_eq!(call, 0x50bb4e7f);470		assert_eq!(471			format!("{:?}", decoder.address().unwrap()),472			"0xad2c0954693c2b5404b7e50967d3481bea432374"473		);474		assert_eq!(decoder.uint32().unwrap(), 1);475		assert_eq!(decoder.string().unwrap(), "Test URI");476	}477478	#[test]479	fn mint_bulk() {480		let (call, mut decoder) = AbiReader::new_call(&hex!(481			"482				36543006483				00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address484				0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]485				0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]486487				0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem488				00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem489				0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem490491				0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60492				0000000000000000000000000000000000000000000000000000000000000040 // offset of string493				000000000000000000000000000000000000000000000000000000000000000a // size of string494				5465737420555249203000000000000000000000000000000000000000000000 // string495496				000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0497				0000000000000000000000000000000000000000000000000000000000000040 // offset of string498				000000000000000000000000000000000000000000000000000000000000000a // size of string499				5465737420555249203100000000000000000000000000000000000000000000 // string500501				000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160502				0000000000000000000000000000000000000000000000000000000000000040 // offset of string503				000000000000000000000000000000000000000000000000000000000000000a // size of string504				5465737420555249203200000000000000000000000000000000000000000000 // string505			"506		))507		.unwrap();508		assert_eq!(call, 0x36543006);509		let _ = decoder.address().unwrap();510		let data =511			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();512		assert_eq!(513			data,514			vec![515				(1.into(), "Test URI 0".to_string()),516				(11.into(), "Test URI 1".to_string()),517				(12.into(), "Test URI 2".to_string())518			]519		);520	}521}
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",