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 getFreeBalance,31 confirmSponsorshipByKeyExpectSuccess,32} from './util/helpers';33import {IKeyringPair} from '@polkadot/types/types';34import {getBalanceSingle} from './substrate/get-balance';3536chai.use(chaiAsPromised);3738describe('Scheduling token and balance transfers', () => {39 let alice: IKeyringPair;40 let bob: IKeyringPair;4142 before(async() => {43 await usingApi(async () => {44 alice = privateKey('//Alice');45 bob = privateKey('//Bob');46 });47 });4849 it('Can schedule a transfer of an owned token with delay', async () => {50 await usingApi(async () => {51 52 const nftCollectionId = await createCollectionExpectSuccess();53 const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');54 await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);55 await confirmSponsorshipExpectSuccess(nftCollectionId);5657 await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4);58 });59 });6061 it('Can transfer funds periodically', async () => {62 await usingApi(async (api) => {63 const waitForBlocks = 4;64 const period = 2;65 await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, period, 2);66 const bobsBalanceBefore = await getBalanceSingle(api, bob.address);6768 69 await waitNewBlocks(waitForBlocks - 2);70 const bobsBalanceAfterFirst = await getBalanceSingle(api, bob.address);71 expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;7273 await waitNewBlocks(period);74 const bobsBalanceAfterSecond = await getBalanceSingle(api, bob.address);75 expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;76 });77 });7879 it('Can sponsor scheduling a transaction', async () => {80 const collectionId = await createCollectionExpectSuccess();81 await setCollectionSponsorExpectSuccess(collectionId, bob.address);82 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');8384 await usingApi(async () => {85 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);8687 const aliceBalanceBefore = await getFreeBalance(alice);88 89 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, 4);90 const aliceBalanceAfter = await getFreeBalance(alice);91 expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;92 });93 });9495 96979899100101102103104105106107108109 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {110 await usingApi(async (api) => {111 112 const zeroBalance = await findUnusedAddress(api);113114 const collectionId = await createCollectionExpectSuccess();115116 117 await enablePublicMintingExpectSuccess(alice, collectionId);118 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);119120 121 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);122 await submitTransactionAsync(alice, balanceTx);123124 125 const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');126127 128 const waitForBlocks = 5;129 await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks);130131 132 const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); 133 const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);134 const events = await submitTransactionAsync(alice, sudoTx);135 expect(getGenericResult(events).success).to.be.true;136137 138 await waitNewBlocks(waitForBlocks - 3);139140 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));141 });142 });143144 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {145 const collectionId = await createCollectionExpectSuccess();146147 await usingApi(async (api) => {148 const zeroBalance = await findUnusedAddress(api);149150 151152153 154 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);155 await submitTransactionAsync(alice, balanceTx);156157 await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);158 await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);159160 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);161162 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, 5);163164 const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);165 const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);166 const events = await submitTransactionAsync(alice, sudoTx);167 expect(getGenericResult(events).success).to.be.true;168169 170 await waitNewBlocks(2);171172 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));173 });174 });175});176177describe.skip('Scheduling EVM smart contracts', () => {178 let alice: IKeyringPair;179 let bob: IKeyringPair;180181 before(async() => {182 await usingApi(async () => {183 alice = privateKey('//Alice');184 bob = privateKey('//Bob');185 });186 });187188 189 it.skip('NFT: Sponsoring of transfers is rate limited', async () => {190 const collectionId = await createCollectionExpectSuccess();191 await setCollectionSponsorExpectSuccess(collectionId, bob.address);192 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');193194 await usingApi(async (api) => {195 196 const zeroBalance = await findUnusedAddress(api);197198 199 const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);200201 202 203 const aliceToZero = api.tx.unique.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 0);204 const events1 = await submitTransactionAsync(alice, aliceToZero);205 const result1 = getGenericResult(events1);206207 208 const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt();209 const zeroToAlice = api.tx.unique.transfer(normalizeAccountId(alice.address), collectionId, itemId, 0);210 const badTransaction = async function () {211 await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);212 };213 await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');214 const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt();215216 217 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);218 await submitTransactionAsync(alice, balancetx);219 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);220 const result2 = getGenericResult(events2);221222 expect(result1.success).to.be.true;223 expect(result2.success).to.be.true;224 expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);225 });226 });227});