git.delta.rocks / unique-network / refs/commits / 6c48590c156a

difftreelog

Merge pull request #483 from UniqueNetwork/feature/mint-for-fungible-token

Yaroslav Bolyukin2022-08-30parents: #5e4ac16 #42824e2.patch.diff
in: master

12 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2169,7 +2169,7 @@
 
 [[package]]
 name = "evm-coder"
-version = "0.1.1"
+version = "0.1.3"
 dependencies = [
  "ethereum",
  "evm-coder-procedural",
@@ -2178,6 +2178,7 @@
  "hex-literal",
  "impl-trait-for-tuples",
  "primitive-types",
+ "sp-std",
 ]
 
 [[package]]
@@ -5748,7 +5749,7 @@
 
 [[package]]
 name = "pallet-fungible"
-version = "0.1.4"
+version = "0.1.5"
 dependencies = [
  "ethereum",
  "evm-coder",
modifiedcrates/evm-coder/CHANGELOG.mddiffbeforeafterboth
--- a/crates/evm-coder/CHANGELOG.md
+++ b/crates/evm-coder/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.1.3] - 2022-08-29
+
+### Fixed
+
+ - Parsing simple values.
+
 <!-- bureaucrate goes here -->
 ## [v0.1.2] 2022-08-19
 
@@ -21,4 +27,4 @@
 
 - build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8
 
-- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
\ No newline at end of file
+- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b
modifiedcrates/evm-coder/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "evm-coder"
-version = "0.1.1"
+version = "0.1.3"
 license = "GPLv3"
 edition = "2021"
 
@@ -11,8 +11,9 @@
 primitive-types = { version = "0.11.1", default-features = false }
 # Evm doesn't have reexports for log and others
 ethereum = { version = "0.12.0", default-features = false }
+sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }
 # Error types for execution
-evm-core = { default-features = false, git = "https://github.com/uniquenetwork/evm", branch = "unique-polkadot-v0.9.27" }
+evm-core = { default-features = false , git = "https://github.com/uniquenetwork/evm", branch = "unique-polkadot-v0.9.27" }
 # We have tuple-heavy code in solidity.rs
 impl-trait-for-tuples = "0.2.2"
 
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;3132const ABI_ALIGNMENT: usize = 32;3334/// View into RLP data, which provides method to read typed items from it35#[derive(Clone)]36pub struct AbiReader<'i> {37	buf: &'i [u8],38	subresult_offset: usize,39	offset: usize,40}41impl<'i> AbiReader<'i> {42	/// Start reading RLP buffer, assuming there is no padding bytes43	pub fn new(buf: &'i [u8]) -> Self {44		Self {45			buf,46			subresult_offset: 0,47			offset: 0,48		}49	}50	/// Start reading RLP buffer, parsing first 4 bytes as selector51	pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {52		if buf.len() < 4 {53			return Err(Error::Error(ExitError::OutOfOffset));54		}55		let mut method_id = [0; 4];56		method_id.copy_from_slice(&buf[0..4]);5758		Ok((59			method_id,60			Self {61				buf,62				subresult_offset: 4,63				offset: 4,64			},65		))66	}6768	fn read_pad<const S: usize>(69		buf: &[u8],70		offset: usize,71		pad_start: usize,72		pad_size: usize,73		block_start: usize,74		block_size: usize,75	) -> Result<[u8; S]> {76		if buf.len() - offset < ABI_ALIGNMENT {77			return Err(Error::Error(ExitError::OutOfOffset));78		}79		let mut block = [0; S];80		// Verify padding is empty81		if !buf[pad_start..pad_size].iter().all(|&v| v == 0) {82			return Err(Error::Error(ExitError::InvalidRange));83		}84		block.copy_from_slice(&buf[block_start..block_size]);85		Ok(block)86	}8788	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {89		let offset = self.offset;90		self.offset += ABI_ALIGNMENT;91		Self::read_pad(92			self.buf,93			offset,94			offset,95			offset + ABI_ALIGNMENT - S,96			offset + ABI_ALIGNMENT - S,97			offset + ABI_ALIGNMENT,98		)99	}100101	fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {102		let offset = self.offset;103		self.offset += ABI_ALIGNMENT;104		Self::read_pad(105			self.buf,106			offset,107			offset + S,108			offset + ABI_ALIGNMENT,109			offset,110			offset + S,111		)112	}113114	/// Read [`H160`] at current position, then advance115	pub fn address(&mut self) -> Result<H160> {116		Ok(H160(self.read_padleft()?))117	}118119	/// Read [`bool`] at current position, then advance120	pub fn bool(&mut self) -> Result<bool> {121		let data: [u8; 1] = self.read_padleft()?;122		match data[0] {123			0 => Ok(false),124			1 => Ok(true),125			_ => Err(Error::Error(ExitError::InvalidRange)),126		}127	}128129	/// Read [`[u8; 4]`] at current position, then advance130	pub fn bytes4(&mut self) -> Result<[u8; 4]> {131		self.read_padright()132	}133134	/// Read [`Vec<u8>`] at current position, then advance135	pub fn bytes(&mut self) -> Result<Vec<u8>> {136		let mut subresult = self.subresult()?;137		let length = subresult.uint32()? as usize;138		if subresult.buf.len() < subresult.offset + length {139			return Err(Error::Error(ExitError::OutOfOffset));140		}141		Ok(subresult.buf[subresult.offset..subresult.offset + length].into())142	}143144	/// Read [`string`] at current position, then advance145	pub fn string(&mut self) -> Result<string> {146		string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))147	}148149	/// Read [`u8`] at current position, then advance150	pub fn uint8(&mut self) -> Result<u8> {151		Ok(self.read_padleft::<1>()?[0])152	}153154	/// Read [`u32`] at current position, then advance155	pub fn uint32(&mut self) -> Result<u32> {156		Ok(u32::from_be_bytes(self.read_padleft()?))157	}158159	/// Read [`u128`] at current position, then advance160	pub fn uint128(&mut self) -> Result<u128> {161		Ok(u128::from_be_bytes(self.read_padleft()?))162	}163164	/// Read [`U256`] at current position, then advance165	pub fn uint256(&mut self) -> Result<U256> {166		let buf: [u8; 32] = self.read_padleft()?;167		Ok(U256::from_big_endian(&buf))168	}169170	/// Read [`u64`] at current position, then advance171	pub fn uint64(&mut self) -> Result<u64> {172		Ok(u64::from_be_bytes(self.read_padleft()?))173	}174175	/// Read [`usize`] at current position, then advance176	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]177	pub fn read_usize(&mut self) -> Result<usize> {178		Ok(usize::from_be_bytes(self.read_padleft()?))179	}180181	/// Slice recursive buffer, advance one word for buffer offset182	fn subresult(&mut self) -> Result<AbiReader<'i>> {183		let offset = self.uint32()? as usize;184		if offset + self.subresult_offset > self.buf.len() {185			return Err(Error::Error(ExitError::InvalidRange));186		}187		Ok(AbiReader {188			buf: self.buf,189			subresult_offset: offset + self.subresult_offset,190			offset: offset + self.subresult_offset,191		})192	}193194	/// Is this parser reached end of buffer?195	pub fn is_finished(&self) -> bool {196		self.buf.len() == self.offset197	}198}199200/// Writer for RLP encoded data201#[derive(Default)]202pub struct AbiWriter {203	static_part: Vec<u8>,204	dynamic_part: Vec<(usize, AbiWriter)>,205	had_call: bool,206}207impl AbiWriter {208	/// Initialize internal buffers for output data, assuming no padding required209	pub fn new() -> Self {210		Self::default()211	}212	/// Initialize internal buffers, inserting method selector at beginning213	pub fn new_call(method_id: u32) -> Self {214		let mut val = Self::new();215		val.static_part.extend(&method_id.to_be_bytes());216		val.had_call = true;217		val218	}219220	fn write_padleft(&mut self, block: &[u8]) {221		assert!(block.len() <= ABI_ALIGNMENT);222		self.static_part223			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);224		self.static_part.extend(block);225	}226227	fn write_padright(&mut self, bytes: &[u8]) {228		assert!(bytes.len() <= ABI_ALIGNMENT);229		self.static_part.extend(bytes);230		self.static_part231			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);232	}233234	/// Write [`H160`] to end of buffer235	pub fn address(&mut self, address: &H160) {236		self.write_padleft(&address.0)237	}238239	/// Write [`bool`] to end of buffer240	pub fn bool(&mut self, value: &bool) {241		self.write_padleft(&[if *value { 1 } else { 0 }])242	}243244	/// Write [`u8`] to end of buffer245	pub fn uint8(&mut self, value: &u8) {246		self.write_padleft(&[*value])247	}248249	/// Write [`u32`] to end of buffer250	pub fn uint32(&mut self, value: &u32) {251		self.write_padleft(&u32::to_be_bytes(*value))252	}253254	/// Write [`u128`] to end of buffer255	pub fn uint128(&mut self, value: &u128) {256		self.write_padleft(&u128::to_be_bytes(*value))257	}258259	/// Write [`U256`] to end of buffer260	pub fn uint256(&mut self, value: &U256) {261		let mut out = [0; 32];262		value.to_big_endian(&mut out);263		self.write_padleft(&out)264	}265266	/// Write [`usize`] to end of buffer267	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]268	pub fn write_usize(&mut self, value: &usize) {269		self.write_padleft(&usize::to_be_bytes(*value))270	}271272	/// Append recursive data, writing pending offset at end of buffer273	pub fn write_subresult(&mut self, result: Self) {274		self.dynamic_part.push((self.static_part.len(), result));275		// Empty block, to be filled later276		self.write_padleft(&[]);277	}278279	fn memory(&mut self, value: &[u8]) {280		let mut sub = Self::new();281		sub.uint32(&(value.len() as u32));282		for chunk in value.chunks(ABI_ALIGNMENT) {283			sub.write_padright(chunk);284		}285		self.write_subresult(sub);286	}287288	/// Append recursive [`str`] at end of buffer289	pub fn string(&mut self, value: &str) {290		self.memory(value.as_bytes())291	}292293	/// Append recursive [`[u8]`] at end of buffer294	pub fn bytes(&mut self, value: &[u8]) {295		self.memory(value)296	}297298	/// Finish writer, concatenating all internal buffers299	pub fn finish(mut self) -> Vec<u8> {300		for (static_offset, part) in self.dynamic_part {301			let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0);302303			let encoded_dynamic_offset = usize::to_be_bytes(part_offset);304			self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()305				..static_offset + ABI_ALIGNMENT]306				.copy_from_slice(&encoded_dynamic_offset);307			self.static_part.extend(part.finish())308		}309		self.static_part310	}311}312313/// [`AbiReader`] implements reading of many types, but it should314/// be limited to types defined in spec315///316/// As this trait can't be made sealed,317/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`318pub trait AbiRead<T> {319	/// Read item from current position, advanding decoder320	fn abi_read(&mut self) -> Result<T>;321}322323macro_rules! impl_abi_readable {324	($ty:ty, $method:ident) => {325		impl AbiRead<$ty> for AbiReader<'_> {326			fn abi_read(&mut self) -> Result<$ty> {327				self.$method()328			}329		}330	};331}332333impl_abi_readable!(u8, uint8);334impl_abi_readable!(u32, uint32);335impl_abi_readable!(u64, uint64);336impl_abi_readable!(u128, uint128);337impl_abi_readable!(U256, uint256);338impl_abi_readable!([u8; 4], bytes4);339impl_abi_readable!(H160, address);340impl_abi_readable!(Vec<u8>, bytes);341impl_abi_readable!(bool, bool);342impl_abi_readable!(string, string);343344mod sealed {345	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead346	pub trait CanBePlacedInVec {}347}348349impl sealed::CanBePlacedInVec for U256 {}350impl sealed::CanBePlacedInVec for string {}351impl sealed::CanBePlacedInVec for H160 {}352353impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>354where355	Self: AbiRead<R>,356{357	fn abi_read(&mut self) -> Result<Vec<R>> {358		let mut sub = self.subresult()?;359		let size = sub.uint32()? as usize;360		sub.subresult_offset = sub.offset;361		let mut out = Vec::with_capacity(size);362		for _ in 0..size {363			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);364		}365		Ok(out)366	}367}368369macro_rules! impl_tuples {370	($($ident:ident)+) => {371		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}372		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>373		where374			$(Self: AbiRead<$ident>),+375		{376			fn abi_read(&mut self) -> Result<($($ident,)+)> {377				let mut subresult = self.subresult()?;378				Ok((379					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+380				))381			}382		}383		#[allow(non_snake_case)]384		impl<$($ident),+> AbiWrite for &($($ident,)+)385		where386			$($ident: AbiWrite,)+387		{388			fn abi_write(&self, writer: &mut AbiWriter) {389				let ($($ident,)+) = self;390				$($ident.abi_write(writer);)+391			}392		}393	};394}395396impl_tuples! {A}397impl_tuples! {A B}398impl_tuples! {A B C}399impl_tuples! {A B C D}400impl_tuples! {A B C D E}401impl_tuples! {A B C D E F}402impl_tuples! {A B C D E F G}403impl_tuples! {A B C D E F G H}404impl_tuples! {A B C D E F G H I}405impl_tuples! {A B C D E F G H I J}406407/// For questions about inability to provide custom implementations,408/// see [`AbiRead`]409pub trait AbiWrite {410	/// Write value to end of specified encoder411	fn abi_write(&self, writer: &mut AbiWriter);412	/// Specialization for [`crate::solidity_interface`] implementation,413	/// see comment in `impl AbiWrite for ResultWithPostInfo`414	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {415		let mut writer = AbiWriter::new();416		self.abi_write(&mut writer);417		Ok(writer.into())418	}419}420421/// This particular AbiWrite implementation should be split to another trait,422/// which only implements `to_result`, but due to lack of specialization feature423/// in stable Rust, we can't have blanket impl of this trait `for T where T: AbiWrite`,424/// so here we abusing default trait methods for it425impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {426	fn abi_write(&self, _writer: &mut AbiWriter) {427		debug_assert!(false, "shouldn't be called, see comment")428	}429	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {430		match self {431			Ok(v) => Ok(WithPostDispatchInfo {432				post_info: v.post_info.clone(),433				data: {434					let mut out = AbiWriter::new();435					v.data.abi_write(&mut out);436					out437				},438			}),439			Err(e) => Err(e.clone()),440		}441	}442}443444macro_rules! impl_abi_writeable {445	($ty:ty, $method:ident) => {446		impl AbiWrite for $ty {447			fn abi_write(&self, writer: &mut AbiWriter) {448				writer.$method(&self)449			}450		}451	};452}453454impl_abi_writeable!(u8, uint8);455impl_abi_writeable!(u32, uint32);456impl_abi_writeable!(u128, uint128);457impl_abi_writeable!(U256, uint256);458impl_abi_writeable!(H160, address);459impl_abi_writeable!(bool, bool);460impl_abi_writeable!(&str, string);461impl AbiWrite for &string {462	fn abi_write(&self, writer: &mut AbiWriter) {463		writer.string(self)464	}465}466impl AbiWrite for &Vec<u8> {467	fn abi_write(&self, writer: &mut AbiWriter) {468		writer.bytes(self)469	}470}471472impl AbiWrite for () {473	fn abi_write(&self, _writer: &mut AbiWriter) {}474}475476/// Helper macros to parse reader into variables477#[deprecated]478#[macro_export]479macro_rules! abi_decode {480	($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {481		$(482			let $name = $reader.$typ()?;483		)+484	}485}486487/// Helper macros to construct RLP-encoded buffer488#[deprecated]489#[macro_export]490macro_rules! abi_encode {491	($($typ:ident($value:expr)),* $(,)?) => {{492		#[allow(unused_mut)]493		let mut writer = ::evm_coder::abi::AbiWriter::new();494		$(495			writer.$typ($value);496		)*497		writer498	}};499	(call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{500		#[allow(unused_mut)]501		let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);502		$(503			writer.$typ($value);504		)*505		writer506	}}507}508509#[cfg(test)]510pub mod test {511	use crate::{512		abi::AbiRead,513		types::{string, uint256},514	};515516	use super::{AbiReader, AbiWriter};517	use hex_literal::hex;518519	#[test]520	fn dynamic_after_static() {521		let mut encoder = AbiWriter::new();522		encoder.bool(&true);523		encoder.string("test");524		let encoded = encoder.finish();525526		let mut encoder = AbiWriter::new();527		encoder.bool(&true);528		// Offset to subresult529		encoder.uint32(&(32 * 2));530		// Len of "test"531		encoder.uint32(&4);532		encoder.write_padright(&[b't', b'e', b's', b't']);533		let alternative_encoded = encoder.finish();534535		assert_eq!(encoded, alternative_encoded);536537		let mut decoder = AbiReader::new(&encoded);538		assert_eq!(decoder.bool().unwrap(), true);539		assert_eq!(decoder.string().unwrap(), "test");540	}541542	#[test]543	fn mint_sample() {544		let (call, mut decoder) = AbiReader::new_call(&hex!(545			"546				50bb4e7f547				000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374548				0000000000000000000000000000000000000000000000000000000000000001549				0000000000000000000000000000000000000000000000000000000000000060550				0000000000000000000000000000000000000000000000000000000000000008551				5465737420555249000000000000000000000000000000000000000000000000552			"553		))554		.unwrap();555		assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));556		assert_eq!(557			format!("{:?}", decoder.address().unwrap()),558			"0xad2c0954693c2b5404b7e50967d3481bea432374"559		);560		assert_eq!(decoder.uint32().unwrap(), 1);561		assert_eq!(decoder.string().unwrap(), "Test URI");562	}563564	#[test]565	fn mint_bulk() {566		let (call, mut decoder) = AbiReader::new_call(&hex!(567			"568				36543006569				00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address570				0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]571				0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]572573				0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem574				00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem575				0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem576577				0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60578				0000000000000000000000000000000000000000000000000000000000000040 // offset of string579				000000000000000000000000000000000000000000000000000000000000000a // size of string580				5465737420555249203000000000000000000000000000000000000000000000 // string581582				000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0583				0000000000000000000000000000000000000000000000000000000000000040 // offset of string584				000000000000000000000000000000000000000000000000000000000000000a // size of string585				5465737420555249203100000000000000000000000000000000000000000000 // string586587				000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160588				0000000000000000000000000000000000000000000000000000000000000040 // offset of string589				000000000000000000000000000000000000000000000000000000000000000a // size of string590				5465737420555249203200000000000000000000000000000000000000000000 // string591			"592		))593		.unwrap();594		assert_eq!(call, u32::to_be_bytes(0x36543006));595		let _ = decoder.address().unwrap();596		let data =597			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();598		assert_eq!(599			data,600			vec![601				(1.into(), "Test URI 0".to_string()),602				(11.into(), "Test URI 1".to_string()),603				(12.into(), "Test URI 2".to_string())604			]605		);606	}607}
after · 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;3132const ABI_ALIGNMENT: usize = 32;3334trait TypeHelper {35	fn is_dynamic() -> bool;36}3738/// View into RLP data, which provides method to read typed items from it39#[derive(Clone)]40pub struct AbiReader<'i> {41	buf: &'i [u8],42	subresult_offset: usize,43	offset: usize,44}45impl<'i> AbiReader<'i> {46	/// Start reading RLP buffer, assuming there is no padding bytes47	pub fn new(buf: &'i [u8]) -> Self {48		Self {49			buf,50			subresult_offset: 0,51			offset: 0,52		}53	}54	/// Start reading RLP buffer, parsing first 4 bytes as selector55	pub fn new_call(buf: &'i [u8]) -> Result<(types::bytes4, Self)> {56		if buf.len() < 4 {57			return Err(Error::Error(ExitError::OutOfOffset));58		}59		let mut method_id = [0; 4];60		method_id.copy_from_slice(&buf[0..4]);6162		Ok((63			method_id,64			Self {65				buf,66				subresult_offset: 4,67				offset: 4,68			},69		))70	}7172	fn read_pad<const S: usize>(73		buf: &[u8],74		offset: usize,75		pad_start: usize,76		pad_size: usize,77		block_start: usize,78		block_size: usize,79	) -> Result<[u8; S]> {80		if buf.len() - offset < ABI_ALIGNMENT {81			return Err(Error::Error(ExitError::OutOfOffset));82		}83		let mut block = [0; S];84		let is_pad_zeroed = buf[pad_start..pad_size].iter().all(|&v| v == 0);85		if !is_pad_zeroed {86			return Err(Error::Error(ExitError::InvalidRange));87		}88		block.copy_from_slice(&buf[block_start..block_size]);89		Ok(block)90	}9192	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {93		let offset = self.offset;94		self.offset += ABI_ALIGNMENT;95		Self::read_pad(96			self.buf,97			offset,98			offset,99			offset + ABI_ALIGNMENT - S,100			offset + ABI_ALIGNMENT - S,101			offset + ABI_ALIGNMENT,102		)103	}104105	fn read_padright<const S: usize>(&mut self) -> Result<[u8; S]> {106		let offset = self.offset;107		self.offset += ABI_ALIGNMENT;108		Self::read_pad(109			self.buf,110			offset,111			offset + S,112			offset + ABI_ALIGNMENT,113			offset,114			offset + S,115		)116	}117118	/// Read [`H160`] at current position, then advance119	pub fn address(&mut self) -> Result<H160> {120		Ok(H160(self.read_padleft()?))121	}122123	/// Read [`bool`] at current position, then advance124	pub fn bool(&mut self) -> Result<bool> {125		let data: [u8; 1] = self.read_padleft()?;126		match data[0] {127			0 => Ok(false),128			1 => Ok(true),129			_ => Err(Error::Error(ExitError::InvalidRange)),130		}131	}132133	/// Read [`[u8; 4]`] at current position, then advance134	pub fn bytes4(&mut self) -> Result<[u8; 4]> {135		self.read_padright()136	}137138	/// Read [`Vec<u8>`] at current position, then advance139	pub fn bytes(&mut self) -> Result<Vec<u8>> {140		let mut subresult = self.subresult(None)?;141		let length = subresult.uint32()? as usize;142		if subresult.buf.len() < subresult.offset + length {143			return Err(Error::Error(ExitError::OutOfOffset));144		}145		Ok(subresult.buf[subresult.offset..subresult.offset + length].into())146	}147148	/// Read [`string`] at current position, then advance149	pub fn string(&mut self) -> Result<string> {150		string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))151	}152153	/// Read [`u8`] at current position, then advance154	pub fn uint8(&mut self) -> Result<u8> {155		Ok(self.read_padleft::<1>()?[0])156	}157158	/// Read [`u32`] at current position, then advance159	pub fn uint32(&mut self) -> Result<u32> {160		Ok(u32::from_be_bytes(self.read_padleft()?))161	}162163	/// Read [`u128`] at current position, then advance164	pub fn uint128(&mut self) -> Result<u128> {165		Ok(u128::from_be_bytes(self.read_padleft()?))166	}167168	/// Read [`U256`] at current position, then advance169	pub fn uint256(&mut self) -> Result<U256> {170		let buf: [u8; 32] = self.read_padleft()?;171		Ok(U256::from_big_endian(&buf))172	}173174	/// Read [`u64`] at current position, then advance175	pub fn uint64(&mut self) -> Result<u64> {176		Ok(u64::from_be_bytes(self.read_padleft()?))177	}178179	/// Read [`usize`] at current position, then advance180	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]181	pub fn read_usize(&mut self) -> Result<usize> {182		Ok(usize::from_be_bytes(self.read_padleft()?))183	}184185	/// Slice recursive buffer, advance one word for buffer offset186	/// If `size` is [`None`] then [`Self::offset`] and [`Self::subresult_offset`] evals from [`Self::buf`].187	fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {188		let subresult_offset = self.subresult_offset;189		let offset = if let Some(size) = size {190			self.offset += size;191			self.subresult_offset += size;192			0193		} else {194			self.uint32()? as usize195		};196197		if offset + self.subresult_offset > self.buf.len() {198			return Err(Error::Error(ExitError::InvalidRange));199		}200201		let new_offset = offset + subresult_offset;202		Ok(AbiReader {203			buf: self.buf,204			subresult_offset: new_offset,205			offset: new_offset,206		})207	}208209	/// Is this parser reached end of buffer?210	pub fn is_finished(&self) -> bool {211		self.buf.len() == self.offset212	}213}214215/// Writer for RLP encoded data216#[derive(Default)]217pub struct AbiWriter {218	static_part: Vec<u8>,219	dynamic_part: Vec<(usize, AbiWriter)>,220	had_call: bool,221}222impl AbiWriter {223	/// Initialize internal buffers for output data, assuming no padding required224	pub fn new() -> Self {225		Self::default()226	}227	/// Initialize internal buffers, inserting method selector at beginning228	pub fn new_call(method_id: u32) -> Self {229		let mut val = Self::new();230		val.static_part.extend(&method_id.to_be_bytes());231		val.had_call = true;232		val233	}234235	fn write_padleft(&mut self, block: &[u8]) {236		assert!(block.len() <= ABI_ALIGNMENT);237		self.static_part238			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - block.len()]);239		self.static_part.extend(block);240	}241242	fn write_padright(&mut self, bytes: &[u8]) {243		assert!(bytes.len() <= ABI_ALIGNMENT);244		self.static_part.extend(bytes);245		self.static_part246			.extend(&[0; ABI_ALIGNMENT][0..ABI_ALIGNMENT - bytes.len()]);247	}248249	/// Write [`H160`] to end of buffer250	pub fn address(&mut self, address: &H160) {251		self.write_padleft(&address.0)252	}253254	/// Write [`bool`] to end of buffer255	pub fn bool(&mut self, value: &bool) {256		self.write_padleft(&[if *value { 1 } else { 0 }])257	}258259	/// Write [`u8`] to end of buffer260	pub fn uint8(&mut self, value: &u8) {261		self.write_padleft(&[*value])262	}263264	/// Write [`u32`] to end of buffer265	pub fn uint32(&mut self, value: &u32) {266		self.write_padleft(&u32::to_be_bytes(*value))267	}268269	/// Write [`u128`] to end of buffer270	pub fn uint128(&mut self, value: &u128) {271		self.write_padleft(&u128::to_be_bytes(*value))272	}273274	/// Write [`U256`] to end of buffer275	pub fn uint256(&mut self, value: &U256) {276		let mut out = [0; 32];277		value.to_big_endian(&mut out);278		self.write_padleft(&out)279	}280281	/// Write [`usize`] to end of buffer282	#[deprecated = "dangerous, as usize may have different width in wasm and native execution"]283	pub fn write_usize(&mut self, value: &usize) {284		self.write_padleft(&usize::to_be_bytes(*value))285	}286287	/// Append recursive data, writing pending offset at end of buffer288	pub fn write_subresult(&mut self, result: Self) {289		self.dynamic_part.push((self.static_part.len(), result));290		// Empty block, to be filled later291		self.write_padleft(&[]);292	}293294	fn memory(&mut self, value: &[u8]) {295		let mut sub = Self::new();296		sub.uint32(&(value.len() as u32));297		for chunk in value.chunks(ABI_ALIGNMENT) {298			sub.write_padright(chunk);299		}300		self.write_subresult(sub);301	}302303	/// Append recursive [`str`] at end of buffer304	pub fn string(&mut self, value: &str) {305		self.memory(value.as_bytes())306	}307308	/// Append recursive [`[u8]`] at end of buffer309	pub fn bytes(&mut self, value: &[u8]) {310		self.memory(value)311	}312313	/// Finish writer, concatenating all internal buffers314	pub fn finish(mut self) -> Vec<u8> {315		for (static_offset, part) in self.dynamic_part {316			let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0);317318			let encoded_dynamic_offset = usize::to_be_bytes(part_offset);319			self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()320				..static_offset + ABI_ALIGNMENT]321				.copy_from_slice(&encoded_dynamic_offset);322			self.static_part.extend(part.finish())323		}324		self.static_part325	}326}327328/// [`AbiReader`] implements reading of many types, but it should329/// be limited to types defined in spec330///331/// As this trait can't be made sealed,332/// instead of having `impl AbiRead for T`, we have `impl AbiRead<T> for AbiReader`333pub trait AbiRead<T> {334	/// Read item from current position, advanding decoder335	fn abi_read(&mut self) -> Result<T>;336337	/// Size for type aligned to [`ABI_ALIGNMENT`].338	fn size() -> usize;339}340341macro_rules! impl_abi_readable {342	($ty:ty, $method:ident, $dynamic:literal) => {343		impl TypeHelper for $ty {344			fn is_dynamic() -> bool {345				$dynamic346			}347		}348		impl AbiRead<$ty> for AbiReader<'_> {349			fn abi_read(&mut self) -> Result<$ty> {350				self.$method()351			}352353			fn size() -> usize {354				ABI_ALIGNMENT355			}356		}357	};358}359360impl_abi_readable!(u8, uint8, false);361impl_abi_readable!(u32, uint32, false);362impl_abi_readable!(u64, uint64, false);363impl_abi_readable!(u128, uint128, false);364impl_abi_readable!(U256, uint256, false);365impl_abi_readable!([u8; 4], bytes4, false);366impl_abi_readable!(H160, address, false);367impl_abi_readable!(Vec<u8>, bytes, true);368impl_abi_readable!(bool, bool, true);369impl_abi_readable!(string, string, true);370371mod sealed {372	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead373	pub trait CanBePlacedInVec {}374}375376impl sealed::CanBePlacedInVec for U256 {}377impl sealed::CanBePlacedInVec for string {}378impl sealed::CanBePlacedInVec for H160 {}379380impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>381where382	Self: AbiRead<R>,383{384	fn abi_read(&mut self) -> Result<Vec<R>> {385		let mut sub = self.subresult(None)?;386		let size = sub.uint32()? as usize;387		sub.subresult_offset = sub.offset;388		let mut out = Vec::with_capacity(size);389		for _ in 0..size {390			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);391		}392		Ok(out)393	}394395	fn size() -> usize {396		ABI_ALIGNMENT397	}398}399400macro_rules! impl_tuples {401	($($ident:ident)+) => {402		impl<$($ident: TypeHelper,)+> TypeHelper for ($($ident,)+) {403			fn is_dynamic() -> bool {404				false405				$(406					|| <$ident>::is_dynamic()407				)*408			}409		}410		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}411		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>412		where413			$(414				Self: AbiRead<$ident>,415			)+416			($($ident,)+): TypeHelper,417		{418			fn abi_read(&mut self) -> Result<($($ident,)+)> {419				let size = if !<($($ident,)+)>::is_dynamic() { Some(<Self as AbiRead<($($ident,)+)>>::size()) } else { None };420				let mut subresult = self.subresult(size)?;421				Ok((422					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+423				))424			}425426			fn size() -> usize {427				0 $(+ <AbiReader<'_> as AbiRead<$ident>>::size())+428			}429		}430		#[allow(non_snake_case)]431		impl<$($ident),+> AbiWrite for &($($ident,)+)432		where433			$($ident: AbiWrite,)+434		{435			fn abi_write(&self, writer: &mut AbiWriter) {436				let ($($ident,)+) = self;437				$($ident.abi_write(writer);)+438			}439		}440	};441}442443impl_tuples! {A}444impl_tuples! {A B}445impl_tuples! {A B C}446impl_tuples! {A B C D}447impl_tuples! {A B C D E}448impl_tuples! {A B C D E F}449impl_tuples! {A B C D E F G}450impl_tuples! {A B C D E F G H}451impl_tuples! {A B C D E F G H I}452impl_tuples! {A B C D E F G H I J}453454/// For questions about inability to provide custom implementations,455/// see [`AbiRead`]456pub trait AbiWrite {457	/// Write value to end of specified encoder458	fn abi_write(&self, writer: &mut AbiWriter);459	/// Specialization for [`crate::solidity_interface`] implementation,460	/// see comment in `impl AbiWrite for ResultWithPostInfo`461	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {462		let mut writer = AbiWriter::new();463		self.abi_write(&mut writer);464		Ok(writer.into())465	}466}467468/// This particular AbiWrite implementation should be split to another trait,469/// which only implements `to_result`, but due to lack of specialization feature470/// in stable Rust, we can't have blanket impl of this trait `for T where T: AbiWrite`,471/// so here we abusing default trait methods for it472impl<T: AbiWrite> AbiWrite for ResultWithPostInfo<T> {473	fn abi_write(&self, _writer: &mut AbiWriter) {474		debug_assert!(false, "shouldn't be called, see comment")475	}476	fn to_result(&self) -> ResultWithPostInfo<AbiWriter> {477		match self {478			Ok(v) => Ok(WithPostDispatchInfo {479				post_info: v.post_info.clone(),480				data: {481					let mut out = AbiWriter::new();482					v.data.abi_write(&mut out);483					out484				},485			}),486			Err(e) => Err(e.clone()),487		}488	}489}490491macro_rules! impl_abi_writeable {492	($ty:ty, $method:ident) => {493		impl AbiWrite for $ty {494			fn abi_write(&self, writer: &mut AbiWriter) {495				writer.$method(&self)496			}497		}498	};499}500501impl_abi_writeable!(u8, uint8);502impl_abi_writeable!(u32, uint32);503impl_abi_writeable!(u128, uint128);504impl_abi_writeable!(U256, uint256);505impl_abi_writeable!(H160, address);506impl_abi_writeable!(bool, bool);507impl_abi_writeable!(&str, string);508impl AbiWrite for &string {509	fn abi_write(&self, writer: &mut AbiWriter) {510		writer.string(self)511	}512}513impl AbiWrite for &Vec<u8> {514	fn abi_write(&self, writer: &mut AbiWriter) {515		writer.bytes(self)516	}517}518519impl AbiWrite for () {520	fn abi_write(&self, _writer: &mut AbiWriter) {}521}522523/// Helper macros to parse reader into variables524#[deprecated]525#[macro_export]526macro_rules! abi_decode {527	($reader:expr, $($name:ident: $typ:ident),+ $(,)?) => {528		$(529			let $name = $reader.$typ()?;530		)+531	}532}533534/// Helper macros to construct RLP-encoded buffer535#[deprecated]536#[macro_export]537macro_rules! abi_encode {538	($($typ:ident($value:expr)),* $(,)?) => {{539		#[allow(unused_mut)]540		let mut writer = ::evm_coder::abi::AbiWriter::new();541		$(542			writer.$typ($value);543		)*544		writer545	}};546	(call $val:expr; $($typ:ident($value:expr)),* $(,)?) => {{547		#[allow(unused_mut)]548		let mut writer = ::evm_coder::abi::AbiWriter::new_call($val);549		$(550			writer.$typ($value);551		)*552		writer553	}}554}555556#[cfg(test)]557pub mod test {558	use crate::{559		abi::AbiRead,560		types::{string, uint256},561	};562563	use super::{AbiReader, AbiWriter};564	use hex_literal::hex;565566	#[test]567	fn dynamic_after_static() {568		let mut encoder = AbiWriter::new();569		encoder.bool(&true);570		encoder.string("test");571		let encoded = encoder.finish();572573		let mut encoder = AbiWriter::new();574		encoder.bool(&true);575		// Offset to subresult576		encoder.uint32(&(32 * 2));577		// Len of "test"578		encoder.uint32(&4);579		encoder.write_padright(&[b't', b'e', b's', b't']);580		let alternative_encoded = encoder.finish();581582		assert_eq!(encoded, alternative_encoded);583584		let mut decoder = AbiReader::new(&encoded);585		assert!(decoder.bool().unwrap());586		assert_eq!(decoder.string().unwrap(), "test");587	}588589	#[test]590	fn mint_sample() {591		let (call, mut decoder) = AbiReader::new_call(&hex!(592			"593				50bb4e7f594				000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374595				0000000000000000000000000000000000000000000000000000000000000001596				0000000000000000000000000000000000000000000000000000000000000060597				0000000000000000000000000000000000000000000000000000000000000008598				5465737420555249000000000000000000000000000000000000000000000000599			"600		))601		.unwrap();602		assert_eq!(call, u32::to_be_bytes(0x50bb4e7f));603		assert_eq!(604			format!("{:?}", decoder.address().unwrap()),605			"0xad2c0954693c2b5404b7e50967d3481bea432374"606		);607		assert_eq!(decoder.uint32().unwrap(), 1);608		assert_eq!(decoder.string().unwrap(), "Test URI");609	}610611	#[test]612	fn mint_bulk() {613		let (call, mut decoder) = AbiReader::new_call(&hex!(614			"615				36543006616				00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address617				0000000000000000000000000000000000000000000000000000000000000040 // offset of (uint256, string)[]618				0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]619620				0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem621				00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem622				0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem623624				0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60625				0000000000000000000000000000000000000000000000000000000000000040 // offset of string626				000000000000000000000000000000000000000000000000000000000000000a // size of string627				5465737420555249203000000000000000000000000000000000000000000000 // string628629				000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0630				0000000000000000000000000000000000000000000000000000000000000040 // offset of string631				000000000000000000000000000000000000000000000000000000000000000a // size of string632				5465737420555249203100000000000000000000000000000000000000000000 // string633634				000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160635				0000000000000000000000000000000000000000000000000000000000000040 // offset of string636				000000000000000000000000000000000000000000000000000000000000000a // size of string637				5465737420555249203200000000000000000000000000000000000000000000 // string638			"639		))640		.unwrap();641		assert_eq!(call, u32::to_be_bytes(0x36543006));642		let _ = decoder.address().unwrap();643		let data =644			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();645		assert_eq!(646			data,647			vec![648				(1.into(), "Test URI 0".to_string()),649				(11.into(), "Test URI 1".to_string()),650				(12.into(), "Test URI 2".to_string())651			]652		);653	}654655	#[test]656	fn parse_vec_with_simple_type() {657		use crate::types::address;658		use primitive_types::{H160, U256};659660		let (call, mut decoder) = AbiReader::new_call(&hex!(661			"662				1ACF2D55663				0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[]664				0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[]665666				0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address667				000000000000000000000000000000000000000000000000000000000000000A // uint256668669				000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address670				0000000000000000000000000000000000000000000000000000000000000014 // uint256671672				0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address673				000000000000000000000000000000000000000000000000000000000000001E // uint256674			"675		))676		.unwrap();677		assert_eq!(call, u32::to_be_bytes(0x1ACF2D55));678		let data =679			<AbiReader<'_> as AbiRead<Vec<(address, uint256)>>>::abi_read(&mut decoder).unwrap();680		assert_eq!(data.len(), 3);681		assert_eq!(682			data,683			vec![684				(685					H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")),686					U256([10, 0, 0, 0])687				),688				(689					H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")),690					U256([20, 0, 0, 0])691				),692				(693					H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")),694					U256([30, 0, 0, 0])695				),696			]697		);698	}699}
modifiedpallets/fungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -2,6 +2,13 @@
 
 All notable changes to this project will be documented in this file.
 
+
+## [0.1.5] - 2022-08-29
+
+### Added
+
+ - Implementation of `mint` and `mint_bulk` methods for ERC20 API.
+
 ## [v0.1.4] - 2022-08-24
 
 ### Change
modifiedpallets/fungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-fungible"
-version = "0.1.4"
+version = "0.1.5"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -129,8 +129,32 @@
 	}
 }
 
+#[solidity_interface(name = ERC20Mintable)]
+impl<T: Config> FungibleHandle<T> {
+	/// Mint tokens for `to` account.
+	/// @param to account that will receive minted tokens
+	/// @param amount amount of tokens to mint
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		<Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+}
+
 #[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,
+	/// 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.
 	#[weight(<SelfWeightOf<T>>::burn_from())]
 	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
 		let caller = T::CrossAccountId::from_eth(caller);
@@ -144,12 +168,36 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	/// Mint tokens for multiple accounts.
+	/// @param amounts array of pairs of account address and amount
+	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]
+	fn mint_bulk(&mut self, caller: caller, amounts: Vec<(address, uint256)>) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		let amounts = amounts
+			.into_iter()
+			.map(|(to, amount)| {
+				Ok((
+					T::CrossAccountId::from_eth(to),
+					amount.try_into().map_err(|_| "amount overflow")?,
+				))
+			})
+			.collect::<Result<_>>()?;
+
+		<Pallet<T>>::create_multiple_items(&self, &caller, amounts, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
 }
 
 #[solidity_interface(
 	name = UniqueFungible,
 	is(
 		ERC20,
+		ERC20Mintable,
 		ERC20UniqueExtensions,
 		Collection(common_mut, CollectionHandle<T>),
 	)
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
@@ -350,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;
@@ -476,6 +508,7 @@
 	Dummy,
 	ERC165,
 	ERC20,
+	ERC20Mintable,
 	ERC20UniqueExtensions,
 	Collection
 {}
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -225,17 +225,38 @@
 	function setOwnerSubstrate(uint256 newOwner) external;
 }
 
+/// @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 0x79cc6790
-interface 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) external returns (bool);
+/// @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
@@ -298,6 +319,7 @@
 	Dummy,
 	ERC165,
 	ERC20,
+	ERC20Mintable,
 	ERC20UniqueExtensions,
 	Collection
 {}
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -14,10 +14,11 @@
 // 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 {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';
+import {approveExpectSuccess, createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';
 import fungibleAbi from './fungibleAbi.json';
 import {expect} from 'chai';
+import {submitTransactionAsync} from '../substrate/substrate-api';
 
 describe('Fungible: Information getting', () => {
   itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
@@ -58,6 +59,128 @@
 });
 
 describe('Fungible: Plain calls', () => {
+  itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    const collection = await createCollection(api, alice, {
+      name: 'token name',
+      mode: {type: 'Fungible', decimalPoints: 0},
+    });
+
+    const receiver = createEthAccount(web3);
+
+    const collectionIdAddress = collectionIdToAddress(collection.collectionId);
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});
+    await submitTransactionAsync(alice, changeAdminTx);
+
+    const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    const result = await collectionContract.methods.mint(receiver, 100).send();
+    const events = normalizeEvents(result.events);
+    
+    expect(events).to.be.deep.equal([
+      {
+        address: collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          value: '100',
+        },
+      },
+    ]);
+  });
+
+  itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    const collection = await createCollection(api, alice, {
+      name: 'token name',
+      mode: {type: 'Fungible', decimalPoints: 0},
+    });
+
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const receiver1 = createEthAccount(web3);
+    const receiver2 = createEthAccount(web3);
+    const receiver3 = createEthAccount(web3);
+
+    const collectionIdAddress = collectionIdToAddress(collection.collectionId);
+    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});
+    await submitTransactionAsync(alice, changeAdminTx);
+
+    const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    const result = await collectionContract.methods.mintBulk([
+      [receiver1, 10],
+      [receiver2, 20],
+      [receiver3, 30],
+    ]).send();
+    const events = normalizeEvents(result.events);
+
+    expect(events).to.be.deep.contain({
+      address:collectionIdAddress,
+      event: 'Transfer',
+      args: {
+        from: '0x0000000000000000000000000000000000000000',
+        to: receiver1,
+        value: '10',
+      },
+    });
+    
+    expect(events).to.be.deep.contain({
+      address:collectionIdAddress,
+      event: 'Transfer',
+      args: {
+        from: '0x0000000000000000000000000000000000000000',
+        to: receiver2,
+        value: '20',
+      },
+    });
+    
+    expect(events).to.be.deep.contain({
+      address:collectionIdAddress,
+      event: 'Transfer',
+      args: {
+        from: '0x0000000000000000000000000000000000000000',
+        to: receiver3,
+        value: '30',
+      },
+    });
+  });
+
+  itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {
+    const alice = privateKeyWrapper('//Alice');
+    const collection = await createCollection(api, alice, {
+      name: 'token name',
+      mode: {type: 'Fungible', decimalPoints: 0},
+    });
+
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});
+    await submitTransactionAsync(alice, changeAdminTx);
+    const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+    const collectionIdAddress = collectionIdToAddress(collection.collectionId);
+    const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    await collectionContract.methods.mint(receiver, 100).send();
+
+    const result = await collectionContract.methods.burnFrom(receiver, 49).send({from: receiver});
+    
+    const events = normalizeEvents(result.events);
+
+    expect(events).to.be.deep.equal([
+      {
+        address: collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: receiver,
+          to: '0x0000000000000000000000000000000000000000',
+          value: '49',
+        },
+      },
+    ]);
+
+    const balance = await collectionContract.methods.balanceOf(receiver).call();
+    expect(balance).to.equal('51');
+  });
+
   itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
       name: 'token name',
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -193,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" }],