diff --git a/yarn-project/end-to-end/src/e2e_fees.test.ts b/yarn-project/end-to-end/src/e2e_fees.test.ts index 061d60a9ab0..992467ee5cc 100644 --- a/yarn-project/end-to-end/src/e2e_fees.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees.test.ts @@ -286,20 +286,6 @@ describe('e2e_fees', () => { const MaxFee = FeeAmount + RefundAmount; const { wallets } = e2eContext; - // const [initialAlicePrivateBananas, initialFPCPrivateBananas] = await bananaPrivateBalances( - // aliceAddress, - // bananaFPC.address, - // ); - // const [initialAlicePublicBananas, initialFPCPublicBananas] = await bananaPublicBalances( - // aliceAddress, - // bananaFPC.address, - // ); - // const [initialAliceGas, initialFPCGas, initialSequencerGas] = await gasBalances( - // aliceAddress, - // bananaFPC.address, - // sequencerAddress, - // ); - // simulation throws an error when setup fails await expect( bananaCoin.methods @@ -328,6 +314,78 @@ describe('e2e_fees', () => { ).rejects.toThrow(/Transaction [0-9a-f]{64} was dropped\. Reason: Tx dropped by P2P node\./); }); + it('fails transaction that error in teardown', async () => { + /** + * We trigger an error in teardown by having the FPC authorize a transfer of its entire balance to Alice + * as part of app logic. This will cause the FPC to not have enough funds to pay the refund back to Alice. + */ + + const PublicMintedAlicePublicBananas = 1000n; + const FeeAmount = 1n; + const RefundAmount = 2n; + const MaxFee = FeeAmount + RefundAmount; + const { wallets } = e2eContext; + + const [initialAlicePrivateBananas, initialFPCPrivateBananas] = await bananaPrivateBalances( + aliceAddress, + bananaFPC.address, + ); + const [initialAlicePublicBananas, initialFPCPublicBananas] = await bananaPublicBalances( + aliceAddress, + bananaFPC.address, + ); + const [initialAliceGas, initialFPCGas, initialSequencerGas] = await gasBalances( + aliceAddress, + bananaFPC.address, + sequencerAddress, + ); + + await bananaCoin.methods.mint_public(aliceAddress, PublicMintedAlicePublicBananas).send().wait(); + + await expect( + bananaCoin.methods + .mint_public(aliceAddress, 1n) // random operation + .send({ + fee: { + maxFee: MaxFee, + paymentMethod: new BuggedTeardownFeePaymentMethod(bananaCoin.address, bananaFPC.address, wallets[0]), + }, + }) + .wait(), + ).rejects.toThrow(/invalid nonce/); + + // node also drops + await expect( + bananaCoin.methods + .mint_public(aliceAddress, 1n) // random operation + .send({ + skipPublicSimulation: true, + fee: { + maxFee: MaxFee, + paymentMethod: new BuggedTeardownFeePaymentMethod(bananaCoin.address, bananaFPC.address, wallets[0]), + }, + }) + .wait(), + ).rejects.toThrow(/Transaction [0-9a-f]{64} was dropped\. Reason: Tx dropped by P2P node\./); + + // nothing happened + await expectMapping( + bananaPrivateBalances, + [aliceAddress, bananaFPC.address, sequencerAddress], + [initialAlicePrivateBananas, initialFPCPrivateBananas, 0n], + ); + await expectMapping( + bananaPublicBalances, + [aliceAddress, bananaFPC.address, sequencerAddress], + [initialAlicePublicBananas + PublicMintedAlicePublicBananas, initialFPCPublicBananas, 0n], + ); + await expectMapping( + gasBalances, + [aliceAddress, bananaFPC.address, sequencerAddress], + [initialAliceGas, initialFPCGas, initialSequencerGas], + ); + }); + function logFunctionSignatures(artifact: ContractArtifact, logger: DebugLogger) { artifact.functions.forEach(fn => { const sig = decodeFunctionSignature(fn.name, fn.parameters); @@ -397,3 +455,49 @@ class BuggedSetupFeePaymentMethod extends PublicFeePaymentMethod { ]); } } + +class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod { + async getFunctionCalls(maxFee: Fr): Promise { + // authorize the FPC to take the max fee from Alice + const nonce = Fr.random(); + const messageHash1 = computeAuthWitMessageHash(this.paymentContract, { + args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce], + functionData: new FunctionData( + FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), + false, + false, + false, + ), + to: this.asset, + }); + + // authorize the FPC to take the maxFee + // do this first because we only get 2 feepayload calls + await this.wallet.setPublicAuth(messageHash1, true).send().wait(); + + return Promise.resolve([ + // in this, we're actually paying the fee in setup + { + to: this.getPaymentContract(), + functionData: new FunctionData( + FunctionSelector.fromSignature('fee_entrypoint_public(Field,(Field),Field)'), + false, + true, + false, + ), + args: [maxFee, this.asset, nonce], + }, + // and trying to take a little extra in teardown, but specify a bad nonce + { + to: this.asset, + functionData: new FunctionData( + FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), + false, + false, + false, + ), + args: [this.wallet.getAddress(), this.paymentContract, new Fr(1), Fr.random()], + }, + ]); + } +} diff --git a/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts b/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts index b8992cdef70..d33e8a06d56 100644 --- a/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts +++ b/yarn-project/sequencer-client/src/simulator/world_state_public_db.test.ts @@ -85,7 +85,7 @@ describe('world_state_public_db', () => { // write a new value to our first value await publicStateDb.storageWrite(addresses[0], slots[0], newValue); - // should read back the uncommited value + // should read back the uncommitted value expect(await publicStateDb.storageRead(addresses[0], slots[0])).toEqual(newValue); // other slots should be unchanged @@ -132,7 +132,7 @@ describe('world_state_public_db', () => { expect(await publicStateDb.storageRead(addresses[0], slots[0])).toEqual(newValue); }); - it('reads original value if rolled back uncommited value', async function () { + it('reads original value if rolled back uncommitted value', async function () { const publicStateDb = new WorldStatePublicDB(db); expect(await publicStateDb.storageRead(addresses[0], slots[0])).toEqual(dbValues[0]); @@ -141,7 +141,7 @@ describe('world_state_public_db', () => { // write a new value to our first value await publicStateDb.storageWrite(addresses[0], slots[0], newValue); - // should read back the uncommited value + // should read back the uncommitted value expect(await publicStateDb.storageRead(addresses[0], slots[0])).toEqual(newValue); // now rollback @@ -174,7 +174,7 @@ describe('world_state_public_db', () => { // write a new value to our first value await publicStateDb.storageWrite(addresses[0], slots[0], newValue2); - // should read back the uncommited value + // should read back the uncommitted value expect(await publicStateDb.storageRead(addresses[0], slots[0])).toEqual(newValue2); });