difftreelog
added `mintCross` function for `UniqueExtensoins` interfaces
in: master
20 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6085,7 +6085,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.7"
+version = "0.1.9"
dependencies = [
"ethereum 0.14.0",
"evm-coder",
pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.1.9] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC20UniqueExtensions` interface.
+
## [0.1.8] - 2022-11-18
### Added
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-fungible"
-version = "0.1.7"
+version = "0.1.9"
license = "GPLv3"
edition = "2021"
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -174,6 +174,19 @@
.collect::<string>())
}
+ #[weight(<SelfWeightOf<T>>::create_item())]
+ fn mint_cross(&mut self, caller: caller, to: EthCrossAccount, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = to.into_sub_cross_account::<T>()?;
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+ <Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
#[weight(<SelfWeightOf<T>>::approve())]
fn approve_cross(
&mut self,
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -152,10 +152,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 (Tuple8 memory) {
+ function collectionSponsor() public view returns (Tuple9 memory) {
require(false, stub_error);
dummy;
- return Tuple8(0x0000000000000000000000000000000000000000, 0);
+ return Tuple9(0x0000000000000000000000000000000000000000, 0);
}
/// Get current collection limits.
@@ -522,7 +522,7 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x5b7038cf
+/// @dev the ERC-165 identifier for this interface is 0x7dee5997
contract ERC20UniqueExtensions is Dummy, ERC165 {
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
@@ -533,6 +533,16 @@
return "";
}
+ /// @dev EVM selector for this function is: 0x269e6158,
+ /// or in textual repr: mintCross((address,uint256),uint256)
+ function mintCross(EthCrossAccount memory to, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
/// @dev EVM selector for this function is: 0x0ecd0ab0,
/// or in textual repr: approveCross((address,uint256),uint256)
function approveCross(EthCrossAccount memory spender, uint256 amount) public returns (bool) {
@@ -577,7 +587,7 @@
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
/// or in textual repr: mintBulk((address,uint256)[])
- function mintBulk(Tuple8[] memory amounts) public returns (bool) {
+ function mintBulk(Tuple9[] memory amounts) public returns (bool) {
require(false, stub_error);
amounts;
dummy = 0;
@@ -611,7 +621,7 @@
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple9 {
address field_0;
uint256 field_1;
}
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,7 +4,7 @@
<!-- bureaucrate goes here -->
-## [0.1.11] - 2022-12-16
+## [0.1.12] - 2022-12-16
### Added
@@ -14,6 +14,12 @@
- Hide `setTokenPropertyPermission` function in `TokenProperties` interface.
+## [0.1.11] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC721UniqueExtensions` interface.
+
## [0.1.10] - 2022-11-18
### Added
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -1069,6 +1069,58 @@
.map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
+
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ #[weight(<SelfWeightOf<T>>::create_item())]
+ fn mint_cross(
+ &mut self,
+ caller: caller,
+ to: EthCrossAccount,
+ properties: Vec<PropertyStruct>,
+ ) -> Result<uint256> {
+ let token_id = <TokensMinted<T>>::get(self.id)
+ .checked_add(1)
+ .ok_or("item id overflow")?;
+
+ let to = to.into_sub_cross_account::<T>()?;
+
+ let properties = properties
+ .into_iter()
+ .map(|PropertyStruct { key, value }| {
+ let key = <Vec<u8>>::from(key)
+ .try_into()
+ .map_err(|_| "key too large")?;
+
+ let value = value.0.try_into().map_err(|_| "value too large")?;
+
+ Ok(Property { key, value })
+ })
+ .collect::<Result<Vec<_>>>()?
+ .try_into()
+ .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+
+ let caller = T::CrossAccountId::from_eth(caller);
+
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::create_item(
+ self,
+ &caller,
+ CreateItemData::<T> {
+ properties,
+ owner: to,
+ },
+ &budget,
+ )
+ .map_err(dispatch_to_evm::<T>)?;
+
+ Ok(token_id.into())
+ }
}
#[solidity_interface(
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -290,10 +290,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 (Tuple30 memory) {
+ function collectionSponsor() public view returns (Tuple32 memory) {
require(false, stub_error);
dummy;
- return Tuple30(0x0000000000000000000000000000000000000000, 0);
+ return Tuple32(0x0000000000000000000000000000000000000000, 0);
}
/// Get current collection limits.
@@ -655,7 +655,7 @@
}
/// @dev anonymous struct
-struct Tuple30 {
+struct Tuple32 {
address field_0;
uint256 field_1;
}
@@ -804,7 +804,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xb74c26b7
+/// @dev the ERC-165 identifier for this interface is 0x0e48fdb4
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,
@@ -961,6 +961,7 @@
dummy;
return 0;
}
+
// /// @notice Function to mint multiple tokens.
// /// @dev `tokenIds` should be an array of consecutive numbers and first number
// /// should be obtained with `nextTokenId` method
@@ -991,6 +992,19 @@
// return false;
// }
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ /// @dev EVM selector for this function is: 0xb904db03,
+ /// or in textual repr: mintCross((address,uint256),(string,bytes)[])
+ function mintCross(EthCrossAccount memory to, Property[] memory properties) public returns (uint256) {
+ require(false, stub_error);
+ to;
+ properties;
+ dummy = 0;
+ return 0;
+ }
}
/// @dev anonymous struct
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,7 +4,7 @@
<!-- bureaucrate goes here -->
-## [0.2.10] - 2022-12-16
+## [0.2.11] - 2022-12-16
### Added
@@ -14,6 +14,12 @@
- Hide `setTokenPropertyPermission` function in `TokenProperties` interface.
+## [0.2.10] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC721UniqueExtensions` interface.
+
## [0.2.9] - 2022-11-18
### Added
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -1120,6 +1120,60 @@
Ok(true)
}
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ #[weight(<SelfWeightOf<T>>::create_item())]
+ fn mint_cross(
+ &mut self,
+ caller: caller,
+ to: EthCrossAccount,
+ properties: Vec<PropertyStruct>,
+ ) -> Result<uint256> {
+ let token_id = <TokensMinted<T>>::get(self.id)
+ .checked_add(1)
+ .ok_or("item id overflow")?;
+
+ let to = to.into_sub_cross_account::<T>()?;
+
+ let properties = properties
+ .into_iter()
+ .map(|PropertyStruct { key, value }| {
+ let key = <Vec<u8>>::from(key)
+ .try_into()
+ .map_err(|_| "key too large")?;
+
+ let value = value.0.try_into().map_err(|_| "value too large")?;
+
+ Ok(Property { key, value })
+ })
+ .collect::<Result<Vec<_>>>()?
+ .try_into()
+ .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+
+ let caller = T::CrossAccountId::from_eth(caller);
+
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ let users = [(to, 1)]
+ .into_iter()
+ .collect::<BTreeMap<_, _>>()
+ .try_into()
+ .unwrap();
+ <Pallet<T>>::create_item(
+ self,
+ &caller,
+ CreateItemData::<T::CrossAccountId> { users, properties },
+ &budget,
+ )
+ .map_err(dispatch_to_evm::<T>)?;
+
+ Ok(token_id.into())
+ }
+
/// Returns EVM address for refungible token
///
/// @param token ID of the token
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -290,10 +290,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 (Tuple29 memory) {
+ function collectionSponsor() public view returns (Tuple31 memory) {
require(false, stub_error);
dummy;
- return Tuple29(0x0000000000000000000000000000000000000000, 0);
+ return Tuple31(0x0000000000000000000000000000000000000000, 0);
}
/// Get current collection limits.
@@ -655,7 +655,7 @@
}
/// @dev anonymous struct
-struct Tuple29 {
+struct Tuple31 {
address field_0;
uint256 field_1;
}
@@ -802,7 +802,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x12f7d6c1
+/// @dev the ERC-165 identifier for this interface is 0xabf30dc2
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,
@@ -979,6 +979,20 @@
// return false;
// }
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ /// @dev EVM selector for this function is: 0xb904db03,
+ /// or in textual repr: mintCross((address,uint256),(string,bytes)[])
+ function mintCross(EthCrossAccount memory to, Property[] memory properties) public returns (uint256) {
+ require(false, stub_error);
+ to;
+ properties;
+ dummy = 0;
+ return 0;
+ }
+
/// Returns EVM address for refungible token
///
/// @param token ID of the token
tests/src/eth/abi/fungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -322,7 +322,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple8",
+ "internalType": "struct Tuple9",
"name": "",
"type": "tuple"
}
@@ -408,7 +408,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple8[]",
+ "internalType": "struct Tuple9[]",
"name": "amounts",
"type": "tuple[]"
}
@@ -419,6 +419,24 @@
"type": "function"
},
{
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct EthCrossAccount",
+ "name": "to",
+ "type": "tuple"
+ },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "mintCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "name",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -352,7 +352,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple30",
+ "internalType": "struct Tuple32",
"name": "",
"type": "tuple"
}
@@ -478,6 +478,32 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct EthCrossAccount",
+ "name": "to",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+ ],
+ "internalType": "struct Property[]",
+ "name": "properties",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "mintCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "string", "name": "tokenUri", "type": "string" }
],
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -334,7 +334,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple29",
+ "internalType": "struct Tuple31",
"name": "",
"type": "tuple"
}
@@ -460,6 +460,32 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct EthCrossAccount",
+ "name": "to",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+ ],
+ "internalType": "struct Property[]",
+ "name": "properties",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "mintCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "string", "name": "tokenUri", "type": "string" }
],
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -102,7 +102,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 (Tuple8 memory);
+ function collectionSponsor() external view returns (Tuple9 memory);
/// Get current collection limits.
///
@@ -362,13 +362,17 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x5b7038cf
+/// @dev the ERC-165 identifier for this interface is 0x7dee5997
interface ERC20UniqueExtensions is Dummy, ERC165 {
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
/// or in textual repr: description()
function description() external view returns (string memory);
+ /// @dev EVM selector for this function is: 0x269e6158,
+ /// or in textual repr: mintCross((address,uint256),uint256)
+ function mintCross(EthCrossAccount memory to, uint256 amount) external returns (bool);
+
/// @dev EVM selector for this function is: 0x0ecd0ab0,
/// or in textual repr: approveCross((address,uint256),uint256)
function approveCross(EthCrossAccount memory spender, uint256 amount) external returns (bool);
@@ -395,7 +399,7 @@
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
/// or in textual repr: mintBulk((address,uint256)[])
- function mintBulk(Tuple8[] memory amounts) external returns (bool);
+ function mintBulk(Tuple9[] memory amounts) external returns (bool);
/// @dev EVM selector for this function is: 0x2ada85ff,
/// or in textual repr: transferCross((address,uint256),uint256)
@@ -411,7 +415,7 @@
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple9 {
address field_0;
uint256 field_1;
}
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -198,7 +198,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 (Tuple27 memory);
+ function collectionSponsor() external view returns (Tuple29 memory);
/// Get current collection limits.
///
@@ -553,7 +553,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xb74c26b7
+/// @dev the ERC-165 identifier for this interface is 0x0e48fdb4
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,
@@ -652,6 +652,7 @@
/// @dev EVM selector for this function is: 0x75794a3c,
/// or in textual repr: nextTokenId()
function nextTokenId() external view returns (uint256);
+
// /// @notice Function to mint multiple tokens.
// /// @dev `tokenIds` should be an array of consecutive numbers and first number
// /// should be obtained with `nextTokenId` method
@@ -670,6 +671,13 @@
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
// function mintBulkWithTokenURI(address to, Tuple13[] memory tokens) external returns (bool);
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ /// @dev EVM selector for this function is: 0xb904db03,
+ /// or in textual repr: mintCross((address,uint256),(string,bytes)[])
+ function mintCross(EthCrossAccount memory to, Property[] memory properties) external returns (uint256);
}
/// @dev anonymous struct
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -198,7 +198,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 (Tuple26 memory);
+ function collectionSponsor() external view returns (Tuple28 memory);
/// Get current collection limits.
///
@@ -551,7 +551,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x12f7d6c1
+/// @dev the ERC-165 identifier for this interface is 0xabf30dc2
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,
@@ -663,6 +663,14 @@
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
// function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool);
+ /// @notice Function to mint token.
+ /// @param to The new owner crossAccountId
+ /// @param properties Properties of minted token
+ /// @return uint256 The id of the newly minted token
+ /// @dev EVM selector for this function is: 0xb904db03,
+ /// or in textual repr: mintCross((address,uint256),(string,bytes)[])
+ function mintCross(EthCrossAccount memory to, Property[] memory properties) external returns (uint256);
+
/// Returns EVM address for refungible token
///
/// @param token ID of the token
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -78,6 +78,25 @@
expect(event.returnValues.to).to.equal(receiver);
expect(event.returnValues.value).to.equal('100');
});
+
+
+ itEth('Can perform mintCross()', async ({helper}) => {
+ const receiverCross = helper.ethCrossAccount.fromKeyringPair(owner);
+ const ethOwner = await helper.eth.createAccountWithBalance(donor);
+ const collection = await helper.ft.mintCollection(alice);
+ await collection.addAdmin(alice, {Ethereum: ethOwner});
+
+ const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);
+
+ const result = await contract.methods.mintCross(receiverCross, 100).send();
+
+ const event = result.events.Transfer;
+ expect(event.address).to.equal(collectionAddress);
+ expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+ expect(event.returnValues.to).to.equal(helper.address.substrateToEth(owner.address));
+ expect(event.returnValues.value).to.equal('100');
+ });
itEth('Can perform mintBulk()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';202122describe('NFT: Information getting', () => {23 let donor: IKeyringPair;24 let alice: IKeyringPair;2526 before(async function() {27 await usingEthPlaygrounds(async (helper, privateKey) => {28 donor = await privateKey({filename: __filename});29 [alice] = await helper.arrange.createAccounts([10n], donor);30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const collection = await helper.nft.mintCollection(alice, {});35 await collection.mintToken(alice);3637 const caller = await helper.eth.createAccountWithBalance(donor);3839 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);40 const totalSupply = await contract.methods.totalSupply().call();4142 expect(totalSupply).to.equal('1');43 });4445 itEth('balanceOf', async ({helper}) => {46 const collection = await helper.nft.mintCollection(alice, {});47 const caller = await helper.eth.createAccountWithBalance(donor);4849 await collection.mintToken(alice, {Ethereum: caller});50 await collection.mintToken(alice, {Ethereum: caller});51 await collection.mintToken(alice, {Ethereum: caller});5253 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);54 const balance = await contract.methods.balanceOf(caller).call();5556 expect(balance).to.equal('3');57 });5859 itEth('ownerOf', async ({helper}) => {60 const collection = await helper.nft.mintCollection(alice, {});61 const caller = await helper.eth.createAccountWithBalance(donor);6263 const token = await collection.mintToken(alice, {Ethereum: caller});6465 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6667 const owner = await contract.methods.ownerOf(token.tokenId).call();6869 expect(owner).to.equal(caller);70 });7172 itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {73 const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});74 const caller = helper.eth.createAccount();7576 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7778 expect(await contract.methods.name().call()).to.equal('test');79 expect(await contract.methods.symbol().call()).to.equal('TEST');80 });81});8283describe('Check ERC721 token URI for NFT', () => {84 let donor: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (_helper, privateKey) => {88 donor = await privateKey({filename: __filename});89 });90 });9192 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = helper.eth.createAccount();9596 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);97 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);9899 const result = await contract.methods.mint(receiver).send();100 const tokenId = result.events.Transfer.returnValues.tokenId;101 expect(tokenId).to.be.equal('1');102103 if (propertyKey && propertyValue) {104 // Set URL or suffix105 await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();106 }107108 const event = result.events.Transfer;109 expect(event.address).to.be.equal(collectionAddress);110 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');111 expect(event.returnValues.to).to.be.equal(receiver);112 expect(event.returnValues.tokenId).to.be.equal(tokenId);113114 return {contract, nextTokenId: tokenId};115 }116117 itEth('Empty tokenURI', async ({helper}) => {118 const {contract, nextTokenId} = await setup(helper, '');119 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');120 });121122 itEth('TokenURI from url', async ({helper}) => {123 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');124 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');125 });126127 itEth('TokenURI from baseURI', async ({helper}) => {128 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');129 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');130 });131132 itEth('TokenURI from baseURI + suffix', async ({helper}) => {133 const suffix = '/some/suffix';134 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);135 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);136 });137});138139describe('NFT: Plain calls', () => {140 let donor: IKeyringPair;141 let minter: IKeyringPair;142 let bob: IKeyringPair;143 let charlie: IKeyringPair;144145 before(async function() {146 await usingEthPlaygrounds(async (helper, privateKey) => {147 donor = await privateKey({filename: __filename});148 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);149 });150 });151152 itEth('Can perform mint() & get crossOwner()', async ({helper}) => {153 const owner = await helper.eth.createAccountWithBalance(donor);154 const receiver = helper.eth.createAccount();155156 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');157 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);158159 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();160 const tokenId = result.events.Transfer.returnValues.tokenId;161 expect(tokenId).to.be.equal('1');162163 const event = result.events.Transfer;164 expect(event.address).to.be.equal(collectionAddress);165 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');166 expect(event.returnValues.to).to.be.equal(receiver);167168 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');169 console.log(await contract.methods.crossOwnerOf(tokenId).call());170 expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);171 // TODO: this wont work right now, need release 919000 first172 // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();173 // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();174 // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);175 });176177 //TODO: CORE-302 add eth methods178 itEth.skip('Can perform mintBulk()', async ({helper}) => {179 const caller = await helper.eth.createAccountWithBalance(donor);180 const receiver = helper.eth.createAccount();181182 const collection = await helper.nft.mintCollection(minter);183 await collection.addAdmin(minter, {Ethereum: caller});184185 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);186 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);187 {188 const bulkSize = 3;189 const nextTokenId = await contract.methods.nextTokenId().call();190 expect(nextTokenId).to.be.equal('1');191 const result = await contract.methods.mintBulkWithTokenURI(192 receiver,193 Array.from({length: bulkSize}, (_, i) => (194 [+nextTokenId + i, `Test URI ${i}`]195 )),196 ).send({from: caller});197198 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);199 for (let i = 0; i < bulkSize; i++) {200 const event = events[i];201 expect(event.address).to.equal(collectionAddress);202 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');203 expect(event.returnValues.to).to.equal(receiver);204 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId+i}`);205206 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);207 }208 }209 });210211 itEth('Can perform burn()', async ({helper}) => {212 const caller = await helper.eth.createAccountWithBalance(donor);213214 const collection = await helper.nft.mintCollection(minter, {});215 const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});216217 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);218 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);219220 {221 const result = await contract.methods.burn(tokenId).send({from: caller});222223 const event = result.events.Transfer;224 expect(event.address).to.be.equal(collectionAddress);225 expect(event.returnValues.from).to.be.equal(caller);226 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');227 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);228 }229 });230231 itEth('Can perform approve()', async ({helper}) => {232 const owner = await helper.eth.createAccountWithBalance(donor);233 const spender = helper.eth.createAccount();234235 const collection = await helper.nft.mintCollection(minter, {});236 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});237238 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);239 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);240241 {242 const result = await contract.methods.approve(spender, tokenId).send({from: owner});243244 const event = result.events.Approval;245 expect(event.address).to.be.equal(collectionAddress);246 expect(event.returnValues.owner).to.be.equal(owner);247 expect(event.returnValues.approved).to.be.equal(spender);248 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);249 }250 });251252 itEth('Can perform setApprovalForAll()', async ({helper}) => {253 const owner = await helper.eth.createAccountWithBalance(donor);254 const operator = helper.eth.createAccount();255256 const collection = await helper.nft.mintCollection(minter, {});257258 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);259 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);260261 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();262 expect(approvedBefore).to.be.equal(false);263264 {265 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});266267 expect(result.events.ApprovalForAll).to.be.like({268 address: collectionAddress,269 event: 'ApprovalForAll',270 returnValues: {271 owner,272 operator,273 approved: true,274 },275 });276277 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();278 expect(approvedAfter).to.be.equal(true);279 }280281 {282 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});283284 expect(result.events.ApprovalForAll).to.be.like({285 address: collectionAddress,286 event: 'ApprovalForAll',287 returnValues: {288 owner,289 operator,290 approved: false,291 },292 });293294 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();295 expect(approvedAfter).to.be.equal(false);296 }297 });298299 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {300 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});301302 const owner = await helper.eth.createAccountWithBalance(donor);303 const operator = await helper.eth.createAccountWithBalance(donor, 100n);304305 const token = await collection.mintToken(minter, {Ethereum: owner});306307 const address = helper.ethAddress.fromCollectionId(collection.collectionId);308 const contract = helper.ethNativeContract.collection(address, 'nft');309310 {311 await contract.methods.setApprovalForAll(operator, true).send({from: owner});312 const ownerCross = helper.ethCrossAccount.fromAddress(owner);313 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});314 const events = result.events.Transfer;315316 expect(events).to.be.like({317 address,318 event: 'Transfer',319 returnValues: {320 from: owner,321 to: '0x0000000000000000000000000000000000000000',322 tokenId: token.tokenId.toString(),323 },324 });325 }326 });327 328 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {329 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});330331 const owner = await helper.eth.createAccountWithBalance(donor);332 const operator = await helper.eth.createAccountWithBalance(donor);333 const receiver = charlie;334335 const token = await collection.mintToken(minter, {Ethereum: owner});336337 const address = helper.ethAddress.fromCollectionId(collection.collectionId);338 const contract = helper.ethNativeContract.collection(address, 'nft');339340 {341 await contract.methods.setApprovalForAll(operator, true).send({from: owner});342 const ownerCross = helper.ethCrossAccount.fromAddress(owner);343 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);344 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});345 const event = result.events.Transfer;346 expect(event).to.be.like({347 address: helper.ethAddress.fromCollectionId(collection.collectionId),348 event: 'Transfer',349 returnValues: {350 from: owner,351 to: helper.address.substrateToEth(receiver.address),352 tokenId: token.tokenId.toString(),353 },354 });355 }356357 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});358 });359360 itEth('Can perform burnFromCross()', async ({helper}) => {361 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});362 const ownerSub = bob;363 const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);364 const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);365 const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);366367 const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);368 const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);369370 const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});371 const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});372373 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);374 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft');375376 // Approve tokens from substrate and ethereum:377 await token1.approve(ownerSub, {Ethereum: burnerEth});378 await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});379380 // can burnFromCross:381 const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});382 const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});383 const events1 = result1.events.Transfer;384 const events2 = result2.events.Transfer;385386 // Check events for burnFromCross (substrate and ethereum):387 [388 [events1, token1, helper.address.substrateToEth(ownerSub.address)], 389 [events2, token2, ownerEth],390 ].map(burnData => {391 expect(burnData[0]).to.be.like({392 address: collectionAddress,393 event: 'Transfer',394 returnValues: {395 from: burnData[2],396 to: '0x0000000000000000000000000000000000000000',397 tokenId: burnData[1].tokenId.toString(),398 },399 });400 });401402 expect(await token1.doesExist()).to.be.false;403 expect(await token2.doesExist()).to.be.false;404 });405406 itEth('Can perform approveCross()', async ({helper}) => {407 // arrange: create accounts408 const owner = await helper.eth.createAccountWithBalance(donor, 100n);409 const ownerCross = helper.ethCrossAccount.fromAddress(owner);410 const receiverSub = charlie;411 const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);412 const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);413 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);414415 // arrange: create collection and tokens:416 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});417 const token1 = await collection.mintToken(minter, {Ethereum: owner});418 const token2 = await collection.mintToken(minter, {Ethereum: owner});419420 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');421422 // Can approveCross substrate and ethereum address:423 const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});424 const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});425 const eventSub = resultSub.events.Approval;426 const eventEth = resultEth.events.Approval;427 expect(eventSub).to.be.like({428 address: helper.ethAddress.fromCollectionId(collection.collectionId),429 event: 'Approval',430 returnValues: {431 owner,432 approved: helper.address.substrateToEth(receiverSub.address),433 tokenId: token1.tokenId.toString(),434 },435 });436 expect(eventEth).to.be.like({437 address: helper.ethAddress.fromCollectionId(collection.collectionId),438 event: 'Approval',439 returnValues: {440 owner,441 approved: receiverEth,442 tokenId: token2.tokenId.toString(),443 },444 });445446 // Substrate address can transferFrom approved tokens:447 await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});448 expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});449 // Ethereum address can transferFromCross approved tokens:450 await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});451 expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});452 });453454 itEth('Can reaffirm approved address', async ({helper}) => {455 const owner = await helper.eth.createAccountWithBalance(donor, 100n);456 const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);457 const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);458 const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);459 const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);460 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});461 const token1 = await collection.mintToken(minter, {Ethereum: owner});462 const token2 = await collection.mintToken(minter, {Ethereum: owner});463 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');464465 // Can approve and reaffirm approved address:466 await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});467 await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});468469 // receiver1 cannot transferFrom:470 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;471 // receiver2 can transferFrom:472 await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});473474 // can set approved address to self address to remove approval:475 await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});476 await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});477478 // receiver1 cannot transfer token anymore:479 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;480 });481482 itEth('Can perform transferFrom()', async ({helper}) => {483 const owner = await helper.eth.createAccountWithBalance(donor);484 const spender = await helper.eth.createAccountWithBalance(donor);485 const receiver = helper.eth.createAccount();486487 const collection = await helper.nft.mintCollection(minter, {});488 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});489490 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);491 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);492493 await contract.methods.approve(spender, tokenId).send({from: owner});494495 {496 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});497498 const event = result.events.Transfer;499 expect(event.address).to.be.equal(collectionAddress);500 expect(event.returnValues.from).to.be.equal(owner);501 expect(event.returnValues.to).to.be.equal(receiver);502 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);503 }504505 {506 const balance = await contract.methods.balanceOf(receiver).call();507 expect(+balance).to.equal(1);508 }509510 {511 const balance = await contract.methods.balanceOf(owner).call();512 expect(+balance).to.equal(0);513 }514 });515516 itEth('Can perform transferFromCross()', async ({helper}) => {517 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});518519 const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);520 const spender = await helper.eth.createAccountWithBalance(donor);521522 const token = await collection.mintToken(minter, {Substrate: owner.address});523524 const address = helper.ethAddress.fromCollectionId(collection.collectionId);525 const contract = helper.ethNativeContract.collection(address, 'nft');526527 await token.approve(owner, {Ethereum: spender});528529 {530 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);531 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);532 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});533 const event = result.events.Transfer;534 expect(event).to.be.like({535 address: helper.ethAddress.fromCollectionId(collection.collectionId),536 event: 'Transfer',537 returnValues: {538 from: helper.address.substrateToEth(owner.address),539 to: helper.address.substrateToEth(receiver.address),540 tokenId: token.tokenId.toString(),541 },542 });543 }544545 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});546 });547548 itEth('Can perform transfer()', async ({helper}) => {549 const collection = await helper.nft.mintCollection(minter, {});550 const owner = await helper.eth.createAccountWithBalance(donor);551 const receiver = helper.eth.createAccount();552553 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});554555 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);556 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);557558 {559 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});560561 const event = result.events.Transfer;562 expect(event.address).to.be.equal(collectionAddress);563 expect(event.returnValues.from).to.be.equal(owner);564 expect(event.returnValues.to).to.be.equal(receiver);565 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);566 }567568 {569 const balance = await contract.methods.balanceOf(owner).call();570 expect(+balance).to.equal(0);571 }572573 {574 const balance = await contract.methods.balanceOf(receiver).call();575 expect(+balance).to.equal(1);576 }577 });578 579 itEth('Can perform transferCross()', async ({helper}) => {580 const collection = await helper.nft.mintCollection(minter, {});581 const owner = await helper.eth.createAccountWithBalance(donor);582 const receiverEth = await helper.eth.createAccountWithBalance(donor);583 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);584 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);585 586 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});587588 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);589 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);590591 {592 // Can transferCross to ethereum address:593 const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});594 // Check events:595 const event = result.events.Transfer;596 expect(event.address).to.be.equal(collectionAddress);597 expect(event.returnValues.from).to.be.equal(owner);598 expect(event.returnValues.to).to.be.equal(receiverEth);599 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);600 601 // owner has balance = 0:602 const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();603 expect(+ownerBalance).to.equal(0);604 // receiver owns token:605 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();606 expect(+receiverBalance).to.equal(1);607 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});608 }609 610 {611 // Can transferCross to substrate address:612 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});613 // Check events:614 const event = substrateResult.events.Transfer;615 expect(event.address).to.be.equal(collectionAddress);616 expect(event.returnValues.from).to.be.equal(receiverEth);617 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));618 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);619 620 // owner has balance = 0:621 const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();622 expect(+ownerBalance).to.equal(0);623 // receiver owns token:624 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});625 expect(receiverBalance).to.contain(tokenId);626 }627 });628629 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {630 const sender = await helper.eth.createAccountWithBalance(donor);631 const tokenOwner = await helper.eth.createAccountWithBalance(donor);632 const receiverSub = minter;633 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);634635 const collection = await helper.nft.mintCollection(minter, {});636 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);637 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);638639 await collection.mintToken(minter, {Ethereum: sender});640 const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});641642 // Cannot transferCross someone else's token:643 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;644 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;645 // Cannot transfer token if it does not exist:646 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;647 }));648});649650describe('NFT: Fees', () => {651 let donor: IKeyringPair;652 let alice: IKeyringPair;653 let bob: IKeyringPair;654 let charlie: IKeyringPair;655656 before(async function() {657 await usingEthPlaygrounds(async (helper, privateKey) => {658 donor = await privateKey({filename: __filename});659 [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);660 });661 });662663 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {664 const owner = await helper.eth.createAccountWithBalance(donor);665 const spender = helper.eth.createAccount();666667 const collection = await helper.nft.mintCollection(alice, {});668 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});669670 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);671672 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));673 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));674 });675676 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {677 const owner = await helper.eth.createAccountWithBalance(donor);678 const spender = await helper.eth.createAccountWithBalance(donor);679680 const collection = await helper.nft.mintCollection(alice, {});681 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});682683 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);684685 await contract.methods.approve(spender, tokenId).send({from: owner});686687 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));688 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));689 });690691 itEth('Can perform transferFromCross()', async ({helper}) => {692 const collectionMinter = alice;693 const owner = bob;694 const receiver = charlie;695 const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});696697 const spender = await helper.eth.createAccountWithBalance(donor, 100n);698699 const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});700701 const address = helper.ethAddress.fromCollectionId(collection.collectionId);702 const contract = helper.ethNativeContract.collection(address, 'nft');703704 await token.approve(owner, {Ethereum: spender});705706 {707 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);708 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);709 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});710 const event = result.events.Transfer;711 expect(event).to.be.like({712 address: helper.ethAddress.fromCollectionId(collection.collectionId),713 event: 'Transfer',714 returnValues: {715 from: helper.address.substrateToEth(owner.address),716 to: helper.address.substrateToEth(receiver.address),717 tokenId: token.tokenId.toString(),718 },719 });720 }721722 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});723 });724725 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {726 const owner = await helper.eth.createAccountWithBalance(donor);727 const receiver = helper.eth.createAccount();728729 const collection = await helper.nft.mintCollection(alice, {});730 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});731732 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);733734 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));735 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));736 });737});738739describe('NFT: Substrate calls', () => {740 let donor: IKeyringPair;741 let alice: IKeyringPair;742743 before(async function() {744 await usingEthPlaygrounds(async (helper, privateKey) => {745 donor = await privateKey({filename: __filename});746 [alice] = await helper.arrange.createAccounts([20n], donor);747 });748 });749750 itEth('Events emitted for mint()', async ({helper}) => {751 const collection = await helper.nft.mintCollection(alice, {});752 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);753 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');754755 const events: any = [];756 contract.events.allEvents((_: any, event: any) => {757 events.push(event);758 });759760 const {tokenId} = await collection.mintToken(alice);761 if (events.length == 0) await helper.wait.newBlocks(1);762 const event = events[0];763764 expect(event.event).to.be.equal('Transfer');765 expect(event.address).to.be.equal(collectionAddress);766 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');767 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));768 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());769 });770771 itEth('Events emitted for burn()', async ({helper}) => {772 const collection = await helper.nft.mintCollection(alice, {});773 const token = await collection.mintToken(alice);774775 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);776 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');777778 const events: any = [];779 contract.events.allEvents((_: any, event: any) => {780 events.push(event);781 });782783 await token.burn(alice);784 if (events.length == 0) await helper.wait.newBlocks(1);785 const event = events[0];786787 expect(event.event).to.be.equal('Transfer');788 expect(event.address).to.be.equal(collectionAddress);789 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));790 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');791 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());792 });793794 itEth('Events emitted for approve()', async ({helper}) => {795 const receiver = helper.eth.createAccount();796797 const collection = await helper.nft.mintCollection(alice, {});798 const token = await collection.mintToken(alice);799800 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);801 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');802803 const events: any = [];804 contract.events.allEvents((_: any, event: any) => {805 events.push(event);806 });807808 await token.approve(alice, {Ethereum: receiver});809 if (events.length == 0) await helper.wait.newBlocks(1);810 const event = events[0];811812 expect(event.event).to.be.equal('Approval');813 expect(event.address).to.be.equal(collectionAddress);814 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));815 expect(event.returnValues.approved).to.be.equal(receiver);816 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());817 });818819 itEth('Events emitted for transferFrom()', async ({helper}) => {820 const [bob] = await helper.arrange.createAccounts([10n], donor);821 const receiver = helper.eth.createAccount();822823 const collection = await helper.nft.mintCollection(alice, {});824 const token = await collection.mintToken(alice);825 await token.approve(alice, {Substrate: bob.address});826827 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);828 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');829830 const events: any = [];831 contract.events.allEvents((_: any, event: any) => {832 events.push(event);833 });834835 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});836837 if (events.length == 0) await helper.wait.newBlocks(1);838 const event = events[0];839840 expect(event.address).to.be.equal(collectionAddress);841 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));842 expect(event.returnValues.to).to.be.equal(receiver);843 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);844 });845846 itEth('Events emitted for transfer()', async ({helper}) => {847 const receiver = helper.eth.createAccount();848849 const collection = await helper.nft.mintCollection(alice, {});850 const token = await collection.mintToken(alice);851852 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);853 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');854855 const events: any = [];856 contract.events.allEvents((_: any, event: any) => {857 events.push(event);858 });859860 await token.transfer(alice, {Ethereum: receiver});861862 if (events.length == 0) await helper.wait.newBlocks(1);863 const event = events[0];864865 expect(event.address).to.be.equal(collectionAddress);866 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));867 expect(event.returnValues.to).to.be.equal(receiver);868 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);869 });870});871872describe('Common metadata', () => {873 let donor: IKeyringPair;874 let alice: IKeyringPair;875876 before(async function() {877 await usingEthPlaygrounds(async (helper, privateKey) => {878 donor = await privateKey({filename: __filename});879 [alice] = await helper.arrange.createAccounts([20n], donor);880 });881 });882883 itEth('Returns collection name', async ({helper}) => {884 const caller = await helper.eth.createAccountWithBalance(donor);885 const tokenPropertyPermissions = [{886 key: 'URI',887 permission: {888 mutable: true,889 collectionAdmin: true,890 tokenOwner: false,891 },892 }];893 const collection = await helper.nft.mintCollection(894 alice,895 {896 name: 'oh River',897 tokenPrefix: 'CHANGE',898 properties: [{key: 'ERC721Metadata', value: '1'}],899 tokenPropertyPermissions,900 },901 );902903 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);904 const name = await contract.methods.name().call();905 expect(name).to.equal('oh River');906 });907908 itEth('Returns symbol name', async ({helper}) => {909 const caller = await helper.eth.createAccountWithBalance(donor);910 const tokenPropertyPermissions = [{911 key: 'URI',912 permission: {913 mutable: true,914 collectionAdmin: true,915 tokenOwner: false,916 },917 }];918 const collection = await helper.nft.mintCollection(919 alice,920 {921 name: 'oh River',922 tokenPrefix: 'CHANGE',923 properties: [{key: 'ERC721Metadata', value: '1'}],924 tokenPropertyPermissions,925 },926 );927928 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);929 const symbol = await contract.methods.symbol().call();930 expect(symbol).to.equal('CHANGE');931 });932});933934describe('Negative tests', () => {935 let donor: IKeyringPair;936 let minter: IKeyringPair;937 let alice: IKeyringPair;938939 before(async function() {940 await usingEthPlaygrounds(async (helper, privateKey) => {941 donor = await privateKey({filename: __filename});942 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);943 });944 });945946 itEth('[negative] Cant perform burn without approval', async ({helper}) => {947 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});948949 const owner = await helper.eth.createAccountWithBalance(donor, 100n);950 const spender = await helper.eth.createAccountWithBalance(donor, 100n);951952 const token = await collection.mintToken(minter, {Ethereum: owner});953954 const address = helper.ethAddress.fromCollectionId(collection.collectionId);955 const contract = helper.ethNativeContract.collection(address, 'nft');956957 const ownerCross = helper.ethCrossAccount.fromAddress(owner);958 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;959960 await contract.methods.setApprovalForAll(spender, true).send({from: owner});961 await contract.methods.setApprovalForAll(spender, false).send({from: owner});962963 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;964 });965966 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {967 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});968 const receiver = alice;969970 const owner = await helper.eth.createAccountWithBalance(donor, 100n);971 const spender = await helper.eth.createAccountWithBalance(donor, 100n);972973 const token = await collection.mintToken(minter, {Ethereum: owner});974975 const address = helper.ethAddress.fromCollectionId(collection.collectionId);976 const contract = helper.ethNativeContract.collection(address, 'nft');977978 const ownerCross = helper.ethCrossAccount.fromAddress(owner);979 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);980981 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;982983 await contract.methods.setApprovalForAll(spender, true).send({from: owner});984 await contract.methods.setApprovalForAll(spender, false).send({from: owner});985 986 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;987 });988});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {Contract} from 'web3-eth-contract';20import exp from 'constants';21import {ITokenPropertyPermission} from '../util/playgrounds/types';222324describe('NFT: Information getting', () => {25 let donor: IKeyringPair;26 let alice: IKeyringPair;2728 before(async function() {29 await usingEthPlaygrounds(async (helper, privateKey) => {30 donor = await privateKey({filename: __filename});31 [alice] = await helper.arrange.createAccounts([10n], donor);32 });33 });3435 itEth('totalSupply', async ({helper}) => {36 const collection = await helper.nft.mintCollection(alice, {});37 await collection.mintToken(alice);3839 const caller = await helper.eth.createAccountWithBalance(donor);4041 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);42 const totalSupply = await contract.methods.totalSupply().call();4344 expect(totalSupply).to.equal('1');45 });4647 itEth('balanceOf', async ({helper}) => {48 const collection = await helper.nft.mintCollection(alice, {});49 const caller = await helper.eth.createAccountWithBalance(donor);5051 await collection.mintToken(alice, {Ethereum: caller});52 await collection.mintToken(alice, {Ethereum: caller});53 await collection.mintToken(alice, {Ethereum: caller});5455 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);56 const balance = await contract.methods.balanceOf(caller).call();5758 expect(balance).to.equal('3');59 });6061 itEth('ownerOf', async ({helper}) => {62 const collection = await helper.nft.mintCollection(alice, {});63 const caller = await helper.eth.createAccountWithBalance(donor);6465 const token = await collection.mintToken(alice, {Ethereum: caller});6667 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);6869 const owner = await contract.methods.ownerOf(token.tokenId).call();7071 expect(owner).to.equal(caller);72 });7374 itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {75 const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});76 const caller = helper.eth.createAccount();7778 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);7980 expect(await contract.methods.name().call()).to.equal('test');81 expect(await contract.methods.symbol().call()).to.equal('TEST');82 });83});8485describe('Check ERC721 token URI for NFT', () => {86 let donor: IKeyringPair;8788 before(async function() {89 await usingEthPlaygrounds(async (_helper, privateKey) => {90 donor = await privateKey({filename: __filename});91 });92 });9394 async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {95 const owner = await helper.eth.createAccountWithBalance(donor);96 const receiver = helper.eth.createAccount();9798 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);99 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);100101 const result = await contract.methods.mint(receiver).send();102 const tokenId = result.events.Transfer.returnValues.tokenId;103 expect(tokenId).to.be.equal('1');104105 if (propertyKey && propertyValue) {106 // Set URL or suffix107 await contract.methods.setProperties(tokenId, [{key: propertyKey, value: Buffer.from(propertyValue)}]).send();108 }109110 const event = result.events.Transfer;111 expect(event.address).to.be.equal(collectionAddress);112 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');113 expect(event.returnValues.to).to.be.equal(receiver);114 expect(event.returnValues.tokenId).to.be.equal(tokenId);115116 return {contract, nextTokenId: tokenId};117 }118119 itEth('Empty tokenURI', async ({helper}) => {120 const {contract, nextTokenId} = await setup(helper, '');121 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');122 });123124 itEth('TokenURI from url', async ({helper}) => {125 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URI', 'Token URI');126 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');127 });128129 itEth('TokenURI from baseURI', async ({helper}) => {130 const {contract, nextTokenId} = await setup(helper, 'BaseURI_');131 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_');132 });133134 itEth('TokenURI from baseURI + suffix', async ({helper}) => {135 const suffix = '/some/suffix';136 const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'URISuffix', suffix);137 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);138 });139});140141describe('NFT: Plain calls', () => {142 let donor: IKeyringPair;143 let minter: IKeyringPair;144 let bob: IKeyringPair;145 let charlie: IKeyringPair;146147 before(async function() {148 await usingEthPlaygrounds(async (helper, privateKey) => {149 donor = await privateKey({filename: __filename});150 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);151 });152 });153154 itEth('Can perform mint() & get crossOwner()', async ({helper}) => {155 const owner = await helper.eth.createAccountWithBalance(donor);156 const receiver = helper.eth.createAccount();157158 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');159 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);160161 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();162 const tokenId = result.events.Transfer.returnValues.tokenId;163 expect(tokenId).to.be.equal('1');164165 const event = result.events.Transfer;166 expect(event.address).to.be.equal(collectionAddress);167 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');168 expect(event.returnValues.to).to.be.equal(receiver);169170 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');171 console.log(await contract.methods.crossOwnerOf(tokenId).call());172 expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);173 // TODO: this wont work right now, need release 919000 first174 // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();175 // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();176 // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);177 });178 179 itEth('Can perform mintCross()', async ({helper}) => {180 const caller = await helper.eth.createAccountWithBalance(donor);181 const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);182 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });183 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,184 collectionAdmin: true,185 mutable: true}}; });186 187 188 const collection = await helper.nft.mintCollection(minter, {189 tokenPrefix: 'ethp',190 tokenPropertyPermissions: permissions,191 });192 await collection.addAdmin(minter, {Ethereum: caller});193 194 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);195 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller, true);196 let expectedTokenId = await contract.methods.nextTokenId().call();197 let result = await contract.methods.mintCross(receiverCross, []).send();198 let tokenId = result.events.Transfer.returnValues.tokenId;199 expect(tokenId).to.be.equal(expectedTokenId);200201 const event = result.events.Transfer;202 expect(event.address).to.be.equal(collectionAddress);203 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');204 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));205 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);206 207 expectedTokenId = await contract.methods.nextTokenId().call();208 result = await contract.methods.mintCross(receiverCross, properties).send();209 tokenId = result.events.Transfer.returnValues.tokenId;210211 expect(tokenId).to.be.equal(expectedTokenId);212 213 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties214 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));215 });216 217 //TODO: CORE-302 add eth methods218 itEth.skip('Can perform mintBulk()', async ({helper}) => {219 const caller = await helper.eth.createAccountWithBalance(donor);220 const receiver = helper.eth.createAccount();221222 const collection = await helper.nft.mintCollection(minter);223 await collection.addAdmin(minter, {Ethereum: caller});224225 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);226 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);227 {228 const bulkSize = 3;229 const nextTokenId = await contract.methods.nextTokenId().call();230 expect(nextTokenId).to.be.equal('1');231 const result = await contract.methods.mintBulkWithTokenURI(232 receiver,233 Array.from({length: bulkSize}, (_, i) => (234 [+nextTokenId + i, `Test URI ${i}`]235 )),236 ).send({from: caller});237238 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);239 for (let i = 0; i < bulkSize; i++) {240 const event = events[i];241 expect(event.address).to.equal(collectionAddress);242 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');243 expect(event.returnValues.to).to.equal(receiver);244 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId+i}`);245246 expect(await contract.methods.tokenURI(+nextTokenId + i).call()).to.be.equal(`Test URI ${i}`);247 }248 }249 });250251 itEth('Can perform burn()', async ({helper}) => {252 const caller = await helper.eth.createAccountWithBalance(donor);253254 const collection = await helper.nft.mintCollection(minter, {});255 const {tokenId} = await collection.mintToken(minter, {Ethereum: caller});256257 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);258 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller);259260 {261 const result = await contract.methods.burn(tokenId).send({from: caller});262263 const event = result.events.Transfer;264 expect(event.address).to.be.equal(collectionAddress);265 expect(event.returnValues.from).to.be.equal(caller);266 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');267 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);268 }269 });270271 itEth('Can perform approve()', async ({helper}) => {272 const owner = await helper.eth.createAccountWithBalance(donor);273 const spender = helper.eth.createAccount();274275 const collection = await helper.nft.mintCollection(minter, {});276 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});277278 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);279 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);280281 {282 const result = await contract.methods.approve(spender, tokenId).send({from: owner});283284 const event = result.events.Approval;285 expect(event.address).to.be.equal(collectionAddress);286 expect(event.returnValues.owner).to.be.equal(owner);287 expect(event.returnValues.approved).to.be.equal(spender);288 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);289 }290 });291292 itEth('Can perform setApprovalForAll()', async ({helper}) => {293 const owner = await helper.eth.createAccountWithBalance(donor);294 const operator = helper.eth.createAccount();295296 const collection = await helper.nft.mintCollection(minter, {});297298 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);299 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);300301 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();302 expect(approvedBefore).to.be.equal(false);303304 {305 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});306307 expect(result.events.ApprovalForAll).to.be.like({308 address: collectionAddress,309 event: 'ApprovalForAll',310 returnValues: {311 owner,312 operator,313 approved: true,314 },315 });316317 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();318 expect(approvedAfter).to.be.equal(true);319 }320321 {322 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});323324 expect(result.events.ApprovalForAll).to.be.like({325 address: collectionAddress,326 event: 'ApprovalForAll',327 returnValues: {328 owner,329 operator,330 approved: false,331 },332 });333334 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();335 expect(approvedAfter).to.be.equal(false);336 }337 });338339 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {340 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});341342 const owner = await helper.eth.createAccountWithBalance(donor);343 const operator = await helper.eth.createAccountWithBalance(donor, 100n);344345 const token = await collection.mintToken(minter, {Ethereum: owner});346347 const address = helper.ethAddress.fromCollectionId(collection.collectionId);348 const contract = helper.ethNativeContract.collection(address, 'nft');349350 {351 await contract.methods.setApprovalForAll(operator, true).send({from: owner});352 const ownerCross = helper.ethCrossAccount.fromAddress(owner);353 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});354 const events = result.events.Transfer;355356 expect(events).to.be.like({357 address,358 event: 'Transfer',359 returnValues: {360 from: owner,361 to: '0x0000000000000000000000000000000000000000',362 tokenId: token.tokenId.toString(),363 },364 });365 }366 });367 368 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {369 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});370371 const owner = await helper.eth.createAccountWithBalance(donor);372 const operator = await helper.eth.createAccountWithBalance(donor);373 const receiver = charlie;374375 const token = await collection.mintToken(minter, {Ethereum: owner});376377 const address = helper.ethAddress.fromCollectionId(collection.collectionId);378 const contract = helper.ethNativeContract.collection(address, 'nft');379380 {381 await contract.methods.setApprovalForAll(operator, true).send({from: owner});382 const ownerCross = helper.ethCrossAccount.fromAddress(owner);383 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);384 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});385 const event = result.events.Transfer;386 expect(event).to.be.like({387 address: helper.ethAddress.fromCollectionId(collection.collectionId),388 event: 'Transfer',389 returnValues: {390 from: owner,391 to: helper.address.substrateToEth(receiver.address),392 tokenId: token.tokenId.toString(),393 },394 });395 }396397 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});398 });399400 itEth('Can perform burnFromCross()', async ({helper}) => {401 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});402 const ownerSub = bob;403 const ownerCrossSub = helper.ethCrossAccount.fromKeyringPair(ownerSub);404 const ownerEth = await helper.eth.createAccountWithBalance(donor, 100n);405 const ownerCrossEth = helper.ethCrossAccount.fromAddress(ownerEth);406407 const burnerEth = await helper.eth.createAccountWithBalance(donor, 100n);408 const burnerCrossEth = helper.ethCrossAccount.fromAddress(burnerEth);409410 const token1 = await collection.mintToken(minter, {Substrate: ownerSub.address});411 const token2 = await collection.mintToken(minter, {Ethereum: ownerEth});412413 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);414 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft');415416 // Approve tokens from substrate and ethereum:417 await token1.approve(ownerSub, {Ethereum: burnerEth});418 await collectionEvm.methods.approveCross(burnerCrossEth, token2.tokenId).send({from: ownerEth});419420 // can burnFromCross:421 const result1 = await collectionEvm.methods.burnFromCross(ownerCrossSub, token1.tokenId).send({from: burnerEth});422 const result2 = await collectionEvm.methods.burnFromCross(ownerCrossEth, token2.tokenId).send({from: burnerEth});423 const events1 = result1.events.Transfer;424 const events2 = result2.events.Transfer;425426 // Check events for burnFromCross (substrate and ethereum):427 [428 [events1, token1, helper.address.substrateToEth(ownerSub.address)], 429 [events2, token2, ownerEth],430 ].map(burnData => {431 expect(burnData[0]).to.be.like({432 address: collectionAddress,433 event: 'Transfer',434 returnValues: {435 from: burnData[2],436 to: '0x0000000000000000000000000000000000000000',437 tokenId: burnData[1].tokenId.toString(),438 },439 });440 });441442 expect(await token1.doesExist()).to.be.false;443 expect(await token2.doesExist()).to.be.false;444 });445446 itEth('Can perform approveCross()', async ({helper}) => {447 // arrange: create accounts448 const owner = await helper.eth.createAccountWithBalance(donor, 100n);449 const ownerCross = helper.ethCrossAccount.fromAddress(owner);450 const receiverSub = charlie;451 const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);452 const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n);453 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);454455 // arrange: create collection and tokens:456 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});457 const token1 = await collection.mintToken(minter, {Ethereum: owner});458 const token2 = await collection.mintToken(minter, {Ethereum: owner});459460 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');461462 // Can approveCross substrate and ethereum address:463 const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner});464 const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner});465 const eventSub = resultSub.events.Approval;466 const eventEth = resultEth.events.Approval;467 expect(eventSub).to.be.like({468 address: helper.ethAddress.fromCollectionId(collection.collectionId),469 event: 'Approval',470 returnValues: {471 owner,472 approved: helper.address.substrateToEth(receiverSub.address),473 tokenId: token1.tokenId.toString(),474 },475 });476 expect(eventEth).to.be.like({477 address: helper.ethAddress.fromCollectionId(collection.collectionId),478 event: 'Approval',479 returnValues: {480 owner,481 approved: receiverEth,482 tokenId: token2.tokenId.toString(),483 },484 });485486 // Substrate address can transferFrom approved tokens:487 await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address});488 expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address});489 // Ethereum address can transferFromCross approved tokens:490 await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth});491 expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});492 });493494 itEth('Can reaffirm approved address', async ({helper}) => {495 const owner = await helper.eth.createAccountWithBalance(donor, 100n);496 const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner);497 const [receiver1, receiver2] = await helper.arrange.createAccounts([100n, 100n], donor);498 const receiver1Cross = helper.ethCrossAccount.fromKeyringPair(receiver1);499 const receiver2Cross = helper.ethCrossAccount.fromKeyringPair(receiver2);500 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});501 const token1 = await collection.mintToken(minter, {Ethereum: owner});502 const token2 = await collection.mintToken(minter, {Ethereum: owner});503 const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft');504505 // Can approve and reaffirm approved address:506 await collectionEvm.methods.approveCross(receiver1Cross, token1.tokenId).send({from: owner});507 await collectionEvm.methods.approveCross(receiver2Cross, token1.tokenId).send({from: owner});508509 // receiver1 cannot transferFrom:510 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;511 // receiver2 can transferFrom:512 await helper.nft.transferTokenFrom(receiver2, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiver2.address});513514 // can set approved address to self address to remove approval:515 await collectionEvm.methods.approveCross(receiver1Cross, token2.tokenId).send({from: owner});516 await collectionEvm.methods.approveCross(ownerCrossEth, token2.tokenId).send({from: owner});517518 // receiver1 cannot transfer token anymore:519 await expect(helper.nft.transferTokenFrom(receiver1, collection.collectionId, token2.tokenId, {Ethereum: owner}, {Substrate: receiver1.address})).to.be.rejected;520 });521522 itEth('Can perform transferFrom()', async ({helper}) => {523 const owner = await helper.eth.createAccountWithBalance(donor);524 const spender = await helper.eth.createAccountWithBalance(donor);525 const receiver = helper.eth.createAccount();526527 const collection = await helper.nft.mintCollection(minter, {});528 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});529530 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);531 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);532533 await contract.methods.approve(spender, tokenId).send({from: owner});534535 {536 const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});537538 const event = result.events.Transfer;539 expect(event.address).to.be.equal(collectionAddress);540 expect(event.returnValues.from).to.be.equal(owner);541 expect(event.returnValues.to).to.be.equal(receiver);542 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);543 }544545 {546 const balance = await contract.methods.balanceOf(receiver).call();547 expect(+balance).to.equal(1);548 }549550 {551 const balance = await contract.methods.balanceOf(owner).call();552 expect(+balance).to.equal(0);553 }554 });555556 itEth('Can perform transferFromCross()', async ({helper}) => {557 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});558559 const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor);560 const spender = await helper.eth.createAccountWithBalance(donor);561562 const token = await collection.mintToken(minter, {Substrate: owner.address});563564 const address = helper.ethAddress.fromCollectionId(collection.collectionId);565 const contract = helper.ethNativeContract.collection(address, 'nft');566567 await token.approve(owner, {Ethereum: spender});568569 {570 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);571 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);572 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});573 const event = result.events.Transfer;574 expect(event).to.be.like({575 address: helper.ethAddress.fromCollectionId(collection.collectionId),576 event: 'Transfer',577 returnValues: {578 from: helper.address.substrateToEth(owner.address),579 to: helper.address.substrateToEth(receiver.address),580 tokenId: token.tokenId.toString(),581 },582 });583 }584585 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});586 });587588 itEth('Can perform transfer()', async ({helper}) => {589 const collection = await helper.nft.mintCollection(minter, {});590 const owner = await helper.eth.createAccountWithBalance(donor);591 const receiver = helper.eth.createAccount();592593 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});594595 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);596 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);597598 {599 const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});600601 const event = result.events.Transfer;602 expect(event.address).to.be.equal(collectionAddress);603 expect(event.returnValues.from).to.be.equal(owner);604 expect(event.returnValues.to).to.be.equal(receiver);605 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);606 }607608 {609 const balance = await contract.methods.balanceOf(owner).call();610 expect(+balance).to.equal(0);611 }612613 {614 const balance = await contract.methods.balanceOf(receiver).call();615 expect(+balance).to.equal(1);616 }617 });618 619 itEth('Can perform transferCross()', async ({helper}) => {620 const collection = await helper.nft.mintCollection(minter, {});621 const owner = await helper.eth.createAccountWithBalance(donor);622 const receiverEth = await helper.eth.createAccountWithBalance(donor);623 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);624 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);625 626 const {tokenId} = await collection.mintToken(minter, {Ethereum: owner});627628 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);629 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);630631 {632 // Can transferCross to ethereum address:633 const result = await collectionEvm.methods.transferCross(receiverCrossEth, tokenId).send({from: owner});634 // Check events:635 const event = result.events.Transfer;636 expect(event.address).to.be.equal(collectionAddress);637 expect(event.returnValues.from).to.be.equal(owner);638 expect(event.returnValues.to).to.be.equal(receiverEth);639 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);640 641 // owner has balance = 0:642 const ownerBalance = await collectionEvm.methods.balanceOf(owner).call();643 expect(+ownerBalance).to.equal(0);644 // receiver owns token:645 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();646 expect(+receiverBalance).to.equal(1);647 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()});648 }649 650 {651 // Can transferCross to substrate address:652 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, tokenId).send({from: receiverEth});653 // Check events:654 const event = substrateResult.events.Transfer;655 expect(event.address).to.be.equal(collectionAddress);656 expect(event.returnValues.from).to.be.equal(receiverEth);657 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));658 expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`);659 660 // owner has balance = 0:661 const ownerBalance = await collectionEvm.methods.balanceOf(receiverEth).call();662 expect(+ownerBalance).to.equal(0);663 // receiver owns token:664 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});665 expect(receiverBalance).to.contain(tokenId);666 }667 });668669 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {670 const sender = await helper.eth.createAccountWithBalance(donor);671 const tokenOwner = await helper.eth.createAccountWithBalance(donor);672 const receiverSub = minter;673 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);674675 const collection = await helper.nft.mintCollection(minter, {});676 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);677 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);678679 await collection.mintToken(minter, {Ethereum: sender});680 const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});681682 // Cannot transferCross someone else's token:683 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;684 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;685 // Cannot transfer token if it does not exist:686 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;687 }));688});689690describe('NFT: Fees', () => {691 let donor: IKeyringPair;692 let alice: IKeyringPair;693 let bob: IKeyringPair;694 let charlie: IKeyringPair;695696 before(async function() {697 await usingEthPlaygrounds(async (helper, privateKey) => {698 donor = await privateKey({filename: __filename});699 [alice, bob, charlie] = await helper.arrange.createAccounts([10n, 10n, 10n], donor);700 });701 });702703 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {704 const owner = await helper.eth.createAccountWithBalance(donor);705 const spender = helper.eth.createAccount();706707 const collection = await helper.nft.mintCollection(alice, {});708 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});709710 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);711712 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));713 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));714 });715716 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {717 const owner = await helper.eth.createAccountWithBalance(donor);718 const spender = await helper.eth.createAccountWithBalance(donor);719720 const collection = await helper.nft.mintCollection(alice, {});721 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});722723 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);724725 await contract.methods.approve(spender, tokenId).send({from: owner});726727 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));728 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));729 });730731 itEth('Can perform transferFromCross()', async ({helper}) => {732 const collectionMinter = alice;733 const owner = bob;734 const receiver = charlie;735 const collection = await helper.nft.mintCollection(collectionMinter, {name: 'A', description: 'B', tokenPrefix: 'C'});736737 const spender = await helper.eth.createAccountWithBalance(donor, 100n);738739 const token = await collection.mintToken(collectionMinter, {Substrate: owner.address});740741 const address = helper.ethAddress.fromCollectionId(collection.collectionId);742 const contract = helper.ethNativeContract.collection(address, 'nft');743744 await token.approve(owner, {Ethereum: spender});745746 {747 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);748 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);749 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});750 const event = result.events.Transfer;751 expect(event).to.be.like({752 address: helper.ethAddress.fromCollectionId(collection.collectionId),753 event: 'Transfer',754 returnValues: {755 from: helper.address.substrateToEth(owner.address),756 to: helper.address.substrateToEth(receiver.address),757 tokenId: token.tokenId.toString(),758 },759 });760 }761762 expect(await token.getOwner()).to.be.like({Substrate: receiver.address});763 });764765 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {766 const owner = await helper.eth.createAccountWithBalance(donor);767 const receiver = helper.eth.createAccount();768769 const collection = await helper.nft.mintCollection(alice, {});770 const {tokenId} = await collection.mintToken(alice, {Ethereum: owner});771772 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner);773774 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));775 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));776 });777});778779describe('NFT: Substrate calls', () => {780 let donor: IKeyringPair;781 let alice: IKeyringPair;782783 before(async function() {784 await usingEthPlaygrounds(async (helper, privateKey) => {785 donor = await privateKey({filename: __filename});786 [alice] = await helper.arrange.createAccounts([20n], donor);787 });788 });789790 itEth('Events emitted for mint()', async ({helper}) => {791 const collection = await helper.nft.mintCollection(alice, {});792 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);793 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');794795 const events: any = [];796 contract.events.allEvents((_: any, event: any) => {797 events.push(event);798 });799800 const {tokenId} = await collection.mintToken(alice);801 if (events.length == 0) await helper.wait.newBlocks(1);802 const event = events[0];803804 expect(event.event).to.be.equal('Transfer');805 expect(event.address).to.be.equal(collectionAddress);806 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');807 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address));808 expect(event.returnValues.tokenId).to.be.equal(tokenId.toString());809 });810811 itEth('Events emitted for burn()', async ({helper}) => {812 const collection = await helper.nft.mintCollection(alice, {});813 const token = await collection.mintToken(alice);814815 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);816 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');817818 const events: any = [];819 contract.events.allEvents((_: any, event: any) => {820 events.push(event);821 });822823 await token.burn(alice);824 if (events.length == 0) await helper.wait.newBlocks(1);825 const event = events[0];826827 expect(event.event).to.be.equal('Transfer');828 expect(event.address).to.be.equal(collectionAddress);829 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));830 expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000');831 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());832 });833834 itEth('Events emitted for approve()', async ({helper}) => {835 const receiver = helper.eth.createAccount();836837 const collection = await helper.nft.mintCollection(alice, {});838 const token = await collection.mintToken(alice);839840 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);841 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');842843 const events: any = [];844 contract.events.allEvents((_: any, event: any) => {845 events.push(event);846 });847848 await token.approve(alice, {Ethereum: receiver});849 if (events.length == 0) await helper.wait.newBlocks(1);850 const event = events[0];851852 expect(event.event).to.be.equal('Approval');853 expect(event.address).to.be.equal(collectionAddress);854 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));855 expect(event.returnValues.approved).to.be.equal(receiver);856 expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString());857 });858859 itEth('Events emitted for transferFrom()', async ({helper}) => {860 const [bob] = await helper.arrange.createAccounts([10n], donor);861 const receiver = helper.eth.createAccount();862863 const collection = await helper.nft.mintCollection(alice, {});864 const token = await collection.mintToken(alice);865 await token.approve(alice, {Substrate: bob.address});866867 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);868 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');869870 const events: any = [];871 contract.events.allEvents((_: any, event: any) => {872 events.push(event);873 });874875 await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver});876877 if (events.length == 0) await helper.wait.newBlocks(1);878 const event = events[0];879880 expect(event.address).to.be.equal(collectionAddress);881 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));882 expect(event.returnValues.to).to.be.equal(receiver);883 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);884 });885886 itEth('Events emitted for transfer()', async ({helper}) => {887 const receiver = helper.eth.createAccount();888889 const collection = await helper.nft.mintCollection(alice, {});890 const token = await collection.mintToken(alice);891892 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);893 const contract = helper.ethNativeContract.collection(collectionAddress, 'nft');894895 const events: any = [];896 contract.events.allEvents((_: any, event: any) => {897 events.push(event);898 });899900 await token.transfer(alice, {Ethereum: receiver});901902 if (events.length == 0) await helper.wait.newBlocks(1);903 const event = events[0];904905 expect(event.address).to.be.equal(collectionAddress);906 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));907 expect(event.returnValues.to).to.be.equal(receiver);908 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);909 });910});911912describe('Common metadata', () => {913 let donor: IKeyringPair;914 let alice: IKeyringPair;915916 before(async function() {917 await usingEthPlaygrounds(async (helper, privateKey) => {918 donor = await privateKey({filename: __filename});919 [alice] = await helper.arrange.createAccounts([20n], donor);920 });921 });922923 itEth('Returns collection name', async ({helper}) => {924 const caller = await helper.eth.createAccountWithBalance(donor);925 const tokenPropertyPermissions = [{926 key: 'URI',927 permission: {928 mutable: true,929 collectionAdmin: true,930 tokenOwner: false,931 },932 }];933 const collection = await helper.nft.mintCollection(934 alice,935 {936 name: 'oh River',937 tokenPrefix: 'CHANGE',938 properties: [{key: 'ERC721Metadata', value: '1'}],939 tokenPropertyPermissions,940 },941 );942943 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);944 const name = await contract.methods.name().call();945 expect(name).to.equal('oh River');946 });947948 itEth('Returns symbol name', async ({helper}) => {949 const caller = await helper.eth.createAccountWithBalance(donor);950 const tokenPropertyPermissions = [{951 key: 'URI',952 permission: {953 mutable: true,954 collectionAdmin: true,955 tokenOwner: false,956 },957 }];958 const collection = await helper.nft.mintCollection(959 alice,960 {961 name: 'oh River',962 tokenPrefix: 'CHANGE',963 properties: [{key: 'ERC721Metadata', value: '1'}],964 tokenPropertyPermissions,965 },966 );967968 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);969 const symbol = await contract.methods.symbol().call();970 expect(symbol).to.equal('CHANGE');971 });972});973974describe('Negative tests', () => {975 let donor: IKeyringPair;976 let minter: IKeyringPair;977 let alice: IKeyringPair;978979 before(async function() {980 await usingEthPlaygrounds(async (helper, privateKey) => {981 donor = await privateKey({filename: __filename});982 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);983 });984 });985986 itEth('[negative] Cant perform burn without approval', async ({helper}) => {987 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});988989 const owner = await helper.eth.createAccountWithBalance(donor, 100n);990 const spender = await helper.eth.createAccountWithBalance(donor, 100n);991992 const token = await collection.mintToken(minter, {Ethereum: owner});993994 const address = helper.ethAddress.fromCollectionId(collection.collectionId);995 const contract = helper.ethNativeContract.collection(address, 'nft');996997 const ownerCross = helper.ethCrossAccount.fromAddress(owner);998 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;9991000 await contract.methods.setApprovalForAll(spender, true).send({from: owner});1001 await contract.methods.setApprovalForAll(spender, false).send({from: owner});10021003 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;1004 });10051006 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {1007 const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});1008 const receiver = alice;10091010 const owner = await helper.eth.createAccountWithBalance(donor, 100n);1011 const spender = await helper.eth.createAccountWithBalance(donor, 100n);10121013 const token = await collection.mintToken(minter, {Ethereum: owner});10141015 const address = helper.ethAddress.fromCollectionId(collection.collectionId);1016 const contract = helper.ethNativeContract.collection(address, 'nft');10171018 const ownerCross = helper.ethCrossAccount.fromAddress(owner);1019 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);10201021 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;10221023 await contract.methods.setApprovalForAll(spender, true).send({from: owner});1024 await contract.methods.setApprovalForAll(spender, false).send({from: owner});1025 1026 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;1027 });1028});tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -17,6 +17,7 @@
import {Pallets, requirePalletsOrSkip} from '../util';
import {expect, itEth, usingEthPlaygrounds} from './util';
import {IKeyringPair} from '@polkadot/types/types';
+import { ITokenPropertyPermission } from '../util/playgrounds/types';
describe('Refungible: Information getting', () => {
let donor: IKeyringPair;
@@ -135,6 +136,44 @@
expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
});
+
+ itEth('Can perform mintCross()', async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);
+ const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });
+ const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,
+ collectionAdmin: true,
+ mutable: true}}; });
+
+
+ const collection = await helper.rft.mintCollection(minter, {
+ tokenPrefix: 'ethp',
+ tokenPropertyPermissions: permissions,
+ });
+ await collection.addAdmin(minter, {Ethereum: caller});
+
+ const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller, true);
+ let expectedTokenId = await contract.methods.nextTokenId().call();
+ let result = await contract.methods.mintCross(receiverCross, []).send();
+ let tokenId = result.events.Transfer.returnValues.tokenId;
+ expect(tokenId).to.be.equal(expectedTokenId);
+
+ const event = result.events.Transfer;
+ expect(event.address).to.be.equal(collectionAddress);
+ expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');
+ expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));
+ expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);
+
+ expectedTokenId = await contract.methods.nextTokenId().call();
+ result = await contract.methods.mintCross(receiverCross, properties).send();
+ tokenId = result.events.Transfer.returnValues.tokenId;
+
+ expect(tokenId).to.be.equal(expectedTokenId);
+
+ expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties
+ .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));
+ });
itEth.skip('Can perform mintBulk()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);