difftreelog
Merge pull request #835 from UniqueNetwork/tests/generalization
in: master
20 files changed
tests/src/apiConsts.test.tsdiffbeforeafterboth--- a/tests/src/apiConsts.test.ts
+++ b/tests/src/apiConsts.test.ts
@@ -15,7 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {ApiPromise} from '@polkadot/api';
-import {usingPlaygrounds, itSub, expect} from './util';
+import {usingPlaygrounds, itSub, expect, COLLECTION_HELPER, CONTRACT_HELPER} from './util';
const MAX_COLLECTION_DESCRIPTION_LENGTH = 256n;
@@ -40,9 +40,6 @@
ownerCanDestroy: true,
transfersEnabled: true,
};
-
-const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
-const HELPERS_CONTRACT_ADDRESS = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';
describe('integration test: API UNIQUE consts', () => {
let api: ApiPromise;
@@ -106,11 +103,11 @@
});
itSub('HELPERS_CONTRACT_ADDRESS', () => {
- expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(HELPERS_CONTRACT_ADDRESS.toLowerCase());
+ expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(CONTRACT_HELPER.toLowerCase());
});
itSub('EVM_COLLECTION_HELPERS_ADDRESS', () => {
- expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS.toLowerCase());
+ expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(COLLECTION_HELPER.toLowerCase());
});
});
tests/src/burnItem.test.tsdiffbeforeafterboth--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -45,32 +45,6 @@
await collection.burnTokens(alice, 1n);
expect(await collection.getBalance({Substrate: alice.address})).to.eq(9n);
});
-
- itSub.ifWithPallets('Burn item in ReFungible collection', [Pallets.ReFungible], async function({helper}) {
- const collection = await helper.rft.mintCollection(alice);
- const token = await collection.mintToken(alice, 100n);
-
- await token.burn(alice, 90n);
- expect(await token.getBalance({Substrate: alice.address})).to.eq(10n);
-
- await token.burn(alice, 10n);
- expect(await token.getBalance({Substrate: alice.address})).to.eq(0n);
- });
-
- itSub.ifWithPallets('Burn owned portion of item in ReFungible collection', [Pallets.ReFungible], async function({helper}) {
- const collection = await helper.rft.mintCollection(alice);
- const token = await collection.mintToken(alice, 100n);
-
- await token.transfer(alice, {Substrate: bob.address}, 1n);
-
- expect(await token.getBalance({Substrate: alice.address})).to.eq(99n);
- expect(await token.getBalance({Substrate: bob.address})).to.eq(1n);
-
- await token.burn(bob, 1n);
-
- expect(await token.getBalance({Substrate: alice.address})).to.eq(99n);
- expect(await token.getBalance({Substrate: bob.address})).to.eq(0n);
- });
});
describe('integration test: ext. burnItem() with admin permissions:', () => {
@@ -104,16 +78,6 @@
await collection.burnTokensFrom(bob, {Substrate: alice.address}, 1n);
expect(await collection.getBalance({Substrate: alice.address})).to.eq(9n);
});
-
- itSub.ifWithPallets('Burn item in ReFungible collection', [Pallets.ReFungible], async function({helper}) {
- const collection = await helper.rft.mintCollection(alice);
- await collection.setLimits(alice, {ownerCanTransfer: true});
- await collection.addAdmin(alice, {Substrate: bob.address});
- const token = await collection.mintToken(alice, 100n);
-
- await token.burnFrom(bob, {Substrate: alice.address}, 100n);
- expect(await token.doesExist()).to.be.false;
- });
});
describe('Negative integration test: ext. burnItem():', () => {
@@ -138,31 +102,6 @@
const token = await collection.mintToken(alice);
await expect(token.burn(bob)).to.be.rejectedWith('common.NoPermission');
- });
-
- itSub.ifWithPallets('RFT: cannot burn non-owned token pieces', [Pallets.ReFungible], async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice);
- const aliceToken = await collection.mintToken(alice, 10n, {Substrate: alice.address});
- const bobToken = await collection.mintToken(alice, 10n, {Substrate: bob.address});
-
- // 1. Cannot burn non-owned token:
- await expect(bobToken.burn(alice, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
- await expect(bobToken.burn(alice, 5n)).to.be.rejectedWith('common.TokenValueTooLow');
- // 2. Cannot burn non-existing token:
- await expect(helper.rft.burnToken(alice, 99999, 10)).to.be.rejectedWith('common.CollectionNotFound');
- await expect(helper.rft.burnToken(alice, collection.collectionId, 99999)).to.be.rejectedWith('common.TokenValueTooLow');
- // 3. Can burn zero amount of owned tokens (EIP-20)
- await aliceToken.burn(alice, 0n);
-
- // 4. Storage is not corrupted:
- expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
- expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
-
- // 4.1 Tokens can be transfered:
- await aliceToken.transfer(alice, {Substrate: bob.address}, 10n);
- await bobToken.transfer(bob, {Substrate: alice.address}, 10n);
- expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
- expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
});
itSub('Transfer a burned token', async ({helper}) => {
tests/src/eth/collectionHelperAddress.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionHelperAddress.test.ts
+++ b/tests/src/eth/collectionHelperAddress.test.ts
@@ -16,9 +16,7 @@
import {itEth, usingEthPlaygrounds, expect} from './util';
import {IKeyringPair} from '@polkadot/types/types';
-import {Pallets} from '../util';
-
-const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
+import {COLLECTION_HELPER, Pallets} from '../util';
describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => {
let donor: IKeyringPair;
@@ -36,7 +34,7 @@
const nftCollection = await helper.ethNativeContract.collection(nftCollectionAddress, 'nft', owner);
expect((await nftCollection.methods.collectionHelperAddress().call())
- .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+ .toString().toLowerCase()).to.be.equal(COLLECTION_HELPER);
});
itEth.ifWithPallets('RFT ', [Pallets.ReFungible], async ({helper}) => {
@@ -46,7 +44,7 @@
const rftCollection = await helper.ethNativeContract.collection(rftCollectionAddress, 'rft', owner);
expect((await rftCollection.methods.collectionHelperAddress().call())
- .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+ .toString().toLowerCase()).to.be.equal(COLLECTION_HELPER);
});
itEth('FT', async ({helper}) => {
@@ -56,7 +54,7 @@
const collection = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
expect((await collection.methods.collectionHelperAddress().call())
- .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+ .toString().toLowerCase()).to.be.equal(COLLECTION_HELPER);
});
itEth('[collectionHelpers] convert collectionId into address', async ({helper}) => {
tests/src/eth/createNFTCollection.test.tsdiffbeforeafterboth--- a/tests/src/eth/createNFTCollection.test.ts
+++ b/tests/src/eth/createNFTCollection.test.ts
@@ -18,6 +18,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {expect, itEth, usingEthPlaygrounds} from './util';
import {CollectionLimitField} from './util/playgrounds/types';
+import {COLLECTION_HELPER} from '../util';
describe('Create NFT collection from EVM', () => {
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -17,38 +17,6 @@
import {expect, itEth, usingEthPlaygrounds} from './util';
import {IKeyringPair} from '@polkadot/types/types';
-describe('Fungible: Information getting', () => {
- let donor: IKeyringPair;
- let alice: IKeyringPair;
-
- before(async function() {
- await usingEthPlaygrounds(async (helper, privateKey) => {
- donor = await privateKey({filename: __filename});
- [alice] = await helper.arrange.createAccounts([20n], donor);
- });
- });
-
- itEth('totalSupply', async ({helper}) => {
- const caller = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.ft.mintCollection(alice);
- await collection.mint(alice, 200n);
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);
- const totalSupply = await contract.methods.totalSupply().call();
- expect(totalSupply).to.equal('200');
- });
-
- itEth('balanceOf', async ({helper}) => {
- const caller = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.ft.mintCollection(alice);
- await collection.mint(alice, 200n, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);
- const balance = await contract.methods.balanceOf(caller).call();
- expect(balance).to.equal('200');
- });
-});
-
describe('Fungible: Plain calls', () => {
let donor: IKeyringPair;
let alice: IKeyringPair;
@@ -59,24 +27,6 @@
donor = await privateKey({filename: __filename});
[alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);
});
- });
-
- itEth('Can perform mint()', async ({helper}) => {
- const owner = await helper.eth.createAccountWithBalance(donor);
- const receiver = helper.eth.createAccount();
- const collection = await helper.ft.mintCollection(alice);
- await collection.addAdmin(alice, {Ethereum: owner});
-
- const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
- const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
-
- const result = await contract.methods.mint(receiver, 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(receiver);
- expect(event.returnValues.value).to.equal('100');
});
[
tests/src/eth/getCode.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/getCode.test.ts
@@ -0,0 +1,53 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect, itEth, usingEthPlaygrounds} from './util';
+import {IKeyringPair} from '@polkadot/types/types';
+import {COLLECTION_HELPER, CONTRACT_HELPER} from '../util';
+
+describe('RPC eth_getCode', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ });
+ });
+
+ [
+ {address: COLLECTION_HELPER},
+ {address: CONTRACT_HELPER},
+ ].map(testCase => {
+ itEth(`returns value for native contract: ${testCase.address}`, async ({helper}) => {
+ const contractCodeSub = (await helper.callRpc('api.rpc.eth.getCode', [testCase.address])).toJSON();
+ const contractCodeEth = (await helper.getWeb3().eth.getCode(testCase.address));
+
+ expect(contractCodeSub).to.has.length.greaterThan(4);
+ expect(contractCodeEth).to.has.length.greaterThan(4);
+ });
+ });
+
+ itEth('returns value for custom contract', async ({helper}) => {
+ const signer = await helper.eth.createAccountWithBalance(donor);
+ const flipper = await helper.eth.deployFlipper(signer);
+
+ const contractCodeSub = (await helper.callRpc('api.rpc.eth.getCode', [flipper.options.address])).toJSON();
+ const contractCodeEth = (await helper.getWeb3().eth.getCode(flipper.options.address));
+
+ expect(contractCodeSub).to.has.length.greaterThan(4);
+ expect(contractCodeEth).to.has.length.greaterThan(4);
+ });
+});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -19,68 +19,6 @@
import {Contract} from 'web3-eth-contract';
import {ITokenPropertyPermission} from '../util/playgrounds/types';
-
-describe('NFT: Information getting', () => {
- let donor: IKeyringPair;
- let alice: IKeyringPair;
-
- before(async function() {
- await usingEthPlaygrounds(async (helper, privateKey) => {
- donor = await privateKey({filename: __filename});
- [alice] = await helper.arrange.createAccounts([10n], donor);
- });
- });
-
- itEth('totalSupply', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {});
- await collection.mintToken(alice);
-
- const caller = await helper.eth.createAccountWithBalance(donor);
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
- const totalSupply = await contract.methods.totalSupply().call();
-
- expect(totalSupply).to.equal('1');
- });
-
- itEth('balanceOf', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {});
- const caller = await helper.eth.createAccountWithBalance(donor);
-
- await collection.mintToken(alice, {Ethereum: caller});
- await collection.mintToken(alice, {Ethereum: caller});
- await collection.mintToken(alice, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
- const balance = await contract.methods.balanceOf(caller).call();
-
- expect(balance).to.equal('3');
- });
-
- itEth('ownerOf', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {});
- const caller = await helper.eth.createAccountWithBalance(donor);
-
- const token = await collection.mintToken(alice, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
-
- const owner = await contract.methods.ownerOf(token.tokenId).call();
-
- expect(owner).to.equal(caller);
- });
-
- itEth('name/symbol is available regardless of ERC721Metadata support', async ({helper}) => {
- const collection = await helper.nft.mintCollection(alice, {name: 'test', tokenPrefix: 'TEST'});
- const caller = helper.eth.createAccount();
-
- const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller);
-
- expect(await contract.methods.name().call()).to.equal('test');
- expect(await contract.methods.symbol().call()).to.equal('TEST');
- });
-});
-
describe('Check ERC721 token URI for NFT', () => {
let donor: IKeyringPair;
@@ -148,30 +86,6 @@
donor = await privateKey({filename: __filename});
[minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
});
- });
-
- itEth('Can perform mint() & get crossOwner()', async ({helper}) => {
- const owner = await helper.eth.createAccountWithBalance(donor);
- const receiver = helper.eth.createAccount();
-
- const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');
- const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
-
- const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();
- const tokenId = result.events.Transfer.returnValues.tokenId;
- expect(tokenId).to.be.equal('1');
-
- 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(receiver);
-
- expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
- expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
- // TODO: this wont work right now, need release 919000 first
- // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();
- // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();
- // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);
});
// TODO combine all minting tests in one place
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 = await 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 = await 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 = await 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 = await 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 = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller, true);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 = await 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 = await 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 = await 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 });139140 [141 'substrate' as const,142 'ethereum' as const,143 ].map(testCase => {144 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {145 const collectionAdmin = await helper.eth.createAccountWithBalance(donor);146147 const receiverEth = helper.eth.createAccount();148 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);149 const receiverSub = bob;150 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);151152 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });153 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {154 tokenOwner: false,155 collectionAdmin: true,156 mutable: false}};157 });158159160 const collection = await helper.rft.mintCollection(minter, {161 tokenPrefix: 'ethp',162 tokenPropertyPermissions: permissions,163 });164 await collection.addAdmin(minter, {Ethereum: collectionAdmin});165166 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);167 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', collectionAdmin, true);168 let expectedTokenId = await contract.methods.nextTokenId().call();169 let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();170 let tokenId = result.events.Transfer.returnValues.tokenId;171 expect(tokenId).to.be.equal(expectedTokenId);172173 let event = result.events.Transfer;174 expect(event.address).to.be.equal(collectionAddress);175 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');176 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));177 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);178179 expectedTokenId = await contract.methods.nextTokenId().call();180 result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();181 event = result.events.Transfer;182 expect(event.address).to.be.equal(collectionAddress);183 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');184 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));185 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);186187 tokenId = result.events.Transfer.returnValues.tokenId;188189 expect(tokenId).to.be.equal(expectedTokenId);190191 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties192 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));193194 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))195 .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});196 });197 });198199 itEth.skip('Can perform mintBulk()', async ({helper}) => {200 const owner = await helper.eth.createAccountWithBalance(donor);201 const receiver = helper.eth.createAccount();202 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');203 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);204205 {206 const nextTokenId = await contract.methods.nextTokenId().call();207 expect(nextTokenId).to.be.equal('1');208 const result = await contract.methods.mintBulkWithTokenURI(209 receiver,210 [211 [nextTokenId, 'Test URI 0'],212 [+nextTokenId + 1, 'Test URI 1'],213 [+nextTokenId + 2, 'Test URI 2'],214 ],215 ).send();216217 const events = result.events.Transfer;218 for (let i = 0; i < 2; i++) {219 const event = events[i];220 expect(event.address).to.equal(collectionAddress);221 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');222 expect(event.returnValues.to).to.equal(receiver);223 expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));224 }225226 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');227 expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');228 expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');229 }230 });231232 itEth('Can perform setApprovalForAll()', async ({helper}) => {233 const owner = await helper.eth.createAccountWithBalance(donor);234 const operator = helper.eth.createAccount();235236 const collection = await helper.rft.mintCollection(minter, {});237238 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);239 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);240241 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();242 expect(approvedBefore).to.be.equal(false);243244 {245 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});246247 expect(result.events.ApprovalForAll).to.be.like({248 address: collectionAddress,249 event: 'ApprovalForAll',250 returnValues: {251 owner,252 operator,253 approved: true,254 },255 });256257 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();258 expect(approvedAfter).to.be.equal(true);259 }260261 {262 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});263264 expect(result.events.ApprovalForAll).to.be.like({265 address: collectionAddress,266 event: 'ApprovalForAll',267 returnValues: {268 owner,269 operator,270 approved: false,271 },272 });273274 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();275 expect(approvedAfter).to.be.equal(false);276 }277 });278279 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {280 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});281282 const owner = await helper.eth.createAccountWithBalance(donor);283 const operator = await helper.eth.createAccountWithBalance(donor, 100n);284285 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});286287 const address = helper.ethAddress.fromCollectionId(collection.collectionId);288 const contract = await helper.ethNativeContract.collection(address, 'rft');289290 {291 await contract.methods.setApprovalForAll(operator, true).send({from: owner});292 const ownerCross = helper.ethCrossAccount.fromAddress(owner);293 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});294 const events = result.events.Transfer;295296 expect(events).to.be.like({297 address,298 event: 'Transfer',299 returnValues: {300 from: owner,301 to: '0x0000000000000000000000000000000000000000',302 tokenId: token.tokenId.toString(),303 },304 });305 }306 });307308 itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {309 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});310311 const owner = await helper.eth.createAccountWithBalance(donor);312 const operator = await helper.eth.createAccountWithBalance(donor, 100n);313314 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});315316 const address = helper.ethAddress.fromCollectionId(collection.collectionId);317 const contract = await helper.ethNativeContract.collection(address, 'rft');318319 const rftToken = await helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner, true);320321 {322 await rftToken.methods.approve(operator, 15n).send({from: owner});323 await contract.methods.setApprovalForAll(operator, true).send({from: owner});324 await rftToken.methods.burnFrom(owner, 10n).send({from: operator});325 const allowance = await rftToken.methods.allowance(owner, operator).call();326 expect(allowance).to.be.equal('5');327 }328 });329330 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {331 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});332333 const owner = await helper.eth.createAccountWithBalance(donor);334 const operator = await helper.eth.createAccountWithBalance(donor);335 const receiver = charlie;336337 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});338339 const address = helper.ethAddress.fromCollectionId(collection.collectionId);340 const contract = await helper.ethNativeContract.collection(address, 'rft');341342 {343 await contract.methods.setApprovalForAll(operator, true).send({from: owner});344 const ownerCross = helper.ethCrossAccount.fromAddress(owner);345 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);346 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});347 const event = result.events.Transfer;348 expect(event).to.be.like({349 address: helper.ethAddress.fromCollectionId(collection.collectionId),350 event: 'Transfer',351 returnValues: {352 from: owner,353 to: helper.address.substrateToEth(receiver.address),354 tokenId: token.tokenId.toString(),355 },356 });357 }358359 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);360 });361362 itEth('Can perform burn()', async ({helper}) => {363 const caller = await helper.eth.createAccountWithBalance(donor);364 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');365 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);366367 const result = await contract.methods.mint(caller).send();368 const tokenId = result.events.Transfer.returnValues.tokenId;369 {370 const result = await contract.methods.burn(tokenId).send();371 const event = result.events.Transfer;372 expect(event.address).to.equal(collectionAddress);373 expect(event.returnValues.from).to.equal(caller);374 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');375 expect(event.returnValues.tokenId).to.equal(tokenId.toString());376 }377 });378379 itEth('Can perform transferFrom()', async ({helper}) => {380 const caller = await helper.eth.createAccountWithBalance(donor);381 const receiver = helper.eth.createAccount();382 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');383 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);384385 const result = await contract.methods.mint(caller).send();386 const tokenId = result.events.Transfer.returnValues.tokenId;387388 const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);389390 const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, caller);391 await tokenContract.methods.repartition(15).send();392393 {394 const tokenEvents: any = [];395 tokenContract.events.allEvents((_: any, event: any) => {396 tokenEvents.push(event);397 });398 const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();399 if (tokenEvents.length == 0) await helper.wait.newBlocks(1);400401 let event = result.events.Transfer;402 expect(event.address).to.equal(collectionAddress);403 expect(event.returnValues.from).to.equal(caller);404 expect(event.returnValues.to).to.equal(receiver);405 expect(event.returnValues.tokenId).to.equal(tokenId.toString());406407 event = tokenEvents[0];408 expect(event.address).to.equal(tokenAddress);409 expect(event.returnValues.from).to.equal(caller);410 expect(event.returnValues.to).to.equal(receiver);411 expect(event.returnValues.value).to.equal('15');412 }413414 {415 const balance = await contract.methods.balanceOf(receiver).call();416 expect(+balance).to.equal(1);417 }418419 {420 const balance = await contract.methods.balanceOf(caller).call();421 expect(+balance).to.equal(0);422 }423 });424425 // Soft-deprecated426 itEth('Can perform burnFrom()', async ({helper}) => {427 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});428429 const owner = await helper.eth.createAccountWithBalance(donor, 100n);430 const spender = await helper.eth.createAccountWithBalance(donor, 100n);431432 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});433434 const address = helper.ethAddress.fromCollectionId(collection.collectionId);435 const contract = await helper.ethNativeContract.collection(address, 'rft', spender, true);436437 const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);438 const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner);439 await tokenContract.methods.repartition(15).send();440 await tokenContract.methods.approve(spender, 15).send();441442 {443 const result = await contract.methods.burnFrom(owner, token.tokenId).send();444 const event = result.events.Transfer;445 expect(event).to.be.like({446 address: helper.ethAddress.fromCollectionId(collection.collectionId),447 event: 'Transfer',448 returnValues: {449 from: owner,450 to: '0x0000000000000000000000000000000000000000',451 tokenId: token.tokenId.toString(),452 },453 });454 }455456 expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);457 });458459 itEth('Can perform burnFromCross()', async ({helper}) => {460 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});461462 const owner = bob;463 const spender = await helper.eth.createAccountWithBalance(donor, 100n);464465 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});466467 const address = helper.ethAddress.fromCollectionId(collection.collectionId);468 const contract = await helper.ethNativeContract.collection(address, 'rft');469470 await token.repartition(owner, 15n);471 await token.approve(owner, {Ethereum: spender}, 15n);472473 {474 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);475 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});476 const event = result.events.Transfer;477 expect(event).to.be.like({478 address: helper.ethAddress.fromCollectionId(collection.collectionId),479 event: 'Transfer',480 returnValues: {481 from: helper.address.substrateToEth(owner.address),482 to: '0x0000000000000000000000000000000000000000',483 tokenId: token.tokenId.toString(),484 },485 });486 }487488 expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);489 });490491 itEth('Can perform transferFromCross()', async ({helper}) => {492 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});493494 const owner = bob;495 const spender = await helper.eth.createAccountWithBalance(donor, 100n);496 const receiver = charlie;497498 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});499500 const address = helper.ethAddress.fromCollectionId(collection.collectionId);501 const contract = await helper.ethNativeContract.collection(address, 'rft');502503 await token.repartition(owner, 15n);504 await token.approve(owner, {Ethereum: spender}, 15n);505506 {507 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);508 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);509 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});510 const event = result.events.Transfer;511 expect(event).to.be.like({512 address: helper.ethAddress.fromCollectionId(collection.collectionId),513 event: 'Transfer',514 returnValues: {515 from: helper.address.substrateToEth(owner.address),516 to: helper.address.substrateToEth(receiver.address),517 tokenId: token.tokenId.toString(),518 },519 });520 }521522 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);523 });524525 itEth('Can perform transfer()', async ({helper}) => {526 const caller = await helper.eth.createAccountWithBalance(donor);527 const receiver = helper.eth.createAccount();528 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');529 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);530531 const result = await contract.methods.mint(caller).send();532 const tokenId = result.events.Transfer.returnValues.tokenId;533534 {535 const result = await contract.methods.transfer(receiver, tokenId).send();536537 const event = result.events.Transfer;538 expect(event.address).to.equal(collectionAddress);539 expect(event.returnValues.from).to.equal(caller);540 expect(event.returnValues.to).to.equal(receiver);541 expect(event.returnValues.tokenId).to.equal(tokenId.toString());542 }543544 {545 const balance = await contract.methods.balanceOf(caller).call();546 expect(+balance).to.equal(0);547 }548549 {550 const balance = await contract.methods.balanceOf(receiver).call();551 expect(+balance).to.equal(1);552 }553 });554555 itEth('Can perform transferCross()', async ({helper}) => {556 const sender = await helper.eth.createAccountWithBalance(donor);557 const receiverEth = await helper.eth.createAccountWithBalance(donor);558 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);559 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);560561 const collection = await helper.rft.mintCollection(minter, {});562 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);563 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);564565 const token = await collection.mintToken(minter, 50n, {Ethereum: sender});566567 {568 // Can transferCross to ethereum address:569 const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});570 // Check events:571 const event = result.events.Transfer;572 expect(event.address).to.equal(collectionAddress);573 expect(event.returnValues.from).to.equal(sender);574 expect(event.returnValues.to).to.equal(receiverEth);575 expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());576 // Sender's balance decreased:577 const senderBalance = await collectionEvm.methods.balanceOf(sender).call();578 expect(+senderBalance).to.equal(0);579 expect(await token.getBalance({Ethereum: sender})).to.eq(0n);580 // Receiver's balance increased:581 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();582 expect(+receiverBalance).to.equal(1);583 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);584 }585586 {587 // Can transferCross to substrate address:588 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});589 // Check events:590 const event = substrateResult.events.Transfer;591 expect(event.address).to.be.equal(collectionAddress);592 expect(event.returnValues.from).to.be.equal(receiverEth);593 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));594 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);595 // Sender's balance decreased:596 const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();597 expect(+senderBalance).to.equal(0);598 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);599 // Receiver's balance increased:600 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});601 expect(receiverBalance).to.contain(token.tokenId);602 expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);603 }604 });605606 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {607 const sender = await helper.eth.createAccountWithBalance(donor);608 const tokenOwner = await helper.eth.createAccountWithBalance(donor);609 const receiverSub = minter;610 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);611612 const collection = await helper.rft.mintCollection(minter, {});613 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);614 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);615616 await collection.mintToken(minter, 50n, {Ethereum: sender});617 const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});618619 // Cannot transferCross someone else's token:620 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;621 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;622 // Cannot transfer token if it does not exist:623 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;624 }));625626 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {627 const caller = await helper.eth.createAccountWithBalance(donor);628 const receiver = helper.eth.createAccount();629 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');630 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);631632 const result = await contract.methods.mint(caller).send();633 const tokenId = result.events.Transfer.returnValues.tokenId;634635 const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);636637 await tokenContract.methods.repartition(2).send();638 await tokenContract.methods.transfer(receiver, 1).send();639640 const events: any = [];641 contract.events.allEvents((_: any, event: any) => {642 events.push(event);643 });644645 await tokenContract.methods.transfer(receiver, 1).send();646 if (events.length == 0) await helper.wait.newBlocks(1);647 const event = events[0];648649 expect(event.address).to.equal(collectionAddress);650 expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');651 expect(event.returnValues.to).to.equal(receiver);652 expect(event.returnValues.tokenId).to.equal(tokenId.toString());653 });654655 itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {656 const caller = await helper.eth.createAccountWithBalance(donor);657 const receiver = helper.eth.createAccount();658 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');659 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);660661 const result = await contract.methods.mint(caller).send();662 const tokenId = result.events.Transfer.returnValues.tokenId;663664 const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);665666 await tokenContract.methods.repartition(2).send();667668 const events: any = [];669 contract.events.allEvents((_: any, event: any) => {670 events.push(event);671 });672673 await tokenContract.methods.transfer(receiver, 1).send();674 if (events.length == 0) await helper.wait.newBlocks(1);675 const event = events[0];676677 expect(event.address).to.equal(collectionAddress);678 expect(event.returnValues.from).to.equal(caller);679 expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');680 expect(event.returnValues.tokenId).to.equal(tokenId.toString());681 });682});683684describe('RFT: Fees', () => {685 let donor: IKeyringPair;686687 before(async function() {688 await usingEthPlaygrounds(async (helper, privateKey) => {689 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);690691 donor = await privateKey({filename: __filename});692 });693 });694695 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {696 const caller = await helper.eth.createAccountWithBalance(donor);697 const receiver = helper.eth.createAccount();698 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');699 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);700701 const result = await contract.methods.mint(caller).send();702 const tokenId = result.events.Transfer.returnValues.tokenId;703704 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());705 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));706 expect(cost > 0n);707 });708709 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {710 const caller = await helper.eth.createAccountWithBalance(donor);711 const receiver = helper.eth.createAccount();712 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');713 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);714715 const result = await contract.methods.mint(caller).send();716 const tokenId = result.events.Transfer.returnValues.tokenId;717718 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());719 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));720 expect(cost > 0n);721 });722});723724describe('Common metadata', () => {725 let donor: IKeyringPair;726 let alice: IKeyringPair;727728 before(async function() {729 await usingEthPlaygrounds(async (helper, privateKey) => {730 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);731732 donor = await privateKey({filename: __filename});733 [alice] = await helper.arrange.createAccounts([20n], donor);734 });735 });736737 itEth('Returns collection name', async ({helper}) => {738 const caller = helper.eth.createAccount();739 const tokenPropertyPermissions = [{740 key: 'URI',741 permission: {742 mutable: true,743 collectionAdmin: true,744 tokenOwner: false,745 },746 }];747 const collection = await helper.rft.mintCollection(748 alice,749 {750 name: 'Leviathan',751 tokenPrefix: '11',752 properties: [{key: 'ERC721Metadata', value: '1'}],753 tokenPropertyPermissions,754 },755 );756757 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);758 const name = await contract.methods.name().call();759 expect(name).to.equal('Leviathan');760 });761762 itEth('Returns symbol name', async ({helper}) => {763 const caller = await helper.eth.createAccountWithBalance(donor);764 const tokenPropertyPermissions = [{765 key: 'URI',766 permission: {767 mutable: true,768 collectionAdmin: true,769 tokenOwner: false,770 },771 }];772 const {collectionId} = await helper.rft.mintCollection(773 alice,774 {775 name: 'Leviathan',776 tokenPrefix: '12',777 properties: [{key: 'ERC721Metadata', value: '1'}],778 tokenPropertyPermissions,779 },780 );781782 const contract = await helper.ethNativeContract.collectionById(collectionId, 'rft', caller);783 const symbol = await contract.methods.symbol().call();784 expect(symbol).to.equal('12');785 });786});787788describe('Negative tests', () => {789 let donor: IKeyringPair;790 let minter: IKeyringPair;791 let alice: IKeyringPair;792793 before(async function() {794 await usingEthPlaygrounds(async (helper, privateKey) => {795 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);796797 donor = await privateKey({filename: __filename});798 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);799 });800 });801802 itEth('[negative] Cant perform burn without approval', async ({helper}) => {803 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});804805 const owner = await helper.eth.createAccountWithBalance(donor, 100n);806 const spender = await helper.eth.createAccountWithBalance(donor, 100n);807808 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});809810 const address = helper.ethAddress.fromCollectionId(collection.collectionId);811 const contract = await helper.ethNativeContract.collection(address, 'rft');812813 const ownerCross = helper.ethCrossAccount.fromAddress(owner);814815 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;816817 await contract.methods.setApprovalForAll(spender, true).send({from: owner});818 await contract.methods.setApprovalForAll(spender, false).send({from: owner});819820 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;821 });822823 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {824 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});825 const owner = await helper.eth.createAccountWithBalance(donor, 100n);826 const receiver = alice;827828 const spender = await helper.eth.createAccountWithBalance(donor, 100n);829830 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});831832 const address = helper.ethAddress.fromCollectionId(collection.collectionId);833 const contract = await helper.ethNativeContract.collection(address, 'rft');834835 const ownerCross = helper.ethCrossAccount.fromAddress(owner);836 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);837838 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;839840 await contract.methods.setApprovalForAll(spender, true).send({from: owner});841 await contract.methods.setApprovalForAll(spender, false).send({from: owner});842843 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;844 });845});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: Plain calls', () => {23 let donor: IKeyringPair;24 let minter: IKeyringPair;25 let bob: IKeyringPair;26 let charlie: IKeyringPair;2728 before(async function() {29 await usingEthPlaygrounds(async (helper, privateKey) => {30 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);3132 donor = await privateKey({filename: __filename});33 [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);34 });35 });3637 [38 'substrate' as const,39 'ethereum' as const,40 ].map(testCase => {41 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {42 const collectionAdmin = await helper.eth.createAccountWithBalance(donor);4344 const receiverEth = helper.eth.createAccount();45 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);46 const receiverSub = bob;47 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub);4849 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });50 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {51 tokenOwner: false,52 collectionAdmin: true,53 mutable: false}};54 });555657 const collection = await helper.rft.mintCollection(minter, {58 tokenPrefix: 'ethp',59 tokenPropertyPermissions: permissions,60 });61 await collection.addAdmin(minter, {Ethereum: collectionAdmin});6263 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);64 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', collectionAdmin, true);65 let expectedTokenId = await contract.methods.nextTokenId().call();66 let result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, []).send();67 let tokenId = result.events.Transfer.returnValues.tokenId;68 expect(tokenId).to.be.equal(expectedTokenId);6970 let event = result.events.Transfer;71 expect(event.address).to.be.equal(collectionAddress);72 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');73 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));74 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);7576 expectedTokenId = await contract.methods.nextTokenId().call();77 result = await contract.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, properties).send();78 event = result.events.Transfer;79 expect(event.address).to.be.equal(collectionAddress);80 expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');81 expect(event.returnValues.to).to.be.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(bob.address));82 expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);8384 tokenId = result.events.Transfer.returnValues.tokenId;8586 expect(tokenId).to.be.equal(expectedTokenId);8788 expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties89 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));9091 expect(await helper.nft.getTokenOwner(collection.collectionId, tokenId))92 .to.deep.eq(testCase === 'ethereum' ? {Ethereum: receiverEth.toLowerCase()} : {Substrate: receiverSub.address});93 });94 });9596 itEth.skip('Can perform mintBulk()', async ({helper}) => {97 const owner = await helper.eth.createAccountWithBalance(donor);98 const receiver = helper.eth.createAccount();99 const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');100 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);101102 {103 const nextTokenId = await contract.methods.nextTokenId().call();104 expect(nextTokenId).to.be.equal('1');105 const result = await contract.methods.mintBulkWithTokenURI(106 receiver,107 [108 [nextTokenId, 'Test URI 0'],109 [+nextTokenId + 1, 'Test URI 1'],110 [+nextTokenId + 2, 'Test URI 2'],111 ],112 ).send();113114 const events = result.events.Transfer;115 for (let i = 0; i < 2; i++) {116 const event = events[i];117 expect(event.address).to.equal(collectionAddress);118 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');119 expect(event.returnValues.to).to.equal(receiver);120 expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));121 }122123 expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');124 expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');125 expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');126 }127 });128129 itEth('Can perform setApprovalForAll()', async ({helper}) => {130 const owner = await helper.eth.createAccountWithBalance(donor);131 const operator = helper.eth.createAccount();132133 const collection = await helper.rft.mintCollection(minter, {});134135 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);136 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner);137138 const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();139 expect(approvedBefore).to.be.equal(false);140141 {142 const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});143144 expect(result.events.ApprovalForAll).to.be.like({145 address: collectionAddress,146 event: 'ApprovalForAll',147 returnValues: {148 owner,149 operator,150 approved: true,151 },152 });153154 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();155 expect(approvedAfter).to.be.equal(true);156 }157158 {159 const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});160161 expect(result.events.ApprovalForAll).to.be.like({162 address: collectionAddress,163 event: 'ApprovalForAll',164 returnValues: {165 owner,166 operator,167 approved: false,168 },169 });170171 const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();172 expect(approvedAfter).to.be.equal(false);173 }174 });175176 itEth('Can perform burn with ApprovalForAll', async ({helper}) => {177 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});178179 const owner = await helper.eth.createAccountWithBalance(donor);180 const operator = await helper.eth.createAccountWithBalance(donor, 100n);181182 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});183184 const address = helper.ethAddress.fromCollectionId(collection.collectionId);185 const contract = await helper.ethNativeContract.collection(address, 'rft');186187 {188 await contract.methods.setApprovalForAll(operator, true).send({from: owner});189 const ownerCross = helper.ethCrossAccount.fromAddress(owner);190 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});191 const events = result.events.Transfer;192193 expect(events).to.be.like({194 address,195 event: 'Transfer',196 returnValues: {197 from: owner,198 to: '0x0000000000000000000000000000000000000000',199 tokenId: token.tokenId.toString(),200 },201 });202 }203 });204205 itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {206 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});207208 const owner = await helper.eth.createAccountWithBalance(donor);209 const operator = await helper.eth.createAccountWithBalance(donor, 100n);210211 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});212213 const address = helper.ethAddress.fromCollectionId(collection.collectionId);214 const contract = await helper.ethNativeContract.collection(address, 'rft');215216 const rftToken = await helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner, true);217218 {219 await rftToken.methods.approve(operator, 15n).send({from: owner});220 await contract.methods.setApprovalForAll(operator, true).send({from: owner});221 await rftToken.methods.burnFrom(owner, 10n).send({from: operator});222 const allowance = await rftToken.methods.allowance(owner, operator).call();223 expect(allowance).to.be.equal('5');224 }225 });226227 itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {228 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});229230 const owner = await helper.eth.createAccountWithBalance(donor);231 const operator = await helper.eth.createAccountWithBalance(donor);232 const receiver = charlie;233234 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});235236 const address = helper.ethAddress.fromCollectionId(collection.collectionId);237 const contract = await helper.ethNativeContract.collection(address, 'rft');238239 {240 await contract.methods.setApprovalForAll(operator, true).send({from: owner});241 const ownerCross = helper.ethCrossAccount.fromAddress(owner);242 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);243 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});244 const event = result.events.Transfer;245 expect(event).to.be.like({246 address: helper.ethAddress.fromCollectionId(collection.collectionId),247 event: 'Transfer',248 returnValues: {249 from: owner,250 to: helper.address.substrateToEth(receiver.address),251 tokenId: token.tokenId.toString(),252 },253 });254 }255256 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);257 });258259 itEth('Can perform burn()', async ({helper}) => {260 const caller = await helper.eth.createAccountWithBalance(donor);261 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');262 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);263264 const result = await contract.methods.mint(caller).send();265 const tokenId = result.events.Transfer.returnValues.tokenId;266 {267 const result = await contract.methods.burn(tokenId).send();268 const event = result.events.Transfer;269 expect(event.address).to.equal(collectionAddress);270 expect(event.returnValues.from).to.equal(caller);271 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');272 expect(event.returnValues.tokenId).to.equal(tokenId.toString());273 }274 });275276 itEth('Can perform transferFrom()', async ({helper}) => {277 const caller = await helper.eth.createAccountWithBalance(donor);278 const receiver = helper.eth.createAccount();279 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');280 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);281282 const result = await contract.methods.mint(caller).send();283 const tokenId = result.events.Transfer.returnValues.tokenId;284285 const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);286287 const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, caller);288 await tokenContract.methods.repartition(15).send();289290 {291 const tokenEvents: any = [];292 tokenContract.events.allEvents((_: any, event: any) => {293 tokenEvents.push(event);294 });295 const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();296 if (tokenEvents.length == 0) await helper.wait.newBlocks(1);297298 let event = result.events.Transfer;299 expect(event.address).to.equal(collectionAddress);300 expect(event.returnValues.from).to.equal(caller);301 expect(event.returnValues.to).to.equal(receiver);302 expect(event.returnValues.tokenId).to.equal(tokenId.toString());303304 event = tokenEvents[0];305 expect(event.address).to.equal(tokenAddress);306 expect(event.returnValues.from).to.equal(caller);307 expect(event.returnValues.to).to.equal(receiver);308 expect(event.returnValues.value).to.equal('15');309 }310311 {312 const balance = await contract.methods.balanceOf(receiver).call();313 expect(+balance).to.equal(1);314 }315316 {317 const balance = await contract.methods.balanceOf(caller).call();318 expect(+balance).to.equal(0);319 }320 });321322 // Soft-deprecated323 itEth('Can perform burnFrom()', async ({helper}) => {324 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});325326 const owner = await helper.eth.createAccountWithBalance(donor, 100n);327 const spender = await helper.eth.createAccountWithBalance(donor, 100n);328329 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});330331 const address = helper.ethAddress.fromCollectionId(collection.collectionId);332 const contract = await helper.ethNativeContract.collection(address, 'rft', spender, true);333334 const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);335 const tokenContract = await helper.ethNativeContract.rftToken(tokenAddress, owner);336 await tokenContract.methods.repartition(15).send();337 await tokenContract.methods.approve(spender, 15).send();338339 {340 const result = await contract.methods.burnFrom(owner, token.tokenId).send();341 const event = result.events.Transfer;342 expect(event).to.be.like({343 address: helper.ethAddress.fromCollectionId(collection.collectionId),344 event: 'Transfer',345 returnValues: {346 from: owner,347 to: '0x0000000000000000000000000000000000000000',348 tokenId: token.tokenId.toString(),349 },350 });351 }352353 expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);354 });355356 itEth('Can perform burnFromCross()', async ({helper}) => {357 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});358359 const owner = bob;360 const spender = await helper.eth.createAccountWithBalance(donor, 100n);361362 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});363364 const address = helper.ethAddress.fromCollectionId(collection.collectionId);365 const contract = await helper.ethNativeContract.collection(address, 'rft');366367 await token.repartition(owner, 15n);368 await token.approve(owner, {Ethereum: spender}, 15n);369370 {371 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);372 const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});373 const event = result.events.Transfer;374 expect(event).to.be.like({375 address: helper.ethAddress.fromCollectionId(collection.collectionId),376 event: 'Transfer',377 returnValues: {378 from: helper.address.substrateToEth(owner.address),379 to: '0x0000000000000000000000000000000000000000',380 tokenId: token.tokenId.toString(),381 },382 });383 }384385 expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);386 });387388 itEth('Can perform transferFromCross()', async ({helper}) => {389 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});390391 const owner = bob;392 const spender = await helper.eth.createAccountWithBalance(donor, 100n);393 const receiver = charlie;394395 const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});396397 const address = helper.ethAddress.fromCollectionId(collection.collectionId);398 const contract = await helper.ethNativeContract.collection(address, 'rft');399400 await token.repartition(owner, 15n);401 await token.approve(owner, {Ethereum: spender}, 15n);402403 {404 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);405 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);406 const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});407 const event = result.events.Transfer;408 expect(event).to.be.like({409 address: helper.ethAddress.fromCollectionId(collection.collectionId),410 event: 'Transfer',411 returnValues: {412 from: helper.address.substrateToEth(owner.address),413 to: helper.address.substrateToEth(receiver.address),414 tokenId: token.tokenId.toString(),415 },416 });417 }418419 expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);420 });421422 itEth('Can perform transfer()', async ({helper}) => {423 const caller = await helper.eth.createAccountWithBalance(donor);424 const receiver = helper.eth.createAccount();425 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');426 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);427428 const result = await contract.methods.mint(caller).send();429 const tokenId = result.events.Transfer.returnValues.tokenId;430431 {432 const result = await contract.methods.transfer(receiver, tokenId).send();433434 const event = result.events.Transfer;435 expect(event.address).to.equal(collectionAddress);436 expect(event.returnValues.from).to.equal(caller);437 expect(event.returnValues.to).to.equal(receiver);438 expect(event.returnValues.tokenId).to.equal(tokenId.toString());439 }440441 {442 const balance = await contract.methods.balanceOf(caller).call();443 expect(+balance).to.equal(0);444 }445446 {447 const balance = await contract.methods.balanceOf(receiver).call();448 expect(+balance).to.equal(1);449 }450 });451452 itEth('Can perform transferCross()', async ({helper}) => {453 const sender = await helper.eth.createAccountWithBalance(donor);454 const receiverEth = await helper.eth.createAccountWithBalance(donor);455 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);456 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);457458 const collection = await helper.rft.mintCollection(minter, {});459 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);460 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);461462 const token = await collection.mintToken(minter, 50n, {Ethereum: sender});463464 {465 // Can transferCross to ethereum address:466 const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});467 // Check events:468 const event = result.events.Transfer;469 expect(event.address).to.equal(collectionAddress);470 expect(event.returnValues.from).to.equal(sender);471 expect(event.returnValues.to).to.equal(receiverEth);472 expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());473 // Sender's balance decreased:474 const senderBalance = await collectionEvm.methods.balanceOf(sender).call();475 expect(+senderBalance).to.equal(0);476 expect(await token.getBalance({Ethereum: sender})).to.eq(0n);477 // Receiver's balance increased:478 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();479 expect(+receiverBalance).to.equal(1);480 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);481 }482483 {484 // Can transferCross to substrate address:485 const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});486 // Check events:487 const event = substrateResult.events.Transfer;488 expect(event.address).to.be.equal(collectionAddress);489 expect(event.returnValues.from).to.be.equal(receiverEth);490 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));491 expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);492 // Sender's balance decreased:493 const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();494 expect(+senderBalance).to.equal(0);495 expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);496 // Receiver's balance increased:497 const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});498 expect(receiverBalance).to.contain(token.tokenId);499 expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);500 }501 });502503 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {504 const sender = await helper.eth.createAccountWithBalance(donor);505 const tokenOwner = await helper.eth.createAccountWithBalance(donor);506 const receiverSub = minter;507 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);508509 const collection = await helper.rft.mintCollection(minter, {});510 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);511 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', sender);512513 await collection.mintToken(minter, 50n, {Ethereum: sender});514 const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});515516 // Cannot transferCross someone else's token:517 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;518 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;519 // Cannot transfer token if it does not exist:520 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;521 }));522523 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {524 const caller = await helper.eth.createAccountWithBalance(donor);525 const receiver = helper.eth.createAccount();526 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');527 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);528529 const result = await contract.methods.mint(caller).send();530 const tokenId = result.events.Transfer.returnValues.tokenId;531532 const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);533534 await tokenContract.methods.repartition(2).send();535 await tokenContract.methods.transfer(receiver, 1).send();536537 const events: any = [];538 contract.events.allEvents((_: any, event: any) => {539 events.push(event);540 });541542 await tokenContract.methods.transfer(receiver, 1).send();543 if (events.length == 0) await helper.wait.newBlocks(1);544 const event = events[0];545546 expect(event.address).to.equal(collectionAddress);547 expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');548 expect(event.returnValues.to).to.equal(receiver);549 expect(event.returnValues.tokenId).to.equal(tokenId.toString());550 });551552 itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {553 const caller = await helper.eth.createAccountWithBalance(donor);554 const receiver = helper.eth.createAccount();555 const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');556 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);557558 const result = await contract.methods.mint(caller).send();559 const tokenId = result.events.Transfer.returnValues.tokenId;560561 const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);562563 await tokenContract.methods.repartition(2).send();564565 const events: any = [];566 contract.events.allEvents((_: any, event: any) => {567 events.push(event);568 });569570 await tokenContract.methods.transfer(receiver, 1).send();571 if (events.length == 0) await helper.wait.newBlocks(1);572 const event = events[0];573574 expect(event.address).to.equal(collectionAddress);575 expect(event.returnValues.from).to.equal(caller);576 expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');577 expect(event.returnValues.tokenId).to.equal(tokenId.toString());578 });579});580581describe('RFT: Fees', () => {582 let donor: IKeyringPair;583584 before(async function() {585 await usingEthPlaygrounds(async (helper, privateKey) => {586 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);587588 donor = await privateKey({filename: __filename});589 });590 });591592 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {593 const caller = await helper.eth.createAccountWithBalance(donor);594 const receiver = helper.eth.createAccount();595 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');596 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);597598 const result = await contract.methods.mint(caller).send();599 const tokenId = result.events.Transfer.returnValues.tokenId;600601 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());602 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));603 expect(cost > 0n);604 });605606 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {607 const caller = await helper.eth.createAccountWithBalance(donor);608 const receiver = helper.eth.createAccount();609 const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');610 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);611612 const result = await contract.methods.mint(caller).send();613 const tokenId = result.events.Transfer.returnValues.tokenId;614615 const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());616 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));617 expect(cost > 0n);618 });619});620621describe('Common metadata', () => {622 let donor: IKeyringPair;623 let alice: IKeyringPair;624625 before(async function() {626 await usingEthPlaygrounds(async (helper, privateKey) => {627 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);628629 donor = await privateKey({filename: __filename});630 [alice] = await helper.arrange.createAccounts([20n], donor);631 });632 });633634 itEth('Returns collection name', async ({helper}) => {635 const caller = helper.eth.createAccount();636 const tokenPropertyPermissions = [{637 key: 'URI',638 permission: {639 mutable: true,640 collectionAdmin: true,641 tokenOwner: false,642 },643 }];644 const collection = await helper.rft.mintCollection(645 alice,646 {647 name: 'Leviathan',648 tokenPrefix: '11',649 properties: [{key: 'ERC721Metadata', value: '1'}],650 tokenPropertyPermissions,651 },652 );653654 const contract = await helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);655 const name = await contract.methods.name().call();656 expect(name).to.equal('Leviathan');657 });658659 itEth('Returns symbol name', async ({helper}) => {660 const caller = await helper.eth.createAccountWithBalance(donor);661 const tokenPropertyPermissions = [{662 key: 'URI',663 permission: {664 mutable: true,665 collectionAdmin: true,666 tokenOwner: false,667 },668 }];669 const {collectionId} = await helper.rft.mintCollection(670 alice,671 {672 name: 'Leviathan',673 tokenPrefix: '12',674 properties: [{key: 'ERC721Metadata', value: '1'}],675 tokenPropertyPermissions,676 },677 );678679 const contract = await helper.ethNativeContract.collectionById(collectionId, 'rft', caller);680 const symbol = await contract.methods.symbol().call();681 expect(symbol).to.equal('12');682 });683});684685describe('Negative tests', () => {686 let donor: IKeyringPair;687 let minter: IKeyringPair;688 let alice: IKeyringPair;689690 before(async function() {691 await usingEthPlaygrounds(async (helper, privateKey) => {692 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);693694 donor = await privateKey({filename: __filename});695 [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);696 });697 });698699 itEth('[negative] Cant perform burn without approval', async ({helper}) => {700 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});701702 const owner = await helper.eth.createAccountWithBalance(donor, 100n);703 const spender = await helper.eth.createAccountWithBalance(donor, 100n);704705 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});706707 const address = helper.ethAddress.fromCollectionId(collection.collectionId);708 const contract = await helper.ethNativeContract.collection(address, 'rft');709710 const ownerCross = helper.ethCrossAccount.fromAddress(owner);711712 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;713714 await contract.methods.setApprovalForAll(spender, true).send({from: owner});715 await contract.methods.setApprovalForAll(spender, false).send({from: owner});716717 await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;718 });719720 itEth('[negative] Cant perform transfer without approval', async ({helper}) => {721 const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});722 const owner = await helper.eth.createAccountWithBalance(donor, 100n);723 const receiver = alice;724725 const spender = await helper.eth.createAccountWithBalance(donor, 100n);726727 const token = await collection.mintToken(minter, 100n, {Ethereum: owner});728729 const address = helper.ethAddress.fromCollectionId(collection.collectionId);730 const contract = await helper.ethNativeContract.collection(address, 'rft');731732 const ownerCross = helper.ethCrossAccount.fromAddress(owner);733 const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);734735 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;736737 await contract.methods.setApprovalForAll(spender, true).send({from: owner});738 await contract.methods.setApprovalForAll(spender, false).send({from: owner});739740 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;741 });742});tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -19,51 +19,6 @@
import {IKeyringPair} from '@polkadot/types/types';
import {Contract} from 'web3-eth-contract';
-
-describe('Refungible token: Information getting', () => {
- let donor: IKeyringPair;
- let alice: IKeyringPair;
-
- before(async function() {
- await usingEthPlaygrounds(async (helper, privateKey) => {
- requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-
- donor = await privateKey({filename: __filename});
- [alice] = await helper.arrange.createAccounts([20n], donor);
- });
- });
-
- itEth('totalSupply', async ({helper}) => {
- const caller = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
- const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller);
- const totalSupply = await contract.methods.totalSupply().call();
- expect(totalSupply).to.equal('200');
- });
-
- itEth('balanceOf', async ({helper}) => {
- const caller = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
- const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller);
- const balance = await contract.methods.balanceOf(caller).call();
- expect(balance).to.equal('200');
- });
-
- itEth('decimals', async ({helper}) => {
- const caller = await helper.eth.createAccountWithBalance(donor);
- const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'});
- const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller});
-
- const contract = await helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller);
- const decimals = await contract.methods.decimals().call();
- expect(decimals).to.equal('0');
- });
-});
-
// FIXME: Need erc721 for ReFubgible.
describe('Check ERC721 token URI for ReFungible', () => {
let donor: IKeyringPair;
tests/src/eth/tokens/callMethodsERC20.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/tokens/callMethodsERC20.test.ts
@@ -0,0 +1,91 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {Pallets, requirePalletsOrSkip} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+[
+ {mode: 'ft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+].map(testCase => {
+ describe(`${testCase.mode.toUpperCase()}: ERC-20 call methods`, () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, testCase.requiredPallets);
+ donor = await privateKey({filename: __filename});
+ });
+ });
+
+ itEth('totalSupply', async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
+
+ const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
+ if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+ // Use collection contract for FT or token contract for RFT:
+ const contract = testCase.mode === 'ft'
+ ? collection
+ : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+ // Mint tokens:
+ testCase.mode === 'ft'
+ ? await contract.methods.mint(...mintingParams).send({from: caller})
+ : await contract.methods.repartition(200).send({from: caller});
+
+ const totalSupply = await contract.methods.totalSupply().call();
+ expect(totalSupply).to.equal('200');
+ });
+
+ itEth('balanceOf', async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller];
+
+ const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+ if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+ // Use collection contract for FT or token contract for RFT:
+ const contract = testCase.mode === 'ft'
+ ? collection
+ : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+ // Mint tokens:
+ testCase.mode === 'ft'
+ ? await contract.methods.mint(...mintingParams).send({from: caller})
+ : await contract.methods.repartition(200).send({from: caller});
+
+ const balance = await contract.methods.balanceOf(caller).call();
+ expect(balance).to.equal('200');
+ });
+
+ itEth('decimals', async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+ if (testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller});
+
+ // Use collection contract for FT or token contract for RFT:
+ const contract = testCase.mode === 'ft'
+ ? collection
+ : await helper.ethNativeContract.rftTokenById(collectionId, 1, caller);
+
+ const decimals = await contract.methods.decimals().call();
+ expect(decimals).to.equal(testCase.mode === 'rft' ? '0' : '18');
+ });
+ });
+});
\ No newline at end of file
tests/src/eth/tokens/callMethodsERC721.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/tokens/callMethodsERC721.test.ts
@@ -0,0 +1,143 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {Pallets} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+
+
+describe('ERC-721 call methods', () => {
+ let donor: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ });
+ });
+
+ [
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'nft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: name/symbol/description`, testCase.requiredPallets, async ({helper}) => {
+ const callerEth = await helper.eth.createAccountWithBalance(donor);
+ const [callerSub] = await helper.arrange.createAccounts([100n], donor);
+ const [name, description, tokenPrefix] = ['Name', 'Description', 'Symbol'];
+
+ const {collection: collectionEth} = await helper.eth.createCollection(testCase.mode, callerEth, name, description, tokenPrefix);
+ await collectionEth.methods.mint(callerEth).send({from: callerEth});
+ const {collectionId} = await helper[testCase.mode].mintCollection(callerSub, {name, description, tokenPrefix});
+ const collectionSub = await helper.ethNativeContract.collectionById(collectionId, testCase.mode, callerEth);
+
+ // Can get name/symbol/description for Eth collection
+ expect(await collectionEth.methods.name().call()).to.eq(name);
+ expect(await collectionEth.methods.symbol().call()).to.eq(tokenPrefix);
+ expect(await collectionEth.methods.description().call()).to.eq(description);
+ // Can get name/symbol/description for Sub collection
+ expect(await collectionSub.methods.name().call()).to.eq(name);
+ expect(await collectionSub.methods.symbol().call()).to.eq(tokenPrefix);
+ expect(await collectionSub.methods.description().call()).to.eq(description);
+ });
+ });
+
+ [
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'nft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: totalSupply`, testCase.requiredPallets, async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+
+ const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6');
+ await collection.methods.mint(caller).send({from: caller});
+
+ const totalSupply = await collection.methods.totalSupply().call();
+ expect(totalSupply).to.equal('1');
+ });
+ });
+
+ [
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'nft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: balanceOf`, testCase.requiredPallets, async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+
+ const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix');
+ await collection.methods.mint(caller).send({from: caller});
+ await collection.methods.mint(caller).send({from: caller});
+ await collection.methods.mint(caller).send({from: caller});
+
+ const balance = await collection.methods.balanceOf(caller).call();
+ expect(balance).to.equal('3');
+ });
+ });
+
+ [
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'nft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: ownerOf`, testCase.requiredPallets, async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf', '6', '6');
+
+ const result = await collection.methods.mint(caller).send();
+ const tokenId = result.events.Transfer.returnValues.tokenId;
+
+ const owner = await collection.methods.ownerOf(tokenId).call();
+ expect(owner).to.equal(caller);
+ });
+ });
+
+ [
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ // TODO {mode: 'nft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: ownerOf after burn`, testCase.requiredPallets, async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+ const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf-AfterBurn', '6', '6');
+
+ const result = await collection.methods.mint(caller).send();
+ const tokenId = result.events.Transfer.returnValues.tokenId;
+ const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller, true);
+
+ await tokenContract.methods.repartition(2).send();
+ await tokenContract.methods.transfer(receiver, 1).send();
+
+ await tokenContract.methods.burnFrom(caller, 1).send();
+
+ const owner = await collection.methods.ownerOf(tokenId).call();
+ expect(owner).to.equal(receiver);
+ });
+ });
+
+ itEth.ifWithPallets('RFT: ownerOf for partial ownership', [Pallets.ReFungible], async ({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+ const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');
+ const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
+
+ const result = await contract.methods.mint(caller).send();
+ const tokenId = result.events.Transfer.returnValues.tokenId;
+ const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);
+
+ await tokenContract.methods.repartition(2).send();
+ await tokenContract.methods.transfer(receiver, 1).send();
+
+ const owner = await contract.methods.ownerOf(tokenId).call();
+ expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
+ });
+});
tests/src/eth/tokens/minting.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/tokens/minting.test.ts
@@ -0,0 +1,167 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets} from '../../util';
+import {expect, itEth, usingEthPlaygrounds} from '../util';
+
+
+describe('Minting tokens', () => {
+ let donor: IKeyringPair;
+ let alice: IKeyringPair;
+
+ before(async function() {
+ await usingEthPlaygrounds(async (helper, privateKey) => {
+ donor = await privateKey({filename: __filename});
+ [alice] = await helper.arrange.createAccounts([30n, 20n], donor);
+ });
+ });
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'ft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: Can mint() for Substrate collection`, testCase.requiredPallets, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+ const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+ const collection = await helper[testCase.mode].mintCollection(alice);
+ await collection.addAdmin(alice, {Ethereum: owner});
+
+ const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);
+
+ const result = await contract.methods.mint(...mintingParams).send({from: owner});
+
+ // Check events:
+ const event = result.events.Transfer;
+ expect(event.address).to.equal(collectionAddress);
+ expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+ expect(event.returnValues.to).to.equal(receiver);
+ if (testCase.mode === 'ft')
+ expect(event.returnValues.value).to.equal('100');
+
+ // Check token exist:
+ if(testCase.mode === 'ft') {
+ expect(await helper.ft.getBalance(collection.collectionId, {Ethereum: receiver})).to.eq(100n);
+ } else {
+ const tokenId = event.returnValues.tokenId;
+ expect(tokenId).to.be.equal('1');
+ expect(await helper.collection.getLastTokenId(collection.collectionId)).to.eq(1);
+ expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+ }
+ });
+ });
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'ft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: Can mint() for Ethereum collection`, testCase.requiredPallets, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+ const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+ const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix');
+
+ const result = await collection.methods.mint(...mintingParams).send({from: owner});
+
+ // Check events:
+ const event = result.events.Transfer;
+ expect(event.address).to.equal(collectionAddress);
+ expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+ expect(event.returnValues.to).to.equal(receiver);
+ if (testCase.mode === 'ft')
+ expect(event.returnValues.value).to.equal('100');
+
+ // Check token exist:
+ if(testCase.mode === 'ft') {
+ expect(await helper.ft.getBalance(collectionId, {Ethereum: receiver})).to.eq(100n);
+ } else {
+ const tokenId = event.returnValues.tokenId;
+ expect(tokenId).to.be.equal('1');
+ expect(await helper.collection.getLastTokenId(collectionId)).to.eq(1);
+ expect(await collection.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+ }
+ });
+ });
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ {mode: 'ft' as const, requiredPallets: []},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: Can mint() for Ethereum collection`, testCase.requiredPallets, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+ const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver];
+
+ const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix');
+
+ const result = await collection.methods.mint(...mintingParams).send({from: owner});
+
+ // Check events:
+ const event = result.events.Transfer;
+ expect(event.address).to.equal(collectionAddress);
+ expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+ expect(event.returnValues.to).to.equal(receiver);
+ if (testCase.mode === 'ft')
+ expect(event.returnValues.value).to.equal('100');
+
+ // Check token exist:
+ if(testCase.mode === 'ft') {
+ expect(await helper.ft.getBalance(collectionId, {Ethereum: receiver})).to.eq(100n);
+ } else {
+ const tokenId = event.returnValues.tokenId;
+ expect(tokenId).to.be.equal('1');
+ expect(await helper.collection.getLastTokenId(collectionId)).to.eq(1);
+ expect(await collection.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+ }
+ });
+ });
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase => {
+ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: Can mintWithTokenURI() for Ethereum collection`, testCase.requiredPallets, async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const receiver = helper.eth.createAccount();
+
+ const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', '6', '6', '');
+ const contract = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner);
+
+ const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();
+ const tokenId = result.events.Transfer.returnValues.tokenId;
+ expect(tokenId).to.be.equal('1');
+
+ 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(receiver);
+
+ expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
+ expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
+ // TODO: this wont work right now, need release 919000 first
+ // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();
+ // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();
+ // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);
+ });
+ });
+});
\ No newline at end of file
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -257,7 +257,7 @@
}
}
- async createCollection(mode: TCollectionMode, signer: string, name: string, description: string, tokenPrefix: string, decimals = 18): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> {
+ async createCollection(mode: TCollectionMode, signer: string, name: string, description: string, tokenPrefix: string, decimals = 18, mergeDeprecated = false): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[], collection: Contract }> {
const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice();
const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(signer);
const functionName: string = this.createCollectionMethodName(mode);
@@ -268,8 +268,9 @@
const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId);
const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress);
const events = this.helper.eth.normalizeEvents(result.events);
+ const collection = await this.helper.ethNativeContract.collectionById(collectionId, mode, signer, mergeDeprecated);
- return {collectionId, collectionAddress, events};
+ return {collectionId, collectionAddress, events, collection};
}
createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> {
tests/src/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -15,7 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {expect, itSub, Pallets, usingPlaygrounds} from '../util';
+import {expect, itSub, usingPlaygrounds} from '../util';
describe('Integration Test: Composite nesting tests', () => {
let alice: IKeyringPair;
@@ -287,54 +287,7 @@
await collectionFT.mint(charlie, 5n);
await collectionFT.transfer(charlie, targetToken.nestingAccount(), 2n);
expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(7n);
- });
-
- // ---------- Re-Fungible ----------
-
- itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}});
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});
-
- await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});
- await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
-
- await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true});
- await collectionRFT.addToAllowList(alice, {Substrate: charlie.address});
- await collectionRFT.addToAllowList(alice, targetToken.nestingAccount());
-
- // Create an immediately nested token
- const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount());
- expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
-
- // Create a token to be nested and nest
- const newToken = await collectionRFT.mintToken(charlie, 5n);
- await newToken.transfer(charlie, targetToken.nestingAccount(), 2n);
- expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(2n);
});
-
- itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});
-
- await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionRFT.collectionId]}});
- await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});
- await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
-
- await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true});
- await collectionRFT.addToAllowList(alice, {Substrate: charlie.address});
- await collectionRFT.addToAllowList(alice, targetToken.nestingAccount());
-
- // Create an immediately nested token
- const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount());
- expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
-
- // Create a token to be nested and nest
- const newToken = await collectionRFT.mintToken(charlie, 5n);
- await newToken.transfer(charlie, targetToken.nestingAccount(), 2n);
- expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(2n);
- });
});
describe('Negative Test: Nesting', () => {
@@ -579,89 +532,5 @@
expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(0n);
expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(5n);
- });
-
- // ---------- Re-Fungible ----------
-
- itSub.ifWithPallets('ReFungible: disallows to nest token if nesting is disabled', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice);
-
- // Try to create an immediately nested token
- await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount()))
- .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
-
- // Try to create a token to be nested and nest
- const token = await collectionRFT.mintToken(alice, 5n);
- await expect(token.transfer(alice, targetToken.nestingAccount(), 2n))
- .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);
- });
-
- itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice);
-
- await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}});
- await collectionNFT.addToAllowList(alice, {Substrate: bob.address});
- await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
-
- // Try to create a token to be nested and nest
- const newToken = await collectionRFT.mintToken(alice);
- await expect(newToken.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.TokenValueTooLow/);
-
- expect(await targetToken.getChildren()).to.be.length(0);
- expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n);
-
- // Nest some tokens as Alice into Bob's token
- await newToken.transfer(alice, targetToken.nestingAccount());
-
- // Try to pull it out
- await expect(newToken.transferFrom(bob, targetToken.nestingAccount(), {Substrate: alice.address}, 1n))
- .to.be.rejectedWith(/common\.ApprovedValueTooLow/);
- expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
- });
-
- itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice);
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice);
-
- await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: [collectionRFT.collectionId]}});
- await collectionNFT.addToAllowList(alice, {Substrate: bob.address});
- await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
-
- // Try to create a token to be nested and nest
- const newToken = await collectionRFT.mintToken(alice);
- await expect(newToken.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.TokenValueTooLow/);
-
- expect(await targetToken.getChildren()).to.be.length(0);
- expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n);
-
- // Nest some tokens as Alice into Bob's token
- await newToken.transfer(alice, targetToken.nestingAccount());
-
- // Try to pull it out
- await expect(newToken.transferFrom(bob, targetToken.nestingAccount(), {Substrate: alice.address}, 1n))
- .to.be.rejectedWith(/common\.ApprovedValueTooLow/);
- expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
- });
-
- itSub.ifWithPallets('ReFungible: disallows to nest token to an unlisted collection', [Pallets.ReFungible], async ({helper}) => {
- const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true, restricted: []}}});
- const collectionRFT = await helper.rft.mintCollection(alice);
- const targetToken = await collectionNFT.mintToken(alice);
-
- // Try to create an immediately nested token
- await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount()))
- .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
-
- // Try to create a token to be nested and nest
- const token = await collectionRFT.mintToken(alice, 5n);
- await expect(token.transfer(alice, targetToken.nestingAccount(), 2n))
- .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);
});
});
tests/src/refungible.test.tsdiffbeforeafterboth--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -88,20 +88,6 @@
expect((await token.getTop10Owners()).length).to.be.equal(10);
});
- itSub('Transfer token pieces', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
-
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
- expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
-
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
- expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
-
- await expect(token.transfer(alice, {Substrate: bob.address}, 41n))
- .to.eventually.be.rejectedWith(/common\.TokenValueTooLow/);
- });
-
itSub('Create multiple tokens', async ({helper}) => {
const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
// TODO: fix mintMultipleTokens
@@ -118,56 +104,8 @@
const lastTokenId = await collection.getLastTokenId();
expect(lastTokenId).to.be.equal(3);
expect(await collection.getTokenBalance(lastTokenId, {Substrate: alice.address})).to.be.equal(100n);
- });
-
- itSub('Burn some pieces', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
- expect(await token.burn(alice, 99n)).to.be.true;
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n);
});
- itSub('Burn all pieces', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
-
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
-
- expect(await token.burn(alice, 100n)).to.be.true;
- expect(await collection.doesTokenExist(token.tokenId)).to.be.false;
- });
-
- itSub('Burn some pieces for multiple users', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
-
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
-
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
- expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
-
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
- expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
-
- expect(await token.burn(alice, 40n)).to.be.true;
-
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
-
- expect(await token.burn(bob, 59n)).to.be.true;
-
- expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n);
- expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
-
- expect(await token.burn(bob, 1n)).to.be.true;
-
- expect(await collection.doesTokenExist(token.tokenId)).to.be.false;
- });
-
itSub('Set allowance for token', async ({helper}) => {
const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
const token = await collection.mintToken(alice, 100n);
@@ -183,68 +121,6 @@
expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n);
});
- itSub('Repartition', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
-
- expect(await token.repartition(alice, 200n)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(200n);
- expect(await token.getTotalPieces()).to.be.equal(200n);
-
- expect(await token.transfer(alice, {Substrate: bob.address}, 110n)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(90n);
- expect(await token.getBalance({Substrate: bob.address})).to.be.equal(110n);
-
- await expect(token.repartition(alice, 80n))
- .to.eventually.be.rejectedWith(/refungible\.RepartitionWhileNotOwningAllPieces/);
-
- expect(await token.transfer(alice, {Substrate: bob.address}, 90n)).to.be.true;
- expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
- expect(await token.getBalance({Substrate: bob.address})).to.be.equal(200n);
-
- expect(await token.repartition(bob, 150n)).to.be.true;
- await expect(token.transfer(bob, {Substrate: alice.address}, 160n))
- .to.eventually.be.rejectedWith(/common\.TokenValueTooLow/);
- });
-
- itSub('Repartition with increased amount', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
- await token.repartition(alice, 200n);
- const chainEvents = helper.chainLog.slice(-1)[0].events;
- const event = chainEvents.find((event: any) => event.section === 'common' && event.method === 'ItemCreated');
- expect(event).to.deep.include({
- section: 'common',
- method: 'ItemCreated',
- index: [66, 2],
- data: [
- collection.collectionId,
- token.tokenId,
- {substrate: alice.address},
- 100n,
- ],
- });
- });
-
- itSub('Repartition with decreased amount', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const token = await collection.mintToken(alice, 100n);
- await token.repartition(alice, 50n);
- const chainEvents = helper.chainLog.slice(-1)[0].events;
- const event = chainEvents.find((event: any) => event.section === 'common' && event.method === 'ItemDestroyed');
- expect(event).to.deep.include({
- section: 'common',
- method: 'ItemDestroyed',
- index: [66, 3],
- data: [
- collection.collectionId,
- token.tokenId,
- {substrate: alice.address},
- 50n,
- ],
- });
- });
-
itSub('Create new collection with properties', async ({helper}) => {
const properties = [{key: 'key1', value: 'val1'}];
const tokenPropertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
@@ -252,46 +128,5 @@
const info = await collection.getData();
expect(info?.raw.properties).to.be.deep.equal(properties);
expect(info?.raw.tokenPropertyPermissions).to.be.deep.equal(tokenPropertyPermissions);
- });
-});
-
-describe('Refungible negative tests', () => {
- let donor: IKeyringPair;
- let alice: IKeyringPair;
- let bob: IKeyringPair;
- let charlie: IKeyringPair;
-
- before(async function() {
- await usingPlaygrounds(async (helper, privateKey) => {
- requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
-
- donor = await privateKey({filename: __filename});
- [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
- });
- });
-
- itSub('Cannot transfer incorrect amount of token pieces', async ({helper}) => {
- const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
- const tokenAlice = await collection.mintToken(alice, 10n, {Substrate: alice.address});
- const tokenBob = await collection.mintToken(alice, 10n, {Substrate: bob.address});
-
- // 1. Alice cannot transfer Bob's token:
- await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
- await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
- await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 10n)).to.be.rejectedWith('common.TokenValueTooLow');
- await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');
-
- // 2. Alice cannot transfer non-existing token:
- await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
- await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
-
- // 3. Zero transfer allowed (EIP-20):
- await tokenAlice.transfer(alice, {Substrate: charlie.address}, 0n);
-
- expect(await tokenAlice.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
- expect(await tokenBob.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
- expect(await tokenAlice.getBalance({Substrate: alice.address})).to.eq(10n);
- expect(await tokenBob.getBalance({Substrate: bob.address})).to.eq(10n);
- expect(await tokenBob.getBalance({Substrate: charlie.address})).to.eq(0n);
});
});
tests/src/sub/refungible/burn.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/sub/refungible/burn.test.ts
@@ -0,0 +1,127 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util';
+
+describe('Refungible: burn', () => {
+ let donor: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+
+ donor = await privateKey({filename: __filename});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
+ });
+ });
+
+ itSub('can burn some pieces', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+ await token.burn(alice, 99n);
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n);
+ });
+
+ itSub('can burn all pieces', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n);
+
+ await token.burn(alice, 100n);
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.false;
+ });
+
+ itSub('burn pieces for multiple users', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+
+ await token.transfer(alice, {Substrate: bob.address}, 60n);
+
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+
+ await token.burn(alice, 40n);
+
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+
+ await token.burn(bob, 59n);
+
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n);
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.true;
+
+ await token.burn(bob, 1n);
+
+ expect(await collection.doesTokenExist(token.tokenId)).to.be.false;
+ });
+
+ itSub('burn pieces by admin', async function({helper}) {
+ const collection = await helper.rft.mintCollection(alice);
+ await collection.setLimits(alice, {ownerCanTransfer: true});
+ await collection.addAdmin(alice, {Substrate: bob.address});
+ const token = await collection.mintToken(alice, 100n);
+
+ await token.burnFrom(bob, {Substrate: alice.address}, 100n);
+ expect(await token.doesExist()).to.be.false;
+ });
+});
+
+describe('Refungible: burn negative tests', () => {
+ let donor: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+ donor = await privateKey({filename: __filename});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor);
+ });
+ });
+
+ itSub('cannot burn non-owned token pieces', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice);
+ const aliceToken = await collection.mintToken(alice, 10n, {Substrate: alice.address});
+ const bobToken = await collection.mintToken(alice, 10n, {Substrate: bob.address});
+
+ // 1. Cannot burn non-owned token:
+ await expect(bobToken.burn(alice, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+ await expect(bobToken.burn(alice, 5n)).to.be.rejectedWith('common.TokenValueTooLow');
+ // 2. Cannot burn non-existing token:
+ await expect(helper.rft.burnToken(alice, 99999, 10)).to.be.rejectedWith('common.CollectionNotFound');
+ await expect(helper.rft.burnToken(alice, collection.collectionId, 99999)).to.be.rejectedWith('common.TokenValueTooLow');
+ // 3. Can burn zero amount of owned tokens (EIP-20)
+ await aliceToken.burn(alice, 0n);
+
+ // 4. Storage is not corrupted:
+ expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+ expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+
+ // 4.1 Tokens can be transfered:
+ await aliceToken.transfer(alice, {Substrate: bob.address}, 10n);
+ await bobToken.transfer(bob, {Substrate: alice.address}, 10n);
+ expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+ expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+ });
+});
tests/src/sub/refungible/nesting.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/sub/refungible/nesting.test.ts
@@ -0,0 +1,180 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../../util';
+
+describe('Refungible nesting', () => {
+ let alice: IKeyringPair;
+ let charlie: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+ const donor = await privateKey({filename: __filename});
+ [alice, charlie] = await helper.arrange.createAccounts([50n, 10n], donor);
+ });
+ });
+
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`Owner can nest their token${testCase.restrictedMode ? ': Restricted mode' : ''}`, async ({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}});
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address});
+
+ await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionRFT.collectionId] : null}});
+ await collectionNFT.addToAllowList(alice, {Substrate: charlie.address});
+ await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
+
+ await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true});
+ await collectionRFT.addToAllowList(alice, {Substrate: charlie.address});
+ await collectionRFT.addToAllowList(alice, targetToken.nestingAccount());
+
+ // Create an immediately nested token
+ const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount());
+ expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n);
+
+ // Create a token to be nested and nest
+ const newToken = await collectionRFT.mintToken(charlie, 5n);
+ await newToken.transfer(charlie, targetToken.nestingAccount(), 2n);
+ expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(2n);
+ expect(await newToken.getBalance({Substrate: charlie.address})).to.be.equal(3n);
+ });
+ });
+
+ itSub('Owner can unnest nested token', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await collection.mintToken(alice);
+
+ // Owner mints nested RFT token:
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const token = await collectionRFT.mintToken(alice, 10n, targetToken.nestingAccount());
+
+ // 1.1 Owner can partially unnest token pieces with transferFrom:
+ await token.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n);
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(9n);
+ expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
+
+ // 1.2 Owner can unnest all pieces:
+ await token.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 1n);
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(10n);
+ expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(0n);
+ });
+
+ itSub('Owner can burn nested token pieces', async ({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}});
+ const targetToken = await collection.mintToken(alice);
+
+ // Owner mints nested RFT token:
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const token = await collectionRFT.mintToken(alice, 100n, {Substrate: alice.address});
+ await token.transfer(alice, targetToken.nestingAccount(), 30n);
+
+ // 1.1 Owner can partially burnFrom nested pieces:
+ await token.burnFrom(alice, targetToken.nestingAccount(), 10n);
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(70n);
+ expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(20n);
+ expect(await targetToken.getChildren()).to.has.length(1);
+
+ // 1.1 Owner can burnFrom all nested pieces:
+ await token.burnFrom(alice, targetToken.nestingAccount(), 20n);
+ expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(0n);
+ expect(await targetToken.getChildren()).to.has.length(0);
+ expect(await token.doesExist()).to.be.true;
+
+ // 2. Target token does not contain any pieces and can be burnt:
+ await targetToken.burn(alice);
+ expect(await targetToken.doesExist()).to.be.false;
+ });
+});
+
+describe('Refungible nesting negative tests', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+ const donor = await privateKey({filename: __filename});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor);
+ });
+ });
+
+ itSub('cannot nest token if nesting is disabled', async ({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice);
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const targetToken = await collectionNFT.mintToken(alice);
+
+ // Try to create an immediately nested token
+ await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount()))
+ .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+
+ // Try to create a token to be nested and nest
+ const token = await collectionRFT.mintToken(alice, 5n);
+ await expect(token.transfer(alice, targetToken.nestingAccount(), 2n))
+ .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);
+ });
+
+ [
+ {restrictedMode: true},
+ {restrictedMode: false},
+ ].map(testCase => {
+ itSub(`non-Owner cannot nest someone else's token${testCase.restrictedMode ? ': Restricted mode' : ''}`, async ({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice);
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const targetToken = await collectionNFT.mintToken(alice);
+
+ await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: testCase.restrictedMode ? [collectionRFT.collectionId] : null}});
+ await collectionNFT.addToAllowList(alice, {Substrate: bob.address});
+ await collectionNFT.addToAllowList(alice, targetToken.nestingAccount());
+
+ // Try to create a token to be nested and nest
+ const newToken = await collectionRFT.mintToken(alice);
+ await expect(newToken.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.TokenValueTooLow/);
+
+ expect(await targetToken.getChildren()).to.be.length(0);
+ expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n);
+
+ // Nest some tokens as Alice into Bob's token
+ await newToken.transfer(alice, targetToken.nestingAccount());
+
+ // Try to pull it out
+ await expect(newToken.transferFrom(bob, targetToken.nestingAccount(), {Substrate: alice.address}, 1n))
+ .to.be.rejectedWith(/common\.ApprovedValueTooLow/);
+ expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(1n);
+ });
+ });
+
+ itSub('ReFungible: disallows to nest token to an unlisted collection', async ({helper}) => {
+ const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true, restricted: []}}});
+ const collectionRFT = await helper.rft.mintCollection(alice);
+ const targetToken = await collectionNFT.mintToken(alice);
+
+ // Try to create an immediately nested token
+ await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount()))
+ .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+
+ // Try to create a token to be nested and nest
+ const token = await collectionRFT.mintToken(alice, 5n);
+ await expect(token.transfer(alice, targetToken.nestingAccount(), 2n))
+ .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n);
+ });
+});
tests/src/sub/refungible/repartition.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/sub/refungible/repartition.test.ts
@@ -0,0 +1,96 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util';
+
+describe('integration test: Refungible functionality:', () => {
+ let donor: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+
+ donor = await privateKey({filename: __filename});
+ [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
+ });
+ });
+
+ itSub('Repartition', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+
+ expect(await token.repartition(alice, 200n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(200n);
+ expect(await token.getTotalPieces()).to.be.equal(200n);
+
+ expect(await token.transfer(alice, {Substrate: bob.address}, 110n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(90n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(110n);
+
+ await expect(token.repartition(alice, 80n))
+ .to.eventually.be.rejectedWith(/refungible\.RepartitionWhileNotOwningAllPieces/);
+
+ expect(await token.transfer(alice, {Substrate: bob.address}, 90n)).to.be.true;
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(200n);
+
+ expect(await token.repartition(bob, 150n)).to.be.true;
+ await expect(token.transfer(bob, {Substrate: alice.address}, 160n))
+ .to.eventually.be.rejectedWith(/common\.TokenValueTooLow/);
+ });
+
+ itSub('Repartition with increased amount', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+ await token.repartition(alice, 200n);
+ const chainEvents = helper.chainLog.slice(-1)[0].events;
+ const event = chainEvents.find((event: any) => event.section === 'common' && event.method === 'ItemCreated');
+ expect(event).to.deep.include({
+ section: 'common',
+ method: 'ItemCreated',
+ index: [66, 2],
+ data: [
+ collection.collectionId,
+ token.tokenId,
+ {substrate: alice.address},
+ 100n,
+ ],
+ });
+ });
+
+ itSub('Repartition with decreased amount', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+ await token.repartition(alice, 50n);
+ const chainEvents = helper.chainLog.slice(-1)[0].events;
+ const event = chainEvents.find((event: any) => event.section === 'common' && event.method === 'ItemDestroyed');
+ expect(event).to.deep.include({
+ section: 'common',
+ method: 'ItemDestroyed',
+ index: [66, 3],
+ data: [
+ collection.collectionId,
+ token.tokenId,
+ {substrate: alice.address},
+ 50n,
+ ],
+ });
+ });
+});
+
tests/src/sub/refungible/transfer.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/sub/refungible/transfer.test.ts
@@ -0,0 +1,73 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../../util';
+
+describe('Refungible transfer tests', () => {
+ let donor: IKeyringPair;
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+ let charlie: IKeyringPair;
+
+ before(async function() {
+ await usingPlaygrounds(async (helper, privateKey) => {
+ requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+
+ donor = await privateKey({filename: __filename});
+ [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+ });
+ });
+
+ itSub('Can transfer token pieces', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const token = await collection.mintToken(alice, 100n);
+
+ expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true;
+ // 1. Can transfer less or equal than have:
+ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n);
+ expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n);
+ });
+
+ itSub('Cannot transfer incorrect amount of token pieces', async ({helper}) => {
+ const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+ const tokenAlice = await collection.mintToken(alice, 10n, {Substrate: alice.address});
+ const tokenBob = await collection.mintToken(alice, 10n, {Substrate: bob.address});
+
+ // 1. Alice cannot transfer Bob's token:
+ await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+ await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
+ await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 10n)).to.be.rejectedWith('common.TokenValueTooLow');
+ await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');
+
+ // 2. Alice cannot transfer non-existing token:
+ await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+ await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
+
+ // 3. Cannot transfer more than have:
+ await expect(tokenAlice.transfer(alice, {Substrate: bob.address}, 11n))
+ .to.eventually.be.rejectedWith(/common\.TokenValueTooLow/);
+
+ // 4. Zero transfer allowed (EIP-20):
+ await tokenAlice.transfer(alice, {Substrate: charlie.address}, 0n);
+
+ expect(await tokenAlice.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+ expect(await tokenBob.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+ expect(await tokenAlice.getBalance({Substrate: alice.address})).to.eq(10n);
+ expect(await tokenBob.getBalance({Substrate: bob.address})).to.eq(10n);
+ expect(await tokenBob.getBalance({Substrate: charlie.address})).to.eq(0n);
+ });
+});
\ No newline at end of file
tests/src/util/index.tsdiffbeforeafterboth--- a/tests/src/util/index.ts
+++ b/tests/src/util/index.ts
@@ -100,6 +100,9 @@
export const LOCKING_PERIOD = 12n; // 12 blocks of relay
export const UNLOCKING_PERIOD = 6n; // 6 blocks of parachain
+// Native contracts
+export const COLLECTION_HELPER = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
+export const CONTRACT_HELPER = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';
export enum Pallets {
Inflation = 'inflation',