123456789101112131415161718import {readFile} from 'fs/promises';1920import {IKeyringPair} from '@polkadot/types/types';21import {evmToAddress} from '@polkadot/util-crypto';2223import {Contract} from 'web3-eth-contract';2425import {usingEthPlaygrounds, expect, itEth, EthUniqueHelper} from '../util';26import {CompiledContract} from '../util/playgrounds/types';27import {requirePalletsOrSkip, Pallets, makeNames} from '../../util';2829const {dirname} = makeNames(import.meta.url);3031let compiledFractionalizer: CompiledContract;3233const compileContract = async (helper: EthUniqueHelper): Promise<CompiledContract> => {34 if(!compiledFractionalizer) {35 compiledFractionalizer = await helper.ethContract.compile('Fractionalizer', (await readFile(`${dirname}/Fractionalizer.sol`)).toString(), [36 {solPath: 'api/CollectionHelpers.sol', fsPath: `${dirname}/../api/CollectionHelpers.sol`},37 {solPath: 'api/ContractHelpers.sol', fsPath: `${dirname}/../api/ContractHelpers.sol`},38 {solPath: 'api/UniqueRefungibleToken.sol', fsPath: `${dirname}/../api/UniqueRefungibleToken.sol`},39 {solPath: 'api/UniqueRefungible.sol', fsPath: `${dirname}/../api/UniqueRefungible.sol`},40 {solPath: 'api/UniqueNFT.sol', fsPath: `${dirname}/../api/UniqueNFT.sol`},41 ]);42 }43 return compiledFractionalizer;44};454647const deployContract = async (helper: EthUniqueHelper, owner: string): Promise<Contract> => {48 const compiled = await compileContract(helper);49 return await helper.ethContract.deployByAbi(owner, compiled.abi, compiled.object);50};515253const initContract = async (helper: EthUniqueHelper, owner: string): Promise<{contract: Contract, rftCollectionAddress: string}> => {54 const fractionalizer = await deployContract(helper, owner);55 const amount = 10n * helper.balance.getOneTokenNominal();56 const web3 = helper.getWeb3();57 await web3.eth.sendTransaction({from: owner, to: fractionalizer.options.address, value: `${amount}`, gas: helper.eth.DEFAULT_GAS});58 const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({value: Number(2n * helper.balance.getOneTokenNominal())});59 const rftCollectionAddress = result.events.RFTCollectionSet.returnValues._collection;60 return {contract: fractionalizer, rftCollectionAddress};61};6263const mintRFTToken = async (helper: EthUniqueHelper, owner: string, fractionalizer: Contract, amount: bigint): Promise<{64 nftCollectionAddress: string, nftTokenId: number, rftTokenAddress: string65}> => {66 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');67 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);68 const mintResult = await nftContract.methods.mint(owner).send({from: owner});69 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;7071 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});72 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send({from: owner});73 const result = await fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, amount).send({from: owner});74 const {_collection, _tokenId, _rftToken} = result.events.Fractionalized.returnValues;75 return {76 nftCollectionAddress: _collection,77 nftTokenId: _tokenId,78 rftTokenAddress: _rftToken,79 };80};818283describe('Fractionalizer contract usage', () => {84 let donor: IKeyringPair;8586 before(async function() {87 await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {88 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);89 donor = await privateKey({url: import.meta.url});90 });91 });9293 itEth('Set RFT collection', async ({helper}) => {94 const owner = await helper.eth.createAccountWithBalance(donor, 10n);95 const fractionalizer = await deployContract(helper, owner);96 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');97 const rftContract = await helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);9899 const fractionalizerAddressCross = helper.ethCrossAccount.fromAddress(fractionalizer.options.address);100 await rftContract.methods.addCollectionAdminCross(fractionalizerAddressCross).send({from: owner});101 const result = await fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).send({from: owner});102 expect(result.events).to.be.like({103 RFTCollectionSet: {104 returnValues: {105 _collection: rftCollection.collectionAddress,106 },107 },108 });109 });110111 itEth('Mint RFT collection', async ({helper}) => {112 const owner = await helper.eth.createAccountWithBalance(donor, 10n);113 const fractionalizer = await deployContract(helper, owner);114 await helper.balance.transferToSubstrate(donor, evmToAddress(fractionalizer.options.address), 10n * helper.balance.getOneTokenNominal());115116 const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner, value: Number(2n * helper.balance.getOneTokenNominal())});117 expect(result.events).to.be.like({118 RFTCollectionSet: {},119 });120 expect(result.events.RFTCollectionSet.returnValues._collection).to.be.ok;121 });122123 itEth('Set Allowlist', async ({helper}) => {124 const owner = await helper.eth.createAccountWithBalance(donor, 20n);125 const {contract: fractionalizer} = await initContract(helper, owner);126 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');127128 const result1 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});129 expect(result1.events).to.be.like({130 AllowListSet: {131 returnValues: {132 _collection: nftCollection.collectionAddress,133 _status: true,134 },135 },136 });137 const result2 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, false).send({from: owner});138 expect(result2.events).to.be.like({139 AllowListSet: {140 returnValues: {141 _collection: nftCollection.collectionAddress,142 _status: false,143 },144 },145 });146 });147148 itEth('NFT to RFT', async ({helper}) => {149 const owner = await helper.eth.createAccountWithBalance(donor, 20n);150151 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');152 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);153 const mintResult = await nftContract.methods.mint(owner).send({from: owner});154 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;155156 const {contract: fractionalizer} = await initContract(helper, owner);157158 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});159 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send({from: owner});160 const result = await fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100).send({from: owner});161 expect(result.events).to.be.like({162 Fractionalized: {163 returnValues: {164 _collection: nftCollection.collectionAddress,165 _tokenId: nftTokenId,166 _amount: '100',167 },168 },169 });170 const rftTokenAddress = result.events.Fractionalized.returnValues._rftToken;171172 const rftTokenContract = await helper.ethNativeContract.rftToken(rftTokenAddress);173 expect(await rftTokenContract.methods.balanceOf(owner).call()).to.equal('100');174 });175176 itEth('RFT to NFT', async ({helper}) => {177 const owner = await helper.eth.createAccountWithBalance(donor, 30n);178179 const {contract: fractionalizer, rftCollectionAddress} = await initContract(helper, owner);180 const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await mintRFTToken(helper, owner, fractionalizer, 100n);181182 const {collectionId, tokenId} = helper.ethAddress.extractTokenId(rftTokenAddress);183 const refungibleAddress = helper.ethAddress.fromCollectionId(collectionId);184 expect(rftCollectionAddress).to.be.equal(refungibleAddress);185 const refungibleTokenContract = await helper.ethNativeContract.rftToken(rftTokenAddress, owner);186 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send({from: owner});187 const result = await fractionalizer.methods.rft2nft(refungibleAddress, tokenId).send({from: owner});188 expect(result.events).to.be.like({189 Defractionalized: {190 returnValues: {191 _rftToken: rftTokenAddress,192 _nftCollection: nftCollectionAddress,193 _nftTokenId: nftTokenId,194 },195 },196 });197 });198199 itEth('Test fractionalizer NFT <-> RFT mapping ', async ({helper}) => {200 const owner = await helper.eth.createAccountWithBalance(donor, 200n);201202 const {contract: fractionalizer, rftCollectionAddress} = await initContract(helper, owner);203 const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await mintRFTToken(helper, owner, fractionalizer, 100n);204205 const {collectionId, tokenId} = helper.ethAddress.extractTokenId(rftTokenAddress);206 const refungibleAddress = helper.ethAddress.fromCollectionId(collectionId);207 expect(rftCollectionAddress).to.be.equal(refungibleAddress);208 const refungibleTokenContract = await helper.ethNativeContract.rftToken(rftTokenAddress, owner);209 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send({from: owner});210211 const rft2nft = await fractionalizer.methods.rft2nftMapping(rftTokenAddress).call();212 expect(rft2nft).to.be.like({213 _collection: nftCollectionAddress,214 _tokenId: nftTokenId,215 });216217 const nft2rft = await fractionalizer.methods.nft2rftMapping(nftCollectionAddress, nftTokenId).call();218 expect(nft2rft).to.be.eq(tokenId.toString());219 });220});221222223224describe('Negative Integration Tests for fractionalizer', () => {225 let donor: IKeyringPair;226227 before(async function() {228 await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => {229 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);230 donor = await privateKey({url: import.meta.url});231 });232 });233234 itEth('call setRFTCollection twice', async ({helper}) => {235 const owner = await helper.eth.createAccountWithBalance(donor, 20n);236 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');237 const refungibleContract = await helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);238239 const fractionalizer = await deployContract(helper, owner);240 const fractionalizerAddressCross = helper.ethCrossAccount.fromAddress(fractionalizer.options.address);241 await refungibleContract.methods.addCollectionAdminCross(fractionalizerAddressCross).send({from: owner});242 await fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).send({from: owner});243244 await expect(fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).call())245 .to.be.rejectedWith(/RFT collection is already set$/g);246 });247248 itEth('call setRFTCollection with NFT collection', async ({helper}) => {249 const owner = await helper.eth.createAccountWithBalance(donor, 20n);250 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');251 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);252253 const fractionalizer = await deployContract(helper, owner);254 const fractionalizerAddressCross = helper.ethCrossAccount.fromAddress(fractionalizer.options.address);255 await nftContract.methods.addCollectionAdminCross(fractionalizerAddressCross).send({from: owner});256257 await expect(fractionalizer.methods.setRFTCollection(nftCollection.collectionAddress).call())258 .to.be.rejectedWith(/Wrong collection type. Collection is not refungible.$/g);259 });260261 itEth('call setRFTCollection while not collection admin', async ({helper}) => {262 const owner = await helper.eth.createAccountWithBalance(donor, 20n);263 const fractionalizer = await deployContract(helper, owner);264 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');265266 await expect(fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).call())267 .to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);268 });269270 itEth('call setRFTCollection after createAndSetRFTCollection', async ({helper}) => {271 const owner = await helper.eth.createAccountWithBalance(donor, 20n);272 const fractionalizer = await deployContract(helper, owner);273 await helper.balance.transferToSubstrate(donor, evmToAddress(fractionalizer.options.address), 10n * helper.balance.getOneTokenNominal());274275 const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner, value: Number(2n * helper.balance.getOneTokenNominal())});276 const collectionIdAddress = result.events.RFTCollectionSet.returnValues._collection;277278 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())279 .to.be.rejectedWith(/RFT collection is already set$/g);280 });281282 itEth('call nft2rft without setting RFT collection for contract', async ({helper}) => {283 const owner = await helper.eth.createAccountWithBalance(donor, 20n);284285 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');286 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);287 const mintResult = await nftContract.methods.mint(owner).send({from: owner});288 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;289290 const fractionalizer = await deployContract(helper, owner);291292 await expect(fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100).call())293 .to.be.rejectedWith(/RFT collection is not set$/g);294 });295296 itEth('call nft2rft while not owner of NFT token', async ({helper}) => {297 const owner = await helper.eth.createAccountWithBalance(donor, 20n);298 const nftOwner = await helper.eth.createAccountWithBalance(donor, 10n);299300 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');301 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);302 const mintResult = await nftContract.methods.mint(owner).send({from: owner});303 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;304 await nftContract.methods.transfer(nftOwner, 1).send({from: owner});305306307 const {contract: fractionalizer} = await initContract(helper, owner);308 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});309310 await expect(fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100).call({from: owner}))311 .to.be.rejectedWith(/Only token owner could fractionalize it$/g);312 });313314 itEth('call nft2rft while not in list of allowed accounts', async ({helper}) => {315 const owner = await helper.eth.createAccountWithBalance(donor, 20n);316317 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');318 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);319 const mintResult = await nftContract.methods.mint(owner).send({from: owner});320 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;321322 const {contract: fractionalizer} = await initContract(helper, owner);323324 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send({from: owner});325 await expect(fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100).call())326 .to.be.rejectedWith(/Fractionalization of this collection is not allowed by admin$/g);327 });328329 itEth('call nft2rft while fractionalizer doesnt have approval for nft token', async ({helper}) => {330 const owner = await helper.eth.createAccountWithBalance(donor, 20n);331332 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');333 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);334 const mintResult = await nftContract.methods.mint(owner).send({from: owner});335 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;336337 const {contract: fractionalizer} = await initContract(helper, owner);338339 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});340 await expect(fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100).call())341 .to.be.rejectedWith(/ApprovedValueTooLow$/g);342 });343344 itEth('call rft2nft without setting RFT collection for contract', async ({helper}) => {345 const owner = await helper.eth.createAccountWithBalance(donor, 20n);346347 const fractionalizer = await deployContract(helper, owner);348 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');349 const refungibleContract = await helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);350 const mintResult = await refungibleContract.methods.mint(owner).send({from: owner});351 const rftTokenId = mintResult.events.Transfer.returnValues.tokenId;352353 await expect(fractionalizer.methods.rft2nft(rftCollection.collectionAddress, rftTokenId).call({from: owner}))354 .to.be.rejectedWith(/RFT collection is not set$/g);355 });356357 itEth('call rft2nft for RFT token that is not from configured RFT collection', async ({helper}) => {358 const owner = await helper.eth.createAccountWithBalance(donor, 20n);359360 const {contract: fractionalizer} = await initContract(helper, owner);361 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');362 const refungibleContract = await helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);363 const mintResult = await refungibleContract.methods.mint(owner).send({from: owner});364 const rftTokenId = mintResult.events.Transfer.returnValues.tokenId;365366 await expect(fractionalizer.methods.rft2nft(rftCollection.collectionAddress, rftTokenId).call())367 .to.be.rejectedWith(/Wrong RFT collection$/g);368 });369370 itEth('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({helper}) => {371 const owner = await helper.eth.createAccountWithBalance(donor, 20n);372 const rftCollection = await helper.eth.createRFTCollection(owner, 'rft', 'RFT collection', 'RFT');373 const refungibleContract = await helper.ethNativeContract.collection(rftCollection.collectionAddress, 'rft', owner);374375 const fractionalizer = await deployContract(helper, owner);376377 const fractionalizerAddressCross = helper.ethCrossAccount.fromAddress(fractionalizer.options.address);378 await refungibleContract.methods.addCollectionAdminCross(fractionalizerAddressCross).send({from: owner});379 await fractionalizer.methods.setRFTCollection(rftCollection.collectionAddress).send({from: owner});380381 const mintResult = await refungibleContract.methods.mint(owner).send({from: owner});382 const rftTokenId = mintResult.events.Transfer.returnValues.tokenId;383384 await expect(fractionalizer.methods.rft2nft(rftCollection.collectionAddress, rftTokenId).call())385 .to.be.rejectedWith(/No corresponding NFT token found$/g);386 });387388 itEth('call rft2nft without owning all RFT pieces', async ({helper}) => {389 const owner = await helper.eth.createAccountWithBalance(donor, 200n);390 const receiver = await helper.eth.createAccountWithBalance(donor, 10n);391392 const {contract: fractionalizer, rftCollectionAddress} = await initContract(helper, owner);393 const {rftTokenAddress} = await mintRFTToken(helper, owner, fractionalizer, 100n);394395 const {tokenId} = helper.ethAddress.extractTokenId(rftTokenAddress);396 const refungibleTokenContract = await helper.ethNativeContract.rftToken(rftTokenAddress, owner);397 await refungibleTokenContract.methods.transfer(receiver, 50).send({from: owner});398 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 50).send({from: receiver});399 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, tokenId).call({from: receiver}))400 .to.be.rejectedWith(/Not all pieces are owned by the caller$/g);401 });402403 itEth('send QTZ/UNQ to contract from non owner', async ({helper}) => {404 const owner = await helper.eth.createAccountWithBalance(donor, 20n);405 const payer = await helper.eth.createAccountWithBalance(donor, 10n);406407 const fractionalizer = await deployContract(helper, owner);408 const amount = 10n * helper.balance.getOneTokenNominal();409 const web3 = helper.getWeb3();410 await expect(web3.eth.sendTransaction({from: payer, to: fractionalizer.options.address, value: `${amount}`, gas: helper.eth.DEFAULT_GAS})).to.be.rejected;411 });412413 itEth('fractionalize NFT with NFT transfers disallowed', async ({helper}) => {414 const nftCollection = await helper.nft.mintCollection(donor, {name: 'A', description: 'B', tokenPrefix: 'C'});415416 const owner = await helper.eth.createAccountWithBalance(donor, 20n);417 const nftToken = await nftCollection.mintToken(donor, {Ethereum: owner});418 await helper.executeExtrinsic(donor, 'api.tx.unique.setTransfersEnabledFlag', [nftCollection.collectionId, false], true);419 const nftCollectionAddress = helper.ethAddress.fromCollectionId(nftCollection.collectionId);420 const {contract: fractionalizer} = await initContract(helper, owner);421 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send({from: owner});422423 const nftContract = await helper.ethNativeContract.collection(nftCollectionAddress, 'nft', owner);424 await nftContract.methods.approve(fractionalizer.options.address, nftToken.tokenId).send({from: owner});425 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftToken.tokenId, 100).call())426 .to.be.rejectedWith(/TransferNotAllowed$/g);427 });428429 itEth('fractionalize NFT with RFT transfers disallowed', async ({helper}) => {430 const owner = await helper.eth.createAccountWithBalance(donor, 20n);431432 const rftCollection = await helper.rft.mintCollection(donor, {name: 'A', description: 'B', tokenPrefix: 'C'});433 const rftCollectionAddress = helper.ethAddress.fromCollectionId(rftCollection.collectionId);434 const fractionalizer = await deployContract(helper, owner);435 await rftCollection.addAdmin(donor, {Ethereum: fractionalizer.options.address});436437 await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send({from: owner});438 await helper.executeExtrinsic(donor, 'api.tx.unique.setTransfersEnabledFlag', [rftCollection.collectionId, false], true);439440 const nftCollection = await helper.eth.createNFTCollection(owner, 'nft', 'NFT collection', 'NFT');441 const nftContract = await helper.ethNativeContract.collection(nftCollection.collectionAddress, 'nft', owner);442 const mintResult = await nftContract.methods.mint(owner).send({from: owner});443 const nftTokenId = mintResult.events.Transfer.returnValues.tokenId;444445 await fractionalizer.methods.setNftCollectionIsAllowed(nftCollection.collectionAddress, true).send({from: owner});446 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send({from: owner});447448 await expect(fractionalizer.methods.nft2rft(nftCollection.collectionAddress, nftTokenId, 100n).call())449 .to.be.rejectedWith(/TransferNotAllowed$/g);450 });451});