git.delta.rocks / unique-network / refs/commits / 78a995207d2e

difftreelog

fix After rebase

Trubnikov Sergey2022-08-29parent: #c288f09.patch.diff
in: master

8 files changed

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//! Implementation of EVM RLP reader/writer1819#![allow(dead_code)]2021#[cfg(not(feature = "std"))]22use alloc::vec::Vec;23use evm_core::ExitError;24use primitive_types::{H160, U256};2526use crate::{27	execution::{Error, ResultWithPostInfo, WithPostDispatchInfo},28	types::{string, self},29};30use crate::execution::Result;31use crate::solidity::SolidityTypeName;3233const ABI_ALIGNMENT: usize = 32;3435/// View into RLP data, which provides method to read typed items from it36#[derive(Clone)]37pub struct AbiReader<'i> {38	buf: &'i [u8],39	subresult_offset: usize,40	offset: usize,41}42impl<'i> AbiReader<'i> {43	/// Start reading RLP buffer, assuming there is no padding bytes44	pub fn new(buf: &'i [u8]) -> Self {45		Self {46			buf,47			subresult_offset: 0,48			offset: 0,49		}50	}51	/// Start reading RLP buffer, parsing first 4 bytes as selector52	pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {53		if buf.len() < 4 {54			return Err(Error::Error(ExitError::OutOfOffset));55		}56		let mut method_id = [0; 4];57		method_id.copy_from_slice(&buf[0..4]);5859		Ok((60			method_id,61			Self {62				buf,63				subresult_offset: 4,64				offset: 4,65			},66		))67	}6869	fn read_pad<const S: usize>(70		buf: &[u8],71		offset: usize,72		pad_start: usize,73		pad_size: usize,74		block_start: usize,75		block_size: usize,76	) -> Result<[u8; S]> {77		if buf.len() - offset < ABI_ALIGNMENT {78			return Err(Error::Error(ExitError::OutOfOffset));79		}80		let mut block = [0; S];81		let is_pad_zeroed = buf[pad_start..pad_size].iter().all(|&v| v == 0);82		if !is_pad_zeroed {83			return Err(Error::Error(ExitError::InvalidRange));84		}85		block.copy_from_slice(&buf[block_start..block_size]);86		Ok(block)87	}8889	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {90		let offset = self.offset;91		self.offset += ABI_ALIGNMENT;92		Self::read_pad(93			self.buf,94			offset,95			offset,96			offset + ABI_ALIGNMENT - S,97			offset + ABI_ALIGNMENT - S,98			offset + ABI_ALIGNMENT,99		)100	}101102	fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {103		let offset = self.offset;104		self.offset += ABI_ALIGNMENT;105		Self::read_pad(106			self.buf,107			offset,108			offset + S,109			offset + ABI_ALIGNMENT,110			offset,111			offset + S,112		)113	}114115	/// Read [`H160`] at current position, then advance116	pub fn address(&mut self) -> Result<H160> {117		Ok(H160(self.read_padleft()?))118	}119120	/// Read [`bool`] at current position, then advance121	pub fn bool(&mut self) -> Result<bool> {122		let data: [u8; 1] = self.read_padleft()?;123		match data[0] {124			0 => Ok(false),125			1 => Ok(true),126			_ => Err(Error::Error(ExitError::InvalidRange)),127		}128	}129130	/// Read [`[u8; 4]`] at current position, then advance131	pub fn bytes4(&mut self) -> Result<[u8; 4]> {132		self.read_padright()133	}134135	/// Read [`Vec<u8>`] at current position, then advance136	pub fn bytes(&mut self) -> Result<Vec<u8>> {137		let mut subresult = self.subresult(None)?;138		let length = subresult.uint32()? as usize;139		if subresult.buf.len() < subresult.offset + length {140			return Err(Error::Error(ExitError::OutOfOffset));141		}142		Ok(subresult.buf[subresult.offset..subresult.offset + length].into())143	}144145	/// Read [`string`] at current position, then advance146	pub fn string(&mut self) -> Result<string> {147		string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))148	}149150	/// Read [`u8`] at current position, then advance151	pub fn uint8(&mut self) -> Result<u8> {152		Ok(self.read_padleft::<1>()?[0])153	}154155	/// Read [`u32`] at current position, then advance156	pub fn uint32(&mut self) -> Result<u32> {157		Ok(u32::from_be_bytes(self.read_padleft()?))158	}159160	/// Read [`u128`] at current position, then advance161	pub fn uint128(&mut self) -> Result<u128> {162		Ok(u128::from_be_bytes(self.read_padleft()?))163	}164165	/// Read [`U256`] at current position, then advance166	pub fn uint256(&mut self) -> Result<U256> {167		let buf: [u8; 32] = self.read_padleft()?;168		Ok(U256::from_big_endian(&buf))169	}170171	/// Read [`u64`] at current position, then advance172	pub fn uint64(&mut self) -> Result<u64> {173		Ok(u64::from_be_bytes(self.read_padleft()?))174	}175176	/// Read [`usize`] at current position, then advance177	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]178	pub fn read_usize(&mut self) -> Result<usize> {179		Ok(usize::from_be_bytes(self.read_padleft()?))180	}181182	/// Slice recursive buffer, advance one word for buffer offset183	/// If `size` is [`None`] then [`Self::offset`] and [`Self::subresult_offset`] evals from [`Self::buf`].184	fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {185		let subresult_offset = self.subresult_offset;186		let offset = if let Some(size) = size {187			self.offset += size;188			self.subresult_offset += size;189			0190		} else {191			self.uint32()? as usize192		};193194		if offset + self.subresult_offset > self.buf.len() {195			return Err(Error::Error(ExitError::InvalidRange));196		}197198		let new_offset = offset + subresult_offset;199		Ok(AbiReader {200			buf: self.buf,201			subresult_offset: new_offset,202			offset: new_offset,203		})204	}205206	/// Is this parser reached end of buffer?207	pub fn is_finished(&self) -> bool {208		self.buf.len() == self.offset209	}210}211212/// Writer for RLP encoded data213#[derive(Default)]214pub struct AbiWriter {215	static_part: Vec<u8>,216	dynamic_part: Vec<(usize, AbiWriter)>,217	had_call: bool,218}219impl AbiWriter {220	/// Initialize internal buffers for output data, assuming no padding required221	pub fn new() -> Self {222		Self::default()223	}224	/// Initialize internal buffers, inserting method selector at beginning225	pub fn new_call(method_id: u32) -> Self {226		let mut val = Self::new();227		val.static_part.extend(&method_id.to_be_bytes());228		val.had_call = true;229		val230	}231232	fn write_padleft(&mut self, block: &[u8]) {233		assert!(block.len() <= ABI_ALIGNMENT);234		self.static_part235			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);236		self.static_part.extend(block);237	}238239	fn write_padright(&mut self, bytes: &[u8]) {240		assert!(bytes.len() <= ABI_ALIGNMENT);241		self.static_part.extend(bytes);242		self.static_part243			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);244	}245246	/// Write [`H160`] to end of buffer247	pub fn address(&mut self, address: &H160) {248		self.write_padleft(&address.0)249	}250251	/// Write [`bool`] to end of buffer252	pub fn bool(&mut self, value: &bool) {253		self.write_padleft(&[if *value { 1 } else { 0 }])254	}255256	/// Write [`u8`] to end of buffer257	pub fn uint8(&mut self, value: &u8) {258		self.write_padleft(&[*value])259	}260261	/// Write [`u32`] to end of buffer262	pub fn uint32(&mut self, value: &u32) {263		self.write_padleft(&u32::to_be_bytes(*value))264	}265266	/// Write [`u128`] to end of buffer267	pub fn uint128(&mut self, value: &u128) {268		self.write_padleft(&u128::to_be_bytes(*value))269	}270271	/// Write [`U256`] to end of buffer272	pub fn uint256(&mut self, value: &U256) {273		let mut out = [0; 32];274		value.to_big_endian(&mut out);275		self.write_padleft(&out)276	}277278	/// Write [`usize`] to end of buffer279	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]280	pub fn write_usize(&mut self, value: &usize) {281		self.write_padleft(&usize::to_be_bytes(*value))282	}283284	/// Append recursive data, writing pending offset at end of buffer285	pub fn write_subresult(&mut self, result: Self) {286		self.dynamic_part.push((self.static_part.len(), result));287		// Empty block, to be filled later288		self.write_padleft(&[]);289	}290291	fn memory(&mut self, value: &[u8]) {292		let mut sub = Self::new();293		sub.uint32(&(value.len() as u32));294		for chunk in value.chunks(ABI_ALIGNMENT) {295			sub.write_padright(chunk);296		}297		self.write_subresult(sub);298	}299300	/// Append recursive [`str`] at end of buffer301	pub fn string(&mut self, value: &str) {302		self.memory(value.as_bytes())303	}304305	/// Append recursive [`[u8]`] at end of buffer306	pub fn bytes(&mut self, value: &[u8]) {307		self.memory(value)308	}309310	/// Finish writer, concatenating all internal buffers311	pub fn finish(mut self) -> Vec<u8> {312		for (static_offset, part) in self.dynamic_part {313			let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0);314315			let encoded_dynamic_offset = usize::to_be_bytes(part_offset);316			self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()317				..static_offset + ABI_ALIGNMENT]318				.copy_from_slice(&encoded_dynamic_offset);319			self.static_part.extend(part.finish())320		}321		self.static_part322	}323}324325/// [`AbiReader`] implements reading of many types, but it should326/// be limited to types defined in spec327///328/// As this trait can't be made sealed,329/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`330pub trait AbiRead<T> {331	/// Read item from current position, advanding decoder332	fn abi_read(&mut self) -> Result<T>;333	fn size() -> usize;334}335336macro_rules! impl_abi_readable {337	($ty:ty, $method:ident) => {338		impl AbiRead<$ty> for AbiReader<'_> {339			fn abi_read(&mut self) -> Result<$ty> {340				self.$method()341			}342343			fn size() -> usize {344				ABI_ALIGNMENT345			}346		}347	};348}349350impl_abi_readable!(u8, uint8);351impl_abi_readable!(u32, uint32);352impl_abi_readable!(u64, uint64);353impl_abi_readable!(u128, uint128);354impl_abi_readable!(U256, uint256);355impl_abi_readable!([u8; 4], bytes4);356impl_abi_readable!(H160, address);357impl_abi_readable!(Vec<u8>, bytes);358impl_abi_readable!(bool, bool);359impl_abi_readable!(string, string);360361mod sealed {362	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead363	pub trait CanBePlacedInVec {}364}365366impl sealed::CanBePlacedInVec for U256 {}367impl sealed::CanBePlacedInVec for string {}368impl sealed::CanBePlacedInVec for H160 {}369370impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>371where372	Self: AbiRead<R>,373{374	fn abi_read(&mut self) -> Result<Vec<R>> {375		let mut sub = self.subresult(None)?;376		let size = sub.uint32()? as usize;377		sub.subresult_offset = sub.offset;378		let mut out = Vec::with_capacity(size);379		for _ in 0..size {380			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);381		}382		Ok(out)383	}384385	fn size() -> usize {386		ABI_ALIGNMENT387	}388}389390macro_rules! impl_tuples {391	($($ident:ident)+) => {392		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}393		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>394		where395			$(396				Self: AbiRead<$ident>,397			)+398			($($ident,)+): SolidityTypeName,399		{400			fn abi_read(&mut self) -> Result<($($ident,)+)> {401				let size = if <($($ident,)+)>::is_simple() { Some(<Self as AbiRead<($($ident,)+)>>::size()) } else { None };402				let mut subresult = self.subresult(size)?;403				Ok((404					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+405				))406			}407408			fn size() -> usize {409				0 $(+ {let _ : $ident; ABI_ALIGNMENT})+410			}411		}412		#[allow(non_snake_case)]413		impl<$($ident),+> AbiWrite for &($($ident,)+)414		where415			$($ident: AbiWrite,)+416		{417			fn abi_write(&self, writer: &mut AbiWriter) {418				let ($($ident,)+) = self;419				$($ident.abi_write(writer);)+420			}421		}422	};423}424425impl_tuples! {A}426impl_tuples! {A B}427impl_tuples! {A B C}428impl_tuples! {A B C D}429impl_tuples! {A B C D E}430impl_tuples! {A B C D E F}431impl_tuples! {A B C D E F G}432impl_tuples! {A B C D E F G H}433impl_tuples! {A B C D E F G H I}434impl_tuples! {A B C D E F G H I J}435436/// For questions about inability to provide custom implementations,437/// see [`AbiRead`]438pub trait AbiWrite {439	/// Write value to end of specified encoder440	fn abi_write(&self, writer: &mut AbiWriter);441	/// Specialization for [`crate::solidity_interface`] implementation,442	/// see comment in `impl AbiWrite for ResultWithPostInfo`443	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {444		let mut writer = AbiWriter::new();445		self.abi_write(&mut writer);446		Ok(writer.into())447	}448}449450/// This particular AbiWrite implementation should be split to another trait,451/// which only implements `to_result`, but due to lack of specialization feature452/// in stable Rust, we can't have blanket impl of this trait `for T where T: AbiWrite`,453/// so here we abusing default trait methods for it454impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {455	fn abi_write(&self, _writer: &mut AbiWriter) {456		debug_assert!(false, "shouldn't be called, see comment")457	}458	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {459		match self {460			Ok(v) => Ok(WithPostDispatchInfo {461				post_info: v.post_info.clone(),462				data: {463					let mut out = AbiWriter::new();464					v.data.abi_write(&mut out);465					out466				},467			}),468			Err(e) => Err(e.clone()),469		}470	}471}472473macro_rules! impl_abi_writeable {474	($ty:ty, $method:ident) => {475		impl AbiWrite for $ty {476			fn abi_write(&self, writer: &mut AbiWriter) {477				writer.$method(&self)478			}479		}480	};481}482483impl_abi_writeable!(u8, uint8);484impl_abi_writeable!(u32, uint32);485impl_abi_writeable!(u128, uint128);486impl_abi_writeable!(U256, uint256);487impl_abi_writeable!(H160, address);488impl_abi_writeable!(bool, bool);489impl_abi_writeable!(&str, string);490impl AbiWrite for &string {491	fn abi_write(&self, writer: &mut AbiWriter) {492		writer.string(self)493	}494}495impl AbiWrite for &Vec<u8> {496	fn abi_write(&self, writer: &mut AbiWriter) {497		writer.bytes(self)498	}499}500501impl AbiWrite for () {502	fn abi_write(&self, _writer: &mut AbiWriter) {}503}504505/// Helper macros to parse reader into variables506#[deprecated]507#[macro_export]508macro_rules! abi_decode {509	($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {510		$(511			let $name = $reader.$typ()?;512		)+513	}514}515516/// Helper macros to construct RLP-encoded buffer517#[deprecated]518#[macro_export]519macro_rules! abi_encode {520	($($typ:ident($value:expr)),* $(,)?) => {{521		#[allow(unused_mut)]522		let mut writer = ::evm_coder::abi::AbiWriter::new();523		$(524			writer.$typ($value);525		)*526		writer527	}};528	(call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{529		#[allow(unused_mut)]530		let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);531		$(532			writer.$typ($value);533		)*534		writer535	}}536}537538#[cfg(test)]539pub mod test {540	use crate::{541		abi::AbiRead,542		types::{string, uint256},543	};544545	use super::{AbiReader, AbiWriter};546	use hex_literal::hex;547548	#[test]549	fn dynamic_after_static() {550		let mut encoder = AbiWriter::new();551		encoder.bool(&true);552		encoder.string("test");553		let encoded = encoder.finish();554555		let mut encoder = AbiWriter::new();556		encoder.bool(&true);557		// Offset to subresult558		encoder.uint32(&(32 * 2));559		// Len of "test"560		encoder.uint32(&4);561		encoder.write_padright(&[b't', b'e', b's', b't']);562		let alternative_encoded = encoder.finish();563564		assert_eq!(encoded, alternative_encoded);565566		let mut decoder = AbiReader::new(&encoded);567		assert!(decoder.bool().unwrap());568		assert_eq!(decoder.string().unwrap(), "test");569	}570571	#[test]572	fn mint_sample() {573		let (call, mut decoder) = AbiReader::new_call(&hex!(574			"575				50bb4e7f576				000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374577				0000000000000000000000000000000000000000000000000000000000000001578				0000000000000000000000000000000000000000000000000000000000000060579				0000000000000000000000000000000000000000000000000000000000000008580				5465737420555249000000000000000000000000000000000000000000000000581			"582		))583		.unwrap();584		assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));585		assert_eq!(586			format!("{:?}", decoder.address().unwrap()),587			"0xad2c0954693c2b5404b7e50967d3481bea432374"588		);589		assert_eq!(decoder.uint32().unwrap(), 1);590		assert_eq!(decoder.string().unwrap(), "Test URI");591	}592593	#[test]594	fn mint_bulk() {595		let (call, mut decoder) = AbiReader::new_call(&hex!(596			"597				36543006598				00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address599				0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]600				0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]601602				0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem603				00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem604				0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem605606				0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60607				0000000000000000000000000000000000000000000000000000000000000040 // offset of string608				000000000000000000000000000000000000000000000000000000000000000a // size of string609				5465737420555249203000000000000000000000000000000000000000000000 // string610611				000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0612				0000000000000000000000000000000000000000000000000000000000000040 // offset of string613				000000000000000000000000000000000000000000000000000000000000000a // size of string614				5465737420555249203100000000000000000000000000000000000000000000 // string615616				000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160617				0000000000000000000000000000000000000000000000000000000000000040 // offset of string618				000000000000000000000000000000000000000000000000000000000000000a // size of string619				5465737420555249203200000000000000000000000000000000000000000000 // string620			"621		))622		.unwrap();623		assert_eq!(call, u32::to_be_bytes(0x36543006));624		let _ = decoder.address().unwrap();625		let data =626			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();627		assert_eq!(628			data,629			vec![630				(1.into(), "Test URI 0".to_string()),631				(11.into(), "Test URI 1".to_string()),632				(12.into(), "Test URI 2".to_string())633			]634		);635	}636637	#[test]638	fn parse_vec_with_simple_type() {639		use crate::types::address;640		use primitive_types::{H160, U256};641642		let (call, mut decoder) = AbiReader::new_call(&hex!(643			"644				1ACF2D55645				0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[]646				0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[]647648				0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address649				000000000000000000000000000000000000000000000000000000000000000A // uint256650651				000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address652				0000000000000000000000000000000000000000000000000000000000000014 // uint256653654				0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address655				000000000000000000000000000000000000000000000000000000000000001E // uint256656			"657		))658		.unwrap();659		assert_eq!(call, u32::to_be_bytes(0x1ACF2D55));660		let data =661			<AbiReader<'_> as AbiRead<Vec<(address, uint256)>>>::abi_read(&mut decoder).unwrap();662		assert_eq!(data.len(), 3);663		assert_eq!(664			data,665			vec![666				(667					H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")),668					U256([10, 0, 0, 0])669				),670				(671					H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")),672					U256([20, 0, 0, 0])673				),674				(675					H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")),676					U256([30, 0, 0, 0])677				),678			]679		);680	}681}
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -192,10 +192,7 @@
 				write!(writer, "{}", tc.collect_tuple::<Self>())
 			}
 			fn is_simple() -> bool {
-				true
-				$(
-					&& <$ident>::is_simple()
-				)*
+				false
 			}
 			#[allow(unused_assignments)]
 			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -129,7 +129,7 @@
 	}
 }
 
-#[solidity_interface(name = "ERC20Mintable")]
+#[solidity_interface(name = ERC20Mintable)]
 impl<T: Config> FungibleHandle<T> {
 	/// Mint tokens for `to` account.
 	/// @param to account that will receive minted tokens
@@ -148,7 +148,7 @@
 	}
 }
 
-#[solidity_interface(name = "ERC20UniqueExtensions")]
+#[solidity_interface(name = ERC20UniqueExtensions)]
 impl<T: Config> FungibleHandle<T> {
 	/// Burn tokens from account
 	/// @dev Function that burns an `amount` of the tokens of a given account,
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -3,13 +3,7 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
-// Anonymous struct
-struct Tuple0 {
-	address field_0;
-	uint256 field_1;
-}
-
-// Common stubs holder
+/// @dev common stubs holder
 contract Dummy {
 	uint8 dummy;
 	string stub_error = "this contract is implemented in native";
@@ -27,49 +21,8 @@
 	}
 }
 
-// Inline
-contract ERC20Events {
-	event Transfer(address indexed from, address indexed to, uint256 value);
-	event Approval(
-		address indexed owner,
-		address indexed spender,
-		uint256 value
-	);
-}
-
-// Selector: 40c10f19
-contract ERC20Mintable is Dummy, ERC165 {
-	// Selector: mint(address,uint256) 40c10f19
-	function mint(address to, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		to;
-		amount;
-		dummy = 0;
-		return false;
-	}
-}
-
-// Selector: 63034ac5
-contract ERC20UniqueExtensions is Dummy, ERC165 {
-	// Selector: burnFrom(address,uint256) 79cc6790
-	function burnFrom(address from, uint256 amount) public returns (bool) {
-		require(false, stub_error);
-		from;
-		amount;
-		dummy = 0;
-		return false;
-	}
-
-	// Selector: mintBulk((address,uint256)[]) 1acf2d55
-	function mintBulk(Tuple0[] memory amounts) public returns (bool) {
-		require(false, stub_error);
-		amounts;
-		dummy = 0;
-		return false;
-	}
-}
-
-// Selector: 6cf113cd
+/// @title A contract that allows you to work with collections.
+/// @dev the ERC-165 identifier for this interface is 0xe54be640
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -397,19 +350,51 @@
 	}
 }
 
+/// @dev the ERC-165 identifier for this interface is 0x63034ac5
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+	/// Burn tokens from account
+	/// @dev Function that burns an `amount` of the tokens of a given account,
+	/// deducting from the sender's allowance for said account.
+	/// @param from The account whose tokens will be burnt.
+	/// @param amount The amount that will be burnt.
+	/// @dev EVM selector for this function is: 0x79cc6790,
+	///  or in textual repr: burnFrom(address,uint256)
+	function burnFrom(address from, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		from;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	/// Mint tokens for multiple accounts.
+	/// @param amounts array of pairs of account address and amount
+	/// @dev EVM selector for this function is: 0x1acf2d55,
+	///  or in textual repr: mintBulk((address,uint256)[])
+	function mintBulk(Tuple6[] memory amounts) public returns (bool) {
+		require(false, stub_error);
+		amounts;
+		dummy = 0;
+		return false;
+	}
+}
+
 /// @dev anonymous struct
 struct Tuple6 {
 	address field_0;
 	uint256 field_1;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x79cc6790
-contract ERC20UniqueExtensions is Dummy, ERC165 {
-	/// @dev EVM selector for this function is: 0x79cc6790,
-	///  or in textual repr: burnFrom(address,uint256)
-	function burnFrom(address from, uint256 amount) public returns (bool) {
+/// @dev the ERC-165 identifier for this interface is 0x40c10f19
+contract ERC20Mintable is Dummy, ERC165 {
+	/// Mint tokens for `to` account.
+	/// @param to account that will receive minted tokens
+	/// @param amount amount of tokens to mint
+	/// @dev EVM selector for this function is: 0x40c10f19,
+	///  or in textual repr: mint(address,uint256)
+	function mint(address to, uint256 amount) public returns (bool) {
 		require(false, stub_error);
-		from;
+		to;
 		amount;
 		dummy = 0;
 		return false;
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -3,13 +3,7 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
-// Anonymous struct
-struct Tuple0 {
-	address field_0;
-	uint256 field_1;
-}
-
-// Common stubs holder
+/// @dev common stubs holder
 interface Dummy {
 
 }
@@ -18,32 +12,8 @@
 	function supportsInterface(bytes4 interfaceID) external view returns (bool);
 }
 
-// Inline
-interface ERC20Events {
-	event Transfer(address indexed from, address indexed to, uint256 value);
-	event Approval(
-		address indexed owner,
-		address indexed spender,
-		uint256 value
-	);
-}
-
-// Selector: 40c10f19
-interface ERC20Mintable is Dummy, ERC165 {
-	// Selector: mint(address,uint256) 40c10f19
-	function mint(address to, uint256 amount) external returns (bool);
-}
-
-// Selector: 63034ac5
-interface ERC20UniqueExtensions is Dummy, ERC165 {
-	// Selector: burnFrom(address,uint256) 79cc6790
-	function burnFrom(address from, uint256 amount) external returns (bool);
-
-	// Selector: mintBulk((address,uint256)[]) 1acf2d55
-	function mintBulk(Tuple0[] memory amounts) external returns (bool);
-}
-
-// Selector: 6cf113cd
+/// @title A contract that allows you to work with collections.
+/// @dev the ERC-165 identifier for this interface is 0xe54be640
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -237,13 +207,56 @@
 	/// @dev EVM selector for this function is: 0xd34b55b8,
 	///  or in textual repr: uniqueCollectionType()
 	function uniqueCollectionType() external returns (string memory);
+
+	/// Changes collection owner to another account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner account
+	/// @dev EVM selector for this function is: 0x13af4035,
+	///  or in textual repr: setOwner(address)
+	function setOwner(address newOwner) external;
+
+	/// Changes collection owner to another substrate account
+	///
+	/// @dev Owner can be changed only by current owner
+	/// @param newOwner new owner substrate account
+	/// @dev EVM selector for this function is: 0xb212138f,
+	///  or in textual repr: setOwnerSubstrate(uint256)
+	function setOwnerSubstrate(uint256 newOwner) external;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x79cc6790
+/// @dev the ERC-165 identifier for this interface is 0x63034ac5
 interface ERC20UniqueExtensions is Dummy, ERC165 {
+	/// Burn tokens from account
+	/// @dev Function that burns an `amount` of the tokens of a given account,
+	/// deducting from the sender's allowance for said account.
+	/// @param from The account whose tokens will be burnt.
+	/// @param amount The amount that will be burnt.
 	/// @dev EVM selector for this function is: 0x79cc6790,
 	///  or in textual repr: burnFrom(address,uint256)
 	function burnFrom(address from, uint256 amount) external returns (bool);
+
+	/// Mint tokens for multiple accounts.
+	/// @param amounts array of pairs of account address and amount
+	/// @dev EVM selector for this function is: 0x1acf2d55,
+	///  or in textual repr: mintBulk((address,uint256)[])
+	function mintBulk(Tuple6[] memory amounts) external returns (bool);
+}
+
+/// @dev anonymous struct
+struct Tuple6 {
+	address field_0;
+	uint256 field_1;
+}
+
+/// @dev the ERC-165 identifier for this interface is 0x40c10f19
+interface ERC20Mintable is Dummy, ERC165 {
+	/// Mint tokens for `to` account.
+	/// @param to account that will receive minted tokens
+	/// @param amount amount of tokens to mint
+	/// @dev EVM selector for this function is: 0x40c10f19,
+	///  or in textual repr: mint(address,uint256)
+	function mint(address to, uint256 amount) external returns (bool);
 }
 
 /// @dev inlined interface
modifiedtests/src/eth/base.test.tsdiffbeforeafterboth
--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -94,7 +94,7 @@
   });
 
   itWeb3('ERC721 support', async ({web3}) => {
-    expect(await contract(web3).methods.supportsInterface('0x58800161').call()).to.be.true;
+    expect(await contract(web3).methods.supportsInterface('0x780e9d63').call()).to.be.true;
   });
 
   itWeb3('ERC721Metadata support', async ({web3}) => {
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -151,33 +151,6 @@
     "type": "function"
   },
   {
-    "inputs": [
-      { "internalType": "address", "name": "to", "type": "address" },
-      { "internalType": "uint256", "name": "amount", "type": "uint256" }
-    ],
-    "name": "mint",
-    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
-    "inputs": [
-      {
-        "components": [
-          { "internalType": "address", "name": "field_0", "type": "address" },
-          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
-        ],
-        "internalType": "struct Tuple0[]",
-        "name": "amounts",
-        "type": "tuple[]"
-      }
-    ],
-    "name": "mintBulk",
-    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
-    "stateMutability": "nonpayable",
-    "type": "function"
-  },
-  {
     "inputs": [],
     "name": "getCollectionSponsor",
     "outputs": [
@@ -220,6 +193,33 @@
     "type": "function"
   },
   {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "mint",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6[]",
+        "name": "amounts",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintBulk",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "name",
     "outputs": [{ "internalType": "string", "name": "", "type": "string" }],