Skip to content

Pankaj/feat/security upgrades pr scripts#420

Open
pankajjagtapp wants to merge 38 commits into
pankaj/feat/security-upgradesfrom
pankaj/feat/security-upgrades-PR-scripts
Open

Pankaj/feat/security upgrades pr scripts#420
pankajjagtapp wants to merge 38 commits into
pankaj/feat/security-upgradesfrom
pankaj/feat/security-upgrades-PR-scripts

Conversation

@pankajjagtapp
Copy link
Copy Markdown
Contributor

@pankajjagtapp pankajjagtapp commented May 21, 2026

Note

Medium Risk
Scripts orchestrate wide mainnet proxy/beacon upgrades and timelocked rollback; mistakes in unset GIT_COMMIT_SHA/PRE_* or batch ordering could brick ops paths, but on-chain logic is unchanged in this diff.

Overview
Adds mainnet upgrade runbook tooling under script/upgrades/security-upgrades/ for the 26Q2 security release (PR #385): a CREATE2 deploy script, a timelock revert script, and a role/ops migration guide, plus small Deployed.s.sol address constants used by those scripts.

deploy.s.sol deploys new implementations (and Blacklister / RevokeAdmin UUPS proxies) via a shared GIT_COMMIT_SHA salt, wires RoleRegistry to RevokeAdmin, then deploys the rest of the upgraded contracts with blacklisterProxy and mainnet addresses from Deployed. It bakes in reviewed immutables (admin/oracle caps, redemption ceilings, WRN share-rate band, etc.) and _preflight blocks broadcast until GIT_COMMIT_SHA and WRN bounds are set.

revert.s.sol builds a 10-day UPGRADE_TIMELOCK batch to roll 22 UUPS proxies + RoleRegistry back to snapshot PRE_* impls, reverts the EtherFiNode beacon via StakingManager.upgradeEtherFiNode, keeps RoleRegistry last so upgrade auth still works mid-batch, dry-runs on a fork, and emits Safe revert_schedule.json / revert_execute.json. It documents what revert does not undo (one-shot inits, rate buckets, pause durations, role grants).

ROLE_MIGRATION.md is the ops worksheet for the companion transactions.s.sol flow: 9 RolesLibrary roles → function map, holder placeholders, rate-limiter buckets, pause durations, and three-batch execution order (upgrade / operating / instant LP bounds), including post-upgrade follow-ups.

Deployed.s.sol gains ETH2_DEPOSIT_CONTRACT and token constants (WETH, STETH, WSTETH, EIGEN) for DepositAdapter / StakingManager constructor args in deploy.

Reviewed by Cursor Bugbot for commit bc3bee3. Bugbot is set up for automated code reviews on this repo. Configure here.

…ion scripts

Added a new deployment script for the 26Q2 security upgrades, including the deployment of new implementations and the Blacklister proxy. Introduced a role migration document outlining the necessary role adjustments post-upgrade. The transactions script has been set up for timelocked upgrades and operational parameter configurations, requiring manual input for addresses before execution.
@pankajjagtapp pankajjagtapp self-assigned this May 21, 2026
@pankajjagtapp pankajjagtapp changed the base branch from master to pankaj/feat/security-upgrades May 21, 2026 20:51
@github-actions
Copy link
Copy Markdown

📊 Forge Coverage Report

Coverage report could not be generated. Check workflow logs for details.

Generated by workflow run #732

@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review

numCommitteeMembers++;
numActiveCommitteeMembers++;
committeeMemberStates[_address] = CommitteeMemberState(true, true, 0, 0);
_checkQuorum();

P1 Badge Allow committee bootstrap when quorum exceeds one

addCommitteeMember now calls _checkQuorum() after incrementing the active count, but _checkQuorum requires numActiveCommitteeMembers >= quorumSize. On a fresh deployment with quorumSize > 1, the first add always reverts (1 < quorumSize), so no members can ever be added; setQuorumSize cannot recover either because it also calls _checkQuorum against zero active members. This makes the oracle committee unbootstrappable unless quorum was preconfigured to exactly 1.


function _validateValidatorApprovals(IEtherFiOracle.OracleReport calldata _report, uint256 elapsedTime) internal view returns (bool, string memory) {
uint256 numValidatorsToApprovePerDay = (_report.validatorsToApprove.length * 1 days) / elapsedTime;
if (numValidatorsToApprovePerDay > maxNumValidatorsToApprovePerDay) return (false, "EtherFiAdmin: number of validators to approve exceeds max");
return (true, "");

P1 Badge Initialize report caps before enforcing new validation

The new report guards compare per-day activity against maxNumValidatorsToApprovePerDay (and similarly maxFinalizedWithdrawalAmountPerDay), but these state variables are not initialized in initialize/constructor storage and therefore default to 0 on upgrade. As a result, any report with nonzero validator approvals or finalized withdrawals is rejected by validation until a separate admin transaction sets the caps, which can halt normal report execution immediately after upgrade.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

…ation

Modified the deployment script to adjust the LIQUIFIER_STALE_PRICE_WINDOW to 7 days and increased ADMIN_MAX_VALIDATOR_TASK_BATCH_SIZE to 100. Updated ADMIN_MAX_FINALIZED_WITHDRAWAL_AMOUNT_PER_DAY to 100,000 ether and reduced ADMIN_MAX_VALIDATORS_TO_APPROVE_PER_DAY to 1,000. Enhanced the role migration documentation to reflect the new role mappings and operational parameters for the security upgrades.
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol
Comment thread script/upgrades/security-upgrades/deploy.s.sol
Introduced a new script to revert the security upgrades by re-pointing proxies to their pre-upgrade implementations. The script includes functionality to confirm current implementations, take snapshots before the revert, and execute the revert process while preserving access control. This allows for a controlled rollback of the recent upgrades if necessary.
Comment thread script/upgrades/security-upgrades/transactions.s.sol
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/revert.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol Outdated
Comment thread script/upgrades/security-upgrades/deploy.s.sol Outdated
Comment thread script/upgrades/security-upgrades/deploy.s.sol Outdated
Comment thread script/upgrades/security-upgrades/transactions.s.sol
0xpanicError and others added 6 commits May 27, 2026 19:53
…stic salts, eyeball block

Addresses findings from PR #420 6-lens review:

C1: Reorder upgrade batch. RoleRegistry impl swap moved from FIRST to AFTER
  the 21 proxy upgrades + beacon, BEFORE the 2 onlyUpgradeTimelock initializers.
  Old proxy impls gate _authorizeUpgrade via roleRegistry.onlyProtocolUpgrader,
  which the new RR drops. Swapping RR first would brick every subsequent
  upgradeTo with a fallback revert. Confirmed via on-chain selector scan of
  4 mainnet impls (LP/EFRM/EFNM/WRN all contain 0x5006bb7b, none contain
  0x6ac5f9eb).

C2: EFRM _treasury constructor arg now WITHDRAW_REQUEST_NFT_BUYBACK_SAFE in
  BOTH deploy.s.sol (already correct) and transactions.s.sol bytecode-verify
  + immutable-assert (was TREASURY, would break verifyDeployedBytecode).
  Matches current mainnet EFRM.treasury() = 0x2f5301..78f0F. Constant name
  unified to RM_MAX_EXIT_FEE_SPLIT_TO_TREASURY_BPS across both files
  (matches contract field maxExitFeeSplitToTreasuryInBps).

C3+C7: GIT_COMMIT_SHA TBD placeholder + deterministic timelock salts.
  Replaces bytes32(0) placeholder with explicit bytes20 GIT_COMMIT_SHA = TBD;
  must be set to first 20 bytes of release commit before broadcast.
  commitHashSalt derived from it; preflight rejects bytes20(0) in all 3
  scripts. Timelock salts now keccak256(abi.encode("batch-{1,2,revert}",
  commitHashSalt)) — deterministic across re-runs, no more block.number
  drift between fork dry-run and broadcast.

C4: LP_MAX_WITHDRAW_AMOUNT bumped from 1_000 ether (dummy) to 3_000 ether.

C6: ADMIN_STALE_ORACLE_REPORT_BLOCK_WINDOW = 7200 * 14 (14 days; previously
  value 7200*7 with comment claiming 14 days).

H3: New _printPleaseEyeball() block at top of run() in both deploy.s.sol
  and transactions.s.sol prints every TBD constant — GIT_COMMIT_SHA, all
  26 deployed impl addresses, all 9 HOLDER_*, 10 rate-limit caps/refills,
  11 pause durations, finalized-withdrawal limit, LP bounds, WNFT band —
  BEFORE preflight runs, so signers can eyeball values in one place.

H4: WNFT share-rate band tightened from [1, 4 ether] to [0.95 ether,
  1.15 ether] — ~[0.9, 1.1] x live amountPerShareCeil() snapshot (~1.05).
  Preflight asserts MIN > 0 and MAX > MIN.

H5: LIQUIFIER_STALE_PRICE_WINDOW = 1 days (was 7 days; Chainlink stETH/ETH
  heartbeat is 24h).

H8: New _preflightRoleHashes() instantiates new RoleRegistry(revokeAdminProxy)
  and cross-checks all 9 hardcoded keccak256(role-string) constants against
  the RR getters. Catches typos in role strings BEFORE any Safe JSON is
  written.

Operational items still on signer's plate before broadcast: GIT_COMMIT_SHA,
6 HOLDER_* placeholders, 20 rate-limit gwei values, 11 pause durations,
finalized-withdrawal setpoint, re-verify WNFT band centers on live rate.
The PLEASE EYEBALL block surfaces all of them on every run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
seongyun-ko and others added 5 commits May 28, 2026 18:38
…ation

Verified PR #420 end-to-end on a Tenderly VNet forked from mainnet
(31698b4b-77c5-4b96-a057-855ccaac67b8). All 3 batches landed clean:
schedule both 10d/2d timelocks together, warp +10d, execute, then the
instant LP-bounds batch. 7 of 8 user-facing QA scenarios passed
(deposit / wrap / transfer / requestWithdraw / blacklist /
unblacklist / pauseContractUntil); two real bugs surfaced and are
fixed here, one operational follow-up is documented.

F1 (transactions.s.sol _auctionImmSels): new AuctionManager dropped
membershipManagerContractAddress(). It existed pre-upgrade so the
_safeSnapshot filter let it through; _postSnap then reverted with
"previously-surviving selector now reverts". Removed from the
selector list (6 -> 5 entries).

F2 (transactions.s.sol executeOperatingConfig): UNRESTAKING /
EXIT_REQUEST / CONSOLIDATION_REQUEST rate-limit buckets already exist
on mainnet from a prior deployment (limitExists() == true). PR #420
unconditionally called createNewLimiter for all 10 buckets ->
LimitAlreadyExists() on these 3, atomic batch revert. Switched the
3 EFNM buckets to setCapacity + setRefillRate; the 7 token + restaker
buckets still use createNewLimiter (they are genuinely new). The
existing consumer wiring for the 3 EFNM buckets is preserved, so
no updateConsumer call is needed for them.

F3 (ROLE_MIGRATION.md §6.1, operational - no script change):
EtherFiRedemptionManager.tokenToRedemptionInfo[EETH/WEETH] defaults
to zero after the upgrade. redeemEEth / redeemWeEth revert until
OPERATION_TIMELOCK calls initializeTokenParameters([EETH, WEETH],
...). Documented as a Day-10+ operational follow-up alongside H7
(per-address rate-limit pre-seeding) and stale-holder hygiene.

Also (deploy.s.sol): set logging=false on LiquidityPool, Liquifier,
and EtherFiAdmin deploys because Utils.formatStaticParam reverts with
"Unsupported static type" on the ConstructorAddresses struct argument.
Deployment still succeeds; only the JSON pretty-print is skipped for
these 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pgrades-scripts-review

fix(security-upgrades): review fixes for #420 — RR ordering, EFRM treasury, deterministic salts, eyeball block
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bc3bee3. Configure here.

uint256 public constant ADMIN_MAX_REQUESTS_TO_FINALIZE_PER_REPORT = 2_000;

// oracle — EtherFiOracle immutable params
uint256 public constant ORACLE_MIN_QUORUM_SIZE = 3; // enforces min 3/5 quorum for consensus
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch for ORACLE_MIN_QUORUM_SIZE across scripts

Low Severity

ORACLE_MIN_QUORUM_SIZE is declared as uint256 in deploy.s.sol but as uint32 in transactions.s.sol. The transactions.s.sol header explicitly states these constructor params "MUST MATCH deploy.s.sol exactly." While abi.encode pads both to identical 32-byte words for the current value of 3, the type divergence undermines the stated invariant and could silently truncate if a future value exceeds uint32.max.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bc3bee3. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants