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
--- 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
before · crates/evm-coder/src/solidity.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#[cfg(not(feature = "std"))]18use alloc::{19	string::String,20	vec::Vec,21	collections::{BTreeSet, BTreeMap},22	format,23};24#[cfg(feature = "std")]25use std::collections::{BTreeSet, BTreeMap};26use core::{27	fmt::{self, Write},28	marker::PhantomData,29	cell::{Cell, RefCell},30};31use impl_trait_for_tuples::impl_for_tuples;32use crate::types::*;3334#[derive(Default)]35pub struct TypeCollector {36	structs: RefCell<BTreeSet<string>>,37	anonymous: RefCell<BTreeMap<Vec<string>, usize>>,38	id: Cell<usize>,39}40impl TypeCollector {41	pub fn new() -> Self {42		Self::default()43	}44	pub fn collect(&self, item: string) {45		self.structs.borrow_mut().insert(item);46	}47	pub fn next_id(&self) -> usize {48		let v = self.id.get();49		self.id.set(v + 1);50		v51	}52	pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {53		let names = T::names(self);54		if let Some(id) = self.anonymous.borrow().get(&names).cloned() {55			return format!("Tuple{}", id);56		}57		let id = self.next_id();58		let mut str = String::new();59		writeln!(str, "// Anonymous struct").unwrap();60		writeln!(str, "struct Tuple{} {{", id).unwrap();61		for (i, name) in names.iter().enumerate() {62			writeln!(str, "\t{} field_{};", name, i).unwrap();63		}64		writeln!(str, "}}").unwrap();65		self.collect(str);66		self.anonymous.borrow_mut().insert(names, id);67		format!("Tuple{}", id)68	}69	pub fn finish(self) -> BTreeSet<string> {70		self.structs.into_inner()71	}72}7374pub trait SolidityTypeName: 'static {75	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;76	fn is_simple() -> bool;77	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;78	fn is_void() -> bool {79		false80	}81}82macro_rules! solidity_type_name {83    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {84        $(85            impl SolidityTypeName for $ty {86                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {87                    write!(writer, $name)88                }89				fn is_simple() -> bool {90					$simple91				}92				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {93					write!(writer, $default)94				}95            }96        )*97    };98}99100solidity_type_name! {101	uint8 => "uint8" true = "0",102	uint32 => "uint32" true = "0",103	uint64 => "uint64" true = "0",104	uint128 => "uint128" true = "0",105	uint256 => "uint256" true = "0",106	address => "address" true = "0x0000000000000000000000000000000000000000",107	string => "string" false = "\"\"",108	bytes => "bytes" false = "hex\"\"",109	bool => "bool" true = "false",110}111impl SolidityTypeName for void {112	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {113		Ok(())114	}115	fn is_simple() -> bool {116		true117	}118	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {119		Ok(())120	}121	fn is_void() -> bool {122		true123	}124}125126mod sealed {127	pub trait CanBePlacedInVec {}128}129130impl sealed::CanBePlacedInVec for uint256 {}131impl sealed::CanBePlacedInVec for string {}132impl sealed::CanBePlacedInVec for address {}133134impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {135	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {136		T::solidity_name(writer, tc)?;137		write!(writer, "[]")138	}139	fn is_simple() -> bool {140		false141	}142	fn solidity_default(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {143		write!(writer, "[]")144	}145}146147pub trait SolidityTupleType {148	fn names(tc: &TypeCollector) -> Vec<String>;149	fn len() -> usize;150}151152macro_rules! count {153    () => (0usize);154    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));155}156157macro_rules! impl_tuples {158	($($ident:ident)+) => {159		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}160		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {161			fn names(tc: &TypeCollector) -> Vec<string> {162				let mut collected = Vec::with_capacity(Self::len());163				$({164					let mut out = string::new();165					$ident::solidity_name(&mut out, tc).expect("no fmt error");166					collected.push(out);167				})*;168				collected169			}170171			fn len() -> usize {172				count!($($ident)*)173			}174		}175		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {176			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {177				write!(writer, "{}", tc.collect_tuple::<Self>())178			}179			fn is_simple() -> bool {180				false181			}182			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {183				write!(writer, "{}(", tc.collect_tuple::<Self>())?;184				$(185					<$ident>::solidity_default(writer, tc)?;186				)*187				write!(writer, ")")188			}189		}190	};191}192193impl_tuples! {A}194impl_tuples! {A B}195impl_tuples! {A B C}196impl_tuples! {A B C D}197impl_tuples! {A B C D E}198impl_tuples! {A B C D E F}199impl_tuples! {A B C D E F G}200impl_tuples! {A B C D E F G H}201impl_tuples! {A B C D E F G H I}202impl_tuples! {A B C D E F G H I J}203204pub trait SolidityArguments {205	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;206	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;207	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;208	fn is_empty(&self) -> bool {209		self.len() == 0210	}211	fn len(&self) -> usize;212}213214#[derive(Default)]215pub struct UnnamedArgument<T>(PhantomData<*const T>);216217impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {218	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {219		if !T::is_void() {220			T::solidity_name(writer, tc)?;221			if !T::is_simple() {222				write!(writer, " memory")?;223			}224			Ok(())225		} else {226			Ok(())227		}228	}229	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {230		Ok(())231	}232	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {233		T::solidity_default(writer, tc)234	}235	fn len(&self) -> usize {236		if T::is_void() {237			0238		} else {239			1240		}241	}242}243244pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);245246impl<T> NamedArgument<T> {247	pub fn new(name: &'static str) -> Self {248		Self(name, Default::default())249	}250}251252impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {253	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {254		if !T::is_void() {255			T::solidity_name(writer, tc)?;256			if !T::is_simple() {257				write!(writer, " memory")?;258			}259			write!(writer, " {}", self.0)260		} else {261			Ok(())262		}263	}264	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {265		writeln!(writer, "\t\t{};", self.0)266	}267	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {268		T::solidity_default(writer, tc)269	}270	fn len(&self) -> usize {271		if T::is_void() {272			0273		} else {274			1275		}276	}277}278279pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);280281impl<T> SolidityEventArgument<T> {282	pub fn new(indexed: bool, name: &'static str) -> Self {283		Self(indexed, name, Default::default())284	}285}286287impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {288	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {289		if !T::is_void() {290			T::solidity_name(writer, tc)?;291			if self.0 {292				write!(writer, " indexed")?;293			}294			write!(writer, " {}", self.1)295		} else {296			Ok(())297		}298	}299	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {300		writeln!(writer, "\t\t{};", self.1)301	}302	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {303		T::solidity_default(writer, tc)304	}305	fn len(&self) -> usize {306		if T::is_void() {307			0308		} else {309			1310		}311	}312}313314impl SolidityArguments for () {315	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {316		Ok(())317	}318	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {319		Ok(())320	}321	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {322		Ok(())323	}324	fn len(&self) -> usize {325		0326	}327}328329#[impl_for_tuples(1, 5)]330impl SolidityArguments for Tuple {331	for_tuples!( where #( Tuple: SolidityArguments ),* );332333	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {334		let mut first = true;335		for_tuples!( #(336            if !Tuple.is_empty() {337                if !first {338                    write!(writer, ", ")?;339                }340                first = false;341                Tuple.solidity_name(writer, tc)?;342            }343        )* );344		Ok(())345	}346	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {347		for_tuples!( #(348            Tuple.solidity_get(writer)?;349        )* );350		Ok(())351	}352	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {353		if self.is_empty() {354			Ok(())355		} else if self.len() == 1 {356			for_tuples!( #(357				Tuple.solidity_default(writer, tc)?;358			)* );359			Ok(())360		} else {361			write!(writer, "(")?;362			let mut first = true;363			for_tuples!( #(364				if !Tuple.is_empty() {365					if !first {366						write!(writer, ", ")?;367					}368					first = false;369					Tuple.solidity_default(writer, tc)?;370				}371			)* );372			write!(writer, ")")?;373			Ok(())374		}375	}376	fn len(&self) -> usize {377		for_tuples!( #( Tuple.len() )+* )378	}379}380381pub trait SolidityFunctions {382	fn solidity_name(383		&self,384		is_impl: bool,385		writer: &mut impl fmt::Write,386		tc: &TypeCollector,387	) -> fmt::Result;388}389390pub enum SolidityMutability {391	Pure,392	View,393	Mutable,394}395pub struct SolidityFunction<A, R> {396	pub docs: &'static [&'static str],397	pub selector: &'static str,398	pub name: &'static str,399	pub args: A,400	pub result: R,401	pub mutability: SolidityMutability,402}403impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {404	fn solidity_name(405		&self,406		is_impl: bool,407		writer: &mut impl fmt::Write,408		tc: &TypeCollector,409	) -> fmt::Result {410		for doc in self.docs {411			writeln!(writer, "\t//{}", doc)?;412		}413		if !self.docs.is_empty() {414			writeln!(writer, "\t//")?;415		}416		writeln!(writer, "\t// Selector: {}", self.selector)?;417		write!(writer, "\tfunction {}(", self.name)?;418		self.args.solidity_name(writer, tc)?;419		write!(writer, ")")?;420		if is_impl {421			write!(writer, " public")?;422		} else {423			write!(writer, " external")?;424		}425		match &self.mutability {426			SolidityMutability::Pure => write!(writer, " pure")?,427			SolidityMutability::View => write!(writer, " view")?,428			SolidityMutability::Mutable => {}429		}430		if !self.result.is_empty() {431			write!(writer, " returns (")?;432			self.result.solidity_name(writer, tc)?;433			write!(writer, ")")?;434		}435		if is_impl {436			writeln!(writer, " {{")?;437			writeln!(writer, "\t\trequire(false, stub_error);")?;438			self.args.solidity_get(writer)?;439			match &self.mutability {440				SolidityMutability::Pure => {}441				SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,442				SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,443			}444			if !self.result.is_empty() {445				write!(writer, "\t\treturn ")?;446				self.result.solidity_default(writer, tc)?;447				writeln!(writer, ";")?;448			}449			writeln!(writer, "\t}}")?;450		} else {451			writeln!(writer, ";")?;452		}453		Ok(())454	}455}456457#[impl_for_tuples(0, 12)]458impl SolidityFunctions for Tuple {459	for_tuples!( where #( Tuple: SolidityFunctions ),* );460461	fn solidity_name(462		&self,463		is_impl: bool,464		writer: &mut impl fmt::Write,465		tc: &TypeCollector,466	) -> fmt::Result {467		let mut first = false;468		for_tuples!( #(469            Tuple.solidity_name(is_impl, writer, tc)?;470        )* );471		Ok(())472	}473}474475pub struct SolidityInterface<F: SolidityFunctions> {476	pub selector: u32,477	pub name: &'static str,478	pub is: &'static [&'static str],479	pub functions: F,480}481482impl<F: SolidityFunctions> SolidityInterface<F> {483	pub fn format(484		&self,485		is_impl: bool,486		out: &mut impl fmt::Write,487		tc: &TypeCollector,488	) -> fmt::Result {489		if self.selector != 0 {490			writeln!(out, "// Selector: {:0>8x}", self.selector)?;491		}492		if is_impl {493			write!(out, "contract ")?;494		} else {495			write!(out, "interface ")?;496		}497		write!(out, "{}", self.name)?;498		if !self.is.is_empty() {499			write!(out, " is")?;500			for (i, n) in self.is.iter().enumerate() {501				if i != 0 {502					write!(out, ",")?;503				}504				write!(out, " {}", n)?;505			}506		}507		writeln!(out, " {{")?;508		self.functions.solidity_name(is_impl, out, tc)?;509		writeln!(out, "}}")?;510		Ok(())511	}512}513514pub struct SolidityEvent<A> {515	pub name: &'static str,516	pub args: A,517}518519impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {520	fn solidity_name(521		&self,522		_is_impl: bool,523		writer: &mut impl fmt::Write,524		tc: &TypeCollector,525	) -> fmt::Result {526		write!(writer, "\tevent {}(", self.name)?;527		self.args.solidity_name(writer, tc)?;528		writeln!(writer, ");")529	}530}
after · crates/evm-coder/src/solidity.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#[cfg(not(feature = "std"))]18use alloc::{19	string::String,20	vec::Vec,21	collections::{BTreeSet, BTreeMap},22	format,23};24#[cfg(feature = "std")]25use std::collections::{BTreeSet, BTreeMap};26use core::{27	fmt::{self, Write},28	marker::PhantomData,29	cell::{Cell, RefCell},30};31use impl_trait_for_tuples::impl_for_tuples;32use crate::types::*;3334#[derive(Default)]35pub struct TypeCollector {36	structs: RefCell<BTreeSet<string>>,37	anonymous: RefCell<BTreeMap<Vec<string>, usize>>,38	id: Cell<usize>,39}40impl TypeCollector {41	pub fn new() -> Self {42		Self::default()43	}44	pub fn collect(&self, item: string) {45		self.structs.borrow_mut().insert(item);46	}47	pub fn next_id(&self) -> usize {48		let v = self.id.get();49		self.id.set(v + 1);50		v51	}52	pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {53		let names = T::names(self);54		if let Some(id) = self.anonymous.borrow().get(&names).cloned() {55			return format!("Tuple{}", id);56		}57		let id = self.next_id();58		let mut str = String::new();59		writeln!(str, "// Anonymous struct").unwrap();60		writeln!(str, "struct Tuple{} {{", id).unwrap();61		for (i, name) in names.iter().enumerate() {62			writeln!(str, "\t{} field_{};", name, i).unwrap();63		}64		writeln!(str, "}}").unwrap();65		self.collect(str);66		self.anonymous.borrow_mut().insert(names, id);67		format!("Tuple{}", id)68	}69	pub fn finish(self) -> BTreeSet<string> {70		self.structs.into_inner()71	}72}7374pub trait SolidityTypeName: 'static {75	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;76	fn is_simple() -> bool;77	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;78	fn is_void() -> bool {79		false80	}81}82macro_rules! solidity_type_name {83    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {84        $(85            impl SolidityTypeName for $ty {86                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {87                    write!(writer, $name)88                }89				fn is_simple() -> bool {90					$simple91				}92				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {93					write!(writer, $default)94				}95            }96        )*97    };98}99100solidity_type_name! {101	uint8 => "uint8" true = "0",102	uint32 => "uint32" true = "0",103	uint64 => "uint64" true = "0",104	uint128 => "uint128" true = "0",105	uint256 => "uint256" true = "0",106	bytes4 => "bytes4" true = "bytes4(0)",107	address => "address" true = "0x0000000000000000000000000000000000000000",108	string => "string" false = "\"\"",109	bytes => "bytes" false = "hex\"\"",110	bool => "bool" true = "false",111}112impl SolidityTypeName for void {113	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {114		Ok(())115	}116	fn is_simple() -> bool {117		true118	}119	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {120		Ok(())121	}122	fn is_void() -> bool {123		true124	}125}126127mod sealed {128	pub trait CanBePlacedInVec {}129}130131impl sealed::CanBePlacedInVec for uint256 {}132impl sealed::CanBePlacedInVec for string {}133impl sealed::CanBePlacedInVec for address {}134135impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {136	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {137		T::solidity_name(writer, tc)?;138		write!(writer, "[]")139	}140	fn is_simple() -> bool {141		false142	}143	fn solidity_default(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {144		write!(writer, "[]")145	}146}147148pub trait SolidityTupleType {149	fn names(tc: &TypeCollector) -> Vec<String>;150	fn len() -> usize;151}152153macro_rules! count {154    () => (0usize);155    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));156}157158macro_rules! impl_tuples {159	($($ident:ident)+) => {160		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}161		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {162			fn names(tc: &TypeCollector) -> Vec<string> {163				let mut collected = Vec::with_capacity(Self::len());164				$({165					let mut out = string::new();166					$ident::solidity_name(&mut out, tc).expect("no fmt error");167					collected.push(out);168				})*;169				collected170			}171172			fn len() -> usize {173				count!($($ident)*)174			}175		}176		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {177			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {178				write!(writer, "{}", tc.collect_tuple::<Self>())179			}180			fn is_simple() -> bool {181				false182			}183			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {184				write!(writer, "{}(", tc.collect_tuple::<Self>())?;185				$(186					<$ident>::solidity_default(writer, tc)?;187				)*188				write!(writer, ")")189			}190		}191	};192}193194impl_tuples! {A}195impl_tuples! {A B}196impl_tuples! {A B C}197impl_tuples! {A B C D}198impl_tuples! {A B C D E}199impl_tuples! {A B C D E F}200impl_tuples! {A B C D E F G}201impl_tuples! {A B C D E F G H}202impl_tuples! {A B C D E F G H I}203impl_tuples! {A B C D E F G H I J}204205pub trait SolidityArguments {206	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;207	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;208	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;209	fn is_empty(&self) -> bool {210		self.len() == 0211	}212	fn len(&self) -> usize;213}214215#[derive(Default)]216pub struct UnnamedArgument<T>(PhantomData<*const T>);217218impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {219	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {220		if !T::is_void() {221			T::solidity_name(writer, tc)?;222			if !T::is_simple() {223				write!(writer, " memory")?;224			}225			Ok(())226		} else {227			Ok(())228		}229	}230	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {231		Ok(())232	}233	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {234		T::solidity_default(writer, tc)235	}236	fn len(&self) -> usize {237		if T::is_void() {238			0239		} else {240			1241		}242	}243}244245pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);246247impl<T> NamedArgument<T> {248	pub fn new(name: &'static str) -> Self {249		Self(name, Default::default())250	}251}252253impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {254	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {255		if !T::is_void() {256			T::solidity_name(writer, tc)?;257			if !T::is_simple() {258				write!(writer, " memory")?;259			}260			write!(writer, " {}", self.0)261		} else {262			Ok(())263		}264	}265	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {266		writeln!(writer, "\t\t{};", self.0)267	}268	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {269		T::solidity_default(writer, tc)270	}271	fn len(&self) -> usize {272		if T::is_void() {273			0274		} else {275			1276		}277	}278}279280pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);281282impl<T> SolidityEventArgument<T> {283	pub fn new(indexed: bool, name: &'static str) -> Self {284		Self(indexed, name, Default::default())285	}286}287288impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {289	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {290		if !T::is_void() {291			T::solidity_name(writer, tc)?;292			if self.0 {293				write!(writer, " indexed")?;294			}295			write!(writer, " {}", self.1)296		} else {297			Ok(())298		}299	}300	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {301		writeln!(writer, "\t\t{};", self.1)302	}303	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {304		T::solidity_default(writer, tc)305	}306	fn len(&self) -> usize {307		if T::is_void() {308			0309		} else {310			1311		}312	}313}314315impl SolidityArguments for () {316	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {317		Ok(())318	}319	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {320		Ok(())321	}322	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {323		Ok(())324	}325	fn len(&self) -> usize {326		0327	}328}329330#[impl_for_tuples(1, 5)]331impl SolidityArguments for Tuple {332	for_tuples!( where #( Tuple: SolidityArguments ),* );333334	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {335		let mut first = true;336		for_tuples!( #(337            if !Tuple.is_empty() {338                if !first {339                    write!(writer, ", ")?;340                }341                first = false;342                Tuple.solidity_name(writer, tc)?;343            }344        )* );345		Ok(())346	}347	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {348		for_tuples!( #(349            Tuple.solidity_get(writer)?;350        )* );351		Ok(())352	}353	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {354		if self.is_empty() {355			Ok(())356		} else if self.len() == 1 {357			for_tuples!( #(358				Tuple.solidity_default(writer, tc)?;359			)* );360			Ok(())361		} else {362			write!(writer, "(")?;363			let mut first = true;364			for_tuples!( #(365				if !Tuple.is_empty() {366					if !first {367						write!(writer, ", ")?;368					}369					first = false;370					Tuple.solidity_default(writer, tc)?;371				}372			)* );373			write!(writer, ")")?;374			Ok(())375		}376	}377	fn len(&self) -> usize {378		for_tuples!( #( Tuple.len() )+* )379	}380}381382pub trait SolidityFunctions {383	fn solidity_name(384		&self,385		is_impl: bool,386		writer: &mut impl fmt::Write,387		tc: &TypeCollector,388	) -> fmt::Result;389}390391pub enum SolidityMutability {392	Pure,393	View,394	Mutable,395}396pub struct SolidityFunction<A, R> {397	pub docs: &'static [&'static str],398	pub selector: &'static str,399	pub name: &'static str,400	pub args: A,401	pub result: R,402	pub mutability: SolidityMutability,403}404impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {405	fn solidity_name(406		&self,407		is_impl: bool,408		writer: &mut impl fmt::Write,409		tc: &TypeCollector,410	) -> fmt::Result {411		for doc in self.docs {412			writeln!(writer, "\t//{}", doc)?;413		}414		if !self.docs.is_empty() {415			writeln!(writer, "\t//")?;416		}417		writeln!(writer, "\t// Selector: {}", self.selector)?;418		write!(writer, "\tfunction {}(", self.name)?;419		self.args.solidity_name(writer, tc)?;420		write!(writer, ")")?;421		if is_impl {422			write!(writer, " public")?;423		} else {424			write!(writer, " external")?;425		}426		match &self.mutability {427			SolidityMutability::Pure => write!(writer, " pure")?,428			SolidityMutability::View => write!(writer, " view")?,429			SolidityMutability::Mutable => {}430		}431		if !self.result.is_empty() {432			write!(writer, " returns (")?;433			self.result.solidity_name(writer, tc)?;434			write!(writer, ")")?;435		}436		if is_impl {437			writeln!(writer, " {{")?;438			writeln!(writer, "\t\trequire(false, stub_error);")?;439			self.args.solidity_get(writer)?;440			match &self.mutability {441				SolidityMutability::Pure => {}442				SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,443				SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,444			}445			if !self.result.is_empty() {446				write!(writer, "\t\treturn ")?;447				self.result.solidity_default(writer, tc)?;448				writeln!(writer, ";")?;449			}450			writeln!(writer, "\t}}")?;451		} else {452			writeln!(writer, ";")?;453		}454		Ok(())455	}456}457458#[impl_for_tuples(0, 12)]459impl SolidityFunctions for Tuple {460	for_tuples!( where #( Tuple: SolidityFunctions ),* );461462	fn solidity_name(463		&self,464		is_impl: bool,465		writer: &mut impl fmt::Write,466		tc: &TypeCollector,467	) -> fmt::Result {468		let mut first = false;469		for_tuples!( #(470            Tuple.solidity_name(is_impl, writer, tc)?;471        )* );472		Ok(())473	}474}475476pub struct SolidityInterface<F: SolidityFunctions> {477	pub selector: bytes4,478	pub name: &'static str,479	pub is: &'static [&'static str],480	pub functions: F,481}482483impl<F: SolidityFunctions> SolidityInterface<F> {484	pub fn format(485		&self,486		is_impl: bool,487		out: &mut impl fmt::Write,488		tc: &TypeCollector,489	) -> fmt::Result {490		const ZERO_BYTES: [u8; 4] = [0; 4];491		if self.selector != ZERO_BYTES {492			writeln!(out, "// Selector: {:0>8x}", u32::from_be_bytes(self.selector))?;493		}494		if is_impl {495			write!(out, "contract ")?;496		} else {497			write!(out, "interface ")?;498		}499		write!(out, "{}", self.name)?;500		if !self.is.is_empty() {501			write!(out, " is")?;502			for (i, n) in self.is.iter().enumerate() {503				if i != 0 {504					write!(out, ",")?;505				}506				write!(out, " {}", n)?;507			}508		}509		writeln!(out, " {{")?;510		self.functions.solidity_name(is_impl, out, tc)?;511		writeln!(out, "}}")?;512		Ok(())513	}514}515516pub struct SolidityEvent<A> {517	pub name: &'static str,518	pub args: A,519}520521impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {522	fn solidity_name(523		&self,524		_is_impl: bool,525		writer: &mut impl fmt::Write,526		tc: &TypeCollector,527	) -> fmt::Result {528		write!(writer, "\tevent {}(", self.name)?;529		self.args.solidity_name(writer, tc)?;530		writeln!(writer, ");")531	}532}
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",