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 fractionalizer = await deployFractionalizer(api, web3, owner);184 const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);185 const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner);186 await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send();187 await fractionalizer.methods.setRFTCollection(collectionIdAddress).send();188189 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).send()).to.be.eventually.rejected;190 });191192 itWeb3('call setRFTCollection with NFT collection', async ({api, web3, privateKeyWrapper}) => {193 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);194 const fractionalizer = await deployFractionalizer(api, web3, owner);195 const {collectionIdAddress} = await createNonfungibleCollection(api, web3, owner);196197 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).send()).to.be.eventually.rejected;198 });199200 itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => {201 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);202 const fractionalizer = await deployFractionalizer(api, web3, owner);203 const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner);204205 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).send()).to.be.eventually.rejected;206 });207208 itWeb3('call setRFTCollection after mintRFTCollection', async ({api, web3, privateKeyWrapper}) => {209 const alice = privateKeyWrapper('//Alice');210 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);211 const fractionalizer = await deployFractionalizer(api, web3, owner);212 const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE);213 await submitTransactionAsync(alice, tx);214215 const result = await fractionalizer.methods.mintRFTCollection('A', 'B', 'C').send({from: owner});216 const collectionIdAddress = result.events.RFTCollectionSet.returnValues._collection;217218 await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).send()).to.be.eventually.rejected;219 });220221 itWeb3('call nft2rft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {222 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);223224 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);225 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);226 const nftTokenId = await nftContract.methods.nextTokenId().call();227 await nftContract.methods.mint(owner, nftTokenId).send();228229 const fractionalizer = await deployFractionalizer(api, web3, owner);230231 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send()).to.be.eventually.rejected;232 });233234 itWeb3('call nft2rft while not owner of NFT token', async ({api, web3, privateKeyWrapper}) => {235 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);236 const nftOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);237238 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);239 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);240 const nftTokenId = await nftContract.methods.nextTokenId().call();241 await nftContract.methods.mint(owner, nftTokenId).send();242 await nftContract.methods.transfer(nftOwner, 1).send();243244245 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);246247 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send()).to.be.eventually.rejected;248 });249250 itWeb3('call nft2rft while not in list of allowed accounts', async ({api, web3, privateKeyWrapper}) => {251 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);252253 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);254 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);255 const nftTokenId = await nftContract.methods.nextTokenId().call();256 await nftContract.methods.mint(owner, nftTokenId).send();257258 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);259260 await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send();261 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send()).to.be.eventually.rejected;262 });263264 itWeb3('call nft2rft while fractionalizer doesnt have approval for nft token', async ({api, web3, privateKeyWrapper}) => {265 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);266267 const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner);268 const nftContract = uniqueNFT(web3, nftCollectionAddress, owner);269 const nftTokenId = await nftContract.methods.nextTokenId().call();270 await nftContract.methods.mint(owner, nftTokenId).send();271272 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner);273274 await fractionalizer.methods.setAllowlist(nftCollectionAddress, true).send();275 await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send()).to.be.eventually.rejected;276 });277278 itWeb3('call rft2nft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => {279 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);280281 const fractionalizer = await deployFractionalizer(api, web3, owner);282 const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner);283 const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner);284 const rftTokenId = await refungibleContract.methods.nextTokenId().call();285 await refungibleContract.methods.mint(owner, rftTokenId).send();286 287 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).send()).to.be.eventually.rejected;288 });289290 itWeb3('call rft2nft for RFT token that is not from configured RFT collection', async ({api, web3, privateKeyWrapper}) => {291 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);292293 const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, 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).send()).to.be.eventually.rejected;300 });301302 itWeb3('call rft2nft without owning all RFT pieces', async ({api, web3, privateKeyWrapper}) => {303 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);304 const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);305306 const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner);307 const {rftTokenAddress} = await createRFTToken(api, web3, owner, fractionalizer, 100n);308 309 const {tokenId} = tokenIdFromAddress(rftTokenAddress);310 const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner);311 await refungibleTokenContract.methods.transfer(receiver, 50).send();312 await refungibleTokenContract.methods.approve(fractionalizer.options.address, 50).send();313 await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, tokenId).send()).to.be.eventually.rejected;314 });315});