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
39 changes: 24 additions & 15 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
bytes32 _txsEffectsHash,
DataStructures.ExecutionFlags memory _flags
) external view override(IRollup) {
uint256 manaBaseFee = getManaBaseFee(true);
uint256 manaBaseFee = getManaBaseFeeAt(_currentTime, true);
HeaderLib.Header memory header = HeaderLib.decode(_header);
_validateHeader(
header, _signatures, _digest, _currentTime, manaBaseFee, _txsEffectsHash, _flags
Expand Down Expand Up @@ -552,7 +552,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
HeaderLib.Header memory header = HeaderLib.decode(_args.header);

setupEpoch();
ManaBaseFeeComponents memory components = getManaBaseFeeComponents(true);
ManaBaseFeeComponents memory components =
getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true);
uint256 manaBaseFee = FeeMath.summedBaseFee(components);
_validateHeader({
_header: header,
Expand Down Expand Up @@ -657,13 +658,13 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
);
}

/**
* @notice Gets the current l1 fees
*
* @return The current l1 fees
*/
function getCurrentL1Fees() public view override(IRollup) returns (L1FeeData memory) {
Slot slot = getCurrentSlot();
function getL1FeesAt(Timestamp _timestamp)
public
view
override(IRollup)
returns (L1FeeData memory)
{
Slot slot = getSlotAt(_timestamp);
if (slot < l1GasOracleValues.slotOfChange) {
return l1GasOracleValues.pre;
}
Expand All @@ -677,9 +678,13 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
*
* @return The mana base fee
*/
function getManaBaseFee(bool _inFeeAsset) public view override(IRollup) returns (uint256) {
ManaBaseFeeComponents memory components = getManaBaseFeeComponents(_inFeeAsset);
return components.summedBaseFee();
function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset)
public
view
override(IRollup)
returns (uint256)
{
return getManaBaseFeeComponentsAt(_timestamp, _inFeeAsset).summedBaseFee();
}

/**
Expand All @@ -694,18 +699,22 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
*
* @return The mana base fee components
*/
function getManaBaseFeeComponents(bool _inFeeAsset)
function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset)
public
view
override(ITestRollup)
returns (ManaBaseFeeComponents memory)
{
FeeHeader storage parentFeeHeader = blocks[tips.pendingBlockNumber].feeHeader;
// If we can prune, we use the proven block, otherwise the pending block
uint256 blockOfInterest =
canPruneAtTime(_timestamp) ? tips.provenBlockNumber : tips.pendingBlockNumber;

FeeHeader storage parentFeeHeader = blocks[blockOfInterest].feeHeader;
uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd(
-int256(FeeMath.MANA_TARGET)
);

L1FeeData memory fees = getCurrentL1Fees();
L1FeeData memory fees = getL1FeesAt(_timestamp);
uint256 dataCost =
Math.mulDiv(3 * BLOB_GAS_PER_BLOB, fees.blobFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil);
uint256 gasUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION
Expand Down
6 changes: 3 additions & 3 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface ITestRollup {
function setVkTreeRoot(bytes32 _vkTreeRoot) external;
function setProtocolContractTreeRoot(bytes32 _protocolContractTreeRoot) external;
function setAssumeProvenThroughBlockNumber(uint256 _blockNumber) external;
function getManaBaseFeeComponents(bool _inFeeAsset)
function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset)
external
view
returns (ManaBaseFeeComponents memory);
Expand Down Expand Up @@ -120,8 +120,8 @@ interface IRollup {
returns (bytes32);
function getBlock(uint256 _blockNumber) external view returns (BlockLog memory);
function getFeeAssetPrice() external view returns (uint256);
function getManaBaseFee(bool _inFeeAsset) external view returns (uint256);
function getCurrentL1Fees() external view returns (L1FeeData memory);
function getManaBaseFeeAt(Timestamp _timestamp, bool _inFeeAsset) external view returns (uint256);
function getL1FeesAt(Timestamp _timestamp) external view returns (L1FeeData memory);

function archive() external view returns (bytes32);
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ contract RollupTest is DecoderBase, TimeFns {
}

function _updateHeaderBaseFee(bytes memory _header) internal view returns (bytes memory) {
uint256 baseFee = rollup.getManaBaseFee(true);
uint256 baseFee = rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true);
assembly {
mstore(add(_header, add(0x20, 0x0228)), baseFee)
}
Expand Down
101 changes: 97 additions & 4 deletions l1-contracts/test/fees/FeeRollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributo
import {OracleInput} from "@aztec/core/libraries/FeeMath.sol";
import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {FeeMath} from "@aztec/core/libraries/FeeMath.sol";

import {
FeeHeader as FeeHeaderModel,
Expand Down Expand Up @@ -80,6 +81,8 @@ contract FakeCanonical {
contract FeeRollupTest is FeeModelTestPoints, DecoderBase {
using SlotLib for Slot;
using EpochLib for Epoch;
using FeeMath for uint256;
using FeeMath for ManaBaseFeeComponents;
// We need to build a block that we can submit. We will be using some values from
// the empty blocks, but otherwise populate using the fee model test points.

Expand Down Expand Up @@ -171,7 +174,11 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase {
+ point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost
);

assertEq(manaBaseFee, rollup.getManaBaseFee(true), "mana base fee mismatch");
assertEq(
manaBaseFee,
rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true),
"mana base fee mismatch"
);

uint256 manaSpent = point.block_header.mana_spent;

Expand Down Expand Up @@ -212,12 +219,92 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase {
});
}

function test__FeeModelPrune() public {
// Submit a few blocks, then compute what the fees would be with/without a potential prune
// and ensure that they match what happens.
Slot nextSlot = Slot.wrap(1);
for (uint256 i = 0; i < SLOT_DURATION / 12 * 5; i++) {
_loadL1Metadata(i);

if (rollup.getCurrentSlot() == nextSlot) {
TestPoint memory point = points[nextSlot.unwrap() - 1];
Block memory b = getBlock();

rollup.propose(
ProposeArgs({
header: b.header,
archive: b.archive,
blockHash: b.blockHash,
oracleInput: OracleInput({
provingCostModifier: point.oracle_input.proving_cost_modifier,
feeAssetPriceModifier: point.oracle_input.fee_asset_price_modifier
}),
txHashes: b.txHashes
}),
b.signatures,
b.body
);
nextSlot = nextSlot + Slot.wrap(1);
}
}

FeeHeader memory parentFeeHeaderNoPrune =
rollup.getBlock(rollup.getPendingBlockNumber()).feeHeader;
uint256 excessManaNoPrune = (
parentFeeHeaderNoPrune.excessMana + parentFeeHeaderNoPrune.manaUsed
).clampedAdd(-int256(FeeMath.MANA_TARGET));

FeeHeader memory parentFeeHeaderPrune = rollup.getBlock(rollup.getProvenBlockNumber()).feeHeader;
uint256 excessManaPrune = (parentFeeHeaderPrune.excessMana + parentFeeHeaderPrune.manaUsed)
.clampedAdd(-int256(FeeMath.MANA_TARGET));

assertGt(excessManaNoPrune, excessManaPrune, "excess mana should be lower if we prune");

// Find the point in time where we can prune. We can be smarter, but I'm not trying to be smart here
// trying to be foolproof, for I am a fool.
uint256 timeOfPrune = block.timestamp;
while (!rollup.canPruneAtTime(Timestamp.wrap(timeOfPrune))) {
timeOfPrune += SLOT_DURATION;
}

ManaBaseFeeComponents memory componentsPrune =
rollup.getManaBaseFeeComponentsAt(Timestamp.wrap(timeOfPrune), true);

// If we assume that everything is proven, we will see what the fee would be if we did not prune.
rollup.setAssumeProvenThroughBlockNumber(10000);
ManaBaseFeeComponents memory componentsNoPrune =
rollup.getManaBaseFeeComponentsAt(Timestamp.wrap(timeOfPrune), true);

// The congestion multipliers should be different, with the no-prune being higher
// as it is based on the accumulated excess mana.
assertGt(
componentsNoPrune.congestionMultiplier,
componentsPrune.congestionMultiplier,
"congestion multiplier should be higher if we do not prune"
);

assertEq(
componentsPrune.congestionMultiplier,
FeeMath.congestionMultiplier(excessManaPrune),
"congestion multiplier mismatch for prune"
);
assertEq(
componentsNoPrune.congestionMultiplier,
FeeMath.congestionMultiplier(excessManaNoPrune),
"congestion multiplier mismatch for no-prune"
);
}

function test_FeeModelEquivalence() public {
Slot nextSlot = Slot.wrap(1);
Epoch nextEpoch = Epoch.wrap(1);

// Loop through all of the L1 metadata
for (uint256 i = 0; i < l1Metadata.length; i++) {
// Predict what the fee will be before we jump in time!
uint256 baseFeePrediction =
rollup.getManaBaseFeeAt(Timestamp.wrap(l1Metadata[i].timestamp), true);

_loadL1Metadata(i);

// For every "new" slot we encounter, we construct a block using current L1 Data
Expand All @@ -226,11 +313,13 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase {
if (rollup.getCurrentSlot() == nextSlot) {
TestPoint memory point = points[nextSlot.unwrap() - 1];

L1FeeData memory fees = rollup.getCurrentL1Fees();
L1FeeData memory fees = rollup.getL1FeesAt(Timestamp.wrap(block.timestamp));
uint256 feeAssetPrice = rollup.getFeeAssetPrice();

ManaBaseFeeComponents memory components = rollup.getManaBaseFeeComponents(false);
ManaBaseFeeComponents memory componentsFeeAsset = rollup.getManaBaseFeeComponents(true);
ManaBaseFeeComponents memory components =
rollup.getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), false);
ManaBaseFeeComponents memory componentsFeeAsset =
rollup.getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true);
BlockLog memory parentBlockLog = rollup.getBlock(nextSlot.unwrap() - 1);

Block memory b = getBlock();
Expand All @@ -252,6 +341,10 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase {

BlockLog memory blockLog = rollup.getBlock(nextSlot.unwrap());

assertEq(
baseFeePrediction, componentsFeeAsset.summedBaseFee(), "base fee prediction mismatch"
);

assertEq(
componentsFeeAsset.congestionCost,
blockLog.feeHeader.congestionCost,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { readFileSync } from 'fs';
import { getPathToFixture, getTestContractArtifact } from '../tests/fixtures.js';
import { computeArtifactHash } from './artifact_hash.js';

const TEST_CONTRACT_ARTIFACT_HASH = `"0x1d429080e986cf55e59203b4229063bf9b4d875e832fe56c5257303075110190"`;
const TEST_CONTRACT_ARTIFACT_HASH = `"0x19142676527045a118066698e292cc35db16ab4d7bd16610d35d2e1c607eb8b2"`;

describe('ArtifactHash', () => {
it('calculates the artifact hash', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,16 +367,17 @@ describe('L1Publisher integration', () => {

const ts = (await publicClient.getBlock()).timestamp;
const slot = await rollup.read.getSlotAt([ts + BigInt(config.ethereumSlotDuration)]);
const timestamp = await rollup.read.getTimestampForSlot([slot]);

const globalVariables = new GlobalVariables(
new Fr(chainId),
new Fr(config.version),
new Fr(1 + i),
new Fr(slot),
new Fr(await rollup.read.getTimestampForSlot([slot])),
new Fr(timestamp),
coinbase,
feeRecipient,
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))),
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFeeAt([timestamp, true]))),
);

const block = await buildBlock(globalVariables, txs, currentL1ToL2Messages);
Expand Down Expand Up @@ -479,15 +480,16 @@ describe('L1Publisher integration', () => {

const ts = (await publicClient.getBlock()).timestamp;
const slot = await rollup.read.getSlotAt([ts + BigInt(config.ethereumSlotDuration)]);
const timestamp = await rollup.read.getTimestampForSlot([slot]);
const globalVariables = new GlobalVariables(
new Fr(chainId),
new Fr(config.version),
new Fr(1 + i),
new Fr(slot),
new Fr(await rollup.read.getTimestampForSlot([slot])),
new Fr(timestamp),
coinbase,
feeRecipient,
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))),
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFeeAt([timestamp, true]))),
);
const block = await buildBlock(globalVariables, txs, l1ToL2Messages);
prevHeader = block.header;
Expand Down Expand Up @@ -554,15 +556,16 @@ describe('L1Publisher integration', () => {
const txs = [makeEmptyProcessedTx(), makeEmptyProcessedTx()];
const ts = (await publicClient.getBlock()).timestamp;
const slot = await rollup.read.getSlotAt([ts + BigInt(config.ethereumSlotDuration)]);
const timestamp = await rollup.read.getTimestampForSlot([slot]);
const globalVariables = new GlobalVariables(
new Fr(chainId),
new Fr(config.version),
new Fr(1),
new Fr(slot),
new Fr(await rollup.read.getTimestampForSlot([slot])),
new Fr(timestamp),
coinbase,
feeRecipient,
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFee([true]))),
new GasFees(Fr.ZERO, new Fr(await rollup.read.getManaBaseFeeAt([timestamp, true]))),
);
const block = await buildBlock(globalVariables, txs, l1ToL2Messages);
prevHeader = block.header;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,21 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
});
}

/**
* Computes the "current" base fees, e.g., the price that you currently should pay to get include in the next block
* @returns Base fees for the expected next block
*/
public async getCurrentBaseFees(): Promise<GasFees> {
return new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFee([true])));
// Since this might be called in the middle of a slot where a block might have been published,
// we need to fetch the last block written, and estimate the earliest timestamp for the next block.
// The timestamp of that last block will act as a lower bound for the next block.

const lastBlock = await this.rollupContract.read.getBlock([await this.rollupContract.read.getPendingBlockNumber()]);
const earliestTimestamp = await this.rollupContract.read.getTimestampForSlot([lastBlock.slotNumber + 1n]);
const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;

return new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFeeAt([timestamp, true])));
}

/**
Expand Down Expand Up @@ -77,7 +90,8 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
const slotFr = new Fr(slotNumber);
const timestampFr = new Fr(timestamp);

const gasFees = await this.getCurrentBaseFees();
// We can skip much of the logic in getCurrentBaseFees since it we already check that we are not within a slot elsewhere.
const gasFees = new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFeeAt([timestamp, true])));

const globalVariables = new GlobalVariables(
chainId,
Expand Down