123456import chai, { expect } from 'chai';7import chaiAsPromised from 'chai-as-promised';8import privateKey from './substrate/privateKey';9import {10 default as usingApi, 11 submitTransactionAsync,12 submitTransactionExpectFailAsync,13} from './substrate/substrate-api';14import {15 createItemExpectSuccess,16 createCollectionExpectSuccess,17 scheduleTransferExpectSuccess,18 scheduleTransferAndWaitExpectSuccess,19 setCollectionSponsorExpectSuccess,20 confirmSponsorshipExpectSuccess,21 findUnusedAddress,22 UNIQUE,23 enablePublicMintingExpectSuccess,24 addToAllowListExpectSuccess,25 waitNewBlocks,26 normalizeAccountId,27 getTokenOwner,28 getGenericResult,29 scheduleTransferFundsPeriodicExpectSuccess,30 setCollectionLimitsExpectSuccess,31 getDetailedCollectionInfo,32 getFreeBalance,33 confirmSponsorshipByKeyExpectSuccess,34} from './util/helpers';35import {IKeyringPair} from '@polkadot/types/types';36import {getBalanceSingle} from './substrate/get-balance';3738chai.use(chaiAsPromised);3940describe('Scheduling token and balance transfers', () => {41 let alice: IKeyringPair;42 let bob: IKeyringPair;4344 before(async() => {45 await usingApi(async () => {46 alice = privateKey('//Alice');47 bob = privateKey('//Bob');48 });49 });5051 it('Can schedule a transfer of an owned token with delay', async () => {52 await usingApi(async () => {53 54 const nftCollectionId = await createCollectionExpectSuccess();55 const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');56 await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);57 await confirmSponsorshipExpectSuccess(nftCollectionId);5859 await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4);60 });61 });6263 it('Can transfer funds periodically', async () => {64 await usingApi(async (api) => {65 const waitForBlocks = 4;66 const period = 2;67 await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, period, 2);68 const bobsBalanceBefore = await getBalanceSingle(api, bob.address);6970 71 await waitNewBlocks(waitForBlocks - 2);72 const bobsBalanceAfterFirst = await getBalanceSingle(api, bob.address);73 expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;7475 await waitNewBlocks(period);76 const bobsBalanceAfterSecond = await getBalanceSingle(api, bob.address);77 expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;78 });79 });8081 it('Can sponsor scheduling a transaction', async () => {82 const collectionId = await createCollectionExpectSuccess();83 await setCollectionSponsorExpectSuccess(collectionId, bob.address);84 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');8586 await usingApi(async () => {87 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);8889 const aliceBalanceBefore = await getFreeBalance(alice);90 91 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, 4);92 const aliceBalanceAfter = await getFreeBalance(alice);93 expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;94 });95 });9697 9899100101102103104105106107108109110111 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {112 await usingApi(async (api) => {113 114 const zeroBalance = await findUnusedAddress(api);115116 const collectionId = await createCollectionExpectSuccess();117118 119 await enablePublicMintingExpectSuccess(alice, collectionId);120 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);121122 123 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);124 await submitTransactionAsync(alice, balanceTx);125126 127 const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');128129 130 const waitForBlocks = 5;131 await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks);132133 134 const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); 135 const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);136 const events = await submitTransactionAsync(alice, sudoTx);137 expect(getGenericResult(events).success).to.be.true;138139 140 await waitNewBlocks(waitForBlocks - 3);141142 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));143 });144 });145146 147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {192 const collectionId = await createCollectionExpectSuccess();193194 await usingApi(async (api) => {195 const zeroBalance = await findUnusedAddress(api);196197 198199200 201 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);202 await submitTransactionAsync(alice, balanceTx);203204 await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);205 await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);206207 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);208209 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, 5);210211 const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);212 const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);213 const events = await submitTransactionAsync(alice, sudoTx);214 expect(getGenericResult(events).success).to.be.true;215216 217 await waitNewBlocks(2);218219 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));220 });221 });222});223224describe.skip('Scheduling EVM smart contracts', () => {225 let alice: IKeyringPair;226 let bob: IKeyringPair;227228 before(async() => {229 await usingApi(async () => {230 alice = privateKey('//Alice');231 bob = privateKey('//Bob');232 });233 });234235 236 it.skip('NFT: Sponsoring of transfers is rate limited', async () => {237 const collectionId = await createCollectionExpectSuccess();238 await setCollectionSponsorExpectSuccess(collectionId, bob.address);239 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');240241 await usingApi(async (api) => {242 243 const zeroBalance = await findUnusedAddress(api);244245 246 const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);247248 249 250 const aliceToZero = api.tx.unique.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 0);251 const events1 = await submitTransactionAsync(alice, aliceToZero);252 const result1 = getGenericResult(events1);253254 255 const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt();256 const zeroToAlice = api.tx.unique.transfer(normalizeAccountId(alice.address), collectionId, itemId, 0);257 const badTransaction = async function () {258 await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);259 };260 await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');261 const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt();262263 264 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);265 await submitTransactionAsync(alice, balancetx);266 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);267 const result2 = getGenericResult(events2);268269 expect(result1.success).to.be.true;270 expect(result2.success).to.be.true;271 expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);272 });273 });274});