difftreelog
refac: rename substrate methods to cross
in: master
27 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1077,13 +1077,13 @@
[[package]]
name = "console"
-version = "0.15.1"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
+checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
dependencies = [
"encode_unicode",
+ "lazy_static",
"libc",
- "once_cell",
"terminal_size",
"winapi",
]
@@ -1871,9 +1871,9 @@
[[package]]
name = "cxx"
-version = "1.0.79"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8"
+checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -1883,9 +1883,9 @@
[[package]]
name = "cxx-build"
-version = "1.0.79"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86"
+checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
dependencies = [
"cc",
"codespan-reporting",
@@ -1898,15 +1898,15 @@
[[package]]
name = "cxxbridge-flags"
-version = "1.0.79"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78"
+checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.79"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f"
+checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
dependencies = [
"proc-macro2",
"quote",
@@ -3561,9 +3561,9 @@
[[package]]
name = "iana-time-zone-haiku"
-version = "0.1.1"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
dependencies = [
"cxx",
"cxx-build",
@@ -10774,9 +10774,9 @@
[[package]]
name = "serde_json"
-version = "1.0.87"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
+checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [
"itoa",
"ryu",
@@ -12055,9 +12055,9 @@
[[package]]
name = "syn"
-version = "1.0.103"
+version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
dependencies = [
"proc-macro2",
"quote",
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -34,8 +34,8 @@
use crate::{
Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
eth::{
- convert_cross_account_to_uint256, convert_uint256_to_cross_account,
- convert_cross_account_to_tuple,
+ convert_cross_account_to_uint256, convert_cross_account_to_tuple,
+ convert_tuple_to_cross_account,
},
weights::WeightInfo,
};
@@ -139,26 +139,25 @@
save(self)
}
- // TODO: Temprorary off. Need refactor
- // /// Set the substrate sponsor of the collection.
- // ///
- // /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
- // ///
- // /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract.
- // fn set_collection_sponsor_substrate(
- // &mut self,
- // caller: caller,
- // sponsor: uint256,
- // ) -> Result<void> {
- // self.consume_store_reads_and_writes(1, 1)?;
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ fn set_collection_sponsor_cross(
+ &mut self,
+ caller: caller,
+ sponsor: (address, uint256),
+ ) -> Result<void> {
+ self.consume_store_reads_and_writes(1, 1)?;
- // check_is_owner_or_admin(caller, self)?;
+ check_is_owner_or_admin(caller, self)?;
- // let sponsor = convert_uint256_to_cross_account::<T>(sponsor);
- // self.set_sponsor(sponsor.as_sub().clone())
- // .map_err(dispatch_to_evm::<T>)?;
- // save(self)
- // }
+ let sponsor = convert_tuple_to_cross_account::<T>(sponsor)?;
+ self.set_sponsor(sponsor.as_sub().clone())
+ .map_err(dispatch_to_evm::<T>)?;
+ save(self)
+ }
/// Whether there is a pending sponsor.
fn has_collection_pending_sponsor(&self) -> Result<bool> {
@@ -300,37 +299,35 @@
Ok(crate::eth::collection_id_to_address(self.id))
}
- // TODO: Temprorary off. Need refactor
- // /// Add collection admin by substrate address.
- // /// @param newAdmin Substrate administrator address.
- // fn add_collection_admin_substrate(
- // &mut self,
- // caller: caller,
- // new_admin: uint256,
- // ) -> Result<void> {
- // self.consume_store_writes(2)?;
+ /// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ fn add_collection_admin_cross(
+ &mut self,
+ caller: caller,
+ new_admin: (address, uint256),
+ ) -> Result<void> {
+ self.consume_store_writes(2)?;
- // let caller = T::CrossAccountId::from_eth(caller);
- // let new_admin = convert_uint256_to_cross_account::<T>(new_admin);
- // <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
- // Ok(())
- // }
+ let caller = T::CrossAccountId::from_eth(caller);
+ let new_admin = convert_tuple_to_cross_account::<T>(new_admin)?;
+ <Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
- // TODO: Temprorary off. Need refactor
- // /// Remove collection admin by substrate address.
- // /// @param admin Substrate administrator address.
- // fn remove_collection_admin_substrate(
- // &mut self,
- // caller: caller,
- // admin: uint256,
- // ) -> Result<void> {
- // self.consume_store_writes(2)?;
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ fn remove_collection_admin_cross(
+ &mut self,
+ caller: caller,
+ admin: (address, uint256),
+ ) -> Result<void> {
+ self.consume_store_writes(2)?;
- // let caller = T::CrossAccountId::from_eth(caller);
- // let admin = convert_uint256_to_cross_account::<T>(admin);
- // <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
- // Ok(())
- // }
+ let caller = T::CrossAccountId::from_eth(caller);
+ let admin = convert_tuple_to_cross_account::<T>(admin)?;
+ <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
/// Add collection admin.
/// @param newAdmin Address of the added administrator.
@@ -479,22 +476,21 @@
Ok(())
}
- // TODO: Temprorary off. Need refactor
- // /// Add substrate user to allowed list.
- // ///
- // /// @param user User substrate address.
- // fn add_to_collection_allow_list_substrate(
- // &mut self,
- // caller: caller,
- // user: uint256,
- // ) -> Result<void> {
- // self.consume_store_writes(1)?;
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ fn add_to_collection_allow_list_cross(
+ &mut self,
+ caller: caller,
+ user: (address, uint256),
+ ) -> Result<void> {
+ self.consume_store_writes(1)?;
- // let caller = T::CrossAccountId::from_eth(caller);
- // let user = convert_uint256_to_cross_account::<T>(user);
- // Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
- // Ok(())
- // }
+ let caller = T::CrossAccountId::from_eth(caller);
+ let user = convert_tuple_to_cross_account::<T>(user)?;
+ Pallet::<T>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
/// Remove the user from the allowed list.
///
@@ -508,22 +504,21 @@
Ok(())
}
- // TODO: Temprorary off. Need refactor
- // /// Remove substrate user from allowed list.
- // ///
- // /// @param user User substrate address.
- // fn remove_from_collection_allow_list_substrate(
- // &mut self,
- // caller: caller,
- // user: uint256,
- // ) -> Result<void> {
- // self.consume_store_writes(1)?;
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ fn remove_from_collection_allow_list_cross(
+ &mut self,
+ caller: caller,
+ user: (address, uint256),
+ ) -> Result<void> {
+ self.consume_store_writes(1)?;
- // let caller = T::CrossAccountId::from_eth(caller);
- // let user = convert_uint256_to_cross_account::<T>(user);
- // Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
- // Ok(())
- // }
+ let caller = T::CrossAccountId::from_eth(caller);
+ let user = convert_tuple_to_cross_account::<T>(user)?;
+ Pallet::<T>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
/// Switch permission for minting.
///
@@ -556,15 +551,14 @@
Ok(self.is_owner_or_admin(&user))
}
- // TODO: Temprorary off. Need refactor
- // /// Check that substrate account is the owner or admin of the collection
- // ///
- // /// @param user account to verify
- // /// @return "true" if account is the owner or admin
- // fn is_owner_or_admin_substrate(&self, user: uint256) -> Result<bool> {
- // let user = convert_uint256_to_cross_account::<T>(user);
- // Ok(self.is_owner_or_admin(&user))
- // }
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ fn is_owner_or_admin_cross(&self, user: (address, uint256)) -> Result<bool> {
+ let user = convert_tuple_to_cross_account::<T>(user)?;
+ Ok(self.is_owner_or_admin(&user))
+ }
/// Returns collection type
///
@@ -580,7 +574,7 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
fn collection_owner(&self) -> Result<(address, uint256)> {
Ok(convert_cross_account_to_tuple::<T>(
@@ -602,20 +596,6 @@
.map_err(dispatch_to_evm::<T>)
}
- // TODO: Temprorary off. Need refactor
- // /// Changes collection owner to another substrate account
- // ///
- // /// @dev Owner can be changed only by current owner
- // /// @param newOwner new owner substrate account
- // fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result<void> {
- // self.consume_store_writes(1)?;
-
- // let caller = T::CrossAccountId::from_eth(caller);
- // let new_owner = convert_uint256_to_cross_account::<T>(new_owner);
- // self.set_owner_internal(caller, new_owner)
- // .map_err(dispatch_to_evm::<T>)
- // }
-
/// Get collection administrators
///
/// @return Vector of tuples with admins address and his substrate mirror.
@@ -626,6 +606,19 @@
.collect();
Ok(result)
}
+
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ fn set_owner_cross(&mut self, caller: caller, new_owner: (address, uint256)) -> Result<void> {
+ self.consume_store_writes(1)?;
+
+ let caller = T::CrossAccountId::from_eth(caller);
+ let new_owner = convert_tuple_to_cross_account::<T>(new_owner)?;
+ self.set_owner_internal(caller, new_owner)
+ .map_err(dispatch_to_evm::<T>)
+ }
}
/// ### Note
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -85,3 +85,27 @@
(eth, Default::default())
}
}
+
+/// Convert tuple `(address, uint256)` to `CrossAccountId`.
+///
+/// If `address` in the tuple has *default* value, then the canonical form is substrate,
+/// if `uint256` has *default* value, then the ethereum form is canonical,
+/// if both values are *default* or *non default*, then this is considered an invalid address and `Error` is returned.
+pub fn convert_tuple_to_cross_account<T: Config>(
+ eth_cross_account_id: (address, uint256),
+) -> evm_coder::execution::Result<T::CrossAccountId>
+where
+ T::AccountId: From<[u8; 32]>,
+{
+ if eth_cross_account_id == Default::default() {
+ Err("All fields of cross account is zeroed".into())
+ } else if eth_cross_account_id.0 == Default::default() {
+ Ok(convert_uint256_to_cross_account::<T>(
+ eth_cross_account_id.1,
+ ))
+ } else if eth_cross_account_id.1 == Default::default() {
+ Ok(T::CrossAccountId::from_eth(eth_cross_account_id.0))
+ } else {
+ Err("All fields of cross account is non zeroed".into())
+ }
+}
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -19,6 +19,7 @@
use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
use core::convert::TryInto;
use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use pallet_common::eth::convert_tuple_to_cross_account;
use up_data_structs::CollectionMode;
use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
use sp_std::vec::Vec;
@@ -149,7 +150,26 @@
}
#[solidity_interface(name = ERC20UniqueExtensions)]
-impl<T: Config> FungibleHandle<T> {
+impl<T: Config> FungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
+ #[weight(<SelfWeightOf<T>>::approve())]
+ fn approve_cross(
+ &mut self,
+ caller: caller,
+ spender: (address, uint256),
+ amount: uint256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let spender = convert_tuple_to_cross_account::<T>(spender)?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Pallet<T>>::set_allowance(self, &caller, &spender, amount)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
/// Burn tokens from account
/// @dev Function that burns an `amount` of the tokens of a given account,
/// deducting from the sender's allowance for said account.
@@ -169,6 +189,30 @@
Ok(true)
}
+ /// Burn tokens from account
+ /// @dev Function that burns an `amount` of the tokens of a given account,
+ /// deducting from the sender's allowance for said account.
+ /// @param from The account whose tokens will be burnt.
+ /// @param amount The amount that will be burnt.
+ #[weight(<SelfWeightOf<T>>::burn_from())]
+ fn burn_from_cross(
+ &mut self,
+ caller: caller,
+ from: (address, uint256),
+ amount: uint256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = convert_tuple_to_cross_account::<T>(from)?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::burn_from(self, &caller, &from, amount, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
/// Mint tokens for multiple accounts.
/// @param amounts array of pairs of account address and amount
#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]
@@ -191,6 +235,27 @@
.map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
+
+ #[weight(<SelfWeightOf<T>>::transfer_from())]
+ fn transfer_from_cross(
+ &mut self,
+ caller: caller,
+ from: (address, uint256),
+ to: (address, uint256),
+ amount: uint256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = convert_tuple_to_cross_account::<T>(from)?;
+ let to = convert_tuple_to_cross_account::<T>(to)?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer_from(self, &caller, &from, &to, amount, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
}
#[solidity_interface(
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
@@ -18,7 +18,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
contract Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -72,6 +72,19 @@
dummy = 0;
}
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+ require(false, stub_error);
+ sponsor;
+ dummy = 0;
+ }
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -155,6 +168,26 @@
}
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+ require(false, stub_error);
+ newAdmin;
+ dummy = 0;
+ }
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) public {
+ require(false, stub_error);
+ admin;
+ dummy = 0;
+ }
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -234,6 +267,17 @@
dummy = 0;
}
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -245,6 +289,17 @@
dummy = 0;
}
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -269,6 +324,19 @@
return false;
}
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+ require(false, stub_error);
+ user;
+ dummy;
+ return false;
+ }
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -282,7 +350,7 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
@@ -315,10 +383,32 @@
dummy;
return new Tuple6[](0);
}
+
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) public {
+ require(false, stub_error);
+ newOwner;
+ dummy = 0;
+ }
}
-/// @dev the ERC-165 identifier for this interface is 0x63034ac5
+/// @dev the ERC-165 identifier for this interface is 0x032e5926
contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// or in textual repr: approveCross((address,uint256),uint256)
+ function approveCross(Tuple6 memory spender, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ spender;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
/// Burn tokens from account
/// @dev Function that burns an `amount` of the tokens of a given account,
/// deducting from the sender's allowance for said account.
@@ -334,6 +424,21 @@
return false;
}
+ /// Burn tokens from account
+ /// @dev Function that burns an `amount` of the tokens of a given account,
+ /// deducting from the sender's allowance for said account.
+ /// @param from The account whose tokens will be burnt.
+ /// @param amount The amount that will be burnt.
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ from;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
/// Mint tokens for multiple accounts.
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
@@ -344,6 +449,21 @@
dummy = 0;
return false;
}
+
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 amount
+ ) public returns (bool) {
+ require(false, stub_error);
+ from;
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
}
/// @dev anonymous struct
pallets/nonfungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37 CollectionHandle, CollectionPropertyPermissions,38};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::call;41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4243use crate::{44 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,45 SelfWeightOf, weights::WeightInfo, TokenProperties,46};4748/// @title A contract that allows to set and delete token properties and change token property permissions.49#[solidity_interface(name = TokenProperties)]50impl<T: Config> NonfungibleHandle<T> {51 /// @notice Set permissions for token property.52 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.53 /// @param key Property key.54 /// @param isMutable Permission to mutate property.55 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.56 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.57 fn set_token_property_permission(58 &mut self,59 caller: caller,60 key: string,61 is_mutable: bool,62 collection_admin: bool,63 token_owner: bool,64 ) -> Result<()> {65 let caller = T::CrossAccountId::from_eth(caller);66 <Pallet<T>>::set_property_permission(67 self,68 &caller,69 PropertyKeyPermission {70 key: <Vec<u8>>::from(key)71 .try_into()72 .map_err(|_| "too long key")?,73 permission: PropertyPermission {74 mutable: is_mutable,75 collection_admin,76 token_owner,77 },78 },79 )80 .map_err(dispatch_to_evm::<T>)81 }8283 /// @notice Set token property value.84 /// @dev Throws error if `msg.sender` has no permission to edit the property.85 /// @param tokenId ID of the token.86 /// @param key Property key.87 /// @param value Property value.88 fn set_property(89 &mut self,90 caller: caller,91 token_id: uint256,92 key: string,93 value: bytes,94 ) -> Result<()> {95 let caller = T::CrossAccountId::from_eth(caller);96 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;97 let key = <Vec<u8>>::from(key)98 .try_into()99 .map_err(|_| "key too long")?;100 let value = value.0.try_into().map_err(|_| "value too long")?;101102 let nesting_budget = self103 .recorder104 .weight_calls_budget(<StructureWeight<T>>::find_parent());105106 <Pallet<T>>::set_token_property(107 self,108 &caller,109 TokenId(token_id),110 Property { key, value },111 &nesting_budget,112 )113 .map_err(dispatch_to_evm::<T>)114 }115116 /// @notice Delete token property value.117 /// @dev Throws error if `msg.sender` has no permission to edit the property.118 /// @param tokenId ID of the token.119 /// @param key Property key.120 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {121 let caller = T::CrossAccountId::from_eth(caller);122 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;123 let key = <Vec<u8>>::from(key)124 .try_into()125 .map_err(|_| "key too long")?;126127 let nesting_budget = self128 .recorder129 .weight_calls_budget(<StructureWeight<T>>::find_parent());130131 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)132 .map_err(dispatch_to_evm::<T>)133 }134135 /// @notice Get token property value.136 /// @dev Throws error if key not found137 /// @param tokenId ID of the token.138 /// @param key Property key.139 /// @return Property value bytes140 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {141 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too long")?;145146 let props = <TokenProperties<T>>::get((self.id, token_id));147 let prop = props.get(&key).ok_or("key not found")?;148149 Ok(prop.to_vec().into())150 }151}152153#[derive(ToLog)]154pub enum ERC721Events {155 /// @dev This emits when ownership of any NFT changes by any mechanism.156 /// This event emits when NFTs are created (`from` == 0) and destroyed157 /// (`to` == 0). Exception: during contract creation, any number of NFTs158 /// may be created and assigned without emitting Transfer. At the time of159 /// any transfer, the approved address for that NFT (if any) is reset to none.160 Transfer {161 #[indexed]162 from: address,163 #[indexed]164 to: address,165 #[indexed]166 token_id: uint256,167 },168 /// @dev This emits when the approved address for an NFT is changed or169 /// reaffirmed. The zero address indicates there is no approved address.170 /// When a Transfer event emits, this also indicates that the approved171 /// address for that NFT (if any) is reset to none.172 Approval {173 #[indexed]174 owner: address,175 #[indexed]176 approved: address,177 #[indexed]178 token_id: uint256,179 },180 /// @dev This emits when an operator is enabled or disabled for an owner.181 /// The operator can manage all NFTs of the owner.182 #[allow(dead_code)]183 ApprovalForAll {184 #[indexed]185 owner: address,186 #[indexed]187 operator: address,188 approved: bool,189 },190}191192#[derive(ToLog)]193pub enum ERC721UniqueMintableEvents {194 #[allow(dead_code)]195 MintingFinished {},196}197198/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension199/// @dev See https://eips.ethereum.org/EIPS/eip-721200#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]201impl<T: Config> NonfungibleHandle<T> {202 /// @notice A descriptive name for a collection of NFTs in this contract203 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`204 #[solidity(hide, rename_selector = "name")]205 fn name_proxy(&self) -> Result<string> {206 self.name()207 }208209 /// @notice An abbreviated name for NFTs in this contract210 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`211 #[solidity(hide, rename_selector = "symbol")]212 fn symbol_proxy(&self) -> Result<string> {213 self.symbol()214 }215216 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.217 ///218 /// @dev If the token has a `url` property and it is not empty, it is returned.219 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.220 /// If the collection property `baseURI` is empty or absent, return "" (empty string)221 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix222 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).223 ///224 /// @return token's const_metadata225 #[solidity(rename_selector = "tokenURI")]226 fn token_uri(&self, token_id: uint256) -> Result<string> {227 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;228229 match get_token_property(self, token_id_u32, &key::url()).as_deref() {230 Err(_) | Ok("") => (),231 Ok(url) => {232 return Ok(url.into());233 }234 };235236 let base_uri =237 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())238 .map(BoundedVec::into_inner)239 .map(string::from_utf8)240 .transpose()241 .map_err(|e| {242 Error::Revert(alloc::format!(243 "Can not convert value \"baseURI\" to string with error \"{}\"",244 e245 ))246 })?;247248 let base_uri = match base_uri.as_deref() {249 None | Some("") => {250 return Ok("".into());251 }252 Some(base_uri) => base_uri.into(),253 };254255 Ok(256 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {257 Err(_) | Ok("") => base_uri,258 Ok(suffix) => base_uri + suffix,259 },260 )261 }262}263264/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension265/// @dev See https://eips.ethereum.org/EIPS/eip-721266#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]267impl<T: Config> NonfungibleHandle<T> {268 /// @notice Enumerate valid NFTs269 /// @param index A counter less than `totalSupply()`270 /// @return The token identifier for the `index`th NFT,271 /// (sort order not specified)272 fn token_by_index(&self, index: uint256) -> Result<uint256> {273 Ok(index)274 }275276 /// @dev Not implemented277 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {278 // TODO: Not implemetable279 Err("not implemented".into())280 }281282 /// @notice Count NFTs tracked by this contract283 /// @return A count of valid NFTs tracked by this contract, where each one of284 /// them has an assigned and queryable owner not equal to the zero address285 fn total_supply(&self) -> Result<uint256> {286 self.consume_store_reads(1)?;287 Ok(<Pallet<T>>::total_supply(self).into())288 }289}290291/// @title ERC-721 Non-Fungible Token Standard292/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md293#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]294impl<T: Config> NonfungibleHandle<T> {295 /// @notice Count all NFTs assigned to an owner296 /// @dev NFTs assigned to the zero address are considered invalid, and this297 /// function throws for queries about the zero address.298 /// @param owner An address for whom to query the balance299 /// @return The number of NFTs owned by `owner`, possibly zero300 fn balance_of(&self, owner: address) -> Result<uint256> {301 self.consume_store_reads(1)?;302 let owner = T::CrossAccountId::from_eth(owner);303 let balance = <AccountBalance<T>>::get((self.id, owner));304 Ok(balance.into())305 }306 /// @notice Find the owner of an NFT307 /// @dev NFTs assigned to zero address are considered invalid, and queries308 /// about them do throw.309 /// @param tokenId The identifier for an NFT310 /// @return The address of the owner of the NFT311 fn owner_of(&self, token_id: uint256) -> Result<address> {312 self.consume_store_reads(1)?;313 let token: TokenId = token_id.try_into()?;314 Ok(*<TokenData<T>>::get((self.id, token))315 .ok_or("token not found")?316 .owner317 .as_eth())318 }319 /// @dev Not implemented320 #[solidity(rename_selector = "safeTransferFrom")]321 fn safe_transfer_from_with_data(322 &mut self,323 _from: address,324 _to: address,325 _token_id: uint256,326 _data: bytes,327 ) -> Result<void> {328 // TODO: Not implemetable329 Err("not implemented".into())330 }331 /// @dev Not implemented332 fn safe_transfer_from(333 &mut self,334 _from: address,335 _to: address,336 _token_id: uint256,337 ) -> Result<void> {338 // TODO: Not implemetable339 Err("not implemented".into())340 }341342 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE343 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE344 /// THEY MAY BE PERMANENTLY LOST345 /// @dev Throws unless `msg.sender` is the current owner or an authorized346 /// operator for this NFT. Throws if `from` is not the current owner. Throws347 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.348 /// @param from The current owner of the NFT349 /// @param to The new owner350 /// @param tokenId The NFT to transfer351 #[weight(<SelfWeightOf<T>>::transfer_from())]352 fn transfer_from(353 &mut self,354 caller: caller,355 from: address,356 to: address,357 token_id: uint256,358 ) -> Result<void> {359 let caller = T::CrossAccountId::from_eth(caller);360 let from = T::CrossAccountId::from_eth(from);361 let to = T::CrossAccountId::from_eth(to);362 let token = token_id.try_into()?;363 let budget = self364 .recorder365 .weight_calls_budget(<StructureWeight<T>>::find_parent());366367 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)368 .map_err(dispatch_to_evm::<T>)?;369 Ok(())370 }371372 /// @notice Set or reaffirm the approved address for an NFT373 /// @dev The zero address indicates there is no approved address.374 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized375 /// operator of the current owner.376 /// @param approved The new approved NFT controller377 /// @param tokenId The NFT to approve378 #[weight(<SelfWeightOf<T>>::approve())]379 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {380 let caller = T::CrossAccountId::from_eth(caller);381 let approved = T::CrossAccountId::from_eth(approved);382 let token = token_id.try_into()?;383384 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))385 .map_err(dispatch_to_evm::<T>)?;386 Ok(())387 }388389 /// @dev Not implemented390 fn set_approval_for_all(391 &mut self,392 _caller: caller,393 _operator: address,394 _approved: bool,395 ) -> Result<void> {396 // TODO: Not implemetable397 Err("not implemented".into())398 }399400 /// @dev Not implemented401 fn get_approved(&self, _token_id: uint256) -> Result<address> {402 // TODO: Not implemetable403 Err("not implemented".into())404 }405406 /// @dev Not implemented407 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {408 // TODO: Not implemetable409 Err("not implemented".into())410 }411}412413/// @title ERC721 Token that can be irreversibly burned (destroyed).414#[solidity_interface(name = ERC721Burnable)]415impl<T: Config> NonfungibleHandle<T> {416 /// @notice Burns a specific ERC721 token.417 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized418 /// operator of the current owner.419 /// @param tokenId The NFT to approve420 #[weight(<SelfWeightOf<T>>::burn_item())]421 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {422 let caller = T::CrossAccountId::from_eth(caller);423 let token = token_id.try_into()?;424425 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;426 Ok(())427 }428}429430/// @title ERC721 minting logic.431#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]432impl<T: Config> NonfungibleHandle<T> {433 fn minting_finished(&self) -> Result<bool> {434 Ok(false)435 }436437 /// @notice Function to mint token.438 /// @param to The new owner439 /// @return uint256 The id of the newly minted token440 #[weight(<SelfWeightOf<T>>::create_item())]441 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {442 let token_id: uint256 = <TokensMinted<T>>::get(self.id)443 .checked_add(1)444 .ok_or("item id overflow")?445 .into();446 self.mint_check_id(caller, to, token_id)?;447 Ok(token_id)448 }449450 /// @notice Function to mint token.451 /// @dev `tokenId` should be obtained with `nextTokenId` method,452 /// unlike standard, you can't specify it manually453 /// @param to The new owner454 /// @param tokenId ID of the minted NFT455 #[solidity(hide, rename_selector = "mint")]456 #[weight(<SelfWeightOf<T>>::create_item())]457 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {458 let caller = T::CrossAccountId::from_eth(caller);459 let to = T::CrossAccountId::from_eth(to);460 let token_id: u32 = token_id.try_into()?;461 let budget = self462 .recorder463 .weight_calls_budget(<StructureWeight<T>>::find_parent());464465 if <TokensMinted<T>>::get(self.id)466 .checked_add(1)467 .ok_or("item id overflow")?468 != token_id469 {470 return Err("item id should be next".into());471 }472473 <Pallet<T>>::create_item(474 self,475 &caller,476 CreateItemData::<T> {477 properties: BoundedVec::default(),478 owner: to,479 },480 &budget,481 )482 .map_err(dispatch_to_evm::<T>)?;483484 Ok(true)485 }486487 /// @notice Function to mint token with the given tokenUri.488 /// @param to The new owner489 /// @param tokenUri Token URI that would be stored in the NFT properties490 /// @return uint256 The id of the newly minted token491 #[solidity(rename_selector = "mintWithTokenURI")]492 #[weight(<SelfWeightOf<T>>::create_item())]493 fn mint_with_token_uri(494 &mut self,495 caller: caller,496 to: address,497 token_uri: string,498 ) -> Result<uint256> {499 let token_id: uint256 = <TokensMinted<T>>::get(self.id)500 .checked_add(1)501 .ok_or("item id overflow")?502 .into();503 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;504 Ok(token_id)505 }506507 /// @notice Function to mint token with the given tokenUri.508 /// @dev `tokenId` should be obtained with `nextTokenId` method,509 /// unlike standard, you can't specify it manually510 /// @param to The new owner511 /// @param tokenId ID of the minted NFT512 /// @param tokenUri Token URI that would be stored in the NFT properties513 #[solidity(hide, rename_selector = "mintWithTokenURI")]514 #[weight(<SelfWeightOf<T>>::create_item())]515 fn mint_with_token_uri_check_id(516 &mut self,517 caller: caller,518 to: address,519 token_id: uint256,520 token_uri: string,521 ) -> Result<bool> {522 let key = key::url();523 let permission = get_token_permission::<T>(self.id, &key)?;524 if !permission.collection_admin {525 return Err("Operation is not allowed".into());526 }527528 let caller = T::CrossAccountId::from_eth(caller);529 let to = T::CrossAccountId::from_eth(to);530 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;531 let budget = self532 .recorder533 .weight_calls_budget(<StructureWeight<T>>::find_parent());534535 if <TokensMinted<T>>::get(self.id)536 .checked_add(1)537 .ok_or("item id overflow")?538 != token_id539 {540 return Err("item id should be next".into());541 }542543 let mut properties = CollectionPropertiesVec::default();544 properties545 .try_push(Property {546 key,547 value: token_uri548 .into_bytes()549 .try_into()550 .map_err(|_| "token uri is too long")?,551 })552 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;553554 <Pallet<T>>::create_item(555 self,556 &caller,557 CreateItemData::<T> {558 properties,559 owner: to,560 },561 &budget,562 )563 .map_err(dispatch_to_evm::<T>)?;564 Ok(true)565 }566567 /// @dev Not implemented568 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {569 Err("not implementable".into())570 }571}572573fn get_token_property<T: Config>(574 collection: &CollectionHandle<T>,575 token_id: u32,576 key: &up_data_structs::PropertyKey,577) -> Result<string> {578 collection.consume_store_reads(1)?;579 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))580 .map_err(|_| Error::Revert("Token properties not found".into()))?;581 if let Some(property) = properties.get(key) {582 return Ok(string::from_utf8_lossy(property).into());583 }584585 Err("Property tokenURI not found".into())586}587588fn get_token_permission<T: Config>(589 collection_id: CollectionId,590 key: &PropertyKey,591) -> Result<PropertyPermission> {592 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)593 .map_err(|_| Error::Revert("No permissions for collection".into()))?;594 let a = token_property_permissions595 .get(key)596 .map(Clone::clone)597 .ok_or_else(|| {598 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();599 Error::Revert(alloc::format!("No permission for key {}", key))600 })?;601 Ok(a)602}603604/// @title Unique extensions for ERC721.605#[solidity_interface(name = ERC721UniqueExtensions)]606impl<T: Config> NonfungibleHandle<T> {607 /// @notice A descriptive name for a collection of NFTs in this contract608 fn name(&self) -> Result<string> {609 Ok(decode_utf16(self.name.iter().copied())610 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))611 .collect::<string>())612 }613614 /// @notice An abbreviated name for NFTs in this contract615 fn symbol(&self) -> Result<string> {616 Ok(string::from_utf8_lossy(&self.token_prefix).into())617 }618619 /// @notice Transfer ownership of an NFT620 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`621 /// is the zero address. Throws if `tokenId` is not a valid NFT.622 /// @param to The new owner623 /// @param tokenId The NFT to transfer624 #[weight(<SelfWeightOf<T>>::transfer())]625 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {626 let caller = T::CrossAccountId::from_eth(caller);627 let to = T::CrossAccountId::from_eth(to);628 let token = token_id.try_into()?;629 let budget = self630 .recorder631 .weight_calls_budget(<StructureWeight<T>>::find_parent());632633 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;634 Ok(())635 }636637 /// @notice Burns a specific ERC721 token.638 /// @dev Throws unless `msg.sender` is the current owner or an authorized639 /// operator for this NFT. Throws if `from` is not the current owner. Throws640 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.641 /// @param from The current owner of the NFT642 /// @param tokenId The NFT to transfer643 #[weight(<SelfWeightOf<T>>::burn_from())]644 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {645 let caller = T::CrossAccountId::from_eth(caller);646 let from = T::CrossAccountId::from_eth(from);647 let token = token_id.try_into()?;648 let budget = self649 .recorder650 .weight_calls_budget(<StructureWeight<T>>::find_parent());651652 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)653 .map_err(dispatch_to_evm::<T>)?;654 Ok(())655 }656657 /// @notice Returns next free NFT ID.658 fn next_token_id(&self) -> Result<uint256> {659 self.consume_store_reads(1)?;660 Ok(<TokensMinted<T>>::get(self.id)661 .checked_add(1)662 .ok_or("item id overflow")?663 .into())664 }665666 /// @notice Function to mint multiple tokens.667 /// @dev `tokenIds` should be an array of consecutive numbers and first number668 /// should be obtained with `nextTokenId` method669 /// @param to The new owner670 /// @param tokenIds IDs of the minted NFTs671 #[solidity(hide)]672 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]673 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {674 let caller = T::CrossAccountId::from_eth(caller);675 let to = T::CrossAccountId::from_eth(to);676 let mut expected_index = <TokensMinted<T>>::get(self.id)677 .checked_add(1)678 .ok_or("item id overflow")?;679 let budget = self680 .recorder681 .weight_calls_budget(<StructureWeight<T>>::find_parent());682683 let total_tokens = token_ids.len();684 for id in token_ids.into_iter() {685 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;686 if id != expected_index {687 return Err("item id should be next".into());688 }689 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;690 }691 let data = (0..total_tokens)692 .map(|_| CreateItemData::<T> {693 properties: BoundedVec::default(),694 owner: to.clone(),695 })696 .collect();697698 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)699 .map_err(dispatch_to_evm::<T>)?;700 Ok(true)701 }702703 /// @notice Function to mint multiple tokens with the given tokenUris.704 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive705 /// numbers and first number should be obtained with `nextTokenId` method706 /// @param to The new owner707 /// @param tokens array of pairs of token ID and token URI for minted tokens708 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]709 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]710 fn mint_bulk_with_token_uri(711 &mut self,712 caller: caller,713 to: address,714 tokens: Vec<(uint256, string)>,715 ) -> Result<bool> {716 let key = key::url();717 let caller = T::CrossAccountId::from_eth(caller);718 let to = T::CrossAccountId::from_eth(to);719 let mut expected_index = <TokensMinted<T>>::get(self.id)720 .checked_add(1)721 .ok_or("item id overflow")?;722 let budget = self723 .recorder724 .weight_calls_budget(<StructureWeight<T>>::find_parent());725726 let mut data = Vec::with_capacity(tokens.len());727 for (id, token_uri) in tokens {728 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;729 if id != expected_index {730 return Err("item id should be next".into());731 }732 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;733734 let mut properties = CollectionPropertiesVec::default();735 properties736 .try_push(Property {737 key: key.clone(),738 value: token_uri739 .into_bytes()740 .try_into()741 .map_err(|_| "token uri is too long")?,742 })743 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;744745 data.push(CreateItemData::<T> {746 properties,747 owner: to.clone(),748 });749 }750751 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)752 .map_err(dispatch_to_evm::<T>)?;753 Ok(true)754 }755}756757#[solidity_interface(758 name = UniqueNFT,759 is(760 ERC721,761 ERC721Enumerable,762 ERC721UniqueExtensions,763 ERC721UniqueMintable,764 ERC721Burnable,765 ERC721Metadata(if(this.flags.erc721metadata)),766 Collection(via(common_mut returns CollectionHandle<T>)),767 TokenProperties,768 )769)]770impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}771772// Not a tests, but code generators773generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);774generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);775776impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>777where778 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,779{780 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");781782 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {783 call::<T, UniqueNFTCall<T>, _, _>(handle, self)784 }785}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37 CollectionHandle, CollectionPropertyPermissions,38 eth::convert_tuple_to_cross_account,39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::call;42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4344use crate::{45 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,46 SelfWeightOf, weights::WeightInfo, TokenProperties,47};4849/// @title A contract that allows to set and delete token properties and change token property permissions.50#[solidity_interface(name = TokenProperties)]51impl<T: Config> NonfungibleHandle<T> {52 /// @notice Set permissions for token property.53 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.54 /// @param key Property key.55 /// @param isMutable Permission to mutate property.56 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.57 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.58 fn set_token_property_permission(59 &mut self,60 caller: caller,61 key: string,62 is_mutable: bool,63 collection_admin: bool,64 token_owner: bool,65 ) -> Result<()> {66 let caller = T::CrossAccountId::from_eth(caller);67 <Pallet<T>>::set_property_permission(68 self,69 &caller,70 PropertyKeyPermission {71 key: <Vec<u8>>::from(key)72 .try_into()73 .map_err(|_| "too long key")?,74 permission: PropertyPermission {75 mutable: is_mutable,76 collection_admin,77 token_owner,78 },79 },80 )81 .map_err(dispatch_to_evm::<T>)82 }8384 /// @notice Set token property value.85 /// @dev Throws error if `msg.sender` has no permission to edit the property.86 /// @param tokenId ID of the token.87 /// @param key Property key.88 /// @param value Property value.89 fn set_property(90 &mut self,91 caller: caller,92 token_id: uint256,93 key: string,94 value: bytes,95 ) -> Result<()> {96 let caller = T::CrossAccountId::from_eth(caller);97 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;98 let key = <Vec<u8>>::from(key)99 .try_into()100 .map_err(|_| "key too long")?;101 let value = value.0.try_into().map_err(|_| "value too long")?;102103 let nesting_budget = self104 .recorder105 .weight_calls_budget(<StructureWeight<T>>::find_parent());106107 <Pallet<T>>::set_token_property(108 self,109 &caller,110 TokenId(token_id),111 Property { key, value },112 &nesting_budget,113 )114 .map_err(dispatch_to_evm::<T>)115 }116117 /// @notice Delete token property value.118 /// @dev Throws error if `msg.sender` has no permission to edit the property.119 /// @param tokenId ID of the token.120 /// @param key Property key.121 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {122 let caller = T::CrossAccountId::from_eth(caller);123 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;124 let key = <Vec<u8>>::from(key)125 .try_into()126 .map_err(|_| "key too long")?;127128 let nesting_budget = self129 .recorder130 .weight_calls_budget(<StructureWeight<T>>::find_parent());131132 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)133 .map_err(dispatch_to_evm::<T>)134 }135136 /// @notice Get token property value.137 /// @dev Throws error if key not found138 /// @param tokenId ID of the token.139 /// @param key Property key.140 /// @return Property value bytes141 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {142 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;143 let key = <Vec<u8>>::from(key)144 .try_into()145 .map_err(|_| "key too long")?;146147 let props = <TokenProperties<T>>::get((self.id, token_id));148 let prop = props.get(&key).ok_or("key not found")?;149150 Ok(prop.to_vec().into())151 }152}153154#[derive(ToLog)]155pub enum ERC721Events {156 /// @dev This emits when ownership of any NFT changes by any mechanism.157 /// This event emits when NFTs are created (`from` == 0) and destroyed158 /// (`to` == 0). Exception: during contract creation, any number of NFTs159 /// may be created and assigned without emitting Transfer. At the time of160 /// any transfer, the approved address for that NFT (if any) is reset to none.161 Transfer {162 #[indexed]163 from: address,164 #[indexed]165 to: address,166 #[indexed]167 token_id: uint256,168 },169 /// @dev This emits when the approved address for an NFT is changed or170 /// reaffirmed. The zero address indicates there is no approved address.171 /// When a Transfer event emits, this also indicates that the approved172 /// address for that NFT (if any) is reset to none.173 Approval {174 #[indexed]175 owner: address,176 #[indexed]177 approved: address,178 #[indexed]179 token_id: uint256,180 },181 /// @dev This emits when an operator is enabled or disabled for an owner.182 /// The operator can manage all NFTs of the owner.183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721UniqueMintableEvents {195 #[allow(dead_code)]196 MintingFinished {},197}198199/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension200/// @dev See https://eips.ethereum.org/EIPS/eip-721201#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]202impl<T: Config> NonfungibleHandle<T>203where204 T::AccountId: From<[u8; 32]>,205{206 /// @notice A descriptive name for a collection of NFTs in this contract207 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`208 #[solidity(hide, rename_selector = "name")]209 fn name_proxy(&self) -> Result<string> {210 self.name()211 }212213 /// @notice An abbreviated name for NFTs in this contract214 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`215 #[solidity(hide, rename_selector = "symbol")]216 fn symbol_proxy(&self) -> Result<string> {217 self.symbol()218 }219220 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.221 ///222 /// @dev If the token has a `url` property and it is not empty, it is returned.223 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.224 /// If the collection property `baseURI` is empty or absent, return "" (empty string)225 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix226 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).227 ///228 /// @return token's const_metadata229 #[solidity(rename_selector = "tokenURI")]230 fn token_uri(&self, token_id: uint256) -> Result<string> {231 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;232233 match get_token_property(self, token_id_u32, &key::url()).as_deref() {234 Err(_) | Ok("") => (),235 Ok(url) => {236 return Ok(url.into());237 }238 };239240 let base_uri =241 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())242 .map(BoundedVec::into_inner)243 .map(string::from_utf8)244 .transpose()245 .map_err(|e| {246 Error::Revert(alloc::format!(247 "Can not convert value \"baseURI\" to string with error \"{}\"",248 e249 ))250 })?;251252 let base_uri = match base_uri.as_deref() {253 None | Some("") => {254 return Ok("".into());255 }256 Some(base_uri) => base_uri.into(),257 };258259 Ok(260 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {261 Err(_) | Ok("") => base_uri,262 Ok(suffix) => base_uri + suffix,263 },264 )265 }266}267268/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension269/// @dev See https://eips.ethereum.org/EIPS/eip-721270#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]271impl<T: Config> NonfungibleHandle<T> {272 /// @notice Enumerate valid NFTs273 /// @param index A counter less than `totalSupply()`274 /// @return The token identifier for the `index`th NFT,275 /// (sort order not specified)276 fn token_by_index(&self, index: uint256) -> Result<uint256> {277 Ok(index)278 }279280 /// @dev Not implemented281 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {282 // TODO: Not implemetable283 Err("not implemented".into())284 }285286 /// @notice Count NFTs tracked by this contract287 /// @return A count of valid NFTs tracked by this contract, where each one of288 /// them has an assigned and queryable owner not equal to the zero address289 fn total_supply(&self) -> Result<uint256> {290 self.consume_store_reads(1)?;291 Ok(<Pallet<T>>::total_supply(self).into())292 }293}294295/// @title ERC-721 Non-Fungible Token Standard296/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md297#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]298impl<T: Config> NonfungibleHandle<T> {299 /// @notice Count all NFTs assigned to an owner300 /// @dev NFTs assigned to the zero address are considered invalid, and this301 /// function throws for queries about the zero address.302 /// @param owner An address for whom to query the balance303 /// @return The number of NFTs owned by `owner`, possibly zero304 fn balance_of(&self, owner: address) -> Result<uint256> {305 self.consume_store_reads(1)?;306 let owner = T::CrossAccountId::from_eth(owner);307 let balance = <AccountBalance<T>>::get((self.id, owner));308 Ok(balance.into())309 }310 /// @notice Find the owner of an NFT311 /// @dev NFTs assigned to zero address are considered invalid, and queries312 /// about them do throw.313 /// @param tokenId The identifier for an NFT314 /// @return The address of the owner of the NFT315 fn owner_of(&self, token_id: uint256) -> Result<address> {316 self.consume_store_reads(1)?;317 let token: TokenId = token_id.try_into()?;318 Ok(*<TokenData<T>>::get((self.id, token))319 .ok_or("token not found")?320 .owner321 .as_eth())322 }323 /// @dev Not implemented324 #[solidity(rename_selector = "safeTransferFrom")]325 fn safe_transfer_from_with_data(326 &mut self,327 _from: address,328 _to: address,329 _token_id: uint256,330 _data: bytes,331 ) -> Result<void> {332 // TODO: Not implemetable333 Err("not implemented".into())334 }335 /// @dev Not implemented336 fn safe_transfer_from(337 &mut self,338 _from: address,339 _to: address,340 _token_id: uint256,341 ) -> Result<void> {342 // TODO: Not implemetable343 Err("not implemented".into())344 }345346 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE347 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE348 /// THEY MAY BE PERMANENTLY LOST349 /// @dev Throws unless `msg.sender` is the current owner or an authorized350 /// operator for this NFT. Throws if `from` is not the current owner. Throws351 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.352 /// @param from The current owner of the NFT353 /// @param to The new owner354 /// @param tokenId The NFT to transfer355 #[weight(<SelfWeightOf<T>>::transfer_from())]356 fn transfer_from(357 &mut self,358 caller: caller,359 from: address,360 to: address,361 token_id: uint256,362 ) -> Result<void> {363 let caller = T::CrossAccountId::from_eth(caller);364 let from = T::CrossAccountId::from_eth(from);365 let to = T::CrossAccountId::from_eth(to);366 let token = token_id.try_into()?;367 let budget = self368 .recorder369 .weight_calls_budget(<StructureWeight<T>>::find_parent());370371 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)372 .map_err(dispatch_to_evm::<T>)?;373 Ok(())374 }375376 /// @notice Set or reaffirm the approved address for an NFT377 /// @dev The zero address indicates there is no approved address.378 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized379 /// operator of the current owner.380 /// @param approved The new approved NFT controller381 /// @param tokenId The NFT to approve382 #[weight(<SelfWeightOf<T>>::approve())]383 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {384 let caller = T::CrossAccountId::from_eth(caller);385 let approved = T::CrossAccountId::from_eth(approved);386 let token = token_id.try_into()?;387388 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))389 .map_err(dispatch_to_evm::<T>)?;390 Ok(())391 }392393 /// @dev Not implemented394 fn set_approval_for_all(395 &mut self,396 _caller: caller,397 _operator: address,398 _approved: bool,399 ) -> Result<void> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403404 /// @dev Not implemented405 fn get_approved(&self, _token_id: uint256) -> Result<address> {406 // TODO: Not implemetable407 Err("not implemented".into())408 }409410 /// @dev Not implemented411 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {412 // TODO: Not implemetable413 Err("not implemented".into())414 }415}416417/// @title ERC721 Token that can be irreversibly burned (destroyed).418#[solidity_interface(name = ERC721Burnable)]419impl<T: Config> NonfungibleHandle<T> {420 /// @notice Burns a specific ERC721 token.421 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized422 /// operator of the current owner.423 /// @param tokenId The NFT to approve424 #[weight(<SelfWeightOf<T>>::burn_item())]425 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {426 let caller = T::CrossAccountId::from_eth(caller);427 let token = token_id.try_into()?;428429 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;430 Ok(())431 }432}433434/// @title ERC721 minting logic.435#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]436impl<T: Config> NonfungibleHandle<T> {437 fn minting_finished(&self) -> Result<bool> {438 Ok(false)439 }440441 /// @notice Function to mint token.442 /// @param to The new owner443 /// @return uint256 The id of the newly minted token444 #[weight(<SelfWeightOf<T>>::create_item())]445 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {446 let token_id: uint256 = <TokensMinted<T>>::get(self.id)447 .checked_add(1)448 .ok_or("item id overflow")?449 .into();450 self.mint_check_id(caller, to, token_id)?;451 Ok(token_id)452 }453454 /// @notice Function to mint token.455 /// @dev `tokenId` should be obtained with `nextTokenId` method,456 /// unlike standard, you can't specify it manually457 /// @param to The new owner458 /// @param tokenId ID of the minted NFT459 #[solidity(hide, rename_selector = "mint")]460 #[weight(<SelfWeightOf<T>>::create_item())]461 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {462 let caller = T::CrossAccountId::from_eth(caller);463 let to = T::CrossAccountId::from_eth(to);464 let token_id: u32 = token_id.try_into()?;465 let budget = self466 .recorder467 .weight_calls_budget(<StructureWeight<T>>::find_parent());468469 if <TokensMinted<T>>::get(self.id)470 .checked_add(1)471 .ok_or("item id overflow")?472 != token_id473 {474 return Err("item id should be next".into());475 }476477 <Pallet<T>>::create_item(478 self,479 &caller,480 CreateItemData::<T> {481 properties: BoundedVec::default(),482 owner: to,483 },484 &budget,485 )486 .map_err(dispatch_to_evm::<T>)?;487488 Ok(true)489 }490491 /// @notice Function to mint token with the given tokenUri.492 /// @param to The new owner493 /// @param tokenUri Token URI that would be stored in the NFT properties494 /// @return uint256 The id of the newly minted token495 #[solidity(rename_selector = "mintWithTokenURI")]496 #[weight(<SelfWeightOf<T>>::create_item())]497 fn mint_with_token_uri(498 &mut self,499 caller: caller,500 to: address,501 token_uri: string,502 ) -> Result<uint256> {503 let token_id: uint256 = <TokensMinted<T>>::get(self.id)504 .checked_add(1)505 .ok_or("item id overflow")?506 .into();507 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;508 Ok(token_id)509 }510511 /// @notice Function to mint token with the given tokenUri.512 /// @dev `tokenId` should be obtained with `nextTokenId` method,513 /// unlike standard, you can't specify it manually514 /// @param to The new owner515 /// @param tokenId ID of the minted NFT516 /// @param tokenUri Token URI that would be stored in the NFT properties517 #[solidity(hide, rename_selector = "mintWithTokenURI")]518 #[weight(<SelfWeightOf<T>>::create_item())]519 fn mint_with_token_uri_check_id(520 &mut self,521 caller: caller,522 to: address,523 token_id: uint256,524 token_uri: string,525 ) -> Result<bool> {526 let key = key::url();527 let permission = get_token_permission::<T>(self.id, &key)?;528 if !permission.collection_admin {529 return Err("Operation is not allowed".into());530 }531532 let caller = T::CrossAccountId::from_eth(caller);533 let to = T::CrossAccountId::from_eth(to);534 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;535 let budget = self536 .recorder537 .weight_calls_budget(<StructureWeight<T>>::find_parent());538539 if <TokensMinted<T>>::get(self.id)540 .checked_add(1)541 .ok_or("item id overflow")?542 != token_id543 {544 return Err("item id should be next".into());545 }546547 let mut properties = CollectionPropertiesVec::default();548 properties549 .try_push(Property {550 key,551 value: token_uri552 .into_bytes()553 .try_into()554 .map_err(|_| "token uri is too long")?,555 })556 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;557558 <Pallet<T>>::create_item(559 self,560 &caller,561 CreateItemData::<T> {562 properties,563 owner: to,564 },565 &budget,566 )567 .map_err(dispatch_to_evm::<T>)?;568 Ok(true)569 }570571 /// @dev Not implemented572 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {573 Err("not implementable".into())574 }575}576577fn get_token_property<T: Config>(578 collection: &CollectionHandle<T>,579 token_id: u32,580 key: &up_data_structs::PropertyKey,581) -> Result<string> {582 collection.consume_store_reads(1)?;583 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))584 .map_err(|_| Error::Revert("Token properties not found".into()))?;585 if let Some(property) = properties.get(key) {586 return Ok(string::from_utf8_lossy(property).into());587 }588589 Err("Property tokenURI not found".into())590}591592fn get_token_permission<T: Config>(593 collection_id: CollectionId,594 key: &PropertyKey,595) -> Result<PropertyPermission> {596 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)597 .map_err(|_| Error::Revert("No permissions for collection".into()))?;598 let a = token_property_permissions599 .get(key)600 .map(Clone::clone)601 .ok_or_else(|| {602 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();603 Error::Revert(alloc::format!("No permission for key {}", key))604 })?;605 Ok(a)606}607608/// @title Unique extensions for ERC721.609#[solidity_interface(name = ERC721UniqueExtensions)]610impl<T: Config> NonfungibleHandle<T>611where612 T::AccountId: From<[u8; 32]>,613{614 /// @notice A descriptive name for a collection of NFTs in this contract615 fn name(&self) -> Result<string> {616 Ok(decode_utf16(self.name.iter().copied())617 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))618 .collect::<string>())619 }620621 /// @notice An abbreviated name for NFTs in this contract622 fn symbol(&self) -> Result<string> {623 Ok(string::from_utf8_lossy(&self.token_prefix).into())624 }625626 /// @notice Set or reaffirm the approved address for an NFT627 /// @dev The zero address indicates there is no approved address.628 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized629 /// operator of the current owner.630 /// @param approved The new substrate address approved NFT controller631 /// @param tokenId The NFT to approve632 #[weight(<SelfWeightOf<T>>::approve())]633 fn approve_cross(634 &mut self,635 caller: caller,636 approved: (address, uint256),637 token_id: uint256,638 ) -> Result<void> {639 let caller = T::CrossAccountId::from_eth(caller);640 let approved = convert_tuple_to_cross_account::<T>(approved)?;641 let token = token_id.try_into()?;642643 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))644 .map_err(dispatch_to_evm::<T>)?;645 Ok(())646 }647648 /// @notice Transfer ownership of an NFT649 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`650 /// is the zero address. Throws if `tokenId` is not a valid NFT.651 /// @param to The new owner652 /// @param tokenId The NFT to transfer653 #[weight(<SelfWeightOf<T>>::transfer())]654 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {655 let caller = T::CrossAccountId::from_eth(caller);656 let to = T::CrossAccountId::from_eth(to);657 let token = token_id.try_into()?;658 let budget = self659 .recorder660 .weight_calls_budget(<StructureWeight<T>>::find_parent());661662 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;663 Ok(())664 }665666 /// @notice Transfer ownership of an NFT from cross account address to cross account address667 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`668 /// is the zero address. Throws if `tokenId` is not a valid NFT.669 /// @param from Cross acccount address of current owner670 /// @param to Cross acccount address of new owner671 /// @param tokenId The NFT to transfer672 #[weight(<SelfWeightOf<T>>::transfer())]673 fn transfer_from_cross(674 &mut self,675 caller: caller,676 from: (address, uint256),677 to: (address, uint256),678 token_id: uint256,679 ) -> Result<void> {680 let caller = T::CrossAccountId::from_eth(caller);681 let from = convert_tuple_to_cross_account::<T>(from)?;682 let to = convert_tuple_to_cross_account::<T>(to)?;683 let token_id = token_id.try_into()?;684 let budget = self685 .recorder686 .weight_calls_budget(<StructureWeight<T>>::find_parent());687 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)688 .map_err(dispatch_to_evm::<T>)?;689 Ok(())690 }691692 /// @notice Burns a specific ERC721 token.693 /// @dev Throws unless `msg.sender` is the current owner or an authorized694 /// operator for this NFT. Throws if `from` is not the current owner. Throws695 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.696 /// @param from The current owner of the NFT697 /// @param tokenId The NFT to transfer698 #[weight(<SelfWeightOf<T>>::burn_from())]699 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {700 let caller = T::CrossAccountId::from_eth(caller);701 let from = T::CrossAccountId::from_eth(from);702 let token = token_id.try_into()?;703 let budget = self704 .recorder705 .weight_calls_budget(<StructureWeight<T>>::find_parent());706707 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)708 .map_err(dispatch_to_evm::<T>)?;709 Ok(())710 }711712 /// @notice Burns a specific ERC721 token.713 /// @dev Throws unless `msg.sender` is the current owner or an authorized714 /// operator for this NFT. Throws if `from` is not the current owner. Throws715 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.716 /// @param from The current owner of the NFT717 /// @param tokenId The NFT to transfer718 #[weight(<SelfWeightOf<T>>::burn_from())]719 fn burn_from_cross(720 &mut self,721 caller: caller,722 from: (address, uint256),723 token_id: uint256,724 ) -> Result<void> {725 let caller = T::CrossAccountId::from_eth(caller);726 let from = convert_tuple_to_cross_account::<T>(from)?;727 let token = token_id.try_into()?;728 let budget = self729 .recorder730 .weight_calls_budget(<StructureWeight<T>>::find_parent());731732 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)733 .map_err(dispatch_to_evm::<T>)?;734 Ok(())735 }736737 /// @notice Returns next free NFT ID.738 fn next_token_id(&self) -> Result<uint256> {739 self.consume_store_reads(1)?;740 Ok(<TokensMinted<T>>::get(self.id)741 .checked_add(1)742 .ok_or("item id overflow")?743 .into())744 }745746 /// @notice Function to mint multiple tokens.747 /// @dev `tokenIds` should be an array of consecutive numbers and first number748 /// should be obtained with `nextTokenId` method749 /// @param to The new owner750 /// @param tokenIds IDs of the minted NFTs751 #[solidity(hide)]752 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]753 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {754 let caller = T::CrossAccountId::from_eth(caller);755 let to = T::CrossAccountId::from_eth(to);756 let mut expected_index = <TokensMinted<T>>::get(self.id)757 .checked_add(1)758 .ok_or("item id overflow")?;759 let budget = self760 .recorder761 .weight_calls_budget(<StructureWeight<T>>::find_parent());762763 let total_tokens = token_ids.len();764 for id in token_ids.into_iter() {765 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;766 if id != expected_index {767 return Err("item id should be next".into());768 }769 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;770 }771 let data = (0..total_tokens)772 .map(|_| CreateItemData::<T> {773 properties: BoundedVec::default(),774 owner: to.clone(),775 })776 .collect();777778 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)779 .map_err(dispatch_to_evm::<T>)?;780 Ok(true)781 }782783 /// @notice Function to mint multiple tokens with the given tokenUris.784 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive785 /// numbers and first number should be obtained with `nextTokenId` method786 /// @param to The new owner787 /// @param tokens array of pairs of token ID and token URI for minted tokens788 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]789 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]790 fn mint_bulk_with_token_uri(791 &mut self,792 caller: caller,793 to: address,794 tokens: Vec<(uint256, string)>,795 ) -> Result<bool> {796 let key = key::url();797 let caller = T::CrossAccountId::from_eth(caller);798 let to = T::CrossAccountId::from_eth(to);799 let mut expected_index = <TokensMinted<T>>::get(self.id)800 .checked_add(1)801 .ok_or("item id overflow")?;802 let budget = self803 .recorder804 .weight_calls_budget(<StructureWeight<T>>::find_parent());805806 let mut data = Vec::with_capacity(tokens.len());807 for (id, token_uri) in tokens {808 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;809 if id != expected_index {810 return Err("item id should be next".into());811 }812 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;813814 let mut properties = CollectionPropertiesVec::default();815 properties816 .try_push(Property {817 key: key.clone(),818 value: token_uri819 .into_bytes()820 .try_into()821 .map_err(|_| "token uri is too long")?,822 })823 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;824825 data.push(CreateItemData::<T> {826 properties,827 owner: to.clone(),828 });829 }830831 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)832 .map_err(dispatch_to_evm::<T>)?;833 Ok(true)834 }835}836837#[solidity_interface(838 name = UniqueNFT,839 is(840 ERC721,841 ERC721Enumerable,842 ERC721UniqueExtensions,843 ERC721UniqueMintable,844 ERC721Burnable,845 ERC721Metadata(if(this.flags.erc721metadata)),846 Collection(via(common_mut returns CollectionHandle<T>)),847 TokenProperties,848 )849)]850impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}851852// Not a tests, but code generators853generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);854generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);855856impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>857where858 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,859{860 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");861862 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {863 call::<T, UniqueNFTCall<T>, _, _>(handle, self)864 }865}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
@@ -91,7 +91,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
contract Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -145,6 +145,19 @@
dummy = 0;
}
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+ require(false, stub_error);
+ sponsor;
+ dummy = 0;
+ }
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -177,10 +190,10 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() public view returns (Tuple17 memory) {
+ function collectionSponsor() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple6(0x0000000000000000000000000000000000000000, 0);
}
/// Set limits for the collection.
@@ -228,6 +241,26 @@
}
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+ require(false, stub_error);
+ newAdmin;
+ dummy = 0;
+ }
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) public {
+ require(false, stub_error);
+ admin;
+ dummy = 0;
+ }
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -307,6 +340,17 @@
dummy = 0;
}
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -318,6 +362,17 @@
dummy = 0;
}
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -342,6 +397,19 @@
return false;
}
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+ require(false, stub_error);
+ user;
+ dummy;
+ return false;
+ }
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -355,14 +423,14 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() public view returns (Tuple17 memory) {
+ function collectionOwner() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple6(0x0000000000000000000000000000000000000000, 0);
}
/// Changes collection owner to another account
@@ -383,17 +451,23 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0x5813216b,
/// or in textual repr: collectionAdmins()
- function collectionAdmins() public view returns (Tuple17[] memory) {
+ function collectionAdmins() public view returns (Tuple6[] memory) {
require(false, stub_error);
dummy;
- return new Tuple17[](0);
+ return new Tuple6[](0);
}
-}
-/// @dev anonymous struct
-struct Tuple17 {
- address field_0;
- uint256 field_1;
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) public {
+ require(false, stub_error);
+ newOwner;
+ dummy = 0;
+ }
}
/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
@@ -540,7 +614,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x4468500d
+/// @dev the ERC-165 identifier for this interface is 0x244543ee
contract ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -560,6 +634,21 @@
return "";
}
+ /// @notice Set or reaffirm the approved address for an NFT
+ /// @dev The zero address indicates there is no approved address.
+ /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ /// operator of the current owner.
+ /// @param approved The new substrate address approved NFT controller
+ /// @param tokenId The NFT to approve
+ /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// or in textual repr: approveCross((address,uint256),uint256)
+ function approveCross(Tuple6 memory approved, uint256 tokenId) public {
+ require(false, stub_error);
+ approved;
+ tokenId;
+ dummy = 0;
+ }
+
/// @notice Transfer ownership of an NFT
/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
/// is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -574,6 +663,26 @@
dummy = 0;
}
+ /// @notice Transfer ownership of an NFT from cross account address to cross account address
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from Cross acccount address of current owner
+ /// @param to Cross acccount address of new owner
+ /// @param tokenId The NFT to transfer
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 tokenId
+ ) public {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ dummy = 0;
+ }
+
/// @notice Burns a specific ERC721 token.
/// @dev Throws unless `msg.sender` is the current owner or an authorized
/// operator for this NFT. Throws if `from` is not the current owner. Throws
@@ -589,6 +698,21 @@
dummy = 0;
}
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this NFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from The current owner of the NFT
+ /// @param tokenId The NFT to transfer
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 tokenId) public {
+ require(false, stub_error);
+ from;
+ tokenId;
+ dummy = 0;
+ }
+
/// @notice Returns next free NFT ID.
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
@@ -619,7 +743,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, Tuple6[] memory tokens) public returns (bool) {
+ // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
// require(false, stub_error);
// to;
// tokens;
@@ -630,11 +754,17 @@
}
/// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
uint256 field_0;
string field_1;
}
+/// @dev anonymous struct
+struct Tuple6 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -30,6 +30,7 @@
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions,
erc::{CommonEvmHandler, CollectionCall, static_property::key},
+ eth::convert_tuple_to_cross_account,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm};
@@ -194,7 +195,10 @@
}
#[solidity_interface(name = ERC721Metadata)]
-impl<T: Config> RefungibleHandle<T> {
+impl<T: Config> RefungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev real implementation of this function lies in `ERC721UniqueExtensions`
#[solidity(hide, rename_selector = "name")]
@@ -631,7 +635,10 @@
/// @title Unique extensions for ERC721.
#[solidity_interface(name = ERC721UniqueExtensions)]
-impl<T: Config> RefungibleHandle<T> {
+impl<T: Config> RefungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
/// @notice A descriptive name for a collection of NFTs in this contract
fn name(&self) -> Result<string> {
Ok(decode_utf16(self.name.iter().copied())
@@ -659,14 +666,44 @@
.recorder
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- let balance = balance(&self, token, &caller)?;
- ensure_single_owner(&self, token, balance)?;
+ let balance = balance(self, token, &caller)?;
+ ensure_single_owner(self, token, balance)?;
<Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)
.map_err(dispatch_to_evm::<T>)?;
Ok(())
}
+ /// @notice Transfer ownership of an RFT
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param to The new owner
+ /// @param tokenId The RFT to transfer
+ #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]
+ fn transfer_from_cross(
+ &mut self,
+ caller: caller,
+ from: (address, uint256),
+ to: (address, uint256),
+ token_id: uint256,
+ ) -> Result<void> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = convert_tuple_to_cross_account::<T>(from)?;
+ let to = convert_tuple_to_cross_account::<T>(to)?;
+ let token_id = token_id.try_into()?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ let balance = balance(self, token_id, &from)?;
+ ensure_single_owner(self, token_id, balance)?;
+
+ Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
+
/// @notice Burns a specific ERC721 token.
/// @dev Throws unless `msg.sender` is the current owner or an authorized
/// operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -683,8 +720,37 @@
.recorder
.weight_calls_budget(<StructureWeight<T>>::find_parent());
- let balance = balance(&self, token, &caller)?;
- ensure_single_owner(&self, token, balance)?;
+ let balance = balance(self, token, &from)?;
+ ensure_single_owner(self, token, balance)?;
+
+ <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
+
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this RFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param from The current owner of the RFT
+ /// @param tokenId The RFT to transfer
+ #[weight(<SelfWeightOf<T>>::burn_from())]
+ fn burn_from_cross(
+ &mut self,
+ caller: caller,
+ from: (address, uint256),
+ token_id: uint256,
+ ) -> Result<void> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = convert_tuple_to_cross_account::<T>(from)?;
+ let token = token_id.try_into()?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ let balance = balance(self, token, &from)?;
+ ensure_single_owner(self, token, balance)?;
<Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)
.map_err(dispatch_to_evm::<T>)?;
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
@@ -91,7 +91,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
contract Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -145,6 +145,19 @@
dummy = 0;
}
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) public {
+ require(false, stub_error);
+ sponsor;
+ dummy = 0;
+ }
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -177,10 +190,10 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() public view returns (Tuple17 memory) {
+ function collectionSponsor() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple6(0x0000000000000000000000000000000000000000, 0);
}
/// Set limits for the collection.
@@ -228,6 +241,26 @@
}
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) public {
+ require(false, stub_error);
+ newAdmin;
+ dummy = 0;
+ }
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) public {
+ require(false, stub_error);
+ admin;
+ dummy = 0;
+ }
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -307,6 +340,17 @@
dummy = 0;
}
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -318,6 +362,17 @@
dummy = 0;
}
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -342,6 +397,19 @@
return false;
}
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) {
+ require(false, stub_error);
+ user;
+ dummy;
+ return false;
+ }
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -355,14 +423,14 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() public view returns (Tuple17 memory) {
+ function collectionOwner() public view returns (Tuple6 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple6(0x0000000000000000000000000000000000000000, 0);
}
/// Changes collection owner to another account
@@ -383,17 +451,23 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0x5813216b,
/// or in textual repr: collectionAdmins()
- function collectionAdmins() public view returns (Tuple17[] memory) {
+ function collectionAdmins() public view returns (Tuple6[] memory) {
require(false, stub_error);
dummy;
- return new Tuple17[](0);
+ return new Tuple6[](0);
}
-}
-/// @dev anonymous struct
-struct Tuple17 {
- address field_0;
- uint256 field_1;
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) public {
+ require(false, stub_error);
+ newOwner;
+ dummy = 0;
+ }
}
/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
@@ -538,7 +612,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xef1eaacb
+/// @dev the ERC-165 identifier for this interface is 0x81feb398
contract ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -573,6 +647,26 @@
dummy = 0;
}
+ /// @notice Transfer ownership of an RFT
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param to The new owner
+ /// @param tokenId The RFT to transfer
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 tokenId
+ ) public {
+ require(false, stub_error);
+ from;
+ to;
+ tokenId;
+ dummy = 0;
+ }
+
/// @notice Burns a specific ERC721 token.
/// @dev Throws unless `msg.sender` is the current owner or an authorized
/// operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -589,6 +683,22 @@
dummy = 0;
}
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this RFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param from The current owner of the RFT
+ /// @param tokenId The RFT to transfer
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 tokenId) public {
+ require(false, stub_error);
+ from;
+ tokenId;
+ dummy = 0;
+ }
+
/// @notice Returns next free RFT ID.
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
@@ -620,7 +730,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, Tuple6[] memory tokens) public returns (bool) {
+ // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
// require(false, stub_error);
// to;
// tokens;
@@ -642,11 +752,17 @@
}
/// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
uint256 field_0;
string field_1;
}
+/// @dev anonymous struct
+struct Tuple6 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
tests/src/eth/allowlist.test.tsdiffbeforeafterboth--- a/tests/src/eth/allowlist.test.ts
+++ b/tests/src/eth/allowlist.test.ts
@@ -89,22 +89,22 @@
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
});
- // TODO: Temprorary off. Need refactor
- // itEth('Collection allowlist can be added and removed by [sub] address', async ({helper}) => {
- // const owner = await helper.eth.createAccountWithBalance(donor);
- // const user = donor;
-
- // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
- // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
- // await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+ //itEth('Collection allowlist can be added and removed by [cross] address', async ({helper}) => {
+ // const owner = await helper.eth.createAccountWithBalance(donor);
+ // const user = donor;
+ //
+ // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
+ // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+ // const userCross = helper.ethCrossAccount.fromKeyringPair(user);
+ //
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+ // await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+ //
+ // await collectionEvm.methods.removeFromCollectionAllowListCross(userCross).send({from: owner});
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+ //});
- // await collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
- // });
-
itEth('Collection allowlist can not be add and remove [eth] address by not owner', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const notOwner = await helper.eth.createAccountWithBalance(donor);
@@ -117,28 +117,27 @@
await expect(collectionEvm.methods.addToCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission');
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false;
await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner});
-
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true;
await expect(collectionEvm.methods.removeFromCollectionAllowList(user).call({from: notOwner})).to.be.rejectedWith('NoPermission');
expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true;
});
- // TODO: Temprorary off. Need refactor
- // itEth('Collection allowlist can not be add and remove [sub] address by not owner', async ({helper}) => {
- // const owner = await helper.eth.createAccountWithBalance(donor);
- // const notOwner = await helper.eth.createAccountWithBalance(donor);
- // const user = donor;
-
- // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
- // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
- // await expect(collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission');
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
- // await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner});
-
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
- // await expect(collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission');
- // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
- // });
+ //itEth('Collection allowlist can not be add and remove [cross] address by not owner', async ({helper}) => {
+ // const owner = await helper.eth.createAccountWithBalance(donor);
+ // const notOwner = await helper.eth.createAccountWithBalance(donor);
+ // const user = donor;
+ //
+ // const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
+ // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
+ //
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+ // const userCross = helper.ethCrossAccount.fromKeyringPair(user);
+ // await expect(collectionEvm.methods.addToCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission');
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false;
+ // await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+ //
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+ // await expect(collectionEvm.methods.removeFromCollectionAllowListCross(userCross).call({from: notOwner})).to.be.rejectedWith('NoPermission');
+ // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true;
+ //});
});
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -13,7 +13,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
interface Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -49,6 +49,15 @@
/// or in textual repr: setCollectionSponsor(address)
function setCollectionSponsor(address sponsor) external;
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -104,6 +113,18 @@
function contractAddress() external view returns (address);
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -153,6 +174,13 @@
/// or in textual repr: addToCollectionAllowList(address)
function addToCollectionAllowList(address user) external;
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) external;
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -160,6 +188,13 @@
/// or in textual repr: removeFromCollectionAllowList(address)
function removeFromCollectionAllowList(address user) external;
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -175,6 +210,14 @@
/// or in textual repr: isOwnerOrAdmin(address)
function isOwnerOrAdmin(address user) external view returns (bool);
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -184,7 +227,7 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
@@ -205,10 +248,22 @@
/// @dev EVM selector for this function is: 0x5813216b,
/// or in textual repr: collectionAdmins()
function collectionAdmins() external view returns (Tuple6[] memory);
+
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) external;
}
-/// @dev the ERC-165 identifier for this interface is 0x63034ac5
+/// @dev the ERC-165 identifier for this interface is 0x032e5926
interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// or in textual repr: approveCross((address,uint256),uint256)
+ function approveCross(Tuple6 memory spender, uint256 amount) external returns (bool);
+
/// Burn tokens from account
/// @dev Function that burns an `amount` of the tokens of a given account,
/// deducting from the sender's allowance for said account.
@@ -218,11 +273,28 @@
/// or in textual repr: burnFrom(address,uint256)
function burnFrom(address from, uint256 amount) external returns (bool);
+ /// Burn tokens from account
+ /// @dev Function that burns an `amount` of the tokens of a given account,
+ /// deducting from the sender's allowance for said account.
+ /// @param from The account whose tokens will be burnt.
+ /// @param amount The amount that will be burnt.
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 amount) external returns (bool);
+
/// Mint tokens for multiple accounts.
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
/// or in textual repr: mintBulk((address,uint256)[])
function mintBulk(Tuple6[] memory amounts) external returns (bool);
+
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 amount
+ ) external returns (bool);
}
/// @dev anonymous struct
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -62,7 +62,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
interface Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -98,6 +98,15 @@
/// or in textual repr: setCollectionSponsor(address)
function setCollectionSponsor(address sponsor) external;
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -120,7 +129,7 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() external view returns (Tuple17 memory);
+ function collectionSponsor() external view returns (Tuple6 memory);
/// Set limits for the collection.
/// @dev Throws error if limit not found.
@@ -153,6 +162,18 @@
function contractAddress() external view returns (address);
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -202,6 +223,13 @@
/// or in textual repr: addToCollectionAllowList(address)
function addToCollectionAllowList(address user) external;
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) external;
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -209,6 +237,13 @@
/// or in textual repr: removeFromCollectionAllowList(address)
function removeFromCollectionAllowList(address user) external;
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -224,6 +259,14 @@
/// or in textual repr: isOwnerOrAdmin(address)
function isOwnerOrAdmin(address user) external view returns (bool);
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -233,11 +276,11 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() external view returns (Tuple17 memory);
+ function collectionOwner() external view returns (Tuple6 memory);
/// Changes collection owner to another account
///
@@ -253,13 +296,15 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0x5813216b,
/// or in textual repr: collectionAdmins()
- function collectionAdmins() external view returns (Tuple17[] memory);
-}
+ function collectionAdmins() external view returns (Tuple6[] memory);
-/// @dev anonymous struct
-struct Tuple17 {
- address field_0;
- uint256 field_1;
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) external;
}
/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
@@ -357,7 +402,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x4468500d
+/// @dev the ERC-165 identifier for this interface is 0x244543ee
interface ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -369,6 +414,16 @@
/// or in textual repr: symbol()
function symbol() external view returns (string memory);
+ /// @notice Set or reaffirm the approved address for an NFT
+ /// @dev The zero address indicates there is no approved address.
+ /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ /// operator of the current owner.
+ /// @param approved The new substrate address approved NFT controller
+ /// @param tokenId The NFT to approve
+ /// @dev EVM selector for this function is: 0x0ecd0ab0,
+ /// or in textual repr: approveCross((address,uint256),uint256)
+ function approveCross(Tuple6 memory approved, uint256 tokenId) external;
+
/// @notice Transfer ownership of an NFT
/// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
/// is the zero address. Throws if `tokenId` is not a valid NFT.
@@ -378,6 +433,20 @@
/// or in textual repr: transfer(address,uint256)
function transfer(address to, uint256 tokenId) external;
+ /// @notice Transfer ownership of an NFT from cross account address to cross account address
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from Cross acccount address of current owner
+ /// @param to Cross acccount address of new owner
+ /// @param tokenId The NFT to transfer
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 tokenId
+ ) external;
+
/// @notice Burns a specific ERC721 token.
/// @dev Throws unless `msg.sender` is the current owner or an authorized
/// operator for this NFT. Throws if `from` is not the current owner. Throws
@@ -388,6 +457,16 @@
/// or in textual repr: burnFrom(address,uint256)
function burnFrom(address from, uint256 tokenId) external;
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this NFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from The current owner of the NFT
+ /// @param tokenId The NFT to transfer
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 tokenId) external;
+
/// @notice Returns next free NFT ID.
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
@@ -408,16 +487,22 @@
// /// @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, Tuple6[] memory tokens) external returns (bool);
+ // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
}
/// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
uint256 field_0;
string field_1;
}
+/// @dev anonymous struct
+struct Tuple6 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -62,7 +62,7 @@
}
/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x3af103fb
+/// @dev the ERC-165 identifier for this interface is 0x25d897dc
interface Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -98,6 +98,15 @@
/// or in textual repr: setCollectionSponsor(address)
function setCollectionSponsor(address sponsor) external;
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract.
+ /// @dev EVM selector for this function is: 0x84a1d5a8,
+ /// or in textual repr: setCollectionSponsorCross((address,uint256))
+ function setCollectionSponsorCross(Tuple6 memory sponsor) external;
+
/// Whether there is a pending sponsor.
/// @dev EVM selector for this function is: 0x058ac185,
/// or in textual repr: hasCollectionPendingSponsor()
@@ -120,7 +129,7 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() external view returns (Tuple17 memory);
+ function collectionSponsor() external view returns (Tuple6 memory);
/// Set limits for the collection.
/// @dev Throws error if limit not found.
@@ -153,6 +162,18 @@
function contractAddress() external view returns (address);
/// Add collection admin.
+ /// @param newAdmin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x859aa7d6,
+ /// or in textual repr: addCollectionAdminCross((address,uint256))
+ function addCollectionAdminCross(Tuple6 memory newAdmin) external;
+
+ /// Remove collection admin.
+ /// @param admin Cross account administrator address.
+ /// @dev EVM selector for this function is: 0x6c0cd173,
+ /// or in textual repr: removeCollectionAdminCross((address,uint256))
+ function removeCollectionAdminCross(Tuple6 memory admin) external;
+
+ /// Add collection admin.
/// @param newAdmin Address of the added administrator.
/// @dev EVM selector for this function is: 0x92e462c7,
/// or in textual repr: addCollectionAdmin(address)
@@ -202,6 +223,13 @@
/// or in textual repr: addToCollectionAllowList(address)
function addToCollectionAllowList(address user) external;
+ /// Add user to allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0xa0184a3a,
+ /// or in textual repr: addToCollectionAllowListCross((address,uint256))
+ function addToCollectionAllowListCross(Tuple6 memory user) external;
+
/// Remove the user from the allowed list.
///
/// @param user Address of a removed user.
@@ -209,6 +237,13 @@
/// or in textual repr: removeFromCollectionAllowList(address)
function removeFromCollectionAllowList(address user) external;
+ /// Remove user from allowed list.
+ ///
+ /// @param user User cross account address.
+ /// @dev EVM selector for this function is: 0x09ba452a,
+ /// or in textual repr: removeFromCollectionAllowListCross((address,uint256))
+ function removeFromCollectionAllowListCross(Tuple6 memory user) external;
+
/// Switch permission for minting.
///
/// @param mode Enable if "true".
@@ -224,6 +259,14 @@
/// or in textual repr: isOwnerOrAdmin(address)
function isOwnerOrAdmin(address user) external view returns (bool);
+ /// Check that account is the owner or admin of the collection
+ ///
+ /// @param user User cross account to verify
+ /// @return "true" if account is the owner or admin
+ /// @dev EVM selector for this function is: 0x3e75a905,
+ /// or in textual repr: isOwnerOrAdminCross((address,uint256))
+ function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool);
+
/// Returns collection type
///
/// @return `Fungible` or `NFT` or `ReFungible`
@@ -233,11 +276,11 @@
/// Get collection owner.
///
- /// @return Tuple with sponsor address and his substrate mirror.
+ /// @return Tuble with sponsor address and his substrate mirror.
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() external view returns (Tuple17 memory);
+ function collectionOwner() external view returns (Tuple6 memory);
/// Changes collection owner to another account
///
@@ -253,13 +296,15 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0x5813216b,
/// or in textual repr: collectionAdmins()
- function collectionAdmins() external view returns (Tuple17[] memory);
-}
+ function collectionAdmins() external view returns (Tuple6[] memory);
-/// @dev anonymous struct
-struct Tuple17 {
- address field_0;
- uint256 field_1;
+ /// Changes collection owner to another account
+ ///
+ /// @dev Owner can be changed only by current owner
+ /// @param newOwner new owner cross account
+ /// @dev EVM selector for this function is: 0xe5c9913f,
+ /// or in textual repr: setOwnerCross((address,uint256))
+ function setOwnerCross(Tuple6 memory newOwner) external;
}
/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
@@ -355,7 +400,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xef1eaacb
+/// @dev the ERC-165 identifier for this interface is 0x81feb398
interface ERC721UniqueExtensions is Dummy, ERC165 {
/// @notice A descriptive name for a collection of NFTs in this contract
/// @dev EVM selector for this function is: 0x06fdde03,
@@ -377,6 +422,20 @@
/// or in textual repr: transfer(address,uint256)
function transfer(address to, uint256 tokenId) external;
+ /// @notice Transfer ownership of an RFT
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param to The new owner
+ /// @param tokenId The RFT to transfer
+ /// @dev EVM selector for this function is: 0xd5cf430b,
+ /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256)
+ function transferFromCross(
+ Tuple6 memory from,
+ Tuple6 memory to,
+ uint256 tokenId
+ ) external;
+
/// @notice Burns a specific ERC721 token.
/// @dev Throws unless `msg.sender` is the current owner or an authorized
/// operator for this RFT. Throws if `from` is not the current owner. Throws
@@ -388,6 +447,17 @@
/// or in textual repr: burnFrom(address,uint256)
function burnFrom(address from, uint256 tokenId) external;
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this RFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.
+ /// Throws if RFT pieces have multiple owners.
+ /// @param from The current owner of the RFT
+ /// @param tokenId The RFT to transfer
+ /// @dev EVM selector for this function is: 0xbb2f5a58,
+ /// or in textual repr: burnFromCross((address,uint256),uint256)
+ function burnFromCross(Tuple6 memory from, uint256 tokenId) external;
+
/// @notice Returns next free RFT ID.
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
@@ -409,7 +479,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, Tuple6[] memory tokens) external returns (bool);
+ // function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
/// Returns EVM address for refungible token
///
@@ -420,11 +490,17 @@
}
/// @dev anonymous struct
-struct Tuple6 {
+struct Tuple8 {
uint256 field_0;
string field_1;
}
+/// @dev anonymous struct
+struct Tuple6 {
+ address field_0;
+ uint256 field_1;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
tests/src/eth/base.test.tsdiffbeforeafterboth--- a/tests/src/eth/base.test.ts
+++ b/tests/src/eth/base.test.ts
@@ -116,8 +116,8 @@
await checkInterface(helper, '0x780e9d63', true, true);
});
- itEth('ERC721UniqueExtensions - 0x4468500d - support', async ({helper}) => {
- await checkInterface(helper, '0x4468500d', true, true);
+ itEth('ERC721UniqueExtensions - 0x244543ee - support', async ({helper}) => {
+ await checkInterface(helper, '0x244543ee', true, true);
});
itEth('ERC721Burnable - 0x42966c68 - support', async ({helper}) => {
tests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionAdmin.test.ts
+++ b/tests/src/eth/collectionAdmin.test.ts
@@ -50,17 +50,18 @@
.to.be.eq(newAdmin.toLocaleLowerCase());
});
- itEth.skip('Add substrate admin by owner', async ({helper}) => {
+ itEth('Add cross account admin by owner', async ({helper, privateKey}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
+
const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');
const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
- const [newAdmin] = await helper.arrange.createAccounts([10n], donor);
- await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();
+
+ const newAdmin = await privateKey('//Bob');
+ const newAdminCross = helper.ethCrossAccount.fromKeyringPair(newAdmin);
+ await collectionEvm.methods.addCollectionAdminCross(newAdminCross).send();
- const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);
- expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())
- .to.be.eq(newAdmin.address.toLocaleLowerCase());
+ const adminList = await helper.collection.getAdmins(collectionId);
+ expect(adminList).to.be.like([{Substrate: newAdmin.address}]);
});
itEth('Verify owner or admin', async ({helper}) => {
tests/src/eth/fractionalizer/Fractionalizer.soldiffbeforeafterboth--- a/tests/src/eth/fractionalizer/Fractionalizer.sol
+++ b/tests/src/eth/fractionalizer/Fractionalizer.sol
@@ -84,7 +84,11 @@
) external payable onlyOwner {
require(rftCollection == address(0), "RFT collection is already set");
address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;
- rftCollection = CollectionHelpers(collectionHelpers).createRFTCollection{value: msg.value}(_name, _description, _tokenPrefix);
+ rftCollection = CollectionHelpers(collectionHelpers).createRFTCollection{value: msg.value}(
+ _name,
+ _description,
+ _tokenPrefix
+ );
emit RFTCollectionSet(rftCollection);
}
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -148,6 +148,49 @@
}
});
+ itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+ await collection.mint(alice, 200n, {Substrate: alice.address});
+ await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'ft');
+
+ const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});
+
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(alice);
+ const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});
+ const events = result.events;
+
+ expect(events).to.be.like({
+ Transfer: {
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(alice.address),
+ to: '0x0000000000000000000000000000000000000000',
+ value: '49',
+ },
+ },
+ Approval: {
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ returnValues: {
+ owner: helper.address.substrateToEth(alice.address),
+ spender: sender,
+ value: '51',
+ },
+ event: 'Approval',
+ },
+ });
+
+ const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});
+ expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);
+ });
+
itEth('Can perform transferFrom()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = await helper.eth.createAccountWithBalance(donor);
@@ -216,6 +259,54 @@
expect(+balance).to.equal(50);
}
});
+
+ itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+ const receiver = helper.eth.createAccount();
+
+ await collection.mint(alice, 200n, {Substrate: alice.address});
+ await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'ft');
+
+ const from = helper.ethCrossAccount.fromKeyringPair(alice);
+ const to = helper.ethCrossAccount.fromAddress(receiver);
+
+ const fromBalanceBefore = await collection.getBalance({Substrate: alice.address});
+ const toBalanceBefore = await collection.getBalance({Ethereum: receiver});
+
+ const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});
+
+ expect(result.events).to.be.like({
+ Transfer: {
+ address,
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(alice.address),
+ to: receiver,
+ value: '51',
+ },
+ },
+ Approval: {
+ address,
+ event: 'Approval',
+ returnValues: {
+ owner: helper.address.substrateToEth(alice.address),
+ spender: sender,
+ value: '49',
+ },
+ }});
+
+ const fromBalanceAfter = await collection.getBalance({Substrate: alice.address});
+ expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);
+ const toBalanceAfter = await collection.getBalance({Ethereum: receiver});
+ expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);
+ });
});
describe('Fungible: Fees', () => {
@@ -362,4 +453,44 @@
expect(event.returnValues.to).to.be.equal(receiver);
expect(event.returnValues.value).to.be.equal('51');
});
+
+ itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const sender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);
+
+ const receiver = helper.eth.createAccount();
+
+ await collection.mint(alice, 200n, {Substrate: alice.address});
+ await collection.approveTokens(alice, {Ethereum: sender}, 100n);
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'ft');
+
+ const from = helper.ethCrossAccount.fromKeyringPair(alice);
+ const to = helper.ethCrossAccount.fromAddress(receiver);
+
+ const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});
+
+ expect(result.events).to.be.like({
+ Transfer: {
+ address,
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(alice.address),
+ to: receiver,
+ value: '51',
+ },
+ },
+ Approval: {
+ address,
+ event: 'Approval',
+ returnValues: {
+ owner: helper.address.substrateToEth(alice.address),
+ spender: sender,
+ value: '49',
+ },
+ }});
+ });
});
tests/src/eth/fungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -60,6 +60,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newAdmin",
+ "type": "tuple"
+ }
+ ],
+ "name": "addCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "user", "type": "address" }
],
"name": "addToCollectionAllowList",
@@ -69,6 +86,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "addToCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "spender", "type": "address" }
],
@@ -98,6 +132,24 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "spender",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "approveCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "owner", "type": "address" }
],
"name": "balanceOf",
@@ -117,6 +169,24 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "burnFromCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "changeCollectionOwner",
@@ -228,6 +298,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "isOwnerOrAdminCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
@@ -270,6 +357,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "admin",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "removeCollectionSponsor",
"outputs": [],
@@ -286,6 +390,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeFromCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],
"name": "setCollectionAccess",
"outputs": [],
@@ -361,6 +482,40 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "sponsor",
+ "type": "tuple"
+ }
+ ],
+ "name": "setCollectionSponsorCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newOwner",
+ "type": "tuple"
+ }
+ ],
+ "name": "setOwnerCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
],
"name": "supportsInterface",
@@ -404,6 +559,33 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFromCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "uniqueCollectionType",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -246,6 +246,64 @@
}
});
+ itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await privateKey('//Bob');
+ const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const token = await collection.mintToken(alice, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ await token.approve(owner, {Ethereum: spender});
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});
+ const events = result.events.Transfer;
+
+ expect(events).to.be.like({
+ address,
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(owner.address),
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+ });
+
+ itEth('Can perform approveCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(alice, 100n);
+ const receiver = await privateKey('//Charlie');
+
+ const token = await collection.mintToken(alice, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ {
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner});
+ const event = result.events.Approval;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Approval',
+ returnValues: {
+ owner,
+ approved: helper.address.substrateToEth(receiver.address),
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+ });
+
itEth('Can perform transferFrom()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = await helper.eth.createAccountWithBalance(donor);
@@ -351,6 +409,40 @@
expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));
});
+ itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await privateKey('//Bob');
+ const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+ const receiver = await privateKey('//Charlie');
+
+ const token = await collection.mintToken(alice, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'nft');
+
+ await token.approve(owner, {Ethereum: spender});
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(owner.address),
+ to: helper.address.substrateToEth(receiver.address),
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await token.getOwner()).to.be.like({Substrate: receiver.address});
+ });
+
itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -91,6 +91,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newAdmin",
+ "type": "tuple"
+ }
+ ],
+ "name": "addCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "user", "type": "address" }
],
"name": "addToCollectionAllowList",
@@ -100,6 +117,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "addToCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "user", "type": "address" }
],
"name": "allowed",
@@ -119,6 +153,24 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "approved",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "approveCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "owner", "type": "address" }
],
"name": "balanceOf",
@@ -147,6 +199,24 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "burnFromCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "changeCollectionOwner",
@@ -163,7 +233,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17[]",
+ "internalType": "struct Tuple6[]",
"name": "",
"type": "tuple[]"
}
@@ -180,7 +250,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple6",
"name": "",
"type": "tuple"
}
@@ -204,7 +274,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple6",
"name": "",
"type": "tuple"
}
@@ -286,6 +356,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "isOwnerOrAdminCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
"inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
"name": "mint",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -352,6 +439,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "admin",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "removeCollectionSponsor",
"outputs": [],
@@ -369,6 +473,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeFromCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "from", "type": "address" },
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -476,6 +597,40 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "sponsor",
+ "type": "tuple"
+ }
+ ],
+ "name": "setCollectionSponsorCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newOwner",
+ "type": "tuple"
+ }
+ ],
+ "name": "setOwnerCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" },
{ "internalType": "string", "name": "key", "type": "string" },
{ "internalType": "bytes", "name": "value", "type": "bytes" }
@@ -570,6 +725,33 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "transferFromCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "uniqueCollectionType",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -227,6 +227,108 @@
}
});
+ itEth('Can perform burnFrom()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await helper.eth.createAccountWithBalance(alice, 100n);
+ const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const token = await collection.mintToken(alice, 100n, {Ethereum: owner});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);
+ const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);
+ await tokenContract.methods.repartition(15).send();
+ await tokenContract.methods.approve(spender, 15).send();
+
+ {
+ const result = await contract.methods.burnFrom(owner, token.tokenId).send({from: spender});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: owner,
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);
+ });
+
+ itEth('Can perform burnFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await privateKey('//Bob');
+ const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+
+ const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ await token.repartition(owner, 15n);
+ await token.approve(owner, {Ethereum: spender}, 15n);
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(owner.address),
+ to: '0x0000000000000000000000000000000000000000',
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);
+ });
+
+ itEth('Can perform transferFromCross()', async ({helper, privateKey}) => {
+ const alice = await privateKey('//Alice');
+ const collection = await helper.rft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});
+
+ const owner = await privateKey('//Bob');
+ const spender = await helper.eth.createAccountWithBalance(alice, 100n);
+ const receiver = await privateKey('//Charlie');
+
+ const token = await collection.mintToken(alice, 100n, {Substrate: owner.address});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(address, 'rft');
+
+ await token.repartition(owner, 15n);
+ await token.approve(owner, {Ethereum: spender}, 15n);
+
+ {
+ const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);
+ const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});
+ const event = result.events.Transfer;
+ expect(event).to.be.like({
+ address: helper.ethAddress.fromCollectionId(collection.collectionId),
+ event: 'Transfer',
+ returnValues: {
+ from: helper.address.substrateToEth(owner.address),
+ to: helper.address.substrateToEth(receiver.address),
+ tokenId: token.tokenId.toString(),
+ },
+ });
+ }
+
+ expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);
+ });
+
itEth('Can perform transfer()', async ({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
tests/src/eth/reFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -91,6 +91,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newAdmin",
+ "type": "tuple"
+ }
+ ],
+ "name": "addCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "user", "type": "address" }
],
"name": "addToCollectionAllowList",
@@ -100,6 +117,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "addToCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "user", "type": "address" }
],
"name": "allowed",
@@ -147,6 +181,24 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "burnFromCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "changeCollectionOwner",
@@ -163,7 +215,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17[]",
+ "internalType": "struct Tuple6[]",
"name": "",
"type": "tuple[]"
}
@@ -180,7 +232,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple6",
"name": "",
"type": "tuple"
}
@@ -204,7 +256,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple6",
"name": "",
"type": "tuple"
}
@@ -286,6 +338,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "isOwnerOrAdminCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
"inputs": [{ "internalType": "address", "name": "to", "type": "address" }],
"name": "mint",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -352,6 +421,23 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "admin",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeCollectionAdminCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "removeCollectionSponsor",
"outputs": [],
@@ -369,6 +455,23 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "user",
+ "type": "tuple"
+ }
+ ],
+ "name": "removeFromCollectionAllowListCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "from", "type": "address" },
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -476,6 +579,40 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "sponsor",
+ "type": "tuple"
+ }
+ ],
+ "name": "setCollectionSponsorCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "newOwner",
+ "type": "tuple"
+ }
+ ],
+ "name": "setOwnerCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "uint256", "name": "tokenId", "type": "uint256" },
{ "internalType": "string", "name": "key", "type": "string" },
{ "internalType": "bytes", "name": "value", "type": "bytes" }
@@ -579,6 +716,33 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "from",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "field_0", "type": "address" },
+ { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ ],
+ "internalType": "struct Tuple6",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
+ ],
+ "name": "transferFromCross",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "uniqueCollectionType",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
tests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -13,3 +13,9 @@
event: string,
args: { [key: string]: string }
};
+export interface TEthCrossAccount {
+ readonly 0: string,
+ readonly 1: string | Uint8Array,
+ readonly field_0: string,
+ readonly field_1: string | Uint8Array,
+}
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -18,7 +18,7 @@
import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev';
-import {ContractImports, CompiledContract, NormalizedEvent} from './types';
+import {ContractImports, CompiledContract, TEthCrossAccount, NormalizedEvent} from './types';
// Native contracts ABI
import collectionHelpersAbi from '../../collectionHelpersAbi.json';
@@ -343,6 +343,26 @@
export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper;
+export class EthCrossAccountGroup extends EthGroupBase {
+ fromAddress(address: TEthereumAccount): TEthCrossAccount {
+ return {
+ 0: address,
+ 1: '0',
+ field_0: address,
+ field_1: '0',
+ };
+ }
+
+ fromKeyringPair(keyring: IKeyringPair): TEthCrossAccount {
+ return {
+ 0: '0x0000000000000000000000000000000000000000',
+ 1: keyring.addressRaw,
+ field_0: '0x0000000000000000000000000000000000000000',
+ field_1: keyring.addressRaw,
+ };
+ }
+}
+
export class EthUniqueHelper extends DevUniqueHelper {
web3: Web3 | null = null;
web3Provider: WebsocketProvider | null = null;
@@ -351,6 +371,7 @@
ethAddress: EthAddressGroup;
ethNativeContract: NativeContractGroup;
ethContract: ContractGroup;
+ ethCrossAccount: EthCrossAccountGroup;
constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {
options.helperBase = options.helperBase ?? EthUniqueHelper;
@@ -358,6 +379,7 @@
super(logger, options);
this.eth = new EthGroup(this);
this.ethAddress = new EthAddressGroup(this);
+ this.ethCrossAccount = new EthCrossAccountGroup(this);
this.ethNativeContract = new NativeContractGroup(this);
this.ethContract = new ContractGroup(this);
}