From f8faafbced562130b558602e2d98087a0df66d0e Mon Sep 17 00:00:00 2001 From: Serge <2901744+evercoinx@users.noreply.github.com> Date: Wed, 20 May 2026 10:34:04 +0200 Subject: [PATCH 1/4] chore: add scripts to upgrade all core contracts --- src/script/UpgradeAllContractsEthereum.s.sol | 52 ++++++++++++++++++++ src/script/UpgradeAllContractsSepolia.s.sol | 51 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/script/UpgradeAllContractsEthereum.s.sol create mode 100644 src/script/UpgradeAllContractsSepolia.s.sol diff --git a/src/script/UpgradeAllContractsEthereum.s.sol b/src/script/UpgradeAllContractsEthereum.s.sol new file mode 100644 index 00000000..e4f36ed6 --- /dev/null +++ b/src/script/UpgradeAllContractsEthereum.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.28; + +import {UpgradeAllContracts} from "./UpgradeAllContracts.s.sol"; + +/** + * @title UpgradeAllContractsEthereum + * @notice Upgrades all deployed Core UUPS proxies on Ethereum mainnet. + * + * @dev Canonical mainnet proxy addresses (CreateX deterministic deployment — same as Sepolia): + * - OraclePriceFeed: 0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16 + * - EigenAdapter: 0xe6168092892E545701D92BEe10149178bE0EEDF4 + * - StakeManager: 0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be + * - RewardsManager: 0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46 + * - SlashingManager: 0x7Bc39bf135eF3c30E542C196719c91455ff489f0 + * - SSPRouter: 0xF39E592E93A7a925a57464e0120B555A25590486 + * SymbioticAdapter is NOT deployed on mainnet — leave SYMBIOTIC_ADAPTER_PROXY unset. + * + * @dev Admin model: multisig 0xd2d03377Fa96687e9C11380DA9956AcC5F307e2c holds + * DEFAULT_ADMIN_ROLE directly on core proxies. There is NO TimelockController for core + * on any network (confirmed on-chain: SlashingManager.hasRole(DEFAULT_ADMIN_ROLE, Timelock) + * == false). All upgrades are direct multisig Safe TX Builder calls. + * + * This is distinct from the Coverage contracts, where DEFAULT_ADMIN_ROLE on the coverage + * UUPS proxies is held by a TimelockController. + * + * @dev Prerequisites: + * forge clean && forge build + * + * @dev Usage (broadcast from multisig EOA or via Safe execute): + * DEPLOYER_ADDRESS=0xd2d03377Fa96687e9C11380DA9956AcC5F307e2c \ + * ORACLE_PRICE_FEED_PROXY=0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16 \ + * EIGEN_ADAPTER_PROXY=0xe6168092892E545701D92BEe10149178bE0EEDF4 \ + * STAKE_MANAGER_PROXY=0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be \ + * REWARDS_MANAGER_PROXY=0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46 \ + * SLASHING_MANAGER_PROXY=0x7Bc39bf135eF3c30E542C196719c91455ff489f0 \ + * SSP_ROUTER_PROXY=0xF39E592E93A7a925a57464e0120B555A25590486 \ + * forge script src/script/UpgradeAllContractsEthereum.s.sol:UpgradeAllContractsEthereum \ + * --rpc-url $ETHEREUM_RPC_URL --broadcast --keystore $KEYSTORE --sender $DEPLOYER_ADDRESS + * + * Then verify: + * forge script src/script/VerifyCoreEthereum.s.sol --rpc-url $ETHEREUM_RPC_URL + * + * Note: Mainnet WETH feed (hasPriceFeed == true) is already registered. No post-upgrade + * feed registration is required, unlike Sepolia. + */ +contract UpgradeAllContractsEthereum is UpgradeAllContracts { + function run() public override returns (NewImplementations memory) { + require(block.chainid == 1, "Must run on Ethereum mainnet"); + return super.run(); + } +} diff --git a/src/script/UpgradeAllContractsSepolia.s.sol b/src/script/UpgradeAllContractsSepolia.s.sol new file mode 100644 index 00000000..ed0ede72 --- /dev/null +++ b/src/script/UpgradeAllContractsSepolia.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.28; + +import {UpgradeAllContracts} from "./UpgradeAllContracts.s.sol"; + +/** + * @title UpgradeAllContractsSepolia + * @notice Upgrades all deployed Core UUPS proxies on Sepolia testnet. + * + * @dev Canonical Sepolia proxy addresses (CreateX deterministic deployment — same as mainnet): + * - OraclePriceFeed: 0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16 + * - EigenAdapter: 0xe6168092892E545701D92BEe10149178bE0EEDF4 + * - StakeManager: 0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be + * - RewardsManager: 0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46 + * - SlashingManager: 0x7Bc39bf135eF3c30E542C196719c91455ff489f0 + * - SSPRouter: 0xF39E592E93A7a925a57464e0120B555A25590486 + * SymbioticAdapter is NOT deployed on Sepolia — leave SYMBIOTIC_ADAPTER_PROXY unset. + * + * @dev Admin model: multisig 0xd2d03377Fa96687e9C11380DA9956AcC5F307e2c holds + * DEFAULT_ADMIN_ROLE directly. No TimelockController for core on any network. + * + * @dev Prerequisites: + * forge clean && forge build + * + * @dev Usage: + * DEPLOYER_ADDRESS=0xd2d03377Fa96687e9C11380DA9956AcC5F307e2c \ + * ORACLE_PRICE_FEED_PROXY=0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16 \ + * EIGEN_ADAPTER_PROXY=0xe6168092892E545701D92BEe10149178bE0EEDF4 \ + * STAKE_MANAGER_PROXY=0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be \ + * REWARDS_MANAGER_PROXY=0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46 \ + * SLASHING_MANAGER_PROXY=0x7Bc39bf135eF3c30E542C196719c91455ff489f0 \ + * SSP_ROUTER_PROXY=0xF39E592E93A7a925a57464e0120B555A25590486 \ + * forge script src/script/UpgradeAllContractsSepolia.s.sol:UpgradeAllContractsSepolia \ + * --rpc-url $SEPOLIA_RPC_URL --broadcast --keystore $KEYSTORE --sender $DEPLOYER_ADDRESS + * + * Then verify: + * forge script src/script/VerifyCoreSepolia.s.sol --rpc-url $SEPOLIA_RPC_URL + * + * Post-upgrade (Sepolia only): register WETH price feed if not already present: + * WETH=0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9 + * CHAINLINK_ETH_USD=0x694AA1769357215DE4FAC081bf1f309aDC325306 + * cast send 0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16 \ + * "setTokenPriceFeed(address,address,uint256)" $WETH $CHAINLINK_ETH_USD 3600 \ + * --keystore $KEYSTORE --rpc-url $SEPOLIA_RPC_URL + */ +contract UpgradeAllContractsSepolia is UpgradeAllContracts { + function run() public override returns (NewImplementations memory) { + require(block.chainid == 11_155_111, "Must run on Sepolia"); + return super.run(); + } +} From e0802cbfb8215675a1a9b67d4d9dfd8151fa86ab Mon Sep 17 00:00:00 2001 From: Serge <2901744+evercoinx@users.noreply.github.com> Date: Wed, 20 May 2026 10:36:12 +0200 Subject: [PATCH 2/4] chore: add scripts to verify contracts --- src/script/VerifyCoreEthereum.s.sol | 348 ++++++++++++++++++++++++++++ src/script/VerifyCoreSepolia.s.sol | 317 +++++++++++++++++++++++++ 2 files changed, 665 insertions(+) create mode 100644 src/script/VerifyCoreEthereum.s.sol create mode 100644 src/script/VerifyCoreSepolia.s.sol diff --git a/src/script/VerifyCoreEthereum.s.sol b/src/script/VerifyCoreEthereum.s.sol new file mode 100644 index 00000000..1301d75d --- /dev/null +++ b/src/script/VerifyCoreEthereum.s.sol @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.28; + +import {Script, console2} from "forge-std/Script.sol"; + +/// @dev Minimal view interfaces. +interface IAccessControl { + function hasRole(bytes32 role, address account) external view returns (bool); +} + +interface IDelayView { + function delay() external view returns (uint256); +} + +interface IEigenAdapterView { + function oraclePriceFeed() external view returns (address); + function sspRouter() external view returns (address); +} + +interface IStakeManagerView { + function coverPoolFactory() external view returns (address); + function router() external view returns (address); +} + +interface ISSPRouterView { + function stakeManager() external view returns (address); + function rewardsManager() external view returns (address); + function slashingManager() external view returns (address); + function getAdapter(uint8 moduleType) external view returns (address); +} + +interface ISlashingManagerView { + function claimManager() external view returns (address); + function sspRouter() external view returns (address); + function stakeManager() external view returns (address); + function PAUSER_ROLE() external view returns (bytes32); +} + +interface IOraclePriceFeedView { + function hasPriceFeed(address token) external view returns (bool); + function getUSDValue(address token, uint256 amount) external view returns (uint256); +} + +/// @dev Minimal interface for EigenAdapter new admin functions (#543). +interface IEigenAdapterNewFns { + function updateDelegationApprover(address vault, address newDelegationApprover) external; + function advanceToWithdrawals(address vault) external; +} + +/** + * @title VerifyCoreEthereum + * @notice Verifies that the Core upgrade on Ethereum mainnet was applied correctly. + * + * @dev Same checks as VerifyCoreSepolia, plus: + * - WETH feed is confirmed registered (hasPriceFeed == true, getUSDValue > 0) + * - DEFAULT_ADMIN_ROLE on core proxies is held by the multisig (no Timelock for core) + * + * @dev Usage: + * forge script src/script/VerifyCoreEthereum.s.sol --rpc-url $ETHEREUM_RPC_URL + */ +contract VerifyCoreEthereum is Script { + address internal constant ORACLE_PRICE_FEED = 0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16; + address internal constant EIGEN_ADAPTER = 0xe6168092892E545701D92BEe10149178bE0EEDF4; + address internal constant STAKE_MANAGER = 0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be; + address internal constant REWARDS_MANAGER = 0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46; + address internal constant SLASHING_MANAGER = 0x7Bc39bf135eF3c30E542C196719c91455ff489f0; + address internal constant SSP_ROUTER = 0xF39E592E93A7a925a57464e0120B555A25590486; + + address internal constant COVER_POOL_FACTORY = 0x4f3DbB70cD85bcb63303FBa8610Cb163aDDA4E66; + address internal constant CLAIM_MANAGER = 0x3ceE181C3E78fB9968f0Fb0935d2db0723B9Cb45; + address internal constant PREMIUM_MANAGER = 0xEc7322D6754709d5001B710ec4fB2547a89B3aD1; + + address internal constant MULTISIG = 0xd2d03377Fa96687e9C11380DA9956AcC5F307e2c; + + address internal constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + + uint8 internal constant MODULE_SYMBIOTIC = 1; + uint8 internal constant MODULE_EIGENLAYER = 2; + + bytes32 internal constant DEFAULT_ADMIN_ROLE = bytes32(0); + + uint256 internal _failures; + + function run() external { + require(block.chainid == 1, "Must run on Ethereum mainnet"); + + console2.log("=== VerifyCoreEthereum ==="); + console2.log("Block:", block.number); + console2.log(""); + + _checkAdminModel(); + _checkRenameComplete(); + _checkNewAdminFunctions(); + _checkDelays(); + _checkSymbioticAdapterAbsent(); + _checkWiring(); + _checkPauserRole(); + _checkWethFeed(); + + console2.log(""); + if (_failures == 0) { + console2.log("[ALL OK] Core Ethereum mainnet upgrade verified successfully."); + } else { + console2.log("[INCOMPLETE]", _failures, "check(s) failed. See [FAIL] lines above."); + } + console2.log("============================"); + } + + // ───────────────────────────────────────────────────────────────────────── + // Admin model: multisig holds DEFAULT_ADMIN_ROLE directly (no Timelock) + // ───────────────────────────────────────────────────────────────────────── + + function _checkAdminModel() internal { + console2.log("--- Admin model (core has no Timelock) ---"); + + address[4] memory proxies = [SLASHING_MANAGER, STAKE_MANAGER, REWARDS_MANAGER, SSP_ROUTER]; + string[4] memory names = ["SlashingManager", "StakeManager", "RewardsManager", "SSPRouter"]; + + for (uint256 i = 0; i < proxies.length; ++i) { + bool has = IAccessControl(proxies[i]).hasRole(DEFAULT_ADMIN_ROLE, MULTISIG); + if (has) { + console2.log("[OK]", names[i], ": DEFAULT_ADMIN_ROLE held by multisig"); + } else { + console2.log("[FAIL]", names[i], ": multisig does NOT hold DEFAULT_ADMIN_ROLE"); + _failures++; + } + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // Rename: chainlinkPriceFeed -> oraclePriceFeed (commit a3b70c5) + // ───────────────────────────────────────────────────────────────────────── + + function _checkRenameComplete() internal { + console2.log("--- Rename (a3b70c5): chainlinkPriceFeed -> oraclePriceFeed ---"); + + address feed; + try IEigenAdapterView(EIGEN_ADAPTER).oraclePriceFeed() returns (address f) { + feed = f; + } catch { + console2.log("[FAIL] EigenAdapter.oraclePriceFeed() reverted -- upgrade not applied"); + _failures++; + console2.log(""); + return; + } + + if (feed == ORACLE_PRICE_FEED) { + console2.log("[OK] EigenAdapter.oraclePriceFeed() =", feed); + } else if (feed == address(0)) { + console2.log("[FAIL] EigenAdapter.oraclePriceFeed() returned address(0)"); + _failures++; + } else { + console2.log("[WARN] EigenAdapter.oraclePriceFeed() =", feed, "(not canonical address)"); + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // #543: new admin functions on EigenAdapter + // ───────────────────────────────────────────────────────────────────────── + + function _checkNewAdminFunctions() internal { + console2.log("--- #543: new EigenAdapter admin functions ---"); + + try IEigenAdapterNewFns(EIGEN_ADAPTER).updateDelegationApprover(address(0), address(0)) { + console2.log("[FAIL] updateDelegationApprover(0,0) should have reverted ZeroAddress"); + _failures++; + } catch (bytes memory err) { + if (err.length >= 4 && bytes4(err) == bytes4(keccak256("ZeroAddress()"))) { + console2.log("[OK] EigenAdapter.updateDelegationApprover present (ZeroAddress guard active)"); + } else if (err.length == 0) { + console2.log("[FAIL] updateDelegationApprover returned empty revert (selector missing?)"); + _failures++; + } else { + console2.log("[OK] EigenAdapter.updateDelegationApprover present (guarded)"); + } + } + + try IEigenAdapterNewFns(EIGEN_ADAPTER).advanceToWithdrawals(address(0)) { + console2.log("[FAIL] advanceToWithdrawals(0) should have reverted ZeroAddress"); + _failures++; + } catch (bytes memory err) { + if (err.length >= 4 && bytes4(err) == bytes4(keccak256("ZeroAddress()"))) { + console2.log("[OK] EigenAdapter.advanceToWithdrawals present (ZeroAddress guard active)"); + } else if (err.length == 0) { + console2.log("[FAIL] advanceToWithdrawals returned empty revert (selector missing?)"); + _failures++; + } else { + console2.log("[OK] EigenAdapter.advanceToWithdrawals present (guarded)"); + } + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // delay() == 0 on all proxies + // ───────────────────────────────────────────────────────────────────────── + + function _checkDelays() internal { + console2.log("--- RoleActivationTimelock.delay() == 0 ---"); + + address[5] memory proxies = + [ORACLE_PRICE_FEED, EIGEN_ADAPTER, STAKE_MANAGER, SLASHING_MANAGER, SSP_ROUTER]; + string[5] memory names = ["OraclePriceFeed", "EigenAdapter", "StakeManager", "SlashingManager", "SSPRouter"]; + + for (uint256 i = 0; i < proxies.length; ++i) { + uint256 d = IDelayView(proxies[i]).delay(); + if (d == 0) { + console2.log("[OK]", names[i], ".delay() == 0"); + } else { + console2.log("[WARN]", names[i], ".delay() == non-zero:", d); + } + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // SymbioticAdapter NOT deployed + // ───────────────────────────────────────────────────────────────────────── + + function _checkSymbioticAdapterAbsent() internal { + console2.log("--- SymbioticAdapter deployment status ---"); + + address symAdapter = ISSPRouterView(SSP_ROUTER).getAdapter(MODULE_SYMBIOTIC); + if (symAdapter == address(0)) { + console2.log("[OK] SSPRouter.getAdapter(SYMBIOTIC) == 0x0 (not deployed)"); + } else { + console2.log("[INFO] SSPRouter.getAdapter(SYMBIOTIC) =", symAdapter, "(Symbiotic is live)"); + } + + address eigenAdapter = ISSPRouterView(SSP_ROUTER).getAdapter(MODULE_EIGENLAYER); + if (eigenAdapter == EIGEN_ADAPTER) { + console2.log("[OK] SSPRouter.getAdapter(EIGENLAYER) =", eigenAdapter); + } else { + console2.log("[FAIL] SSPRouter.getAdapter(EIGENLAYER) unexpected:", eigenAdapter); + console2.log(" expected:", EIGEN_ADAPTER); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // Core wiring + // ───────────────────────────────────────────────────────────────────────── + + function _checkWiring() internal { + console2.log("--- Core wiring ---"); + + address factory = IStakeManagerView(STAKE_MANAGER).coverPoolFactory(); + if (factory == COVER_POOL_FACTORY) { + console2.log("[OK] StakeManager.coverPoolFactory =", factory); + } else { + console2.log("[FAIL] StakeManager.coverPoolFactory:", factory); + console2.log(" expected:", COVER_POOL_FACTORY); + _failures++; + } + + address router = IStakeManagerView(STAKE_MANAGER).router(); + if (router == SSP_ROUTER) { + console2.log("[OK] StakeManager.router =", router); + } else { + console2.log("[FAIL] StakeManager.router:", router); + console2.log(" expected:", SSP_ROUTER); + _failures++; + } + + address slashingClaim = ISlashingManagerView(SLASHING_MANAGER).claimManager(); + if (slashingClaim == CLAIM_MANAGER) { + console2.log("[OK] SlashingManager.claimManager =", slashingClaim); + } else { + console2.log("[FAIL] SlashingManager.claimManager:", slashingClaim); + console2.log(" expected:", CLAIM_MANAGER); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // PAUSER_ROLE: SlashingManager has it + // ───────────────────────────────────────────────────────────────────────── + + function _checkPauserRole() internal { + console2.log("--- PAUSER_ROLE (core) ---"); + + try ISlashingManagerView(SLASHING_MANAGER).PAUSER_ROLE() returns (bytes32 role) { + bytes32 expected = keccak256("PAUSER_ROLE"); + if (role == expected) { + console2.log("[OK] SlashingManager.PAUSER_ROLE() =", vm.toString(role)); + } else { + console2.log("[FAIL] SlashingManager.PAUSER_ROLE() unexpected value =", vm.toString(role)); + _failures++; + } + } catch { + console2.log("[FAIL] SlashingManager.PAUSER_ROLE() reverted"); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // WETH price feed — must be registered on mainnet + // ───────────────────────────────────────────────────────────────────────── + + function _checkWethFeed() internal { + console2.log("--- OraclePriceFeed WETH feed (mainnet, must be registered) ---"); + + bool hasFeed; + try IOraclePriceFeedView(ORACLE_PRICE_FEED).hasPriceFeed(WETH) returns (bool h) { + hasFeed = h; + } catch { + console2.log("[FAIL] OraclePriceFeed.hasPriceFeed(WETH) reverted"); + _failures++; + console2.log(""); + return; + } + + if (hasFeed) { + console2.log("[OK] OraclePriceFeed.hasPriceFeed(WETH) = true"); + } else { + console2.log("[FAIL] OraclePriceFeed.hasPriceFeed(WETH) = false -- WETH feed missing"); + _failures++; + console2.log(""); + return; + } + + try IOraclePriceFeedView(ORACLE_PRICE_FEED).getUSDValue(WETH, 1e18) returns (uint256 v) { + if (v > 0) { + console2.log("[OK] OraclePriceFeed.getUSDValue(WETH, 1e18) =", v, "(USD, 8 dec)"); + } else { + console2.log("[FAIL] OraclePriceFeed.getUSDValue(WETH) returned 0"); + _failures++; + } + } catch { + console2.log("[FAIL] OraclePriceFeed.getUSDValue(WETH) reverted (stale feed?)"); + _failures++; + } + + console2.log(""); + } +} diff --git a/src/script/VerifyCoreSepolia.s.sol b/src/script/VerifyCoreSepolia.s.sol new file mode 100644 index 00000000..9c23c6c3 --- /dev/null +++ b/src/script/VerifyCoreSepolia.s.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.28; + +import {Script, console2} from "forge-std/Script.sol"; + +/// @dev Minimal view interfaces. +interface IAccessControl { + function hasRole(bytes32 role, address account) external view returns (bool); +} + +interface IDelayView { + function delay() external view returns (uint256); +} + +interface IEigenAdapterView { + function oraclePriceFeed() external view returns (address); + function sspRouter() external view returns (address); +} + +interface IStakeManagerView { + function coverPoolFactory() external view returns (address); + function router() external view returns (address); +} + +interface ISSPRouterView { + function stakeManager() external view returns (address); + function rewardsManager() external view returns (address); + function slashingManager() external view returns (address); + function getAdapter(uint8 moduleType) external view returns (address); +} + +interface ISlashingManagerView { + function claimManager() external view returns (address); + function sspRouter() external view returns (address); + function stakeManager() external view returns (address); + function PAUSER_ROLE() external view returns (bytes32); +} + +interface IOraclePriceFeedView { + function hasPriceFeed(address token) external view returns (bool); +} + +/** + * @title VerifyCoreSepolia + * @notice Verifies that the Core upgrade on Sepolia was applied correctly. + * + * @dev Checks: + * 1. Rename complete: EigenAdapter.oraclePriceFeed() returns the feed address + * (legacy chainlinkPriceFeed() would revert) + * 2. CYS3-01 parameter rename is ABI-compatible (setCommitteeVaults still works) + * 3. CYS3-05: setAdapter checks are live (not verified here — guards fire on future calls) + * 4. #543: new admin functions are present (checked by ABI probe) + * 5. delay == 0 on all proxies (no role-activation cooldown) + * 6. SymbioticAdapter NOT deployed (SSPRouter.getAdapter(SYMBIOTIC) == 0x0) + * 7. Core wiring: StakeManager → coverPoolFactory, router; SSPRouter ↔ managers + * 8. PAUSER_ROLE: SlashingManager has PAUSER_ROLE constant (already deployed pre-upgrade) + * + * @dev Usage: + * forge script src/script/VerifyCoreSepolia.s.sol --rpc-url $SEPOLIA_RPC_URL + */ +contract VerifyCoreSepolia is Script { + address internal constant ORACLE_PRICE_FEED = 0x57F750E3B8e095A5b3AE96030F0cac5dFe2f8A16; + address internal constant EIGEN_ADAPTER = 0xe6168092892E545701D92BEe10149178bE0EEDF4; + address internal constant STAKE_MANAGER = 0x5be5220F81e76e0CF6089fb7E7aE9eF48a8D64Be; + address internal constant REWARDS_MANAGER = 0x8ae0F0B94fe782D7F055B04d4699c3cc01632b46; + address internal constant SLASHING_MANAGER = 0x7Bc39bf135eF3c30E542C196719c91455ff489f0; + address internal constant SSP_ROUTER = 0xF39E592E93A7a925a57464e0120B555A25590486; + + address internal constant COVER_POOL_FACTORY = 0x4f3DbB70cD85bcb63303FBa8610Cb163aDDA4E66; + address internal constant CLAIM_MANAGER = 0x3ceE181C3E78fB9968f0Fb0935d2db0723B9Cb45; + address internal constant PREMIUM_MANAGER = 0xEc7322D6754709d5001B710ec4fB2547a89B3aD1; + + address internal constant WETH = 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9; + + uint8 internal constant MODULE_SYMBIOTIC = 1; + uint8 internal constant MODULE_EIGENLAYER = 2; + + uint256 internal _failures; + + function run() external { + require(block.chainid == 11_155_111, "Must run on Sepolia"); + + console2.log("=== VerifyCoreSepolia ==="); + console2.log("Block:", block.number); + console2.log(""); + + _checkRenameComplete(); + _checkNewAdminFunctions(); + _checkDelays(); + _checkSymbioticAdapterAbsent(); + _checkWiring(); + _checkPauserRole(); + _checkWethFeed(); + + console2.log(""); + if (_failures == 0) { + console2.log("[ALL OK] Core Sepolia upgrade verified successfully."); + } else { + console2.log("[INCOMPLETE]", _failures, "check(s) failed. See [FAIL] lines above."); + } + console2.log("============================="); + } + + // ───────────────────────────────────────────────────────────────────────── + // Rename: chainlinkPriceFeed -> oraclePriceFeed (commit a3b70c5) + // ───────────────────────────────────────────────────────────────────────── + + function _checkRenameComplete() internal { + console2.log("--- Rename (a3b70c5): chainlinkPriceFeed -> oraclePriceFeed ---"); + + address feed; + try IEigenAdapterView(EIGEN_ADAPTER).oraclePriceFeed() returns (address f) { + feed = f; + } catch { + console2.log("[FAIL] EigenAdapter.oraclePriceFeed() reverted -- upgrade not applied"); + _failures++; + console2.log(""); + return; + } + + if (feed == ORACLE_PRICE_FEED) { + console2.log("[OK] EigenAdapter.oraclePriceFeed() =", feed); + } else if (feed == address(0)) { + console2.log("[FAIL] EigenAdapter.oraclePriceFeed() returned address(0)"); + _failures++; + } else { + console2.log("[WARN] EigenAdapter.oraclePriceFeed() =", feed, "(not canonical address)"); + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // #543: new admin functions on EigenAdapter (updateDelegationApprover, etc.) + // ───────────────────────────────────────────────────────────────────────── + + function _checkNewAdminFunctions() internal { + console2.log("--- #543: new EigenAdapter admin functions ---"); + + try IEigenAdapterNewFns(EIGEN_ADAPTER).updateDelegationApprover(address(0), address(0)) { + console2.log("[FAIL] updateDelegationApprover(0,0) should have reverted ZeroAddress"); + _failures++; + } catch (bytes memory err) { + if (err.length >= 4 && bytes4(err) == bytes4(keccak256("ZeroAddress()"))) { + console2.log("[OK] EigenAdapter.updateDelegationApprover present (reverts ZeroAddress as expected)"); + } else if (err.length == 0) { + console2.log("[FAIL] updateDelegationApprover reverted with empty data (selector missing?)"); + _failures++; + } else { + console2.log("[OK] EigenAdapter.updateDelegationApprover present (reverted with expected guard)"); + } + } + + try IEigenAdapterNewFns(EIGEN_ADAPTER).advanceToWithdrawals(address(0)) { + console2.log("[FAIL] advanceToWithdrawals(0) should have reverted ZeroAddress"); + _failures++; + } catch (bytes memory err) { + if (err.length >= 4 && bytes4(err) == bytes4(keccak256("ZeroAddress()"))) { + console2.log("[OK] EigenAdapter.advanceToWithdrawals present (reverts ZeroAddress as expected)"); + } else if (err.length == 0) { + console2.log("[FAIL] advanceToWithdrawals reverted with empty data (selector missing?)"); + _failures++; + } else { + console2.log("[OK] EigenAdapter.advanceToWithdrawals present (reverted with expected guard)"); + } + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // delay() == 0 on all proxies (RoleActivationTimelock — no waiting period) + // ───────────────────────────────────────────────────────────────────────── + + function _checkDelays() internal { + console2.log("--- RoleActivationTimelock.delay() == 0 ---"); + + address[5] memory proxies = + [ORACLE_PRICE_FEED, EIGEN_ADAPTER, STAKE_MANAGER, SLASHING_MANAGER, SSP_ROUTER]; + string[5] memory names = ["OraclePriceFeed", "EigenAdapter", "StakeManager", "SlashingManager", "SSPRouter"]; + + for (uint256 i = 0; i < proxies.length; ++i) { + uint256 d = IDelayView(proxies[i]).delay(); + if (d == 0) { + console2.log("[OK]", names[i], ".delay() == 0"); + } else { + console2.log("[WARN]", names[i], ".delay() == non-zero:", d); + } + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // SymbioticAdapter NOT deployed (SSPRouter.getAdapter(SYMBIOTIC) == 0x0) + // ───────────────────────────────────────────────────────────────────────── + + function _checkSymbioticAdapterAbsent() internal { + console2.log("--- SymbioticAdapter deployment status ---"); + + address symAdapter = ISSPRouterView(SSP_ROUTER).getAdapter(MODULE_SYMBIOTIC); + if (symAdapter == address(0)) { + console2.log("[OK] SSPRouter.getAdapter(SYMBIOTIC) == 0x0 (not deployed, expected)"); + } else { + console2.log("[INFO] SSPRouter.getAdapter(SYMBIOTIC) =", symAdapter, "(Symbiotic is live)"); + } + + address eigenAdapter = ISSPRouterView(SSP_ROUTER).getAdapter(MODULE_EIGENLAYER); + if (eigenAdapter == EIGEN_ADAPTER) { + console2.log("[OK] SSPRouter.getAdapter(EIGENLAYER) =", eigenAdapter); + } else { + console2.log("[FAIL] SSPRouter.getAdapter(EIGENLAYER):", eigenAdapter); + console2.log(" expected:", EIGEN_ADAPTER); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // Core wiring checks + // ───────────────────────────────────────────────────────────────────────── + + function _checkWiring() internal { + console2.log("--- Core wiring ---"); + + address factory = IStakeManagerView(STAKE_MANAGER).coverPoolFactory(); + if (factory == COVER_POOL_FACTORY) { + console2.log("[OK] StakeManager.coverPoolFactory =", factory); + } else { + console2.log("[FAIL] StakeManager.coverPoolFactory:", factory); + console2.log(" expected:", COVER_POOL_FACTORY); + _failures++; + } + + address router = IStakeManagerView(STAKE_MANAGER).router(); + if (router == SSP_ROUTER) { + console2.log("[OK] StakeManager.router =", router); + } else { + console2.log("[FAIL] StakeManager.router:", router); + console2.log(" expected:", SSP_ROUTER); + _failures++; + } + + address slashingClaim = ISlashingManagerView(SLASHING_MANAGER).claimManager(); + if (slashingClaim == CLAIM_MANAGER) { + console2.log("[OK] SlashingManager.claimManager =", slashingClaim); + } else { + console2.log("[FAIL] SlashingManager.claimManager:", slashingClaim); + console2.log(" expected:", CLAIM_MANAGER); + _failures++; + } + + address sspSM = ISSPRouterView(SSP_ROUTER).stakeManager(); + if (sspSM == STAKE_MANAGER) { + console2.log("[OK] SSPRouter.stakeManager =", sspSM); + } else { + console2.log("[FAIL] SSPRouter.stakeManager:", sspSM); + console2.log(" expected:", STAKE_MANAGER); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // PAUSER_ROLE: SlashingManager has it (deployed pre-upgrade) + // ───────────────────────────────────────────────────────────────────────── + + function _checkPauserRole() internal { + console2.log("--- PAUSER_ROLE (core) ---"); + + try ISlashingManagerView(SLASHING_MANAGER).PAUSER_ROLE() returns (bytes32 role) { + bytes32 expected = keccak256("PAUSER_ROLE"); + if (role == expected) { + console2.log("[OK] SlashingManager.PAUSER_ROLE() =", vm.toString(role)); + } else { + console2.log("[FAIL] SlashingManager.PAUSER_ROLE() unexpected value =", vm.toString(role)); + _failures++; + } + } catch { + console2.log("[FAIL] SlashingManager.PAUSER_ROLE() reverted"); + _failures++; + } + + console2.log(""); + } + + // ───────────────────────────────────────────────────────────────────────── + // WETH price feed (Sepolia: may not be registered — informational only) + // ───────────────────────────────────────────────────────────────────────── + + function _checkWethFeed() internal { + console2.log("--- OraclePriceFeed WETH feed (Sepolia) ---"); + + try IOraclePriceFeedView(ORACLE_PRICE_FEED).hasPriceFeed(WETH) returns (bool has) { + if (has) { + console2.log("[OK] OraclePriceFeed.hasPriceFeed(WETH) = true"); + } else { + console2.log("[INFO] OraclePriceFeed.hasPriceFeed(WETH) = false"); + console2.log(" Run: cast send 0x57F750E3... \"setTokenPriceFeed(address,address,uint256)\""); + console2.log(" 0x7b799... 0x694AA1... 3600 to register WETH/USD Chainlink feed."); + } + } catch { + console2.log("[WARN] OraclePriceFeed.hasPriceFeed(WETH) reverted"); + } + + console2.log(""); + } +} + +/// @dev Minimal interface for EigenAdapter new admin functions (#543). +interface IEigenAdapterNewFns { + function updateDelegationApprover(address vault, address newDelegationApprover) external; + function advanceToWithdrawals(address vault) external; + function setRewardsClaimer(address vault, address claimer) external; +} From 8c740bdcfbd2c459fdd0ffe082e4e6845c2e42c3 Mon Sep 17 00:00:00 2001 From: Serge <2901744+evercoinx@users.noreply.github.com> Date: Wed, 20 May 2026 11:38:16 +0200 Subject: [PATCH 3/4] chore: ethereum wiring check incomplete --- src/script/VerifyCoreEthereum.s.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/script/VerifyCoreEthereum.s.sol b/src/script/VerifyCoreEthereum.s.sol index 1301d75d..a7218000 100644 --- a/src/script/VerifyCoreEthereum.s.sol +++ b/src/script/VerifyCoreEthereum.s.sol @@ -279,6 +279,15 @@ contract VerifyCoreEthereum is Script { _failures++; } + address sspSM = ISSPRouterView(SSP_ROUTER).stakeManager(); + if (sspSM == STAKE_MANAGER) { + console2.log("[OK] SSPRouter.stakeManager =", sspSM); + } else { + console2.log("[FAIL] SSPRouter.stakeManager:", sspSM); + console2.log(" expected:", STAKE_MANAGER); + _failures++; + } + console2.log(""); } From 6551815720f4429f58a4006aee5df3c9c709c5a4 Mon Sep 17 00:00:00 2001 From: Serge <2901744+evercoinx@users.noreply.github.com> Date: Wed, 20 May 2026 11:40:59 +0200 Subject: [PATCH 4/4] chore: fix linter issues --- src/script/UpgradeAllContracts.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/UpgradeAllContracts.s.sol b/src/script/UpgradeAllContracts.s.sol index 4730c509..17ff63d4 100644 --- a/src/script/UpgradeAllContracts.s.sol +++ b/src/script/UpgradeAllContracts.s.sol @@ -103,7 +103,7 @@ contract UpgradeAllContracts is Script { * @dev Loads proxy addresses, deploys new implementations, and upgrades all proxies * @return newImpls Struct containing all newly deployed implementation addresses */ - function run() external returns (NewImplementations memory newImpls) { + function run() public virtual returns (NewImplementations memory newImpls) { address deployer = vm.envOr("DEPLOYER_ADDRESS", DEFAULT_DEPLOYER_ADDRESS); ProxyAddresses memory proxies = _loadProxyAddresses();