1234567891011121314151617import chai, {expect} from 'chai';18import chaiAsPromised from 'chai-as-promised';19import privateKey from './substrate/privateKey';20import {21 default as usingApi, 22 submitTransactionAsync,23 submitTransactionExpectFailAsync,24} from './substrate/substrate-api';25import {26 createItemExpectSuccess,27 createCollectionExpectSuccess,28 scheduleTransferExpectSuccess,29 scheduleTransferAndWaitExpectSuccess,30 setCollectionSponsorExpectSuccess,31 confirmSponsorshipExpectSuccess,32 findUnusedAddress,33 UNIQUE,34 enablePublicMintingExpectSuccess,35 addToAllowListExpectSuccess,36 waitNewBlocks,37 normalizeAccountId,38 getTokenOwner,39 getGenericResult,40 scheduleTransferFundsPeriodicExpectSuccess,41 getFreeBalance,42 confirmSponsorshipByKeyExpectSuccess,43} from './util/helpers';44import {IKeyringPair} from '@polkadot/types/types';45import {getBalanceSingle} from './substrate/get-balance';4647chai.use(chaiAsPromised);4849describe('Scheduling token and balance transfers', () => {50 let alice: IKeyringPair;51 let bob: IKeyringPair;5253 before(async() => {54 await usingApi(async () => {55 alice = privateKey('//Alice');56 bob = privateKey('//Bob');57 });58 });5960 it('Can schedule a transfer of an owned token with delay', async () => {61 await usingApi(async () => {62 63 const nftCollectionId = await createCollectionExpectSuccess();64 const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');65 await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);66 await confirmSponsorshipExpectSuccess(nftCollectionId);6768 await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4);69 });70 });7172 it('Can transfer funds periodically', async () => {73 await usingApi(async (api) => {74 const waitForBlocks = 4;75 const period = 2;76 await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, period, 2);77 const bobsBalanceBefore = await getBalanceSingle(api, bob.address);7879 80 await waitNewBlocks(waitForBlocks - 2);81 const bobsBalanceAfterFirst = await getBalanceSingle(api, bob.address);82 expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;8384 await waitNewBlocks(period);85 const bobsBalanceAfterSecond = await getBalanceSingle(api, bob.address);86 expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;87 });88 });8990 it('Can sponsor scheduling a transaction', async () => {91 const collectionId = await createCollectionExpectSuccess();92 await setCollectionSponsorExpectSuccess(collectionId, bob.address);93 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');9495 await usingApi(async () => {96 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);9798 const aliceBalanceBefore = await getFreeBalance(alice);99 100 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, 4);101 const aliceBalanceAfter = await getFreeBalance(alice);102 expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;103 });104 });105106 107108109110111112113114115116117118119120 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {121 await usingApi(async (api) => {122 123 const zeroBalance = await findUnusedAddress(api);124125 const collectionId = await createCollectionExpectSuccess();126127 128 await enablePublicMintingExpectSuccess(alice, collectionId);129 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);130131 132 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);133 await submitTransactionAsync(alice, balanceTx);134135 136 const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');137138 139 const waitForBlocks = 5;140 await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks);141142 143 const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); 144 const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);145 const events = await submitTransactionAsync(alice, sudoTx);146 expect(getGenericResult(events).success).to.be.true;147148 149 await waitNewBlocks(waitForBlocks - 3);150151 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));152 });153 });154155 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {156 const collectionId = await createCollectionExpectSuccess();157158 await usingApi(async (api) => {159 const zeroBalance = await findUnusedAddress(api);160161 162163164 165 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);166 await submitTransactionAsync(alice, balanceTx);167168 await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);169 await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);170171 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);172173 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, 5);174175 const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);176 const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);177 const events = await submitTransactionAsync(alice, sudoTx);178 expect(getGenericResult(events).success).to.be.true;179180 181 await waitNewBlocks(2);182183 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));184 });185 });186});187188describe.skip('Scheduling EVM smart contracts', () => {189 let alice: IKeyringPair;190 let bob: IKeyringPair;191192 before(async() => {193 await usingApi(async () => {194 alice = privateKey('//Alice');195 bob = privateKey('//Bob');196 });197 });198199 200 it.skip('NFT: Sponsoring of transfers is rate limited', async () => {201 const collectionId = await createCollectionExpectSuccess();202 await setCollectionSponsorExpectSuccess(collectionId, bob.address);203 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');204205 await usingApi(async (api) => {206 207 const zeroBalance = await findUnusedAddress(api);208209 210 const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);211212 213 214 const aliceToZero = api.tx.unique.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 0);215 const events1 = await submitTransactionAsync(alice, aliceToZero);216 const result1 = getGenericResult(events1);217218 219 const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt();220 const zeroToAlice = api.tx.unique.transfer(normalizeAccountId(alice.address), collectionId, itemId, 0);221 const badTransaction = async function () {222 await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);223 };224 await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');225 const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt();226227 228 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);229 await submitTransactionAsync(alice, balancetx);230 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);231 const result2 = getGenericResult(events2);232233 expect(result1.success).to.be.true;234 expect(result2.success).to.be.true;235 expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);236 });237 });238});