--- a/tests/src/eth/contractSponsoring.test.ts +++ b/tests/src/eth/contractSponsoring.test.ts @@ -22,29 +22,31 @@ describe('Sponsoring EVM contracts', () => { let donor: IKeyringPair; + let nominal: bigint; before(async () => { - await usingPlaygrounds(async (_helper, privateKey) => { + await usingPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); + nominal = helper.balance.getOneTokenNominal(); }); }); - itEth('Self sponsored can be set by the address that deployed the contract', async ({helper}) => { + itEth('Self sponsoring can be set by the address that deployed the contract', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const flipper = await helper.eth.deployFlipper(owner); const helpers = helper.ethNativeContract.contractHelpers(owner); + // 1. owner can set selfSponsoring: expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; - await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected; + const result = await helpers.methods.selfSponsoredEnable(flipper.options.address).send({from: owner}); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; - }); - itEth('Set self sponsored events', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const flipper = await helper.eth.deployFlipper(owner); - const helpers = helper.ethNativeContract.contractHelpers(owner); - - const result = await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); + // 1.1 Can get sponsor using methods.sponsor: + const actualSponsor = await helpers.methods.sponsor(flipper.options.address).call(); + expect(actualSponsor.eth).to.eq(flipper.options.address); + expect(actualSponsor.sub).to.eq('0'); + + // 2. Events should be: const ethEvents = helper.eth.helper.eth.normalizeEvents(result.events); expect(ethEvents).to.be.deep.equal([ { @@ -66,7 +68,7 @@ ]); }); - itEth('Self sponsored can not be set by the address that did not deployed the contract', async ({helper}) => { + itEth('Self sponsoring cannot be set by the address that did not deployed the contract', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const notOwner = await helper.eth.createAccountWithBalance(donor); const helpers = helper.ethNativeContract.contractHelpers(owner); @@ -83,7 +85,7 @@ const flipper = await helper.eth.deployFlipper(owner); expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false; - await expect(helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner})).to.be.not.rejected; + await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true; }); @@ -104,18 +106,12 @@ const helpers = helper.ethNativeContract.contractHelpers(owner); const flipper = await helper.eth.deployFlipper(owner); + // 1. owner can set a sponsor: expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false; - await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; - expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true; - }); - - itEth('Set sponsor event', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - const result = await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); + expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true; + + // 2. Events should be: const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -129,7 +125,7 @@ ]); }); - itEth('Sponsor can not be set by the address that did not deployed the contract', async ({helper}) => { + itEth('Sponsor cannot be set by the address that did not deployed the contract', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const sponsor = await helper.eth.createAccountWithBalance(donor); const notOwner = await helper.eth.createAccountWithBalance(donor); @@ -148,19 +144,18 @@ const flipper = await helper.eth.deployFlipper(owner); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; - await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; - await expect(helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor})).to.be.not.rejected; + await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); + + // 1. sponsor can confirm sponsorship: + const result = await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; - }); - itEth('Confirm sponsorship event', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); + // 1.1 Can get sponsor using methods.sponsor: + const actualSponsor = await helpers.methods.sponsor(flipper.options.address).call(); + expect(actualSponsor.eth).to.eq(sponsor); + expect(actualSponsor.sub).to.eq('0'); - await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; - const result = await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); + // 2. Events should be: const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -196,34 +191,6 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPendingSponsor'); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; - }); - - itEth('Get self sponsored sponsor', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); - - const result = await helpers.methods.sponsor(flipper.options.address).call(); - - expect(result[0]).to.be.eq(flipper.options.address); - expect(result[1]).to.be.eq('0'); - }); - - itEth('Get confirmed sponsor', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); - await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - - const result = await helpers.methods.sponsor(flipper.options.address).call(); - - expect(result[0]).to.be.eq(sponsor); - expect(result[1]).to.be.eq('0'); }); itEth('Sponsor can be removed by the address that deployed the contract', async ({helper}) => { @@ -236,21 +203,11 @@ await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; - - await helpers.methods.removeSponsor(flipper.options.address).send(); + // 1. Can remove sponsor: + const result = await helpers.methods.removeSponsor(flipper.options.address).send(); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; - }); - itEth('Remove sponsor event', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); - await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - - const result = await helpers.methods.removeSponsor(flipper.options.address).send(); + // 2. Events should be: const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -261,6 +218,11 @@ }, }, ]); + + // TODO: why call method reverts? + // const actualSponsor = await helpers.methods.sponsor(flipper.options.address).call(); + // expect(actualSponsor.eth).to.eq(sponsor); + // expect(actualSponsor.sub).to.eq('0'); }); itEth('Sponsor can not be removed by the address that did not deployed the contract', async ({helper}) => { @@ -276,6 +238,7 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; await expect(helpers.methods.removeSponsor(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + await expect(helpers.methods.removeSponsor(flipper.options.address).send({from: notOwner})).to.be.rejected; expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); @@ -292,15 +255,15 @@ await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner}); await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - const callerBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const callerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(caller)); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; // Balance should be taken from sponsor instead of caller - const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - const callerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const callerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(caller)); expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; expect(callerBalanceAfter).to.be.eq(callerBalanceBefore); }); @@ -331,82 +294,67 @@ expect(callerBalanceAfter).to.be.eq(callerBalanceBefore); }); - itEth('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const caller = helper.eth.createAccount(); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); - await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); - - await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); - await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - - await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); - await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - - const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - expect(sponsorBalanceBefore).to.be.not.equal('0'); - - await flipper.methods.flip().send({from: caller}); - expect(await flipper.methods.getValue().call()).to.be.true; - - // Balance should be taken from flipper instead of caller - const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + [ + {balance: 0n, label: '0'}, + {balance: 10n, label: '10'}, + ].map(testCase => { + itEth(`Allow-listed address that has ${testCase.label} UNQ can call a contract. Sponsor balance should decrease`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = helper.eth.createAccount(); + await helper.eth.transferBalanceFromSubstrate(donor, caller, testCase.balance); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + + await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); + await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); + + await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); + await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); + + await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); + await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); + + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceBefore > 0n).to.be.true; + + await flipper.methods.flip().send({from: caller}); + expect(await flipper.methods.getValue().call()).to.be.true; + + // Balance should be taken from flipper instead of caller + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + // Caller's balance does not change: + const callerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(caller)); + expect(callerBalanceAfter).to.eq(testCase.balance * nominal); + }); }); - itEth('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({helper}) => { + itEth('Non-allow-listed address can call a contract. Sponsor balance should not decrease', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const caller = await helper.eth.createAccount(); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); - await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); + const caller = helper.eth.createAccount(); + const contractHelpers = helper.ethNativeContract.contractHelpers(owner); + // Deploy flipper and send some tokens: + const flipper = await helper.eth.deployFlipper(owner); await helper.eth.transferBalanceFromSubstrate(donor, flipper.options.address); - + expect(await flipper.methods.getValue().call()).to.be.false; + // flipper address has some tokens: const originalFlipperBalance = await helper.balance.getEthereum(flipper.options.address); - expect(originalFlipperBalance).to.be.not.equal('0'); + expect(originalFlipperBalance > 0n).to.be.true; + + // Set Allowlisted sponsoring mode. caller is not in allow list: + await contractHelpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); + await contractHelpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); + await contractHelpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); + // 1. Caller has no UNQ and is not in allow list. So he cannot flip: await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/Returned error: insufficient funds for gas \* price \+ value/); expect(await flipper.methods.getValue().call()).to.be.false; - // Balance should be taken from flipper instead of caller - // FIXME the comment is wrong! What check should be here? + // Flipper's balance does not change: const balanceAfter = await helper.balance.getEthereum(flipper.options.address); expect(balanceAfter).to.be.equal(originalFlipperBalance); - }); - - itEth('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const sponsor = await helper.eth.createAccountWithBalance(donor); - const caller = await helper.eth.createAccountWithBalance(donor); - const helpers = helper.ethNativeContract.contractHelpers(owner); - const flipper = await helper.eth.deployFlipper(owner); - - await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); - await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); - - await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); - await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - - await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); - await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - - const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - const callerBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); - - await flipper.methods.flip().send({from: caller}); - expect(await flipper.methods.getValue().call()).to.be.true; - - const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); - const callerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); - expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; - expect(callerBalanceAfter).to.be.equal(callerBalanceBefore); }); itEth('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({helper}) => { @@ -427,7 +375,7 @@ await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); const originalFlipperBalance = await helper.balance.getEthereum(sponsor); - expect(originalFlipperBalance).to.be.not.equal('0'); + expect(originalFlipperBalance > 0n).to.be.true; await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true;