git.delta.rocks / unique-network / refs/commits / 118b8d5eaba9

difftreelog

feature: add `createERC721MetadataCompatibleRFTCollection` method

Trubnikov Sergey2022-07-25parent: #e516992.patch.diff
in: master

4 files changed

modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -237,6 +237,33 @@
 		Ok(address)
 	}
 
+	#[weight(<SelfWeightOf<T>>::create_collection())]
+	#[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]
+	fn create_refungible_collection_with_properties(
+		&mut self,
+		caller: caller,
+		name: string,
+		description: string,
+		token_prefix: string,
+		base_uri: string,
+	) -> Result<address> {
+		let (caller, name, description, token_prefix, base_uri_value) =
+			convert_data::<T>(caller, name, description, token_prefix, base_uri)?;
+		let data = make_data::<T>(
+			name,
+			CollectionMode::NFT,
+			description,
+			token_prefix,
+			base_uri_value,
+			true,
+		)?;
+		let collection_id = <pallet_refungible::Pallet<T>>::init_collection(caller.clone(), data)
+			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+
+		let address = pallet_common::eth::collection_id_to_address(collection_id);
+		Ok(address)
+	}
+
 	/// Check if a collection exists
 	/// @param collection_address Address of the collection in question
 	/// @return bool Does the collection exist?
addedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+	function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+// Selector: 7d9262e6
+interface Collection is Dummy, ERC165 {
+	// Set collection property.
+	//
+	// @param key Property key.
+	// @param value Propery value.
+	//
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		external;
+
+	// Delete collection property.
+	//
+	// @param key Property key.
+	//
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) external;
+
+	// Get collection property.
+	//
+	// @dev Throws error if key not found.
+	//
+	// @param key Property key.
+	// @return bytes The property corresponding to the key.
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		external
+		view
+		returns (bytes memory);
+
+	// Set the sponsor of the collection.
+	//
+	// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+	//
+	// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
+	//
+	// Selector: setCollectionSponsor(address) 7623402e
+	function setCollectionSponsor(address sponsor) external;
+
+	// Collection sponsorship confirmation.
+	//
+	// @dev After setting the sponsor for the collection, it must be confirmed with this function.
+	//
+	// Selector: confirmCollectionSponsorship() 3c50e97a
+	function confirmCollectionSponsorship() external;
+
+	// Set limits for the collection.
+	// @dev Throws error if limit not found.
+	// @param limit Name of the limit. Valid names:
+	// 	"accountTokenOwnershipLimit",
+	// 	"sponsoredDataSize",
+	// 	"sponsoredDataRateLimit",
+	// 	"tokenLimit",
+	// 	"sponsorTransferTimeout",
+	// 	"sponsorApproveTimeout"
+	// @param value Value of the limit.
+	//
+	// Selector: setCollectionLimit(string,uint32) 6a3841db
+	function setCollectionLimit(string memory limit, uint32 value) external;
+
+	// Set limits for the collection.
+	// @dev Throws error if limit not found.
+	// @param limit Name of the limit. Valid names:
+	// 	"ownerCanTransfer",
+	// 	"ownerCanDestroy",
+	// 	"transfersEnabled"
+	// @param value Value of the limit.
+	//
+	// Selector: setCollectionLimit(string,bool) 993b7fba
+	function setCollectionLimit(string memory limit, bool value) external;
+
+	// Get contract address.
+	//
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+
+	// Add collection admin by substrate address.
+	// @param new_admin Substrate administrator address.
+	//
+	// Selector: addCollectionAdminSubstrate(uint256) 5730062b
+	function addCollectionAdminSubstrate(uint256 newAdmin) external;
+
+	// Remove collection admin by substrate address.
+	// @param admin Substrate administrator address.
+	//
+	// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+	function removeCollectionAdminSubstrate(uint256 admin) external;
+
+	// Add collection admin.
+	// @param new_admin Address of the added administrator.
+	//
+	// Selector: addCollectionAdmin(address) 92e462c7
+	function addCollectionAdmin(address newAdmin) external;
+
+	// Remove collection admin.
+	//
+	// @param new_admin Address of the removed administrator.
+	//
+	// Selector: removeCollectionAdmin(address) fafd7b42
+	function removeCollectionAdmin(address admin) external;
+
+	// Toggle accessibility of collection nesting.
+	//
+	// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
+	//
+	// Selector: setCollectionNesting(bool) 112d4586
+	function setCollectionNesting(bool enable) external;
+
+	// Toggle accessibility of collection nesting.
+	//
+	// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+	// @param collections Addresses of collections that will be available for nesting.
+	//
+	// Selector: setCollectionNesting(bool,address[]) 64872396
+	function setCollectionNesting(bool enable, address[] memory collections)
+		external;
+
+	// Set the collection access method.
+	// @param mode Access mode
+	// 	0 for Normal
+	// 	1 for AllowList
+	//
+	// Selector: setCollectionAccess(uint8) 41835d4c
+	function setCollectionAccess(uint8 mode) external;
+
+	// Add the user to the allowed list.
+	//
+	// @param user Address of a trusted user.
+	//
+	// Selector: addToCollectionAllowList(address) 67844fe6
+	function addToCollectionAllowList(address user) external;
+
+	// Remove the user from the allowed list.
+	//
+	// @param user Address of a removed user.
+	//
+	// Selector: removeFromCollectionAllowList(address) 85c51acb
+	function removeFromCollectionAllowList(address user) external;
+
+	// Switch permission for minting.
+	//
+	// @param mode Enable if "true".
+	//
+	// Selector: setCollectionMintMode(bool) 00018e84
+	function setCollectionMintMode(bool mode) external;
+}
+
+interface UniqueRefungible is Dummy, ERC165, Collection {}
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
before · tests/src/eth/nonFungible.test.ts
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 {approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';19import nonFungibleAbi from './nonFungibleAbi.json';20import {expect} from 'chai';21import {submitTransactionAsync} from '../substrate/substrate-api';2223describe('NFT: Information getting', () => {24  itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {25    const collection = await createCollectionExpectSuccess({26      mode: {type: 'NFT'},27    });28    const alice = privateKeyWrapper('//Alice');29    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);3031    await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});3233    const address = collectionIdToAddress(collection);34    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});35    const totalSupply = await contract.methods.totalSupply().call();3637    expect(totalSupply).to.equal('1');38  });3940  itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {41    const collection = await createCollectionExpectSuccess({42      mode: {type: 'NFT'},43    });44    const alice = privateKeyWrapper('//Alice');4546    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);47    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum:caller});48    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});49    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});5051    const address = collectionIdToAddress(collection);52    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});53    const balance = await contract.methods.balanceOf(caller).call();5455    expect(balance).to.equal('3');56  });5758  itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => {59    const collection = await createCollectionExpectSuccess({60      mode: {type: 'NFT'},61    });62    const alice = privateKeyWrapper('//Alice');6364    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);65    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});6667    const address = collectionIdToAddress(collection);68    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});69    const owner = await contract.methods.ownerOf(tokenId).call();7071    expect(owner).to.equal(caller);72  });73});7475describe('Check ERC721 token URI', () => {76  itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {77    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);78    const helper = evmCollectionHelpers(web3, owner);79    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', '').send();80    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);81    const receiver = createEthAccount(web3);82    const contract = evmCollection(web3, owner, collectionIdAddress);83    84    const nextTokenId = await contract.methods.nextTokenId().call();85    expect(nextTokenId).to.be.equal('1');86    result = await contract.methods.mint(87      receiver,88      nextTokenId,89    ).send();9091    const events = normalizeEvents(result.events);92    const address = collectionIdToAddress(collectionId);9394    expect(events).to.be.deep.equal([95      {96        address,97        event: 'Transfer',98        args: {99          from: '0x0000000000000000000000000000000000000000',100          to: receiver,101          tokenId: nextTokenId,102        },103      },104    ]);105106    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');107  });108109  itWeb3('TokenURI from url', async ({web3, api, privateKeyWrapper}) => {110    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);111    const helper = evmCollectionHelpers(web3, owner);112    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();113    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);114    const receiver = createEthAccount(web3);115    const contract = evmCollection(web3, owner, collectionIdAddress);116    117    const nextTokenId = await contract.methods.nextTokenId().call();118    expect(nextTokenId).to.be.equal('1');119    result = await contract.methods.mint(120      receiver,121      nextTokenId,122    ).send();123    124    // Set URL125    await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send();126      127    const events = normalizeEvents(result.events);128    const address = collectionIdToAddress(collectionId);129130    expect(events).to.be.deep.equal([131      {132        address,133        event: 'Transfer',134        args: {135          from: '0x0000000000000000000000000000000000000000',136          to: receiver,137          tokenId: nextTokenId,138        },139      },140    ]);141142    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');143  });144145  itWeb3('TokenURI from baseURI + tokenId', async ({web3, api, privateKeyWrapper}) => {146    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);147    const helper = evmCollectionHelpers(web3, owner);148    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();149    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);150    const receiver = createEthAccount(web3);151    const contract = evmCollection(web3, owner, collectionIdAddress);152    153    const nextTokenId = await contract.methods.nextTokenId().call();154    expect(nextTokenId).to.be.equal('1');155    result = await contract.methods.mint(156      receiver,157      nextTokenId,158    ).send();159          160    const events = normalizeEvents(result.events);161    const address = collectionIdToAddress(collectionId);162163    expect(events).to.be.deep.equal([164      {165        address,166        event: 'Transfer',167        args: {168          from: '0x0000000000000000000000000000000000000000',169          to: receiver,170          tokenId: nextTokenId,171        },172      },173    ]);174175    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + nextTokenId);176  });177178  itWeb3('TokenURI from baseURI + suffix', async ({web3, api, privateKeyWrapper}) => {179    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);180    const helper = evmCollectionHelpers(web3, owner);181    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();182    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);183    const receiver = createEthAccount(web3);184    const contract = evmCollection(web3, owner, collectionIdAddress);185    186    const nextTokenId = await contract.methods.nextTokenId().call();187    expect(nextTokenId).to.be.equal('1');188    result = await contract.methods.mint(189      receiver,190      nextTokenId,191    ).send();192          193    // Set suffix194    const suffix = '/some/suffix';195    await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send();196197    const events = normalizeEvents(result.events);198    const address = collectionIdToAddress(collectionId);199200    expect(events).to.be.deep.equal([201      {202        address,203        event: 'Transfer',204        args: {205          from: '0x0000000000000000000000000000000000000000',206          to: receiver,207          tokenId: nextTokenId,208        },209      },210    ]);211212    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);213  });214});215216describe('NFT: Plain calls', () => {217  itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {218    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);219    const helper = evmCollectionHelpers(web3, owner);220    let result = await helper.methods.createNonfungibleCollection('Mint collection', '6', '6').send();221    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);222    const receiver = createEthAccount(web3);223    const contract = evmCollection(web3, owner, collectionIdAddress);224    const nextTokenId = await contract.methods.nextTokenId().call();225226    expect(nextTokenId).to.be.equal('1');227    result = await contract.methods.mintWithTokenURI(228      receiver,229      nextTokenId,230      'Test URI',231    ).send();232233    const events = normalizeEvents(result.events);234    const address = collectionIdToAddress(collectionId);235236    expect(events).to.be.deep.equal([237      {238        address,239        event: 'Transfer',240        args: {241          from: '0x0000000000000000000000000000000000000000',242          to: receiver,243          tokenId: nextTokenId,244        },245      },246    ]);247248    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');249250    // TODO: this wont work right now, need release 919000 first251    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();252    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();253    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);254  });255256  //TODO: CORE-302 add eth methods257  itWeb3.skip('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {258    const collection = await createCollectionExpectSuccess({259      mode: {type: 'NFT'},260    });261    const alice = privateKeyWrapper('//Alice');262263    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);264    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: caller});265    await submitTransactionAsync(alice, changeAdminTx);266    const receiver = createEthAccount(web3);267268    const address = collectionIdToAddress(collection);269    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});270271    {272      const nextTokenId = await contract.methods.nextTokenId().call();273      expect(nextTokenId).to.be.equal('1');274      const result = await contract.methods.mintBulkWithTokenURI(275        receiver,276        [277          [nextTokenId, 'Test URI 0'],278          [+nextTokenId + 1, 'Test URI 1'],279          [+nextTokenId + 2, 'Test URI 2'],280        ],281      ).send({from: caller});282      const events = normalizeEvents(result.events);283284      expect(events).to.be.deep.equal([285        {286          address,287          event: 'Transfer',288          args: {289            from: '0x0000000000000000000000000000000000000000',290            to: receiver,291            tokenId: nextTokenId,292          },293        },294        {295          address,296          event: 'Transfer',297          args: {298            from: '0x0000000000000000000000000000000000000000',299            to: receiver,300            tokenId: String(+nextTokenId + 1),301          },302        },303        {304          address,305          event: 'Transfer',306          args: {307            from: '0x0000000000000000000000000000000000000000',308            to: receiver,309            tokenId: String(+nextTokenId + 2),310          },311        },312      ]);313314      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');315      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');316      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');317    }318  });319320  itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {321    const collection = await createCollectionExpectSuccess({322      mode: {type: 'NFT'},323    });324    const alice = privateKeyWrapper('//Alice');325326    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);327328    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});329330    const address = collectionIdToAddress(collection);331    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});332333    {334      const result = await contract.methods.burn(tokenId).send({from: owner});335      const events = normalizeEvents(result.events);336337      expect(events).to.be.deep.equal([338        {339          address,340          event: 'Transfer',341          args: {342            from: owner,343            to: '0x0000000000000000000000000000000000000000',344            tokenId: tokenId.toString(),345          },346        },347      ]);348    }349  });350351  itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {352    const collection = await createCollectionExpectSuccess({353      mode: {type: 'NFT'},354    });355    const alice = privateKeyWrapper('//Alice');356357    const owner = createEthAccount(web3);358    await transferBalanceToEth(api, alice, owner);359360    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});361362    const spender = createEthAccount(web3);363364    const address = collectionIdToAddress(collection);365    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);366367    {368      const result = await contract.methods.approve(spender, tokenId).send({from: owner, ...GAS_ARGS});369      const events = normalizeEvents(result.events);370371      expect(events).to.be.deep.equal([372        {373          address,374          event: 'Approval',375          args: {376            owner,377            approved: spender,378            tokenId: tokenId.toString(),379          },380        },381      ]);382    }383  });384385  itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {386    const collection = await createCollectionExpectSuccess({387      mode: {type: 'NFT'},388    });389    const alice = privateKeyWrapper('//Alice');390391    const owner = createEthAccount(web3);392    await transferBalanceToEth(api, alice, owner);393394    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});395396    const spender = createEthAccount(web3);397    await transferBalanceToEth(api, alice, spender);398399    const receiver = createEthAccount(web3);400401    const address = collectionIdToAddress(collection);402    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});403404    await contract.methods.approve(spender, tokenId).send({from: owner});405406    {407      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});408      const events = normalizeEvents(result.events);409      expect(events).to.be.deep.equal([410        {411          address,412          event: 'Transfer',413          args: {414            from: owner,415            to: receiver,416            tokenId: tokenId.toString(),417          },418        },419      ]);420    }421422    {423      const balance = await contract.methods.balanceOf(receiver).call();424      expect(+balance).to.equal(1);425    }426427    {428      const balance = await contract.methods.balanceOf(owner).call();429      expect(+balance).to.equal(0);430    }431  });432433  itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {434    const collection = await createCollectionExpectSuccess({435      mode: {type: 'NFT'},436    });437    const alice = privateKeyWrapper('//Alice');438439    const owner = createEthAccount(web3);440    await transferBalanceToEth(api, alice, owner);441442    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});443444    const receiver = createEthAccount(web3);445    await transferBalanceToEth(api, alice, receiver);446447    const address = collectionIdToAddress(collection);448    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});449450    {451      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});452      const events = normalizeEvents(result.events);453      expect(events).to.be.deep.equal([454        {455          address,456          event: 'Transfer',457          args: {458            from: owner,459            to: receiver,460            tokenId: tokenId.toString(),461          },462        },463      ]);464    }465466    {467      const balance = await contract.methods.balanceOf(owner).call();468      expect(+balance).to.equal(0);469    }470471    {472      const balance = await contract.methods.balanceOf(receiver).call();473      expect(+balance).to.equal(1);474    }475  });476});477478describe('NFT: Fees', () => {479  itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {480    const collection = await createCollectionExpectSuccess({481      mode: {type: 'NFT'},482    });483    const alice = privateKeyWrapper('//Alice');484485    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);486    const spender = createEthAccount(web3);487488    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});489490    const address = collectionIdToAddress(collection);491    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});492493    const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));494    expect(cost < BigInt(0.2 * Number(UNIQUE)));495  });496497  itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {498    const collection = await createCollectionExpectSuccess({499      mode: {type: 'NFT'},500    });501    const alice = privateKeyWrapper('//Alice');502503    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);504    const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);505506    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});507508    const address = collectionIdToAddress(collection);509    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});510511    await contract.methods.approve(spender, tokenId).send({from: owner});512513    const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));514    expect(cost < BigInt(0.2 * Number(UNIQUE)));515  });516517  itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {518    const collection = await createCollectionExpectSuccess({519      mode: {type: 'NFT'},520    });521    const alice = privateKeyWrapper('//Alice');522523    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);524    const receiver = createEthAccount(web3);525526    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});527528    const address = collectionIdToAddress(collection);529    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});530531    const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));532    expect(cost < BigInt(0.2 * Number(UNIQUE)));533  });534});535536describe('NFT: Substrate calls', () => {537  itWeb3('Events emitted for mint()', async ({web3, privateKeyWrapper}) => {538    const collection = await createCollectionExpectSuccess({539      mode: {type: 'NFT'},540    });541    const alice = privateKeyWrapper('//Alice');542543    const address = collectionIdToAddress(collection);544    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);545546    let tokenId: number;547    const events = await recordEvents(contract, async () => {548      tokenId = await createItemExpectSuccess(alice, collection, 'NFT');549    });550551    expect(events).to.be.deep.equal([552      {553        address,554        event: 'Transfer',555        args: {556          from: '0x0000000000000000000000000000000000000000',557          to: subToEth(alice.address),558          tokenId: tokenId!.toString(),559        },560      },561    ]);562  });563564  itWeb3('Events emitted for burn()', async ({web3, privateKeyWrapper}) => {565    const collection = await createCollectionExpectSuccess({566      mode: {type: 'NFT'},567    });568    const alice = privateKeyWrapper('//Alice');569570    const address = collectionIdToAddress(collection);571    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);572573    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');574    const events = await recordEvents(contract, async () => {575      await burnItemExpectSuccess(alice, collection, tokenId);576    });577578    expect(events).to.be.deep.equal([579      {580        address,581        event: 'Transfer',582        args: {583          from: subToEth(alice.address),584          to: '0x0000000000000000000000000000000000000000',585          tokenId: tokenId.toString(),586        },587      },588    ]);589  });590591  itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => {592    const collection = await createCollectionExpectSuccess({593      mode: {type: 'NFT'},594    });595    const alice = privateKeyWrapper('//Alice');596597    const receiver = createEthAccount(web3);598599    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');600601    const address = collectionIdToAddress(collection);602    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);603604    const events = await recordEvents(contract, async () => {605      await approveExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1);606    });607608    expect(events).to.be.deep.equal([609      {610        address,611        event: 'Approval',612        args: {613          owner: subToEth(alice.address),614          approved: receiver,615          tokenId: tokenId.toString(),616        },617      },618    ]);619  });620621  itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => {622    const collection = await createCollectionExpectSuccess({623      mode: {type: 'NFT'},624    });625    const alice = privateKeyWrapper('//Alice');626    const bob = privateKeyWrapper('//Bob');627628    const receiver = createEthAccount(web3);629630    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');631    await approveExpectSuccess(collection, tokenId, alice, bob.address, 1);632633    const address = collectionIdToAddress(collection);634    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);635636    const events = await recordEvents(contract, async () => {637      await transferFromExpectSuccess(collection, tokenId, bob, alice, {Ethereum: receiver}, 1, 'NFT');638    });639640    expect(events).to.be.deep.equal([641      {642        address,643        event: 'Transfer',644        args: {645          from: subToEth(alice.address),646          to: receiver,647          tokenId: tokenId.toString(),648        },649      },650    ]);651  });652653  itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => {654    const collection = await createCollectionExpectSuccess({655      mode: {type: 'NFT'},656    });657    const alice = privateKeyWrapper('//Alice');658659    const receiver = createEthAccount(web3);660661    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');662663    const address = collectionIdToAddress(collection);664    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);665666    const events = await recordEvents(contract, async () => {667      await transferExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1, 'NFT');668    });669670    expect(events).to.be.deep.equal([671      {672        address,673        event: 'Transfer',674        args: {675          from: subToEth(alice.address),676          to: receiver,677          tokenId: tokenId.toString(),678        },679      },680    ]);681  });682});683684describe('Common metadata', () => {685  itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => {686    const collection = await createCollectionExpectSuccess({687      name: 'token name',688      mode: {type: 'NFT'},689    });690    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);691692    const address = collectionIdToAddress(collection);693    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});694    const name = await contract.methods.name().call();695696    expect(name).to.equal('token name');697  });698699  itWeb3('Returns symbol name', async ({api, web3, privateKeyWrapper}) => {700    const collection = await createCollectionExpectSuccess({701      tokenPrefix: 'TOK',702      mode: {type: 'NFT'},703    });704    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);705706    const address = collectionIdToAddress(collection);707    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});708    const symbol = await contract.methods.symbol().call();709710    expect(symbol).to.equal('TOK');711  });712});
after · tests/src/eth/nonFungible.test.ts
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 {approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';19import nonFungibleAbi from './nonFungibleAbi.json';20import {expect} from 'chai';21import {submitTransactionAsync} from '../substrate/substrate-api';2223describe('NFT: Information getting', () => {24  itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {25    const collection = await createCollectionExpectSuccess({26      mode: {type: 'NFT'},27    });28    const alice = privateKeyWrapper('//Alice');29    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);3031    await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address});3233    const address = collectionIdToAddress(collection);34    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});35    const totalSupply = await contract.methods.totalSupply().call();3637    expect(totalSupply).to.equal('1');38  });3940  itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {41    const collection = await createCollectionExpectSuccess({42      mode: {type: 'NFT'},43    });44    const alice = privateKeyWrapper('//Alice');4546    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);47    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum:caller});48    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});49    await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});5051    const address = collectionIdToAddress(collection);52    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});53    const balance = await contract.methods.balanceOf(caller).call();5455    expect(balance).to.equal('3');56  });5758  itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => {59    const collection = await createCollectionExpectSuccess({60      mode: {type: 'NFT'},61    });62    const alice = privateKeyWrapper('//Alice');6364    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);65    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller});6667    const address = collectionIdToAddress(collection);68    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});69    const owner = await contract.methods.ownerOf(tokenId).call();7071    expect(owner).to.equal(caller);72  });73});7475describe('Check ERC721 token URI for NFT', () => {76  itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {77    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);78    const helper = evmCollectionHelpers(web3, owner);79    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', '').send();80    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);81    const receiver = createEthAccount(web3);82    const contract = evmCollection(web3, owner, collectionIdAddress);83    84    const nextTokenId = await contract.methods.nextTokenId().call();85    expect(nextTokenId).to.be.equal('1');86    result = await contract.methods.mint(87      receiver,88      nextTokenId,89    ).send();9091    const events = normalizeEvents(result.events);92    const address = collectionIdToAddress(collectionId);9394    expect(events).to.be.deep.equal([95      {96        address,97        event: 'Transfer',98        args: {99          from: '0x0000000000000000000000000000000000000000',100          to: receiver,101          tokenId: nextTokenId,102        },103      },104    ]);105106    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');107  });108109  itWeb3('TokenURI from url', async ({web3, api, privateKeyWrapper}) => {110    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);111    const helper = evmCollectionHelpers(web3, owner);112    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();113    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);114    const receiver = createEthAccount(web3);115    const contract = evmCollection(web3, owner, collectionIdAddress);116    117    const nextTokenId = await contract.methods.nextTokenId().call();118    expect(nextTokenId).to.be.equal('1');119    result = await contract.methods.mint(120      receiver,121      nextTokenId,122    ).send();123    124    // Set URL125    await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send();126      127    const events = normalizeEvents(result.events);128    const address = collectionIdToAddress(collectionId);129130    expect(events).to.be.deep.equal([131      {132        address,133        event: 'Transfer',134        args: {135          from: '0x0000000000000000000000000000000000000000',136          to: receiver,137          tokenId: nextTokenId,138        },139      },140    ]);141142    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');143  });144145  itWeb3('TokenURI from baseURI + tokenId', async ({web3, api, privateKeyWrapper}) => {146    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);147    const helper = evmCollectionHelpers(web3, owner);148    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();149    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);150    const receiver = createEthAccount(web3);151    const contract = evmCollection(web3, owner, collectionIdAddress);152    153    const nextTokenId = await contract.methods.nextTokenId().call();154    expect(nextTokenId).to.be.equal('1');155    result = await contract.methods.mint(156      receiver,157      nextTokenId,158    ).send();159          160    const events = normalizeEvents(result.events);161    const address = collectionIdToAddress(collectionId);162163    expect(events).to.be.deep.equal([164      {165        address,166        event: 'Transfer',167        args: {168          from: '0x0000000000000000000000000000000000000000',169          to: receiver,170          tokenId: nextTokenId,171        },172      },173    ]);174175    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + nextTokenId);176  });177178  itWeb3('TokenURI from baseURI + suffix', async ({web3, api, privateKeyWrapper}) => {179    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);180    const helper = evmCollectionHelpers(web3, owner);181    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();182    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);183    const receiver = createEthAccount(web3);184    const contract = evmCollection(web3, owner, collectionIdAddress);185    186    const nextTokenId = await contract.methods.nextTokenId().call();187    expect(nextTokenId).to.be.equal('1');188    result = await contract.methods.mint(189      receiver,190      nextTokenId,191    ).send();192          193    // Set suffix194    const suffix = '/some/suffix';195    await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send();196197    const events = normalizeEvents(result.events);198    const address = collectionIdToAddress(collectionId);199200    expect(events).to.be.deep.equal([201      {202        address,203        event: 'Transfer',204        args: {205          from: '0x0000000000000000000000000000000000000000',206          to: receiver,207          tokenId: nextTokenId,208        },209      },210    ]);211212    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);213  });214});215216describe('NFT: Plain calls', () => {217  itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {218    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);219    const helper = evmCollectionHelpers(web3, owner);220    let result = await helper.methods.createNonfungibleCollection('Mint collection', '6', '6').send();221    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);222    const receiver = createEthAccount(web3);223    const contract = evmCollection(web3, owner, collectionIdAddress);224    const nextTokenId = await contract.methods.nextTokenId().call();225226    expect(nextTokenId).to.be.equal('1');227    result = await contract.methods.mintWithTokenURI(228      receiver,229      nextTokenId,230      'Test URI',231    ).send();232233    const events = normalizeEvents(result.events);234    const address = collectionIdToAddress(collectionId);235236    expect(events).to.be.deep.equal([237      {238        address,239        event: 'Transfer',240        args: {241          from: '0x0000000000000000000000000000000000000000',242          to: receiver,243          tokenId: nextTokenId,244        },245      },246    ]);247248    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');249250    // TODO: this wont work right now, need release 919000 first251    // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send();252    // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();253    // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);254  });255256  //TODO: CORE-302 add eth methods257  itWeb3.skip('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {258    const collection = await createCollectionExpectSuccess({259      mode: {type: 'NFT'},260    });261    const alice = privateKeyWrapper('//Alice');262263    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);264    const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: caller});265    await submitTransactionAsync(alice, changeAdminTx);266    const receiver = createEthAccount(web3);267268    const address = collectionIdToAddress(collection);269    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});270271    {272      const nextTokenId = await contract.methods.nextTokenId().call();273      expect(nextTokenId).to.be.equal('1');274      const result = await contract.methods.mintBulkWithTokenURI(275        receiver,276        [277          [nextTokenId, 'Test URI 0'],278          [+nextTokenId + 1, 'Test URI 1'],279          [+nextTokenId + 2, 'Test URI 2'],280        ],281      ).send({from: caller});282      const events = normalizeEvents(result.events);283284      expect(events).to.be.deep.equal([285        {286          address,287          event: 'Transfer',288          args: {289            from: '0x0000000000000000000000000000000000000000',290            to: receiver,291            tokenId: nextTokenId,292          },293        },294        {295          address,296          event: 'Transfer',297          args: {298            from: '0x0000000000000000000000000000000000000000',299            to: receiver,300            tokenId: String(+nextTokenId + 1),301          },302        },303        {304          address,305          event: 'Transfer',306          args: {307            from: '0x0000000000000000000000000000000000000000',308            to: receiver,309            tokenId: String(+nextTokenId + 2),310          },311        },312      ]);313314      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');315      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');316      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');317    }318  });319320  itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {321    const collection = await createCollectionExpectSuccess({322      mode: {type: 'NFT'},323    });324    const alice = privateKeyWrapper('//Alice');325326    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);327328    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});329330    const address = collectionIdToAddress(collection);331    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});332333    {334      const result = await contract.methods.burn(tokenId).send({from: owner});335      const events = normalizeEvents(result.events);336337      expect(events).to.be.deep.equal([338        {339          address,340          event: 'Transfer',341          args: {342            from: owner,343            to: '0x0000000000000000000000000000000000000000',344            tokenId: tokenId.toString(),345          },346        },347      ]);348    }349  });350351  itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {352    const collection = await createCollectionExpectSuccess({353      mode: {type: 'NFT'},354    });355    const alice = privateKeyWrapper('//Alice');356357    const owner = createEthAccount(web3);358    await transferBalanceToEth(api, alice, owner);359360    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});361362    const spender = createEthAccount(web3);363364    const address = collectionIdToAddress(collection);365    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);366367    {368      const result = await contract.methods.approve(spender, tokenId).send({from: owner, ...GAS_ARGS});369      const events = normalizeEvents(result.events);370371      expect(events).to.be.deep.equal([372        {373          address,374          event: 'Approval',375          args: {376            owner,377            approved: spender,378            tokenId: tokenId.toString(),379          },380        },381      ]);382    }383  });384385  itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {386    const collection = await createCollectionExpectSuccess({387      mode: {type: 'NFT'},388    });389    const alice = privateKeyWrapper('//Alice');390391    const owner = createEthAccount(web3);392    await transferBalanceToEth(api, alice, owner);393394    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});395396    const spender = createEthAccount(web3);397    await transferBalanceToEth(api, alice, spender);398399    const receiver = createEthAccount(web3);400401    const address = collectionIdToAddress(collection);402    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});403404    await contract.methods.approve(spender, tokenId).send({from: owner});405406    {407      const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender});408      const events = normalizeEvents(result.events);409      expect(events).to.be.deep.equal([410        {411          address,412          event: 'Transfer',413          args: {414            from: owner,415            to: receiver,416            tokenId: tokenId.toString(),417          },418        },419      ]);420    }421422    {423      const balance = await contract.methods.balanceOf(receiver).call();424      expect(+balance).to.equal(1);425    }426427    {428      const balance = await contract.methods.balanceOf(owner).call();429      expect(+balance).to.equal(0);430    }431  });432433  itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {434    const collection = await createCollectionExpectSuccess({435      mode: {type: 'NFT'},436    });437    const alice = privateKeyWrapper('//Alice');438439    const owner = createEthAccount(web3);440    await transferBalanceToEth(api, alice, owner);441442    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});443444    const receiver = createEthAccount(web3);445    await transferBalanceToEth(api, alice, receiver);446447    const address = collectionIdToAddress(collection);448    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});449450    {451      const result = await contract.methods.transfer(receiver, tokenId).send({from: owner});452      const events = normalizeEvents(result.events);453      expect(events).to.be.deep.equal([454        {455          address,456          event: 'Transfer',457          args: {458            from: owner,459            to: receiver,460            tokenId: tokenId.toString(),461          },462        },463      ]);464    }465466    {467      const balance = await contract.methods.balanceOf(owner).call();468      expect(+balance).to.equal(0);469    }470471    {472      const balance = await contract.methods.balanceOf(receiver).call();473      expect(+balance).to.equal(1);474    }475  });476});477478describe('NFT: Fees', () => {479  itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {480    const collection = await createCollectionExpectSuccess({481      mode: {type: 'NFT'},482    });483    const alice = privateKeyWrapper('//Alice');484485    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);486    const spender = createEthAccount(web3);487488    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});489490    const address = collectionIdToAddress(collection);491    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});492493    const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, tokenId).send({from: owner}));494    expect(cost < BigInt(0.2 * Number(UNIQUE)));495  });496497  itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {498    const collection = await createCollectionExpectSuccess({499      mode: {type: 'NFT'},500    });501    const alice = privateKeyWrapper('//Alice');502503    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);504    const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);505506    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});507508    const address = collectionIdToAddress(collection);509    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});510511    await contract.methods.approve(spender, tokenId).send({from: owner});512513    const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender}));514    expect(cost < BigInt(0.2 * Number(UNIQUE)));515  });516517  itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {518    const collection = await createCollectionExpectSuccess({519      mode: {type: 'NFT'},520    });521    const alice = privateKeyWrapper('//Alice');522523    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);524    const receiver = createEthAccount(web3);525526    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner});527528    const address = collectionIdToAddress(collection);529    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS});530531    const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner}));532    expect(cost < BigInt(0.2 * Number(UNIQUE)));533  });534});535536describe('NFT: Substrate calls', () => {537  itWeb3('Events emitted for mint()', async ({web3, privateKeyWrapper}) => {538    const collection = await createCollectionExpectSuccess({539      mode: {type: 'NFT'},540    });541    const alice = privateKeyWrapper('//Alice');542543    const address = collectionIdToAddress(collection);544    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);545546    let tokenId: number;547    const events = await recordEvents(contract, async () => {548      tokenId = await createItemExpectSuccess(alice, collection, 'NFT');549    });550551    expect(events).to.be.deep.equal([552      {553        address,554        event: 'Transfer',555        args: {556          from: '0x0000000000000000000000000000000000000000',557          to: subToEth(alice.address),558          tokenId: tokenId!.toString(),559        },560      },561    ]);562  });563564  itWeb3('Events emitted for burn()', async ({web3, privateKeyWrapper}) => {565    const collection = await createCollectionExpectSuccess({566      mode: {type: 'NFT'},567    });568    const alice = privateKeyWrapper('//Alice');569570    const address = collectionIdToAddress(collection);571    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);572573    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');574    const events = await recordEvents(contract, async () => {575      await burnItemExpectSuccess(alice, collection, tokenId);576    });577578    expect(events).to.be.deep.equal([579      {580        address,581        event: 'Transfer',582        args: {583          from: subToEth(alice.address),584          to: '0x0000000000000000000000000000000000000000',585          tokenId: tokenId.toString(),586        },587      },588    ]);589  });590591  itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => {592    const collection = await createCollectionExpectSuccess({593      mode: {type: 'NFT'},594    });595    const alice = privateKeyWrapper('//Alice');596597    const receiver = createEthAccount(web3);598599    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');600601    const address = collectionIdToAddress(collection);602    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);603604    const events = await recordEvents(contract, async () => {605      await approveExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1);606    });607608    expect(events).to.be.deep.equal([609      {610        address,611        event: 'Approval',612        args: {613          owner: subToEth(alice.address),614          approved: receiver,615          tokenId: tokenId.toString(),616        },617      },618    ]);619  });620621  itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => {622    const collection = await createCollectionExpectSuccess({623      mode: {type: 'NFT'},624    });625    const alice = privateKeyWrapper('//Alice');626    const bob = privateKeyWrapper('//Bob');627628    const receiver = createEthAccount(web3);629630    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');631    await approveExpectSuccess(collection, tokenId, alice, bob.address, 1);632633    const address = collectionIdToAddress(collection);634    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);635636    const events = await recordEvents(contract, async () => {637      await transferFromExpectSuccess(collection, tokenId, bob, alice, {Ethereum: receiver}, 1, 'NFT');638    });639640    expect(events).to.be.deep.equal([641      {642        address,643        event: 'Transfer',644        args: {645          from: subToEth(alice.address),646          to: receiver,647          tokenId: tokenId.toString(),648        },649      },650    ]);651  });652653  itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => {654    const collection = await createCollectionExpectSuccess({655      mode: {type: 'NFT'},656    });657    const alice = privateKeyWrapper('//Alice');658659    const receiver = createEthAccount(web3);660661    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');662663    const address = collectionIdToAddress(collection);664    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);665666    const events = await recordEvents(contract, async () => {667      await transferExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1, 'NFT');668    });669670    expect(events).to.be.deep.equal([671      {672        address,673        event: 'Transfer',674        args: {675          from: subToEth(alice.address),676          to: receiver,677          tokenId: tokenId.toString(),678        },679      },680    ]);681  });682});683684describe('Common metadata', () => {685  itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => {686    const collection = await createCollectionExpectSuccess({687      name: 'token name',688      mode: {type: 'NFT'},689    });690    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);691692    const address = collectionIdToAddress(collection);693    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});694    const name = await contract.methods.name().call();695696    expect(name).to.equal('token name');697  });698699  itWeb3('Returns symbol name', async ({api, web3, privateKeyWrapper}) => {700    const collection = await createCollectionExpectSuccess({701      tokenPrefix: 'TOK',702      mode: {type: 'NFT'},703    });704    const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);705706    const address = collectionIdToAddress(collection);707    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});708    const symbol = await contract.methods.symbol().call();709710    expect(symbol).to.equal('TOK');711  });712});
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE} from '../util/helpers';
-import {createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers';
+import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers';
 import reFungibleTokenAbi from './reFungibleTokenAbi.json';
 
 import chai from 'chai';
@@ -73,6 +73,148 @@
   });
 });
 
+// FIXME: Need erc721 for ReFubgible.
+describe.skip('Check ERC721 token URI for ReFungible', () => {
+  itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, owner);
+    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', '').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const receiver = createEthAccount(web3);
+    const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
+    
+    const nextTokenId = await contract.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await contract.methods.mint(
+      receiver,
+      nextTokenId,
+    ).send();
+
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+
+    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('');
+  });
+
+  itWeb3('TokenURI from url', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, owner);
+    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const receiver = createEthAccount(web3);
+    const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
+    
+    const nextTokenId = await contract.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await contract.methods.mint(
+      receiver,
+      nextTokenId,
+    ).send();
+    
+    // Set URL
+    await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send();
+      
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+
+    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI');
+  });
+
+  itWeb3('TokenURI from baseURI + tokenId', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, owner);
+    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const receiver = createEthAccount(web3);
+    const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
+    
+    const nextTokenId = await contract.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await contract.methods.mint(
+      receiver,
+      nextTokenId,
+    ).send();
+          
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+
+    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + nextTokenId);
+  });
+
+  itWeb3('TokenURI from baseURI + suffix', async ({web3, api, privateKeyWrapper}) => {
+    const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+    const helper = evmCollectionHelpers(web3, owner);
+    let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send();
+    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);
+    const receiver = createEthAccount(web3);
+    const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'});
+    
+    const nextTokenId = await contract.methods.nextTokenId().call();
+    expect(nextTokenId).to.be.equal('1');
+    result = await contract.methods.mint(
+      receiver,
+      nextTokenId,
+    ).send();
+          
+    // Set suffix
+    const suffix = '/some/suffix';
+    await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send();
+
+    const events = normalizeEvents(result.events);
+    const address = collectionIdToAddress(collectionId);
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          tokenId: nextTokenId,
+        },
+      },
+    ]);
+
+    expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix);
+  });
+});
+
 describe('Refungible: Plain calls', () => {
   itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {
     const alice = privateKeyWrapper('//Alice');