refactor(l1): hold the rollup config in immutables - #25456
Conversation
The deployment-time rollup configuration -- VK tree root, protocol contracts hash, version, fee asset, fee asset portal, epoch proof verifier, Inbox and Outbox -- never changes after construction, so it moves out of `RollupStore.config` storage into `Rollup` immutables. A new `_getRollupConfig` assembles a `RollupConfig` memory struct and threads it down into the libraries, which cannot read a contract's immutables themselves. The config getters route through that one accessor rather than reading their immutable directly: each direct read inlines a 32-byte push into `Rollup`'s runtime code, and `Rollup` sits close to the EIP-170 limit, so sharing one assembly across all of them is about 95 bytes cheaper. `ValidateHeaderArgs` is likewise assembled inside `RollupOperationsExtLib` instead of in the Rollup, which saves several hundred more bytes of runtime code. Immutables must be assigned directly in the constructor body, so the store setup can no longer be factored out into `_initializeStore`; `STFLib.initialize` now writes only the genesis archive root. `Rollup` links against `EpochProofExtLib`, which the generated artifact list was missing. Propose drops about 1.9k gas and an epoch proof submission about 10.8k.
|
Closing as redundant: this landed on The rest of the stack has been rebased directly onto The one piece of this PR not on Branch left in place. |
Pure L1 refactor, no fast-inbox semantics. It moves the rollup's deployment-time configuration out of storage and into immutables to buy back runtime bytecode and gas.
The problem
RollupStore.configheld eight values that are fixed at construction: the VK tree root, the protocol contracts hash, the version, the fee asset, the fee asset portal, the epoch proof verifier, the Inbox and the Outbox. Every read of any of them cost a cold or warmSLOAD, on paths that run on every proposal and every epoch proof.The change
Those eight values become
Rollupimmutables, assigned directly in the constructor body. A new_getRollupConfig()assembles them into aRollupConfigmemory struct, which is threaded down intoEpochProofLib,EpochProofExtLib,RewardLib,RewardExtLiband (as the narrowerProposeConfig { IInbox inbox; bool checkBlob; })ProposeLib. The libraries need the struct because a library cannot read the calling contract's immutables.The config getters on
Rollupalso go through_getRollupConfig()rather than reading their own immutable directly. That looks backwards, and is deliberate: each direct immutable read inlines a 32-byte push intoRollup's runtime code, andRollupsits close to the EIP-170 limit, so sharing one assembly across all of them is about 95 bytes cheaper. In the same spirit,ValidateHeaderArgsis now built insideRollupOperationsExtLibinstead of in the Rollup — building a struct that embeds a fullProposedHeaderin the Rollup's own code costs several hundred bytes it cannot spare — andcheckHeaderWithAttestations's parameters becomecalldata.Because immutables can only be assigned in the constructor body, the store setup can no longer live in a helper:
_initializeStoreis gone andSTFLib.initializenow writes only the genesis archive root.Two follow-on fixes ride along, both consequences of the refactor rather than independent changes.
Rollup's link references listEpochProofExtLib, which the generated artifact list ingenerate-artifacts.shwas missing; and on the TypeScript side (in thelabs-patchesqueue, since the node lives in thelabssubmodule now)getVkTreeRoot()andgetProtocolContractsHash()stop reading raw storage atstfStorageSlot + 3and+ 4— those slots no longer hold the config — and call the contract getters instead. ThestfStorageSlot + 0/1/2offsets other readers use are unaffected:configsat aftertips,archivesandtempCheckpointLogsin the struct.Effect
propose(48 validators)submitEpochRootProof(48 validators)propose(100 validators)submitEpochRootProof(100 validators)The three gas artifacts and the partial-epoch-proof report are regenerated accordingly.
Test-side notes
PartialEpochProofGasReporteris etched onto a live Rollup withvm.etchso the gas report runs against that rollup's storage. Immutables live in the code, so the Inbox and Outbox the reporter's own constructor deploys travel with the etched code and would displace the live rollup's; the live ones are now passed into the reporter and substituted back through an override of_getRollupConfig, which isvirtualfor that reason.RewardLibWrapperholds the fee asset and portal it needs in its own immutables and hands the library a partially populated config.