difftreelog
Merge pull request #869 from UniqueNetwork/feature/generalize_2_methods
in: master
Feature/generalize_2_methods
34 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6361,7 +6361,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.9"
+version = "0.1.10"
dependencies = [
"evm-coder",
"frame-benchmarking",
@@ -6774,7 +6774,7 @@
[[package]]
name = "pallet-refungible"
-version = "0.2.12"
+version = "0.2.13"
dependencies = [
"evm-coder",
"frame-benchmarking",
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -18,7 +18,10 @@
use alloc::format;
use sp_std::{vec, vec::Vec};
-use evm_coder::{AbiCoder, types::Address};
+use evm_coder::{
+ AbiCoder,
+ types::{Address, String},
+};
pub use pallet_evm::{Config, account::CrossAccountId};
use sp_core::{H160, U256};
use up_data_structs::CollectionId;
@@ -390,6 +393,16 @@
}
}
+/// Data for creation token with uri.
+#[derive(Debug, AbiCoder)]
+pub struct TokenUri {
+ /// Id of new token.
+ pub id: U256,
+
+ /// Uri of new token.
+ pub uri: String,
+}
+
/// Nested collections.
#[derive(Debug, Default, AbiCoder)]
pub struct CollectionNesting {
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -66,10 +66,11 @@
ensure,
traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},
dispatch::Pays,
- transactional,
+ transactional, fail,
};
use pallet_evm::GasWeightMapping;
use up_data_structs::{
+ AccessMode,
COLLECTION_NUMBER_LIMIT,
Collection,
RpcCollection,
@@ -124,6 +125,8 @@
pub use pallet::*;
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+
+use crate::erc::CollectionHelpersEvents;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod dispatch;
@@ -1264,6 +1267,133 @@
Ok(())
}
+ /// A batch operation to add, edit or remove properties for a token.
+ /// It sets or removes a token's properties according to
+ /// `properties_updates` contents:
+ /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`
+ /// * removes a property under the <key> if the value is `None` `(<key>, None)`.
+ ///
+ /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
+ /// - `is_token_create`: Indicates that method is called during token initialization.
+ /// Allows to bypass ownership check.
+ ///
+ /// All affected properties should have `mutable` permission
+ /// to be **deleted** or to be **set more than once**,
+ /// and the sender should have permission to edit those properties.
+ ///
+ /// This function fires an event for each property change.
+ /// In case of an error, all the changes (including the events) will be reverted
+ /// since the function is transactional.
+ pub fn modify_token_properties(
+ collection: &CollectionHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
+ is_token_create: bool,
+ mut stored_properties: Properties,
+ is_token_owner: impl Fn() -> Result<bool, DispatchError>,
+ set_token_properties: impl FnOnce(Properties),
+ ) -> DispatchResult {
+ let is_collection_admin = collection.is_owner_or_admin(sender);
+ let permissions = Self::property_permissions(collection.id);
+
+ let mut token_owner_result = None;
+ let mut is_token_owner = || -> Result<bool, DispatchError> {
+ *token_owner_result.get_or_insert_with(&is_token_owner)
+ };
+
+ for (key, value) in properties_updates {
+ let permission = permissions
+ .get(&key)
+ .cloned()
+ .unwrap_or_else(PropertyPermission::none);
+
+ let is_property_exists = stored_properties.get(&key).is_some();
+
+ match permission {
+ PropertyPermission { mutable: false, .. } if is_property_exists => {
+ return Err(<Error<T>>::NoPermission.into());
+ }
+
+ PropertyPermission {
+ collection_admin,
+ token_owner,
+ ..
+ } => {
+ //TODO: investigate threats during public minting.
+ let is_token_create =
+ is_token_create && (collection_admin || token_owner) && value.is_some();
+ if !(is_token_create
+ || (collection_admin && is_collection_admin)
+ || (token_owner && is_token_owner()?))
+ {
+ fail!(<Error<T>>::NoPermission);
+ }
+ }
+ }
+
+ match value {
+ Some(value) => {
+ stored_properties
+ .try_set(key.clone(), value)
+ .map_err(<Error<T>>::from)?;
+
+ Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));
+ }
+ None => {
+ stored_properties.remove(&key).map_err(<Error<T>>::from)?;
+
+ Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));
+ }
+ }
+
+ <PalletEvm<T>>::deposit_log(
+ CollectionHelpersEvents::TokenChanged {
+ collection_id: eth::collection_id_to_address(collection.id),
+ token_id: token_id.into(),
+ }
+ .to_log(T::ContractAddress::get()),
+ );
+ }
+
+ set_token_properties(stored_properties);
+
+ Ok(())
+ }
+
+ /// Sets or unsets the approval of a given operator.
+ ///
+ /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.
+ /// - `owner`: Token owner
+ /// - `operator`: Operator
+ /// - `approve`: Should operator status be granted or revoked?
+ pub fn set_allowance_for_all(
+ collection: &CollectionHandle<T>,
+ owner: &T::CrossAccountId,
+ operator: &T::CrossAccountId,
+ approve: bool,
+ set_allowance: impl FnOnce(),
+ log: evm_coder::ethereum::Log,
+ ) -> DispatchResult {
+ if collection.permissions.access() == AccessMode::AllowList {
+ collection.check_allowlist(owner)?;
+ collection.check_allowlist(operator)?;
+ }
+
+ Self::ensure_correct_receiver(operator)?;
+
+ set_allowance();
+
+ <PalletEvm<T>>::deposit_log(log);
+ Self::deposit_event(Event::ApprovedForAll(
+ collection.id,
+ owner.clone(),
+ operator.clone(),
+ approve,
+ ));
+ Ok(())
+ }
+
/// Set collection property.
///
/// * `collection` - Collection handler.
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.1.10] - 2023-02-01
+
+### Added
+
+- The functions `allowanceCross` to `ERC20UniqueExtensions` interface.
+
## [0.1.9] - 2022-12-01
### Added
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-fungible"
-version = "0.1.9"
+version = "0.1.10"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -19,6 +19,7 @@
extern crate alloc;
use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
use core::convert::TryInto;
+use evm_coder::AbiCoder;
use evm_coder::{
abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,
weight,
@@ -27,6 +28,7 @@
use pallet_common::{
CollectionHandle,
erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+ eth::CrossAddress,
};
use sp_std::vec::Vec;
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
@@ -57,6 +59,12 @@
},
}
+#[derive(AbiCoder, Debug)]
+pub struct AmountForAddress {
+ to: Address,
+ amount: U256,
+}
+
#[solidity_interface(name = ERC20, events(ERC20Events), expect_selector = 0x942e8b22)]
impl<T: Config> FungibleHandle<T> {
fn name(&self) -> Result<String> {
@@ -161,6 +169,17 @@
where
T::AccountId: From<[u8; 32]>,
{
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ fn allowance_cross(&self, owner: CrossAddress, spender: CrossAddress) -> Result<U256> {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let spender = spender.into_sub_cross_account::<T>()?;
+
+ Ok(<Allowance<T>>::get((self.id, owner, spender)).into())
+ }
+
/// @notice A description for the collection.
fn description(&self) -> Result<String> {
Ok(decode_utf16(self.description.iter().copied())
@@ -169,12 +188,7 @@
}
#[weight(<SelfWeightOf<T>>::create_item())]
- fn mint_cross(
- &mut self,
- caller: Caller,
- to: pallet_common::eth::CrossAddress,
- amount: U256,
- ) -> Result<bool> {
+ fn mint_cross(&mut self, caller: Caller, to: CrossAddress, amount: U256) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
let to = to.into_sub_cross_account::<T>()?;
let amount = amount.try_into().map_err(|_| "amount overflow")?;
@@ -190,7 +204,7 @@
fn approve_cross(
&mut self,
caller: Caller,
- spender: pallet_common::eth::CrossAddress,
+ spender: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -231,7 +245,7 @@
fn burn_from_cross(
&mut self,
caller: Caller,
- from: pallet_common::eth::CrossAddress,
+ from: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -249,14 +263,14 @@
/// 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, U256)>) -> Result<bool> {
+ fn mint_bulk(&mut self, caller: Caller, amounts: Vec<AmountForAddress>) -> 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)| {
+ .map(|AmountForAddress { to, amount }| {
Ok((
T::CrossAccountId::from_eth(to),
amount.try_into().map_err(|_| "amount overflow")?,
@@ -270,12 +284,7 @@
}
#[weight(<SelfWeightOf<T>>::transfer())]
- fn transfer_cross(
- &mut self,
- caller: Caller,
- to: pallet_common::eth::CrossAddress,
- amount: U256,
- ) -> Result<bool> {
+ fn transfer_cross(&mut self, caller: Caller, to: CrossAddress, amount: U256) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
let to = to.into_sub_cross_account::<T>()?;
let amount = amount.try_into().map_err(|_| "amount overflow")?;
@@ -291,8 +300,8 @@
fn transfer_from_cross(
&mut self,
caller: Caller,
- from: pallet_common::eth::CrossAddress,
- to: pallet_common::eth::CrossAddress,
+ from: CrossAddress,
+ to: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -165,8 +165,8 @@
pub type Allowance<T: Config> = StorageNMap<
Key = (
Key<Twox64Concat, CollectionId>,
- Key<Blake2_128, T::CrossAccountId>,
- Key<Blake2_128Concat, T::CrossAccountId>,
+ Key<Blake2_128, T::CrossAccountId>, // Owner
+ Key<Blake2_128Concat, T::CrossAccountId>, // Spender
),
Value = u128,
QueryKind = ValueQuery,
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
@@ -511,8 +511,22 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x65789571
+/// @dev the ERC-165 identifier for this interface is 0x85d7dea6
contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
/// or in textual repr: description()
@@ -576,7 +590,7 @@
/// @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(Tuple9[] memory amounts) public returns (bool) {
+ function mintBulk(AmountForAddress[] memory amounts) public returns (bool) {
require(false, stub_error);
amounts;
dummy = 0;
@@ -618,10 +632,9 @@
}
}
-/// @dev anonymous struct
-struct Tuple9 {
- address field_0;
- uint256 field_1;
+struct AmountForAddress {
+ address to;
+ uint256 amount;
}
/// @dev the ERC-165 identifier for this interface is 0x40c10f19
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -38,7 +38,7 @@
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
- eth,
+ eth::{self, TokenUri},
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::call;
@@ -948,7 +948,7 @@
&mut self,
caller: Caller,
to: Address,
- tokens: Vec<(U256, String)>,
+ tokens: Vec<TokenUri>,
) -> Result<bool> {
let key = key::url();
let caller = T::CrossAccountId::from_eth(caller);
@@ -961,7 +961,7 @@
.weight_calls_budget(<StructureWeight<T>>::find_parent());
let mut data = Vec::with_capacity(tokens.len());
- for (id, token_uri) in tokens {
+ for TokenUri { id, uri } in tokens {
let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
if id != expected_index {
return Err("item id should be next".into());
@@ -972,7 +972,7 @@
properties
.try_push(Property {
key: key.clone(),
- value: token_uri
+ value: uri
.into_bytes()
.try_into()
.map_err(|_| "token uri is too long")?,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -101,18 +101,18 @@
};
use up_data_structs::{
AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData,
- CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission,
- PropertyKey, PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty,
- TokenChild, AuxPropertyValue, PropertiesPermissionMap,
+ CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey,
+ PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,
+ AuxPropertyValue, PropertiesPermissionMap,
};
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
- eth::collection_id_to_address, erc::CollectionHelpersEvents,
+ eth::collection_id_to_address,
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
-use sp_core::{H160, Get};
+use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use core::ops::Deref;
@@ -578,10 +578,6 @@
}
/// A batch operation to add, edit or remove properties for a token.
- /// It sets or removes a token's properties according to
- /// `properties_updates` contents:
- /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`
- /// * removes a property under the <key> if the value is `None` `(<key>, None)`.
///
/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
/// - `is_token_create`: Indicates that method is called during token initialization.
@@ -603,97 +599,30 @@
is_token_create: bool,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let mut collection_admin_status = None;
- let mut token_owner_result = None;
-
- let mut is_collection_admin =
- || *collection_admin_status.get_or_insert_with(|| collection.is_owner_or_admin(sender));
+ let is_token_owner = || {
+ let is_owned = <PalletStructure<T>>::check_indirectly_owned(
+ sender.clone(),
+ collection.id,
+ token_id,
+ None,
+ nesting_budget,
+ )?;
- let mut is_token_owner = || {
- *token_owner_result.get_or_insert_with(|| -> Result<bool, DispatchError> {
- let is_owned = <PalletStructure<T>>::check_indirectly_owned(
- sender.clone(),
- collection.id,
- token_id,
- None,
- nesting_budget,
- )?;
-
- Ok(is_owned)
- })
+ Ok(is_owned)
};
-
- let mut stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
- let permissions = <PalletCommon<T>>::property_permissions(collection.id);
-
- for (key, value) in properties_updates {
- let permission = permissions
- .get(&key)
- .cloned()
- .unwrap_or_else(PropertyPermission::none);
-
- let is_property_exists = stored_properties.get(&key).is_some();
-
- match permission {
- PropertyPermission { mutable: false, .. } if is_property_exists => {
- return Err(<CommonError<T>>::NoPermission.into());
- }
-
- PropertyPermission {
- collection_admin,
- token_owner,
- ..
- } => {
- //TODO: investigate threats during public minting.
- if is_token_create && (collection_admin || token_owner) && value.is_some() {
- // Pass
- } else if collection_admin && is_collection_admin() {
- // Pass
- } else if token_owner && is_token_owner()? {
- // Pass
- } else {
- fail!(<CommonError<T>>::NoPermission);
- }
- }
- }
- match value {
- Some(value) => {
- stored_properties
- .try_set(key.clone(), value)
- .map_err(<CommonError<T>>::from)?;
-
- <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
- collection.id,
- token_id,
- key,
- ));
- }
- None => {
- stored_properties
- .remove(&key)
- .map_err(<CommonError<T>>::from)?;
-
- <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(
- collection.id,
- token_id,
- key,
- ));
- }
- }
-
- <PalletEvm<T>>::deposit_log(
- CollectionHelpersEvents::TokenChanged {
- collection_id: collection_id_to_address(collection.id),
- token_id: token_id.into(),
- }
- .to_log(T::ContractAddress::get()),
- );
- }
-
- <TokenProperties<T>>::set((collection.id, token_id), stored_properties);
+ let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
- Ok(())
+ <PalletCommon<T>>::modify_token_properties(
+ collection,
+ sender,
+ token_id,
+ properties_updates,
+ is_token_create,
+ stored_properties,
+ is_token_owner,
+ |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ )
}
/// Batch operation to add or edit properties for the token
@@ -1418,31 +1347,19 @@
operator: &T::CrossAccountId,
approve: bool,
) -> DispatchResult {
- if collection.permissions.access() == AccessMode::AllowList {
- collection.check_allowlist(owner)?;
- collection.check_allowlist(operator)?;
- }
-
- <PalletCommon<T>>::ensure_correct_receiver(operator)?;
-
- // =========
-
- <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve);
- <PalletEvm<T>>::deposit_log(
+ <PalletCommon<T>>::set_allowance_for_all(
+ collection,
+ owner,
+ operator,
+ approve,
+ || <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),
ERC721Events::ApprovalForAll {
owner: *owner.as_eth(),
operator: *operator.as_eth(),
approved: approve,
}
.to_log(collection_id_to_address(collection.id)),
- );
- <PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
- collection.id,
- owner.clone(),
- operator.clone(),
- approve,
- ));
- Ok(())
+ )
}
/// Tells whether the given `owner` approves the `operator`.
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -949,7 +949,7 @@
// /// @param tokens array of pairs of token ID and token URI for minted tokens
// /// @dev EVM selector for this function is: 0x36543006,
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- // function mintBulkWithTokenURI(address to, Tuple15[] memory tokens) public returns (bool) {
+ // function mintBulkWithTokenURI(address to, TokenUri[] memory tokens) public returns (bool) {
// require(false, stub_error);
// to;
// tokens;
@@ -981,10 +981,12 @@
}
}
-/// @dev anonymous struct
-struct Tuple15 {
- uint256 field_0;
- string field_1;
+/// Data for creation token with uri.
+struct TokenUri {
+ /// Id of new token.
+ uint256 id;
+ /// Uri of new token.
+ string uri;
}
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.2.13] - 2023-02-01
+
+### Added
+
+- The functions `allowanceCross` to `ERC20UniqueExtensions` interface.
+
## [0.2.12] - 2023-01-20
### Fixed
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-refungible"
-version = "0.2.12"
+version = "0.2.13"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -34,7 +34,7 @@
CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
Error as CommonError,
erc::{CommonEvmHandler, CollectionCall, static_property::key},
- eth,
+ eth::{self, TokenUri},
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm};
@@ -999,7 +999,7 @@
&mut self,
caller: Caller,
to: Address,
- tokens: Vec<(U256, String)>,
+ tokens: Vec<TokenUri>,
) -> Result<bool> {
let key = key::url();
let caller = T::CrossAccountId::from_eth(caller);
@@ -1017,7 +1017,7 @@
.collect::<BTreeMap<_, _>>()
.try_into()
.unwrap();
- for (id, token_uri) in tokens {
+ for TokenUri { id, uri } in tokens {
let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
if id != expected_index {
return Err("item id should be next".into());
@@ -1028,7 +1028,7 @@
properties
.try_push(Property {
key: key.clone(),
- value: token_uri
+ value: uri
.into_bytes()
.try_into()
.map_err(|_| "token uri is too long")?,
pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -31,7 +31,7 @@
use pallet_common::{
CommonWeightInfo,
erc::{CommonEvmHandler, PrecompileResult},
- eth::collection_id_to_address,
+ eth::{collection_id_to_address, CrossAddress},
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};
@@ -203,6 +203,17 @@
where
T::AccountId: From<[u8; 32]>,
{
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ fn allowance_cross(&self, owner: CrossAddress, spender: CrossAddress) -> Result<U256> {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let spender = spender.into_sub_cross_account::<T>()?;
+
+ Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())
+ }
+
/// @dev Function that burns an amount of the token of a given account,
/// deducting from the sender's allowance for said account.
/// @param from The account whose tokens will be burnt.
@@ -230,7 +241,7 @@
fn burn_from_cross(
&mut self,
caller: Caller,
- from: pallet_common::eth::CrossAddress,
+ from: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -256,7 +267,7 @@
fn approve_cross(
&mut self,
caller: Caller,
- spender: pallet_common::eth::CrossAddress,
+ spender: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -283,12 +294,7 @@
/// @param to The crossaccount to transfer to.
/// @param amount The amount to be transferred.
#[weight(<CommonWeights<T>>::transfer())]
- fn transfer_cross(
- &mut self,
- caller: Caller,
- to: pallet_common::eth::CrossAddress,
- amount: U256,
- ) -> Result<bool> {
+ fn transfer_cross(&mut self, caller: Caller, to: CrossAddress, amount: U256) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
let to = to.into_sub_cross_account::<T>()?;
let amount = amount.try_into().map_err(|_| "amount overflow")?;
@@ -309,8 +315,8 @@
fn transfer_from_cross(
&mut self,
caller: Caller,
- from: pallet_common::eth::CrossAddress,
- to: pallet_common::eth::CrossAddress,
+ from: CrossAddress,
+ to: CrossAddress,
amount: U256,
) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -92,22 +92,22 @@
use core::ops::Deref;
use evm_coder::ToLog;
-use frame_support::{ensure, fail, storage::with_transaction, transactional};
+use frame_support::{ensure, storage::with_transaction, transactional};
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_evm_coder_substrate::WithRecorder;
use pallet_common::{
CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
- Event as CommonEvent, Pallet as PalletCommon, erc::CollectionHelpersEvents,
+ Event as CommonEvent, Pallet as PalletCommon,
};
use pallet_structure::Pallet as PalletStructure;
-use sp_core::{Get, H160};
+use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
use up_data_structs::{
AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData,
mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,
- PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, TokenId,
- TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,
+ PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TrySetProperty,
+ PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,
};
pub use pallet::*;
@@ -240,13 +240,13 @@
QueryKind = ValueQuery,
>;
- /// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.
+ /// Spender set by a wallet owner that could perform certain transactions on all tokens in the wallet.
#[pallet::storage]
pub type CollectionAllowance<T: Config> = StorageNMap<
Key = (
Key<Twox64Concat, CollectionId>,
- Key<Blake2_128Concat, T::CrossAccountId>,
- Key<Blake2_128Concat, T::CrossAccountId>,
+ Key<Blake2_128Concat, T::CrossAccountId>, // Owner
+ Key<Blake2_128Concat, T::CrossAccountId>, // Spender
),
Value = bool,
QueryKind = ValueQuery,
@@ -541,7 +541,6 @@
is_token_create: bool,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_collection_admin = || collection.is_owner_or_admin(sender);
let is_token_owner = || -> Result<bool, DispatchError> {
let balance = collection.balance(sender.clone(), token_id);
let total_pieces: u128 =
@@ -560,77 +559,19 @@
Ok(is_bundle_owner)
};
-
- let mut stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
- let permissions = <PalletCommon<T>>::property_permissions(collection.id);
-
- for (key, value) in properties_updates {
- let permission = permissions
- .get(&key)
- .cloned()
- .unwrap_or_else(PropertyPermission::none);
-
- let is_property_exists = stored_properties.get(&key).is_some();
-
- match permission {
- PropertyPermission { mutable: false, .. } if is_property_exists => {
- return Err(<CommonError<T>>::NoPermission.into());
- }
- PropertyPermission {
- collection_admin,
- token_owner,
- ..
- } => {
- //TODO: investigate threats during public minting.
- let is_token_create =
- is_token_create && (collection_admin || token_owner) && value.is_some();
- if !(is_token_create
- || (collection_admin && is_collection_admin())
- || (token_owner && is_token_owner()?))
- {
- fail!(<CommonError<T>>::NoPermission);
- }
- }
- }
-
- match value {
- Some(value) => {
- stored_properties
- .try_set(key.clone(), value)
- .map_err(<CommonError<T>>::from)?;
-
- <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
- collection.id,
- token_id,
- key,
- ));
- }
- None => {
- stored_properties
- .remove(&key)
- .map_err(<CommonError<T>>::from)?;
-
- <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(
- collection.id,
- token_id,
- key,
- ));
- }
- }
-
- <PalletEvm<T>>::deposit_log(
- CollectionHelpersEvents::TokenChanged {
- collection_id: collection_id_to_address(collection.id),
- token_id: token_id.into(),
- }
- .to_log(T::ContractAddress::get()),
- );
- }
+ let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
- <TokenProperties<T>>::set((collection.id, token_id), stored_properties);
-
- Ok(())
+ <PalletCommon<T>>::modify_token_properties(
+ collection,
+ sender,
+ token_id,
+ properties_updates,
+ is_token_create,
+ stored_properties,
+ is_token_owner,
+ |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
+ )
}
pub fn set_token_properties(
@@ -1462,43 +1403,31 @@
pub fn set_allowance_for_all(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
- operator: &T::CrossAccountId,
+ spender: &T::CrossAccountId,
approve: bool,
) -> DispatchResult {
- if collection.permissions.access() == AccessMode::AllowList {
- collection.check_allowlist(owner)?;
- collection.check_allowlist(operator)?;
- }
-
- <PalletCommon<T>>::ensure_correct_receiver(operator)?;
-
- // =========
-
- <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve);
- <PalletEvm<T>>::deposit_log(
+ <PalletCommon<T>>::set_allowance_for_all(
+ collection,
+ owner,
+ spender,
+ approve,
+ || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),
ERC721Events::ApprovalForAll {
owner: *owner.as_eth(),
- operator: *operator.as_eth(),
+ operator: *spender.as_eth(),
approved: approve,
}
.to_log(collection_id_to_address(collection.id)),
- );
- <PalletCommon<T>>::deposit_event(CommonEvent::ApprovedForAll(
- collection.id,
- owner.clone(),
- operator.clone(),
- approve,
- ));
- Ok(())
+ )
}
/// Tells whether the given `owner` approves the `operator`.
pub fn allowance_for_all(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
- operator: &T::CrossAccountId,
+ spender: &T::CrossAccountId,
) -> bool {
- <CollectionAllowance<T>>::get((collection.id, owner, operator))
+ <CollectionAllowance<T>>::get((collection.id, owner, spender))
}
pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -938,7 +938,7 @@
// /// @param tokens array of pairs of token ID and token URI for minted tokens
// /// @dev EVM selector for this function is: 0x36543006,
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- // function mintBulkWithTokenURI(address to, Tuple14[] memory tokens) public returns (bool) {
+ // function mintBulkWithTokenURI(address to, TokenUri[] memory tokens) public returns (bool) {
// require(false, stub_error);
// to;
// tokens;
@@ -982,10 +982,12 @@
}
}
-/// @dev anonymous struct
-struct Tuple14 {
- uint256 field_0;
- string field_1;
+/// Data for creation token with uri.
+struct TokenUri {
+ /// Id of new token.
+ uint256 id;
+ /// Uri of new token.
+ string uri;
}
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
pallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
@@ -36,8 +36,22 @@
}
}
-/// @dev the ERC-165 identifier for this interface is 0xe17a7d2b
+/// @dev the ERC-165 identifier for this interface is 0x01d536fc
contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+
// /// @dev Function that burns an amount of the token of a given account,
// /// deducting from the sender's allowance for said account.
// /// @param from The account whose tokens will be burnt.
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
runtime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -341,7 +341,9 @@
ERC165Call(_, _) => None,
// Not sponsored
- BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => None,
+ AllowanceCross { .. } | BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => {
+ None
+ }
TransferCross { .. } | TransferFromCross { .. } => {
let RefungibleTokenHandle(handle, token_id) = token;
tests/src/eth/abi/fungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -101,6 +101,32 @@
{ "internalType": "uint256", "name": "sub", "type": "uint256" }
],
"internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "spender",
+ "type": "tuple"
+ }
+ ],
+ "name": "allowanceCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
"name": "user",
"type": "tuple"
}
@@ -408,10 +434,10 @@
"inputs": [
{
"components": [
- { "internalType": "address", "name": "field_0", "type": "address" },
- { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
],
- "internalType": "struct Tuple9[]",
+ "internalType": "struct AmountForAddress[]",
"name": "amounts",
"type": "tuple[]"
}
tests/src/eth/abi/reFungibleToken.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungibleToken.json
+++ b/tests/src/eth/abi/reFungibleToken.json
@@ -61,6 +61,32 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "spender",
+ "type": "tuple"
+ }
+ ],
+ "name": "allowanceCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -353,8 +353,16 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x65789571
+/// @dev the ERC-165 identifier for this interface is 0x85d7dea6
interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) external view returns (uint256);
+
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
/// or in textual repr: description()
@@ -390,7 +398,7 @@
/// @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(Tuple9[] memory amounts) external returns (bool);
+ function mintBulk(AmountForAddress[] memory amounts) external returns (bool);
/// @dev EVM selector for this function is: 0x2ada85ff,
/// or in textual repr: transferCross((address,uint256),uint256)
@@ -410,10 +418,9 @@
function collectionHelperAddress() external view returns (address);
}
-/// @dev anonymous struct
-struct Tuple9 {
- address field_0;
- uint256 field_1;
+struct AmountForAddress {
+ address to;
+ uint256 amount;
}
/// @dev the ERC-165 identifier for this interface is 0x40c10f19
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -644,7 +644,7 @@
// /// @param tokens array of pairs of token ID and token URI for minted tokens
// /// @dev EVM selector for this function is: 0x36543006,
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- // function mintBulkWithTokenURI(address to, Tuple13[] memory tokens) external returns (bool);
+ // function mintBulkWithTokenURI(address to, TokenUri[] memory tokens) external returns (bool);
/// @notice Function to mint a token.
/// @param to The new owner crossAccountId
@@ -660,10 +660,12 @@
function collectionHelperAddress() external view returns (address);
}
-/// @dev anonymous struct
-struct Tuple13 {
- uint256 field_0;
- string field_1;
+/// Data for creation token with uri.
+struct TokenUri {
+ /// Id of new token.
+ uint256 id;
+ /// Uri of new token.
+ string uri;
}
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -638,7 +638,7 @@
// /// @param tokens array of pairs of token ID and token URI for minted tokens
// /// @dev EVM selector for this function is: 0x36543006,
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- // function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool);
+ // function mintBulkWithTokenURI(address to, TokenUri[] memory tokens) external returns (bool);
/// @notice Function to mint a token.
/// @param to The new owner crossAccountId
@@ -661,10 +661,12 @@
function collectionHelperAddress() external view returns (address);
}
-/// @dev anonymous struct
-struct Tuple12 {
- uint256 field_0;
- string field_1;
+/// Data for creation token with uri.
+struct TokenUri {
+ /// Id of new token.
+ uint256 id;
+ /// Uri of new token.
+ string uri;
}
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
tests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungibleToken.sol
+++ b/tests/src/eth/api/UniqueRefungibleToken.sol
@@ -23,8 +23,16 @@
function parentTokenId() external view returns (uint256);
}
-/// @dev the ERC-165 identifier for this interface is 0xe17a7d2b
+/// @dev the ERC-165 identifier for this interface is 0x01d536fc
interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) external view returns (uint256);
+
// /// @dev Function that burns an amount of the token of a given account,
// /// deducting from the sender's allowance for said account.
// /// @param from The account whose tokens will be burnt.
tests/src/eth/fungible.test.tsdiffbeforeafterboth1// 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/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Plain calls', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;23 let owner: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 donor = await privateKey({filename: __filename});28 [alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);29 });30 });3132 [33 'substrate' as const,34 'ethereum' as const,35 ].map(testCase => {36 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {37 // 1. Create receiver depending on the test case:38 const receiverEth = helper.eth.createAccount();39 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);40 const receiverSub = owner;41 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(owner);4243 const ethOwner = await helper.eth.createAccountWithBalance(donor);44 const collection = await helper.ft.mintCollection(alice);45 await collection.addAdmin(alice, {Ethereum: ethOwner});4647 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);48 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);4950 // 2. Mint tokens:51 const result = await collectionEvm.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, 100).send();5253 const event = result.events.Transfer;54 expect(event.address).to.equal(collectionAddress);55 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');56 expect(event.returnValues.to).to.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(receiverSub.address));57 expect(event.returnValues.value).to.equal('100');5859 // 3. Get balance depending on the test case:60 let balance;61 if (testCase === 'ethereum') balance = await collection.getBalance({Ethereum: receiverEth});62 else if (testCase === 'substrate') balance = await collection.getBalance({Substrate: receiverSub.address});63 // 3.1 Check balance:64 expect(balance).to.eq(100n);65 });66 });6768 itEth('Can perform mintBulk()', async ({helper}) => {69 const owner = await helper.eth.createAccountWithBalance(donor);70 const bulkSize = 3;71 const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());72 const collection = await helper.ft.mintCollection(alice);73 await collection.addAdmin(alice, {Ethereum: owner});7475 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);76 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7778 const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (79 [receivers[i], (i + 1) * 10]80 ))).send();81 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);82 for (let i = 0; i < bulkSize; i++) {83 const event = events[i];84 expect(event.address).to.equal(collectionAddress);85 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');86 expect(event.returnValues.to).to.equal(receivers[i]);87 expect(event.returnValues.value).to.equal(String(10 * (i + 1)));88 }89 });9091 // Soft-deprecated92 itEth('Can perform burn()', async ({helper}) => {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = await helper.eth.createAccountWithBalance(donor);95 const collection = await helper.ft.mintCollection(alice);96 await collection.addAdmin(alice, {Ethereum: owner});9798 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);99 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);100 await contract.methods.mint(receiver, 100).send();101102 const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});103104 const event = result.events.Transfer;105 expect(event.address).to.equal(collectionAddress);106 expect(event.returnValues.from).to.equal(receiver);107 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');108 expect(event.returnValues.value).to.equal('49');109110 const balance = await contract.methods.balanceOf(receiver).call();111 expect(balance).to.equal('51');112 });113114 itEth('Can perform approve()', async ({helper}) => {115 const owner = await helper.eth.createAccountWithBalance(donor);116 const spender = helper.eth.createAccount();117 const collection = await helper.ft.mintCollection(alice);118 await collection.mint(alice, 200n, {Ethereum: owner});119120 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);121 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);122123 {124 const result = await contract.methods.approve(spender, 100).send({from: owner});125126 const event = result.events.Approval;127 expect(event.address).to.be.equal(collectionAddress);128 expect(event.returnValues.owner).to.be.equal(owner);129 expect(event.returnValues.spender).to.be.equal(spender);130 expect(event.returnValues.value).to.be.equal('100');131 }132133 {134 const allowance = await contract.methods.allowance(owner, spender).call();135 expect(+allowance).to.equal(100);136 }137 });138139 itEth('Can perform approveCross()', async ({helper}) => {140 const owner = await helper.eth.createAccountWithBalance(donor);141 const spender = helper.eth.createAccount();142 const spenderSub = (await helper.arrange.createAccounts([1n], donor))[0];143 const spenderCrossEth = helper.ethCrossAccount.fromAddress(spender);144 const spenderCrossSub = helper.ethCrossAccount.fromKeyringPair(spenderSub);145146147 const collection = await helper.ft.mintCollection(alice);148 await collection.mint(alice, 200n, {Ethereum: owner});149150 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);151 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);152153 {154 const result = await contract.methods.approveCross(spenderCrossEth, 100).send({from: owner});155 const event = result.events.Approval;156 expect(event.address).to.be.equal(collectionAddress);157 expect(event.returnValues.owner).to.be.equal(owner);158 expect(event.returnValues.spender).to.be.equal(spender);159 expect(event.returnValues.value).to.be.equal('100');160 }161162 {163 const allowance = await contract.methods.allowance(owner, spender).call();164 expect(+allowance).to.equal(100);165 }166167168 {169 const result = await contract.methods.approveCross(spenderCrossSub, 100).send({from: owner});170 const event = result.events.Approval;171 expect(event.address).to.be.equal(collectionAddress);172 expect(event.returnValues.owner).to.be.equal(owner);173 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(spenderSub.address));174 expect(event.returnValues.value).to.be.equal('100');175 }176177 {178 const allowance = await collection.getApprovedTokens({Ethereum: owner}, {Substrate: spenderSub.address});179 expect(allowance).to.equal(100n);180 }181182 {183 //TO-DO expect with future allowanceCross(owner, spenderCrossEth).call()184 }185 });186187 itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {188 const nonOwner = await helper.eth.createAccountWithBalance(donor);189 const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);190 const owner = await helper.eth.createAccountWithBalance(donor);191 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});192 await collection.mint(alice, 100n, {Ethereum: owner});193194 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);195 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);196197 await expect(collectionEvm.methods.approveCross(nonOwnerCross, 20).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');198 });199200201 itEth('Can perform burnFromCross()', async ({helper}) => {202 const sender = await helper.eth.createAccountWithBalance(donor, 100n);203204 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);205206 await collection.mint(owner, 200n, {Substrate: owner.address});207 await collection.approveTokens(owner, {Ethereum: sender}, 100n);208209 const address = helper.ethAddress.fromCollectionId(collection.collectionId);210 const contract = await helper.ethNativeContract.collection(address, 'ft');211212 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});213214 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);215 const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});216 const events = result.events;217218 expect(events).to.be.like({219 Transfer: {220 address: helper.ethAddress.fromCollectionId(collection.collectionId),221 event: 'Transfer',222 returnValues: {223 from: helper.address.substrateToEth(owner.address),224 to: '0x0000000000000000000000000000000000000000',225 value: '49',226 },227 },228 Approval: {229 address: helper.ethAddress.fromCollectionId(collection.collectionId),230 returnValues: {231 owner: helper.address.substrateToEth(owner.address),232 spender: sender,233 value: '51',234 },235 event: 'Approval',236 },237 });238239 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});240 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);241 });242243 itEth('Can perform transferFrom()', async ({helper}) => {244 const owner = await helper.eth.createAccountWithBalance(donor);245 const spender = await helper.eth.createAccountWithBalance(donor);246 const receiver = helper.eth.createAccount();247 const collection = await helper.ft.mintCollection(alice);248 await collection.mint(alice, 200n, {Ethereum: owner});249250 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);251 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);252253 await contract.methods.approve(spender, 100).send();254255 {256 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});257258 let event = result.events.Transfer;259 expect(event.address).to.be.equal(collectionAddress);260 expect(event.returnValues.from).to.be.equal(owner);261 expect(event.returnValues.to).to.be.equal(receiver);262 expect(event.returnValues.value).to.be.equal('49');263264 event = result.events.Approval;265 expect(event.address).to.be.equal(collectionAddress);266 expect(event.returnValues.owner).to.be.equal(owner);267 expect(event.returnValues.spender).to.be.equal(spender);268 expect(event.returnValues.value).to.be.equal('51');269 }270271 {272 const balance = await contract.methods.balanceOf(receiver).call();273 expect(+balance).to.equal(49);274 }275276 {277 const balance = await contract.methods.balanceOf(owner).call();278 expect(+balance).to.equal(151);279 }280 });281282 itEth('Can perform transferCross()', async ({helper}) => {283 const sender = await helper.eth.createAccountWithBalance(donor);284 const receiverEth = await helper.eth.createAccountWithBalance(donor);285 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);286 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(donor);287 const collection = await helper.ft.mintCollection(alice);288 await collection.mint(alice, 200n, {Ethereum: sender});289290 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);291 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);292293 {294 // Can transferCross to ethereum address:295 const result = await collectionEvm.methods.transferCross(receiverCrossEth, 50).send({from: sender});296 // Check events:297 const event = result.events.Transfer;298 expect(event.address).to.be.equal(collectionAddress);299 expect(event.returnValues.from).to.be.equal(sender);300 expect(event.returnValues.to).to.be.equal(receiverEth);301 expect(event.returnValues.value).to.be.equal('50');302 // Sender's balance decreased:303 const ownerBalance = await collectionEvm.methods.balanceOf(sender).call();304 expect(+ownerBalance).to.equal(150);305 // Receiver's balance increased:306 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();307 expect(+receiverBalance).to.equal(50);308 }309310 {311 // Can transferCross to substrate address:312 const result = await collectionEvm.methods.transferCross(receiverCrossSub, 50).send({from: sender});313 // Check events:314 const event = result.events.Transfer;315 expect(event.address).to.be.equal(collectionAddress);316 expect(event.returnValues.from).to.be.equal(sender);317 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(donor.address));318 expect(event.returnValues.value).to.be.equal('50');319 // Sender's balance decreased:320 const senderBalance = await collection.getBalance({Ethereum: sender});321 expect(senderBalance).to.equal(100n);322 // Receiver's balance increased:323 const balance = await collection.getBalance({Substrate: donor.address});324 expect(balance).to.equal(50n);325 }326 });327328 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {329 const sender = await helper.eth.createAccountWithBalance(donor);330 const receiverEth = await helper.eth.createAccountWithBalance(donor);331 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);332 const BALANCE = 200n;333 const BALANCE_TO_TRANSFER = BALANCE + 100n;334335 const collection = await helper.ft.mintCollection(alice);336 await collection.mint(alice, BALANCE, {Ethereum: sender});337 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);338 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);339340 // 1. Cannot transfer more than have341 const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;342 await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;343 // 2. Zero transfer allowed (EIP-20):344 await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});345 }));346347348 itEth('Can perform transfer()', async ({helper}) => {349 const owner = await helper.eth.createAccountWithBalance(donor);350 const receiver = await helper.eth.createAccountWithBalance(donor);351 const collection = await helper.ft.mintCollection(alice);352 await collection.mint(alice, 200n, {Ethereum: owner});353354 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);355 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);356357 {358 const result = await contract.methods.transfer(receiver, 50).send({from: owner});359360 const event = result.events.Transfer;361 expect(event.address).to.be.equal(collectionAddress);362 expect(event.returnValues.from).to.be.equal(owner);363 expect(event.returnValues.to).to.be.equal(receiver);364 expect(event.returnValues.value).to.be.equal('50');365 }366367 {368 const balance = await contract.methods.balanceOf(owner).call();369 expect(+balance).to.equal(150);370 }371372 {373 const balance = await contract.methods.balanceOf(receiver).call();374 expect(+balance).to.equal(50);375 }376 });377378 itEth('Can perform transferFromCross()', async ({helper}) => {379 const sender = await helper.eth.createAccountWithBalance(donor, 100n);380381 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);382383 const receiver = helper.eth.createAccount();384385 await collection.mint(owner, 200n, {Substrate: owner.address});386 await collection.approveTokens(owner, {Ethereum: sender}, 100n);387388 const address = helper.ethAddress.fromCollectionId(collection.collectionId);389 const contract = await helper.ethNativeContract.collection(address, 'ft');390391 const from = helper.ethCrossAccount.fromKeyringPair(owner);392 const to = helper.ethCrossAccount.fromAddress(receiver);393394 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});395 const toBalanceBefore = await collection.getBalance({Ethereum: receiver});396397 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});398399 expect(result.events).to.be.like({400 Transfer: {401 address,402 event: 'Transfer',403 returnValues: {404 from: helper.address.substrateToEth(owner.address),405 to: receiver,406 value: '51',407 },408 },409 Approval: {410 address,411 event: 'Approval',412 returnValues: {413 owner: helper.address.substrateToEth(owner.address),414 spender: sender,415 value: '49',416 },417 }});418419 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});420 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);421 const toBalanceAfter = await collection.getBalance({Ethereum: receiver});422 expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);423 });424});425426describe('Fungible: Fees', () => {427 let donor: IKeyringPair;428 let alice: IKeyringPair;429430 before(async function() {431 await usingEthPlaygrounds(async (helper, privateKey) => {432 donor = await privateKey({filename: __filename});433 [alice] = await helper.arrange.createAccounts([20n], donor);434 });435 });436437 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {438 const owner = await helper.eth.createAccountWithBalance(donor);439 const spender = helper.eth.createAccount();440 const collection = await helper.ft.mintCollection(alice);441 await collection.mint(alice, 200n, {Ethereum: owner});442443 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);444 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);445446 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));447 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));448 });449450 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {451 const owner = await helper.eth.createAccountWithBalance(donor);452 const spender = await helper.eth.createAccountWithBalance(donor);453 const collection = await helper.ft.mintCollection(alice);454 await collection.mint(alice, 200n, {Ethereum: owner});455456 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);457 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);458459 await contract.methods.approve(spender, 100).send({from: owner});460461 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));462 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));463 });464465 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {466 const owner = await helper.eth.createAccountWithBalance(donor);467 const receiver = helper.eth.createAccount();468 const collection = await helper.ft.mintCollection(alice);469 await collection.mint(alice, 200n, {Ethereum: owner});470471 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);472 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);473474 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));475 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));476 });477});478479describe('Fungible: Substrate calls', () => {480 let donor: IKeyringPair;481 let alice: IKeyringPair;482 let owner: IKeyringPair;483484 before(async function() {485 await usingEthPlaygrounds(async (helper, privateKey) => {486 donor = await privateKey({filename: __filename});487 [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);488 });489 });490491 itEth('Events emitted for approve()', async ({helper}) => {492 const receiver = helper.eth.createAccount();493 const collection = await helper.ft.mintCollection(alice);494 await collection.mint(alice, 200n);495496 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);497 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');498499 const events: any = [];500 contract.events.allEvents((_: any, event: any) => {501 events.push(event);502 });503504 await collection.approveTokens(alice, {Ethereum: receiver}, 100n);505 if (events.length == 0) await helper.wait.newBlocks(1);506 const event = events[0];507508 expect(event.event).to.be.equal('Approval');509 expect(event.address).to.be.equal(collectionAddress);510 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));511 expect(event.returnValues.spender).to.be.equal(receiver);512 expect(event.returnValues.value).to.be.equal('100');513 });514515 itEth('Events emitted for transferFrom()', async ({helper}) => {516 const [bob] = await helper.arrange.createAccounts([10n], donor);517 const receiver = helper.eth.createAccount();518 const collection = await helper.ft.mintCollection(alice);519 await collection.mint(alice, 200n);520 await collection.approveTokens(alice, {Substrate: bob.address}, 100n);521522 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);523 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');524525 const events: any = [];526 contract.events.allEvents((_: any, event: any) => {527 events.push(event);528 });529530 await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);531 if (events.length == 0) await helper.wait.newBlocks(1);532 let event = events[0];533534 expect(event.event).to.be.equal('Transfer');535 expect(event.address).to.be.equal(collectionAddress);536 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));537 expect(event.returnValues.to).to.be.equal(receiver);538 expect(event.returnValues.value).to.be.equal('51');539540 event = events[1];541 expect(event.event).to.be.equal('Approval');542 expect(event.address).to.be.equal(collectionAddress);543 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));544 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));545 expect(event.returnValues.value).to.be.equal('49');546 });547548 itEth('Events emitted for transfer()', async ({helper}) => {549 const receiver = helper.eth.createAccount();550 const collection = await helper.ft.mintCollection(alice);551 await collection.mint(alice, 200n);552553 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);554 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');555556 const events: any = [];557 contract.events.allEvents((_: any, event: any) => {558 events.push(event);559 });560561 await collection.transfer(alice, {Ethereum:receiver}, 51n);562 if (events.length == 0) await helper.wait.newBlocks(1);563 const event = events[0];564565 expect(event.event).to.be.equal('Transfer');566 expect(event.address).to.be.equal(collectionAddress);567 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));568 expect(event.returnValues.to).to.be.equal(receiver);569 expect(event.returnValues.value).to.be.equal('51');570 });571572 itEth('Events emitted for transferFromCross()', async ({helper}) => {573 const sender = await helper.eth.createAccountWithBalance(donor, 100n);574575 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);576577 const receiver = helper.eth.createAccount();578579 await collection.mint(owner, 200n, {Substrate: owner.address});580 await collection.approveTokens(owner, {Ethereum: sender}, 100n);581582 const address = helper.ethAddress.fromCollectionId(collection.collectionId);583 const contract = await helper.ethNativeContract.collection(address, 'ft');584585 const from = helper.ethCrossAccount.fromKeyringPair(owner);586 const to = helper.ethCrossAccount.fromAddress(receiver);587588 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});589590 expect(result.events).to.be.like({591 Transfer: {592 address,593 event: 'Transfer',594 returnValues: {595 from: helper.address.substrateToEth(owner.address),596 to: receiver,597 value: '51',598 },599 },600 Approval: {601 address,602 event: 'Approval',603 returnValues: {604 owner: helper.address.substrateToEth(owner.address),605 spender: sender,606 value: '49',607 },608 }});609 });610});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/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Plain calls', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;23 let owner: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 donor = await privateKey({filename: __filename});28 [alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);29 });30 });3132 [33 'substrate' as const,34 'ethereum' as const,35 ].map(testCase => {36 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {37 // 1. Create receiver depending on the test case:38 const receiverEth = helper.eth.createAccount();39 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);40 const receiverSub = owner;41 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(owner);4243 const ethOwner = await helper.eth.createAccountWithBalance(donor);44 const collection = await helper.ft.mintCollection(alice);45 await collection.addAdmin(alice, {Ethereum: ethOwner});4647 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);48 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);4950 // 2. Mint tokens:51 const result = await collectionEvm.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, 100).send();5253 const event = result.events.Transfer;54 expect(event.address).to.equal(collectionAddress);55 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');56 expect(event.returnValues.to).to.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(receiverSub.address));57 expect(event.returnValues.value).to.equal('100');5859 // 3. Get balance depending on the test case:60 let balance;61 if (testCase === 'ethereum') balance = await collection.getBalance({Ethereum: receiverEth});62 else if (testCase === 'substrate') balance = await collection.getBalance({Substrate: receiverSub.address});63 // 3.1 Check balance:64 expect(balance).to.eq(100n);65 });66 });6768 itEth('Can perform mintBulk()', async ({helper}) => {69 const owner = await helper.eth.createAccountWithBalance(donor);70 const bulkSize = 3;71 const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());72 const collection = await helper.ft.mintCollection(alice);73 await collection.addAdmin(alice, {Ethereum: owner});7475 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);76 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7778 const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (79 [receivers[i], (i + 1) * 10]80 ))).send();81 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);82 for (let i = 0; i < bulkSize; i++) {83 const event = events[i];84 expect(event.address).to.equal(collectionAddress);85 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');86 expect(event.returnValues.to).to.equal(receivers[i]);87 expect(event.returnValues.value).to.equal(String(10 * (i + 1)));88 }89 });9091 // Soft-deprecated92 itEth('Can perform burn()', async ({helper}) => {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = await helper.eth.createAccountWithBalance(donor);95 const collection = await helper.ft.mintCollection(alice);96 await collection.addAdmin(alice, {Ethereum: owner});9798 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);99 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);100 await contract.methods.mint(receiver, 100).send();101102 const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});103104 const event = result.events.Transfer;105 expect(event.address).to.equal(collectionAddress);106 expect(event.returnValues.from).to.equal(receiver);107 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');108 expect(event.returnValues.value).to.equal('49');109110 const balance = await contract.methods.balanceOf(receiver).call();111 expect(balance).to.equal('51');112 });113114 itEth('Can perform approve()', async ({helper}) => {115 const owner = await helper.eth.createAccountWithBalance(donor);116 const spender = helper.eth.createAccount();117 const collection = await helper.ft.mintCollection(alice);118 await collection.mint(alice, 200n, {Ethereum: owner});119120 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);121 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);122123 {124 const result = await contract.methods.approve(spender, 100).send({from: owner});125126 const event = result.events.Approval;127 expect(event.address).to.be.equal(collectionAddress);128 expect(event.returnValues.owner).to.be.equal(owner);129 expect(event.returnValues.spender).to.be.equal(spender);130 expect(event.returnValues.value).to.be.equal('100');131 }132133 {134 const allowance = await contract.methods.allowance(owner, spender).call();135 expect(+allowance).to.equal(100);136 }137 {138 const ownerCross = helper.ethCrossAccount.fromAddress(owner);139 const spenderCross = helper.ethCrossAccount.fromAddress(spender);140 const allowance = await contract.methods.allowanceCross(ownerCross, spenderCross).call();141 expect(+allowance).to.equal(100);142 }143 });144145 itEth('Can perform approveCross()', async ({helper}) => {146 const owner = await helper.eth.createAccountWithBalance(donor);147 const spender = helper.eth.createAccount();148 const spenderSub = (await helper.arrange.createAccounts([1n], donor))[0];149 const spenderCrossEth = helper.ethCrossAccount.fromAddress(spender);150 const spenderCrossSub = helper.ethCrossAccount.fromKeyringPair(spenderSub);151152153 const collection = await helper.ft.mintCollection(alice);154 await collection.mint(alice, 200n, {Ethereum: owner});155156 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);157 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);158159 {160 const result = await contract.methods.approveCross(spenderCrossEth, 100).send({from: owner});161 const event = result.events.Approval;162 expect(event.address).to.be.equal(collectionAddress);163 expect(event.returnValues.owner).to.be.equal(owner);164 expect(event.returnValues.spender).to.be.equal(spender);165 expect(event.returnValues.value).to.be.equal('100');166 }167168 {169 const allowance = await contract.methods.allowance(owner, spender).call();170 expect(+allowance).to.equal(100);171 }172173174 {175 const result = await contract.methods.approveCross(spenderCrossSub, 100).send({from: owner});176 const event = result.events.Approval;177 expect(event.address).to.be.equal(collectionAddress);178 expect(event.returnValues.owner).to.be.equal(owner);179 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(spenderSub.address));180 expect(event.returnValues.value).to.be.equal('100');181 }182183 {184 const allowance = await collection.getApprovedTokens({Ethereum: owner}, {Substrate: spenderSub.address});185 expect(allowance).to.equal(100n);186 }187188 {189 //TO-DO expect with future allowanceCross(owner, spenderCrossEth).call()190 }191 });192193 itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {194 const nonOwner = await helper.eth.createAccountWithBalance(donor);195 const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);196 const owner = await helper.eth.createAccountWithBalance(donor);197 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});198 await collection.mint(alice, 100n, {Ethereum: owner});199200 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);201 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);202203 await expect(collectionEvm.methods.approveCross(nonOwnerCross, 20).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');204 });205206207 itEth('Can perform burnFromCross()', async ({helper}) => {208 const sender = await helper.eth.createAccountWithBalance(donor, 100n);209210 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);211212 await collection.mint(owner, 200n, {Substrate: owner.address});213 await collection.approveTokens(owner, {Ethereum: sender}, 100n);214215 const address = helper.ethAddress.fromCollectionId(collection.collectionId);216 const contract = await helper.ethNativeContract.collection(address, 'ft');217218 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});219220 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);221 const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});222 const events = result.events;223224 expect(events).to.be.like({225 Transfer: {226 address: helper.ethAddress.fromCollectionId(collection.collectionId),227 event: 'Transfer',228 returnValues: {229 from: helper.address.substrateToEth(owner.address),230 to: '0x0000000000000000000000000000000000000000',231 value: '49',232 },233 },234 Approval: {235 address: helper.ethAddress.fromCollectionId(collection.collectionId),236 returnValues: {237 owner: helper.address.substrateToEth(owner.address),238 spender: sender,239 value: '51',240 },241 event: 'Approval',242 },243 });244245 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});246 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);247 });248249 itEth('Can perform transferFrom()', async ({helper}) => {250 const owner = await helper.eth.createAccountWithBalance(donor);251 const spender = await helper.eth.createAccountWithBalance(donor);252 const receiver = helper.eth.createAccount();253 const collection = await helper.ft.mintCollection(alice);254 await collection.mint(alice, 200n, {Ethereum: owner});255256 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);257 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);258259 await contract.methods.approve(spender, 100).send();260261 {262 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});263264 let event = result.events.Transfer;265 expect(event.address).to.be.equal(collectionAddress);266 expect(event.returnValues.from).to.be.equal(owner);267 expect(event.returnValues.to).to.be.equal(receiver);268 expect(event.returnValues.value).to.be.equal('49');269270 event = result.events.Approval;271 expect(event.address).to.be.equal(collectionAddress);272 expect(event.returnValues.owner).to.be.equal(owner);273 expect(event.returnValues.spender).to.be.equal(spender);274 expect(event.returnValues.value).to.be.equal('51');275 }276277 {278 const balance = await contract.methods.balanceOf(receiver).call();279 expect(+balance).to.equal(49);280 }281282 {283 const balance = await contract.methods.balanceOf(owner).call();284 expect(+balance).to.equal(151);285 }286 });287288 itEth('Can perform transferCross()', async ({helper}) => {289 const sender = await helper.eth.createAccountWithBalance(donor);290 const receiverEth = await helper.eth.createAccountWithBalance(donor);291 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);292 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(donor);293 const collection = await helper.ft.mintCollection(alice);294 await collection.mint(alice, 200n, {Ethereum: sender});295296 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);297 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);298299 {300 // Can transferCross to ethereum address:301 const result = await collectionEvm.methods.transferCross(receiverCrossEth, 50).send({from: sender});302 // Check events:303 const event = result.events.Transfer;304 expect(event.address).to.be.equal(collectionAddress);305 expect(event.returnValues.from).to.be.equal(sender);306 expect(event.returnValues.to).to.be.equal(receiverEth);307 expect(event.returnValues.value).to.be.equal('50');308 // Sender's balance decreased:309 const ownerBalance = await collectionEvm.methods.balanceOf(sender).call();310 expect(+ownerBalance).to.equal(150);311 // Receiver's balance increased:312 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();313 expect(+receiverBalance).to.equal(50);314 }315316 {317 // Can transferCross to substrate address:318 const result = await collectionEvm.methods.transferCross(receiverCrossSub, 50).send({from: sender});319 // Check events:320 const event = result.events.Transfer;321 expect(event.address).to.be.equal(collectionAddress);322 expect(event.returnValues.from).to.be.equal(sender);323 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(donor.address));324 expect(event.returnValues.value).to.be.equal('50');325 // Sender's balance decreased:326 const senderBalance = await collection.getBalance({Ethereum: sender});327 expect(senderBalance).to.equal(100n);328 // Receiver's balance increased:329 const balance = await collection.getBalance({Substrate: donor.address});330 expect(balance).to.equal(50n);331 }332 });333334 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {335 const sender = await helper.eth.createAccountWithBalance(donor);336 const receiverEth = await helper.eth.createAccountWithBalance(donor);337 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);338 const BALANCE = 200n;339 const BALANCE_TO_TRANSFER = BALANCE + 100n;340341 const collection = await helper.ft.mintCollection(alice);342 await collection.mint(alice, BALANCE, {Ethereum: sender});343 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);344 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);345346 // 1. Cannot transfer more than have347 const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;348 await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;349 // 2. Zero transfer allowed (EIP-20):350 await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});351 }));352353354 itEth('Can perform transfer()', async ({helper}) => {355 const owner = await helper.eth.createAccountWithBalance(donor);356 const receiver = await helper.eth.createAccountWithBalance(donor);357 const collection = await helper.ft.mintCollection(alice);358 await collection.mint(alice, 200n, {Ethereum: owner});359360 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);361 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);362363 {364 const result = await contract.methods.transfer(receiver, 50).send({from: owner});365366 const event = result.events.Transfer;367 expect(event.address).to.be.equal(collectionAddress);368 expect(event.returnValues.from).to.be.equal(owner);369 expect(event.returnValues.to).to.be.equal(receiver);370 expect(event.returnValues.value).to.be.equal('50');371 }372373 {374 const balance = await contract.methods.balanceOf(owner).call();375 expect(+balance).to.equal(150);376 }377378 {379 const balance = await contract.methods.balanceOf(receiver).call();380 expect(+balance).to.equal(50);381 }382 });383384 itEth('Can perform transferFromCross()', async ({helper}) => {385 const sender = await helper.eth.createAccountWithBalance(donor, 100n);386387 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);388389 const receiver = helper.eth.createAccount();390391 await collection.mint(owner, 200n, {Substrate: owner.address});392 await collection.approveTokens(owner, {Ethereum: sender}, 100n);393394 const address = helper.ethAddress.fromCollectionId(collection.collectionId);395 const contract = await helper.ethNativeContract.collection(address, 'ft');396397 const from = helper.ethCrossAccount.fromKeyringPair(owner);398 const to = helper.ethCrossAccount.fromAddress(receiver);399400 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});401 const toBalanceBefore = await collection.getBalance({Ethereum: receiver});402403 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});404405 expect(result.events).to.be.like({406 Transfer: {407 address,408 event: 'Transfer',409 returnValues: {410 from: helper.address.substrateToEth(owner.address),411 to: receiver,412 value: '51',413 },414 },415 Approval: {416 address,417 event: 'Approval',418 returnValues: {419 owner: helper.address.substrateToEth(owner.address),420 spender: sender,421 value: '49',422 },423 }});424425 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});426 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);427 const toBalanceAfter = await collection.getBalance({Ethereum: receiver});428 expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);429 });430});431432describe('Fungible: Fees', () => {433 let donor: IKeyringPair;434 let alice: IKeyringPair;435436 before(async function() {437 await usingEthPlaygrounds(async (helper, privateKey) => {438 donor = await privateKey({filename: __filename});439 [alice] = await helper.arrange.createAccounts([20n], donor);440 });441 });442443 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {444 const owner = await helper.eth.createAccountWithBalance(donor);445 const spender = helper.eth.createAccount();446 const collection = await helper.ft.mintCollection(alice);447 await collection.mint(alice, 200n, {Ethereum: owner});448449 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);450 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);451452 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));453 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));454 });455456 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {457 const owner = await helper.eth.createAccountWithBalance(donor);458 const spender = await helper.eth.createAccountWithBalance(donor);459 const collection = await helper.ft.mintCollection(alice);460 await collection.mint(alice, 200n, {Ethereum: owner});461462 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);463 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);464465 await contract.methods.approve(spender, 100).send({from: owner});466467 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));468 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));469 });470471 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {472 const owner = await helper.eth.createAccountWithBalance(donor);473 const receiver = helper.eth.createAccount();474 const collection = await helper.ft.mintCollection(alice);475 await collection.mint(alice, 200n, {Ethereum: owner});476477 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);478 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);479480 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));481 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));482 });483});484485describe('Fungible: Substrate calls', () => {486 let donor: IKeyringPair;487 let alice: IKeyringPair;488 let owner: IKeyringPair;489490 before(async function() {491 await usingEthPlaygrounds(async (helper, privateKey) => {492 donor = await privateKey({filename: __filename});493 [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);494 });495 });496497 itEth('Events emitted for approve()', async ({helper}) => {498 const receiver = helper.eth.createAccount();499 const collection = await helper.ft.mintCollection(alice);500 await collection.mint(alice, 200n);501502 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);503 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');504505 const events: any = [];506 contract.events.allEvents((_: any, event: any) => {507 events.push(event);508 });509510 await collection.approveTokens(alice, {Ethereum: receiver}, 100n);511 if (events.length == 0) await helper.wait.newBlocks(1);512 const event = events[0];513514 expect(event.event).to.be.equal('Approval');515 expect(event.address).to.be.equal(collectionAddress);516 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));517 expect(event.returnValues.spender).to.be.equal(receiver);518 expect(event.returnValues.value).to.be.equal('100');519 });520521 itEth('Events emitted for transferFrom()', async ({helper}) => {522 const [bob] = await helper.arrange.createAccounts([10n], donor);523 const receiver = helper.eth.createAccount();524 const collection = await helper.ft.mintCollection(alice);525 await collection.mint(alice, 200n);526 await collection.approveTokens(alice, {Substrate: bob.address}, 100n);527528 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);529 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');530531 const events: any = [];532 contract.events.allEvents((_: any, event: any) => {533 events.push(event);534 });535536 await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);537 if (events.length == 0) await helper.wait.newBlocks(1);538 let event = events[0];539540 expect(event.event).to.be.equal('Transfer');541 expect(event.address).to.be.equal(collectionAddress);542 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));543 expect(event.returnValues.to).to.be.equal(receiver);544 expect(event.returnValues.value).to.be.equal('51');545546 event = events[1];547 expect(event.event).to.be.equal('Approval');548 expect(event.address).to.be.equal(collectionAddress);549 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));550 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));551 expect(event.returnValues.value).to.be.equal('49');552 });553554 itEth('Events emitted for transfer()', async ({helper}) => {555 const receiver = helper.eth.createAccount();556 const collection = await helper.ft.mintCollection(alice);557 await collection.mint(alice, 200n);558559 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);560 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');561562 const events: any = [];563 contract.events.allEvents((_: any, event: any) => {564 events.push(event);565 });566567 await collection.transfer(alice, {Ethereum:receiver}, 51n);568 if (events.length == 0) await helper.wait.newBlocks(1);569 const event = events[0];570571 expect(event.event).to.be.equal('Transfer');572 expect(event.address).to.be.equal(collectionAddress);573 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));574 expect(event.returnValues.to).to.be.equal(receiver);575 expect(event.returnValues.value).to.be.equal('51');576 });577578 itEth('Events emitted for transferFromCross()', async ({helper}) => {579 const sender = await helper.eth.createAccountWithBalance(donor, 100n);580581 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);582583 const receiver = helper.eth.createAccount();584585 await collection.mint(owner, 200n, {Substrate: owner.address});586 await collection.approveTokens(owner, {Ethereum: sender}, 100n);587588 const address = helper.ethAddress.fromCollectionId(collection.collectionId);589 const contract = await helper.ethNativeContract.collection(address, 'ft');590591 const from = helper.ethCrossAccount.fromKeyringPair(owner);592 const to = helper.ethCrossAccount.fromAddress(receiver);593594 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});595596 expect(result.events).to.be.like({597 Transfer: {598 address,599 event: 'Transfer',600 returnValues: {601 from: helper.address.substrateToEth(owner.address),602 to: receiver,603 value: '51',604 },605 },606 Approval: {607 address,608 event: 'Approval',609 returnValues: {610 owner: helper.address.substrateToEth(owner.address),611 spender: sender,612 value: '49',613 },614 }});615 });616});tests/src/eth/proxy/UniqueNFTProxy.soldiffbeforeafterboth--- a/tests/src/eth/proxy/UniqueNFTProxy.sol
+++ b/tests/src/eth/proxy/UniqueNFTProxy.sol
@@ -168,7 +168,7 @@
return proxied.mintBulk(to, tokenIds);
}
- function mintBulkWithTokenURI(address to, Tuple6[] memory tokens)
+ function mintBulkWithTokenURI(address to, TokenUri[] memory tokens)
external
override
returns (bool)
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -219,8 +219,16 @@
await rftToken.methods.approve(operator, 15n).send({from: owner});
await contract.methods.setApprovalForAll(operator, true).send({from: owner});
await rftToken.methods.burnFrom(owner, 10n).send({from: operator});
+ }
+ {
const allowance = await rftToken.methods.allowance(owner, operator).call();
- expect(allowance).to.be.equal('5');
+ expect(+allowance).to.be.equal(5);
+ }
+ {
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const operatorCross = helper.ethCrossAccount.fromAddress(operator);
+ const allowance = await rftToken.methods.allowanceCross(ownerCross, operatorCross).call();
+ expect(+allowance).to.equal(5);
}
});