Skip to content

Pankaj/feat/security upgrades pr scripts#420

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

Pankaj/feat/security upgrades pr scripts#420
pankajjagtapp wants to merge 3 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
Medium risk because these scripts orchestrate timelocked upgrades and role/parameter configuration across many core proxies; incorrect constants or calldata generation could misconfigure production access control or rate limits.

Overview
Introduces a new script/upgrades/security-upgrades package to execute PR #385’s rollout: a deploy.s.sol script to deploy new implementations (plus a Blacklister proxy), and a transactions.s.sol script that generates and dry-runs two timelocked batches—Batch A upgrades 16 proxies + runs two one-shot migrations + grants tier roles, and Batch B configures EtherFiRateLimiter buckets, sets pauseUntilDuration across multiple contracts, and grants guardian/ops roles.

Adds revert.s.sol to generate/dry-run a timelocked rollback that repoints the same 16 proxies to pre-upgrade implementations (explicitly not undoing storage migrations, limiter config, pause durations, or role grants), and adds ROLE_MIGRATION.md as an ops worksheet mapping modifiers to roles and listing required role holders/operational parameters (intentionally forcing all non-fixed values to be filled before broadcast via _preflight()).

Reviewed by Cursor Bugbot for commit 440734f. 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.
address constant LIDO = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
address constant STETH_ETH_CURVE_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
uint256 constant LIQUIFIER_MIN_DISCOUNT_BPS = 100;
uint256 constant LIQUIFIER_STALE_PRICE_WINDOW = 24 hours;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale price window mismatch between deploy and verification

High Severity

LIQUIFIER_STALE_PRICE_WINDOW is 24 hours in transactions.s.sol but 7 days in deploy.s.sol. The comment on line 73–74 of transactions.s.sol explicitly states these constructor params "MUST MATCH deploy.s.sol exactly" since verifyDeployedBytecode rebuilds each implementation locally and compares bytecode. This mismatch will cause the Liquifier bytecode verification to fail, or if one value is corrected to match the wrong file, the deployed contract will have an incorrect stale price window.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f71453. Configure here.


// EtherFiAdmin
int256 constant ADMIN_MAX_REBASE_APR_BPS = 1_000;
uint256 constant ADMIN_MAX_VALIDATOR_TASK_BATCH_SIZE = 25;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Validator batch size mismatch between deploy and verification

High Severity

ADMIN_MAX_VALIDATOR_TASK_BATCH_SIZE is 25 in transactions.s.sol but 100 in deploy.s.sol. These are constructor params that the comment says "MUST MATCH deploy.s.sol exactly." This mismatch will cause verifyDeployedBytecode to fail for the EtherFiAdmin implementation, and the ambiguity means one file encodes a 4x different operational ceiling for validator task batches.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f71453. Configure here.

uint256 constant ADMIN_MAX_VALIDATOR_TASK_BATCH_SIZE = 25;
uint256 constant ADMIN_STALE_ORACLE_REPORT_BLOCK_WINDOW = 7200 * 7;
uint256 constant ADMIN_MAX_FINALIZED_WITHDRAWAL_AMOUNT_PER_DAY = 100_000 ether;
uint256 constant ADMIN_MAX_VALIDATORS_TO_APPROVE_PER_DAY = 2_000;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Max validators per day mismatch across scripts

High Severity

ADMIN_MAX_VALIDATORS_TO_APPROVE_PER_DAY is 2_000 in transactions.s.sol but 1_000 in deploy.s.sol. These constructor params "MUST MATCH deploy.s.sol exactly" per the explicit comment. This mismatch causes the EtherFiAdmin bytecode verification to fail and creates ambiguity about the intended daily validator approval ceiling.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f71453. Configure here.

WITHDRAW_REQUEST_NFT_BUYBACK_SAFE, EETH, LIQUIDITY_POOL, MEMBERSHIP_MANAGER,
ROLE_REGISTRY, blacklisterProxy, ETHERFI_ADMIN,
WNFT_MIN_ACCEPTABLE_SHARE_RATE, WNFT_MAX_ACCEPTABLE_SHARE_RATE
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WithdrawRequestNFT treasury address mismatch between scripts

High Severity

deploy.s.sol constructs WithdrawRequestNFT with TREASURY (0x0c83...) as the treasury argument, while transactions.s.sol uses WITHDRAW_REQUEST_NFT_BUYBACK_SAFE (0x2f53...) — a completely different address. The constructor params must match for bytecode verification to pass. Additionally, _verifyImmutablesNFT checks treasury() against WITHDRAW_REQUEST_NFT_BUYBACK_SAFE, which won't match the deployed value if TREASURY was used.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f71453. Configure here.

bytes memory args = abi.encode(ROLE_REGISTRY);
bytes memory bc = abi.encodePacked(type(BucketRateLimiter).creationCode, args);
bucketRateLimiterImpl = deploy(name, args, bc, commitHashSalt, true, factory);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deploy deploys wrong rate limiter implementation contract

High Severity

deploy.s.sol deploys BucketRateLimiter as the new implementation for ETHERFI_RATE_LIMITER, but the operating config in transactions.s.sol calls EtherFiRateLimiter.createNewLimiter.selector and EtherFiRateLimiter.updateConsumers.selector on that proxy after the upgrade. BucketRateLimiter lacks these functions entirely (confirmed by grep), so all Batch B rate-limiter operations would revert. The verification in verifyOperatingConfig using limitExists and isConsumerAllowed would also fail.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7f71453. Configure here.

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.
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.

There are 6 total unresolved issues (including 5 from previous reviews).

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 440734f. Configure here.

require(WeETHToken(WEETH).pauseUntilDuration() == PAUSE_UNTIL_WEETH, "WeETH pause duration mismatch");
require(LiquidityPool(payable(LIQUIDITY_POOL)).pauseUntilDuration() == PAUSE_UNTIL_LIQUIDITY_POOL, "LP pause duration mismatch");
require(WithdrawRequestNFT(payable(WITHDRAW_REQUEST_NFT)).pauseUntilDuration()== PAUSE_UNTIL_WITHDRAW_REQUEST_NFT, "NFT pause duration mismatch");
require(Liquifier(payable(LIQUIFIER)).pauseUntilDuration() == PAUSE_UNTIL_LIQUIFIER, "Liquifier pause duration mismatch");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Incomplete verification of pause duration configuration

Medium Severity

verifyOperatingConfig only checks pauseUntilDuration for 5 of the 12 contracts that have it set in executeOperatingConfig. The 7 missing verifications are for ETHERFI_NODES_MANAGER, ETHERFI_ADMIN, ETHERFI_REDEMPTION_MANAGER, MEMBERSHIP_MANAGER, MEMBERSHIP_NFT, AUCTION_MANAGER, and NODE_OPERATOR_MANAGER. If any of those setPauseUntilDuration calls silently fail or are removed, the verification step won't catch it.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 440734f. 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.

1 participant