|
| 1 | +""" |
| 2 | +Vote 2026_04_30 |
| 3 | +
|
| 4 | +1. Submit a Dual Governance proposal to activate Staking Router v3 + Curated Module v2 + Community Staking Module v3 |
| 5 | +# ======================== Core ======================== |
| 6 | +1.1. Call UpgradeTemplate.startUpgrade |
| 7 | +1.2. Upgrade LidoLocator implementation |
| 8 | +1.3. Upgrade and finalize StakingRouter |
| 9 | +1.4. Upgrade and finalize AccountingOracle |
| 10 | +1.5. Upgrade and finalize ValidatorsExitBusOracle |
| 11 | +1.6. Upgrade Accounting implementation |
| 12 | +1.7. Upgrade WithdrawalVault implementation |
| 13 | +1.8. Grant Aragon APP_MANAGER_ROLE to the AGENT |
| 14 | +1.9. Set Lido implementation in Kernel |
| 15 | +1.10. Revoke Aragon APP_MANAGER_ROLE from the AGENT |
| 16 | +1.11. Create and grant Aragon BUFFER_RESERVE_MANAGER_ROLE to the AGENT |
| 17 | +1.12. Call finalizeUpgrade_v4 on Lido |
| 18 | +1.13. Grant STAKING_MODULE_SHARE_MANAGE_ROLE to EasyTrack executor |
| 19 | +1.14. Revoke STAKING_MODULE_UNVETTING_ROLE from old DSM |
| 20 | +1.15. Grant STAKING_MODULE_UNVETTING_ROLE to new DSM |
| 21 | +1.16. Grant TW_EXIT_LIMIT_MANAGER_ROLE to Agent on TWGateway |
| 22 | +1.17. Set TWGateway exit request limits |
| 23 | +1.18. Register CircuitBreaker pauser for ConsolidationGateway |
| 24 | +# ======================== CSM ======================== |
| 25 | +1.19. Upgrade and finalize CSM v3 |
| 26 | +1.20. Upgrade and finalize ParametersRegistry v3 |
| 27 | +1.21. Upgrade and finalize FeeOracle v3 |
| 28 | +1.22. Upgrade CSVettedGate implementation |
| 29 | +1.23. Upgrade and finalize Accounting v3 |
| 30 | +1.24. Upgrade and finalize FeeDistributor v3 |
| 31 | +1.25. Upgrade ExitPenalties implementation |
| 32 | +1.26. Upgrade ValidatorStrikes implementation |
| 33 | +1.27. Point ValidatorStrikes to the new Ejector |
| 34 | +1.28. Revoke REPORT_EL_REWARDS_STEALING_PENALTY_ROLE |
| 35 | +1.29. Grant REPORT_GENERAL_DELAYED_PENALTY_ROLE |
| 36 | +1.30. Revoke SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE |
| 37 | +1.31. Grant SETTLE_GENERAL_DELAYED_PENALTY_ROLE |
| 38 | +1.32. Revoke VERIFIER_ROLE from old verifier |
| 39 | +1.33. Grant VERIFIER_ROLE to new verifier |
| 40 | +1.34. Grant REPORT_REGULAR_WITHDRAWN_VALIDATORS_ROLE to VerifierV3 |
| 41 | +1.35. Grant REPORT_SLASHED_WITHDRAWN_VALIDATORS_ROLE to Easy Track |
| 42 | +1.36. Revoke CREATE_NODE_OPERATOR_ROLE from old PermissionlessGate |
| 43 | +1.37. Grant CREATE_NODE_OPERATOR_ROLE to new PermissionlessGate |
| 44 | +1.38. Revoke START_REFERRAL_SEASON_ROLE |
| 45 | +1.39. Revoke END_REFERRAL_SEASON_ROLE |
| 46 | +1.40. Register CircuitBreaker pauser for CSM new verifier |
| 47 | +1.41. Register CircuitBreaker pauser for CSM Ejector |
| 48 | +1.42. Register CircuitBreaker pauser for CSM identified DVT cluster gate |
| 49 | +1.43. Grant CREATE_NODE_OPERATOR_ROLE to identified DVT cluster gate |
| 50 | +1.44. Grant SET_BOND_CURVE_ROLE to identified DVT cluster gate |
| 51 | +1.45. Grant MANAGE_BOND_CURVES_ROLE to identified DVT cluster curve setup |
| 52 | +1.46. Grant MANAGE_CURVE_PARAMETERS_ROLE to identified DVT cluster curve setup |
| 53 | +1.47. Execute identified DVT cluster curve setup |
| 54 | +1.48. Grant MANAGE_GENERAL_PENALTIES_AND_CHARGES_ROLE to CSM Committee |
| 55 | +1.49. Revoke REQUEST_BURN_SHARES_ROLE from CSM Accounting |
| 56 | +1.50. Grant REQUEST_BURN_MY_STETH_ROLE to CSM Accounting |
| 57 | +1.51. Revoke TWG full-withdrawal role from old Ejector |
| 58 | +1.52. Grant TWG full-withdrawal role to new Ejector |
| 59 | +# ======================== Curated Module ======================== |
| 60 | +1.53. Add Curated module to StakingRouter |
| 61 | +1.54. Grant REQUEST_BURN_MY_STETH_ROLE to Curated Accounting |
| 62 | +1.55. Grant TWG full-withdrawal role to Curated Ejector |
| 63 | +1.56. Grant RESUME_ROLE to agent on Curated module |
| 64 | +1.57. Resume Curated module |
| 65 | +1.58. Revoke RESUME_ROLE from agent on Curated module |
| 66 | +1.59. Update Curated HashConsensus frame config |
| 67 | +1.60. Register CircuitBreaker pauser for Curated module |
| 68 | +1.61. Register CircuitBreaker pauser for Curated Accounting |
| 69 | +1.62. Register CircuitBreaker pauser for Curated FeeOracle |
| 70 | +1.63. Register CircuitBreaker pauser for Curated Verifier |
| 71 | +1.64. Register CircuitBreaker pauser for Curated Ejector |
| 72 | +# ======================== Finish Upgrade ======================== |
| 73 | +1.65. Call UpgradeTemplate.finishUpgrade |
| 74 | +
|
| 75 | +# ======================== EasyTrack ======================== |
| 76 | +2. Remove CSMSettleElStealingPenalty ET factory |
| 77 | +3. Remove CSMSetVettedGateTree ET factory |
| 78 | +4. Add UpdateStakingModuleShareLimits ET factory |
| 79 | +5. Add AllowConsolidationPair ET factory |
| 80 | +6. Add SetMerkleGateTree CSM ET factory |
| 81 | +7. Add ReportWithdrawalsForSlashedValidators CSM ET factory |
| 82 | +8. Add SettleGeneralDelayedPenalty CSM ET factory |
| 83 | +9. Add SetMerkleGateTree CM ET factory |
| 84 | +10. Add ReportWithdrawalsForSlashedValidators CM ET factory |
| 85 | +11. Add SettleGeneralDelayedPenalty CM ET factory |
| 86 | +12. Add CreateOrUpdateOperatorGroup CM ET factory |
| 87 | +
|
| 88 | +Vote passed & executed on Apr-30-2026 02:04:12 PM +UTC, block 2721709. |
| 89 | +
|
| 90 | +""" |
| 91 | + |
| 92 | +from typing import Dict, List, Optional, Tuple |
| 93 | + |
| 94 | +from brownie import interface |
| 95 | + |
| 96 | +from utils.config import get_deployer_account, get_is_live, get_priority_fee |
| 97 | +from utils.dual_governance import submit_proposals |
| 98 | +from utils.ipfs import calculate_vote_ipfs_description, upload_vote_ipfs_description |
| 99 | +from utils.mainnet_fork import pass_and_exec_dao_vote |
| 100 | +from utils.voting import bake_vote_items, confirm_vote_script, create_vote |
| 101 | + |
| 102 | + |
| 103 | +# ============================== Addresses =================================== |
| 104 | +UPGRADE_VOTE_SCRIPT = "0xaC83987948dB29c54b91B9a3Bd7a5cA99fA7F1D1" |
| 105 | + |
| 106 | + |
| 107 | +# ============================= Description ================================== |
| 108 | +DG_PROPOSAL_METADATA = "Activate Staking Router v3 + Curated Module v2 + Community Staking Module v3" |
| 109 | +DG_SUBMISSION_DESCRIPTION = "1. Submit a Dual Governance proposal to activate Staking Router v3 + Curated Module v2 + Community Staking Module v3" |
| 110 | +IPFS_DESCRIPTION = """ |
| 111 | +1. **Activate Staking Router v3**, including protocol contract upgrades and Dual Governance execution setup. Items 1.1-1.18. |
| 112 | +2. **Upgrade Community Staking Module to v3**, including CSM contract upgrades, role updates and identified DVT cluster setup. Items 1.19-1.52. |
| 113 | +3. **Add and configure Curated Module v2**. Items 1.53-1.64. |
| 114 | +4. **Finalize the protocol upgrade**. Item 1.65. |
| 115 | +5. **Update Easy Track factories for CSM v3 and Curated Module v2 operations**. Items 2-12. |
| 116 | +""" |
| 117 | + |
| 118 | + |
| 119 | +def is_placeholder_vote_script_address(value: str) -> bool: |
| 120 | + normalized = value.strip().lower() |
| 121 | + return normalized in ("", "0x0000000000000000000000000000000000000000") or normalized.startswith("todo") |
| 122 | + |
| 123 | + |
| 124 | +def get_dg_items(upgrade_vote_script: Optional[str] = None) -> List[Tuple[str, str]]: |
| 125 | + vote_script_address = (upgrade_vote_script or UPGRADE_VOTE_SCRIPT).strip() |
| 126 | + if is_placeholder_vote_script_address(vote_script_address): |
| 127 | + raise ValueError( |
| 128 | + "UpgradeVoteScript address is not configured. " |
| 129 | + "Pass upgrade_vote_script explicitly or set UPGRADE_VOTE_SCRIPT at the top of this file." |
| 130 | + ) |
| 131 | + |
| 132 | + omnibus = interface.UpgradeVoteScript(vote_script_address) |
| 133 | + dg_items: List[Tuple[str, str]] = [] |
| 134 | + |
| 135 | + for _, call_script in omnibus.getVoteItems(): |
| 136 | + dg_items.append((call_script[0], call_script[1].hex())) |
| 137 | + |
| 138 | + return dg_items |
| 139 | + |
| 140 | + |
| 141 | +def get_vote_items( |
| 142 | + upgrade_vote_script: Optional[str] = None, |
| 143 | +) -> Tuple[List[str], List[Tuple[str, str]]]: |
| 144 | + vote_script_address = (upgrade_vote_script or UPGRADE_VOTE_SCRIPT).strip() |
| 145 | + if is_placeholder_vote_script_address(vote_script_address): |
| 146 | + raise ValueError( |
| 147 | + "UpgradeVoteScript address is not configured. " |
| 148 | + "Pass upgrade_vote_script explicitly or set UPGRADE_VOTE_SCRIPT at the top of this file." |
| 149 | + ) |
| 150 | + |
| 151 | + omnibus = interface.UpgradeVoteScript(vote_script_address) |
| 152 | + |
| 153 | + vote_desc_items: List[str] = [] |
| 154 | + call_script_items: List[Tuple[str, str]] = [] |
| 155 | + |
| 156 | + dg_items = get_dg_items(upgrade_vote_script) |
| 157 | + |
| 158 | + dg_call_script = submit_proposals([(dg_items, DG_PROPOSAL_METADATA)]) |
| 159 | + vote_desc_items.append(DG_SUBMISSION_DESCRIPTION) |
| 160 | + call_script_items.append(dg_call_script[0]) |
| 161 | + |
| 162 | + voting_items = omnibus.getVotingVoteItems() |
| 163 | + for desc, call_script in voting_items: |
| 164 | + vote_desc_items.append(desc) |
| 165 | + call_script_items.append((call_script[0], call_script[1].hex())) |
| 166 | + |
| 167 | + return vote_desc_items, call_script_items |
| 168 | + |
| 169 | + |
| 170 | +def start_vote( |
| 171 | + tx_params: Dict[str, str], |
| 172 | + silent: bool = False, |
| 173 | + upgrade_vote_script: Optional[str] = None, |
| 174 | +): |
| 175 | + vote_desc_items, call_script_items = get_vote_items( |
| 176 | + upgrade_vote_script=upgrade_vote_script, |
| 177 | + ) |
| 178 | + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) |
| 179 | + desc_ipfs = ( |
| 180 | + calculate_vote_ipfs_description(IPFS_DESCRIPTION) |
| 181 | + if silent |
| 182 | + else upload_vote_ipfs_description(IPFS_DESCRIPTION) |
| 183 | + ) |
| 184 | + |
| 185 | + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( |
| 186 | + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) |
| 187 | + ) |
| 188 | + |
| 189 | + vote_script_address = (upgrade_vote_script or UPGRADE_VOTE_SCRIPT).strip() |
| 190 | + assert interface.UpgradeVoteScript(vote_script_address).isValidVoteScript( |
| 191 | + vote_id, |
| 192 | + DG_PROPOSAL_METADATA, |
| 193 | + ) |
| 194 | + |
| 195 | + return vote_id, tx |
| 196 | + |
| 197 | + |
| 198 | +def main(upgrade_vote_script: Optional[str] = None): |
| 199 | + tx_params: Dict[str, str] = {"from": get_deployer_account().address} |
| 200 | + if get_is_live(): |
| 201 | + tx_params["priority_fee"] = get_priority_fee() |
| 202 | + |
| 203 | + vote_id, _ = start_vote( |
| 204 | + tx_params=tx_params, |
| 205 | + silent=False, |
| 206 | + upgrade_vote_script=upgrade_vote_script, |
| 207 | + ) |
| 208 | + vote_id >= 0 and print(f"Vote created: {vote_id}.") |
| 209 | + |
| 210 | + |
| 211 | +def start_and_execute_vote_on_fork_manual(upgrade_vote_script: Optional[str] = None): |
| 212 | + if get_is_live(): |
| 213 | + raise Exception("This script is for local testing only.") |
| 214 | + |
| 215 | + tx_params = {"from": get_deployer_account()} |
| 216 | + vote_id, _ = start_vote( |
| 217 | + tx_params=tx_params, |
| 218 | + silent=True, |
| 219 | + upgrade_vote_script=upgrade_vote_script, |
| 220 | + ) |
| 221 | + print(f"Vote created: {vote_id}.") |
| 222 | + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) |
0 commit comments