difftreelog
Merge pull request #483 from UniqueNetwork/feature/mint-for-fungible-token
in: master
12 files changed
Cargo.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",
crates/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
crates/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"
crates/evm-coder/src/abi.rsdiffbeforeafterboth1// 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}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}pallets/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
pallets/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"
pallets/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>),
)
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/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
{}
tests/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
{}
tests/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',
tests/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" }],