Skip to content

Commit 3417b22

Browse files
authored
feat: integrate base fee computation into rollup (#10076)
1 parent f0f8d22 commit 3417b22

File tree

43 files changed

+886
-137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+886
-137
lines changed

l1-contracts/src/core/Rollup.sol

+219-23
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ pragma solidity >=0.8.27;
44

55
import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol";
66
import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol";
7-
import {IRollup, ITestRollup} from "@aztec/core/interfaces/IRollup.sol";
7+
import {
8+
IRollup,
9+
ITestRollup,
10+
FeeHeader,
11+
ManaBaseFeeComponents,
12+
BlockLog,
13+
L1FeeData
14+
} from "@aztec/core/interfaces/IRollup.sol";
815
import {IVerifier} from "@aztec/core/interfaces/IVerifier.sol";
916
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
1017
import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";
@@ -15,6 +22,7 @@ import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol";
1522
import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
1623
import {EpochProofQuoteLib} from "@aztec/core/libraries/EpochProofQuoteLib.sol";
1724
import {Errors} from "@aztec/core/libraries/Errors.sol";
25+
import {FeeMath} from "@aztec/core/libraries/FeeMath.sol";
1826
import {HeaderLib} from "@aztec/core/libraries/HeaderLib.sol";
1927
import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/ProposeLib.sol";
2028
import {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol";
@@ -29,6 +37,19 @@ import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol";
2937
import {EIP712} from "@oz/utils/cryptography/EIP712.sol";
3038
import {Math} from "@oz/utils/math/Math.sol";
3139
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
40+
import {Vm} from "forge-std/Vm.sol";
41+
42+
struct ChainTips {
43+
uint256 pendingBlockNumber;
44+
uint256 provenBlockNumber;
45+
}
46+
47+
struct Config {
48+
uint256 aztecSlotDuration;
49+
uint256 aztecEpochDuration;
50+
uint256 targetCommitteeSize;
51+
uint256 aztecEpochProofClaimWindowInL2Slots;
52+
}
3253

3354
/**
3455
* @title Rollup
@@ -42,29 +63,27 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
4263
using EpochLib for Epoch;
4364
using SafeERC20 for IERC20;
4465
using ProposeLib for ProposeArgs;
66+
using FeeMath for uint256;
4567

46-
struct ChainTips {
47-
uint256 pendingBlockNumber;
48-
uint256 provenBlockNumber;
68+
struct L1GasOracleValues {
69+
L1FeeData pre;
70+
L1FeeData post;
71+
Slot slotOfChange;
4972
}
5073

51-
struct BlockLog {
52-
bytes32 archive;
53-
bytes32 blockHash;
54-
Slot slotNumber;
55-
}
74+
uint256 internal constant BLOB_GAS_PER_BLOB = 2 ** 17;
75+
uint256 internal constant GAS_PER_BLOB_POINT_EVALUATION = 50_000;
5676

57-
struct Config {
58-
uint256 aztecSlotDuration;
59-
uint256 aztecEpochDuration;
60-
uint256 targetCommitteeSize;
61-
uint256 aztecEpochProofClaimWindowInL2Slots;
62-
}
77+
Slot public constant LIFETIME = Slot.wrap(5);
78+
Slot public constant LAG = Slot.wrap(2);
6379

6480
// See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb
6581
// for justification of CLAIM_DURATION_IN_L2_SLOTS.
6682
uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000;
6783

84+
address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
85+
bool public immutable IS_FOUNDRY_TEST;
86+
6887
uint256 public immutable CLAIM_DURATION_IN_L2_SLOTS;
6988
uint256 public immutable L1_BLOCK_AT_GENESIS;
7089
IInbox public immutable INBOX;
@@ -85,7 +104,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
85104
// e.g., changing any values in the block or header should in the end make its way to the archive
86105
//
87106
// More direct approach would be storing keccak256(header) as well
88-
mapping(uint256 blockNumber => BlockLog log) public blocks;
107+
mapping(uint256 blockNumber => BlockLog log) internal blocks;
89108

90109
bytes32 public vkTreeRoot;
91110
bytes32 public protocolContractTreeRoot;
@@ -94,6 +113,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
94113
// Testing only. This should be removed eventually.
95114
uint256 private assumeProvenThroughBlockNumber;
96115

116+
L1GasOracleValues public l1GasOracleValues;
117+
97118
constructor(
98119
IFeeJuicePortal _fpcJuicePortal,
99120
IRewardDistributor _rewardDistributor,
@@ -125,12 +146,25 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
125146
L1_BLOCK_AT_GENESIS = block.number;
126147
CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots;
127148

149+
IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0;
150+
128151
// Genesis block
129152
blocks[0] = BlockLog({
153+
feeHeader: FeeHeader({
154+
excessMana: 0,
155+
feeAssetPriceNumerator: 0,
156+
manaUsed: 0,
157+
provingCostPerManaNumerator: 0
158+
}),
130159
archive: bytes32(Constants.GENESIS_ARCHIVE_ROOT),
131160
blockHash: bytes32(0), // TODO(palla/prover): The first block does not have hash zero
132161
slotNumber: Slot.wrap(0)
133162
});
163+
l1GasOracleValues = L1GasOracleValues({
164+
pre: L1FeeData({baseFee: 1 gwei, blobFee: 1}),
165+
post: L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()}),
166+
slotOfChange: LIFETIME
167+
});
134168
for (uint256 i = 0; i < _validators.length; i++) {
135169
_addValidator(_validators[i]);
136170
}
@@ -398,8 +432,11 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
398432
bytes32 _txsEffectsHash,
399433
DataStructures.ExecutionFlags memory _flags
400434
) external view override(IRollup) {
435+
uint256 manaBaseFee = getManaBaseFee(true);
401436
HeaderLib.Header memory header = HeaderLib.decode(_header);
402-
_validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags);
437+
_validateHeader(
438+
header, _signatures, _digest, _currentTime, manaBaseFee, _txsEffectsHash, _flags
439+
);
403440
}
404441

405442
/**
@@ -473,6 +510,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
473510
if (canPrune()) {
474511
_prune();
475512
}
513+
updateL1GasFeeOracle();
514+
476515
// The `body` is passed outside the "args" as it does not directly need to be in the digest
477516
// as long as the `txsEffectsHash` is included and matches what is in the header.
478517
// Which we are checking in the `_validateHeader` call below.
@@ -483,22 +522,41 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
483522

484523
bytes32 digest = _args.digest();
485524
setupEpoch();
525+
uint256 manaBaseFee = getManaBaseFee(true);
486526
_validateHeader({
487527
_header: header,
488528
_signatures: _signatures,
489529
_digest: digest,
490530
_currentTime: Timestamp.wrap(block.timestamp),
531+
_manaBaseFee: manaBaseFee,
491532
_txEffectsHash: txsEffectsHash,
492533
_flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false})
493534
});
494535

495536
uint256 blockNumber = ++tips.pendingBlockNumber;
496537

497-
blocks[blockNumber] = BlockLog({
498-
archive: _args.archive,
499-
blockHash: _args.blockHash,
500-
slotNumber: Slot.wrap(header.globalVariables.slotNumber)
501-
});
538+
{
539+
FeeHeader memory parentFeeHeader = blocks[blockNumber - 1].feeHeader;
540+
uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd(
541+
-int256(FeeMath.MANA_TARGET)
542+
);
543+
544+
blocks[blockNumber] = BlockLog({
545+
archive: _args.archive,
546+
blockHash: _args.blockHash,
547+
slotNumber: Slot.wrap(header.globalVariables.slotNumber),
548+
feeHeader: FeeHeader({
549+
excessMana: excessMana,
550+
feeAssetPriceNumerator: parentFeeHeader.feeAssetPriceNumerator.clampedAdd(
551+
_args.oracleInput.feeAssetPriceModifier
552+
),
553+
manaUsed: header.totalManaUsed,
554+
provingCostPerManaNumerator: parentFeeHeader.provingCostPerManaNumerator.clampedAdd(
555+
_args.oracleInput.provingCostModifier
556+
)
557+
})
558+
});
559+
}
502560

503561
// @note The block number here will always be >=1 as the genesis block is at 0
504562
bytes32 inHash = INBOX.consume(blockNumber);
@@ -536,6 +594,113 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
536594
}
537595
}
538596

597+
/**
598+
* @notice Updates the l1 gas fee oracle
599+
* @dev This function is called by the `propose` function
600+
*/
601+
function updateL1GasFeeOracle() public override(IRollup) {
602+
Slot slot = getCurrentSlot();
603+
// The slot where we find a new queued value acceptable
604+
Slot acceptableSlot = l1GasOracleValues.slotOfChange + (LIFETIME - LAG);
605+
606+
if (slot < acceptableSlot) {
607+
return;
608+
}
609+
610+
l1GasOracleValues.pre = l1GasOracleValues.post;
611+
l1GasOracleValues.post = L1FeeData({baseFee: block.basefee, blobFee: _getBlobBaseFee()});
612+
l1GasOracleValues.slotOfChange = slot + LAG;
613+
}
614+
615+
/**
616+
* @notice Gets the fee asset price as fee_asset / eth with 1e9 precision
617+
*
618+
* @return The fee asset price
619+
*/
620+
function getFeeAssetPrice() public view override(IRollup) returns (uint256) {
621+
return FeeMath.feeAssetPriceModifier(
622+
blocks[tips.pendingBlockNumber].feeHeader.feeAssetPriceNumerator
623+
);
624+
}
625+
626+
/**
627+
* @notice Gets the current l1 fees
628+
*
629+
* @return The current l1 fees
630+
*/
631+
function getCurrentL1Fees() public view override(IRollup) returns (L1FeeData memory) {
632+
Slot slot = getCurrentSlot();
633+
if (slot < l1GasOracleValues.slotOfChange) {
634+
return l1GasOracleValues.pre;
635+
}
636+
return l1GasOracleValues.post;
637+
}
638+
639+
/**
640+
* @notice Gets the mana base fee
641+
*
642+
* @param _inFeeAsset - Whether to return the fee in the fee asset or ETH
643+
*
644+
* @return The mana base fee
645+
*/
646+
function getManaBaseFee(bool _inFeeAsset) public view override(IRollup) returns (uint256) {
647+
ManaBaseFeeComponents memory components = manaBaseFeeComponents(_inFeeAsset);
648+
return
649+
components.dataCost + components.gasCost + components.provingCost + components.congestionCost;
650+
}
651+
652+
/**
653+
* @notice Gets the mana base fee components
654+
* For more context, consult:
655+
* https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md
656+
*
657+
* @dev TODO #10004 - As part of the refactor, will likely get rid of this function or make it private
658+
* keeping it public for now makes it simpler to test.
659+
*
660+
* @param _inFeeAsset - Whether to return the fee in the fee asset or ETH
661+
*
662+
* @return The mana base fee components
663+
*/
664+
function manaBaseFeeComponents(bool _inFeeAsset)
665+
public
666+
view
667+
override(ITestRollup)
668+
returns (ManaBaseFeeComponents memory)
669+
{
670+
FeeHeader storage parentFeeHeader = blocks[tips.pendingBlockNumber].feeHeader;
671+
uint256 excessMana = (parentFeeHeader.excessMana + parentFeeHeader.manaUsed).clampedAdd(
672+
-int256(FeeMath.MANA_TARGET)
673+
);
674+
675+
L1FeeData memory fees = getCurrentL1Fees();
676+
uint256 dataCost =
677+
Math.mulDiv(3 * BLOB_GAS_PER_BLOB, fees.blobFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil);
678+
uint256 gasUsed = FeeMath.L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION
679+
+ FeeMath.L1_GAS_PER_EPOCH_VERIFIED / EPOCH_DURATION;
680+
uint256 gasCost = Math.mulDiv(gasUsed, fees.baseFee, FeeMath.MANA_TARGET, Math.Rounding.Ceil);
681+
uint256 provingCost = FeeMath.provingCostPerMana(
682+
blocks[tips.pendingBlockNumber].feeHeader.provingCostPerManaNumerator
683+
);
684+
685+
uint256 congestionMultiplier = FeeMath.congestionMultiplier(excessMana);
686+
uint256 total = dataCost + gasCost + provingCost;
687+
uint256 congestionCost = Math.mulDiv(
688+
total, congestionMultiplier, FeeMath.MINIMUM_CONGESTION_MULTIPLIER, Math.Rounding.Floor
689+
) - total;
690+
691+
uint256 feeAssetPrice = _inFeeAsset ? getFeeAssetPrice() : 1e9;
692+
693+
// @todo @lherskind. The following is a crime against humanity, but it makes it
694+
// very neat to plot etc from python, #10004 will fix it across the board
695+
return ManaBaseFeeComponents({
696+
dataCost: Math.mulDiv(dataCost, feeAssetPrice, 1e9, Math.Rounding.Ceil),
697+
gasCost: Math.mulDiv(gasCost, feeAssetPrice, 1e9, Math.Rounding.Ceil),
698+
provingCost: Math.mulDiv(provingCost, feeAssetPrice, 1e9, Math.Rounding.Ceil),
699+
congestionCost: Math.mulDiv(congestionCost, feeAssetPrice, 1e9, Math.Rounding.Ceil),
700+
congestionMultiplier: congestionMultiplier
701+
});
702+
}
703+
539704
function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory _quote)
540705
public
541706
view
@@ -757,6 +922,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
757922
return tips.pendingBlockNumber;
758923
}
759924

925+
function getBlock(uint256 _blockNumber) public view override(IRollup) returns (BlockLog memory) {
926+
require(
927+
_blockNumber <= tips.pendingBlockNumber,
928+
Errors.Rollup__InvalidBlockNumber(tips.pendingBlockNumber, _blockNumber)
929+
);
930+
return blocks[_blockNumber];
931+
}
932+
760933
function getEpochForBlock(uint256 _blockNumber) public view override(IRollup) returns (Epoch) {
761934
require(
762935
_blockNumber <= tips.pendingBlockNumber,
@@ -856,13 +1029,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
8561029
SignatureLib.Signature[] memory _signatures,
8571030
bytes32 _digest,
8581031
Timestamp _currentTime,
1032+
uint256 _manaBaseFee,
8591033
bytes32 _txEffectsHash,
8601034
DataStructures.ExecutionFlags memory _flags
8611035
) internal view {
8621036
uint256 pendingBlockNumber =
8631037
canPruneAtTime(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber;
8641038
_validateHeaderForSubmissionBase(
865-
_header, _currentTime, _txEffectsHash, pendingBlockNumber, _flags
1039+
_header, _currentTime, _manaBaseFee, _txEffectsHash, pendingBlockNumber, _flags
8661040
);
8671041
_validateHeaderForSubmissionSequencerSelection(
8681042
Slot.wrap(_header.globalVariables.slotNumber), _signatures, _digest, _currentTime, _flags
@@ -928,6 +1102,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
9281102
function _validateHeaderForSubmissionBase(
9291103
HeaderLib.Header memory _header,
9301104
Timestamp _currentTime,
1105+
uint256 _manaBaseFee,
9311106
bytes32 _txsEffectsHash,
9321107
uint256 _pendingBlockNumber,
9331108
DataStructures.ExecutionFlags memory _flags
@@ -983,6 +1158,12 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
9831158
if (address(this) != FEE_JUICE_PORTAL.canonicalRollup()) {
9841159
require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee());
9851160
require(_header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee());
1161+
} else {
1162+
require(_header.globalVariables.gasFees.feePerDaGas == 0, Errors.Rollup__NonZeroDaFee());
1163+
require(
1164+
_header.globalVariables.gasFees.feePerL2Gas == _manaBaseFee,
1165+
Errors.Rollup__InvalidManaBaseFee(_manaBaseFee, _header.globalVariables.gasFees.feePerL2Gas)
1166+
);
9861167
}
9871168
}
9881169

@@ -1004,4 +1185,19 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
10041185
}
10051186
}
10061187
}
1188+
1189+
/**
1190+
* @notice Get the blob base fee
1191+
*
1192+
* @dev If we are in a foundry test, we use the cheatcode to get the blob base fee.
1193+
* Otherwise, we use the `block.blobbasefee`
1194+
*
1195+
* @return uint256 - The blob base fee
1196+
*/
1197+
function _getBlobBaseFee() private view returns (uint256) {
1198+
if (IS_FOUNDRY_TEST) {
1199+
return Vm(VM_ADDRESS).getBlobBaseFee();
1200+
}
1201+
return block.blobbasefee;
1202+
}
10071203
}

0 commit comments

Comments
 (0)