Split failed proposal 24 into proposal 24 (de-whitelist + guard) and proposal 25 (un-nominate), under the EIP-7825 gas cap#309
Conversation
…-tx gas cap
The original single 41-action proposal 24 reverted on-chain
(tx 0x540c2026c0a122465806240becdd8381489e19e61d74e74ec184a9660621e20f):
its execute() needed ~19.9M gas while EIP-7825 (Fusaka) caps a single
transaction at 2^24 = 16,777,216 gas. Every simulation passed because
forge/Tenderly/Safe run with a high gas allowance and none enforce the
per-tx cap.
Split the bundle so each proposal's execute stays well under the cap
(measured on a mainnet fork, pure Timelock execute):
- 24a — de-whitelist the same-address multisig adapters (8) + extend the
GuardCM allowlist (19 triples). 9 actions, ~10.4M gas.
- 24b — un-nominate the 32 retired staking contracts. 32 actions, ~9.2M gas.
Bridge encodings, the GuardCM batch, and the Arbitrum retryable params are
byte-for-byte identical to the original proposal 24, so the L2 delivery is
unchanged; only the L1 bundling differs.
Fork tests (all green): full propose -> vote -> queue -> execute lifecycle
through the live GovernorOLAS for each proposal, plus per-L2 bridge-delivery
sims for 24a (Gnosis/Polygon[two adapters]/Arbitrum/Optimism/Base/Celo/Mode).
24b is entirely L1 (VoteWeighting tracks all chains via the chainId arg), so
it has no L2 propagation.
Removes the obsolete single-bundle proposal_24 folder and its fork tests
(preserved in git history); the failed proposal can never be re-executed.
proposalId 24a: 0xa670d15c929979d29c817dd94cf28995248de5b23b68dfa3403b624f173a125c
proposalId 24b: 0xdaea1b75e72e86880322782b4a8a4a022992711bbeba32df08bc1d67feebbca6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(un-nominate) The two halves of the split are independent proposals, so give them distinct DAO numbers: proposal 24 = de-whitelist same-address multisigs + extend the GuardCM allowlist; proposal 25 = un-nominate the 32 retired staking contracts. Pure rename of folders, files, contracts, tests and prose; the DESCRIPTION constants and description.txt are untouched, so the proposalIds are unchanged: - proposal 24: 0xa670d15c929979d29c817dd94cf28995248de5b23b68dfa3403b624f173a125c - proposal 25: 0xdaea1b75e72e86880322782b4a8a4a022992711bbeba32df08bc1d67feebbca6 Each description already matches its own context (24 = security hardening + guard; 25 = staking nominee cleanup); neither references the other or a number. Build + all 13 fork tests green; proposalIds re-verified after the rename. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-contained, color-coded, collapsible HTML that DECODES each proposal's authoritative Forge-builder calldata (calldata.json) and re-derives the proposalId from it, so the artifact cannot drift from what is voted: - proposal 24: 0xa670d15c929979d29c817dd94cf28995248de5b23b68dfa3403b624f173a125c - proposal 25: 0xdaea1b75e72e86880322782b4a8a4a022992711bbeba32df08bc1d67feebbca6 annotate.js auto-groups entries by selector, so the one generator renders both (24 = de-whitelist + GuardCM; 25 = removeNominee). Each folder gets annotate.js, calldata.json and the rendered <folder>.html. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77ph
left a comment
There was a problem hiding this comment.
Audit review — requesting changes (one blocking item; the split itself is correct).
The gas-cap split is sound and well done: the root cause is correctly diagnosed (the original 41-action bundle reverted at gasUsed 16,530,883 against gasLimit 16,777,216 = 2²⁴, the EIP-7825/Fusaka per-tx cap; the note that forge/Safe/Tenderly simulations all pass because none enforce the per-tx cap is the sharp catch), the 41 → 9 + 32 split is faithful with byte-for-byte-identical encodings, and the fork tests are green. Proposal 24 (same-address adapter de-whitelists + GuardCM extension) is good.
Blocking — proposal 25 (removeNominee × 32). I checked the current weight of all 32 nominees on the live VoteWeighting (0x9541…c5c1):
- 31 of 32 have zero current weight → clean removals.
- 1 —
n/a id2(Base, chainId 8453,0x51c5f4982B9b0B3c0482678f5847EA6228Cc8E54) — still carries a substantial active vote (~64% ofgetWeightsSum).
Removing a nominee that still has active votes leaves orphaned voting power in the sum accounting (governance vulnerabilities-list item #8). Please exclude n/a id2 from proposal 25: execute the 31 zero-weight removals now, and handle n/a id2 separately — e.g. have its voter(s) revokeRemovedNomineeVotingPower first so its removal is a clean zero-weight case. This is only about the proposal-25 nominee set; the split mechanics are unaffected. I'll approve once n/a id2 is out of the batch.
Add Part 3 to proposal 24: shut down every OLAS-payment mech factory by calling setMechFactoryStatuses([olasFactory], [false]) on the Mech Marketplace of each network where an OLAS (fixed-price-token, PAYMENT_TYPE 0x3679...45e9) factory is currently whitelisted. Once removed, no new mech that takes payment in OLAS can be created, so nothing can ever route a payment to an OLAS balanceTracker (the tracker mapping is unreachable; it cannot be unset on-chain because setPaymentTypeBalanceTrackers reverts on address(0)). Scope re-derived from live on-chain state + deployment globals, not the stale side analysis. configuration.json mislabels the USDC factory as MechFactoryFixedPriceToken on Ethereum/Arbitrum/Celo; mechFactoryFixedPriceTokenOLASAddress gives exactly one OLAS factory per chain. All 7 are whitelisted right now (mapMechFactories == true), including Ethereum and Gnosis (both activated after the earlier analysis), each Mech Marketplace owned by the same governance mediator as its ServiceRegistryL2: - Ethereum 0xddF6...26fD (direct L1) - Gnosis 0x31ff...45FA (AMB) - Polygon 0xa0DA...0f82 (FxRoot) - Arbitrum 0x70A0...2bcf (retryable) - Optimism 0x26Ea...684c (OP-stack) - Base 0x9737...9BEe (OP-stack) - Celo 0xA123...21E6 (OP-stack) Proposal 24 is now 16 actions. The 3 OP-stack factory-disable sends use a small FACTORY_MIN_GAS (300k; measured L2 delivery ~65k) so execute stays under the EIP-7825 cap. Governor.execute() = 12,634,309 gas vs the 16,777,216 cap (24.7% headroom), asserted in the L1 lifecycle fork test. Content changed, so proposal 24's id changes: old 0xa670d15c929979d29c817dd94cf28995248de5b23b68dfa3403b624f173a125c new 0xc59c9567b77860836814a2144dcd1523fe8811248ea6ac94ed45f67cff8c1f0e description.txt matches the DESCRIPTION constant byte-for-byte; the id is reproduced from calldata.json + description.txt. Fork tests (all green): L1 lifecycle + Timelock execute; per-L2 factory-disable delivery for Optimism/Base/Celo (OP-stack) and Gnosis/Polygon/Arbitrum. annotate.js groups the new setMechFactoryStatuses entries and the HTML breakdown is regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sage per L2 Both effects for the same L2 chain (Part 1 same-address multisig de-whitelist + Part 3 OLAS mech-factory de-whitelist) now ride in ONE bridge message. Each L2 BridgeMediator loops over concatenated (target,value,len,payload) tuples, so: - Gnosis (HomeMediator): 2 tuples in one requireToPassMessage - Polygon (FxGovernorTunnel): 3 tuples (2 adapters + factory) in one sendMessageToChild - Optimism/Base/Celo (OP-stack): 2 tuples in one sendMessage Arbitrum is the exception — a retryable targets a single L2 address with no looping mediator, so ServiceRegistryL2 and the Mech Marketplace need two Inbox retryables. Mode has no OLAS factory (de-whitelist only). Mainnet stays two direct Timelock calls. This collapses 16 actions to 11 (drops 5 redundant bridge sends) and cuts Governor.execute() from 12,634,309 to 10,602,040 gas (~2.03M saved; now 36.8% under the 16,777,216 EIP-7825 cap). Follows the established multi-tuple packing pattern (see proposal_22 FxRoot batching). description.txt is unchanged (same three effects), so only the calldata batching differs; proposalId changes accordingly: old (16-action) 0xc59c9567b77860836814a2144dcd1523fe8811248ea6ac94ed45f67cff8c1f0e new (11-action) 0x0f66ff0be88382e028474de0c50f59ac0c4bd98579e8f637c2d587aabc6fa62d Fork tests (all green): L1 lifecycle asserts execute < cap; per-L2 COMBINED delivery asserts BOTH the adapter and the OLAS factory flip to false in one message (Gnosis/Polygon/Optimism/Base/Celo), Mode de-whitelist only, and the two Arbitrum retryables. annotate.js gains a "combined" group; HTML regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Re-review of the post-review commits ( Gas (the constraint that reverted the original 41-action bundle) — both proposals sit comfortably under the EIP-7825 per-tx cap (16,777,216):
Fork tests: 14/14 pass (6 L1 + 8 L2) — full On-chain authorization re-verified on all 7 chains: each Mech Marketplace Minor (test parity): Blocker unchanged — requesting changes stands. Re-scanned the current weight of all 32 nominees on the live Note (monitoring, not a blocker): once proposal 24 executes, the OLAS-payment balance trackers on these 7 chains become retired branches — per the |
Adds a fork test that verifies and replays the REAL proposal 24 submission (tx 0xd543cd06415529a021ef30be0124af31f318ed61adb604e0aae92647941184ac, block 25,476,048, proposer Safe 0x3447...0039). It forks mainnet at latest, finds the already-created proposal by its on-chain id 0x0f66ff0be88382e028474de0c50f59ac0c4bd98579e8f637c2d587aabc6fa62d (NOT a re-propose), reconstructs the queue/execute args via buildProposal() and requires their hash to equal the submitted proposalId, then drives it through vote -> queue -> execute. Asserts every L1 effect lands (mainnet same-address multisig + OLAS mech factory de-whitelisted, 19 GuardCM triples set, Timelock retains no funds) and that Governor.execute() = 10,588,589 gas stays under the 16,777,216 EIP-7825 cap. The replay reads the live still-Pending proposal and simulates its fixed voting window (blocks 25,489,139-25,508,775), so it self-skips once on-chain voting starts; the durable executability proof is Proposal24ForkL1Test (proposes fresh, independent of the live proposal's timeline). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Why
The original single 41-action bundle (submitted as proposal 24) reverted on-chain:
0x540c…20ffailed withstatus 0,gasUsed 16,530,883againstgasLimit 16,777,216.That gas limit is exactly 2²⁴ = 16,777,216 — the EIP-7825 (Fusaka) per-transaction gas cap. The bundle's
execute()needs ~19.9M gas (profiled on a mainnet fork), which no single transaction can supply. Every simulation (forge fork tests, Safe/Tenderly) passed because they run with a high gas allowance and none enforce the per-tx cap — only mainnet consensus does.A GovernorOLAS
execute()is atomic, so the bundle can't be executed in pieces and re-submitting fails identically. The fix is to split it into two independent, sub-cap proposals.Where the gas went (mainnet-fork profile)
sendMessage(Optimism/Base/Celo/Mode)minGasLimitfor a trivial L2 sstoreremoveNomineeWhat
Two independent proposals, each measured (pure Timelock
execute) well under the cap:scripts/proposals/proposal_24_dewhitelist_and_guard/(9 actions, ~10.4M gas): de-whitelist the 8 same-address multisig adapters (mainnet + 7 L2 bridges; Polygon carries two adapters) + extend the GuardCM allowlist with 19 emergency-pause/drain triples. Carries the Arbitrum retryable value.scripts/proposals/proposal_25_unnominate/(32 actions, ~9.2M gas):VoteWeighting.removeNomineefor the 32 retired staking nominees. All direct L1 calls — no L2 propagation.Bridge encodings, the GuardCM batch, and the Arbitrum retryable params are byte-for-byte identical to the original bundle, so L2 delivery is unchanged; only the L1 bundling split. Each proposal's description is self-contained and context-specific (24 = security hardening + guard; 25 = staking nominee cleanup).
proposalIds
0xa670d15c929979d29c817dd94cf28995248de5b23b68dfa3403b624f173a125c0xdaea1b75e72e86880322782b4a8a4a022992711bbeba32df08bc1d67feebbca6Tests — all green (mainnet + L2 forks)
test/Proposal24ForkL1.t.sol— full propose → vote → queue → execute through the live GovernorOLAS; asserts the mainnet de-whitelist + all 19 GuardCM triples; Arbitrum retryable executor-funded. 3 passtest/Proposal24ForkL2OpStack.t.sol— Optimism/Base/Celo/Mode delivery. 4 passtest/Proposal24ForkL2Other.t.sol— Gnosis (AMB) / Polygon (FxRoot, both adapters) / Arbitrum (aliased Timelock). 3 passtest/Proposal25ForkL1.t.sol— full lifecycle; asserts all 32 nominees removed. 3 passPure-execute gas from the fork tests: proposal 24
execute= 10,380,834, proposal 25execute= 9,163,012 — both comfortably under 16,777,216.Timing note (proposal 25)
removeNomineereverts within the last week of the ongoing Tokenomics epoch (block.timestamp >= getEpochEndTime(eCounter-1) + epochLen - 1 week). The full propose→execute wall-clock is ~6.4 days (votingDelay + votingPeriod ≈ 4.55 d +governorDelay≈ 1.82 d, not the TimelockminDelayof 0). Proposal 25 must be timed so execution lands in an epoch's first-week window.Cleanup
Removes the obsolete single-bundle proposal folder and its three fork tests (preserved in git history). The failed proposal can never be re-executed.
Note on
MIN_GASThe 2M
minGasLimiton the four OP-stack sends (a trivial L2 sstore) accounts for ~9.6M of the original total and is intentionally left unchanged here, to keep proposal 24's bridged payloads byte-identical to the already-simulated original bundle. It could be trimmed in a future revision for cheaper execution; the split alone resolves the cap violation.