difftreelog
refactor EthCrossAccount impl, signature some evm methods, tests and stubs
in: master
16 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -36,7 +36,7 @@
use crate::{
Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
eth::{
- EthCrossAccount, convert_cross_account_to_uint256, CollectionPermissions as EvmPermissions,
+ EthCrossAccount, CollectionPermissions as EvmPermissions,
CollectionLimits as EvmCollectionLimits,
},
weights::WeightInfo,
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -53,15 +53,6 @@
address[0..16] == ETH_COLLECTION_PREFIX
}
-/// Convert `CrossAccountId` to `uint256`.
-pub fn convert_cross_account_to_uint256<T: Config>(from: &T::CrossAccountId) -> uint256
-where
- T::AccountId: AsRef<[u8; 32]>,
-{
- let slice = from.as_sub().as_ref();
- uint256::from_big_endian(slice)
-}
-
/// Convert `uint256` to `CrossAccountId`.
pub fn convert_uint256_to_cross_account<T: Config>(from: uint256) -> T::CrossAccountId
where
@@ -71,22 +62,6 @@
from.to_big_endian(&mut new_admin_arr);
let account_id = T::AccountId::from(new_admin_arr);
T::CrossAccountId::from_sub(account_id)
-}
-
-/// Convert `CrossAccountId` to `(address, uint256)`.
-pub fn convert_cross_account_to_tuple<T: Config>(
- cross_account_id: &T::CrossAccountId,
-) -> (address, uint256)
-where
- T::AccountId: AsRef<[u8; 32]>,
-{
- if cross_account_id.is_canonical_substrate() {
- let sub = convert_cross_account_to_uint256::<T>(cross_account_id);
- (Default::default(), sub)
- } else {
- let eth = *cross_account_id.as_eth();
- (eth, Default::default())
- }
}
/// Convert tuple `(address, uint256)` to `CrossAccountId`.
@@ -128,10 +103,7 @@
T::AccountId: AsRef<[u8; 32]>,
{
if cross_account_id.is_canonical_substrate() {
- Self {
- eth: Default::default(),
- sub: convert_cross_account_to_uint256::<T>(cross_account_id),
- }
+ Self::from_sub::<T>(cross_account_id.as_sub())
} else {
Self {
eth: *cross_account_id.as_eth(),
pallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -50,6 +50,7 @@
"pallet-evm-coder-substrate/std",
"pallet-evm/std",
"up-sponsorship/std",
+ "pallet-common/std",
]
try-runtime = ["frame-support/try-runtime"]
-stubgen = ["evm-coder/stubgen"]
+stubgen = ["evm-coder/stubgen", "pallet-common/stubgen"]
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -175,18 +175,11 @@
///
/// @param contractAddress The contract for which a sponsor is requested.
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
- fn sponsor(&self, contract_address: address) -> Result<(address, uint256)> {
- let sponsor =
- Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?;
- Ok(pallet_common::eth::convert_cross_account_to_tuple::<T>(
- &sponsor,
+ fn sponsor(&self, contract_address: address) -> Result<EthCrossAccount> {
+ Ok(EthCrossAccount::from_sub_cross_account::<T>(
+ &Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?,
))
}
- // fn sponsor(&self, contract_address: address) -> Result<EthCrossAccount> {
- // Ok(EthCrossAccount::from_sub_cross_account::<T>(
- // &Pallet::<T>::get_sponsor(contract_address).ok_or("Contract has no sponsor")?,
- // ))
- // }
/// Check tat contract has confirmed sponsor.
///
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -96,11 +96,11 @@
/// @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: 0x766c4f37,
/// or in textual repr: sponsor(address)
- function sponsor(address contractAddress) public view returns (Tuple0 memory) {
+ function sponsor(address contractAddress) public view returns (EthCrossAccount memory) {
require(false, stub_error);
contractAddress;
dummy;
- return Tuple0(0x0000000000000000000000000000000000000000, 0);
+ return EthCrossAccount(0x0000000000000000000000000000000000000000, 0);
}
/// Check tat contract has confirmed sponsor.
@@ -265,8 +265,8 @@
}
}
-/// @dev anonymous struct
-struct Tuple0 {
- address field_0;
- uint256 field_1;
+/// @dev Cross account struct
+struct EthCrossAccount {
+ address eth;
+ uint256 sub;
}
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -605,7 +605,7 @@
Ok(false)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
#[weight(<SelfWeightOf<T>>::create_item())]
@@ -618,7 +618,7 @@
Ok(token_id)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @dev `tokenId` should be obtained with `nextTokenId` method,
/// unlike standard, you can't specify it manually
/// @param to The new owner
@@ -1070,7 +1070,7 @@
Ok(true)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -738,7 +738,7 @@
return false;
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
/// @dev EVM selector for this function is: 0x6a627842,
@@ -750,7 +750,7 @@
return 0;
}
- // /// @notice Function to mint token.
+ // /// @notice Function to a mint token.
// /// @dev `tokenId` should be obtained with `nextTokenId` method,
// /// unlike standard, you can't specify it manually
// /// @param to The new owner
@@ -995,7 +995,7 @@
// return false;
// }
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -637,7 +637,7 @@
Ok(false)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
#[weight(<SelfWeightOf<T>>::create_item())]
@@ -650,7 +650,7 @@
Ok(token_id)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @dev `tokenId` should be obtained with `nextTokenId` method,
/// unlike standard, you can't specify it manually
/// @param to The new owner
@@ -1120,7 +1120,7 @@
Ok(true)
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -733,7 +733,7 @@
return false;
}
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
/// @dev EVM selector for this function is: 0x6a627842,
@@ -745,7 +745,7 @@
return 0;
}
- // /// @notice Function to mint token.
+ // /// @notice Function to a mint token.
// /// @dev `tokenId` should be obtained with `nextTokenId` method,
// /// unlike standard, you can't specify it manually
// /// @param to The new owner
@@ -979,7 +979,7 @@
// return false;
// }
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
tests/src/eth/abi/contractHelpers.jsondiffbeforeafterboth--- a/tests/src/eth/abi/contractHelpers.json
+++ b/tests/src/eth/abi/contractHelpers.json
@@ -223,10 +223,10 @@
"outputs": [
{
"components": [
- { "internalType": "address", "name": "field_0", "type": "address" },
- { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
],
- "internalType": "struct Tuple0",
+ "internalType": "struct EthCrossAccount",
"name": "",
"type": "tuple"
}
tests/src/eth/api/ContractHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -69,7 +69,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: 0x766c4f37,
/// or in textual repr: sponsor(address)
- function sponsor(address contractAddress) external view returns (Tuple0 memory);
+ function sponsor(address contractAddress) external view returns (EthCrossAccount memory);
/// Check tat contract has confirmed sponsor.
///
@@ -171,8 +171,8 @@
function toggleAllowlist(address contractAddress, bool enabled) external;
}
-/// @dev anonymous struct
-struct Tuple0 {
- address field_0;
- uint256 field_1;
+/// @dev Cross account struct
+struct EthCrossAccount {
+ address eth;
+ uint256 sub;
}
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -515,14 +515,14 @@
/// or in textual repr: mintingFinished()
function mintingFinished() external view returns (bool);
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
/// @dev EVM selector for this function is: 0x6a627842,
/// or in textual repr: mint(address)
function mint(address to) external returns (uint256);
- // /// @notice Function to mint token.
+ // /// @notice Function to a mint token.
// /// @dev `tokenId` should be obtained with `nextTokenId` method,
// /// unlike standard, you can't specify it manually
// /// @param to The new owner
@@ -674,7 +674,7 @@
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
// function mintBulkWithTokenURI(address to, Tuple13[] memory tokens) external returns (bool);
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -513,14 +513,14 @@
/// or in textual repr: mintingFinished()
function mintingFinished() external view returns (bool);
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner
/// @return uint256 The id of the newly minted token
/// @dev EVM selector for this function is: 0x6a627842,
/// or in textual repr: mint(address)
function mint(address to) external returns (uint256);
- // /// @notice Function to mint token.
+ // /// @notice Function to a mint token.
// /// @dev `tokenId` should be obtained with `nextTokenId` method,
// /// unlike standard, you can't specify it manually
// /// @param to The new owner
@@ -666,7 +666,7 @@
// /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
// function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool);
- /// @notice Function to mint token.
+ /// @notice Function to a mint token.
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -17,7 +17,6 @@
import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';
import {IKeyringPair} from '@polkadot/types/types';
import {Contract} from 'web3-eth-contract';
-import exp from 'constants';
import {ITokenPropertyPermission} from '../util/playgrounds/types';
@@ -180,9 +179,16 @@
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 permissions: ITokenPropertyPermission[] = properties
+ .map(p => {
+ return {
+ key: p.key, permission: {
+ tokenOwner: true,
+ collectionAdmin: true,
+ mutable: true,
+ },
+ };
+ });
const collection = await helper.nft.mintCollection(minter, {
@@ -198,7 +204,7 @@
let tokenId = result.events.Transfer.returnValues.tokenId;
expect(tokenId).to.be.equal(expectedTokenId);
- const event = result.events.Transfer;
+ let 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));
@@ -206,10 +212,16 @@
expectedTokenId = await contract.methods.nextTokenId().call();
result = await contract.methods.mintCross(receiverCross, properties).send();
+ 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([]);
+
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()); }));
});
tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';20import { ITokenPropertyPermission } from '../util/playgrounds/types';2122describe('Refungible: Information getting', () => {23 let donor: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);2829 donor = await privateKey({filename: __filename});30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const caller = await helper.eth.createAccountWithBalance(donor);35 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');36 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);3738 await contract.methods.mint(caller).send();3940 const totalSupply = await contract.methods.totalSupply().call();41 expect(totalSupply).to.equal('1');42 });4344 itEth('balanceOf', async ({helper}) => {45 const caller = await helper.eth.createAccountWithBalance(donor);46 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');47 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);4849 await contract.methods.mint(caller).send();50 await contract.methods.mint(caller).send();51 await contract.methods.mint(caller).send();5253 const balance = await contract.methods.balanceOf(caller).call();54 expect(balance).to.equal('3');55 });5657 itEth('ownerOf', async ({helper}) => {58 const caller = await helper.eth.createAccountWithBalance(donor);59 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');60 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);6162 const result = await contract.methods.mint(caller).send();63 const tokenId = result.events.Transfer.returnValues.tokenId;6465 const owner = await contract.methods.ownerOf(tokenId).call();66 expect(owner).to.equal(caller);67 });6869 itEth('ownerOf after burn', async ({helper}) => {70 const caller = await helper.eth.createAccountWithBalance(donor);71 const receiver = helper.eth.createAccount();72 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');73 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);7475 const result = await contract.methods.mint(caller).send();76 const tokenId = result.events.Transfer.returnValues.tokenId;77 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);7879 await tokenContract.methods.repartition(2).send();80 await tokenContract.methods.transfer(receiver, 1).send();8182 await tokenContract.methods.burnFrom(caller, 1).send();8384 const owner = await contract.methods.ownerOf(tokenId).call();85 expect(owner).to.equal(receiver);86 });8788 itEth('ownerOf for partial ownership', async ({helper}) => {89 const caller = await helper.eth.createAccountWithBalance(donor);90 const receiver = helper.eth.createAccount();91 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');92 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);9394 const result = await contract.methods.mint(caller).send();95 const tokenId = result.events.Transfer.returnValues.tokenId;96 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);9798 await tokenContract.methods.repartition(2).send();99 await tokenContract.methods.transfer(receiver, 1).send();100101 const owner = await contract.methods.ownerOf(tokenId).call();102 expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');103 });104});105106describe('Refungible: Plain calls', () => {107 let donor: IKeyringPair;108 let minter: IKeyringPair;109 let bob: IKeyringPair;110 let charlie: IKeyringPair;111112 before(async function() {113 await usingEthPlaygrounds(async (helper, privateKey) => {114 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);115116 donor = await privateKey({filename: __filename});117 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);118 });119 });120121 itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => {122 const owner = await helper.eth.createAccountWithBalance(donor);123 const receiver = helper.eth.createAccount();124 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');125 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);126127 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();128129 const event = result.events.Transfer;130 expect(event.address).to.equal(collectionAddress);131 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');132 expect(event.returnValues.to).to.equal(receiver);133 const tokenId = event.returnValues.tokenId;134 expect(tokenId).to.be.equal('1');135136 expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);137 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');138 });139 140 itEth('Can perform mintCross()', async ({helper}) => {141 const caller = await helper.eth.createAccountWithBalance(donor);142 const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);143 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });144 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,145 collectionAdmin: true,146 mutable: true}}; });147 148 149 const collection = await helper.rft.mintCollection(minter, {150 tokenPrefix: 'ethp',151 tokenPropertyPermissions: permissions,152 });153 await collection.addAdmin(minter, {Ethereum: caller});154 155 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);156 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller, true);157 let expectedTokenId = await contract.methods.nextTokenId().call();158 let result = await contract.methods.mintCross(receiverCross, []).send();159 let tokenId = result.events.Transfer.returnValues.tokenId;160 expect(tokenId).to.be.equal(expectedTokenId);161162 const event = result.events.Transfer;163 expect(event.address).to.be.equal(collectionAddress);164 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');165 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));166 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);167 168 expectedTokenId = await contract.methods.nextTokenId().call();169 result = await contract.methods.mintCross(receiverCross, properties).send();170 tokenId = result.events.Transfer.returnValues.tokenId;171172 expect(tokenId).to.be.equal(expectedTokenId);173 174 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties175 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));176 });177178 itEth.skip('Can perform mintBulk()', async ({helper}) => {179 const owner = await helper.eth.createAccountWithBalance(donor);180 const receiver = helper.eth.createAccount();181 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');182 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);183184 {185 const nextTokenId = await contract.methods.nextTokenId().call();186 expect(nextTokenId).to.be.equal('1');187 const result = await contract.methods.mintBulkWithTokenURI(188 receiver,189 [190 [nextTokenId, 'Test URI 0'],191 [+nextTokenId + 1, 'Test URI 1'],192 [+nextTokenId + 2, 'Test URI 2'],193 ],194 ).send();195196 const events = result.events.Transfer;197 for (let i = 0; i < 2; i++) {198 const event = events[i];199 expect(event.address).to.equal(collectionAddress);200 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');201 expect(event.returnValues.to).to.equal(receiver);202 expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));203 }204205 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');206 expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');207 expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');208 }209 });210211 itEth('Can perform setApprovalForAll()', async ({helper}) => {212 const owner = await helper.eth.createAccountWithBalance(donor);213 const operator = helper.eth.createAccount();214215 const collection = await helper.rft.mintCollection(minter, {});216217 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);218 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);219220 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();221 expect(approvedBefore).to.be.equal(false);222223 {224 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});225226 expect(result.events.ApprovalForAll).to.be.like({227 address: collectionAddress,228 event: 'ApprovalForAll',229 returnValues: {230 owner,231 operator,232 approved: true,233 },234 });235236 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();237 expect(approvedAfter).to.be.equal(true);238 }239240 {241 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});242243 expect(result.events.ApprovalForAll).to.be.like({244 address: collectionAddress,245 event: 'ApprovalForAll',246 returnValues: {247 owner,248 operator,249 approved: false,250 },251 });252253 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();254 expect(approvedAfter).to.be.equal(false);255 }256 });257258 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {259 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});260261 const owner = await helper.eth.createAccountWithBalance(donor);262 const operator = await helper.eth.createAccountWithBalance(donor, 100n);263264 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});265266 const address = helper.ethAddress.fromCollectionId(collection.collectionId);267 const contract = helper.ethNativeContract.collection(address, 'rft');268269 {270 await contract.methods.setApprovalForAll(operator, true).send({from: owner});271 const ownerCross = helper.ethCrossAccount.fromAddress(owner);272 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});273 const events = result.events.Transfer;274275 expect(events).to.be.like({276 address,277 event: 'Transfer',278 returnValues: {279 from: owner,280 to: '0x0000000000000000000000000000000000000000',281 tokenId: token.tokenId.toString(),282 },283 });284 }285 });286287 itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {288 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});289290 const owner = await helper.eth.createAccountWithBalance(donor);291 const operator = await helper.eth.createAccountWithBalance(donor, 100n);292293 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});294295 const address = helper.ethAddress.fromCollectionId(collection.collectionId);296 const contract = helper.ethNativeContract.collection(address, 'rft');297298 const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);299300 {301 await rftToken.methods.approve(operator, 15n).send({from: owner});302 await contract.methods.setApprovalForAll(operator, true).send({from: owner});303 await rftToken.methods.burnFrom(owner, 10n).send({from: operator});304 const allowance = await rftToken.methods.allowance(owner, operator).call();305 expect(allowance).to.be.equal('5');306 }307 });308 309 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {310 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});311312 const owner = await helper.eth.createAccountWithBalance(donor);313 const operator = await helper.eth.createAccountWithBalance(donor);314 const receiver = charlie;315316 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});317318 const address = helper.ethAddress.fromCollectionId(collection.collectionId);319 const contract = helper.ethNativeContract.collection(address, 'rft');320321 {322 await contract.methods.setApprovalForAll(operator, true).send({from: owner});323 const ownerCross = helper.ethCrossAccount.fromAddress(owner);324 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);325 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});326 const event = result.events.Transfer;327 expect(event).to.be.like({328 address: helper.ethAddress.fromCollectionId(collection.collectionId),329 event: 'Transfer',330 returnValues: {331 from: owner,332 to: helper.address.substrateToEth(receiver.address),333 tokenId: token.tokenId.toString(),334 },335 });336 }337338 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);339 });340341 itEth('Can perform burn()', async ({helper}) => {342 const caller = await helper.eth.createAccountWithBalance(donor);343 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');344 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);345346 const result = await contract.methods.mint(caller).send();347 const tokenId = result.events.Transfer.returnValues.tokenId;348 {349 const result = await contract.methods.burn(tokenId).send();350 const event = result.events.Transfer;351 expect(event.address).to.equal(collectionAddress);352 expect(event.returnValues.from).to.equal(caller);353 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');354 expect(event.returnValues.tokenId).to.equal(tokenId.toString());355 }356 });357358 itEth('Can perform transferFrom()', async ({helper}) => {359 const caller = await helper.eth.createAccountWithBalance(donor);360 const receiver = helper.eth.createAccount();361 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');362 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);363364 const result = await contract.methods.mint(caller).send();365 const tokenId = result.events.Transfer.returnValues.tokenId;366367 const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);368369 const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller);370 await tokenContract.methods.repartition(15).send();371372 {373 const tokenEvents: any = [];374 tokenContract.events.allEvents((_: any, event: any) => {375 tokenEvents.push(event);376 });377 const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();378 if (tokenEvents.length == 0) await helper.wait.newBlocks(1);379380 let event = result.events.Transfer;381 expect(event.address).to.equal(collectionAddress);382 expect(event.returnValues.from).to.equal(caller);383 expect(event.returnValues.to).to.equal(receiver);384 expect(event.returnValues.tokenId).to.equal(tokenId.toString());385386 event = tokenEvents[0];387 expect(event.address).to.equal(tokenAddress);388 expect(event.returnValues.from).to.equal(caller);389 expect(event.returnValues.to).to.equal(receiver);390 expect(event.returnValues.value).to.equal('15');391 }392393 {394 const balance = await contract.methods.balanceOf(receiver).call();395 expect(+balance).to.equal(1);396 }397398 {399 const balance = await contract.methods.balanceOf(caller).call();400 expect(+balance).to.equal(0);401 }402 });403404 // Soft-deprecated405 itEth('Can perform burnFrom()', async ({helper}) => {406 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});407408 const owner = await helper.eth.createAccountWithBalance(donor, 100n);409 const spender = await helper.eth.createAccountWithBalance(donor, 100n);410411 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});412413 const address = helper.ethAddress.fromCollectionId(collection.collectionId);414 const contract = helper.ethNativeContract.collection(address, 'rft', spender, true);415416 const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);417 const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);418 await tokenContract.methods.repartition(15).send();419 await tokenContract.methods.approve(spender, 15).send();420421 {422 const result = await contract.methods.burnFrom(owner, token.tokenId).send();423 const event = result.events.Transfer;424 expect(event).to.be.like({425 address: helper.ethAddress.fromCollectionId(collection.collectionId),426 event: 'Transfer',427 returnValues: {428 from: owner,429 to: '0x0000000000000000000000000000000000000000',430 tokenId: token.tokenId.toString(),431 },432 });433 }434435 expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);436 });437438 itEth('Can perform burnFromCross()', async ({helper}) => {439 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});440 441 const owner = bob;442 const spender = await helper.eth.createAccountWithBalance(donor, 100n);443444 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});445446 const address = helper.ethAddress.fromCollectionId(collection.collectionId);447 const contract = helper.ethNativeContract.collection(address, 'rft');448449 await token.repartition(owner, 15n);450 await token.approve(owner, {Ethereum: spender}, 15n);451452 {453 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);454 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});455 const event = result.events.Transfer;456 expect(event).to.be.like({457 address: helper.ethAddress.fromCollectionId(collection.collectionId),458 event: 'Transfer',459 returnValues: {460 from: helper.address.substrateToEth(owner.address),461 to: '0x0000000000000000000000000000000000000000',462 tokenId: token.tokenId.toString(),463 },464 });465 }466467 expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);468 });469470 itEth('Can perform transferFromCross()', async ({helper}) => {471 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});472473 const owner = bob;474 const spender = await helper.eth.createAccountWithBalance(donor, 100n);475 const receiver = charlie;476477 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});478479 const address = helper.ethAddress.fromCollectionId(collection.collectionId);480 const contract = helper.ethNativeContract.collection(address, 'rft');481482 await token.repartition(owner, 15n);483 await token.approve(owner, {Ethereum: spender}, 15n);484485 {486 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);487 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);488 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});489 const event = result.events.Transfer;490 expect(event).to.be.like({491 address: helper.ethAddress.fromCollectionId(collection.collectionId),492 event: 'Transfer',493 returnValues: {494 from: helper.address.substrateToEth(owner.address),495 to: helper.address.substrateToEth(receiver.address),496 tokenId: token.tokenId.toString(),497 },498 });499 }500501 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);502 });503504 itEth('Can perform transfer()', async ({helper}) => {505 const caller = await helper.eth.createAccountWithBalance(donor);506 const receiver = helper.eth.createAccount();507 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');508 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);509510 const result = await contract.methods.mint(caller).send();511 const tokenId = result.events.Transfer.returnValues.tokenId;512513 {514 const result = await contract.methods.transfer(receiver, tokenId).send();515516 const event = result.events.Transfer;517 expect(event.address).to.equal(collectionAddress);518 expect(event.returnValues.from).to.equal(caller);519 expect(event.returnValues.to).to.equal(receiver);520 expect(event.returnValues.tokenId).to.equal(tokenId.toString());521 }522523 {524 const balance = await contract.methods.balanceOf(caller).call();525 expect(+balance).to.equal(0);526 }527528 {529 const balance = await contract.methods.balanceOf(receiver).call();530 expect(+balance).to.equal(1);531 }532 });533 534 itEth('Can perform transferCross()', async ({helper}) => {535 const sender = await helper.eth.createAccountWithBalance(donor);536 const receiverEth = await helper.eth.createAccountWithBalance(donor);537 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);538 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);539540 const collection = await helper.rft.mintCollection(minter, {});541 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);542 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);543544 const token = await collection.mintToken(minter, 50n, {Ethereum: sender});545546 {547 // Can transferCross to ethereum address:548 const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});549 // Check events:550 const event = result.events.Transfer;551 expect(event.address).to.equal(collectionAddress);552 expect(event.returnValues.from).to.equal(sender);553 expect(event.returnValues.to).to.equal(receiverEth);554 expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());555 // Sender's balance decreased:556 const senderBalance = await collectionEvm.methods.balanceOf(sender).call();557 expect(+senderBalance).to.equal(0);558 expect(await token.getBalance({Ethereum: sender})).to.eq(0n);559 // Receiver's balance increased:560 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();561 expect(+receiverBalance).to.equal(1);562 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);563 }564 565 {566 // Can transferCross to substrate address:567 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});568 // Check events:569 const event = substrateResult.events.Transfer;570 expect(event.address).to.be.equal(collectionAddress);571 expect(event.returnValues.from).to.be.equal(receiverEth);572 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));573 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);574 // Sender's balance decreased:575 const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();576 expect(+senderBalance).to.equal(0);577 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);578 // Receiver's balance increased:579 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});580 expect(receiverBalance).to.contain(token.tokenId);581 expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);582 }583 });584585 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {586 const sender = await helper.eth.createAccountWithBalance(donor);587 const tokenOwner = await helper.eth.createAccountWithBalance(donor);588 const receiverSub = minter;589 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);590591 const collection = await helper.rft.mintCollection(minter, {});592 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);593 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);594595 await collection.mintToken(minter, 50n, {Ethereum: sender});596 const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});597598 // Cannot transferCross someone else's token:599 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;600 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;601 // Cannot transfer token if it does not exist:602 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;603 }));604605 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {606 const caller = await helper.eth.createAccountWithBalance(donor);607 const receiver = helper.eth.createAccount();608 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');609 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);610611 const result = await contract.methods.mint(caller).send();612 const tokenId = result.events.Transfer.returnValues.tokenId;613614 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);615616 await tokenContract.methods.repartition(2).send();617 await tokenContract.methods.transfer(receiver, 1).send();618619 const events: any = [];620 contract.events.allEvents((_: any, event: any) => {621 events.push(event);622 });623624 await tokenContract.methods.transfer(receiver, 1).send();625 if (events.length == 0) await helper.wait.newBlocks(1);626 const event = events[0];627628 expect(event.address).to.equal(collectionAddress);629 expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');630 expect(event.returnValues.to).to.equal(receiver);631 expect(event.returnValues.tokenId).to.equal(tokenId.toString());632 });633634 itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {635 const caller = await helper.eth.createAccountWithBalance(donor);636 const receiver = helper.eth.createAccount();637 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');638 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);639640 const result = await contract.methods.mint(caller).send();641 const tokenId = result.events.Transfer.returnValues.tokenId;642643 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);644645 await tokenContract.methods.repartition(2).send();646647 const events: any = [];648 contract.events.allEvents((_: any, event: any) => {649 events.push(event);650 });651652 await tokenContract.methods.transfer(receiver, 1).send();653 if (events.length == 0) await helper.wait.newBlocks(1);654 const event = events[0];655656 expect(event.address).to.equal(collectionAddress);657 expect(event.returnValues.from).to.equal(caller);658 expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');659 expect(event.returnValues.tokenId).to.equal(tokenId.toString());660 });661});662663describe('RFT: Fees', () => {664 let donor: IKeyringPair;665666 before(async function() {667 await usingEthPlaygrounds(async (helper, privateKey) => {668 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);669670 donor = await privateKey({filename: __filename});671 });672 });673674 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {675 const caller = await helper.eth.createAccountWithBalance(donor);676 const receiver = helper.eth.createAccount();677 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');678 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);679680 const result = await contract.methods.mint(caller).send();681 const tokenId = result.events.Transfer.returnValues.tokenId;682683 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());684 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));685 expect(cost > 0n);686 });687688 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {689 const caller = await helper.eth.createAccountWithBalance(donor);690 const receiver = helper.eth.createAccount();691 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');692 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);693694 const result = await contract.methods.mint(caller).send();695 const tokenId = result.events.Transfer.returnValues.tokenId;696697 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());698 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));699 expect(cost > 0n);700 });701});702703describe('Common metadata', () => {704 let donor: IKeyringPair;705 let alice: IKeyringPair;706707 before(async function() {708 await usingEthPlaygrounds(async (helper, privateKey) => {709 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);710711 donor = await privateKey({filename: __filename});712 [alice] = await helper.arrange.createAccounts([20n], donor);713 });714 });715716 itEth('Returns collection name', async ({helper}) => {717 const caller = helper.eth.createAccount();718 const tokenPropertyPermissions = [{719 key: 'URI',720 permission: {721 mutable: true,722 collectionAdmin: true,723 tokenOwner: false,724 },725 }];726 const collection = await helper.rft.mintCollection(727 alice,728 {729 name: 'Leviathan',730 tokenPrefix: '11',731 properties: [{key: 'ERC721Metadata', value: '1'}],732 tokenPropertyPermissions,733 },734 );735736 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);737 const name = await contract.methods.name().call();738 expect(name).to.equal('Leviathan');739 });740741 itEth('Returns symbol name', async ({helper}) => {742 const caller = await helper.eth.createAccountWithBalance(donor);743 const tokenPropertyPermissions = [{744 key: 'URI',745 permission: {746 mutable: true,747 collectionAdmin: true,748 tokenOwner: false,749 },750 }];751 const {collectionId} = await helper.rft.mintCollection(752 alice,753 {754 name: 'Leviathan',755 tokenPrefix: '12',756 properties: [{key: 'ERC721Metadata', value: '1'}],757 tokenPropertyPermissions,758 },759 );760761 const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);762 const symbol = await contract.methods.symbol().call();763 expect(symbol).to.equal('12');764 });765});766767describe('Negative tests', () => {768 let donor: IKeyringPair;769 let minter: IKeyringPair;770 let alice: IKeyringPair;771772 before(async function() {773 await usingEthPlaygrounds(async (helper, privateKey) => {774 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);775 776 donor = await privateKey({filename: __filename});777 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);778 });779 });780781 itEth('[negative] Cant perform burn without approval', async ({helper}) => {782 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});783784 const owner = await helper.eth.createAccountWithBalance(donor, 100n);785 const spender = await helper.eth.createAccountWithBalance(donor, 100n);786787 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});788789 const address = helper.ethAddress.fromCollectionId(collection.collectionId);790 const contract = helper.ethNativeContract.collection(address, 'rft');791792 const ownerCross = helper.ethCrossAccount.fromAddress(owner);793794 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;795796 await contract.methods.setApprovalForAll(spender, true).send({from: owner});797 await contract.methods.setApprovalForAll(spender, false).send({from: owner});798799 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;800 });801802 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {803 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});804 const owner = await helper.eth.createAccountWithBalance(donor, 100n);805 const receiver = alice;806807 const spender = await helper.eth.createAccountWithBalance(donor, 100n);808809 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});810811 const address = helper.ethAddress.fromCollectionId(collection.collectionId);812 const contract = helper.ethNativeContract.collection(address, 'rft');813814 const ownerCross = helper.ethCrossAccount.fromAddress(owner);815 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);816 817 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;818819 await contract.methods.setApprovalForAll(spender, true).send({from: owner});820 await contract.methods.setApprovalForAll(spender, false).send({from: owner});821 822 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;823 });824});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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';20import {ITokenPropertyPermission} from '../util/playgrounds/types';2122describe('Refungible: Information getting', () => {23 let donor: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);2829 donor = await privateKey({filename: __filename});30 });31 });3233 itEth('totalSupply', async ({helper}) => {34 const caller = await helper.eth.createAccountWithBalance(donor);35 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');36 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);3738 await contract.methods.mint(caller).send();3940 const totalSupply = await contract.methods.totalSupply().call();41 expect(totalSupply).to.equal('1');42 });4344 itEth('balanceOf', async ({helper}) => {45 const caller = await helper.eth.createAccountWithBalance(donor);46 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');47 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);4849 await contract.methods.mint(caller).send();50 await contract.methods.mint(caller).send();51 await contract.methods.mint(caller).send();5253 const balance = await contract.methods.balanceOf(caller).call();54 expect(balance).to.equal('3');55 });5657 itEth('ownerOf', async ({helper}) => {58 const caller = await helper.eth.createAccountWithBalance(donor);59 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');60 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);6162 const result = await contract.methods.mint(caller).send();63 const tokenId = result.events.Transfer.returnValues.tokenId;6465 const owner = await contract.methods.ownerOf(tokenId).call();66 expect(owner).to.equal(caller);67 });6869 itEth('ownerOf after burn', async ({helper}) => {70 const caller = await helper.eth.createAccountWithBalance(donor);71 const receiver = helper.eth.createAccount();72 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');73 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);7475 const result = await contract.methods.mint(caller).send();76 const tokenId = result.events.Transfer.returnValues.tokenId;77 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);7879 await tokenContract.methods.repartition(2).send();80 await tokenContract.methods.transfer(receiver, 1).send();8182 await tokenContract.methods.burnFrom(caller, 1).send();8384 const owner = await contract.methods.ownerOf(tokenId).call();85 expect(owner).to.equal(receiver);86 });8788 itEth('ownerOf for partial ownership', async ({helper}) => {89 const caller = await helper.eth.createAccountWithBalance(donor);90 const receiver = helper.eth.createAccount();91 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');92 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);9394 const result = await contract.methods.mint(caller).send();95 const tokenId = result.events.Transfer.returnValues.tokenId;96 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);9798 await tokenContract.methods.repartition(2).send();99 await tokenContract.methods.transfer(receiver, 1).send();100101 const owner = await contract.methods.ownerOf(tokenId).call();102 expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');103 });104});105106describe('Refungible: Plain calls', () => {107 let donor: IKeyringPair;108 let minter: IKeyringPair;109 let bob: IKeyringPair;110 let charlie: IKeyringPair;111112 before(async function() {113 await usingEthPlaygrounds(async (helper, privateKey) => {114 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);115116 donor = await privateKey({filename: __filename});117 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);118 });119 });120121 itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => {122 const owner = await helper.eth.createAccountWithBalance(donor);123 const receiver = helper.eth.createAccount();124 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');125 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);126127 const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();128129 const event = result.events.Transfer;130 expect(event.address).to.equal(collectionAddress);131 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');132 expect(event.returnValues.to).to.equal(receiver);133 const tokenId = event.returnValues.tokenId;134 expect(tokenId).to.be.equal('1');135136 expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);137 expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');138 });139 140 itEth('Can perform mintCross()', async ({helper}) => {141 const caller = await helper.eth.createAccountWithBalance(donor);142 const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);143 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });144 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,145 collectionAdmin: true,146 mutable: true}}; });147 148 149 const collection = await helper.rft.mintCollection(minter, {150 tokenPrefix: 'ethp',151 tokenPropertyPermissions: permissions,152 });153 await collection.addAdmin(minter, {Ethereum: caller});154 155 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);156 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller, true);157 let expectedTokenId = await contract.methods.nextTokenId().call();158 let result = await contract.methods.mintCross(receiverCross, []).send();159 let tokenId = result.events.Transfer.returnValues.tokenId;160 expect(tokenId).to.be.equal(expectedTokenId);161162 let event = result.events.Transfer;163 expect(event.address).to.be.equal(collectionAddress);164 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');165 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));166 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);167 168 expectedTokenId = await contract.methods.nextTokenId().call();169 result = await contract.methods.mintCross(receiverCross, properties).send();170 event = result.events.Transfer;171 expect(event.address).to.be.equal(collectionAddress);172 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');173 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));174 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);175 176 tokenId = result.events.Transfer.returnValues.tokenId;177178 expect(tokenId).to.be.equal(expectedTokenId);179 180 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties181 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));182 });183184 itEth.skip('Can perform mintBulk()', async ({helper}) => {185 const owner = await helper.eth.createAccountWithBalance(donor);186 const receiver = helper.eth.createAccount();187 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');188 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);189190 {191 const nextTokenId = await contract.methods.nextTokenId().call();192 expect(nextTokenId).to.be.equal('1');193 const result = await contract.methods.mintBulkWithTokenURI(194 receiver,195 [196 [nextTokenId, 'Test URI 0'],197 [+nextTokenId + 1, 'Test URI 1'],198 [+nextTokenId + 2, 'Test URI 2'],199 ],200 ).send();201202 const events = result.events.Transfer;203 for (let i = 0; i < 2; i++) {204 const event = events[i];205 expect(event.address).to.equal(collectionAddress);206 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');207 expect(event.returnValues.to).to.equal(receiver);208 expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));209 }210211 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');212 expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');213 expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');214 }215 });216217 itEth('Can perform setApprovalForAll()', async ({helper}) => {218 const owner = await helper.eth.createAccountWithBalance(donor);219 const operator = helper.eth.createAccount();220221 const collection = await helper.rft.mintCollection(minter, {});222223 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);224 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);225226 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();227 expect(approvedBefore).to.be.equal(false);228229 {230 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});231232 expect(result.events.ApprovalForAll).to.be.like({233 address: collectionAddress,234 event: 'ApprovalForAll',235 returnValues: {236 owner,237 operator,238 approved: true,239 },240 });241242 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();243 expect(approvedAfter).to.be.equal(true);244 }245246 {247 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});248249 expect(result.events.ApprovalForAll).to.be.like({250 address: collectionAddress,251 event: 'ApprovalForAll',252 returnValues: {253 owner,254 operator,255 approved: false,256 },257 });258259 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();260 expect(approvedAfter).to.be.equal(false);261 }262 });263264 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {265 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});266267 const owner = await helper.eth.createAccountWithBalance(donor);268 const operator = await helper.eth.createAccountWithBalance(donor, 100n);269270 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});271272 const address = helper.ethAddress.fromCollectionId(collection.collectionId);273 const contract = helper.ethNativeContract.collection(address, 'rft');274275 {276 await contract.methods.setApprovalForAll(operator, true).send({from: owner});277 const ownerCross = helper.ethCrossAccount.fromAddress(owner);278 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});279 const events = result.events.Transfer;280281 expect(events).to.be.like({282 address,283 event: 'Transfer',284 returnValues: {285 from: owner,286 to: '0x0000000000000000000000000000000000000000',287 tokenId: token.tokenId.toString(),288 },289 });290 }291 });292293 itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {294 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});295296 const owner = await helper.eth.createAccountWithBalance(donor);297 const operator = await helper.eth.createAccountWithBalance(donor, 100n);298299 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});300301 const address = helper.ethAddress.fromCollectionId(collection.collectionId);302 const contract = helper.ethNativeContract.collection(address, 'rft');303304 const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);305306 {307 await rftToken.methods.approve(operator, 15n).send({from: owner});308 await contract.methods.setApprovalForAll(operator, true).send({from: owner});309 await rftToken.methods.burnFrom(owner, 10n).send({from: operator});310 const allowance = await rftToken.methods.allowance(owner, operator).call();311 expect(allowance).to.be.equal('5');312 }313 });314 315 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {316 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});317318 const owner = await helper.eth.createAccountWithBalance(donor);319 const operator = await helper.eth.createAccountWithBalance(donor);320 const receiver = charlie;321322 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});323324 const address = helper.ethAddress.fromCollectionId(collection.collectionId);325 const contract = helper.ethNativeContract.collection(address, 'rft');326327 {328 await contract.methods.setApprovalForAll(operator, true).send({from: owner});329 const ownerCross = helper.ethCrossAccount.fromAddress(owner);330 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);331 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});332 const event = result.events.Transfer;333 expect(event).to.be.like({334 address: helper.ethAddress.fromCollectionId(collection.collectionId),335 event: 'Transfer',336 returnValues: {337 from: owner,338 to: helper.address.substrateToEth(receiver.address),339 tokenId: token.tokenId.toString(),340 },341 });342 }343344 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);345 });346347 itEth('Can perform burn()', async ({helper}) => {348 const caller = await helper.eth.createAccountWithBalance(donor);349 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');350 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);351352 const result = await contract.methods.mint(caller).send();353 const tokenId = result.events.Transfer.returnValues.tokenId;354 {355 const result = await contract.methods.burn(tokenId).send();356 const event = result.events.Transfer;357 expect(event.address).to.equal(collectionAddress);358 expect(event.returnValues.from).to.equal(caller);359 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');360 expect(event.returnValues.tokenId).to.equal(tokenId.toString());361 }362 });363364 itEth('Can perform transferFrom()', async ({helper}) => {365 const caller = await helper.eth.createAccountWithBalance(donor);366 const receiver = helper.eth.createAccount();367 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');368 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);369370 const result = await contract.methods.mint(caller).send();371 const tokenId = result.events.Transfer.returnValues.tokenId;372373 const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);374375 const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller);376 await tokenContract.methods.repartition(15).send();377378 {379 const tokenEvents: any = [];380 tokenContract.events.allEvents((_: any, event: any) => {381 tokenEvents.push(event);382 });383 const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();384 if (tokenEvents.length == 0) await helper.wait.newBlocks(1);385386 let event = result.events.Transfer;387 expect(event.address).to.equal(collectionAddress);388 expect(event.returnValues.from).to.equal(caller);389 expect(event.returnValues.to).to.equal(receiver);390 expect(event.returnValues.tokenId).to.equal(tokenId.toString());391392 event = tokenEvents[0];393 expect(event.address).to.equal(tokenAddress);394 expect(event.returnValues.from).to.equal(caller);395 expect(event.returnValues.to).to.equal(receiver);396 expect(event.returnValues.value).to.equal('15');397 }398399 {400 const balance = await contract.methods.balanceOf(receiver).call();401 expect(+balance).to.equal(1);402 }403404 {405 const balance = await contract.methods.balanceOf(caller).call();406 expect(+balance).to.equal(0);407 }408 });409410 // Soft-deprecated411 itEth('Can perform burnFrom()', async ({helper}) => {412 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});413414 const owner = await helper.eth.createAccountWithBalance(donor, 100n);415 const spender = await helper.eth.createAccountWithBalance(donor, 100n);416417 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});418419 const address = helper.ethAddress.fromCollectionId(collection.collectionId);420 const contract = helper.ethNativeContract.collection(address, 'rft', spender, true);421422 const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);423 const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);424 await tokenContract.methods.repartition(15).send();425 await tokenContract.methods.approve(spender, 15).send();426427 {428 const result = await contract.methods.burnFrom(owner, token.tokenId).send();429 const event = result.events.Transfer;430 expect(event).to.be.like({431 address: helper.ethAddress.fromCollectionId(collection.collectionId),432 event: 'Transfer',433 returnValues: {434 from: owner,435 to: '0x0000000000000000000000000000000000000000',436 tokenId: token.tokenId.toString(),437 },438 });439 }440441 expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);442 });443444 itEth('Can perform burnFromCross()', async ({helper}) => {445 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});446 447 const owner = bob;448 const spender = await helper.eth.createAccountWithBalance(donor, 100n);449450 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});451452 const address = helper.ethAddress.fromCollectionId(collection.collectionId);453 const contract = helper.ethNativeContract.collection(address, 'rft');454455 await token.repartition(owner, 15n);456 await token.approve(owner, {Ethereum: spender}, 15n);457458 {459 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);460 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});461 const event = result.events.Transfer;462 expect(event).to.be.like({463 address: helper.ethAddress.fromCollectionId(collection.collectionId),464 event: 'Transfer',465 returnValues: {466 from: helper.address.substrateToEth(owner.address),467 to: '0x0000000000000000000000000000000000000000',468 tokenId: token.tokenId.toString(),469 },470 });471 }472473 expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);474 });475476 itEth('Can perform transferFromCross()', async ({helper}) => {477 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});478479 const owner = bob;480 const spender = await helper.eth.createAccountWithBalance(donor, 100n);481 const receiver = charlie;482483 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});484485 const address = helper.ethAddress.fromCollectionId(collection.collectionId);486 const contract = helper.ethNativeContract.collection(address, 'rft');487488 await token.repartition(owner, 15n);489 await token.approve(owner, {Ethereum: spender}, 15n);490491 {492 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);493 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);494 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});495 const event = result.events.Transfer;496 expect(event).to.be.like({497 address: helper.ethAddress.fromCollectionId(collection.collectionId),498 event: 'Transfer',499 returnValues: {500 from: helper.address.substrateToEth(owner.address),501 to: helper.address.substrateToEth(receiver.address),502 tokenId: token.tokenId.toString(),503 },504 });505 }506507 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);508 });509510 itEth('Can perform transfer()', async ({helper}) => {511 const caller = await helper.eth.createAccountWithBalance(donor);512 const receiver = helper.eth.createAccount();513 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');514 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);515516 const result = await contract.methods.mint(caller).send();517 const tokenId = result.events.Transfer.returnValues.tokenId;518519 {520 const result = await contract.methods.transfer(receiver, tokenId).send();521522 const event = result.events.Transfer;523 expect(event.address).to.equal(collectionAddress);524 expect(event.returnValues.from).to.equal(caller);525 expect(event.returnValues.to).to.equal(receiver);526 expect(event.returnValues.tokenId).to.equal(tokenId.toString());527 }528529 {530 const balance = await contract.methods.balanceOf(caller).call();531 expect(+balance).to.equal(0);532 }533534 {535 const balance = await contract.methods.balanceOf(receiver).call();536 expect(+balance).to.equal(1);537 }538 });539 540 itEth('Can perform transferCross()', async ({helper}) => {541 const sender = await helper.eth.createAccountWithBalance(donor);542 const receiverEth = await helper.eth.createAccountWithBalance(donor);543 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);544 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);545546 const collection = await helper.rft.mintCollection(minter, {});547 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);548 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);549550 const token = await collection.mintToken(minter, 50n, {Ethereum: sender});551552 {553 // Can transferCross to ethereum address:554 const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});555 // Check events:556 const event = result.events.Transfer;557 expect(event.address).to.equal(collectionAddress);558 expect(event.returnValues.from).to.equal(sender);559 expect(event.returnValues.to).to.equal(receiverEth);560 expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());561 // Sender's balance decreased:562 const senderBalance = await collectionEvm.methods.balanceOf(sender).call();563 expect(+senderBalance).to.equal(0);564 expect(await token.getBalance({Ethereum: sender})).to.eq(0n);565 // Receiver's balance increased:566 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();567 expect(+receiverBalance).to.equal(1);568 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);569 }570 571 {572 // Can transferCross to substrate address:573 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});574 // Check events:575 const event = substrateResult.events.Transfer;576 expect(event.address).to.be.equal(collectionAddress);577 expect(event.returnValues.from).to.be.equal(receiverEth);578 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));579 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);580 // Sender's balance decreased:581 const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();582 expect(+senderBalance).to.equal(0);583 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);584 // Receiver's balance increased:585 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});586 expect(receiverBalance).to.contain(token.tokenId);587 expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);588 }589 });590591 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {592 const sender = await helper.eth.createAccountWithBalance(donor);593 const tokenOwner = await helper.eth.createAccountWithBalance(donor);594 const receiverSub = minter;595 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);596597 const collection = await helper.rft.mintCollection(minter, {});598 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);599 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);600601 await collection.mintToken(minter, 50n, {Ethereum: sender});602 const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});603604 // Cannot transferCross someone else's token:605 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;606 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;607 // Cannot transfer token if it does not exist:608 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;609 }));610611 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {612 const caller = await helper.eth.createAccountWithBalance(donor);613 const receiver = helper.eth.createAccount();614 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');615 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);616617 const result = await contract.methods.mint(caller).send();618 const tokenId = result.events.Transfer.returnValues.tokenId;619620 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);621622 await tokenContract.methods.repartition(2).send();623 await tokenContract.methods.transfer(receiver, 1).send();624625 const events: any = [];626 contract.events.allEvents((_: any, event: any) => {627 events.push(event);628 });629630 await tokenContract.methods.transfer(receiver, 1).send();631 if (events.length == 0) await helper.wait.newBlocks(1);632 const event = events[0];633634 expect(event.address).to.equal(collectionAddress);635 expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');636 expect(event.returnValues.to).to.equal(receiver);637 expect(event.returnValues.tokenId).to.equal(tokenId.toString());638 });639640 itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {641 const caller = await helper.eth.createAccountWithBalance(donor);642 const receiver = helper.eth.createAccount();643 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');644 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);645646 const result = await contract.methods.mint(caller).send();647 const tokenId = result.events.Transfer.returnValues.tokenId;648649 const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);650651 await tokenContract.methods.repartition(2).send();652653 const events: any = [];654 contract.events.allEvents((_: any, event: any) => {655 events.push(event);656 });657658 await tokenContract.methods.transfer(receiver, 1).send();659 if (events.length == 0) await helper.wait.newBlocks(1);660 const event = events[0];661662 expect(event.address).to.equal(collectionAddress);663 expect(event.returnValues.from).to.equal(caller);664 expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');665 expect(event.returnValues.tokenId).to.equal(tokenId.toString());666 });667});668669describe('RFT: Fees', () => {670 let donor: IKeyringPair;671672 before(async function() {673 await usingEthPlaygrounds(async (helper, privateKey) => {674 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);675676 donor = await privateKey({filename: __filename});677 });678 });679680 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {681 const caller = await helper.eth.createAccountWithBalance(donor);682 const receiver = helper.eth.createAccount();683 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');684 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);685686 const result = await contract.methods.mint(caller).send();687 const tokenId = result.events.Transfer.returnValues.tokenId;688689 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());690 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));691 expect(cost > 0n);692 });693694 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {695 const caller = await helper.eth.createAccountWithBalance(donor);696 const receiver = helper.eth.createAccount();697 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');698 const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);699700 const result = await contract.methods.mint(caller).send();701 const tokenId = result.events.Transfer.returnValues.tokenId;702703 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());704 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));705 expect(cost > 0n);706 });707});708709describe('Common metadata', () => {710 let donor: IKeyringPair;711 let alice: IKeyringPair;712713 before(async function() {714 await usingEthPlaygrounds(async (helper, privateKey) => {715 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);716717 donor = await privateKey({filename: __filename});718 [alice] = await helper.arrange.createAccounts([20n], donor);719 });720 });721722 itEth('Returns collection name', async ({helper}) => {723 const caller = helper.eth.createAccount();724 const tokenPropertyPermissions = [{725 key: 'URI',726 permission: {727 mutable: true,728 collectionAdmin: true,729 tokenOwner: false,730 },731 }];732 const collection = await helper.rft.mintCollection(733 alice,734 {735 name: 'Leviathan',736 tokenPrefix: '11',737 properties: [{key: 'ERC721Metadata', value: '1'}],738 tokenPropertyPermissions,739 },740 );741742 const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);743 const name = await contract.methods.name().call();744 expect(name).to.equal('Leviathan');745 });746747 itEth('Returns symbol name', async ({helper}) => {748 const caller = await helper.eth.createAccountWithBalance(donor);749 const tokenPropertyPermissions = [{750 key: 'URI',751 permission: {752 mutable: true,753 collectionAdmin: true,754 tokenOwner: false,755 },756 }];757 const {collectionId} = await helper.rft.mintCollection(758 alice,759 {760 name: 'Leviathan',761 tokenPrefix: '12',762 properties: [{key: 'ERC721Metadata', value: '1'}],763 tokenPropertyPermissions,764 },765 );766767 const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);768 const symbol = await contract.methods.symbol().call();769 expect(symbol).to.equal('12');770 });771});772773describe('Negative tests', () => {774 let donor: IKeyringPair;775 let minter: IKeyringPair;776 let alice: IKeyringPair;777778 before(async function() {779 await usingEthPlaygrounds(async (helper, privateKey) => {780 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);781 782 donor = await privateKey({filename: __filename});783 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);784 });785 });786787 itEth('[negative] Cant perform burn without approval', async ({helper}) => {788 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});789790 const owner = await helper.eth.createAccountWithBalance(donor, 100n);791 const spender = await helper.eth.createAccountWithBalance(donor, 100n);792793 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});794795 const address = helper.ethAddress.fromCollectionId(collection.collectionId);796 const contract = helper.ethNativeContract.collection(address, 'rft');797798 const ownerCross = helper.ethCrossAccount.fromAddress(owner);799800 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;801802 await contract.methods.setApprovalForAll(spender, true).send({from: owner});803 await contract.methods.setApprovalForAll(spender, false).send({from: owner});804805 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;806 });807808 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {809 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});810 const owner = await helper.eth.createAccountWithBalance(donor, 100n);811 const receiver = alice;812813 const spender = await helper.eth.createAccountWithBalance(donor, 100n);814815 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});816817 const address = helper.ethAddress.fromCollectionId(collection.collectionId);818 const contract = helper.ethNativeContract.collection(address, 'rft');819820 const ownerCross = helper.ethCrossAccount.fromAddress(owner);821 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);822 823 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;824825 await contract.methods.setApprovalForAll(spender, true).send({from: owner});826 await contract.methods.setApprovalForAll(spender, false).send({from: owner});827 828 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;829 });830});