123456789101112131415161718import Web3 from 'web3';19import {ApiPromise} from '@polkadot/api';20import {evmToAddress} from '@polkadot/util-crypto';21import {readFile} from 'fs/promises';22import fractionalizerAbi from './FractionalizerAbi.json';23import {submitTransactionAsync} from '../../substrate/substrate-api';24import {UNIQUE} from '../../util/helpers';25import {collectionIdToAddress, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers';26import {Contract} from 'web3-eth-contract';2728import chai from 'chai';29import chaiAsPromised from 'chai-as-promised';30import chaiLike from 'chai-like';31import {IKeyringPair} from '@polkadot/types/types';32chai.use(chaiAsPromised);33chai.use(chaiLike);34const expect = chai.expect;3536async function deployFractionalizer(api: ApiPromise, web3: Web3, owner: string) {37 const fractionalizerContract = new web3.eth.Contract(fractionalizerAbi as any, undefined, {38 from: owner,39 ...GAS_ARGS,40 });41 return await fractionalizerContract.deploy({data: (await readFile(`${__dirname}/Fractionalizer.bin`)).toString()}).send({from: owner});42}4344async function initFractionalizer(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair, owner: string) {45 const fractionalizer = await deployFractionalizer(api, web3, owner);46 const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);47 const alice = privateKeyWrapper('//Alice');48 await submitTransactionAsync(alice, tx);49 const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send();50 const rftCollectionAddress = result.events.RFTCollectionSet.returnValues._collection;51 return {fractionalizer, rftCollectionAddress};52}5354async function createRFTToken(api: ApiPromise, web3: Web3, owner: string, fractionalizer: Contract, amount: bigint) {55 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);56 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);57 const nftTokenId = await nftContract.methods.nextTokenId().call();58 await nftContract.methods.mint(owner, nftTokenId).send();5960 await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send();61 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();62 const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, amount).send();63 const {_collection, _tokenId, _rftToken} = result.events.Fractionalized.returnValues;64 return {65 nftCollectionAddress: _collection,66 nftTokenId: _tokenId,67 rftTokenAddress: _rftToken,68 };69}7071describe('Fractionalizer contract usage', () => {72 itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => {73 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);74 const fractionalizer = await deployFractionalizer(api, web3, owner);75 const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);76 const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);77 await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();78 const result = await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();79 expect(result.events).to.be.like({80 RFTCollectionSet: {81 returnValues: {82 _collection: collectionIdAddress,83 },84 },85 });86 });8788 itWeb3('Mint RFT collection', async ({api, web3, privateKeyWrapper}) => {89 const alice = privateKeyWrapper('//Alice');90 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);91 const fractionalizer = await deployFractionalizer(api, web3, owner);92 const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);93 await submitTransactionAsync(alice, tx);9495 const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send({from: owner});96 expect(result.events).to.be.like({97 RFTCollectionSet: {},98 });99 expect(result.events.RFTCollectionSet.returnValues._collection).to.be.ok;100 });101102 itWeb3('Set Allowlist', async ({api, web3, privateKeyWrapper}) => {103 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);104 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); 105106 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);107 const result1 = await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send({from: owner});108 expect(result1.events).to.be.like({109 AllowListSet: {110 returnValues: {111 _collection: nftCollectionAddress,112 _status: true,113 },114 },115 });116 const result2 = await fractionalizer.methods.setAllowlist(nftCollectionAddress, false).send({from: owner});117 expect(result2.events).to.be.like({118 AllowListSet: {119 returnValues: {120 _collection: nftCollectionAddress,121 _status: false,122 },123 },124 });125 });126127 itWeb3('NFT to RFT', async ({api, web3, privateKeyWrapper}) => {128 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);129130 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);131 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);132 const nftTokenId = await nftContract.methods.nextTokenId().call();133 await nftContract.methods.mint(owner, nftTokenId).send();134135 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);136137 await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send();138 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();139 const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send();140 expect(result.events).to.be.like({141 Fractionalized: {142 returnValues: {143 _collection: nftCollectionAddress,144 _tokenId: nftTokenId,145 _amount: '100',146 },147 },148 });149 const rftTokenAddress = result.events.Fractionalized.returnValues._rftToken;150 const rftTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);151 expect(await rftTokenContract.methods.balanceOf(owner).call()).to.equal('100');152 });153154 itWeb3('RFT to NFT', async ({api, web3, privateKeyWrapper}) => {155 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);156157 const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner);158 const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await createRFTToken(api, web3, owner, fractionalizer, 100n);159160 const {collectionId, tokenId} = tokenIdFromAddress(rftTokenAddress);161 const refungibleAddress = collectionIdToAddress(collectionId);162 expect(rftCollectionAddress).to.be.equal(refungibleAddress);163 const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);164 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send();165 const result = await fractionalizer.methods.rft2nft(refungibleAddress, tokenId).send();166 expect(result.events).to.be.like({167 DeFractionalized: {168 returnValues: {169 _rftToken: rftTokenAddress,170 _nftCollection: nftCollectionAddress,171 _nftTokenId: nftTokenId,172 },173 },174 });175 });176});177178179180describe('Negative Integration Tests for fractionalizer', () => {181 itWeb3('call setRFTCollection twice', async ({api, web3, privateKeyWrapper}) => {182 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);183 const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);184 const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);185186 const fractionalizer = await deployFractionalizer(api, web3, owner);187 await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();188 await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();189190 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())191 .to.eventually.be.rejectedWith(/RFT collection is already set$/g);192 });193194 itWeb3('call setRFTCollection with NFT collection', async ({api, web3, privateKeyWrapper}) => {195 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);196 const {collectionIdAddress} = await createNonfungibleCollection(api, web3, owner);197 const nftContract = uniqueNFT(web3, collectionIdAddress, owner);198199 const fractionalizer = await deployFractionalizer(api, web3, owner);200 await nftContract.methods.addCollectionAdmin(fractionalizer.options.address).send();201202 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())203 .to.eventually.be.rejectedWith(/Wrong collection type. Collection is not refungible.$/g);204 });205206 itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => {207 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);208 const fractionalizer = await deployFractionalizer(api, web3, owner);209 const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);210211 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())212 .to.eventually.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g);213 });214215 itWeb3('call setRFTCollection after mintRFTCollection', async ({api, web3, privateKeyWrapper}) => {216 const alice = privateKeyWrapper('//Alice');217 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);218 const fractionalizer = await deployFractionalizer(api, web3, owner);219 const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);220 await submitTransactionAsync(alice, tx);221222 const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send({from: owner});223 const collectionIdAddress = result.events.RFTCollectionSet.returnValues._collection;224225 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call())226 .to.eventually.be.rejectedWith(/RFT collection is already set$/g);227 });228229 itWeb3('call nft2rft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {230 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);231232 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);233 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);234 const nftTokenId = await nftContract.methods.nextTokenId().call();235 await nftContract.methods.mint(owner, nftTokenId).send();236237 const fractionalizer = await deployFractionalizer(api, web3, owner);238239 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())240 .to.eventually.be.rejectedWith(/RFT collection is not set$/g);241 });242243 itWeb3('call nft2rft while not owner of NFT token', async ({api, web3, privateKeyWrapper}) => {244 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);245 const nftOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);246247 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);248 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);249 const nftTokenId = await nftContract.methods.nextTokenId().call();250 await nftContract.methods.mint(owner, nftTokenId).send();251 await nftContract.methods.transfer(nftOwner, 1).send();252253254 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);255256 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())257 .to.eventually.be.rejectedWith(/Only token owner could fractionalize it$/g);258 });259260 itWeb3('call nft2rft while not in list of allowed accounts', async ({api, web3, privateKeyWrapper}) => {261 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);262263 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);264 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);265 const nftTokenId = await nftContract.methods.nextTokenId().call();266 await nftContract.methods.mint(owner, nftTokenId).send();267268 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);269270 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();271 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())272 .to.eventually.be.rejectedWith(/Fractionalization of this collection is not allowed by admin$/g);273 });274275 itWeb3('call nft2rft while fractionalizer doesnt have approval for nft token', async ({api, web3, privateKeyWrapper}) => {276 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);277278 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);279 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);280 const nftTokenId = await nftContract.methods.nextTokenId().call();281 await nftContract.methods.mint(owner, nftTokenId).send();282283 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);284285 await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send();286 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call())287 .to.eventually.be.rejectedWith(/ApprovedValueTooLow$/g);288 });289290 itWeb3('call rft2nft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {291 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);292293 const fractionalizer = await deployFractionalizer(api, web3, owner);294 const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);295 const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);296 const rftTokenId = await refungibleContract.methods.nextTokenId().call();297 await refungibleContract.methods.mint(owner, rftTokenId).send();298 299 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())300 .to.eventually.be.rejectedWith(/RFT collection is not set$/g);301 });302303 itWeb3('call rft2nft for RFT token that is not from configured RFT collection', async ({api, web3, privateKeyWrapper}) => {304 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);305306 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);307 const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);308 const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);309 const rftTokenId = await refungibleContract.methods.nextTokenId().call();310 await refungibleContract.methods.mint(owner, rftTokenId).send();311 312 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())313 .to.eventually.be.rejectedWith(/Wrong RFT collection$/g);314 });315316 itWeb3('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({api, web3, privateKeyWrapper}) => {317 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);318 const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);319320 const fractionalizer = await deployFractionalizer(api, web3, owner);321 const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);322323 await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();324 await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send();325326 const rftTokenId = await refungibleContract.methods.nextTokenId().call();327 await refungibleContract.methods.mint(owner, rftTokenId).send();328 329 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call())330 .to.eventually.be.rejectedWith(/No corresponding NFT token found$/g);331 });332333 itWeb3('call rft2nft without owning all RFT pieces', async ({api, web3, privateKeyWrapper}) => {334 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);335 const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);336337 const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner);338 const {rftTokenAddress} = await createRFTToken(api, web3, owner, fractionalizer, 100n);339 340 const {tokenId} = tokenIdFromAddress(rftTokenAddress);341 const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);342 await refungibleTokenContract.methods.transfer(receiver, 50).send();343 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 50).send();344 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, tokenId).call())345 .to.eventually.be.rejectedWith(/Not all pieces are owned by the caller$/g);346 });347});