@@ -2,8 +2,10 @@ import { FunctionSelector } from '@aztec/aztec.js/abi';
22import type { AztecAddress } from '@aztec/aztec.js/addresses' ;
33import { EthAddress } from '@aztec/aztec.js/addresses' ;
44import { SetPublicAuthwitContractInteraction } from '@aztec/aztec.js/authorization' ;
5+ import { waitForProven } from '@aztec/aztec.js/contracts' ;
56import { PrivateFeePaymentMethod , PublicFeePaymentMethod } from '@aztec/aztec.js/fee' ;
67import { Fr } from '@aztec/aztec.js/fields' ;
8+ import type { AztecNode } from '@aztec/aztec.js/node' ;
79import { TxExecutionResult } from '@aztec/aztec.js/tx' ;
810import type { Wallet } from '@aztec/aztec.js/wallet' ;
911import type { FPCContract } from '@aztec/noir-contracts.js/FPC' ;
@@ -23,6 +25,7 @@ describe('e2e_fees failures', () => {
2325 let bananaCoin : BananaCoin ;
2426 let bananaFPC : FPCContract ;
2527 let gasSettings : GasSettings ;
28+ let aztecNode : AztecNode ;
2629 const coinbase = EthAddress . random ( ) ;
2730
2831 const t = new FeesTest ( 'failures' , 3 , { coinbase } ) ;
@@ -31,6 +34,7 @@ describe('e2e_fees failures', () => {
3134 await t . setup ( ) ;
3235 await t . applyFPCSetup ( ) ;
3336 ( { wallet, aliceAddress, sequencerAddress, bananaCoin, bananaFPC, gasSettings } = t ) ;
37+ aztecNode = t . aztecNode ;
3438
3539 // Prove up until the current state by just marking it as proven.
3640 // Then turn off the watcher to prevent it from keep proving
@@ -317,9 +321,98 @@ describe('e2e_fees failures', () => {
317321 [ aliceAddress , bananaFPC . address , sequencerAddress ] ,
318322 [ initialAliceGas , initialFPCGas - receipt . transactionFee ! , initialSequencerGas ] ,
319323 ) ;
324+
325+ // Prove the block containing the teardown-reverted tx (revert_code = 2).
326+ await t . context . watcher . trigger ( ) ;
327+ await t . cheatCodes . rollup . advanceToNextEpoch ( ) ;
328+ const provenTimeout =
329+ ( t . context . config . aztecProofSubmissionEpochs + 1 ) *
330+ t . context . config . aztecEpochDuration *
331+ t . context . config . aztecSlotDuration ;
332+ await waitForProven ( aztecNode , receipt , { provenTimeout } ) ;
333+ } ) ;
334+
335+ it ( 'proves transaction where both app logic and teardown revert' , async ( ) => {
336+ const outrageousPublicAmountAliceDoesNotHave = t . ALICE_INITIAL_BANANAS * 5n ;
337+
338+ // Send a tx that will revert in BOTH app logic and teardown.
339+ const { receipt } = await bananaCoin . methods
340+ . transfer_in_public ( aliceAddress , sequencerAddress , outrageousPublicAmountAliceDoesNotHave , 0 )
341+ . send ( {
342+ from : aliceAddress ,
343+ fee : {
344+ paymentMethod : new BuggedTeardownFeePaymentMethod ( bananaFPC . address , aliceAddress , wallet , gasSettings ) ,
345+ } ,
346+ wait : { dontThrowOnRevert : true } ,
347+ } ) ;
348+
349+ expect ( receipt . executionResult ) . toBe ( TxExecutionResult . BOTH_REVERTED ) ;
350+ expect ( receipt . transactionFee ) . toBeGreaterThan ( 0n ) ;
351+
352+ await t . context . watcher . trigger ( ) ;
353+ await t . cheatCodes . rollup . advanceToNextEpoch ( ) ;
354+ const provenTimeout =
355+ ( t . context . config . aztecProofSubmissionEpochs + 1 ) *
356+ t . context . config . aztecEpochDuration *
357+ t . context . config . aztecSlotDuration ;
358+ await waitForProven ( aztecNode , receipt , { provenTimeout } ) ;
320359 } ) ;
321360} ) ;
322361
362+ /**
363+ * Fee payment method whose teardown always reverts because max_fee is set to 0.
364+ * The FPC's _pay_refund will assert `0 >= actual_fee` which always fails since actual_fee > 0.
365+ * The setup transfer of 0 tokens succeeds (and the authwit matches the 0 amount).
366+ */
367+ class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod {
368+ override async getExecutionPayload ( ) : Promise < ExecutionPayload > {
369+ const zeroFee = new Fr ( 0n ) ;
370+ const authwitNonce = Fr . random ( ) ;
371+
372+ const asset = await this . getAsset ( ) ;
373+
374+ // Authorize the FPC to transfer 0 tokens (matches the 0 max_fee we'll pass).
375+ const setPublicAuthWitInteraction = await SetPublicAuthwitContractInteraction . create (
376+ this . wallet ,
377+ this . sender ,
378+ {
379+ caller : this . paymentContract ,
380+ call : FunctionCall . from ( {
381+ name : 'transfer_in_public' ,
382+ to : asset ,
383+ selector : await FunctionSelector . fromSignature ( 'transfer_in_public((Field),(Field),u128,Field)' ) ,
384+ type : FunctionType . PUBLIC ,
385+ hideMsgSender : false ,
386+ isStatic : false ,
387+ args : [ this . sender . toField ( ) , this . paymentContract . toField ( ) , zeroFee , authwitNonce ] ,
388+ returnTypes : [ ] ,
389+ } ) ,
390+ } ,
391+ true ,
392+ ) ;
393+
394+ return new ExecutionPayload (
395+ [
396+ ...( await setPublicAuthWitInteraction . request ( ) ) . calls ,
397+ FunctionCall . from ( {
398+ name : 'fee_entrypoint_public' ,
399+ to : this . paymentContract ,
400+ selector : await FunctionSelector . fromSignature ( 'fee_entrypoint_public(u128,Field)' ) ,
401+ type : FunctionType . PRIVATE ,
402+ hideMsgSender : false ,
403+ isStatic : false ,
404+ args : [ zeroFee , authwitNonce ] ,
405+ returnTypes : [ ] ,
406+ } ) ,
407+ ] ,
408+ [ ] ,
409+ [ ] ,
410+ [ ] ,
411+ this . paymentContract ,
412+ ) ;
413+ }
414+ }
415+
323416class BuggedSetupFeePaymentMethod extends PublicFeePaymentMethod {
324417 override async getExecutionPayload ( ) : Promise < ExecutionPayload > {
325418 const maxFee = this . gasSettings . getFeeLimit ( ) ;
0 commit comments