Skip to content

26Q2 - Security Upgrades#385

Open
pankajjagtapp wants to merge 628 commits into
masterfrom
pankaj/feat/security-upgrades
Open

26Q2 - Security Upgrades#385
pankajjagtapp wants to merge 628 commits into
masterfrom
pankaj/feat/security-upgrades

Conversation

@pankajjagtapp

@pankajjagtapp pankajjagtapp commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Note

High Risk
Touches upgrade verification, centralized role semantics, and many mainnet deployment/ops scripts tied to timelocked proxy upgrades and live protocol contracts.

Overview
Prepares the 26Q2 security upgrade batch: records Create2 deployment manifests for the new implementations/proxies, and aligns Forge scripts, verification, and CI with the refactored codebase and consolidated access control.

Source & build layout: remappings.txt now routes @etherfi/, @tests/, @scripts/, and @eigenlayer-libraries/; scripts and libs switch from flat ../src/ imports to those paths. Eigenlayer proof libs use the @eigenlayer-libraries/ prefix. foundry.lock is gitignored; forge tests get OP_RPC_URL.

Governance / roles (script-facing): Upgrade and ops scripts stop using per-contract role constants (STAKING_MANAGER_NODE_CREATOR_ROLE, ETHERFI_NODES_MANAGER_*, PROTOCOL_PAUSER, etc.) and instead use RoleRegistry tier roles (OPERATION_TIMELOCK_ROLE, EXECUTOR_OPERATIONS_ROLE, onlyUpgradeTimelock, …). EtherFiNode deployments drop the roleRegistry constructor arg; EtherFiRateLimiter gains mainnet eETH/weETH immutables in deploy/bytecode scripts. WeETHWithdrawAdapter deploy/verify paths remove withdrawRequestNFT from the constructor and drop Ownable/timelock owner checks.

Upgrade safety: ContractCodeChecker.assertByteCodeMatch adds a reverting bytecode equality gate that masks only address-derived immutables (UUPS __self, optional EIP-712 domain word). VerifyV3Upgrade drops EtherFiNode roleRegistry checks and validates consolidated roles.

Operations: New V0 → V1 membership NFT migration (MembershipV0Migrator, MigrateV0ToV1.s.sol, id lists + README). Oracle fork scripts build OracleReport without withdrawalRequestsToInvalidate. Hoodi staking part 1 drops NodeOperatorManager admin whitelisting. SimulateBatchApprove.s.sol is removed. Timelock delay constants are renamed to minDelay_* across el-exits/operations scripts.

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


Timelock batch simulations (Batch 1 — UPGRADE_TIMELOCK, 10-day delay)

Tenderly public simulations of the two proposed Safe transactions (overriding the existing queued txns, nonces 177 / 178):

Comment thread src/EtherFiAdmin.sol
@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown

📊 Forge Coverage Report

| Contract                    | Selector                         | Calls | Reverts | Discards |
| FrozenRateWithdrawalHandler | advanceTime                      | 972   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_cancel                        | 990   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_claim                         | 957   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_fulfill                       | 967   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_invalidate                    | 1018  | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_requestWithWeETH              | 893   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_requestWithdraw               | 935   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | pq_request_at_tolerance_boundary | 870   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | rebase                           | 1001  | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | rebaseExtreme                    | 970   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | verifyFrozenRatePersistence      | 979   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_claim                        | 999   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_invalidate                   | 1058  | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_lockAndFinalize              | 894   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_requestWithdraw              | 976   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_safeTransfer                 | 946   | 0       | 0        |
|-----------------------------+----------------------------------+-------+---------+----------|
| FrozenRateWithdrawalHandler | wrn_validate                     | 959   | 0       | 0        |
| Contract                  | Selector                             | Calls | Reverts | Discards |
| ProtocolInvariantsHandler | adversarial_drainHookProof           | 924   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | adversarial_drainToSingleDigitShares | 976   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | adversarial_inflateFirstShare        | 967   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | burnEEthShares                       | 1015  | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | burnEEthSharesForNonETHWithdrawal    | 941   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | claimSegregated                      | 929   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | depositEth                           | 998   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | donateEEthToProxy                    | 910   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | observeBackingGap                    | 1028  | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | pause_lp_and_attempt_deposit         | 959   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | rebase                               | 931   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | rebaseExtreme                        | 932   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | sendRawEth                           | 961   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | transferEEth                         | 905   | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | transferWeETH                        | 1008  | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | unwrap                               | 1005  | 0       | 0        |
|---------------------------+--------------------------------------+-------+---------+----------|
| ProtocolInvariantsHandler | wrap                                 | 995   | 0       | 0        |
| Contract                 | Selector                       | Calls | Reverts | Discards |
| RedemptionManagerHandler | admin_pause_and_attempt_redeem | 1471  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | admin_setCapacity              | 1495  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | admin_setExitFeeBps            | 1498  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | admin_setExitFeeSplit          | 1439  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | admin_setLowWatermarkBps       | 1505  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | admin_setRefillRate            | 1464  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | advance_time                   | 1526  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | lp_deposit                     | 1522  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | lp_rebase                      | 1494  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | redeemEEth                     | 1450  | 0       | 0        |
|--------------------------+--------------------------------+-------+---------+----------|
| RedemptionManagerHandler | redeemWeEth                    | 1520  | 0       | 0        |
| File                                                     | % Lines            | % Statements       | % Branches       | % Funcs          |
| src/core/EETH.sol                                        | 98.41% (124/126)   | 97.74% (130/133)   | 84.62% (11/13)   | 96.97% (32/33)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/core/LiquidityPool.sol                               | 98.67% (222/225)   | 94.67% (284/300)   | 70.00% (35/50)   | 97.92% (47/48)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/core/WeETH.sol                                       | 100.00% (54/54)    | 96.55% (56/58)     | 71.43% (5/7)     | 100.00% (17/17)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/deposits/DepositAdapter.sol                          | 85.94% (55/64)     | 85.90% (67/78)     | 54.55% (6/11)    | 81.82% (9/11)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/deposits/LiquidRefer.sol                             | 88.24% (30/34)     | 88.89% (24/27)     | 100.00% (2/2)    | 80.00% (8/10)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/deposits/Liquifier.sol                               | 92.21% (142/154)   | 84.34% (167/198)   | 55.10% (27/49)   | 87.10% (27/31)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/Blacklister.sol                           | 88.00% (22/25)     | 72.73% (16/22)     | 20.00% (1/5)     | 77.78% (7/9)     |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/RevokeAdmin.sol                           | 0.00% (0/19)       | 0.00% (0/9)        | 100.00% (0/0)    | 0.00% (0/10)     |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/RoleRegistry.sol                          | 100.00% (47/47)    | 95.65% (44/46)     | 84.62% (11/13)   | 100.00% (20/20)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/rate-limiting/EtherFiRateLimiter.sol      | 100.00% (52/52)    | 100.00% (58/58)    | 100.00% (12/12)  | 100.00% (17/17)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/rate-limiting/libraries/BucketLimiter.sol | 100.00% (44/44)    | 100.00% (38/38)    | 100.00% (5/5)    | 100.00% (9/9)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/utils/Pausable.sol                        | 100.00% (18/18)    | 100.00% (16/16)    | 100.00% (3/3)    | 100.00% (6/6)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/utils/PausableUntil.sol                   | 100.00% (39/39)    | 100.00% (38/38)    | 100.00% (5/5)    | 100.00% (12/12)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/governance/utils/RolesLibrary.sol                    | 100.00% (20/20)    | 100.00% (10/10)    | 100.00% (0/0)    | 100.00% (10/10)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/oracle/EtherFiAdmin.sol                              | 97.42% (189/194)   | 95.76% (271/283)   | 76.00% (38/50)   | 96.43% (27/28)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/oracle/EtherFiOracle.sol                             | 99.31% (143/144)   | 98.32% (176/179)   | 90.32% (28/31)   | 96.00% (24/25)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/restaking/EtherFiRestaker.sol                        | 85.42% (123/144)   | 84.04% (158/188)   | 21.43% (3/14)    | 74.07% (20/27)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/restaking/RestakingRewardsRouter.sol                 | 100.00% (26/26)    | 100.00% (26/26)    | 100.00% (5/5)    | 100.00% (7/7)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/rewards/CumulativeMerkleRewardsDistributor.sol       | 89.66% (52/58)     | 85.94% (55/64)     | 40.00% (4/10)    | 91.67% (11/12)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/rewards/EtherFiRewardsRouter.sol                     | 100.00% (25/25)    | 95.65% (22/23)     | 50.00% (1/2)     | 100.00% (8/8)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/AuctionManager.sol                           | 98.97% (96/97)     | 94.64% (106/112)   | 66.67% (14/21)   | 100.00% (18/18)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/EtherFiNode.sol                              | 98.39% (61/62)     | 90.00% (63/70)     | 28.57% (2/7)     | 100.00% (17/17)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/EtherFiNodesManager.sol                      | 98.18% (162/165)   | 95.92% (188/196)   | 86.21% (25/29)   | 100.00% (38/38)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/NodeOperatorManager.sol                      | 86.05% (37/43)     | 85.71% (30/35)     | 75.00% (3/4)     | 78.57% (11/14)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/StakingManager.sol                           | 100.00% (84/84)    | 95.69% (111/116)   | 73.68% (14/19)   | 100.00% (13/13)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/staking/libraries/DepositDataRootGenerator.sol       | 100.00% (18/18)    | 100.00% (20/20)    | 100.00% (0/0)    | 100.00% (2/2)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/utils/AssetRecovery.sol                              | 100.00% (16/16)    | 96.77% (30/31)     | 85.71% (6/7)     | 100.00% (3/3)    |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/withdrawals/EtherFiRedemptionManager.sol             | 98.12% (157/160)   | 94.67% (213/225)   | 67.57% (25/37)   | 96.77% (30/31)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/withdrawals/PriorityWithdrawalQueue.sol              | 98.50% (197/200)   | 91.40% (255/279)   | 56.86% (29/51)   | 97.22% (35/36)   |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/withdrawals/WeETHWithdrawAdapter.sol                 | 83.87% (26/31)     | 81.25% (26/32)     | 50.00% (2/4)     | 62.50% (5/8)     |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| src/withdrawals/WithdrawRequestNFT.sol                   | 100.00% (111/111)  | 93.02% (120/129)   | 66.67% (18/27)   | 100.00% (25/25)  |
|----------------------------------------------------------+--------------------+--------------------+------------------+------------------|
| Total                                                    | 95.72% (2392/2499) | 92.73% (2818/3039) | 68.97% (340/493) | 92.79% (515/555) |

---
Ran 5 tests for test/AddressProvider.t.sol:AddressProviderTest
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 57.86ms (8.48ms CPU time)
Ran 21 tests for test/AuctionManager.t.sol:AuctionManagerTest
Suite result: ok. 21 passed; 0 failed; 0 skipped; finished in 123.13ms (68.20ms CPU time)
Ran 1 test for test/BNFT.t.sol:BNFTTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 53.04ms (1.76ms CPU time)
Ran 119 tests for test/LiquidityPool.t.sol:LiquidityPoolTest
Suite result: ok. 119 passed; 0 failed; 0 skipped; finished in 354.92ms (299.65ms CPU time)
Ran 38 tests for test/Blacklist.t.sol:BlacklistTest
Suite result: ok. 38 passed; 0 failed; 0 skipped; finished in 129.17ms (74.92ms CPU time)
Ran 58 tests for test/BucketRaterLimiter.t.sol:BucketRateLimiterTest
Suite result: ok. 58 passed; 0 failed; 0 skipped; finished in 39.24ms (36.63ms CPU time)
Ran 122 tests for test/EtherFiOracle.t.sol:EtherFiOracleTest
Suite result: ok. 122 passed; 0 failed; 0 skipped; finished in 1.06s (1.01s CPU time)
Ran 56 tests for test/EtherFiRateLimiter.t.sol:EtherFiRateLimiterTest
Suite result: ok. 56 passed; 0 failed; 0 skipped; finished in 2.37s (2.34s CPU time)
Ran 2 tests for test/fork-tests/pectra-fork-tests/Consolidation-through-EOA.sol:ConsolidationThroughEOATest
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.73s (2.14s CPU time)
Ran 2 tests for test/ContractCodeChecker.t.sol:ContractCodeCheckerTest
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.88s (156.83ms CPU time)
Ran 9 tests for test/ContractCodeCheckerAssert.t.sol:ContractCodeCheckerAssertTest
Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 13.28ms (12.73ms CPU time)
Ran 27 tests for test/CumulativeMerkleRewardsDistributor.t.sol:CumulativeMerkleRewardsDistributorTest
Suite result: ok. 27 passed; 0 failed; 0 skipped; finished in 197.53ms (165.04ms CPU time)
Ran 8 tests for test/integration-tests/Deposit.t.sol:DepositIntegrationTest
Suite result: ok. 8 passed; 0 failed; 0 skipped; finished in 4.13s (3.99s CPU time)
Ran 6 tests for test/DepositAdapter.t.sol:DepositAdapterTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 362.68ms (274.84ms CPU time)
Ran 47 tests for test/EETH.t.sol:EETHTest
Suite result: ok. 47 passed; 0 failed; 0 skipped; finished in 176.52ms (140.38ms CPU time)
Ran 1 test for test/behaviour-tests/pectra-fork-tests/EL-withdrawals.t.sol:ELExitsTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 575.17ms (497.96ms CPU time)
Ran 2 tests for test/behaviour-tests/ELExitsForkTestingDeployment.t.sol:ELExitsForkTestingDeploymentTest
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.58ms (151.82µs CPU time)
Ran 1 test for test/invariant/FrozenRateWithdrawal.invariant.t.sol:FrozenRateWithdrawalInvariantTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 20.39s (20.33s CPU time)
Ran 1 test for test/liquid-tests/LiquidReferBtc.t.sol:LiquidReferBtcOPTest
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 788.72µs (0.00ns CPU time)
Ran 4 tests for test/liquid-tests/LiquidReferBtc.t.sol:LiquidReferBtcTest
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.27s (4.05s CPU time)
Ran 1 test for test/liquid-tests/LiquidReferEth.t.sol:LiquidReferETHOPTest
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 673.23µs (0.00ns CPU time)
Ran 101 tests for test/EtherFiNodesManager.t.sol:EtherFiNodesManagerTest
Suite result: ok. 101 passed; 0 failed; 0 skipped; finished in 14.92s (6.40s CPU time)
Ran 7 tests for test/EtherFiOperationParameters.t.sol:EtherFiOperationParametersTest
Suite result: ok. 7 passed; 0 failed; 0 skipped; finished in 521.67ms (508.98ms CPU time)
Ran 32 tests for test/EtherFiRewardsRouter.t.sol:EtherFiRewardsRouterTest
Suite result: ok. 32 passed; 0 failed; 0 skipped; finished in 36.73ms (34.06ms CPU time)
Ran 4 tests for test/liquid-tests/LiquidReferEth.t.sol:LiquidReferEthTest
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 4.08s (3.77s CPU time)
Ran 1 test for test/liquid-tests/LiquidReferUsdPermit.t.sol:LiquidReferUsdPermitOPTest
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 730.95µs (0.00ns CPU time)
Ran 6 tests for test/EtherFiTimelock.t.sol:TimelockTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.98s (4.29s CPU time)
Ran 1 test for test/EtherFiViewer.t.sol:EtherFiViewerTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 444.33ms (441.62ms CPU time)
Ran 1 test for test/behaviour-tests/ForwardedCallWhitelistRegrant.t.sol:ForwardedCallWhitelistRegrantTest
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 99.73ms (0.00ns CPU time)
Ran 66 tests for test/EtherFiRedemptionManager.t.sol:EtherFiRedemptionManagerTest
Suite result: ok. 66 passed; 0 failed; 0 skipped; finished in 28.96s (28.93s CPU time)
Ran 12 tests for test/liquid-tests/LiquidReferWhitelist.t.sol:LiquidReferWhitelistTest
Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 1.17s (1.09s CPU time)
Ran 1 test for test/fork-tests/RoleMigrationStorageIntegrity.t.sol:RoleMigrationStorageIntegrityTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.19ms (32.20µs CPU time)
Ran 17 tests for test/RoleRegistry.t.sol:RoleRegistryTest
Suite result: ok. 17 passed; 0 failed; 0 skipped; finished in 6.34ms (5.11ms CPU time)
Ran 19 tests for test/StakingManager.t.sol:StakingManagerTest
Suite result: ok. 19 passed; 0 failed; 0 skipped; finished in 2.42s (2.31s CPU time)
Ran 2 tests for test/TNFT.t.sol:TnftTest
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 34.59ms (2.24ms CPU time)
Ran 6 tests for test/TVLOracle.t.sol:TVLOracleTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 41.95ms (7.38ms CPU time)
Ran 13 tests for test/TokenRateLimit.t.sol:TokenRateLimitTest
Suite result: ok. 13 passed; 0 failed; 0 skipped; finished in 55.69ms (20.71ms CPU time)
Ran 6 tests for test/liquid-tests/LiquidReferUsdPermit.t.sol:LiquidReferUsdPermitTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 7.54s (7.55s CPU time)
Ran 89 tests for test/PriorityWithdrawalQueue.t.sol:PriorityWithdrawalQueueTest
Suite result: ok. 89 passed; 0 failed; 0 skipped; finished in 9.64s (3.98s CPU time)
Ran 50 tests for test/Liquifier.t.sol:LiquifierTest
Suite result: ok. 50 passed; 0 failed; 0 skipped; finished in 55.89s (55.88s CPU time)
Ran 13 tests for test/LiquifierStEthPriceFeed.t.sol:LiquifierStEthPriceFeedTest
Suite result: ok. 13 passed; 0 failed; 0 skipped; finished in 84.57ms (82.44ms CPU time)
Ran 9 tests for test/fork-tests/LiquifierStEthPriceFeedFork.t.sol:LiquifierStEthPriceFeedForkTest
Suite result: ok. 9 passed; 0 failed; 0 skipped; finished in 9.27s (2.15s CPU time)
Ran 3 tests for test/LpRebaseWrnClaimUnderflow.t.sol:LpRebaseWrnClaimUnderflowTest
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 39.34ms (7.06ms CPU time)
Ran 4 tests for test/MembershipDeprecationUpgradeFork.t.sol:MembershipDeprecationUpgradeForkTest
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.58s (2.52s CPU time)
Ran 6 tests for test/NodeOperatorManager.t.sol:NodeOperatorManagerTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 45.02ms (9.97ms CPU time)
Ran 30 tests for test/PausableUntil.t.sol:PausableUntilTest
Suite result: ok. 30 passed; 0 failed; 0 skipped; finished in 274.90ms (273.99ms CPU time)
Ran 6 tests for test/ReentrancyGuard.t.sol:ReentrancyGuardTest
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 50.39ms (20.11ms CPU time)
Ran 1 test for test/invariant/ProtocolInvariants.invariant.t.sol:ProtocolInvariantsInvariantTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 29.18s (29.13s CPU time)
Ran 3 tests for test/behaviour-tests/pectra-fork-tests/Request-consolidation.t.sol:RequestConsolidationTest
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 8.19s (2.35s CPU time)
Ran 26 tests for test/RestakingRewardsRouter.t.sol:RestakingRewardsRouterTest
Suite result: ok. 26 passed; 0 failed; 0 skipped; finished in 24.62ms (21.19ms CPU time)
Ran 4 tests for test/fork-tests/RevertEscrowDrain.t.sol:RevertEscrowDrainTest
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 5.57s (5.27s CPU time)
Ran 27 tests for test/ProtocolInvariants.t.sol:ProtocolInvariantsTest
Suite result: ok. 27 passed; 0 failed; 0 skipped; finished in 10.03s (9.98s CPU time)
Ran 2 tests for test/integration-tests/Validator-Flows.t.sol:ValidatorFlowsIntegrationTest
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 16.18s (8.69s CPU time)
Ran 46 tests for test/WeETH.t.sol:WeETHTest
Suite result: ok. 46 passed; 0 failed; 0 skipped; finished in 165.66ms (132.50ms CPU time)
Ran 18 tests for test/WeETHWithdrawAdapter.t.sol:WeETHWithdrawAdapterTest
Suite result: ok. 18 passed; 0 failed; 0 skipped; finished in 71.03ms (39.17ms CPU time)
Ran 1 test for test/invariant/RedemptionManager.invariant.t.sol:RedemptionManagerInvariantTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 19.47s (19.43s CPU time)
Ran 11 tests for test/integration-tests/Withdraw.t.sol:WithdrawIntegrationTest
Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 17.69s (10.44s CPU time)
Ran 5 tests for test/integration-tests/WithdrawEscrowE2E.t.sol:WithdrawEscrowE2ETest
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 7.47s (709.25ms CPU time)
Ran 1 test for test/V0MigrationFork.t.sol:V0MigrationForkTest
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 29.28s (29.15s CPU time)
Ran 81 tests for test/WithdrawRequestNFT.t.sol:WithdrawRequestNFTTest
Suite result: ok. 81 passed; 0 failed; 0 skipped; finished in 7.93s (7.90s CPU time)
Ran 24 tests for test/fork-tests/validator-key-gen.t.sol:ValidatorKeyGenTest
Suite result: ok. 24 passed; 0 failed; 0 skipped; finished in 5.75s (3.80s CPU time)
Ran 11 tests for test/fork-tests/UpgradeStorageIntegrity.t.sol:UpgradeStorageIntegrityTest
Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 153.58s (191.75s CPU time)
Ran 12 tests for test/EtherFiRestaker.t.sol:EtherFiRestakerTest
Suite result: ok. 12 passed; 0 failed; 0 skipped; finished in 796.13s (797.91s CPU time)
Ran 42 tests for test/behaviour-tests/prelude.t.sol:PreludeTest
Suite result: ok. 42 passed; 0 failed; 0 skipped; finished in 805.01s (2424.97s CPU time)
Ran 64 test suites in 940.12s (2094.81s CPU time): 1347 tests passed, 4 failed, 0 skipped (1351 total tests)

Generated by workflow run #770

Comment thread src/core/LiquidityPool.sol
Comment thread src/withdrawals/PriorityWithdrawalQueue.sol
Comment thread src/deposits/Liquifier.sol Outdated
Comment thread src/EtherFiAdmin.sol Outdated
Comment thread src/staking/EtherFiNodesManager.sol
Comment thread src/LiquidityPool.sol
Comment thread src/Liquifier.sol Outdated
@seongyun-ko seongyun-ko changed the title Security Upgrades to harden Protocol Security 26Q2 - Security Upgrades May 7, 2026
Comment thread src/Liquifier.sol Outdated
Comment thread src/oracle/EtherFiAdmin.sol
Comment thread src/oracle/EtherFiAdmin.sol Outdated
Comment thread src/utils/PausableUntil.sol Outdated

import "../interfaces/IRoleRegistry.sol";

contract PausableUntil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does it make sense to make this abstract? All functions internal and never to be deployed ?

@seongyun-ko seongyun-ko May 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fine to make it abstract

Comment thread src/EtherFiRateLimiter.sol Outdated
import "./utils/PausableUntil.sol";

contract EtherFiRateLimiter is IEtherFiRateLimiter, Initializable, UUPSUpgradeable, PausableUpgradeable {
contract EtherFiRateLimiter is IEtherFiRateLimiter, Initializable, UUPSUpgradeable, PausableUpgradeable, PausableUntil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What advantage do we have of adding pausable on Ratelimiter ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agreed,

Comment thread src/EETH.sol Outdated
Comment on lines +203 to +205
function _transferShares(address _sender, address _recipient, uint256 _sharesAmount) internal whenNotPaused {
blacklister.nonBlacklisted(_sender);
blacklister.nonBlacklisted(_recipient);

@pankajjagtapp pankajjagtapp May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Need to add this for safe guard from transferFrom similar to MembershipNFT checks:

blacklister.nonBlacklisted(msg.sender);

Comment thread src/utils/PausableUntil.sol Outdated
}
}

uint256 public constant MAX_PAUSE_DURATION = 7 days;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We would be reverting back to 1 day?

Comment thread src/EETH.sol Outdated
Comment thread src/Liquifier.sol Outdated
Comment thread src/core/EETH.sol Outdated
Comment thread src/WeETH.sol Outdated
import "./interfaces/IRoleRegistry.sol";
import "./interfaces/IBlacklister.sol";

contract WeETH is ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, IRateProvider, AssetRecovery {

@pankajjagtapp pankajjagtapp May 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why haven't we added PausableUntil to weETH ? @0xpanicError

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

we discussed that pausable until shouldn't be applies on token transfer. so was only added on mint/burn on eeth. not any ops on weETH.

@seongyun-ko seongyun-ko May 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That is not exact description of our discussion
https://discord.com/channels/827197391068856371/1501877625784893490/1504007820696354899

Unless we have high confidence on having only 'blacklistUntil', we should add 'pausableUntill'.

Comment thread src/EtherFiAdmin.sol Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review this Pausing/Unpausing functions. Either make them complete across most contracts, or remove this from here.
Cannot have inconsistency

Comment thread src/EtherFiAdmin.sol Outdated
if (_report.protocolFees < 0) return (false, "EtherFiAdmin: protocol fees can't be negative");
int128 totalRewards = _report.protocolFees + _report.accruedRewards;
// protocol fees are less than 20% of total rewards
if (_report.protocolFees > 0 && _report.protocolFees * 5 > totalRewards) return (false, "EtherFiAdmin: protocol fees exceed 20% total rewards");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Would be best to use a defined constant instead of 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agreed

Comment thread src/EtherFiNodesManager.sol Outdated
Comment on lines 108 to 114

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need to add per node address sweeping function

Comment thread src/EtherFiOracle.sol Outdated
Comment on lines 301 to 304

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Need to get rid of this EtherFIAdmin setter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agreed

Comment thread src/EtherFiRedemptionManager.sol Outdated
Comment on lines 138 to 140
function redeemEEth(uint256 eEthAmount, address receiver, address outputToken) public whenNotPaused nonReentrant nonBlacklisted(receiver) {
_redeemEEth(eEthAmount, receiver, outputToken);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do we want to check if the sender is blacklisted or not for all functions here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shuoldn't it be both caller of the function and receiver of the fund which need to be checked?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

the modifier checks both.

Comment thread src/AuctionManager.sol Outdated
uint128 public accumulatedRevenueThreshold;

mapping(address => bool) public admins;
mapping(address => bool) public DEPRECATED_admins;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's make the deprecated variable, private

Comment thread src/AuctionManager.sol Outdated

function initializeOnUpgrade(address _membershipManagerContractAddress, uint128 _accumulatedRevenueThreshold, address _etherFiAdminContractAddress, address _nodeOperatorManagerAddress) external onlyOwner {
require(_membershipManagerContractAddress != address(0) && _etherFiAdminContractAddress != address(0) && _nodeOperatorManagerAddress != address(0), "No Zero Addresses");
function initializeOnUpgrade(address _membershipManagerContractAddress, uint128 _accumulatedRevenueThreshold, address _nodeOperatorManagerAddress) external onlyOwner {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remove this function, if not needed anymore

0xpanicError and others added 20 commits June 19, 2026 19:37
Addresses three verification gaps found in the upgrade security review:

- Vuln 1: ContractCodeChecker only logged Fail and never reverted, so
  verifyDeployedBytecode printed [OK] regardless of the real bytecode at
  the impl addresses. Add assertByteCodeMatch, a hard-reverting gate that
  masks each contract's own 20-byte self-address (UUPSUpgradeable.__self)
  and tolerates EETH's EIP-712 domain separator (verified via
  DOMAIN_SEPARATOR()), rejecting any other byte difference. Switch all 26
  verify calls in transactions.s.sol to it.
- Vuln 2: extend the storage-layout slot-scan fork coverage to the 8
  previously-unverified upgraded proxies (EETH, WeETH, EtherFiAdmin,
  Liquifier, StakingManager, EtherFiNodesManager, EtherFiRedemptionManager,
  MembershipManager); assert 0 sequential drift and preserved totalSupply
  for the value-bearing tokens.
- Vuln 4: add _verifyReleaseCommit (--ffi) pinning the checkout to
  GIT_COMMIT_SHA and rejecting a dirty tree, so verification reflects the
  audited release commit; loud warning when skipped.

Also re-adds the FORK_RPC_URL override so verification can run against the
Tenderly testnet where the new impls are deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The gate treated a difference as a self-address/word region only when the
FIRST unequal byte was the region start. If the on-chain and local addresses
(or allowed words) share leading bytes, the difference first appears at an
interior offset, so the anchored _eq20/_eq32 window misaligned and the gate
reverted on legitimately-matching source (~1/256 per embedded address).

Replace the anchored skip with a scan-and-mask pass: find each region at its
true boundary (masking only where the on-chain side holds its address/word AND
the local side holds its address/word at the same offset, keeping both aligned),
then require equality on all non-exempt bytes. First-byte guards keep it ~O(n).

Adds regression tests with addresses sharing 19 leading bytes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 8 extra proxies (EETH, WeETH, EtherFiAdmin, Liquifier, StakingManager,
EtherFiNodesManager, EtherFiRedemptionManager, MembershipManager) lived in a
separate UpgradeStorageIntegrityExtended.t.sol that gated on block.chainid==1
but never forked in setUp. Under plain 'forge test' (chainid 31337, as CI runs)
every test returned immediately and passed vacuously, leaving the coverage gap
unfilled.

Move the checks into UpgradeStorageIntegrity.t.sol so they inherit its
createSelectFork(MAINNET_RPC_URL) setUp and actually run in CI. Switch the base
to SecurityUpgradesConstants for the constructor params; delete the separate file.

Verified: plain 'forge test --match-test test_extStorage' (no --fork-url) runs
all 8 against the mainnet fork, 0 drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erification

Step 10 (verifyOperatingConfig) only checked bucket existence + consumer, not the
configured capacity/refillRate. Add _assertBucketConfig for all three buckets
(EETH mint, EETH burn, STETH_REQUEST_WITHDRAWAL) so they must equal the Constants.

Step 11 (_flowEEthRateLimits) only proved decrement + an arbitrary over-limit
revert. Add _assertExactBoundary: drain each bucket to PRECISELY zero through its
on-chain consumer (a consume(id,0) first flushes time-based refill so lastRefill
== now and the drain is exact), then assert one unit over reverts LimitExceeded.
This also exercises the STETH_REQUEST_WITHDRAWAL bucket for the first time (no
cheap fork flow mints a stETH withdrawal request).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rade-verification-hardening

fix: harden security-upgrade bytecode + storage verification
The new EtherFiNodesManager re-keys the forwarded-call whitelist from a global (selector-keyed) mapping to a per-caller mapping, which orphans the legacy entries on upgrade. Add the re-grant to the OPERATING_TIMELOCK batch (Batch 2) so the eigenpod-operations role holder keeps forwarding rights post-upgrade.

The whitelisted set is pinned as named SEL_* constants (decoded function signatures in comments) and shared by the batch builder and verifyOperatingConfig. withdrawRestakedBeaconChainETH is excluded: it is onlyEigenPodManager, so a forwarded call (msg.sender == EtherFiNode) always reverts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…older

A mainnet-fork simulation showed the ENM is already on the per-user whitelist layout: the legacy forwarder 0x7835 currently holds exactly 4 effective entries (startCheckpoint, verifyCheckpointProofs, verifyWithdrawalCredentials, and processClaim on the RewardsCoordinator). The prior set, derived from stale global-era events, was wrong.

Batch 2 now grants those 4 entries to HOLDER_EIGENPOD_OPERATIONS_ROLE and revokes them from 0x7835. Add a fork test that upgrades the ENM, applies the migration as the operating timelock, and asserts the legacy caller starts with all 4 and ends with none.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test previously asserted the legacy caller's per-user entries were live immediately after the upgrade, coupling it to mainnet's incidental state and to a layout assumption (per-user slots could be empty until migration). It now seeds the entries on the legacy caller as the operating timelock, then verifies the migration moves them to the new holder. Also pins the fork block for determinism.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ant-only)

3CP #580 whitelists the call-forwarder for batch queue/complete across backfilled nodes. Grant the same forwardExternalCall selectors on the DelegationManager to HOLDER_EIGENPOD_OPERATIONS_ROLE in Batch 2: queueWithdrawals 0x0dd8dd02, completeQueuedWithdrawals 0x9435bb43, and the singular completeQueuedWithdrawal 0xe4cc3f90 (used by completeQueuedETHWithdrawals). The four names collapse to these three on-chain selectors since queueETHWithdrawal and queueWithdrawals both call DelegationManager.queueWithdrawals.

Grant-only: the legacy caller's copies are revoked in a separate later 3CP, so these are not revoked here. verifyOperatingConfig and the fork test assert the holder receives all three.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rded-call-whitelist

Re-grant ENM forwarded-call whitelist to eigenpod-ops role in Batch 2
…ctly ahead)

No file changes — PR-scripts already supersedes all of security-upgrades' content;
this records ea30038 (audit PR #466) as an ancestor so the branch is strictly ahead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…grades-PR-scripts

Pankaj/feat/security upgrades pr scripts
Comment thread script/DeployWeETHWithdrawAdapter.s.sol
Comment thread script/DeployHoodiContracts.s.sol
Comment thread script/upgrades/CrossPodApproval/transactions.s.sol

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

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

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 84c79e1. Configure here.

}
verified++;
}
console.log("Post-migration verification passed for ids:", verified);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Migration verify runs after broadcast

Medium Severity

The V0 migration script broadcasts all batch transactions inside vm.startBroadcast / vm.stopBroadcast, then runs post-migration checks that can revert with MigrationIncomplete. A failed final verification still leaves earlier batches on-chain while the script run reports failure.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 84c79e1. Configure here.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants