diff --git a/contracts/upgrade/StakingRouterV3VoteScript.sol b/contracts/upgrade/StakingRouterV3VoteScript.sol new file mode 100644 index 0000000000..fbf7cd104b --- /dev/null +++ b/contracts/upgrade/StakingRouterV3VoteScript.sol @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.25; + +import {IAccessControl} from "@openzeppelin/contracts-v5.2/access/IAccessControl.sol"; + +import {IForwarder} from "./interfaces/IForwarder.sol"; +import {CallsScriptBuilder} from "./utils/CallScriptBuilder.sol"; + +interface IOssifiableProxyV2 { + function proxy__upgradeTo(address newImplementation_) external; + function proxy__upgradeToAndCall(address newImplementation_, bytes calldata setupCalldata_) external; +} + +interface ILidoLocatorV3 { + function burner() external view returns (address); + function stakingRouter() external view returns (address); + function triggerableWithdrawalsGateway() external view returns (address); +} + +interface IBaseModuleV3 { + function LIDO_LOCATOR() external view returns (address); + function PARAMETERS_REGISTRY() external view returns (address); + function ACCOUNTING() external view returns (address); + function EXIT_PENALTIES() external view returns (address); + function FEE_DISTRIBUTOR() external view returns (address); +} + +interface IAccountingV3View { + function FEE_DISTRIBUTOR() external view returns (address); +} + +interface IFeeDistributorV3View { + function ORACLE() external view returns (address); +} + +interface IFeeOracleV3View { + function STRIKES() external view returns (address); +} + +interface ICSModuleV3 { + function finalizeUpgradeV3() external; + function REPORT_GENERAL_DELAYED_PENALTY_ROLE() external view returns (bytes32); + function SETTLE_GENERAL_DELAYED_PENALTY_ROLE() external view returns (bytes32); + function VERIFIER_ROLE() external view returns (bytes32); + function REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE() external view returns (bytes32); + function REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE() external view returns (bytes32); + function CREATE_NODE_OPERATOR_ROLE() external view returns (bytes32); + function PAUSE_ROLE() external view returns (bytes32); + function RESUME_ROLE() external view returns (bytes32); + function resume() external; +} + +interface IParametersRegistryV3 { + function finalizeUpgradeV3() external; +} + +interface IFeeOracleV3 { + function finalizeUpgradeV3(uint256 consensusVersion) external; + function PAUSE_ROLE() external view returns (bytes32); +} + +interface IAccountingV3 { + function finalizeUpgradeV3() external; + function PAUSE_ROLE() external view returns (bytes32); +} + +interface IFeeDistributorV3 { + function finalizeUpgradeV3() external; +} + +interface IPausableWithResumeRoles { + function PAUSE_ROLE() external view returns (bytes32); + function RESUME_ROLE() external view returns (bytes32); +} + +interface IPausableRole { + function PAUSE_ROLE() external view returns (bytes32); +} + +interface IValidatorStrikesV3 { + function ejector() external view returns (address); + function setEjector(address newEjector) external; +} + +interface IBurner { + function REQUEST_BURN_SHARES_ROLE() external view returns (bytes32); + function REQUEST_BURN_MY_STETH_ROLE() external view returns (bytes32); +} + +interface ITriggerableWithdrawalsGateway { + function ADD_FULL_WITHDRAWAL_REQUEST_ROLE() external view returns (bytes32); +} + +interface IHashConsensusV3 { + function updateInitialEpoch(uint256 epoch) external; +} + +interface IStakingRouter { + function addStakingModule( + string calldata _name, + address _stakingModuleAddress, + uint256 _stakeShareLimit, + uint256 _priorityExitShareThreshold, + uint256 _stakingModuleFee, + uint256 _treasuryFee, + uint256 _maxDepositsPerBlock, + uint256 _minDepositBlockDistance + ) external; +} + +/// @title StakingRouterV3VoteScript +/// @notice Encodes the full CSM v2 -> v3 upgrade sequence and curated module addition +/// into one atomic Agent call script. +contract StakingRouterV3VoteScript { + using CallsScriptBuilder for CallsScriptBuilder.Context; + + struct ScriptCall { + address to; + bytes data; + } + + struct VoteItem { + string description; + ScriptCall call; + } + + struct UpgradeConfigInput { + address csmProxy; + address csmImpl; + address vettedGateProxy; + address parametersRegistryImpl; + address feeOracleImpl; + uint256 feeOracleConsensusVersion; + address vettedGateImpl; + address accountingImpl; + address feeDistributorImpl; + address exitPenaltiesImpl; + address strikesImpl; + address oldPermissionlessGate; + address verifier; + address verifierV3; + address permissionlessGate; + address ejector; + } + + struct CuratedModuleConfigInput { + address module; + address hashConsensus; + string moduleName; + uint256 stakeShareLimit; + uint256 priorityExitShareThreshold; + uint256 stakingModuleFee; + uint256 treasuryFee; + uint256 maxDepositsPerBlock; + uint256 minDepositBlockDistance; + } + + struct ScriptParamsInput { + address agent; + address easyTrackEVMScriptExecutor; + address resealManager; + address identifiedCommunityStakersGateManager; + address gateSeal; + address gateSealV3; + address generalDelayedPenaltyReporter; + uint256 hashConsensusInitialEpoch; + UpgradeConfigInput upgrade; + CuratedModuleConfigInput curatedModule; + } + + bytes32 public constant REPORT_EL_REWARDS_STEALING_PENALTY_ROLE = + keccak256("REPORT_EL_REWARDS_STEALING_PENALTY_ROLE"); + bytes32 public constant SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE = + keccak256("SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE"); + bytes32 public constant REPORT_GENERAL_DELAYED_PENALTY_ROLE = + keccak256("REPORT_GENERAL_DELAYED_PENALTY_ROLE"); + bytes32 public constant SETTLE_GENERAL_DELAYED_PENALTY_ROLE = + keccak256("SETTLE_GENERAL_DELAYED_PENALTY_ROLE"); + bytes32 public constant REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE = + keccak256("REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE"); + bytes32 public constant REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE = + keccak256("REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE"); + bytes32 public constant START_REFERRAL_SEASON_ROLE = keccak256("START_REFERRAL_SEASON_ROLE"); + bytes32 public constant END_REFERRAL_SEASON_ROLE = keccak256("END_REFERRAL_SEASON_ROLE"); + + uint256 public constant ITEMS_COUNT = 46; + + address public immutable AGENT; + address public immutable STAKING_ROUTER; + address public immutable BURNER; + address public immutable TRIGGERABLE_WITHDRAWALS_GATEWAY; + address public immutable EASY_TRACK_EVM_SCRIPT_EXECUTOR; + address public immutable RESEAL_MANAGER; + address public immutable IDENTIFIED_COMMUNITY_STAKERS_GATE_MANAGER; + address public immutable GATE_SEAL; + address public immutable GATE_SEAL_V3; + address public immutable GENERAL_DELAYED_PENALTY_REPORTER; + uint256 public immutable HASH_CONSENSUS_INITIAL_EPOCH; + + address public immutable CSM; + address public immutable CSM_IMPL; + address public immutable PARAMETERS_REGISTRY; + address public immutable PARAMETERS_REGISTRY_IMPL; + address public immutable FEE_ORACLE; + address public immutable FEE_ORACLE_IMPL; + uint256 public immutable FEE_ORACLE_CONSENSUS_VERSION; + address public immutable VETTED_GATE; + address public immutable VETTED_GATE_IMPL; + address public immutable ACCOUNTING; + address public immutable ACCOUNTING_IMPL; + address public immutable FEE_DISTRIBUTOR; + address public immutable FEE_DISTRIBUTOR_IMPL; + address public immutable EXIT_PENALTIES; + address public immutable EXIT_PENALTIES_IMPL; + address public immutable STRIKES; + address public immutable STRIKES_IMPL; + address public immutable OLD_PERMISSIONLESS_GATE; + address public immutable VERIFIER; + address public immutable VERIFIER_V3; + address public immutable PERMISSIONLESS_GATE; + address public immutable EJECTOR; + + address public immutable CURATED_MODULE; + address public immutable CURATED_ACCOUNTING; + address public immutable CURATED_EJECTOR; + address public immutable CURATED_HASH_CONSENSUS; + string public CURATED_MODULE_NAME; + uint256 public immutable CURATED_STAKE_SHARE_LIMIT; + uint256 public immutable CURATED_PRIORITY_EXIT_SHARE_THRESHOLD; + uint256 public immutable CURATED_STAKING_MODULE_FEE; + uint256 public immutable CURATED_TREASURY_FEE; + uint256 public immutable CURATED_MAX_DEPOSITS_PER_BLOCK; + uint256 public immutable CURATED_MIN_DEPOSIT_BLOCK_DISTANCE; + + constructor(ScriptParamsInput memory _paramsInput) { + UpgradeConfigInput memory upgradeInput = _paramsInput.upgrade; + CuratedModuleConfigInput memory curatedInput = _paramsInput.curatedModule; + + AGENT = _paramsInput.agent; + EASY_TRACK_EVM_SCRIPT_EXECUTOR = _paramsInput.easyTrackEVMScriptExecutor; + RESEAL_MANAGER = _paramsInput.resealManager; + IDENTIFIED_COMMUNITY_STAKERS_GATE_MANAGER = _paramsInput.identifiedCommunityStakersGateManager; + GATE_SEAL = _paramsInput.gateSeal; + GATE_SEAL_V3 = _paramsInput.gateSealV3; + GENERAL_DELAYED_PENALTY_REPORTER = _paramsInput.generalDelayedPenaltyReporter; + HASH_CONSENSUS_INITIAL_EPOCH = _paramsInput.hashConsensusInitialEpoch; + + CSM = upgradeInput.csmProxy; + CSM_IMPL = upgradeInput.csmImpl; + PARAMETERS_REGISTRY_IMPL = upgradeInput.parametersRegistryImpl; + FEE_ORACLE_IMPL = upgradeInput.feeOracleImpl; + FEE_ORACLE_CONSENSUS_VERSION = upgradeInput.feeOracleConsensusVersion; + VETTED_GATE = upgradeInput.vettedGateProxy; + VETTED_GATE_IMPL = upgradeInput.vettedGateImpl; + ACCOUNTING_IMPL = upgradeInput.accountingImpl; + FEE_DISTRIBUTOR_IMPL = upgradeInput.feeDistributorImpl; + EXIT_PENALTIES_IMPL = upgradeInput.exitPenaltiesImpl; + STRIKES_IMPL = upgradeInput.strikesImpl; + OLD_PERMISSIONLESS_GATE = upgradeInput.oldPermissionlessGate; + VERIFIER = upgradeInput.verifier; + VERIFIER_V3 = upgradeInput.verifierV3; + PERMISSIONLESS_GATE = upgradeInput.permissionlessGate; + EJECTOR = upgradeInput.ejector; + + IBaseModuleV3 csm = IBaseModuleV3(CSM); + PARAMETERS_REGISTRY = csm.PARAMETERS_REGISTRY(); + ACCOUNTING = csm.ACCOUNTING(); + EXIT_PENALTIES = csm.EXIT_PENALTIES(); + FEE_DISTRIBUTOR = csm.FEE_DISTRIBUTOR(); + FEE_ORACLE = IFeeDistributorV3View(FEE_DISTRIBUTOR).ORACLE(); + STRIKES = IFeeOracleV3View(FEE_ORACLE).STRIKES(); + + ILidoLocatorV3 locator = ILidoLocatorV3(csm.LIDO_LOCATOR()); + STAKING_ROUTER = locator.stakingRouter(); + BURNER = locator.burner(); + TRIGGERABLE_WITHDRAWALS_GATEWAY = locator.triggerableWithdrawalsGateway(); + + CURATED_MODULE = curatedInput.module; + CURATED_HASH_CONSENSUS = curatedInput.hashConsensus; + CURATED_MODULE_NAME = curatedInput.moduleName; + CURATED_STAKE_SHARE_LIMIT = curatedInput.stakeShareLimit; + CURATED_PRIORITY_EXIT_SHARE_THRESHOLD = curatedInput.priorityExitShareThreshold; + CURATED_STAKING_MODULE_FEE = curatedInput.stakingModuleFee; + CURATED_TREASURY_FEE = curatedInput.treasuryFee; + CURATED_MAX_DEPOSITS_PER_BLOCK = curatedInput.maxDepositsPerBlock; + CURATED_MIN_DEPOSIT_BLOCK_DISTANCE = curatedInput.minDepositBlockDistance; + + CURATED_ACCOUNTING = IBaseModuleV3(CURATED_MODULE).ACCOUNTING(); + address curatedFeeDistributor = IAccountingV3View(CURATED_ACCOUNTING).FEE_DISTRIBUTOR(); + address curatedFeeOracle = IFeeDistributorV3View(curatedFeeDistributor).ORACLE(); + address curatedStrikes = IFeeOracleV3View(curatedFeeOracle).STRIKES(); + CURATED_EJECTOR = IValidatorStrikesV3(curatedStrikes).ejector(); + } + + function getVoteItems() public view returns (VoteItem[] memory voteItems) { + voteItems = new VoteItem[](ITEMS_COUNT); + + address oldPermissionlessGate = OLD_PERMISSIONLESS_GATE; + + address oldEjector = IValidatorStrikesV3(STRIKES).ejector(); + + uint256 index = 0; + + voteItems[index++] = _item({ + description: "1. Upgrade and finalize CSM v3", + to: CSM, + data: abi.encodeCall( + IOssifiableProxyV2.proxy__upgradeToAndCall, + (CSM_IMPL, abi.encodeCall(ICSModuleV3.finalizeUpgradeV3, ())) + ) + }); + + voteItems[index++] = _item({ + description: "2. Upgrade and finalize ParametersRegistry v3", + to: PARAMETERS_REGISTRY, + data: abi.encodeCall( + IOssifiableProxyV2.proxy__upgradeToAndCall, + ( + PARAMETERS_REGISTRY_IMPL, + abi.encodeCall(IParametersRegistryV3.finalizeUpgradeV3, ()) + ) + ) + }); + + voteItems[index++] = _item({ + description: "3. Upgrade and finalize FeeOracle v3", + to: FEE_ORACLE, + data: abi.encodeCall( + IOssifiableProxyV2.proxy__upgradeToAndCall, + ( + FEE_ORACLE_IMPL, + abi.encodeCall(IFeeOracleV3.finalizeUpgradeV3, (FEE_ORACLE_CONSENSUS_VERSION)) + ) + ) + }); + + voteItems[index++] = _item({ + description: "4. Upgrade VettedGate implementation", + to: VETTED_GATE, + data: abi.encodeCall(IOssifiableProxyV2.proxy__upgradeTo, (VETTED_GATE_IMPL)) + }); + + voteItems[index++] = _item({ + description: "5. Upgrade and finalize Accounting v3", + to: ACCOUNTING, + data: abi.encodeCall( + IOssifiableProxyV2.proxy__upgradeToAndCall, + (ACCOUNTING_IMPL, abi.encodeCall(IAccountingV3.finalizeUpgradeV3, ())) + ) + }); + + voteItems[index++] = _item({ + description: "6. Upgrade and finalize FeeDistributor v3", + to: FEE_DISTRIBUTOR, + data: abi.encodeCall( + IOssifiableProxyV2.proxy__upgradeToAndCall, + (FEE_DISTRIBUTOR_IMPL, abi.encodeCall(IFeeDistributorV3.finalizeUpgradeV3, ())) + ) + }); + + voteItems[index++] = _item({ + description: "7. Upgrade ExitPenalties implementation", + to: EXIT_PENALTIES, + data: abi.encodeCall(IOssifiableProxyV2.proxy__upgradeTo, (EXIT_PENALTIES_IMPL)) + }); + + voteItems[index++] = _item({ + description: "8. Upgrade ValidatorStrikes implementation", + to: STRIKES, + data: abi.encodeCall(IOssifiableProxyV2.proxy__upgradeTo, (STRIKES_IMPL)) + }); + + voteItems[index++] = _item({ + description: "9. Point ValidatorStrikes to the new Ejector", + to: STRIKES, + data: abi.encodeCall(IValidatorStrikesV3.setEjector, (EJECTOR)) + }); + + voteItems[index++] = _item({ + description: "10. Grant REPORT_GENERAL_DELAYED_PENALTY_ROLE", + call: _grantRole( + CSM, + REPORT_GENERAL_DELAYED_PENALTY_ROLE, + GENERAL_DELAYED_PENALTY_REPORTER + ) + }); + + voteItems[index++] = _item({ + description: "11. Grant SETTLE_GENERAL_DELAYED_PENALTY_ROLE", + call: _grantRole( + CSM, + SETTLE_GENERAL_DELAYED_PENALTY_ROLE, + EASY_TRACK_EVM_SCRIPT_EXECUTOR + ) + }); + + voteItems[index++] = _item({ + description: "12. Revoke REPORT_EL_REWARDS_STEALING_PENALTY_ROLE", + call: _revokeRole( + CSM, + REPORT_EL_REWARDS_STEALING_PENALTY_ROLE, + GENERAL_DELAYED_PENALTY_REPORTER + ) + }); + + voteItems[index++] = _item({ + description: "13. Revoke SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE", + call: _revokeRole( + CSM, + SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE, + EASY_TRACK_EVM_SCRIPT_EXECUTOR + ) + }); + + voteItems[index++] = _item({ + description: "14. Revoke VERIFIER_ROLE from old verifier", + call: _revokeRole( + CSM, + ICSModuleV3(CSM).VERIFIER_ROLE(), + VERIFIER + ) + }); + + voteItems[index++] = _item({ + description: "15. Grant VERIFIER_ROLE to VerifierV3", + call: _grantRole( + CSM, + ICSModuleV3(CSM).VERIFIER_ROLE(), + VERIFIER_V3 + ) + }); + + voteItems[index++] = _item({ + description: "16. Grant REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE to VerifierV3", + call: _grantRole( + CSM, + REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE, + VERIFIER_V3 + ) + }); + + voteItems[index++] = _item({ + description: "17. Grant REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE to Easy Track", + call: _grantRole( + CSM, + REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE, + EASY_TRACK_EVM_SCRIPT_EXECUTOR + ) + }); + + voteItems[index++] = _item({ + description: "18. Revoke CREATE_NODE_OPERATOR_ROLE from old PermissionlessGate", + call: _revokeRole( + CSM, + ICSModuleV3(CSM).CREATE_NODE_OPERATOR_ROLE(), + oldPermissionlessGate + ) + }); + + voteItems[index++] = _item({ + description: "19. Grant CREATE_NODE_OPERATOR_ROLE to new PermissionlessGate", + call: _grantRole( + CSM, + ICSModuleV3(CSM).CREATE_NODE_OPERATOR_ROLE(), + PERMISSIONLESS_GATE + ) + }); + + voteItems[index++] = _item({ + description: "20. Revoke PAUSE_ROLE from old gate seal on CSModule", + call: _revokeRole(CSM, ICSModuleV3(CSM).PAUSE_ROLE(), GATE_SEAL) + }); + + voteItems[index++] = _item({ + description: "21. Revoke PAUSE_ROLE from old gate seal on Accounting", + call: _revokeRole( + ACCOUNTING, + IAccountingV3(ACCOUNTING).PAUSE_ROLE(), + GATE_SEAL + ) + }); + + voteItems[index++] = _item({ + description: "22. Revoke PAUSE_ROLE from old gate seal on FeeOracle", + call: _revokeRole( + FEE_ORACLE, + IFeeOracleV3(FEE_ORACLE).PAUSE_ROLE(), + GATE_SEAL + ) + }); + + voteItems[index++] = _item({ + description: "23. Revoke PAUSE_ROLE from old gate seal on VettedGate", + call: _revokeRole( + VETTED_GATE, + IPausableRole(VETTED_GATE).PAUSE_ROLE(), + GATE_SEAL + ) + }); + + voteItems[index++] = _item({ + description: "24. Revoke PAUSE_ROLE from old gate seal on old Verifier", + call: _revokeRole(VERIFIER, IPausableRole(VERIFIER).PAUSE_ROLE(), GATE_SEAL) + }); + + voteItems[index++] = _item({ + description: "25. Revoke PAUSE_ROLE from old gate seal on old Ejector", + call: _revokeRole(oldEjector, IPausableWithResumeRoles(oldEjector).PAUSE_ROLE(), GATE_SEAL) + }); + + voteItems[index++] = _item({ + description: "26. Revoke PAUSE_ROLE from reseal manager on old Verifier", + call: _revokeRole( + VERIFIER, + IPausableWithResumeRoles(VERIFIER).PAUSE_ROLE(), + RESEAL_MANAGER + ) + }); + + voteItems[index++] = _item({ + description: "27. Revoke RESUME_ROLE from reseal manager on old Verifier", + call: _revokeRole( + VERIFIER, + IPausableWithResumeRoles(VERIFIER).RESUME_ROLE(), + RESEAL_MANAGER + ) + }); + + voteItems[index++] = _item({ + description: "28. Revoke PAUSE_ROLE from reseal manager on old Ejector", + call: _revokeRole( + oldEjector, + IPausableWithResumeRoles(oldEjector).PAUSE_ROLE(), + RESEAL_MANAGER + ) + }); + + voteItems[index++] = _item({ + description: "29. Revoke RESUME_ROLE from reseal manager on old Ejector", + call: _revokeRole( + oldEjector, + IPausableWithResumeRoles(oldEjector).RESUME_ROLE(), + RESEAL_MANAGER + ) + }); + + voteItems[index++] = _item({ + description: "30. Revoke START_REFERRAL_SEASON_ROLE", + call: _revokeRole(VETTED_GATE, START_REFERRAL_SEASON_ROLE, AGENT) + }); + + voteItems[index++] = _item({ + description: "31. Revoke END_REFERRAL_SEASON_ROLE", + call: _revokeRole( + VETTED_GATE, + END_REFERRAL_SEASON_ROLE, + IDENTIFIED_COMMUNITY_STAKERS_GATE_MANAGER + ) + }); + + voteItems[index++] = _item({ + description: "32. Grant PAUSE_ROLE to GateSealV3 on CSModule", + call: _grantRole(CSM, ICSModuleV3(CSM).PAUSE_ROLE(), GATE_SEAL_V3) + }); + + voteItems[index++] = _item({ + description: "33. Grant PAUSE_ROLE to GateSealV3 on Accounting", + call: _grantRole( + ACCOUNTING, + IAccountingV3(ACCOUNTING).PAUSE_ROLE(), + GATE_SEAL_V3 + ) + }); + + voteItems[index++] = _item({ + description: "34. Grant PAUSE_ROLE to GateSealV3 on FeeOracle", + call: _grantRole( + FEE_ORACLE, + IFeeOracleV3(FEE_ORACLE).PAUSE_ROLE(), + GATE_SEAL_V3 + ) + }); + + voteItems[index++] = _item({ + description: "35. Grant PAUSE_ROLE to GateSealV3 on VettedGate", + call: _grantRole( + VETTED_GATE, + IPausableRole(VETTED_GATE).PAUSE_ROLE(), + GATE_SEAL_V3 + ) + }); + + voteItems[index++] = _item({ + description: "36. Revoke REQUEST_BURN_SHARES_ROLE from CSM Accounting", + call: _revokeRole( + BURNER, + IBurner(BURNER).REQUEST_BURN_SHARES_ROLE(), + ACCOUNTING + ) + }); + + voteItems[index++] = _item({ + description: "37. Grant REQUEST_BURN_MY_STETH_ROLE to CSM Accounting", + call: _grantRole( + BURNER, + IBurner(BURNER).REQUEST_BURN_MY_STETH_ROLE(), + ACCOUNTING + ) + }); + + voteItems[index++] = _item({ + description: "38. Revoke TWG full-withdrawal role from old Ejector", + call: _revokeRole( + TRIGGERABLE_WITHDRAWALS_GATEWAY, + ITriggerableWithdrawalsGateway(TRIGGERABLE_WITHDRAWALS_GATEWAY) + .ADD_FULL_WITHDRAWAL_REQUEST_ROLE(), + oldEjector + ) + }); + + voteItems[index++] = _item({ + description: "39. Grant TWG full-withdrawal role to new Ejector", + call: _grantRole( + TRIGGERABLE_WITHDRAWALS_GATEWAY, + ITriggerableWithdrawalsGateway(TRIGGERABLE_WITHDRAWALS_GATEWAY) + .ADD_FULL_WITHDRAWAL_REQUEST_ROLE(), + EJECTOR + ) + }); + + voteItems[index++] = _item({ + description: "40. Add Curated module to StakingRouter", + to: STAKING_ROUTER, + data: abi.encodeCall( + IStakingRouter.addStakingModule, + ( + CURATED_MODULE_NAME, + CURATED_MODULE, + CURATED_STAKE_SHARE_LIMIT, + CURATED_PRIORITY_EXIT_SHARE_THRESHOLD, + CURATED_STAKING_MODULE_FEE, + CURATED_TREASURY_FEE, + CURATED_MAX_DEPOSITS_PER_BLOCK, + CURATED_MIN_DEPOSIT_BLOCK_DISTANCE + ) + ) + }); + + voteItems[index++] = _item({ + description: "41. Grant REQUEST_BURN_MY_STETH_ROLE to Curated Accounting", + call: _grantRole( + BURNER, + IBurner(BURNER).REQUEST_BURN_MY_STETH_ROLE(), + CURATED_ACCOUNTING + ) + }); + + voteItems[index++] = _item({ + description: "42. Grant TWG full-withdrawal role to Curated Ejector", + call: _grantRole( + TRIGGERABLE_WITHDRAWALS_GATEWAY, + ITriggerableWithdrawalsGateway(TRIGGERABLE_WITHDRAWALS_GATEWAY) + .ADD_FULL_WITHDRAWAL_REQUEST_ROLE(), + CURATED_EJECTOR + ) + }); + + voteItems[index++] = _item({ + description: "43. Grant RESUME_ROLE to agent on Curated module", + call: _grantRole( + CURATED_MODULE, + ICSModuleV3(CURATED_MODULE).RESUME_ROLE(), + AGENT + ) + }); + + voteItems[index++] = _item({ + description: "44. Resume Curated module", + to: CURATED_MODULE, + data: abi.encodeCall(ICSModuleV3.resume, ()) + }); + + voteItems[index++] = _item({ + description: "45. Revoke RESUME_ROLE from agent on Curated module", + call: _revokeRole( + CURATED_MODULE, + ICSModuleV3(CURATED_MODULE).RESUME_ROLE(), + AGENT + ) + }); + + voteItems[index++] = _item({ + description: "46. Update Curated HashConsensus initial epoch", + to: CURATED_HASH_CONSENSUS, + data: abi.encodeCall(IHashConsensusV3.updateInitialEpoch, (HASH_CONSENSUS_INITIAL_EPOCH)) + }); + + assert(index == ITEMS_COUNT); + } + + function getAgentEVMScript() public view returns (bytes memory) { + VoteItem[] memory voteItems = getVoteItems(); + CallsScriptBuilder.Context memory scriptBuilder = CallsScriptBuilder.create(); + for (uint256 i = 0; i < voteItems.length; ++i) { + scriptBuilder.addCall(voteItems[i].call.to, voteItems[i].call.data); + } + return scriptBuilder.getResult(); + } + + function getAgentForwardCalldata() external view returns (bytes memory) { + return abi.encodeCall(IForwarder.forward, (getAgentEVMScript())); + } + + function _item( + string memory description, + address to, + bytes memory data + ) private pure returns (VoteItem memory) { + return VoteItem({description: description, call: ScriptCall({to: to, data: data})}); + } + + function _item(string memory description, ScriptCall memory call) private pure returns (VoteItem memory) { + return VoteItem({description: description, call: call}); + } + + function _grantRole(address target, bytes32 role, address account) private pure returns (ScriptCall memory) { + return ScriptCall({to: target, data: abi.encodeCall(IAccessControl.grantRole, (role, account))}); + } + + function _revokeRole(address target, bytes32 role, address account) private pure returns (ScriptCall memory) { + return ScriptCall({to: target, data: abi.encodeCall(IAccessControl.revokeRole, (role, account))}); + } +} diff --git a/lib/state-file.ts b/lib/state-file.ts index 46046408e4..045ec38ee4 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -111,6 +111,7 @@ export enum Sk { v3Template = "v3Template", v3Addresses = "v3Addresses", v3VoteScript = "v3VoteScript", + stakingRouterV3VoteScript = "stakingRouterV3VoteScript", operatorGrid = "operatorGrid", validatorConsolidationRequests = "validatorConsolidationRequests", lazyOracle = "lazyOracle", @@ -196,6 +197,7 @@ export function getAddress(contractKey: Sk, state: DeploymentState): string { case Sk.minFirstAllocationStrategy: case Sk.validatorConsolidationRequests: case Sk.v3VoteScript: + case Sk.stakingRouterV3VoteScript: case Sk.depositsTempStorage: case Sk.beaconChainDepositor: case Sk.vaultsAdapter: diff --git a/scripts/dao-hoodi-v3-phase-2.sh b/scripts/dao-hoodi-v3-phase-2.sh deleted file mode 100755 index 2f40eeaf56..0000000000 --- a/scripts/dao-hoodi-v3-phase-2.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -e +u -set -o pipefail - -export NETWORK=${NETWORK:="hoodi"} # if defined use the value set to default otherwise -export RPC_URL=${RPC_URL:="http://127.0.0.1:8545"} # if defined use the value set to default otherwise - -export DEPLOYER=${DEPLOYER:="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} # first acc of default mnemonic "test test ..." -export GAS_PRIORITY_FEE=1 -export GAS_MAX_FEE=100 - -export NETWORK_STATE_FILE=${NETWORK_STATE_FILE:="deployed-hoodi.json"} -export STEPS_FILE=upgrade/steps-upgrade-hoodi-v3-phase-2.json -export UPGRADE_PARAMETERS_FILE=scripts/upgrade/upgrade-params-hoodi.toml - -yarn hardhat --network $NETWORK run --no-compile scripts/utils/migrate.ts diff --git a/scripts/dao-staking-router-v3-vote.sh b/scripts/dao-staking-router-v3-vote.sh new file mode 100755 index 0000000000..0f82224a47 --- /dev/null +++ b/scripts/dao-staking-router-v3-vote.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e +u +set -o pipefail + +export NETWORK=${NETWORK:="hoodi"} +export RPC_URL=${RPC_URL:="http://127.0.0.1:8545"} + +export DEPLOYER=${DEPLOYER:="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"} +export GAS_PRIORITY_FEE=${GAS_PRIORITY_FEE:=1} +export GAS_MAX_FEE=${GAS_MAX_FEE:=100} + +export NETWORK_STATE_FILE=${NETWORK_STATE_FILE:="deployed-hoodi.json"} +export STEPS_FILE=${STEPS_FILE:="upgrade/steps-staking-router-v3-vote.json"} +export UPGRADE_PARAMETERS_FILE=${UPGRADE_PARAMETERS_FILE:="scripts/upgrade/upgrade-params-hoodi.toml"} +export STAKING_ROUTER_V3_VOTE_PROPOSAL_METADATA=${STAKING_ROUTER_V3_VOTE_PROPOSAL_METADATA:="staking-router-v3-vote"} + +yarn hardhat --network $NETWORK run --no-compile scripts/utils/migrate.ts diff --git a/scripts/upgrade/steps-staking-router-v3-vote.json b/scripts/upgrade/steps-staking-router-v3-vote.json new file mode 100644 index 0000000000..982d722cb3 --- /dev/null +++ b/scripts/upgrade/steps-staking-router-v3-vote.json @@ -0,0 +1,7 @@ +{ + "steps": [ + "upgrade/steps/0000-check-env", + "upgrade/steps/0100-deploy-staking-router-v3-vote-script", + "upgrade/steps/0200-run-staking-router-v3-vote-script" + ] +} diff --git a/scripts/upgrade/steps-upgrade-hoodi-v3-phase-2.json b/scripts/upgrade/steps-upgrade-hoodi-v3-phase-2.json deleted file mode 100644 index 2edca2518c..0000000000 --- a/scripts/upgrade/steps-upgrade-hoodi-v3-phase-2.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "steps": ["upgrade/steps/0000-check-env", "upgrade/steps/0100-upgrade-hoodi-to-v3-phase-2"] -} diff --git a/scripts/upgrade/steps/0100-deploy-staking-router-v3-vote-script.ts b/scripts/upgrade/steps/0100-deploy-staking-router-v3-vote-script.ts new file mode 100644 index 0000000000..aacd9ef5a3 --- /dev/null +++ b/scripts/upgrade/steps/0100-deploy-staking-router-v3-vote-script.ts @@ -0,0 +1,14 @@ +import assert from "assert"; +import { ethers } from "hardhat"; +import { readStakingRouterV3VoteScriptParameters } from "scripts/utils/staking-router-v3-vote"; + +import { deployWithoutProxy, Sk } from "lib"; + +export async function main() { + const deployer = (await ethers.provider.getSigner()).address; + assert.equal(process.env.DEPLOYER, deployer); + + const params = readStakingRouterV3VoteScriptParameters(); + + await deployWithoutProxy(Sk.stakingRouterV3VoteScript, "StakingRouterV3VoteScript", deployer, [params]); +} diff --git a/scripts/upgrade/steps/0100-upgrade-hoodi-to-v3-phase-2.ts b/scripts/upgrade/steps/0100-upgrade-hoodi-to-v3-phase-2.ts deleted file mode 100644 index 274d07811d..0000000000 --- a/scripts/upgrade/steps/0100-upgrade-hoodi-to-v3-phase-2.ts +++ /dev/null @@ -1,25 +0,0 @@ -import assert from "assert"; -import { ethers } from "hardhat"; -import { readUpgradeParameters } from "scripts/utils/upgrade"; - -import { deployImplementation, Sk } from "lib"; - -export async function main(): Promise { - const deployer = (await ethers.provider.getSigner()).address; - assert.equal(process.env.DEPLOYER, deployer); - - const parameters = readUpgradeParameters(true); - const pdgDeployParams = parameters.predepositGuarantee; - - // - // New PredepositGuarantee implementation - // - const predepositGuarantee = await deployImplementation(Sk.predepositGuarantee, "PredepositGuarantee", deployer, [ - pdgDeployParams.genesisForkVersion, - pdgDeployParams.gIndex, - pdgDeployParams.gIndexAfterChange, - pdgDeployParams.changeSlot, - ]); - const newPredepositGuaranteeAddress = await predepositGuarantee.getAddress(); - console.log("New PredepositGuarantee implementation address", newPredepositGuaranteeAddress); -} diff --git a/scripts/upgrade/steps/0200-run-staking-router-v3-vote-script.ts b/scripts/upgrade/steps/0200-run-staking-router-v3-vote-script.ts new file mode 100644 index 0000000000..ae1e421850 --- /dev/null +++ b/scripts/upgrade/steps/0200-run-staking-router-v3-vote-script.ts @@ -0,0 +1,162 @@ +import assert from "assert"; +import { Contract, JsonRpcProvider } from "ethers"; +import { ethers } from "hardhat"; + +import { advanceChainTime, ether, log } from "lib"; +import { readNetworkState, Sk } from "lib/state-file"; + +const ONE_HOUR = 3600n; +const DG_ABI = [ + "function getProposers() view returns ((address account,address executor)[])", + "function submitProposal((address target,uint96 value,bytes payload)[] calls,string metadata) returns (uint256 proposalId)", + "function scheduleProposal(uint256 proposalId)", +]; +const TIMELOCK_ABI = [ + "function getAfterSubmitDelay() view returns (uint32)", + "function getAfterScheduleDelay() view returns (uint32)", + "function execute(uint256 proposalId)", +]; +const VOTE_SCRIPT_ABI = [ + "function AGENT() view returns (address)", + "function ITEMS_COUNT() view returns (uint256)", + "function getAgentForwardCalldata() view returns (bytes)", +]; + +async function getRpcProvider() { + const rpcUrl = process.env.RPC_URL; + if (!rpcUrl) { + throw new Error("RPC_URL is required"); + } + + const provider = new JsonRpcProvider(rpcUrl); + const clientVersion = String(await provider.send("web3_clientVersion", [])); + const clientLower = clientVersion.toLowerCase(); + const rpcPrefix = clientLower.includes("anvil") ? "anvil" : "hardhat"; + + return { provider, rpcPrefix }; +} + +async function impersonateRpc(provider: JsonRpcProvider, rpcPrefix: string, address: string) { + await provider.send(`${rpcPrefix}_impersonateAccount`, [address]); + await provider.send(`${rpcPrefix}_setBalance`, [address, ethers.toQuantity(ether("100"))]); + return provider.getSigner(address); +} + +export async function main(): Promise { + const deployer = (await ethers.provider.getSigner()).address; + assert.equal(process.env.DEPLOYER, deployer); + + // State file can be imported from another environment with a different historical deployer. + // For vote execution on forks we only need addresses from that state, not deployer equality. + const state = readNetworkState(); + const voteScriptAddress = state[Sk.stakingRouterV3VoteScript]?.address; + const dualGovernanceAddress = state[Sk.dgDualGovernance]?.proxy?.address; + const timelockAddress = state[Sk.dgEmergencyProtectedTimelock]?.proxy?.address; + + if (!voteScriptAddress) { + throw new Error(`State key ${Sk.stakingRouterV3VoteScript} is missing. Run deploy step first.`); + } + if (!dualGovernanceAddress) { + throw new Error(`State key ${Sk.dgDualGovernance} is missing in state file.`); + } + if (!timelockAddress) { + throw new Error(`State key ${Sk.dgEmergencyProtectedTimelock} is missing in state file.`); + } + + const { provider: rpcProvider, rpcPrefix } = await getRpcProvider(); + const voteScript = new Contract(voteScriptAddress, VOTE_SCRIPT_ABI, rpcProvider); + const dualGovernance = new Contract(dualGovernanceAddress, DG_ABI, rpcProvider); + const timelock = new Contract(timelockAddress, TIMELOCK_ABI, rpcProvider); + + const agentAddress = (await voteScript.getFunction("AGENT")()) as string; + const itemsCount = (await voteScript.getFunction("ITEMS_COUNT")()) as bigint; + const agentForwardCalldata = (await voteScript.getFunction("getAgentForwardCalldata")()) as string; + + const proposers = (await dualGovernance.getFunction("getProposers")()) as { account: string; executor: string }[]; + const preferredProposer = process.env.STAKING_ROUTER_V3_VOTE_PROPOSER || state[Sk.appVoting]?.proxy?.address; + const proposer = proposers.find((p) => p.account.toLowerCase() === preferredProposer?.toLowerCase()) ?? proposers[0]; + + if (!proposer) { + throw new Error("No proposer found in DualGovernance."); + } + + const proposerAddress = proposer.account; + const executorAddress = + process.env.STAKING_ROUTER_V3_VOTE_EXECUTOR || + (typeof proposer.executor === "string" && proposer.executor !== ethers.ZeroAddress ? proposer.executor : undefined); + + if (!executorAddress) { + throw new Error("DualGovernance proposer has no executor and STAKING_ROUTER_V3_VOTE_EXECUTOR is not set."); + } + + const proposalMetadata = process.env.STAKING_ROUTER_V3_VOTE_PROPOSAL_METADATA || "staking-router-v3-vote"; + const proposalCalls = [{ target: agentAddress, value: 0n, payload: agentForwardCalldata }]; + + log.info("Prepared StakingRouterV3 vote script for DG execution", { + voteScript: voteScriptAddress, + itemsCount: itemsCount.toString(), + dualGovernance: dualGovernanceAddress, + timelock: timelockAddress, + proposer: proposerAddress, + executor: executorAddress, + }); + + const proposerSigner = await impersonateRpc(rpcProvider, rpcPrefix, proposerAddress); + const executorSigner = await impersonateRpc(rpcProvider, rpcPrefix, executorAddress); + + const proposalId = (await dualGovernance + .connect(proposerSigner) + .getFunction("submitProposal") + .staticCall(proposalCalls, proposalMetadata)) as bigint; + + const submitTx = await dualGovernance.connect(proposerSigner).getFunction("submitProposal")( + proposalCalls, + proposalMetadata, + ); + await log.txLink(submitTx.hash); + const submitReceipt = await submitTx.wait(); + if (!submitReceipt) { + throw new Error("submitProposal transaction was not mined"); + } + log.success("DG proposal submitted", proposalId.toString()); + + const afterSubmitDelay = (await timelock.getFunction("getAfterSubmitDelay")()) as bigint; + await advanceChainTime(afterSubmitDelay); + const scheduleTx = await dualGovernance.connect(proposerSigner).getFunction("scheduleProposal")(proposalId); + await log.txLink(scheduleTx.hash); + const scheduleReceipt = await scheduleTx.wait(); + if (!scheduleReceipt) { + throw new Error("scheduleProposal transaction was not mined"); + } + log.success("DG proposal scheduled", proposalId.toString()); + + const afterScheduleDelay = (await timelock.getFunction("getAfterScheduleDelay")()) as bigint; + await advanceChainTime(afterScheduleDelay); + + const maxExecuteRetries = Number(process.env.STAKING_ROUTER_V3_VOTE_EXECUTE_RETRIES || "0"); + let lastError: unknown; + + for (let attempt = 0; attempt <= maxExecuteRetries; attempt++) { + try { + const executeTx = await timelock.connect(executorSigner).getFunction("execute")(proposalId); + await log.txLink(executeTx.hash); + const executeReceipt = await executeTx.wait(); + if (!executeReceipt) { + throw new Error("timelock.execute transaction was not mined"); + } + log.success("Atomic StakingRouterV3 vote script executed via Dual Governance", proposalId.toString()); + log.info("Execution receipt", { + txHash: executeTx.hash, + gasUsed: executeReceipt.gasUsed.toString(), + }); + return; + } catch (error) { + lastError = error; + if (attempt < maxExecuteRetries) { + await advanceChainTime(ONE_HOUR); + } + } + } + + throw new Error(`Failed to execute proposal ${proposalId.toString()}. ` + `Original error: ${String(lastError)}`); +} diff --git a/scripts/upgrade/upgrade-params-hoodi.toml b/scripts/upgrade/upgrade-params-hoodi.toml index a529bd5a5d..f95897abb8 100644 --- a/scripts/upgrade/upgrade-params-hoodi.toml +++ b/scripts/upgrade/upgrade-params-hoodi.toml @@ -1,9 +1,59 @@ # Lido Protocol Upgrade Parameters - Hoodi Configuration # This file contains deployment parameters for upgrading Lido protocol contracts on Ethereum Hoodi testnet -# Predeposit guarantee configuration for validator deposit guarantees -[predepositGuarantee] -genesisForkVersion = "0x10000910" # Ethereum Hoodi testnet genesis fork version -gIndex = "0x0000000000000000000000000000000000000000000000000096000000000028" # Generalized index for state verification -gIndexAfterChange = "0x0000000000000000000000000000000000000000000000000096000000000028" -changeSlot = 0 # Slot number when the change takes effect +[stakingRouterV3VoteScript] +# Common addresses +agent = "0x0534aA41907c9631fae990960bCC72d75fA7cfeD" +stakingRouter = "0xCc820558B39ee15C7C45B59390B503b83fb499A8" +burner = "0xb2c99cd38a2636a6281a849C8de938B3eF4A7C3D" +triggerableWithdrawalsGateway = "0x6679090D92b08a2a686eF8614feECD8cDFE209db" +easyTrackEVMScriptExecutor = "0x79a20FD0FA36453B2F45eAbab19bfef43575Ba9E" +resealManager = "0x05172CbCDb7307228F781436b327679e4DAE166B" +identifiedCommunityStakersGateManager = "0x4AF43Ee34a6fcD1fEcA1e1F832124C763561dA53" +gateSeal = "0x725166f143DdcD9EC1b96dfb70f16E3f44968A65" +# TODO: upgrade-specific GateSealV3 is not present in artifacts/hoodi/deploy-hoodi.json. +# Prefill required. +gateSealV3 = "" +generalDelayedPenaltyReporter = "0x4AF43Ee34a6fcD1fEcA1e1F832124C763561dA53" +hashConsensusInitialEpoch = 47480 + +[stakingRouterV3VoteScript.upgrade] +csmProxy = "0x79CEf36D84743222f37765204Bec41E92a93E59d" +csmImpl = "0x6140195075a2BFaE0C332377af1f1461Fced0791" +parametersRegistryProxy = "0xA4aD5236963f9Fe4229864712269D8d79B65C5Ad" +parametersRegistryImpl = "0xae34514B7A5B402264dB13ac8ffe25766173B3cc" +feeOracleProxy = "0xe7314f561B2e72f9543F1004e741bab6Fc51028B" +feeOracleImpl = "0x2fcc1bE07eAf30FC7daC541327B96B038C73eE7a" +feeOracleConsensusVersion = 4 +vettedGateProxy = "0x10a254E724fe2b7f305F76f3F116a3969c53845f" +vettedGateImpl = "0x86915F7D0C68A627385eF36DdB40A48391118882" +accountingProxy = "0xA54b90BA34C5f326BC1485054080994e38FB4C60" +accountingImpl = "0xD32b59d6054b94516f8EEE2A84C3c33DaF1d06B8" +feeDistributorProxy = "0xaCd9820b0A2229a82dc1A0770307ce5522FF3582" +feeDistributorImpl = "0x1B952bd0C5d65B86d4F1F29589A31584e2bE038f" +exitPenaltiesProxy = "0xD259b31083Be841E5C85b2D481Cfc17C14276800" +exitPenaltiesImpl = "0xF8CC5BFFC0580395a5D8bBE3C4CD07ce3F623d89" +strikesProxy = "0x8fBA385C3c334D251eE413e79d4D3890db98693c" +strikesImpl = "0xF9CcA99e34197E5fc70aB05F5dEb112EcD2367e9" +oldPermissionlessGate = "0x5553077102322689876A6AdFd48D75014c28acfb" +verifier = "0x1773b2Ff99A030F6000554Cb8A5Ec93145650cbA" +# TODO: upgrade-specific VerifierV3 is not present in artifacts/hoodi/deploy-hoodi.json. +# Prefill required. +verifierV3 = "" +permissionlessGate = "0x5553077102322689876A6AdFd48D75014c28acfb" +ejector = "0x777bd76326E4aDcD353b03AD45b33BAF41048476" + +[stakingRouterV3VoteScript.curatedModule] +# TODO: curated module deployment addresses are not present in artifacts/hoodi. +# Prefill required. +module = "" +accounting = "" +ejector = "" +hashConsensus = "" +moduleName = "curated-onchain-v1" +stakeShareLimit = 2000 +priorityExitShareThreshold = 2500 +stakingModuleFee = 800 +treasuryFee = 200 +maxDepositsPerBlock = 30 +minDepositBlockDistance = 25 diff --git a/scripts/upgrade/upgrade-params-mainnet.toml b/scripts/upgrade/upgrade-params-mainnet.toml index 5e7093219f..d048fb4e05 100644 --- a/scripts/upgrade/upgrade-params-mainnet.toml +++ b/scripts/upgrade/upgrade-params-mainnet.toml @@ -1,14 +1,52 @@ # Lido Protocol Upgrade Parameters - Mainnet Configuration # This file contains deployment parameters for upgrading Lido protocol contracts on Ethereum mainnet -# Predeposit guarantee configuration for validator deposit guarantees -[predepositGuarantee] -genesisForkVersion = "0x00000000" # Ethereum mainnet genesis fork version: https://github.com/ethereum/consensus-specs/blob/01b53691dcc36d37a5ad8994b3a32d8de69fb1aa/configs/mainnet.yaml#L30 -gIndex = "0x0000000000000000000000000000000000000000000000000096000000000028" # Generalized index for state verification: https://research.lido.fi/t/lip-27-ensuring-compatibility-with-ethereum-s-pectra-upgrade/9444#p-20086-update-gindexes-5 -gIndexAfterChange = "0x0000000000000000000000000000000000000000000000000096000000000028" # Required for hardfork upgrades, the same for now -changeSlot = 0 # Slot number when the change takes effect - [consolidationGateway] maxConsolidationRequestsLimit = 8000 # Maximum number of consolidations requests that can be processed consolidationsPerFrame = 1 # Number of consolidations processed per frame frameDurationInSec = 48 # Duration of each processing frame in seconds + +[stakingRouterV3VoteScript] +agent = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +easyTrackEVMScriptExecutor = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +resealManager = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" +identifiedCommunityStakersGateManager = "0xC52fC3081123073078698F1EAc2f1Dc7Bd71880f" +gateSeal = "0xE1686C2E90eb41a48356c1cC7FaA17629af3ADB3" +# TODO: upgrade-specific GateSealV3 is not present in artifacts/mainnet/deploy-mainnet.json. +# Prefill required. +gateSealV3 = "" +generalDelayedPenaltyReporter = "0xC52fC3081123073078698F1EAc2f1Dc7Bd71880f" +hashConsensusInitialEpoch = 47480 + +[stakingRouterV3VoteScript.upgrade] +csmProxy = "0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F" +csmImpl = "0x1eB6d4da13ca9566c17F526aE0715325d7a07665" +vettedGateProxy = "0xB314D4A76C457c93150d308787939063F4Cc67E0" +parametersRegistryImpl = "0x25fdC3BE9977CD4da679dF72A64C8B6Bd5216A78" +feeOracleImpl = "0xe0B234f99E413E27D9Bc31aBba9A49A3e570Da97" +feeOracleConsensusVersion = 4 +vettedGateImpl = "0x65D4D92Cd0EabAa05cD5A46269C24b71C21cfdc4" +accountingImpl = "0x6f09d2426c7405C5546413e6059F884D2D03f449" +feeDistributorImpl = "0x5DCF7cF7c6645E9E822a379dF046a8b0390251A1" +exitPenaltiesImpl = "0xDa22fA1CEa40d05Fe4CD536967afdD839586D546" +strikesImpl = "0x3E5021424c9e13FC853e523Cd68ebBec848956a0" +oldPermissionlessGate = "0xcF33a38111d0B1246A3F38a838fb41D626B454f0" +verifier = "0xdC5FE1782B6943f318E05230d688713a560063DC" +# TODO: upgrade-specific VerifierV3 is not present in artifacts/mainnet/deploy-mainnet.json. +# Prefill required. +verifierV3 = "" +permissionlessGate = "0xcF33a38111d0B1246A3F38a838fb41D626B454f0" +ejector = "0xc72b58aa02E0e98cF8A4a0E9Dce75e763800802C" + +[stakingRouterV3VoteScript.curatedModule] +# TODO: curated module deployment addresses are not present in artifacts/mainnet. +# Prefill required. +module = "" +hashConsensus = "" +moduleName = "curated-onchain-v1" +stakeShareLimit = 2000 +priorityExitShareThreshold = 2500 +stakingModuleFee = 800 +treasuryFee = 200 +maxDepositsPerBlock = 30 +minDepositBlockDistance = 25 diff --git a/scripts/utils/staking-router-v3-vote.ts b/scripts/utils/staking-router-v3-vote.ts new file mode 100644 index 0000000000..e3a27f8fc3 --- /dev/null +++ b/scripts/utils/staking-router-v3-vote.ts @@ -0,0 +1,75 @@ +import fs from "fs"; +import { z } from "zod"; + +import * as toml from "@iarna/toml"; + +const UPGRADE_PARAMETERS_FILE = process.env.UPGRADE_PARAMETERS_FILE; + +const EthereumAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address"); +const NonNegativeIntSchema = z.number().int().nonnegative(); + +const UpgradeConfigSchema = z.object({ + csmProxy: EthereumAddressSchema, + csmImpl: EthereumAddressSchema, + vettedGateProxy: EthereumAddressSchema, + parametersRegistryImpl: EthereumAddressSchema, + feeOracleImpl: EthereumAddressSchema, + feeOracleConsensusVersion: NonNegativeIntSchema, + vettedGateImpl: EthereumAddressSchema, + accountingImpl: EthereumAddressSchema, + feeDistributorImpl: EthereumAddressSchema, + exitPenaltiesImpl: EthereumAddressSchema, + strikesImpl: EthereumAddressSchema, + oldPermissionlessGate: EthereumAddressSchema, + verifier: EthereumAddressSchema, + verifierV3: EthereumAddressSchema, + permissionlessGate: EthereumAddressSchema, + ejector: EthereumAddressSchema, +}); + +const CuratedModuleConfigSchema = z.object({ + module: EthereumAddressSchema, + hashConsensus: EthereumAddressSchema, + moduleName: z.string().min(1), + stakeShareLimit: NonNegativeIntSchema, + priorityExitShareThreshold: NonNegativeIntSchema, + stakingModuleFee: NonNegativeIntSchema, + treasuryFee: NonNegativeIntSchema, + maxDepositsPerBlock: NonNegativeIntSchema, + minDepositBlockDistance: NonNegativeIntSchema, +}); + +const StakingRouterV3VoteScriptParamsSchema = z.object({ + agent: EthereumAddressSchema, + easyTrackEVMScriptExecutor: EthereumAddressSchema, + resealManager: EthereumAddressSchema, + identifiedCommunityStakersGateManager: EthereumAddressSchema, + gateSeal: EthereumAddressSchema, + gateSealV3: EthereumAddressSchema, + generalDelayedPenaltyReporter: EthereumAddressSchema, + hashConsensusInitialEpoch: NonNegativeIntSchema, + upgrade: UpgradeConfigSchema, + curatedModule: CuratedModuleConfigSchema, +}); + +const UpgradeTomlSchema = z.object({ + stakingRouterV3VoteScript: StakingRouterV3VoteScriptParamsSchema, +}); + +export type StakingRouterV3VoteScriptParams = z.infer; + +export function readStakingRouterV3VoteScriptParameters(): StakingRouterV3VoteScriptParams { + if (!UPGRADE_PARAMETERS_FILE) { + throw new Error("UPGRADE_PARAMETERS_FILE is not set"); + } + + if (!fs.existsSync(UPGRADE_PARAMETERS_FILE)) { + throw new Error(`Upgrade parameters file not found: ${UPGRADE_PARAMETERS_FILE}`); + } + + const rawData = fs.readFileSync(UPGRADE_PARAMETERS_FILE, "utf8"); + const parsedData = toml.parse(rawData); + const validated = UpgradeTomlSchema.parse(parsedData); + + return validated.stakingRouterV3VoteScript; +}