From 100aaeb2ecd31339ea5eefc2b9f5a02405c9ce1c Mon Sep 17 00:00:00 2001 From: Pankaj Jagtap Date: Wed, 19 Nov 2025 14:48:55 -0500 Subject: [PATCH 1/4] feat: Add Deployed_Hoodi contract with addresses of all deployed contracts on Hoodi testnet --- script/deploys/Deployed_Hoodi.s.sol | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 script/deploys/Deployed_Hoodi.s.sol diff --git a/script/deploys/Deployed_Hoodi.s.sol b/script/deploys/Deployed_Hoodi.s.sol new file mode 100644 index 00000000..22ccfbef --- /dev/null +++ b/script/deploys/Deployed_Hoodi.s.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** + * @title Deployed_Hoodi + * @notice Contains addresses of all deployed contracts on Hoodi testnet + */ +contract Deployed_Hoodi { + // Core Protocol Contracts + address public constant ADDRESS_PROVIDER = 0xd4bBb3Ba0827Ed7abC6977C572910d25a1488296; + address public constant LIQUIDITY_POOL = 0x4a8081095549e63153a61D21F92ff079fe39858E; + address public constant ETHFI = address(0); + address public constant EETH = 0x5595b182162DB7ECfdFE5Ea948d7636b9e250C4D; + address public constant WEETH = 0xd5A50FAE2736CA59Bd6Ac4AF59b1f0fFAB62c4A2; + + // Membership & NFTs + address public constant MEMBERSHIP_MANAGER = 0x79eF7d2d9b68056912Eb020ac65b971017191DE0; + address public constant MEMBERSHIP_NFT = 0x254eAD7aca562D50624b0556729Ca9843b7f6FbB; + address public constant TNFT = 0xd31bC004Ba46A048e272A45A6b24Ed985c4DF5AC; + address public constant BNFT = 0x2B736C58EE03C5d4930a32D3c8F6acd7FbbdA08C; + address public constant WITHDRAW_REQUEST_NFT = 0xb17528f26b0F7ED107E4E17f48bcC2E169Dcb6c1; + + // Staking & Withdrawals + address public constant NODE_OPERATOR_MANAGER = 0x3e17543CaE3366cc67a3CBeD5Aa42d9d09D59b39; + address public constant AUCTION_MANAGER = 0x261315c176864cE29D582f38DdA4930ED17CD95A; + address public constant STAKING_MANAGER = 0xDbE50E32Ed95f539F36bA315a75377FBc35aBc12; + address public constant ETHERFI_NODE_BEACON = 0x7AbD4dF572a4Daaed21b1FdaDE897a5A634a1fd1; + address public constant ETHERFI_NODES_MANAGER = 0x7579194b8265e3Aa7df451c6BD2aff5B1FC5F945; + address public constant ETHERFI_REDEMPTION_MANAGER = 0x95AeCaa1B0C3A04C8aFf5D05f27363e9e3367D6F; + + // Oracle + address public constant ETHERFI_ORACLE = 0x1888Fd1914af6980204AA0424f550d9bE35735e1; + address public constant ETHERFI_ADMIN = 0x0CF5ddcF6861Efd8C498466d162F231E44eB85Dd; + + // Adapters & Liquifiers + address public constant DEPOSIT_ADAPTER = address(0); // MISSING + address public constant LIQUIFIER = 0x2e871581aAcc79EbcF75F9da364f5078FAd9bb4D; + + // AVS & Sync + address public constant ETHERFI_RESTAKER = 0xc27F4dae10Ec60539619F7Deb0E2dBb413df6EAd; + address public constant ETHERFI_AVS_OPERATORS_MANAGER = address(0); // MISSING + address public constant ETHERFI_L1_SYNC_POOL_ETH = address(0); + address public constant ETHERFI_OFT_ADAPTER = address(0); + + // Utilities + address public constant ETHERFI_VIEWER = 0xA239C957951C2237eCd730596629b246E2c75857; + address public constant ETHERFI_REWARDS_ROUTER = 0x703f2f1eC0B82EFe1C16927aEbc4D99536ECF5CE; + address public constant ETHERFI_OPERATION_PARAMETERS = 0x01bc3a772307394E755a3519E6983fEA445B2722; + address public constant ETHERFI_RATE_LIMITER = 0x1e6881572e7bB49B4737ac650bce5587085a4d48; + + address public constant EARLY_ADOPTER_POOL = address(0); + + // role registry & multi-sig + address public constant ROLE_REGISTRY = 0x7279853cA1804d4F705d885FeA7f1662323B5Aab; + address public constant UPGRADE_TIMELOCK = address(0); + address public constant OPERATING_TIMELOCK = address(0); + address public constant ETHERFI_OPERATING_ADMIN = address(0); + address public constant ETHERFI_UPGRADE_ADMIN = address(0); + + // Additional Hoodi-specific contracts + address public constant TREASURY = 0xa16E2fcf1331B2AA90b3a83EC0B54923d74b5E19; + address public constant ETHERFI_TIMELOCK = 0x75AEB07F913a895F1eE2e0a8990B633D1dB00731; + address public constant PROTOCOL_REVENUE_MANAGER = 0xA7C53aCCBB67D803e185E63730BB78C68db2966d; + address public constant REGULATIONS_MANAGER = 0x91E4e2c24f8634f05a46dd88F8d79cA0767575f6; + address public constant BUCKET_RATE_LIMITER = 0x52DbeF0e3E019aafbB654bc80c15ffa4Dcc17566; + address public constant TVL_ORACLE = 0x9B9D42E2D3B3989567de5028A91d9492B8cF68c2; + address public constant ETHERFI_NODE = 0xCb77c1EDf717b551C57c15332700b213c02f1b90; + address public constant MAIN_ADMIN = 0x001000621b95AA950c1a27Bb2e1273e10d8dfF68; + + // External contracts (EigenLayer, Ethereum) + address public constant ETH2_DEPOSIT = 0x00000000219ab540356cBB839Cbe05303d7705Fa; + address public constant EIGEN_POD_MANAGER = 0xcd1442415Fc5C29Aa848A49d2e232720BE07976c; + address public constant DELEGATION_MANAGER = 0x867837a9722C512e0862d8c2E15b8bE220E8b87d; + address public constant REWARDS_COORDINATOR = 0x29e8572678e0c272350aa0b4B8f304E47EBcd5e7; + address public constant BEACON_ORACLE = 0x5e1577f8efB21b229cD5Eb4C5Aa3d6C4b228f650; + address public constant STRATEGY_MANAGER = 0xeE45e76ddbEDdA2918b8C7E3035cd37Eab3b5D41; + address public constant CREATE2_FACTORY = 0x29bd9fc3E826f10288D58bEa41d1258FB3ecF4F0; + + mapping(address => address) public timelockToAdmin; + + constructor() { + } +} From 888efd1f8d53e8c6c8766e774fa42d7bd052838c Mon Sep 17 00:00:00 2001 From: Pankaj Jagtap Date: Thu, 20 Nov 2025 13:51:09 -0500 Subject: [PATCH 2/4] feat: Introduce deployment scripts and salts for Hoodi testnet, including Create2Factory integration --- script/deploys/Deployed_Hoodi.s.sol | 2 +- script/hoodi/Hoodi-Salts.s.sol | 41 ++++ script/hoodi/deploy-hoodi.s.sol | 303 ++++++++++++++++++++++++++++ 3 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 script/hoodi/Hoodi-Salts.s.sol create mode 100644 script/hoodi/deploy-hoodi.s.sol diff --git a/script/deploys/Deployed_Hoodi.s.sol b/script/deploys/Deployed_Hoodi.s.sol index 22ccfbef..ee2bbfaa 100644 --- a/script/deploys/Deployed_Hoodi.s.sol +++ b/script/deploys/Deployed_Hoodi.s.sol @@ -74,7 +74,7 @@ contract Deployed_Hoodi { address public constant REWARDS_COORDINATOR = 0x29e8572678e0c272350aa0b4B8f304E47EBcd5e7; address public constant BEACON_ORACLE = 0x5e1577f8efB21b229cD5Eb4C5Aa3d6C4b228f650; address public constant STRATEGY_MANAGER = 0xeE45e76ddbEDdA2918b8C7E3035cd37Eab3b5D41; - address public constant CREATE2_FACTORY = 0x29bd9fc3E826f10288D58bEa41d1258FB3ecF4F0; + address public constant CREATE2_FACTORY_HOODI = 0x29bd9fc3E826f10288D58bEa41d1258FB3ecF4F0; mapping(address => address) public timelockToAdmin; diff --git a/script/hoodi/Hoodi-Salts.s.sol b/script/hoodi/Hoodi-Salts.s.sol new file mode 100644 index 00000000..c8dc346d --- /dev/null +++ b/script/hoodi/Hoodi-Salts.s.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title Hoodi-Salts + * @notice Salt values for Create2 deployments on Hoodi testnet + * @dev These salts ensure deterministic addresses across deployments + */ +contract HoodiSalts { + // Core Protocol Contracts + bytes32 public constant TREASURY_SALT = keccak256("Treasury"); + bytes32 public constant NODE_OPERATOR_MANAGER_SALT = keccak256("NodeOperatorManager"); + bytes32 public constant AUCTION_MANAGER_SALT = keccak256("AuctionManager"); + bytes32 public constant STAKING_MANAGER_SALT = keccak256("StakingManager"); + bytes32 public constant ETHERFI_NODE_SALT = keccak256("EtherFiNode"); + bytes32 public constant BNFT_SALT = keccak256("BNFT"); + bytes32 public constant TNFT_SALT = keccak256("TNFT"); + bytes32 public constant PROTOCOL_REVENUE_MANAGER_SALT = keccak256("ProtocolRevenueManager"); + bytes32 public constant ETHERFI_NODES_MANAGER_SALT = keccak256("EtherFiNodesManager"); + bytes32 public constant REGULATIONS_MANAGER_SALT = keccak256("RegulationsManager"); + bytes32 public constant MEMBERSHIP_MANAGER_SALT = keccak256("MembershipManager"); + bytes32 public constant WITHDRAW_REQUEST_NFT_SALT = keccak256("WithdrawRequestNFT"); + bytes32 public constant MEMBERSHIP_NFT_SALT = keccak256("MembershipNFT"); + bytes32 public constant LIQUIDITY_POOL_SALT = keccak256("LiquidityPool"); + bytes32 public constant EETH_SALT = keccak256("EETH"); + bytes32 public constant WEETH_SALT = keccak256("WeETH"); + bytes32 public constant ETHERFI_ORACLE_SALT = keccak256("EtherFiOracle"); + bytes32 public constant ETHERFI_ADMIN_SALT = keccak256("EtherFiAdmin"); + bytes32 public constant ROLE_REGISTRY_SALT = keccak256("RoleRegistry"); + bytes32 public constant ETHERFI_OPERATION_PARAMETERS_SALT = keccak256("EtherFiOperationParameters"); + bytes32 public constant BUCKET_RATE_LIMITER_SALT = keccak256("BucketRateLimiter"); + bytes32 public constant TVL_ORACLE_SALT = keccak256("TVLOracle"); + bytes32 public constant ETHERFI_TIMELOCK_SALT = keccak256("EtherFiTimelock"); + bytes32 public constant LIQUIFIER_SALT = keccak256("Liquifier"); + bytes32 public constant ETHERFI_RESTAKER_SALT = keccak256("EtherFiRestaker"); + bytes32 public constant ETHERFI_REWARDS_ROUTER_SALT = keccak256("EtherFiRewardsRouter"); + bytes32 public constant ADDRESS_PROVIDER_SALT = keccak256("AddressProvider"); + bytes32 public constant ETHERFI_VIEWER_SALT = keccak256("EtherFiViewer"); + bytes32 public constant ETHERFI_REDEMPTION_MANAGER_SALT = keccak256("EtherFiRedemptionManager"); +} + diff --git a/script/hoodi/deploy-hoodi.s.sol b/script/hoodi/deploy-hoodi.s.sol new file mode 100644 index 00000000..7b58253a --- /dev/null +++ b/script/hoodi/deploy-hoodi.s.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title DeployHoodi + * @notice Script to deploy contract implementations on Hoodi testnet using Create2Factory + * + * @dev Usage: + * 1. Ensure contracts are compiled: `forge build` + * 2. Ensure Create2Factory is deployed at CREATE2_FACTORY_HOODI address + * 3. Set HOODI_RPC_URL environment variable or use --fork-url flag + * 4. Set HOODI_PRIVATE_KEY environment variable + * 5. Run: `forge script script/hoodi/deploy-hoodi.s.sol --fork-url $HOODI_RPC_URL -vvv --broadcast --verify` + * + * @dev Create2 Deployment: + * - Uses Create2Factory for deterministic addresses + * - Salts are defined in Hoodi-Salts.s.sol + * - All implementations are deployed using Create2 + */ + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; +import {Deployed_Hoodi} from "../deploys/Deployed_Hoodi.s.sol"; +import {HoodiSalts} from "./Hoodi-Salts.s.sol"; +import {StakingManager} from "../../src/StakingManager.sol"; +import {EtherFiNodesManager} from "../../src/EtherFiNodesManager.sol"; +import {EtherFiRedemptionManager} from "../../src/EtherFiRedemptionManager.sol"; +import {EtherFiNode} from "../../src/EtherFiNode.sol"; + +interface ICreate2Factory { + function deploy(bytes memory code, string memory contractName) external payable returns (address); +} + +contract DeployHoodi is Script, Deployed_Hoodi, HoodiSalts { + // Create2Factory for deterministic deployments + ICreate2Factory constant factory = ICreate2Factory(CREATE2_FACTORY_HOODI); + + // Artifact paths for contracts (no constructor params) + string constant LIQUIDITY_POOL_ARTIFACT = "LiquidityPool.sol:LiquidityPool"; + string constant ETHERFI_ADMIN_ARTIFACT = "EtherFiAdmin.sol:EtherFiAdmin"; + string constant TNFT_ARTIFACT = "TNFT.sol:TNFT"; + string constant ETHERFI_VIEWER_ARTIFACT = "helpers/EtherFiViewer.sol:EtherFiViewer"; + string constant ETHERFI_REWARDS_ROUTER_ARTIFACT = "EtherFiRewardsRouter.sol:EtherFiRewardsRouter"; + string constant EETH_ARTIFACT = "EETH.sol:EETH"; + string constant WEETH_ARTIFACT = "WeETH.sol:WeETH"; + string constant MEMBERSHIP_MANAGER_ARTIFACT = "MembershipManager.sol:MembershipManager"; + string constant WITHDRAW_REQUEST_NFT_ARTIFACT = "WithdrawRequestNFT.sol:WithdrawRequestNFT"; + string constant BUCKET_RATE_LIMITER_ARTIFACT = "BucketRateLimiter.sol:BucketRateLimiter"; + + // Contract names for Create2Factory (must match Hoodi-Salts.s.sol) + string constant STAKING_MANAGER_NAME = "StakingManager"; + string constant ETHERFI_NODE_NAME = "EtherFiNode"; + string constant ETHERFI_NODES_MANAGER_NAME = "EtherFiNodesManager"; + string constant ETHERFI_REDEMPTION_MANAGER_NAME = "EtherFiRedemptionManager"; + + // Deployed implementation addresses + address public stakingManagerImpl; + address public etherFiNodeImpl; + address public etherFiNodesManagerImpl; + address public etherFiRedemptionManagerImpl; + + function run() external { + // Fork Hoodi testnet + string memory rpc; + try vm.envString("HOODI_RPC_URL") returns (string memory envRpc) { + rpc = envRpc; + } catch { + try vm.rpcUrl("hoodi") returns (string memory configRpc) { + rpc = configRpc; + } catch { + revert("Please set HOODI_RPC_URL env var, add 'hoodi' to foundry.toml rpc_endpoints, or use --fork-url flag"); + } + } + vm.createSelectFork(rpc); + + uint256 deployerPrivateKey = vm.envUint("HOODI_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + console2.log("========================================"); + console2.log("Deploying Hoodi Contract Implementations (Create2)"); + console2.log("========================================"); + console2.log("Deployer: %s", vm.toString(deployer)); + console2.log("Create2Factory: %s", vm.toString(address(factory))); + console2.log(""); + + // vm.startBroadcast(deployerPrivateKey); + vm.startPrank(deployer); + + deployStakingManager(); + deployEtherFiNodesManager(); + deployEtherFiRedemptionManager(); + deployEtherFiNode(); + + // vm.stopBroadcast(); + vm.stopPrank(); + + // Print summary + printSummary(); + } + + function deployStakingManager() internal { + console2.log("Deploying STAKING_MANAGER implementation..."); + stakingManagerImpl = _deployStakingManager(); + console2.log(" [OK] STAKING_MANAGER deployed: %s\n", vm.toString(stakingManagerImpl)); + } + + function deployEtherFiNodesManager() internal { + console2.log("Deploying ETHERFI_NODES_MANAGER implementation..."); + etherFiNodesManagerImpl = _deployEtherFiNodesManager(); + console2.log(" [OK] ETHERFI_NODES_MANAGER deployed: %s\n", vm.toString(etherFiNodesManagerImpl)); + } + + function deployEtherFiRedemptionManager() internal { + console2.log("Deploying ETHERFI_REDEMPTION_MANAGER implementation..."); + etherFiRedemptionManagerImpl = _deployEtherFiRedemptionManager(); + console2.log(" [OK] ETHERFI_REDEMPTION_MANAGER deployed: %s\n", vm.toString(etherFiRedemptionManagerImpl)); + } + + function deployEtherFiNode() internal { + console2.log("Deploying ETHERFI_NODE implementation..."); + etherFiNodeImpl = _deployEtherFiNode(); + console2.log(" [OK] ETHERFI_NODE deployed: %s\n", vm.toString(etherFiNodeImpl)); + } + + // Internal deployment functions + /// @notice Deploys implementation using Create2Factory for deterministic addresses + /// @param artifactPath Path to contract artifact (e.g., "LiquidityPool.sol:LiquidityPool") + /// @param contractName Contract name for Create2 salt (must match Hoodi-Salts.s.sol) + function _deployImplementation(string memory artifactPath, string memory contractName) internal returns (address) { + bytes memory bytecode = vm.getCode(artifactPath); + require(bytecode.length > 0, "Failed to get bytecode"); + + bytes32 salt = keccak256(abi.encodePacked(contractName)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, contractName); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + require(deployedAddress != address(0), "Failed to deploy implementation"); + + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployStakingManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + ETHERFI_NODES_MANAGER, + ETH2_DEPOSIT, + AUCTION_MANAGER, + ETHERFI_NODE_BEACON, + ROLE_REGISTRY + ); + bytes memory bytecode = abi.encodePacked( + type(StakingManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(STAKING_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, STAKING_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiNodesManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + STAKING_MANAGER, + ROLE_REGISTRY, + ETHERFI_RATE_LIMITER + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiNodesManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODES_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_NODES_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiRedemptionManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + EETH, + WEETH, + TREASURY, + ROLE_REGISTRY, + ETHERFI_RESTAKER + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiRedemptionManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_REDEMPTION_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_REDEMPTION_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiNode() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + ETHERFI_NODES_MANAGER, + EIGEN_POD_MANAGER, + DELEGATION_MANAGER, + ROLE_REGISTRY + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiNode).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODE_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_NODE_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function printSummary() internal view { + console2.log("\n========================================"); + console2.log("Deployment Summary"); + console2.log("========================================"); + console2.log("STAKING_MANAGER: %s", vm.toString(stakingManagerImpl)); + console2.log("ETHERFI_NODES_MANAGER: %s", vm.toString(etherFiNodesManagerImpl)); + console2.log("ETHERFI_REDEMPTION_MANAGER: %s", vm.toString(etherFiRedemptionManagerImpl)); + console2.log("ETHERFI_NODE: %s", vm.toString(etherFiNodeImpl)); + console2.log(""); + } +} + From 4a58ca55d05b1cd5515f7d0b74ae8c3dd941b087 Mon Sep 17 00:00:00 2001 From: Pankaj Jagtap Date: Thu, 20 Nov 2025 14:09:30 -0500 Subject: [PATCH 3/4] feat: Add UpgradeHoodi script for upgrading contracts on Hoodi testnet using Create2Factory --- script/hoodi/Upgrade-hoodi.s.sol | 322 +++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 script/hoodi/Upgrade-hoodi.s.sol diff --git a/script/hoodi/Upgrade-hoodi.s.sol b/script/hoodi/Upgrade-hoodi.s.sol new file mode 100644 index 00000000..62148fbd --- /dev/null +++ b/script/hoodi/Upgrade-hoodi.s.sol @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +/** + * @title UpgradeHoodi + * @notice Script to upgrade contracts on Hoodi testnet that need upgrades using Create2Factory + * + * @dev Usage: + * 1. Ensure contracts are compiled: `forge build` + * 2. Ensure Create2Factory is deployed at CREATE2_FACTORY_HOODI address + * 3. Set HOODI_RPC_URL environment variable or use --fork-url flag + * 4. Set HOODI_PRIVATE_KEY environment variable (must be protocol upgrader for most contracts, owner for some) + * 5. Run: `forge script script/hoodi/Upgrade-hoodi.s.sol --fork-url $HOODI_RPC_URL -vvv --broadcast` + * + * @dev Create2 Deployment: + * - Uses Create2Factory for deterministic addresses + * - Salts are defined in Hoodi-Salts.s.sol + * - All implementations are deployed using Create2 before upgrading proxies + */ + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; +import {Deployed_Hoodi} from "../deploys/Deployed_Hoodi.s.sol"; +import {HoodiSalts} from "./Hoodi-Salts.s.sol"; +import {IRoleRegistry} from "../../src/interfaces/IRoleRegistry.sol"; +import {IStakingManager} from "../../src/interfaces/IStakingManager.sol"; +import {StakingManager} from "../../src/StakingManager.sol"; +import {EtherFiNodesManager} from "../../src/EtherFiNodesManager.sol"; +import {EtherFiRedemptionManager} from "../../src/EtherFiRedemptionManager.sol"; +import {EtherFiNode} from "../../src/EtherFiNode.sol"; + +interface IUpgradeable { + function upgradeTo(address newImplementation) external; +} + +interface ICreate2Factory { + function deploy(bytes memory code, string memory contractName) external payable returns (address); +} + +contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { + // Create2Factory for deterministic deployments + ICreate2Factory constant factory = ICreate2Factory(CREATE2_FACTORY_HOODI); + + // Contract addresses that need upgrades + address constant STAKING_MANAGER_PROXY = 0xDbE50E32Ed95f539F36bA315a75377FBc35aBc12; + address constant ETHERFI_NODES_MANAGER_PROXY = 0x7579194b8265e3Aa7df451c6BD2aff5B1FC5F945; + address constant ETHERFI_REDEMPTION_MANAGER_PROXY = 0x95AeCaa1B0C3A04C8aFf5D05f27363e9e3367D6F; + + // Contract names for Create2Factory (must match Hoodi-Salts.s.sol) + string constant STAKING_MANAGER_NAME = "StakingManager"; + string constant ETHERFI_NODE_NAME = "EtherFiNode"; + string constant ETHERFI_NODES_MANAGER_NAME = "EtherFiNodesManager"; + string constant ETHERFI_REDEMPTION_MANAGER_NAME = "EtherFiRedemptionManager"; + + struct UpgradeResult { + string name; + bool upgraded; + string reason; + } + + UpgradeResult[] public upgradeResults; + + // Deployer address (set in run()) + address public deployer; + + function run() external { + // Fork Hoodi testnet + string memory rpc; + try vm.envString("HOODI_RPC_URL") returns (string memory envRpc) { + rpc = envRpc; + } catch { + try vm.rpcUrl("hoodi") returns (string memory configRpc) { + rpc = configRpc; + } catch { + revert("Please set HOODI_RPC_URL env var, add 'hoodi' to foundry.toml rpc_endpoints, or use --fork-url flag"); + } + } + vm.createSelectFork(rpc); + + uint256 deployerPrivateKey = vm.envUint("HOODI_PRIVATE_KEY"); + deployer = vm.addr(deployerPrivateKey); + + console2.log("========================================"); + console2.log("Upgrading Hoodi Contracts (Create2)"); + console2.log("========================================"); + console2.log("Deployer: %s", vm.toString(deployer)); + console2.log("Create2Factory: %s", vm.toString(address(factory))); + console2.log(""); + + // Get RoleRegistry to check protocol upgrader + IRoleRegistry roleRegistry = IRoleRegistry(ROLE_REGISTRY); + address protocolUpgrader = roleRegistry.owner(); + console2.log("Protocol Upgrader (RoleRegistry owner): %s", vm.toString(protocolUpgrader)); + + // Check if deployer is protocol upgrader + bool isProtocolUpgrader = (deployer == protocolUpgrader); + console2.log("Deployer is protocol upgrader: %s\n", isProtocolUpgrader ? "YES" : "NO"); + + vm.startBroadcast(deployerPrivateKey); + console2.log("Broadcasting from: %s", vm.toString(deployer)); + + // Upgrade contracts that require protocol upgrader + if (isProtocolUpgrader) { + upgradeStakingManager(); + upgradeEtherFiNodesManager(); + upgradeEtherFiRedemptionManager(); + upgradeEtherFiNodeBeacon(); + } else { + console2.log("[SKIP] Skipping protocol upgrader contracts (deployer is not protocol upgrader)\n"); + } + + vm.stopBroadcast(); + + // Print summary + printSummary(); + } + + // Protocol Upgrader Contracts + function upgradeStakingManager() internal { + console2.log("Upgrading STAKING_MANAGER..."); + address impl = _deployStakingManager(); + IUpgradeable(STAKING_MANAGER_PROXY).upgradeTo(impl); + console2.log(" [OK] STAKING_MANAGER upgraded: %s\n", vm.toString(impl)); + upgradeResults.push(UpgradeResult("STAKING_MANAGER", true, "")); + } + + function upgradeEtherFiNodesManager() internal { + console2.log("Upgrading ETHERFI_NODES_MANAGER..."); + address impl = _deployEtherFiNodesManager(); + IUpgradeable(ETHERFI_NODES_MANAGER_PROXY).upgradeTo(impl); + console2.log(" [OK] ETHERFI_NODES_MANAGER upgraded: %s\n", vm.toString(impl)); + upgradeResults.push(UpgradeResult("ETHERFI_NODES_MANAGER", true, "")); + } + + function upgradeEtherFiRedemptionManager() internal { + console2.log("Upgrading ETHERFI_REDEMPTION_MANAGER..."); + address impl = _deployEtherFiRedemptionManager(); + IUpgradeable(ETHERFI_REDEMPTION_MANAGER_PROXY).upgradeTo(impl); + console2.log(" [OK] ETHERFI_REDEMPTION_MANAGER upgraded: %s\n", vm.toString(impl)); + upgradeResults.push(UpgradeResult("ETHERFI_REDEMPTION_MANAGER", true, "")); + } + + function upgradeEtherFiNodeBeacon() internal { + console2.log("Upgrading ETHERFI_NODE_BEACON..."); + address impl = _deployEtherFiNode(); + IStakingManager(STAKING_MANAGER_PROXY).upgradeEtherFiNode(impl); + console2.log(" [OK] ETHERFI_NODE_BEACON upgraded: %s\n", vm.toString(impl)); + upgradeResults.push(UpgradeResult("ETHERFI_NODE_BEACON", true, "")); + } + + // Internal deployment functions + + function _deployStakingManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + ETHERFI_NODES_MANAGER, + ETH2_DEPOSIT, + AUCTION_MANAGER, + ETHERFI_NODE_BEACON, + ROLE_REGISTRY + ); + bytes memory bytecode = abi.encodePacked( + type(StakingManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(STAKING_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, STAKING_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiNodesManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + STAKING_MANAGER, + ROLE_REGISTRY, + ETHERFI_RATE_LIMITER + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiNodesManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODES_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_NODES_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiRedemptionManager() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + EETH, + WEETH, + TREASURY, + ROLE_REGISTRY, + ETHERFI_RESTAKER + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiRedemptionManager).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_REDEMPTION_MANAGER_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_REDEMPTION_MANAGER_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function _deployEtherFiNode() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + ETHERFI_NODES_MANAGER, + EIGEN_POD_MANAGER, + DELEGATION_MANAGER, + ROLE_REGISTRY + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiNode).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODE_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + + address deployedAddress = factory.deploy(bytecode, ETHERFI_NODE_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + + function printSummary() internal view { + console2.log("\n========================================"); + console2.log("Upgrade Summary"); + console2.log("========================================"); + + uint256 successCount = 0; + uint256 failCount = 0; + uint256 skipCount = 0; + + for (uint256 i = 0; i < upgradeResults.length; i++) { + UpgradeResult memory result = upgradeResults[i]; + if (result.upgraded) { + console2.log("[OK] %s", result.name); + successCount++; + } else if (bytes(result.reason).length > 0) { + console2.log("[%s] %s: %s", + keccak256(bytes(result.reason)) == keccak256(bytes("Not owner")) ? "SKIP" : "FAIL", + result.name, + result.reason + ); + if (keccak256(bytes(result.reason)) == keccak256(bytes("Not owner"))) { + skipCount++; + } else { + failCount++; + } + } else { + console2.log("[FAIL] %s", result.name); + failCount++; + } + } + + console2.log("\nTotal: %d upgraded, %d failed, %d skipped", successCount, failCount, skipCount); + } +} From dc646252ef719a43b42b8ebdfebd399191aeabf3 Mon Sep 17 00:00:00 2001 From: Pankaj Jagtap Date: Fri, 21 Nov 2025 15:44:07 -0500 Subject: [PATCH 4/4] feat: Remove obsolete DeployHoodi script and introduce EtherFiRedemptionManagerTemp for contract upgrades on Hoodi testnet --- script/hoodi/EtherFiRedemptionManagerTemp.sol | 329 ++++++++++++++++++ script/hoodi/Upgrade-hoodi.s.sol | 54 ++- script/hoodi/deploy-hoodi.s.sol | 303 ---------------- 3 files changed, 376 insertions(+), 310 deletions(-) create mode 100644 script/hoodi/EtherFiRedemptionManagerTemp.sol delete mode 100644 script/hoodi/deploy-hoodi.s.sol diff --git a/script/hoodi/EtherFiRedemptionManagerTemp.sol b/script/hoodi/EtherFiRedemptionManagerTemp.sol new file mode 100644 index 00000000..46e76bd0 --- /dev/null +++ b/script/hoodi/EtherFiRedemptionManagerTemp.sol @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol"; +import "@openzeppelin-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin-upgradeable/contracts/security/PausableUpgradeable.sol"; + +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/math/Math.sol"; + +import "../../src/interfaces/ILiquidityPool.sol"; +import "../../src/interfaces/IeETH.sol"; +import "../../src/interfaces/IWeETH.sol"; + +import "lib/BucketLimiter.sol"; + +import "../../src/RoleRegistry.sol"; + +/* + The contract allows instant redemption of eETH and weETH tokens to ETH with an exit fee. + - It has the exit fee as a percentage of the total amount redeemed. + - It has a rate limiter to limit the total amount that can be redeemed in a given time period. +*/ +contract EtherFiRedemptionManagerTemp is Initializable, PausableUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable { + using SafeERC20 for IERC20; + using Math for uint256; + + uint256 private constant BUCKET_UNIT_SCALE = 1e12; + uint256 private constant BASIS_POINT_SCALE = 1e4; + + bytes32 public constant ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE = keccak256("ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE"); + + RoleRegistry public immutable roleRegistry; + address public immutable treasury; + IeETH public immutable eEth; + IWeETH public immutable weEth; + ILiquidityPool public immutable liquidityPool; + + BucketLimiter.Limit public limit; + uint16 public exitFeeSplitToTreasuryInBps; + uint16 public exitFeeInBps; + uint16 public lowWatermarkInBpsOfTvl; // bps of TVL + + event Redeemed(address indexed receiver, uint256 redemptionAmount, uint256 feeAmountToTreasury, uint256 feeAmountToStakers); + + receive() external payable {} + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address _liquidityPool, address _eEth, address _weEth, address _treasury, address _roleRegistry) { + roleRegistry = RoleRegistry(_roleRegistry); + treasury = _treasury; + liquidityPool = ILiquidityPool(payable(_liquidityPool)); + eEth = IeETH(_eEth); + weEth = IWeETH(_weEth); + + _disableInitializers(); + } + + function initialize(uint16 _exitFeeSplitToTreasuryInBps, uint16 _exitFeeInBps, uint16 _lowWatermarkInBpsOfTvl, uint256 _bucketCapacity, uint256 _bucketRefillRate) external initializer { + require(_exitFeeInBps <= BASIS_POINT_SCALE, "INVALID"); + require(_exitFeeSplitToTreasuryInBps <= BASIS_POINT_SCALE, "INVALID"); + require(_lowWatermarkInBpsOfTvl <= BASIS_POINT_SCALE, "INVALID"); + + __UUPSUpgradeable_init(); + __Pausable_init(); + __ReentrancyGuard_init(); + + limit = BucketLimiter.create(_convertToBucketUnit(_bucketCapacity, Math.Rounding.Down), _convertToBucketUnit(_bucketRefillRate, Math.Rounding.Down)); + exitFeeSplitToTreasuryInBps = _exitFeeSplitToTreasuryInBps; + exitFeeInBps = _exitFeeInBps; + lowWatermarkInBpsOfTvl = _lowWatermarkInBpsOfTvl; + } + + function clearOutSlotForUpgrade() external { + require(msg.sender == roleRegistry.owner(), "IncorrectCaller"); + delete limit; + delete exitFeeSplitToTreasuryInBps; + delete exitFeeInBps; + delete lowWatermarkInBpsOfTvl; + } + + /** + * @notice Redeems eETH for ETH. + * @param eEthAmount The amount of eETH to redeem after the exit fee. + * @param receiver The address to receive the redeemed ETH. + */ + function redeemEEth(uint256 eEthAmount, address receiver) public whenNotPaused nonReentrant { + _redeemEEth(eEthAmount, receiver); + } + + /** + * @notice Redeems weETH for ETH. + * @param weEthAmount The amount of weETH to redeem after the exit fee. + * @param receiver The address to receive the redeemed ETH. + */ + function redeemWeEth(uint256 weEthAmount, address receiver) public whenNotPaused nonReentrant { + _redeemWeEth(weEthAmount, receiver); + } + + /** + * @notice Redeems eETH for ETH with permit. + * @param eEthAmount The amount of eETH to redeem after the exit fee. + * @param receiver The address to receive the redeemed ETH. + * @param permit The permit params. + */ + function redeemEEthWithPermit(uint256 eEthAmount, address receiver, IeETH.PermitInput calldata permit) external whenNotPaused nonReentrant { + try eEth.permit(msg.sender, address(this), permit.value, permit.deadline, permit.v, permit.r, permit.s) {} catch {} + _redeemEEth(eEthAmount, receiver); + } + + /** + * @notice Redeems weETH for ETH. + * @param weEthAmount The amount of weETH to redeem after the exit fee. + * @param receiver The address to receive the redeemed ETH. + * @param permit The permit params. + */ + function redeemWeEthWithPermit(uint256 weEthAmount, address receiver, IWeETH.PermitInput calldata permit) external whenNotPaused nonReentrant { + try weEth.permit(msg.sender, address(this), permit.value, permit.deadline, permit.v, permit.r, permit.s) {} catch {} + _redeemWeEth(weEthAmount, receiver); + } + + /** + * @notice Redeems ETH. + * @param ethAmount The amount of ETH to redeem after the exit fee. + * @param receiver The address to receive the redeemed ETH. + */ + function _redeem(uint256 ethAmount, uint256 eEthShares, address receiver, uint256 eEthAmountToReceiver, uint256 eEthFeeAmountToTreasury, uint256 sharesToBurn, uint256 feeShareToTreasury) internal { + _updateRateLimit(ethAmount); + + // Derive additionals + uint256 eEthShareFee = eEthShares - sharesToBurn; + uint256 feeShareToStakers = eEthShareFee - feeShareToTreasury; + + // Snapshot balances & shares for sanity check at the end + uint256 prevBalance = address(this).balance; + uint256 prevLpBalance = address(liquidityPool).balance; + uint256 totalEEthShare = eEth.totalShares(); + + // Withdraw ETH from the liquidity pool + require(liquidityPool.withdraw(address(this), eEthAmountToReceiver) == sharesToBurn, "invalid num shares burnt"); + uint256 ethReceived = address(this).balance - prevBalance; + + // To Stakers by burning shares + liquidityPool.burnEEthShares(feeShareToStakers); + + // To Treasury by transferring eETH + IERC20(address(eEth)).safeTransfer(treasury, eEthFeeAmountToTreasury); + + // uint256 totalShares = eEth.totalShares(); + require(eEth.totalShares() >= 1 gwei && eEth.totalShares() == totalEEthShare - (sharesToBurn + feeShareToStakers), "EtherFiRedemptionManager: Invalid total shares"); + + // To Receiver by transferring ETH, using gas 10k for additional safety + (bool success, ) = receiver.call{value: ethReceived, gas: 10_000}(""); + require(success, "EtherFiRedemptionManager: Transfer failed"); + + // Make sure the liquidity pool balance is correct && total shares are correct + require(address(liquidityPool).balance == prevLpBalance - ethReceived, "EtherFiRedemptionManager: Invalid liquidity pool balance"); + // require(eEth.totalShares() >= 1 gwei && eEth.totalShares() == totalEEthShare - (sharesToBurn + feeShareToStakers), "EtherFiRedemptionManager: Invalid total shares"); + + emit Redeemed(receiver, ethAmount, eEthFeeAmountToTreasury, eEthAmountToReceiver); + } + + /** + * @dev if the contract has less than the low watermark, it will not allow any instant redemption. + */ + function lowWatermarkInETH() public view returns (uint256) { + return liquidityPool.getTotalPooledEther().mulDiv(lowWatermarkInBpsOfTvl, BASIS_POINT_SCALE); + } + + /** + * @dev Returns the total amount that can be redeemed. + */ + function totalRedeemableAmount() external view returns (uint256) { + uint256 liquidEthAmount = address(liquidityPool).balance - liquidityPool.ethAmountLockedForWithdrawal(); + if (liquidEthAmount < lowWatermarkInETH()) { + return 0; + } + uint64 consumableBucketUnits = BucketLimiter.consumable(limit); + uint256 consumableAmount = _convertFromBucketUnit(consumableBucketUnits); + return Math.min(consumableAmount, liquidEthAmount); + } + + /** + * @dev Returns whether the given amount can be redeemed. + * @param amount The ETH or eETH amount to check. + */ + function canRedeem(uint256 amount) public view returns (bool) { + uint256 liquidEthAmount = address(liquidityPool).balance - liquidityPool.ethAmountLockedForWithdrawal(); + if (liquidEthAmount < lowWatermarkInETH()) { + return false; + } + uint64 bucketUnit = _convertToBucketUnit(amount, Math.Rounding.Up); + bool consumable = BucketLimiter.canConsume(limit, bucketUnit); + return consumable && amount <= liquidEthAmount; + } + + /** + * @dev Sets the maximum size of the bucket that can be consumed in a given time period. + * @param capacity The capacity of the bucket. + */ + function setCapacity(uint256 capacity) external hasRole(ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE) { + // max capacity = max(uint64) * 1e12 ~= 16 * 1e18 * 1e12 = 16 * 1e12 ether, which is practically enough + uint64 bucketUnit = _convertToBucketUnit(capacity, Math.Rounding.Down); + BucketLimiter.setCapacity(limit, bucketUnit); + } + + /** + * @dev Sets the rate at which the bucket is refilled per second. + * @param refillRate The rate at which the bucket is refilled per second. + */ + function setRefillRatePerSecond(uint256 refillRate) external hasRole(ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE) { + // max refillRate = max(uint64) * 1e12 ~= 16 * 1e18 * 1e12 = 16 * 1e12 ether per second, which is practically enough + uint64 bucketUnit = _convertToBucketUnit(refillRate, Math.Rounding.Down); + BucketLimiter.setRefillRate(limit, bucketUnit); + } + + /** + * @dev Sets the exit fee. + * @param _exitFeeInBps The exit fee. + */ + function setExitFeeBasisPoints(uint16 _exitFeeInBps) external hasRole(ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE) { + require(_exitFeeInBps <= BASIS_POINT_SCALE, "INVALID"); + exitFeeInBps = _exitFeeInBps; + } + + function setLowWatermarkInBpsOfTvl(uint16 _lowWatermarkInBpsOfTvl) external hasRole(ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE) { + require(_lowWatermarkInBpsOfTvl <= BASIS_POINT_SCALE, "INVALID"); + lowWatermarkInBpsOfTvl = _lowWatermarkInBpsOfTvl; + } + + function setExitFeeSplitToTreasuryInBps(uint16 _exitFeeSplitToTreasuryInBps) external hasRole(ETHERFI_REDEMPTION_MANAGER_ADMIN_ROLE) { + require(_exitFeeSplitToTreasuryInBps <= BASIS_POINT_SCALE, "INVALID"); + exitFeeSplitToTreasuryInBps = _exitFeeSplitToTreasuryInBps; + } + + function pauseContract() external hasRole(roleRegistry.PROTOCOL_PAUSER()) { + _pause(); + } + + function unPauseContract() external hasRole(roleRegistry.PROTOCOL_UNPAUSER()) { + _unpause(); + } + + function _redeemEEth(uint256 eEthAmount, address receiver) internal { + require(eEthAmount <= eEth.balanceOf(msg.sender), "EtherFiRedemptionManager: Insufficient balance"); + require(canRedeem(eEthAmount), "EtherFiRedemptionManager: Exceeded total redeemable amount"); + + (uint256 eEthShares, uint256 eEthAmountToReceiver, uint256 eEthFeeAmountToTreasury, uint256 sharesToBurn, uint256 feeShareToTreasury) = _calcRedemption(eEthAmount); + + IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), eEthAmount); + + _redeem(eEthAmount, eEthShares, receiver, eEthAmountToReceiver, eEthFeeAmountToTreasury, sharesToBurn, feeShareToTreasury); + } + + function _redeemWeEth(uint256 weEthAmount, address receiver) internal { + uint256 eEthAmount = weEth.getEETHByWeETH(weEthAmount); + require(weEthAmount <= weEth.balanceOf(msg.sender), "EtherFiRedemptionManager: Insufficient balance"); + require(canRedeem(eEthAmount), "EtherFiRedemptionManager: Exceeded total redeemable amount"); + + (uint256 eEthShares, uint256 eEthAmountToReceiver, uint256 eEthFeeAmountToTreasury, uint256 sharesToBurn, uint256 feeShareToTreasury) = _calcRedemption(eEthAmount); + + IERC20(address(weEth)).safeTransferFrom(msg.sender, address(this), weEthAmount); + weEth.unwrap(weEthAmount); + + _redeem(eEthAmount, eEthShares, receiver, eEthAmountToReceiver, eEthFeeAmountToTreasury, sharesToBurn, feeShareToTreasury); + } + + + function _updateRateLimit(uint256 amount) internal { + uint64 bucketUnit = _convertToBucketUnit(amount, Math.Rounding.Up); + require(BucketLimiter.consume(limit, bucketUnit), "BucketRateLimiter: rate limit exceeded"); + } + + function _convertToBucketUnit(uint256 amount, Math.Rounding rounding) internal pure returns (uint64) { + require(amount < type(uint64).max * BUCKET_UNIT_SCALE, "EtherFiRedemptionManager: Amount too large"); + return (rounding == Math.Rounding.Up) ? SafeCast.toUint64((amount + BUCKET_UNIT_SCALE - 1) / BUCKET_UNIT_SCALE) : SafeCast.toUint64(amount / BUCKET_UNIT_SCALE); + } + + function _convertFromBucketUnit(uint64 bucketUnit) internal pure returns (uint256) { + return bucketUnit * BUCKET_UNIT_SCALE; + } + + + function _calcRedemption(uint256 ethAmount) internal view returns (uint256 eEthShares, uint256 eEthAmountToReceiver, uint256 eEthFeeAmountToTreasury, uint256 sharesToBurn, uint256 feeShareToTreasury) { + eEthShares = liquidityPool.sharesForAmount(ethAmount); + eEthAmountToReceiver = liquidityPool.amountForShare(eEthShares.mulDiv(BASIS_POINT_SCALE - exitFeeInBps, BASIS_POINT_SCALE)); // ethShareToReceiver + + sharesToBurn = liquidityPool.sharesForWithdrawalAmount(eEthAmountToReceiver); + uint256 eEthShareFee = eEthShares - sharesToBurn; + feeShareToTreasury = eEthShareFee.mulDiv(exitFeeSplitToTreasuryInBps, BASIS_POINT_SCALE); + eEthFeeAmountToTreasury = liquidityPool.amountForShare(feeShareToTreasury); + } + + /** + * @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}. + */ + // redeemable amount after exit fee + function previewRedeem(uint256 shares) public view returns (uint256) { + uint256 amountInEth = liquidityPool.amountForShare(shares); + return amountInEth - _fee(amountInEth, exitFeeInBps); + } + + function _fee(uint256 assets, uint256 feeBasisPoints) internal pure virtual returns (uint256) { + return assets.mulDiv(feeBasisPoints, BASIS_POINT_SCALE, Math.Rounding.Up); + } + + function _authorizeUpgrade(address newImplementation) internal override { + roleRegistry.onlyProtocolUpgrader(msg.sender); + } + + function getImplementation() external view returns (address) { + return _getImplementation(); + } + + function _hasRole(bytes32 role, address account) internal view returns (bool) { + require(roleRegistry.hasRole(role, account), "EtherFiRedemptionManager: Unauthorized"); + } + + modifier hasRole(bytes32 role) { + _hasRole(role, msg.sender); + _; + } + +} \ No newline at end of file diff --git a/script/hoodi/Upgrade-hoodi.s.sol b/script/hoodi/Upgrade-hoodi.s.sol index 62148fbd..4204c593 100644 --- a/script/hoodi/Upgrade-hoodi.s.sol +++ b/script/hoodi/Upgrade-hoodi.s.sol @@ -10,24 +10,20 @@ pragma solidity ^0.8.27; * 2. Ensure Create2Factory is deployed at CREATE2_FACTORY_HOODI address * 3. Set HOODI_RPC_URL environment variable or use --fork-url flag * 4. Set HOODI_PRIVATE_KEY environment variable (must be protocol upgrader for most contracts, owner for some) - * 5. Run: `forge script script/hoodi/Upgrade-hoodi.s.sol --fork-url $HOODI_RPC_URL -vvv --broadcast` - * - * @dev Create2 Deployment: - * - Uses Create2Factory for deterministic addresses - * - Salts are defined in Hoodi-Salts.s.sol - * - All implementations are deployed using Create2 before upgrading proxies + * 5. Run: `forge script script/hoodi/Upgrade-hoodi.s.sol --fork-url $HOODI_RPC_URL -vvvv` */ import "forge-std/Script.sol"; import "forge-std/console2.sol"; import {Deployed_Hoodi} from "../deploys/Deployed_Hoodi.s.sol"; -import {HoodiSalts} from "./Hoodi-Salts.s.sol"; +import {HoodiSalts} from "./Hoodi-Salts.s.sol"; import {IRoleRegistry} from "../../src/interfaces/IRoleRegistry.sol"; import {IStakingManager} from "../../src/interfaces/IStakingManager.sol"; import {StakingManager} from "../../src/StakingManager.sol"; import {EtherFiNodesManager} from "../../src/EtherFiNodesManager.sol"; import {EtherFiRedemptionManager} from "../../src/EtherFiRedemptionManager.sol"; import {EtherFiNode} from "../../src/EtherFiNode.sol"; +import {EtherFiRedemptionManagerTemp} from "./EtherFiRedemptionManagerTemp.sol"; interface IUpgradeable { function upgradeTo(address newImplementation) external; @@ -51,6 +47,7 @@ contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { string constant ETHERFI_NODE_NAME = "EtherFiNode"; string constant ETHERFI_NODES_MANAGER_NAME = "EtherFiNodesManager"; string constant ETHERFI_REDEMPTION_MANAGER_NAME = "EtherFiRedemptionManager"; + string constant ETHERFI_REDEMPTION_MANAGER_TEMP_NAME = "EtherFiRedemptionManagerTemp"; struct UpgradeResult { string name; @@ -97,6 +94,7 @@ contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { console2.log("Deployer is protocol upgrader: %s\n", isProtocolUpgrader ? "YES" : "NO"); vm.startBroadcast(deployerPrivateKey); + // vm.startPrank(deployer); console2.log("Broadcasting from: %s", vm.toString(deployer)); // Upgrade contracts that require protocol upgrader @@ -110,6 +108,7 @@ contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { } vm.stopBroadcast(); + // vm.stopPrank(); // Print summary printSummary(); @@ -133,6 +132,14 @@ contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { } function upgradeEtherFiRedemptionManager() internal { + address tempImpl = _deployEtherFiRedemptionManagerTemp(); + IUpgradeable(ETHERFI_REDEMPTION_MANAGER_PROXY).upgradeTo(tempImpl); + console2.log(" [OK] ETHERFI_REDEMPTION_MANAGER_TEMP upgraded: %s\n", vm.toString(tempImpl)); + + console2.log("Clearnig out slot for upgrade..."); + EtherFiRedemptionManagerTemp(payable(ETHERFI_REDEMPTION_MANAGER_PROXY)).clearOutSlotForUpgrade(); + console2.log(" [OK] ETHERFI_REDEMPTION_MANAGER_TEMP slot cleared \n"); + console2.log("Upgrading ETHERFI_REDEMPTION_MANAGER..."); address impl = _deployEtherFiRedemptionManager(); IUpgradeable(ETHERFI_REDEMPTION_MANAGER_PROXY).upgradeTo(impl); @@ -217,6 +224,39 @@ contract UpgradeHoodi is Script, Deployed_Hoodi, HoodiSalts { return deployedAddress; } + function _deployEtherFiRedemptionManagerTemp() internal returns (address) { + bytes memory constructorArgs = abi.encode( + LIQUIDITY_POOL, + EETH, + WEETH, + TREASURY, + ROLE_REGISTRY + ); + bytes memory bytecode = abi.encodePacked( + type(EtherFiRedemptionManagerTemp).creationCode, + constructorArgs + ); + + bytes32 salt = keccak256(abi.encodePacked(ETHERFI_REDEMPTION_MANAGER_TEMP_NAME)); + bytes32 initCodeHash = keccak256(bytecode); + address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); + console2.log(" Predicted address: %s", vm.toString(predictedAddress)); + + // Check if contract already exists at this address + uint256 codeSize; + assembly { + codeSize := extcodesize(predictedAddress) + } + if (codeSize > 0) { + console2.log(" Contract already deployed, skipping deployment"); + return predictedAddress; + } + address deployedAddress = factory.deploy(bytecode, ETHERFI_REDEMPTION_MANAGER_TEMP_NAME); + require(deployedAddress == predictedAddress, "Deployment address mismatch"); + console2.log(" Deployed address: %s", vm.toString(deployedAddress)); + return deployedAddress; + } + function _deployEtherFiRedemptionManager() internal returns (address) { bytes memory constructorArgs = abi.encode( LIQUIDITY_POOL, diff --git a/script/hoodi/deploy-hoodi.s.sol b/script/hoodi/deploy-hoodi.s.sol deleted file mode 100644 index 7b58253a..00000000 --- a/script/hoodi/deploy-hoodi.s.sol +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.27; - -/** - * @title DeployHoodi - * @notice Script to deploy contract implementations on Hoodi testnet using Create2Factory - * - * @dev Usage: - * 1. Ensure contracts are compiled: `forge build` - * 2. Ensure Create2Factory is deployed at CREATE2_FACTORY_HOODI address - * 3. Set HOODI_RPC_URL environment variable or use --fork-url flag - * 4. Set HOODI_PRIVATE_KEY environment variable - * 5. Run: `forge script script/hoodi/deploy-hoodi.s.sol --fork-url $HOODI_RPC_URL -vvv --broadcast --verify` - * - * @dev Create2 Deployment: - * - Uses Create2Factory for deterministic addresses - * - Salts are defined in Hoodi-Salts.s.sol - * - All implementations are deployed using Create2 - */ - -import "forge-std/Script.sol"; -import "forge-std/console2.sol"; -import {Deployed_Hoodi} from "../deploys/Deployed_Hoodi.s.sol"; -import {HoodiSalts} from "./Hoodi-Salts.s.sol"; -import {StakingManager} from "../../src/StakingManager.sol"; -import {EtherFiNodesManager} from "../../src/EtherFiNodesManager.sol"; -import {EtherFiRedemptionManager} from "../../src/EtherFiRedemptionManager.sol"; -import {EtherFiNode} from "../../src/EtherFiNode.sol"; - -interface ICreate2Factory { - function deploy(bytes memory code, string memory contractName) external payable returns (address); -} - -contract DeployHoodi is Script, Deployed_Hoodi, HoodiSalts { - // Create2Factory for deterministic deployments - ICreate2Factory constant factory = ICreate2Factory(CREATE2_FACTORY_HOODI); - - // Artifact paths for contracts (no constructor params) - string constant LIQUIDITY_POOL_ARTIFACT = "LiquidityPool.sol:LiquidityPool"; - string constant ETHERFI_ADMIN_ARTIFACT = "EtherFiAdmin.sol:EtherFiAdmin"; - string constant TNFT_ARTIFACT = "TNFT.sol:TNFT"; - string constant ETHERFI_VIEWER_ARTIFACT = "helpers/EtherFiViewer.sol:EtherFiViewer"; - string constant ETHERFI_REWARDS_ROUTER_ARTIFACT = "EtherFiRewardsRouter.sol:EtherFiRewardsRouter"; - string constant EETH_ARTIFACT = "EETH.sol:EETH"; - string constant WEETH_ARTIFACT = "WeETH.sol:WeETH"; - string constant MEMBERSHIP_MANAGER_ARTIFACT = "MembershipManager.sol:MembershipManager"; - string constant WITHDRAW_REQUEST_NFT_ARTIFACT = "WithdrawRequestNFT.sol:WithdrawRequestNFT"; - string constant BUCKET_RATE_LIMITER_ARTIFACT = "BucketRateLimiter.sol:BucketRateLimiter"; - - // Contract names for Create2Factory (must match Hoodi-Salts.s.sol) - string constant STAKING_MANAGER_NAME = "StakingManager"; - string constant ETHERFI_NODE_NAME = "EtherFiNode"; - string constant ETHERFI_NODES_MANAGER_NAME = "EtherFiNodesManager"; - string constant ETHERFI_REDEMPTION_MANAGER_NAME = "EtherFiRedemptionManager"; - - // Deployed implementation addresses - address public stakingManagerImpl; - address public etherFiNodeImpl; - address public etherFiNodesManagerImpl; - address public etherFiRedemptionManagerImpl; - - function run() external { - // Fork Hoodi testnet - string memory rpc; - try vm.envString("HOODI_RPC_URL") returns (string memory envRpc) { - rpc = envRpc; - } catch { - try vm.rpcUrl("hoodi") returns (string memory configRpc) { - rpc = configRpc; - } catch { - revert("Please set HOODI_RPC_URL env var, add 'hoodi' to foundry.toml rpc_endpoints, or use --fork-url flag"); - } - } - vm.createSelectFork(rpc); - - uint256 deployerPrivateKey = vm.envUint("HOODI_PRIVATE_KEY"); - address deployer = vm.addr(deployerPrivateKey); - - console2.log("========================================"); - console2.log("Deploying Hoodi Contract Implementations (Create2)"); - console2.log("========================================"); - console2.log("Deployer: %s", vm.toString(deployer)); - console2.log("Create2Factory: %s", vm.toString(address(factory))); - console2.log(""); - - // vm.startBroadcast(deployerPrivateKey); - vm.startPrank(deployer); - - deployStakingManager(); - deployEtherFiNodesManager(); - deployEtherFiRedemptionManager(); - deployEtherFiNode(); - - // vm.stopBroadcast(); - vm.stopPrank(); - - // Print summary - printSummary(); - } - - function deployStakingManager() internal { - console2.log("Deploying STAKING_MANAGER implementation..."); - stakingManagerImpl = _deployStakingManager(); - console2.log(" [OK] STAKING_MANAGER deployed: %s\n", vm.toString(stakingManagerImpl)); - } - - function deployEtherFiNodesManager() internal { - console2.log("Deploying ETHERFI_NODES_MANAGER implementation..."); - etherFiNodesManagerImpl = _deployEtherFiNodesManager(); - console2.log(" [OK] ETHERFI_NODES_MANAGER deployed: %s\n", vm.toString(etherFiNodesManagerImpl)); - } - - function deployEtherFiRedemptionManager() internal { - console2.log("Deploying ETHERFI_REDEMPTION_MANAGER implementation..."); - etherFiRedemptionManagerImpl = _deployEtherFiRedemptionManager(); - console2.log(" [OK] ETHERFI_REDEMPTION_MANAGER deployed: %s\n", vm.toString(etherFiRedemptionManagerImpl)); - } - - function deployEtherFiNode() internal { - console2.log("Deploying ETHERFI_NODE implementation..."); - etherFiNodeImpl = _deployEtherFiNode(); - console2.log(" [OK] ETHERFI_NODE deployed: %s\n", vm.toString(etherFiNodeImpl)); - } - - // Internal deployment functions - /// @notice Deploys implementation using Create2Factory for deterministic addresses - /// @param artifactPath Path to contract artifact (e.g., "LiquidityPool.sol:LiquidityPool") - /// @param contractName Contract name for Create2 salt (must match Hoodi-Salts.s.sol) - function _deployImplementation(string memory artifactPath, string memory contractName) internal returns (address) { - bytes memory bytecode = vm.getCode(artifactPath); - require(bytecode.length > 0, "Failed to get bytecode"); - - bytes32 salt = keccak256(abi.encodePacked(contractName)); - bytes32 initCodeHash = keccak256(bytecode); - address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); - console2.log(" Predicted address: %s", vm.toString(predictedAddress)); - - // Check if contract already exists at this address - uint256 codeSize; - assembly { - codeSize := extcodesize(predictedAddress) - } - if (codeSize > 0) { - console2.log(" Contract already deployed, skipping deployment"); - return predictedAddress; - } - - address deployedAddress = factory.deploy(bytecode, contractName); - require(deployedAddress == predictedAddress, "Deployment address mismatch"); - require(deployedAddress != address(0), "Failed to deploy implementation"); - - console2.log(" Deployed address: %s", vm.toString(deployedAddress)); - return deployedAddress; - } - - function _deployStakingManager() internal returns (address) { - bytes memory constructorArgs = abi.encode( - LIQUIDITY_POOL, - ETHERFI_NODES_MANAGER, - ETH2_DEPOSIT, - AUCTION_MANAGER, - ETHERFI_NODE_BEACON, - ROLE_REGISTRY - ); - bytes memory bytecode = abi.encodePacked( - type(StakingManager).creationCode, - constructorArgs - ); - - bytes32 salt = keccak256(abi.encodePacked(STAKING_MANAGER_NAME)); - bytes32 initCodeHash = keccak256(bytecode); - address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); - console2.log(" Predicted address: %s", vm.toString(predictedAddress)); - - // Check if contract already exists at this address - uint256 codeSize; - assembly { - codeSize := extcodesize(predictedAddress) - } - if (codeSize > 0) { - console2.log(" Contract already deployed, skipping deployment"); - return predictedAddress; - } - - address deployedAddress = factory.deploy(bytecode, STAKING_MANAGER_NAME); - require(deployedAddress == predictedAddress, "Deployment address mismatch"); - console2.log(" Deployed address: %s", vm.toString(deployedAddress)); - return deployedAddress; - } - - function _deployEtherFiNodesManager() internal returns (address) { - bytes memory constructorArgs = abi.encode( - STAKING_MANAGER, - ROLE_REGISTRY, - ETHERFI_RATE_LIMITER - ); - bytes memory bytecode = abi.encodePacked( - type(EtherFiNodesManager).creationCode, - constructorArgs - ); - - bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODES_MANAGER_NAME)); - bytes32 initCodeHash = keccak256(bytecode); - address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); - console2.log(" Predicted address: %s", vm.toString(predictedAddress)); - - // Check if contract already exists at this address - uint256 codeSize; - assembly { - codeSize := extcodesize(predictedAddress) - } - if (codeSize > 0) { - console2.log(" Contract already deployed, skipping deployment"); - return predictedAddress; - } - - address deployedAddress = factory.deploy(bytecode, ETHERFI_NODES_MANAGER_NAME); - require(deployedAddress == predictedAddress, "Deployment address mismatch"); - console2.log(" Deployed address: %s", vm.toString(deployedAddress)); - return deployedAddress; - } - - function _deployEtherFiRedemptionManager() internal returns (address) { - bytes memory constructorArgs = abi.encode( - LIQUIDITY_POOL, - EETH, - WEETH, - TREASURY, - ROLE_REGISTRY, - ETHERFI_RESTAKER - ); - bytes memory bytecode = abi.encodePacked( - type(EtherFiRedemptionManager).creationCode, - constructorArgs - ); - - bytes32 salt = keccak256(abi.encodePacked(ETHERFI_REDEMPTION_MANAGER_NAME)); - bytes32 initCodeHash = keccak256(bytecode); - address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); - console2.log(" Predicted address: %s", vm.toString(predictedAddress)); - - // Check if contract already exists at this address - uint256 codeSize; - assembly { - codeSize := extcodesize(predictedAddress) - } - if (codeSize > 0) { - console2.log(" Contract already deployed, skipping deployment"); - return predictedAddress; - } - - address deployedAddress = factory.deploy(bytecode, ETHERFI_REDEMPTION_MANAGER_NAME); - require(deployedAddress == predictedAddress, "Deployment address mismatch"); - console2.log(" Deployed address: %s", vm.toString(deployedAddress)); - return deployedAddress; - } - - function _deployEtherFiNode() internal returns (address) { - bytes memory constructorArgs = abi.encode( - LIQUIDITY_POOL, - ETHERFI_NODES_MANAGER, - EIGEN_POD_MANAGER, - DELEGATION_MANAGER, - ROLE_REGISTRY - ); - bytes memory bytecode = abi.encodePacked( - type(EtherFiNode).creationCode, - constructorArgs - ); - - bytes32 salt = keccak256(abi.encodePacked(ETHERFI_NODE_NAME)); - bytes32 initCodeHash = keccak256(bytecode); - address predictedAddress = vm.computeCreate2Address(salt, initCodeHash, address(factory)); - console2.log(" Predicted address: %s", vm.toString(predictedAddress)); - - // Check if contract already exists at this address - uint256 codeSize; - assembly { - codeSize := extcodesize(predictedAddress) - } - if (codeSize > 0) { - console2.log(" Contract already deployed, skipping deployment"); - return predictedAddress; - } - - address deployedAddress = factory.deploy(bytecode, ETHERFI_NODE_NAME); - require(deployedAddress == predictedAddress, "Deployment address mismatch"); - console2.log(" Deployed address: %s", vm.toString(deployedAddress)); - return deployedAddress; - } - - function printSummary() internal view { - console2.log("\n========================================"); - console2.log("Deployment Summary"); - console2.log("========================================"); - console2.log("STAKING_MANAGER: %s", vm.toString(stakingManagerImpl)); - console2.log("ETHERFI_NODES_MANAGER: %s", vm.toString(etherFiNodesManagerImpl)); - console2.log("ETHERFI_REDEMPTION_MANAGER: %s", vm.toString(etherFiRedemptionManagerImpl)); - console2.log("ETHERFI_NODE: %s", vm.toString(etherFiNodeImpl)); - console2.log(""); - } -} -