feat: prevent gov update from bricking rollup#22656
Open
just-mitch wants to merge 1 commit intonextfrom
Open
Conversation
504903a to
a019231
Compare
a019231 to
a45292e
Compare
336da25 to
5179bda
Compare
8c89fa4 to
6805039
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements AZIP-2.
The shared theme: governance and rollup-config setters that could mute or strand the escape hatch — or strand validators or rewards — are removed, made one-shot, delay-gated, or rate-limited.
Escape hatch (one-shot):
updateEscapeHatch→setEscapeHatch. Reverts onaddress(0)and on any second call. Once a rollup has an escape hatch, it cannot be replaced or removed; rollups that want no escape hatch simply never call the setter.EscapeHatchUpdated→EscapeHatchSet. New errorsValidatorSelection__EscapeHatchAlreadySet,ValidatorSelection__EscapeHatchCannotBeZero.Reward distributor — per-rollup earmarking + canonical inheritance: Anyone can subsidize block production on a specific rollup instance, regardless of whether it is canonical.
subsidizeRollup(rollup, amount)is permissionless and earmarks ASSET to a specific rollup inspecificRollupBalance[rollup](tracked intotalEarmarkedBalance).availableTo(rollup)=(balance - totalEarmarked) + specificRollupBalance[rollup]for the canonical rollup; justspecificRollupBalance[rollup]otherwise.claim(to, amount)is now_amount <= availableTo(msg.sender). Old (non-canonical) rollups can still drain anything earmarked to them across rotations.recover(fromRollup, to, amount)(wasrecover(asset, to, amount)) mirrorsclaim's accounting under owner gating. The non-ASSET path moves torecoverWrongAsset(asset, to, amount), which refuses ASSET so it cannot bypass the accounting.RewardDistributor__InsufficientAvailable,RewardDistributor__ZeroRollup.Reward config — addresses immutable post-deployment:
setRewardConfigno longer takes a fullRewardConfig. It takesMutableRewardConfig, which exposes onlysequencerBpsandcheckpointReward. TherewardDistributorandboosteraddresses are written exactly once in the constructor (RewardLib.initializeConfig) and immutable thereafter. Rotating either requires redeploying the rollup viaRegistry.addRollup. The post-deployment writer isRewardLib.updateConfig.RewardConfigUpdatedevent signature follows.setProvingCostPerMana— rate-limited: floor of 2 (MIN_PROVING_COST_PER_MANA), 30-day cooldown (first post-init update waived), symmetric 3/2 multiplicative step.FeeStoregainsuint64 provingCostLastUpdate. New errorsFeeLib__ProvingCostBelowFloor,FeeLib__ProvingCostCooldown,FeeLib__ProvingCostStepExceeded. With 3/2 per 30 days, the value needs ~170 days to move 10× and ~340 days to move 100×.Staking queue invariants — enforced on every write:
assertValidQueueConfiglifted intoStakingLiband called from both the constructor andupdateStakingQueueConfig.normalFlushSizeMin > 0andnormalFlushSizeQuotient > 0for the life of the rollup; the path that could close deposits on a running rollup is gone.Slasher swap — 60-day timelock:
setSlasherremoved, replaced byqueueSetSlasher(owner) →cancelSetSlasher(owner) |finalizeSetSlasher(permissionless).SLASHER_EXECUTION_DELAY = 60 daysexceeds the ~38-day withdrawal window so validators who object can exit before the change lands. Queueing while a change is pending overwrites it and resets the timer. New eventsPendingSlasherQueued,PendingSlasherCancelled. New errorsStaking__NoPendingSlasher,Staking__SlasherNotReady. New viewsgetPendingSlasher,getSlasherExecutionDelay.setLocalEjectionThreshold— removed: mutator gone entirely; reader stays. Threshold is fixed at deploy.updateManaTargetis deliberately left otherwise unchanged — its worst-case outcomes no longer mute the escape hatch.Test plan
setEscapeHatchOneShot.t.sol— every transition of the one-shot guard, including zero-then-nonzero.ProvingCostRateLimit.t.sol— floor, step boundaries (up and down), cooldown boundaries, and a 10-step amplification check that bounds (3/2)^10 growth.setSlasher.t.sol— queue/cancel/finalize states, finalize permissionless, overwrite-pending semantics.setLocalEjectionThresholdRemoval.t.sol— selector unreachable post-removal.slash.t.sol— newSlashLocalEjectionTestdeploys with a non-zero threshold to exercise the local-ejection path.updateStakingQueueConfig.t.sol— invalid-config reverts.initialize.t.sol— proving-cost floor enforced at construction.claim.t.sol,recover.t.sol,subsidizeRollup.t.solcover boundary, multi-rollup isolation, and canonical-rotation semantics. Newinvariant.t.soladds a stateful fuzz handler asserting three accounting identities (balance ≥ totalEarmarked, sum of specifics == totalEarmarked, canonical availableTo identity) under random subsidize/claim/recover/donate/rotate sequences.rollup_cheat_codes.tsgainsclearProvingCostCooldownfor tests that need to bump proving cost more than once;slash_veto_demo.test.tsdeploys the slasher with the correct vetoer up front instead of swapping mid-test (now thatsetSlasheris gone).