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
28 changes: 27 additions & 1 deletion docs/docs-operate/operators/reference/changelog/v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,36 @@ The variable fully **replaced** the hardcoded defaults. Format allowed entries w
--txPublicSetupAllowListExtend <value> ($TX_PUBLIC_SETUP_ALLOWLIST)
```

The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. Format: `I:address:selector,C:classId:selector`.
The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. An optional flags segment can be appended for additional validation:

```
I:address:selector[:flags]
C:classId:selector[:flags]
```

Where `flags` is a `+`-separated list of:
- `os` — `onlySelf`: only allow calls where msg_sender == contract address
- `rn` — `rejectNullMsgSender`: reject calls with a null msg_sender
- `cl=N` — `calldataLength`: enforce exact calldata length of N fields

Example: `C:0xabc:0x1234:os+cl=4`

**Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically.

### Token removed from default setup allowlist

Token class-based entries (`_increase_public_balance` and `transfer_in_public`) have been removed from the default public setup allowlist. FPC-based fee payments using custom tokens no longer work out of the box.

This change was made because Token class IDs change with aztec-nr releases, making the allowlist impossible to keep up to date with new library releases. In addition, `transfer_in_public` requires complex additional logic to be built into the node to prevent mass transaction invalidation attacks. **FPC-based fee payment with custom tokens won't work on mainnet alpha**.

**Migration**: Node operators who need FPC support must manually add Token entries via `TX_PUBLIC_SETUP_ALLOWLIST`. Example:

```bash
TX_PUBLIC_SETUP_ALLOWLIST="C:<tokenClassId>:<increaseBalanceSelector>:os+cl=3,C:<tokenClassId>:<transferInPublicSelector>:cl=5"
```

Replace `<tokenClassId>` with the deployed Token contract class ID and `<increaseBalanceSelector>`/`<transferInPublicSelector>` with the respective function selectors. Keep in mind that this will only work on local network setups, since even if you as an operator add these entries, other nodes will not have them and will not pick up these transactions.

## Removed features

## New features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ use aztec::macros::aztec;
/// Fee Payment Contract (FPC) allows users to pay for the transaction fee with an arbitrary asset. Supports private
/// and public fee payment flows.
///
/// **WARNING**: This is an example/reference implementation. FPC-based fee payment with custom tokens will NOT work
/// on mainnet alpha because Token class IDs change with aztec-nr releases and are not included in the default public
/// setup allowlist.
///
/// ***Note:***
/// Accepted asset funds sent by the users to this contract stay in this contract and later on can
/// be pulled by the admin using the `pull_funds` function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { FeePaymentMethod } from './fee_payment_method.js';

/**
* Holds information about how the fee for a transaction is to be paid.
* @deprecated Is not supported on mainnet. Use {@link FeeJuicePaymentMethodWithClaim} or `SponsoredFeePaymentMethod` instead.
*/
export class PrivateFeePaymentMethod implements FeePaymentMethod {
private assetPromise: Promise<AztecAddress> | null = null;
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/fee/public_fee_payment_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { FeePaymentMethod } from './fee_payment_method.js';

/**
* Holds information about how the fee for a transaction is to be paid.
* @deprecated Is not supported on mainnet. Use {@link FeeJuicePaymentMethodWithClaim} or `SponsoredFeePaymentMethod` instead.
*/
export class PublicFeePaymentMethod implements FeePaymentMethod {
private assetPromise: Promise<AztecAddress> | null = null;
Expand Down
43 changes: 42 additions & 1 deletion yarn-project/aztec/src/local-network/local-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import { SecretValue } from '@aztec/foundation/config';
import { EthAddress } from '@aztec/foundation/eth-address';
import type { LogFn } from '@aztec/foundation/log';
import { DateProvider, TestDateProvider } from '@aztec/foundation/timer';
import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types/vk-tree';
import { protocolContractsHash } from '@aztec/protocol-contracts';
import { SequencerState } from '@aztec/sequencer-client';
import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi';
import type { FunctionAbi } from '@aztec/stdlib/abi';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import type { ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
import type { AllowedElement, ProvingJobBroker } from '@aztec/stdlib/interfaces/server';
import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees';
import {
type TelemetryClient,
Expand All @@ -44,6 +48,38 @@ import { getSponsoredFPCAddress } from './sponsored_fpc.js';

const logger = createLogger('local-network');

/**
* Returns Token-specific allowlist entries for FPC-based fee payments.
* The local network deploys a banana FPC and Token contracts, so the node must allow Token setup functions.
*/
async function getTokenAllowedSetupFunctions(): Promise<AllowedElement[]> {
const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id;
const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat(
TokenContractArtifact.nonDispatchPublicFunctions || [],
);
const getCalldataLength = (name: string) => {
const fn = allFunctions.find(f => f.name === name)!;
return 1 + countArgumentsSize(fn);
};
const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
const transferInPublicSelector = await FunctionSelector.fromSignature(
'transfer_in_public((Field),(Field),u128,Field)',
);
return [
{
classId: tokenClassId,
selector: increaseBalanceSelector,
calldataLength: getCalldataLength('_increase_public_balance'),
onlySelf: true,
},
{
classId: tokenClassId,
selector: transferInPublicSelector,
calldataLength: getCalldataLength('transfer_in_public'),
},
];
}

const localAnvil = foundry;

/**
Expand Down Expand Up @@ -102,9 +138,14 @@ export async function createLocalNetwork(config: Partial<LocalNetworkConfig> = {
logger.warn(`Multiple L1 RPC URLs provided. Local networks will only use the first one: ${l1RpcUrl}`);
}

// The local network deploys a banana FPC with Token contracts, so include Token entries
// in the setup allowlist so FPC-based fee payments work out of the box.
const tokenAllowList = await getTokenAllowedSetupFunctions();

const aztecNodeConfig: AztecNodeConfig = {
...getConfigEnvVars(),
...config,
txPublicSetupAllowListExtend: [...tokenAllowList, ...(config.txPublicSetupAllowListExtend ?? [])],
};
const hdAccount = mnemonicToAccount(config.l1Mnemonic || DefaultMnemonic);
if (
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/cli-wallet/src/utils/options/fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,19 @@ export function parsePaymentMethod(
case 'fpc-public': {
const fpc = getFpc();
const asset = getAsset();
log(
`WARNING: fpc-public is deprecated and will not work on mainnet alpha. Use fee_juice or fpc-sponsored instead.`,
);
log(`Using public fee payment with asset ${asset} via paymaster ${fpc}`);
const { PublicFeePaymentMethod } = await import('@aztec/aztec.js/fee');
return new PublicFeePaymentMethod(fpc, from, wallet, gasSettings);
}
case 'fpc-private': {
const fpc = getFpc();
const asset = getAsset();
log(
`WARNING: fpc-private is deprecated and will not work on mainnet alpha. Use fee_juice or fpc-sponsored instead.`,
);
log(`Using private fee payment with asset ${asset} via paymaster ${fpc}`);
const { PrivateFeePaymentMethod } = await import('@aztec/aztec.js/fee');
return new PrivateFeePaymentMethod(fpc, from, wallet, gasSettings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ import { AMMContract } from '@aztec/noir-contracts.js/AMM';
import { FPCContract } from '@aztec/noir-contracts.js/FPC';
import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice';
import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC';
import { TokenContract as BananaCoin, TokenContract } from '@aztec/noir-contracts.js/Token';
import { TokenContract as BananaCoin, TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import { type PXEConfig, getPXEConfig } from '@aztec/pxe/server';
import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi';
import type { FunctionAbi } from '@aztec/stdlib/abi';
import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
import { GasSettings } from '@aztec/stdlib/gas';
import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
import { deriveSigningKey } from '@aztec/stdlib/keys';

import { MNEMONIC } from '../../fixtures/fixtures.js';
Expand All @@ -42,6 +46,35 @@ import { type ClientFlowsConfig, FULL_FLOWS_CONFIG, KEY_FLOWS_CONFIG } from './c

const { BENCHMARK_CONFIG } = process.env;

/** Returns Token-specific allowlist entries for FPC-based fee payments (test-only). */
async function getTokenAllowedSetupFunctions(): Promise<AllowedElement[]> {
const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id;
const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat(
TokenContractArtifact.nonDispatchPublicFunctions || [],
);
const getCalldataLength = (name: string) => {
const fn = allFunctions.find(f => f.name === name)!;
return 1 + countArgumentsSize(fn);
};
const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
const transferInPublicSelector = await FunctionSelector.fromSignature(
'transfer_in_public((Field),(Field),u128,Field)',
);
return [
{
classId: tokenClassId,
selector: increaseBalanceSelector,
calldataLength: getCalldataLength('_increase_public_balance'),
onlySelf: true,
},
{
classId: tokenClassId,
selector: transferInPublicSelector,
calldataLength: getCalldataLength('transfer_in_public'),
},
];
}

export type AccountType = 'ecdsar1' | 'schnorr';
export type FeePaymentMethodGetter = (wallet: Wallet, sender: AztecAddress) => Promise<FeePaymentMethod | undefined>;
export type BenchmarkingFeePaymentMethod = 'bridged_fee_juice' | 'private_fpc' | 'sponsored_fpc' | 'fee_juice';
Expand Down Expand Up @@ -130,11 +163,14 @@ export class ClientFlowsBenchmark {

async setup() {
this.logger.info('Setting up subsystems from fresh');
// Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha.
const tokenAllowList = await getTokenAllowedSetupFunctions();
this.context = await setup(0, {
...this.setupOptions,
fundSponsoredFPC: true,
skipAccountDeployment: true,
l1ContractsArgs: this.setupOptions,
txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList],
});
await this.applyBaseSetup();

Expand Down
49 changes: 48 additions & 1 deletion yarn-project/end-to-end/src/e2e_fees/fees_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import { AppSubscriptionContract } from '@aztec/noir-contracts.js/AppSubscriptio
import { FPCContract } from '@aztec/noir-contracts.js/FPC';
import { FeeJuiceContract } from '@aztec/noir-contracts.js/FeeJuice';
import { SponsoredFPCContract } from '@aztec/noir-contracts.js/SponsoredFPC';
import { TokenContract as BananaCoin } from '@aztec/noir-contracts.js/Token';
import { TokenContract as BananaCoin, TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
import { CounterContract } from '@aztec/noir-test-contracts.js/Counter';
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
import { FunctionSelector, countArgumentsSize } from '@aztec/stdlib/abi';
import type { FunctionAbi } from '@aztec/stdlib/abi';
import { getContractClassFromArtifact } from '@aztec/stdlib/contract';
import { GasSettings } from '@aztec/stdlib/gas';
import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
import type { AllowedElement } from '@aztec/stdlib/interfaces/server';

import { getContract } from 'viem';

Expand All @@ -37,6 +41,46 @@ import { type BalancesFn, getBalancesFn, setupSponsoredFPC } from '../fixtures/u
import { FeeJuicePortalTestingHarnessFactory, type GasBridgingTestHarness } from '../shared/gas_portal_test_harness.js';
import { TestWallet } from '../test-wallet/test_wallet.js';

/** Returns the calldata length for a function: 1 (selector) + arguments size. */
function getCalldataLength(functionName: string): number {
const allFunctions: FunctionAbi[] = (TokenContractArtifact.functions as FunctionAbi[]).concat(
TokenContractArtifact.nonDispatchPublicFunctions || [],
);
const fn = allFunctions.find(f => f.name === functionName);
if (!fn) {
throw new Error(`Unknown function ${functionName} in Token artifact`);
}
return 1 + countArgumentsSize(fn);
}

/**
* Returns Token-specific allowlist entries needed for FPC-based fee payments.
* These are test-only — FPC-based fee payment with custom tokens won't work on mainnet alpha.
*/
async function getTokenAllowedSetupFunctions(): Promise<AllowedElement[]> {
const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id;
const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)');
const transferInPublicSelector = await FunctionSelector.fromSignature(
'transfer_in_public((Field),(Field),u128,Field)',
);

return [
// Token: needed for private transfers via FPC (transfer_to_public enqueues this)
{
classId: tokenClassId,
selector: increaseBalanceSelector,
calldataLength: getCalldataLength('_increase_public_balance'),
onlySelf: true,
},
// Token: needed for public transfers via FPC (fee_entrypoint_public enqueues this)
{
classId: tokenClassId,
selector: transferInPublicSelector,
calldataLength: getCalldataLength('transfer_in_public'),
},
];
}

/**
* Test fixture for testing fees. Provides the following setup steps:
* InitialAccounts: Initializes 3 Schnorr account contracts.
Expand Down Expand Up @@ -104,12 +148,15 @@ export class FeesTest {

async setup() {
this.logger.verbose('Setting up fresh context...');
// Token allowlist entries are test-only: FPC-based fee payment with custom tokens won't work on mainnet alpha.
const tokenAllowList = await getTokenAllowedSetupFunctions();
this.context = await setup(0, {
startProverNode: true,
...this.setupOptions,
fundSponsoredFPC: true,
skipAccountDeployment: true,
l1ContractsArgs: { ...this.setupOptions },
txPublicSetupAllowListExtend: [...(this.setupOptions.txPublicSetupAllowListExtend ?? []), ...tokenAllowList],
});

this.rollupContract = RollupContract.getFromConfig(this.context.config);
Expand Down
34 changes: 34 additions & 0 deletions yarn-project/p2p/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,40 @@ describe('config', () => {
expect(allowList).toEqual(config);
});

it('parses entries with flags', async () => {
const address = await AztecAddress.random();
const selector = FunctionSelector.random();
const classId = Fr.random();
const classSelector = FunctionSelector.random();

const allowList = parseAllowList(`I:${address}:${selector}:os+cl=4,C:${classId}:${classSelector}:rn+os+cl=10`);

expect(allowList).toEqual([
{ address, selector, onlySelf: true, calldataLength: 4 },
{ classId, selector: classSelector, rejectNullMsgSender: true, onlySelf: true, calldataLength: 10 },
]);
});

it('parses entries without flags (backward compat)', async () => {
const address = await AztecAddress.random();
const selector = FunctionSelector.random();

const allowList = parseAllowList(`I:${address}:${selector}`);
expect(allowList).toEqual([{ address, selector }]);
});

it('rejects entry with unknown flag', async () => {
const address = await AztecAddress.random();
const selector = FunctionSelector.random();
expect(() => parseAllowList(`I:${address}:${selector}:unknown`)).toThrow('unknown flag');
});

it('rejects entry with invalid calldataLength', async () => {
const address = await AztecAddress.random();
const selector = FunctionSelector.random();
expect(() => parseAllowList(`I:${address}:${selector}:cl=abc`)).toThrow('invalid calldataLength');
});

it('rejects instance entry without selector', async () => {
const address = await AztecAddress.random();
expect(() => parseAllowList(`I:${address}`)).toThrow('selector is required');
Expand Down
Loading
Loading