Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ describe('NodePublicCallsSimulator', () => {
});

function makeFeeHeader(): FeeHeader {
return { excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: 0n, congestionCost: 0n, proverCost: 0n };
return { excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: 0n, protocolFee: 0n, proverCost: 0n };
}

function makeProposedCheckpointData(args: {
Expand Down
9 changes: 5 additions & 4 deletions yarn-project/end-to-end/src/single-node/fees/fees_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,14 @@ export class FeesTest extends SingleNodeTestContext {
return feeHeader.manaUsed * feeHeader.proverCost;
};

// RewardLib computes sequencerFee = checkpointFee - burn - proverFee where burn = manaUsed * congestionCost.
// The fixture's typical case keeps congestionCost at zero, but reading it explicitly avoids latent bugs
// when test load changes excess mana.
// RewardLib computes sequencerFee = checkpointFee - protocolFee - proverFee where
// protocolFee = manaUsed * feeHeader.protocolFee. The fixture's typical case keeps the
// protocol fee at zero, but reading it explicitly avoids latent bugs when test load changes
// excess mana.
this.getCommittedBurn = async (blockNumber: BlockNumber) => {
const block = await this.aztecNode.getBlock(blockNumber);
const feeHeader = await this.rollupContract.getFeeHeader(BigInt(block!.checkpointNumber));
return feeHeader.manaUsed * feeHeader.congestionCost;
return feeHeader.manaUsed * feeHeader.protocolFee;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('Uniswap Price Oracle', () => {
expect(modifier).toBe(49n);

const child = RollupContract.computeChildFeeHeader(
{ excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: predictedParentE12, congestionCost: 0n, proverCost: 0n },
{ excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: predictedParentE12, protocolFee: 0n, proverCost: 0n },
0n,
modifier,
100n,
Expand Down
28 changes: 14 additions & 14 deletions yarn-project/ethereum/src/contracts/rollup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { type FeeHeader, RollupContract, TempCheckpointLogField } from './rollup
describe('compressFeeHeader', () => {
/** Creates a zero fee header with the given overrides. */
function makeFeeHeader(overrides: Partial<FeeHeader> = {}): FeeHeader {
return { manaUsed: 0n, excessMana: 0n, ethPerFeeAsset: 0n, congestionCost: 0n, proverCost: 0n, ...overrides };
return { manaUsed: 0n, excessMana: 0n, ethPerFeeAsset: 0n, protocolFee: 0n, proverCost: 0n, ...overrides };
}

it('sets the preheat flag (bit 255)', () => {
Expand Down Expand Up @@ -62,15 +62,15 @@ describe('compressFeeHeader', () => {
expect((result >> 80n) & ((1n << 48n) - 1n)).toBe(999n);
});

it('packs congestionCost into bits [128:191]', () => {
const header = makeFeeHeader({ congestionCost: 42n });
it('packs protocolFee into bits [128:191]', () => {
const header = makeFeeHeader({ protocolFee: 42n });
const result = RollupContract.compressFeeHeader(header);
expect((result >> 128n) & ((1n << 64n) - 1n)).toBe(42n);
});

it('clamps congestionCost to 64 bits', () => {
it('clamps protocolFee to 64 bits', () => {
const maxValue = (1n << 64n) - 1n;
const header = makeFeeHeader({ congestionCost: maxValue + 1n });
const header = makeFeeHeader({ protocolFee: maxValue + 1n });
const result = RollupContract.compressFeeHeader(header);
expect((result >> 128n) & maxValue).toBe(maxValue);
});
Expand All @@ -93,7 +93,7 @@ describe('compressFeeHeader', () => {
manaUsed: 1000n,
excessMana: 2000n,
ethPerFeeAsset: 3000n,
congestionCost: 4000n,
protocolFee: 4000n,
proverCost: 5000n,
});
const result = RollupContract.compressFeeHeader(header);
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('computeChildFeeHeader', () => {
manaUsed: 5000n,
excessMana: 3000n,
ethPerFeeAsset: 1000n,
congestionCost: 100n,
protocolFee: 100n,
proverCost: 200n,
};

Expand All @@ -144,9 +144,9 @@ describe('computeChildFeeHeader', () => {
expect(result.manaUsed).toBe(7777n);
});

it('always sets congestionCost and proverCost to zero', () => {
it('always sets protocolFee and proverCost to zero', () => {
const result = RollupContract.computeChildFeeHeader(baseFeeHeader, 0n, 0n, manaTarget);
expect(result.congestionCost).toBe(0n);
expect(result.protocolFee).toBe(0n);
expect(result.proverCost).toBe(0n);
});

Expand Down Expand Up @@ -217,14 +217,14 @@ describe('computeChildFeeHeader', () => {
manaUsed: 8000n,
excessMana: 15000n,
ethPerFeeAsset: 5000n,
congestionCost: 999n,
protocolFee: 999n,
proverCost: 888n,
};
const result = RollupContract.computeChildFeeHeader(parent, 42n, 250n, manaTarget);
expect(result.excessMana).toBe(13000n);
expect(result.manaUsed).toBe(42n);
expect(result.ethPerFeeAsset).toBe(5125n);
expect(result.congestionCost).toBe(0n);
expect(result.protocolFee).toBe(0n);
expect(result.proverCost).toBe(0n);
});
});
Expand Down Expand Up @@ -425,7 +425,7 @@ describe('Rollup', () => {
manaUsed: 12345n,
excessMana: 67890n,
ethPerFeeAsset: 1_000_000_000_000n,
congestionCost: 99999n,
protocolFee: 99999n,
proverCost: 55555n,
} as FeeHeader,
};
Expand Down Expand Up @@ -561,7 +561,7 @@ describe('Rollup', () => {
manaUsed: 12345n,
excessMana: 67890n,
ethPerFeeAsset: 1_000_000_000_000n,
congestionCost: 99999n,
protocolFee: 99999n,
proverCost: 55555n,
};

Expand All @@ -580,7 +580,7 @@ describe('Rollup', () => {
expect(result.manaUsed).toBe(feeHeader.manaUsed);
expect(result.excessMana).toBe(feeHeader.excessMana);
expect(result.ethPerFeeAsset).toBe(feeHeader.ethPerFeeAsset);
expect(result.congestionCost).toBe(feeHeader.congestionCost);
expect(result.protocolFee).toBe(feeHeader.protocolFee);
expect(result.proverCost).toBe(feeHeader.proverCost);
});
});
Expand Down
24 changes: 17 additions & 7 deletions yarn-project/ethereum/src/contracts/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export type FeeHeader = {
excessMana: bigint;
manaUsed: bigint;
ethPerFeeAsset: bigint;
congestionCost: bigint;
protocolFee: bigint;
proverCost: bigint;
};

Expand Down Expand Up @@ -177,7 +177,7 @@ export type TempCheckpointLogOverrideFields = {
export type ManaMinFeeComponents = {
sequencerCost: bigint;
proverCost: bigint;
congestionCost: bigint;
protocolFee: bigint;
congestionMultiplier: bigint;
};

Expand Down Expand Up @@ -432,6 +432,16 @@ export class RollupContract {
return this.rollup.read.getProvingCostPerManaInFeeAsset();
}

/** Returns the current protocol fee margin in basis points. Not memoized: governance can change it. */
getProtocolFeeMargin(): Promise<number> {
return this.rollup.read.getProtocolFeeMargin();
}

/** Returns the current recipient of the protocol fee tranche. Not memoized: governance can change it. */
async getProtocolFeeRecipient(): Promise<EthAddress> {
return EthAddress.fromString(await this.rollup.read.getProtocolFeeRecipient());
}

@memoize
getManaLimit(): Promise<bigint> {
return this.rollup.read.getManaLimit();
Expand Down Expand Up @@ -636,7 +646,7 @@ export class RollupContract {
excessMana: result.excessMana,
manaUsed: result.manaUsed,
ethPerFeeAsset: result.ethPerFeeAsset,
congestionCost: result.congestionCost,
protocolFee: result.protocolFee,
proverCost: result.proverCost,
};
}
Expand Down Expand Up @@ -729,7 +739,7 @@ export class RollupContract {
excessMana: result.feeHeader.excessMana,
manaUsed: result.feeHeader.manaUsed,
ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
congestionCost: result.feeHeader.congestionCost,
protocolFee: result.feeHeader.protocolFee,
proverCost: result.feeHeader.proverCost,
},
};
Expand Down Expand Up @@ -1079,7 +1089,7 @@ export class RollupContract {
let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
value |= (feeHeader.protocolFee < MASK_64_BITS ? feeHeader.protocolFee : MASK_64_BITS) << 128n; // bits [128:191]
value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
value |= 1n << 255n; // preheat flag
return value;
Expand Down Expand Up @@ -1121,7 +1131,7 @@ export class RollupContract {
excessMana,
manaUsed: childManaUsed,
ethPerFeeAsset: newPrice,
congestionCost: 0n,
protocolFee: 0n,
proverCost: 0n,
};
}
Expand Down Expand Up @@ -1183,7 +1193,7 @@ export class RollupContract {
return {
sequencerCost: result.sequencerCost,
proverCost: result.proverCost,
congestionCost: result.congestionCost,
protocolFee: result.protocolFee,
congestionMultiplier: result.congestionMultiplier,
};
}
Expand Down
4 changes: 2 additions & 2 deletions yarn-project/ethereum/src/test/chain_monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { ViemClient } from '../types.js';

/** L2 fee data reported by the chain monitor. */
export type L2FeeData = ManaMinFeeComponents & {
/** Total minimum fee per mana in Fee Juice (sum of sequencerCost + proverCost + congestionCost). */
/** Total minimum fee per mana in Fee Juice (sum of sequencerCost + proverCost + protocolFee). */
minFeePerMana: bigint;
/** L1 base fee observed by the oracle. */
l1BaseFee: bigint;
Expand Down Expand Up @@ -381,7 +381,7 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
return (
this.l2FeeData.sequencerCost !== newData.sequencerCost ||
this.l2FeeData.proverCost !== newData.proverCost ||
this.l2FeeData.congestionCost !== newData.congestionCost ||
this.l2FeeData.protocolFee !== newData.protocolFee ||
this.l2FeeData.l1BaseFee !== newData.l1BaseFee ||
this.l2FeeData.l1BlobFee !== newData.l1BlobFee ||
this.l2FeeData.ethPerFeeAsset !== newData.ethPerFeeAsset
Expand Down
27 changes: 26 additions & 1 deletion yarn-project/ethereum/src/test/rollup_cheat_codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,30 @@ export class RollupCheatCodes {
});
}

/**
* Sets the protocol fee margin (in basis points). Throws if the on-chain tx reverts
* (e.g. rate-limit cooldown or step cap) instead of silently succeeding.
* @param bps - The new protocol fee margin in basis points
*/
public async setProtocolFeeMargin(bps: number) {
await this.asOwner(async (account, rollup) => {
const hash = await rollup.write.setProtocolFeeMargin([bps], {
account,
chain: this.client.chain,
gasLimit: 1000000n,
});
const receipt = await this.client.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') {
throw new Error(
`setProtocolFeeMargin(${bps}) reverted on L1 (tx ${hash}). ` +
`Likely FeeLib rate-limit (30-day cooldown or x3/2 step cap on the fee multiplier); ` +
`use clearProvingCostCooldown() between successive updates (it clears both cooldowns).`,
);
}
this.logger.warn(`Updated protocol fee margin to ${bps} bps`);
});
}

/**
* Resets the 30-day proving-cost update cooldown enforced by FeeLib.updateProvingCostPerMana
* by zeroing `FeeStore.provingCostLastUpdate` directly in contract storage. Use between
Expand All @@ -384,7 +408,8 @@ export class RollupCheatCodes {
* l1-contracts/src/core/libraries/rollup/FeeLib.sol:
* slot + 0: CompressedFeeConfig config (uint256)
* slot + 1: L1GasOracleValues l1GasOracleValues (14+14+4 bytes, packed)
* slot + 2: uint64 provingCostLastUpdate (only member — zeroing the slot is safe)
* slot + 2: uint64 provingCostLastUpdate + uint64 protocolMarginLastUpdate (packed --
* zeroing the slot clears BOTH the proving-cost and protocol-fee-margin cooldowns)
* If the struct layout changes, update the offset below.
*/
public async clearProvingCostCooldown() {
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/prover-node/src/prover-node-publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe('prover-node-publisher', () => {
excessMana: 0n, // unused
manaUsed: 0n, // unused
ethPerFeeAsset: 0n, // unused
congestionCost: 0n, // unused
protocolFee: 0n, // unused
proverCost: 0n, // unused
},
}),
Expand Down Expand Up @@ -244,7 +244,7 @@ describe('prover-node-publisher', () => {
blobCommitmentsHash: Buffer32.ZERO,
outHash: '0x',
slotNumber: SlotNumber(0),
feeHeader: { excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: 0n, congestionCost: 0n, proverCost: 0n },
feeHeader: { excessMana: 0n, manaUsed: 0n, ethPerFeeAsset: 0n, protocolFee: 0n, proverCost: 0n },
}),
);

Expand Down Expand Up @@ -311,7 +311,7 @@ describe('prover-node-publisher', () => {
excessMana: 0n, // unused
manaUsed: 0n, // unused
ethPerFeeAsset: 0n, // unused
congestionCost: 0n, // unused
protocolFee: 0n, // unused
proverCost: 0n, // unused
},
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ describe('FeePredictor', () => {
excessMana: newExcessMana,
manaUsed: assumedManaUsed,
ethPerFeeAsset: decayEthPerFeeAsset(currentFeeHeader.ethPerFeeAsset, i + 1),
congestionCost: 0n,
protocolFee: 0n,
proverCost: 0n,
};

Expand Down Expand Up @@ -341,7 +341,7 @@ describe('FeePredictor', () => {
excessMana: newExcessMana,
manaUsed: 0n,
ethPerFeeAsset: decayedEthPerFeeAsset,
congestionCost: 0n,
protocolFee: 0n,
proverCost: 0n,
};

Expand All @@ -359,6 +359,33 @@ describe('FeePredictor', () => {
nextCheckpointOffset++;
}
}, 60_000);

it('slot 0 matches L1 getManaMinFeeAt with a nonzero protocol fee margin', async () => {
// Pin the comparison timestamp so before/after fees differ only by the margin.
const startSlot = await getPredictionStartSlot();
const timestamp = getTimestamp(startSlot + 1n);
const feeBefore = await rollup.getManaMinFeeAt(timestamp, true);

// The first-ever margin update bypasses the 30-day cooldown; from 0 the x3/2 step cap on the
// fee multiplier permits up to 5000 bps.
await rollupCheatCodes.setProtocolFeeMargin(5000);
expect(await rollup.getProtocolFeeMargin()).toBe(5000);

// The margin must move the pinned fee. If L1 scaled both the fakeExponential factor and the
// mulDiv divisor, mu would cancel silently and this catches it.
const l1Fee = await rollup.getManaMinFeeAt(timestamp, true);
expect(l1Fee).toBeGreaterThan(feeBefore);

// The predictor must agree with L1 exactly at mu != 0. This is the only check that catches
// the same factor+divisor double-scaling on the TS side of the mirror.
for (const manaUsage of Object.values(ManaUsageEstimate)) {
const predictor = new FeePredictor(rollup, dateProvider, feePredictorConfig);
const predicted = await predictor.getPredictedMinFees(manaUsage);
const predictionStartSlot = await getPredictionStartSlot();
const l1FeeAtStart = await rollup.getManaMinFeeAt(getTimestamp(predictionStartSlot), true);
expect(predicted[0].feePerL2Gas).toBe(l1FeeAtStart);
}
}, 60_000);
});

describe('FeePredictor state caching', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type FeeOracleState = {
manaLimit: bigint;
provingCostPerManaEth: bigint;
epochDuration: bigint;
protocolFeeMarginBps: bigint;
/** Pre-resolved L1 fees for each slot in the prediction window. */
l1FeesBySlot: L1FeeData[];
};
Expand Down Expand Up @@ -75,11 +76,12 @@ export class FeePredictor {
const opts = { blockNumber };

// Cached constants don't need pinning
const [manaTarget, manaLimit, provingCostPerManaEth, epochDuration] = await Promise.all([
const [manaTarget, manaLimit, provingCostPerManaEth, epochDuration, protocolFeeMarginBps] = await Promise.all([
this.rollupContract.getManaTarget(),
this.rollupContract.getManaLimit(),
this.rollupContract.getProvingCostPerMana(),
this.rollupContract.getEpochDuration(),
this.rollupContract.getProtocolFeeMargin(),
]);

// First, compute the earliest possible nextSlot independently of the checkpoint, so we can
Expand Down Expand Up @@ -115,6 +117,7 @@ export class FeePredictor {
manaLimit,
provingCostPerManaEth,
epochDuration: BigInt(epochDuration),
protocolFeeMarginBps: BigInt(protocolFeeMarginBps),
l1FeesBySlot,
};
}
Expand Down Expand Up @@ -170,6 +173,7 @@ export class FeePredictor {
provingCostPerManaEth: state.provingCostPerManaEth,
excessMana,
ethPerFeeAsset,
protocolFeeMarginBps: state.protocolFeeMarginBps,
}),
);
}
Expand Down
Loading
Loading