Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions yarn-project/end-to-end/src/e2e_fees/failures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { FunctionSelector } from '@aztec/aztec.js/abi';
import type { AztecAddress } from '@aztec/aztec.js/addresses';
import { EthAddress } from '@aztec/aztec.js/addresses';
import { SetPublicAuthwitContractInteraction } from '@aztec/aztec.js/authorization';
import { waitForProven } from '@aztec/aztec.js/contracts';
import { PrivateFeePaymentMethod, PublicFeePaymentMethod } from '@aztec/aztec.js/fee';
import { Fr } from '@aztec/aztec.js/fields';
import type { AztecNode } from '@aztec/aztec.js/node';
import { TxExecutionResult } from '@aztec/aztec.js/tx';
import type { Wallet } from '@aztec/aztec.js/wallet';
import type { FPCContract } from '@aztec/noir-contracts.js/FPC';
Expand All @@ -23,6 +25,7 @@ describe('e2e_fees failures', () => {
let bananaCoin: BananaCoin;
let bananaFPC: FPCContract;
let gasSettings: GasSettings;
let aztecNode: AztecNode;
const coinbase = EthAddress.random();

const t = new FeesTest('failures', 3, { coinbase });
Expand All @@ -31,6 +34,7 @@ describe('e2e_fees failures', () => {
await t.setup();
await t.applyFPCSetup();
({ wallet, aliceAddress, sequencerAddress, bananaCoin, bananaFPC, gasSettings } = t);
aztecNode = t.aztecNode;

// Prove up until the current state by just marking it as proven.
// Then turn off the watcher to prevent it from keep proving
Expand Down Expand Up @@ -317,9 +321,98 @@ describe('e2e_fees failures', () => {
[aliceAddress, bananaFPC.address, sequencerAddress],
[initialAliceGas, initialFPCGas - receipt.transactionFee!, initialSequencerGas],
);

// Prove the block containing the teardown-reverted tx (revert_code = 2).
await t.context.watcher.trigger();
await t.cheatCodes.rollup.advanceToNextEpoch();
const provenTimeout =
(t.context.config.aztecProofSubmissionEpochs + 1) *
t.context.config.aztecEpochDuration *
t.context.config.aztecSlotDuration;
await waitForProven(aztecNode, receipt, { provenTimeout });
});

it('proves transaction where both app logic and teardown revert', async () => {
const outrageousPublicAmountAliceDoesNotHave = t.ALICE_INITIAL_BANANAS * 5n;

// Send a tx that will revert in BOTH app logic and teardown.
const { receipt } = await bananaCoin.methods
.transfer_in_public(aliceAddress, sequencerAddress, outrageousPublicAmountAliceDoesNotHave, 0)
.send({
from: aliceAddress,
fee: {
paymentMethod: new BuggedTeardownFeePaymentMethod(bananaFPC.address, aliceAddress, wallet, gasSettings),
},
wait: { dontThrowOnRevert: true },
});

expect(receipt.executionResult).toBe(TxExecutionResult.BOTH_REVERTED);
expect(receipt.transactionFee).toBeGreaterThan(0n);

await t.context.watcher.trigger();
await t.cheatCodes.rollup.advanceToNextEpoch();
const provenTimeout =
(t.context.config.aztecProofSubmissionEpochs + 1) *
t.context.config.aztecEpochDuration *
t.context.config.aztecSlotDuration;
await waitForProven(aztecNode, receipt, { provenTimeout });
});
});

/**
* Fee payment method whose teardown always reverts because max_fee is set to 0.
* The FPC's _pay_refund will assert `0 >= actual_fee` which always fails since actual_fee > 0.
* The setup transfer of 0 tokens succeeds (and the authwit matches the 0 amount).
*/
class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod {
override async getExecutionPayload(): Promise<ExecutionPayload> {
const zeroFee = new Fr(0n);
const authwitNonce = Fr.random();

const asset = await this.getAsset();

// Authorize the FPC to transfer 0 tokens (matches the 0 max_fee we'll pass).
const setPublicAuthWitInteraction = await SetPublicAuthwitContractInteraction.create(
this.wallet,
this.sender,
{
caller: this.paymentContract,
call: FunctionCall.from({
name: 'transfer_in_public',
to: asset,
selector: await FunctionSelector.fromSignature('transfer_in_public((Field),(Field),u128,Field)'),
type: FunctionType.PUBLIC,
hideMsgSender: false,
isStatic: false,
args: [this.sender.toField(), this.paymentContract.toField(), zeroFee, authwitNonce],
returnTypes: [],
}),
},
true,
);

return new ExecutionPayload(
[
...(await setPublicAuthWitInteraction.request()).calls,
FunctionCall.from({
name: 'fee_entrypoint_public',
to: this.paymentContract,
selector: await FunctionSelector.fromSignature('fee_entrypoint_public(u128,Field)'),
type: FunctionType.PRIVATE,
hideMsgSender: false,
isStatic: false,
args: [zeroFee, authwitNonce],
returnTypes: [],
}),
],
[],
[],
[],
this.paymentContract,
);
}
}

class BuggedSetupFeePaymentMethod extends PublicFeePaymentMethod {
override async getExecutionPayload(): Promise<ExecutionPayload> {
const maxFee = this.gasSettings.getFeeLimit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,8 @@ export class PublicTxContext {
}
if (phase === TxExecutionPhase.SETUP) {
this.log.warn(`Setup phase reverted! The transaction will be thrown out.`);
} else if (phase === TxExecutionPhase.APP_LOGIC) {
this.revertCode = RevertCode.APP_LOGIC_REVERTED;
} else if (phase === TxExecutionPhase.TEARDOWN) {
if (this.revertCode.equals(RevertCode.APP_LOGIC_REVERTED)) {
this.revertCode = RevertCode.BOTH_REVERTED;
} else {
this.revertCode = RevertCode.TEARDOWN_REVERTED;
}
} else if (phase === TxExecutionPhase.APP_LOGIC || phase === TxExecutionPhase.TEARDOWN) {
this.revertCode = RevertCode.REVERTED;
}
}

Expand Down
104 changes: 1 addition & 103 deletions yarn-project/stdlib/src/avm/__snapshots__/revert_code.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`revert_code should serialize RevertCode<0> properly 1`] = `
{
Expand Down Expand Up @@ -101,105 +101,3 @@ exports[`revert_code should serialize RevertCode<1> properly 2`] = `
`;

exports[`revert_code should serialize RevertCode<1> properly 3`] = `"0x0000000000000000000000000000000000000000000000000000000000000001"`;

exports[`revert_code should serialize RevertCode<2> properly 1`] = `
{
"data": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2,
],
"type": "Buffer",
}
`;

exports[`revert_code should serialize RevertCode<2> properly 2`] = `
{
"data": [
2,
],
"type": "Buffer",
}
`;

exports[`revert_code should serialize RevertCode<2> properly 3`] = `"0x0000000000000000000000000000000000000000000000000000000000000002"`;

exports[`revert_code should serialize RevertCode<3> properly 1`] = `
{
"data": [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
3,
],
"type": "Buffer",
}
`;

exports[`revert_code should serialize RevertCode<3> properly 2`] = `
{
"data": [
3,
],
"type": "Buffer",
}
`;

exports[`revert_code should serialize RevertCode<3> properly 3`] = `"0x0000000000000000000000000000000000000000000000000000000000000003"`;
42 changes: 21 additions & 21 deletions yarn-project/stdlib/src/avm/revert_code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@ import { jsonStringify } from '@aztec/foundation/json-rpc';
import { RevertCode } from './revert_code.js';

describe('revert_code', () => {
it.each([RevertCode.OK, RevertCode.APP_LOGIC_REVERTED, RevertCode.TEARDOWN_REVERTED, RevertCode.BOTH_REVERTED])(
'should serialize %s properly',
revertCode => {
expect(revertCode.getSerializedLength()).toBe(1);
it.each([RevertCode.OK, RevertCode.REVERTED])('should serialize %s properly', revertCode => {
expect(revertCode.getSerializedLength()).toBe(1);

const hashPreimage = revertCode.toHashPreimage();
expect(hashPreimage).toMatchSnapshot();
expect(hashPreimage.length).toBe(32);
const hashPreimage = revertCode.toHashPreimage();
expect(hashPreimage).toMatchSnapshot();
expect(hashPreimage.length).toBe(32);

const buf = revertCode.toBuffer();
expect(buf).toMatchSnapshot();
expect(RevertCode.fromBuffer(buf)).toEqual(revertCode);
const buf = revertCode.toBuffer();
expect(buf).toMatchSnapshot();
expect(RevertCode.fromBuffer(buf)).toEqual(revertCode);

const field = revertCode.toField();
expect(field).toMatchSnapshot();
expect(RevertCode.fromField(field)).toEqual(revertCode);
expect(RevertCode.fromFields([field])).toEqual(revertCode);
const field = revertCode.toField();
expect(field).toMatchSnapshot();
expect(RevertCode.fromField(field)).toEqual(revertCode);
expect(RevertCode.fromFields([field])).toEqual(revertCode);

const json = jsonStringify(revertCode);
expect(RevertCode.schema.parse(JSON.parse(json))).toEqual(revertCode);
},
);
const json = jsonStringify(revertCode);
expect(RevertCode.schema.parse(JSON.parse(json))).toEqual(revertCode);
});

it('should throw when deserializing from invalid buffer', () => {
expect(() => RevertCode.fromBuffer(Buffer.from([42]))).toThrow();
expect(() => RevertCode.fromField(new Fr(42))).toThrow();
it('should coerce values >= 1 to REVERTED', () => {
expect(RevertCode.fromNumber(2)).toEqual(RevertCode.REVERTED);
expect(RevertCode.fromNumber(3)).toEqual(RevertCode.REVERTED);
expect(RevertCode.fromNumber(42)).toEqual(RevertCode.REVERTED);
expect(RevertCode.fromField(new Fr(42))).toEqual(RevertCode.REVERTED);
expect(RevertCode.fromBuffer(Buffer.from([5]))).toEqual(RevertCode.REVERTED);
});
});
Loading
Loading