difftreelog
Merge pull request #483 from UniqueNetwork/feature/mint-for-fungible-token
in: master
12 files changed
Cargo.lockdiffbeforeafterboth216921692170[[package]]2170[[package]]2171name = "evm-coder"2171name = "evm-coder"2172version = "0.1.1"2172version = "0.1.3"2173dependencies = [2173dependencies = [2174 "ethereum",2174 "ethereum",2175 "evm-coder-procedural",2175 "evm-coder-procedural",2178 "hex-literal",2178 "hex-literal",2179 "impl-trait-for-tuples",2179 "impl-trait-for-tuples",2180 "primitive-types",2180 "primitive-types",2181 "sp-std",2181]2182]218221832183[[package]]2184[[package]]574857495749[[package]]5750[[package]]5750name = "pallet-fungible"5751name = "pallet-fungible"5751version = "0.1.4"5752version = "0.1.5"5752dependencies = [5753dependencies = [5753 "ethereum",5754 "ethereum",5754 "evm-coder",5755 "evm-coder",crates/evm-coder/CHANGELOG.mddiffbeforeafterboth223All notable changes to this project will be documented in this file.3All notable changes to this project will be documented in this file.445## [0.1.3] - 2022-08-2967### Fixed89 - Parsing simple values.105<!-- bureaucrate goes here -->11<!-- bureaucrate goes here -->6## [v0.1.2] 2022-08-1912## [v0.1.2] 2022-08-19713212722- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf828- build: Upgrade polkadot to v0.9.26 85515e54c4ca1b82a2630034e55dcc804c643bf8232924- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b30- build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b31crates/evm-coder/Cargo.tomldiffbeforeafterboth1[package]1[package]2name = "evm-coder"2name = "evm-coder"3version = "0.1.1"3version = "0.1.3"4license = "GPLv3"4license = "GPLv3"5edition = "2021"5edition = "2021"6611primitive-types = { version = "0.11.1", default-features = false }11primitive-types = { version = "0.11.1", default-features = false }12# Evm doesn't have reexports for log and others12# Evm doesn't have reexports for log and others13ethereum = { version = "0.12.0", default-features = false }13ethereum = { version = "0.12.0", default-features = false }14sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.27" }14# Error types for execution15# Error types for execution15evm-core = { default-features = false, git = "https://github.com/uniquenetwork/evm", branch = "unique-polkadot-v0.9.27" }16evm-core = { default-features = false , git = "https://github.com/uniquenetwork/evm", branch = "unique-polkadot-v0.9.27" }16# We have tuple-heavy code in solidity.rs17# We have tuple-heavy code in solidity.rscrates/evm-coder/src/abi.rsdiffbeforeafterboth313132const ABI_ALIGNMENT: usize = 32;32const ABI_ALIGNMENT: usize = 32;3334trait TypeHelper {35 fn is_dynamic() -> bool;36}333734/// View into RLP data, which provides method to read typed items from it38/// View into RLP data, which provides method to read typed items from it35#[derive(Clone)]39#[derive(Clone)]77 return Err(Error::Error(ExitError::OutOfOffset));81 return Err(Error::Error(ExitError::OutOfOffset));78 }82 }79 let mut block = [0; S];83 let mut block = [0; S];80 // Verify padding is empty81 if !buf[pad_start..pad_size].iter().all(|&v| v == 0) {84 let is_pad_zeroed = buf[pad_start..pad_size].iter().all(|&v| v == 0);85 if !is_pad_zeroed {82 return Err(Error::Error(ExitError::InvalidRange));86 return Err(Error::Error(ExitError::InvalidRange));83 }87 }84 block.copy_from_slice(&buf[block_start..block_size]);88 block.copy_from_slice(&buf[block_start..block_size]);133137134 /// Read [`Vec<u8>`] at current position, then advance138 /// Read [`Vec<u8>`] at current position, then advance135 pub fn bytes(&mut self) -> Result<Vec<u8>> {139 pub fn bytes(&mut self) -> Result<Vec<u8>> {136 let mut subresult = self.subresult()?;140 let mut subresult = self.subresult(None)?;137 let length = subresult.uint32()? as usize;141 let length = subresult.uint32()? as usize;138 if subresult.buf.len() < subresult.offset + length {142 if subresult.buf.len() < subresult.offset + length {139 return Err(Error::Error(ExitError::OutOfOffset));143 return Err(Error::Error(ExitError::OutOfOffset));179 }183 }180184181 /// Slice recursive buffer, advance one word for buffer offset185 /// Slice recursive buffer, advance one word for buffer offset186 /// If `size` is [`None`] then [`Self::offset`] and [`Self::subresult_offset`] evals from [`Self::buf`].182 fn subresult(&mut self) -> Result<AbiReader<'i>> {187 fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {188 let subresult_offset = self.subresult_offset;183 let offset = self.uint32()? as usize;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 };196184 if offset + self.subresult_offset > self.buf.len() {197 if offset + self.subresult_offset > self.buf.len() {185 return Err(Error::Error(ExitError::InvalidRange));198 return Err(Error::Error(ExitError::InvalidRange));186 }199 }200201 let new_offset = offset + subresult_offset;187 Ok(AbiReader {202 Ok(AbiReader {188 buf: self.buf,203 buf: self.buf,189 subresult_offset: offset + self.subresult_offset,204 subresult_offset: new_offset,190 offset: offset + self.subresult_offset,205 offset: new_offset,191 })206 })192 }207 }193208319 /// Read item from current position, advanding decoder334 /// Read item from current position, advanding decoder320 fn abi_read(&mut self) -> Result<T>;335 fn abi_read(&mut self) -> Result<T>;336337 /// Size for type aligned to [`ABI_ALIGNMENT`].338 fn size() -> usize;321}339}322340323macro_rules! impl_abi_readable {341macro_rules! impl_abi_readable {324 ($ty:ty, $method:ident) => {342 ($ty:ty, $method:ident, $dynamic:literal) => {343 impl TypeHelper for $ty {344 fn is_dynamic() -> bool {345 $dynamic346 }347 }325 impl AbiRead<$ty> for AbiReader<'_> {348 impl AbiRead<$ty> for AbiReader<'_> {326 fn abi_read(&mut self) -> Result<$ty> {349 fn abi_read(&mut self) -> Result<$ty> {327 self.$method()350 self.$method()328 }351 }352353 fn size() -> usize {354 ABI_ALIGNMENT355 }329 }356 }330 };357 };331}358}332359333impl_abi_readable!(u8, uint8);360impl_abi_readable!(u8, uint8, false);334impl_abi_readable!(u32, uint32);361impl_abi_readable!(u32, uint32, false);335impl_abi_readable!(u64, uint64);362impl_abi_readable!(u64, uint64, false);336impl_abi_readable!(u128, uint128);363impl_abi_readable!(u128, uint128, false);337impl_abi_readable!(U256, uint256);364impl_abi_readable!(U256, uint256, false);338impl_abi_readable!([u8; 4], bytes4);365impl_abi_readable!([u8; 4], bytes4, false);339impl_abi_readable!(H160, address);366impl_abi_readable!(H160, address, false);340impl_abi_readable!(Vec<u8>, bytes);367impl_abi_readable!(Vec<u8>, bytes, true);341impl_abi_readable!(bool, bool);368impl_abi_readable!(bool, bool, true);342impl_abi_readable!(string, string);369impl_abi_readable!(string, string, true);343370344mod sealed {371mod sealed {345 /// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead372 /// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead355 Self: AbiRead<R>,382 Self: AbiRead<R>,356{383{357 fn abi_read(&mut self) -> Result<Vec<R>> {384 fn abi_read(&mut self) -> Result<Vec<R>> {358 let mut sub = self.subresult()?;385 let mut sub = self.subresult(None)?;359 let size = sub.uint32()? as usize;386 let size = sub.uint32()? as usize;360 sub.subresult_offset = sub.offset;387 sub.subresult_offset = sub.offset;361 let mut out = Vec::with_capacity(size);388 let mut out = Vec::with_capacity(size);365 Ok(out)392 Ok(out)366 }393 }394395 fn size() -> usize {396 ABI_ALIGNMENT397 }367}398}368399369macro_rules! impl_tuples {400macro_rules! impl_tuples {370 ($($ident:ident)+) => {401 ($($ident:ident)+) => {402 impl<$($ident: TypeHelper,)+> TypeHelper for ($($ident,)+) {403 fn is_dynamic() -> bool {404 false405 $(406 || <$ident>::is_dynamic()407 )*408 }409 }371 impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}410 impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}372 impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>411 impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>373 where412 where374 $(Self: AbiRead<$ident>),+413 $(414 Self: AbiRead<$ident>,415 )+416 ($($ident,)+): TypeHelper,375 {417 {376 fn abi_read(&mut self) -> Result<($($ident,)+)> {418 fn abi_read(&mut self) -> Result<($($ident,)+)> {419 let size = if !<($($ident,)+)>::is_dynamic() { Some(<Self as AbiRead<($($ident,)+)>>::size()) } else { None };377 let mut subresult = self.subresult()?;420 let mut subresult = self.subresult(size)?;378 Ok((421 Ok((379 $(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+422 $(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+380 ))423 ))381 }424 }425426 fn size() -> usize {427 0 $(+ <AbiReader<'_> as AbiRead<$ident>>::size())+428 }382 }429 }383 #[allow(non_snake_case)]430 #[allow(non_snake_case)]384 impl<$($ident),+> AbiWrite for &($($ident,)+)431 impl<$($ident),+> AbiWrite for &($($ident,)+)535 assert_eq!(encoded, alternative_encoded);582 assert_eq!(encoded, alternative_encoded);536583537 let mut decoder = AbiReader::new(&encoded);584 let mut decoder = AbiReader::new(&encoded);538 assert_eq!(decoder.bool().unwrap(), true);585 assert!(decoder.bool().unwrap());539 assert_eq!(decoder.string().unwrap(), "test");586 assert_eq!(decoder.string().unwrap(), "test");540 }587 }541588605 );652 );606 }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 }607}699}608700pallets/fungible/CHANGELOG.mddiffbeforeafterboth223All notable changes to this project will be documented in this file.3All notable changes to this project will be documented in this file.4456## [0.1.5] - 2022-08-2978### Added910 - Implementation of `mint` and `mint_bulk` methods for ERC20 API.115## [v0.1.4] - 2022-08-2412## [v0.1.4] - 2022-08-246137### Change14### Changepallets/fungible/Cargo.tomldiffbeforeafterboth1[package]1[package]2name = "pallet-fungible"2name = "pallet-fungible"3version = "0.1.4"3version = "0.1.5"4license = "GPLv3"4license = "GPLv3"5edition = "2021"5edition = "2021"66pallets/fungible/src/erc.rsdiffbeforeafterboth129 }129 }130}130}131132#[solidity_interface(name = ERC20Mintable)]133impl<T: Config> FungibleHandle<T> {134 /// Mint tokens for `to` account.135 /// @param to account that will receive minted tokens136 /// @param amount amount of tokens to mint137 #[weight(<SelfWeightOf<T>>::create_item())]138 fn mint(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {139 let caller = T::CrossAccountId::from_eth(caller);140 let to = T::CrossAccountId::from_eth(to);141 let amount = amount.try_into().map_err(|_| "amount overflow")?;142 let budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145 <Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)146 .map_err(dispatch_to_evm::<T>)?;147 Ok(true)148 }149}131150132#[solidity_interface(name = ERC20UniqueExtensions)]151#[solidity_interface(name = ERC20UniqueExtensions)]133impl<T: Config> FungibleHandle<T> {152impl<T: Config> FungibleHandle<T> {153 /// Burn tokens from account154 /// @dev Function that burns an `amount` of the tokens of a given account,155 /// deducting from the sender's allowance for said account.156 /// @param from The account whose tokens will be burnt.157 /// @param amount The amount that will be burnt.134 #[weight(<SelfWeightOf<T>>::burn_from())]158 #[weight(<SelfWeightOf<T>>::burn_from())]135 fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {159 fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {136 let caller = T::CrossAccountId::from_eth(caller);160 let caller = T::CrossAccountId::from_eth(caller);145 Ok(true)169 Ok(true)146 }170 }171172 /// Mint tokens for multiple accounts.173 /// @param amounts array of pairs of account address and amount174 #[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]175 fn mint_bulk(&mut self, caller: caller, amounts: Vec<(address, uint256)>) -> Result<bool> {176 let caller = T::CrossAccountId::from_eth(caller);177 let budget = self178 .recorder179 .weight_calls_budget(<StructureWeight<T>>::find_parent());180 let amounts = amounts181 .into_iter()182 .map(|(to, amount)| {183 Ok((184 T::CrossAccountId::from_eth(to),185 amount.try_into().map_err(|_| "amount overflow")?,186 ))187 })188 .collect::<Result<_>>()?;189190 <Pallet<T>>::create_multiple_items(&self, &caller, amounts, &budget)191 .map_err(dispatch_to_evm::<T>)?;192 Ok(true)193 }147}194}148195149#[solidity_interface(196#[solidity_interface(150 name = UniqueFungible,197 name = UniqueFungible,151 is(198 is(152 ERC20,199 ERC20,200 ERC20Mintable,153 ERC20UniqueExtensions,201 ERC20UniqueExtensions,154 Collection(common_mut, CollectionHandle<T>),202 Collection(common_mut, CollectionHandle<T>),155 )203 )pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth350 }350 }351}351}352353/// @dev the ERC-165 identifier for this interface is 0x63034ac5354contract ERC20UniqueExtensions is Dummy, ERC165 {355 /// Burn tokens from account356 /// @dev Function that burns an `amount` of the tokens of a given account,357 /// deducting from the sender's allowance for said account.358 /// @param from The account whose tokens will be burnt.359 /// @param amount The amount that will be burnt.360 /// @dev EVM selector for this function is: 0x79cc6790,361 /// or in textual repr: burnFrom(address,uint256)362 function burnFrom(address from, uint256 amount) public returns (bool) {363 require(false, stub_error);364 from;365 amount;366 dummy = 0;367 return false;368 }369370 /// Mint tokens for multiple accounts.371 /// @param amounts array of pairs of account address and amount372 /// @dev EVM selector for this function is: 0x1acf2d55,373 /// or in textual repr: mintBulk((address,uint256)[])374 function mintBulk(Tuple6[] memory amounts) public returns (bool) {375 require(false, stub_error);376 amounts;377 dummy = 0;378 return false;379 }380}352381353/// @dev anonymous struct382/// @dev anonymous struct354struct Tuple6 {383struct Tuple6 {355 address field_0;384 address field_0;356 uint256 field_1;385 uint256 field_1;357}386}358387359/// @dev the ERC-165 identifier for this interface is 0x79cc6790388/// @dev the ERC-165 identifier for this interface is 0x40c10f19360contract ERC20UniqueExtensions is Dummy, ERC165 {389contract ERC20Mintable is Dummy, ERC165 {390 /// Mint tokens for `to` account.391 /// @param to account that will receive minted tokens392 /// @param amount amount of tokens to mint361 /// @dev EVM selector for this function is: 0x79cc6790,393 /// @dev EVM selector for this function is: 0x40c10f19,362 /// or in textual repr: burnFrom(address,uint256)394 /// or in textual repr: mint(address,uint256)363 function burnFrom(address from, uint256 amount) public returns (bool) {395 function mint(address to, uint256 amount) public returns (bool) {364 require(false, stub_error);396 require(false, stub_error);365 from;397 to;366 amount;398 amount;367 dummy = 0;399 dummy = 0;368 return false;400 return false;476 Dummy,508 Dummy,477 ERC165,509 ERC165,478 ERC20,510 ERC20,511 ERC20Mintable,479 ERC20UniqueExtensions,512 ERC20UniqueExtensions,480 Collection513 Collection481{}514{}tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth225 function setOwnerSubstrate(uint256 newOwner) external;225 function setOwnerSubstrate(uint256 newOwner) external;226}226}227228/// @dev the ERC-165 identifier for this interface is 0x63034ac5229interface ERC20UniqueExtensions is Dummy, ERC165 {230 /// Burn tokens from account231 /// @dev Function that burns an `amount` of the tokens of a given account,232 /// deducting from the sender's allowance for said account.233 /// @param from The account whose tokens will be burnt.234 /// @param amount The amount that will be burnt.235 /// @dev EVM selector for this function is: 0x79cc6790,236 /// or in textual repr: burnFrom(address,uint256)237 function burnFrom(address from, uint256 amount) external returns (bool);238239 /// Mint tokens for multiple accounts.240 /// @param amounts array of pairs of account address and amount241 /// @dev EVM selector for this function is: 0x1acf2d55,242 /// or in textual repr: mintBulk((address,uint256)[])243 function mintBulk(Tuple6[] memory amounts) external returns (bool);244}227245228/// @dev anonymous struct246/// @dev anonymous struct229struct Tuple6 {247struct Tuple6 {230 address field_0;248 address field_0;231 uint256 field_1;249 uint256 field_1;232}250}233251234/// @dev the ERC-165 identifier for this interface is 0x79cc6790252/// @dev the ERC-165 identifier for this interface is 0x40c10f19235interface ERC20UniqueExtensions is Dummy, ERC165 {253interface ERC20Mintable is Dummy, ERC165 {254 /// Mint tokens for `to` account.255 /// @param to account that will receive minted tokens256 /// @param amount amount of tokens to mint236 /// @dev EVM selector for this function is: 0x79cc6790,257 /// @dev EVM selector for this function is: 0x40c10f19,237 /// or in textual repr: burnFrom(address,uint256)258 /// or in textual repr: mint(address,uint256)238 function burnFrom(address from, uint256 amount) external returns (bool);259 function mint(address to, uint256 amount) external returns (bool);239}260}240261241/// @dev inlined interface262/// @dev inlined interface298 Dummy,319 Dummy,299 ERC165,320 ERC165,300 ERC20,321 ERC20,322 ERC20Mintable,301 ERC20UniqueExtensions,323 ERC20UniqueExtensions,302 Collection324 Collection303{}325{}tests/src/eth/fungible.test.tsdiffbeforeafterboth14// You should have received a copy of the GNU General Public License14// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.161617import {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';17import {approveExpectSuccess, createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';19import fungibleAbi from './fungibleAbi.json';19import fungibleAbi from './fungibleAbi.json';20import {expect} from 'chai';20import {expect} from 'chai';21import {submitTransactionAsync} from '../substrate/substrate-api';212222describe('Fungible: Information getting', () => {23describe('Fungible: Information getting', () => {23 itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {24 itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {58});59});596060describe('Fungible: Plain calls', () => {61describe('Fungible: Plain calls', () => {62 itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {63 const alice = privateKeyWrapper('//Alice');64 const collection = await createCollection(api, alice, {65 name: 'token name',66 mode: {type: 'Fungible', decimalPoints: 0},67 });6869 const receiver = createEthAccount(web3);7071 const collectionIdAddress = collectionIdToAddress(collection.collectionId);72 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);73 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});74 await submitTransactionAsync(alice, changeAdminTx);7576 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});77 const result = await collectionContract.methods.mint(receiver, 100).send();78 const events = normalizeEvents(result.events);79 80 expect(events).to.be.deep.equal([81 {82 address: collectionIdAddress,83 event: 'Transfer',84 args: {85 from: '0x0000000000000000000000000000000000000000',86 to: receiver,87 value: '100',88 },89 },90 ]);91 });9293 itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {94 const alice = privateKeyWrapper('//Alice');95 const collection = await createCollection(api, alice, {96 name: 'token name',97 mode: {type: 'Fungible', decimalPoints: 0},98 });99100 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);101 const receiver1 = createEthAccount(web3);102 const receiver2 = createEthAccount(web3);103 const receiver3 = createEthAccount(web3);104105 const collectionIdAddress = collectionIdToAddress(collection.collectionId);106 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});107 await submitTransactionAsync(alice, changeAdminTx);108109 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});110 const result = await collectionContract.methods.mintBulk([111 [receiver1, 10],112 [receiver2, 20],113 [receiver3, 30],114 ]).send();115 const events = normalizeEvents(result.events);116117 expect(events).to.be.deep.contain({118 address:collectionIdAddress,119 event: 'Transfer',120 args: {121 from: '0x0000000000000000000000000000000000000000',122 to: receiver1,123 value: '10',124 },125 });126 127 expect(events).to.be.deep.contain({128 address:collectionIdAddress,129 event: 'Transfer',130 args: {131 from: '0x0000000000000000000000000000000000000000',132 to: receiver2,133 value: '20',134 },135 });136 137 expect(events).to.be.deep.contain({138 address:collectionIdAddress,139 event: 'Transfer',140 args: {141 from: '0x0000000000000000000000000000000000000000',142 to: receiver3,143 value: '30',144 },145 });146 });147148 itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {149 const alice = privateKeyWrapper('//Alice');150 const collection = await createCollection(api, alice, {151 name: 'token name',152 mode: {type: 'Fungible', decimalPoints: 0},153 });154155 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);156 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});157 await submitTransactionAsync(alice, changeAdminTx);158 const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);159160 const collectionIdAddress = collectionIdToAddress(collection.collectionId);161 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});162 await collectionContract.methods.mint(receiver, 100).send();163164 const result = await collectionContract.methods.burnFrom(receiver, 49).send({from: receiver});165 166 const events = normalizeEvents(result.events);167168 expect(events).to.be.deep.equal([169 {170 address: collectionIdAddress,171 event: 'Transfer',172 args: {173 from: receiver,174 to: '0x0000000000000000000000000000000000000000',175 value: '49',176 },177 },178 ]);179180 const balance = await collectionContract.methods.balanceOf(receiver).call();181 expect(balance).to.equal('51');182 });18361 itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {184 itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {62 const collection = await createCollectionExpectSuccess({185 const collection = await createCollectionExpectSuccess({tests/src/eth/fungibleAbi.jsondiffbeforeafterboth192 "stateMutability": "view",192 "stateMutability": "view",193 "type": "function"193 "type": "function"194 },194 },195 {196 "inputs": [197 { "internalType": "address", "name": "to", "type": "address" },198 { "internalType": "uint256", "name": "amount", "type": "uint256" }199 ],200 "name": "mint",201 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],202 "stateMutability": "nonpayable",203 "type": "function"204 },205 {206 "inputs": [207 {208 "components": [209 { "internalType": "address", "name": "field_0", "type": "address" },210 { "internalType": "uint256", "name": "field_1", "type": "uint256" }211 ],212 "internalType": "struct Tuple6[]",213 "name": "amounts",214 "type": "tuple[]"215 }216 ],217 "name": "mintBulk",218 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],219 "stateMutability": "nonpayable",220 "type": "function"221 },195 {222 {196 "inputs": [],223 "inputs": [],197 "name": "name",224 "name": "name",