From 815933c0c85207e02ca6e163d2aa80170d946b17 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 14:14:02 +0300 Subject: [PATCH 01/29] feat: add helpers to create permission and set permission manager --- utils/permissions.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/utils/permissions.py b/utils/permissions.py index 4089f05ed..c6a5d1856 100644 --- a/utils/permissions.py +++ b/utils/permissions.py @@ -24,6 +24,18 @@ def encode_permission_revoke(target_app, permission_name, revoke_from) -> Tuple[ return acl.address, acl.revokePermission.encode_input(revoke_from, target_app, permission_id) +def encode_permission_set_manager(target_app, permission_name: str, grant_to: str) -> Tuple[str, str]: + acl = contracts.acl + permission_id = convert.to_uint(Web3.keccak(text=permission_name)) + return acl.address, acl.setPermissionManager.encode_input(grant_to, target_app, permission_id) + + +def encode_permission_create(target_app, permission_name: str, grant_to, manager) -> Tuple[str, str]: + acl = contracts.acl + permission_id = convert.to_uint(Web3.keccak(text=permission_name)) + return acl.address, acl.createPermission.encode_input(grant_to, target_app, permission_id, manager) + + def encode_permission_grant_p( target_app, permission_name: str, From 4f25ff70b6573bb6a1a6666d4fdb2f63af0d2832 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 14:14:41 +0300 Subject: [PATCH 02/29] feat: add allowed tokens registry to config --- configs/config_mainnet.py | 2 ++ utils/config.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 1c784fe60..4c66c5cd3 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -70,6 +70,8 @@ EASYTRACK_SIMPLE_DVT_CHANGE_NODE_OPERATOR_MANAGERS_FACTORY = "0xE31A0599A6772BCf9b2bFc9e25cf941e793c9a7D" EASYTRACK_CSM_SETTLE_EL_REWARDS_STEALING_PENALTY_FACTORY = "0xF6B6E7997338C48Ea3a8BCfa4BB64a315fDa76f4" +EASYTRACK_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" + # Multisigs FINANCE_MULTISIG = "0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb" L1_EMERGENCY_BRAKES_MULTISIG = "0x73b047fe6337183A454c5217241D780a932777bD" diff --git a/utils/config.py b/utils/config.py index 767e86afc..c5c80115c 100644 --- a/utils/config.py +++ b/utils/config.py @@ -390,6 +390,10 @@ def trp_escrow_factory(self) -> interface.VestingEscrowFactory: @property def token_rate_notifier(self) -> interface.TokenRateNotifier: return interface.TokenRateNotifier(L1_TOKEN_RATE_NOTIFIER) + + @property + def allowed_tokens_registry(self) -> interface.AllowedTokensRegistry: + return interface.AllowedTokensRegistry(EASYTRACK_ALLOWED_TOKENS_REGISTRY) def __getattr__(name: str) -> Any: if name == "contracts": From 6bc0b07b1d9d02741557e4d55956822faa2cdf26 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 14:19:24 +0300 Subject: [PATCH 03/29] feat: add inteface for dg verifier --- interfaces/DualGovernanceVerifier.json | 611 +++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 interfaces/DualGovernanceVerifier.json diff --git a/interfaces/DualGovernanceVerifier.json b/interfaces/DualGovernanceVerifier.json new file mode 100644 index 000000000..4a6b56628 --- /dev/null +++ b/interfaces/DualGovernanceVerifier.json @@ -0,0 +1,611 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "Duration", + "name": "MIN_EXECUTION_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "AFTER_SUBMIT_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_AFTER_SUBMIT_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "AFTER_SCHEDULE_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_AFTER_SCHEDULE_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "EMERGENCY_MODE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_EMERGENCY_MODE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "EMERGENCY_PROTECTION_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_EMERGENCY_PROTECTION_DURATION", + "type": "uint32" + }, + { + "internalType": "address", + "name": "EMERGENCY_ACTIVATION_COMMITTEE", + "type": "address" + }, + { + "internalType": "address", + "name": "EMERGENCY_EXECUTION_COMMITTEE", + "type": "address" + }, + { + "internalType": "address", + "name": "RESEAL_COMMITTEE", + "type": "address" + }, + { + "internalType": "uint256", + "name": "MIN_WITHDRAWALS_BATCH_SIZE", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "Duration", + "name": "activationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "minActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "executionDelay", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "influencers", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "nodeOperators", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "protocols", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "sealableWithdrawalBlockers", + "type": "address[]" + } + ], + "internalType": "struct TiebreakerDeployConfig", + "name": "tiebreakerConfig", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT", + "type": "uint256" + }, + { + "internalType": "PercentD16", + "name": "FIRST_SEAL_RAGE_QUIT_SUPPORT", + "type": "uint128" + }, + { + "internalType": "PercentD16", + "name": "SECOND_SEAL_RAGE_QUIT_SUPPORT", + "type": "uint128" + }, + { + "internalType": "Duration", + "name": "MIN_ASSETS_LOCK_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_MIN_ASSETS_LOCK_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MIN_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MAX_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MIN_ACTIVE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_DEACTIVATION_MAX_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_COOLDOWN_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_EXTENSION_PERIOD_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH", + "type": "uint32" + }, + { + "internalType": "address", + "name": "TEMPORARY_EMERGENCY_GOVERNANCE_PROPOSER", + "type": "address" + } + ], + "internalType": "struct DeployConfig", + "name": "config", + "type": "tuple" + }, + { + "components": [ + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "contract IStETH", + "name": "stETH", + "type": "address" + }, + { + "internalType": "contract IWstETH", + "name": "wstETH", + "type": "address" + }, + { + "internalType": "contract IWithdrawalQueue", + "name": "withdrawalQueue", + "type": "address" + }, + { "internalType": "address", "name": "voting", "type": "address" } + ], + "internalType": "struct LidoContracts", + "name": "lidoAddresses", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "DurationOverflow", "type": "error" }, + { "inputs": [], "name": "TimestampOverflow", "type": "error" }, + { "anonymous": false, "inputs": [], "name": "Verified", "type": "event" }, + { + "inputs": [], + "name": "getConfig", + "outputs": [ + { + "components": [ + { + "internalType": "Duration", + "name": "MIN_EXECUTION_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "AFTER_SUBMIT_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_AFTER_SUBMIT_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "AFTER_SCHEDULE_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_AFTER_SCHEDULE_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "EMERGENCY_MODE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_EMERGENCY_MODE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "EMERGENCY_PROTECTION_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_EMERGENCY_PROTECTION_DURATION", + "type": "uint32" + }, + { + "internalType": "address", + "name": "EMERGENCY_ACTIVATION_COMMITTEE", + "type": "address" + }, + { + "internalType": "address", + "name": "EMERGENCY_EXECUTION_COMMITTEE", + "type": "address" + }, + { + "internalType": "address", + "name": "RESEAL_COMMITTEE", + "type": "address" + }, + { + "internalType": "uint256", + "name": "MIN_WITHDRAWALS_BATCH_SIZE", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "Duration", + "name": "activationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "minActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "executionDelay", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "influencers", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "nodeOperators", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + } + ], + "internalType": "struct TiebreakerSubCommitteeDeployConfig", + "name": "protocols", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "quorum", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "sealableWithdrawalBlockers", + "type": "address[]" + } + ], + "internalType": "struct TiebreakerDeployConfig", + "name": "tiebreakerConfig", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT", + "type": "uint256" + }, + { + "internalType": "PercentD16", + "name": "FIRST_SEAL_RAGE_QUIT_SUPPORT", + "type": "uint128" + }, + { + "internalType": "PercentD16", + "name": "SECOND_SEAL_RAGE_QUIT_SUPPORT", + "type": "uint128" + }, + { + "internalType": "Duration", + "name": "MIN_ASSETS_LOCK_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "MAX_MIN_ASSETS_LOCK_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MIN_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MAX_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_MIN_ACTIVE_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_SIGNALLING_DEACTIVATION_MAX_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "VETO_COOLDOWN_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_EXTENSION_PERIOD_DURATION", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH", + "type": "uint32" + }, + { + "internalType": "address", + "name": "TEMPORARY_EMERGENCY_GOVERNANCE_PROPOSER", + "type": "address" + } + ], + "internalType": "struct DeployConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLidoAddresses", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "contract IStETH", + "name": "stETH", + "type": "address" + }, + { + "internalType": "contract IWstETH", + "name": "wstETH", + "type": "address" + }, + { + "internalType": "contract IWithdrawalQueue", + "name": "withdrawalQueue", + "type": "address" + }, + { "internalType": "address", "name": "voting", "type": "address" } + ], + "internalType": "struct LidoContracts", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract Executor", + "name": "adminExecutor", + "type": "address" + }, + { + "internalType": "contract IEmergencyProtectedTimelock", + "name": "timelock", + "type": "address" + }, + { + "internalType": "contract TimelockedGovernance", + "name": "emergencyGovernance", + "type": "address" + }, + { + "internalType": "contract ResealManager", + "name": "resealManager", + "type": "address" + }, + { + "internalType": "contract DualGovernance", + "name": "dualGovernance", + "type": "address" + }, + { + "internalType": "contract TiebreakerCoreCommittee", + "name": "tiebreakerCoreCommittee", + "type": "address" + }, + { + "internalType": "contract TiebreakerSubCommittee", + "name": "tiebreakerSubCommitteeInfluencers", + "type": "address" + }, + { + "internalType": "contract TiebreakerSubCommittee", + "name": "tiebreakerSubCommitteeNodeOperators", + "type": "address" + }, + { + "internalType": "contract TiebreakerSubCommittee", + "name": "tiebreakerSubCommitteeProtocols", + "type": "address" + }, + { + "internalType": "contract TimelockedGovernance", + "name": "temporaryEmergencyGovernance", + "type": "address" + } + ], + "internalType": "struct DeployedContracts", + "name": "dgContracts", + "type": "tuple" + }, + { "internalType": "bool", "name": "onchainVotingCheck", "type": "bool" } + ], + "name": "verify", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] From cb168cf2564a54f32cd0c3a7b6406661a29354d2 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 14:50:57 +0300 Subject: [PATCH 04/29] feat: add dual governance contracts to config --- configs/config_holesky.py | 5 + configs/config_mainnet.py | 5 + interfaces/DualGovernance.json | 1117 +++++++++++++++++++ interfaces/DualGovernanceAdminExecutor.json | 142 +++ utils/config.py | 12 + 5 files changed, 1281 insertions(+) create mode 100644 interfaces/DualGovernance.json create mode 100644 interfaces/DualGovernanceAdminExecutor.json diff --git a/configs/config_holesky.py b/configs/config_holesky.py index c9b22258d..3843d2113 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -48,6 +48,11 @@ WITHDRAWAL_VAULT = "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" +# Dual Governance +DUAL_GOVERNANCE = "0x9F14118Fc548658660a40B351C782a22e9937b42" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04" +DUAL_GOVERNANCE_VERIFIER = "0xd67DF125fDC3360DeCB880804D1FA3Ae3fC6FFF1" + # EasyTracks EASYTRACK = "0x1763b9ED3586B08AE796c7787811a2E1bc16163a" diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 4c66c5cd3..ae9927095 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -54,6 +54,11 @@ DEPOSIT_SECURITY_MODULE_V1 = "0x710B3303fB508a84F10793c1106e32bE873C24cd" WITHDRAWAL_VAULT = "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f" +# Dual Governance +DUAL_GOVERNANCE = "0x0000000000000000000000000000000000000000" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x0000000000000000000000000000000000000000" +DUAL_GOVERNANCE_VERIFIER = "0x0000000000000000000000000000000000000000" + # EasyTracks EASYTRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" diff --git a/interfaces/DualGovernance.json b/interfaces/DualGovernance.json new file mode 100644 index 000000000..9f9c30d40 --- /dev/null +++ b/interfaces/DualGovernance.json @@ -0,0 +1,1117 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ITimelock", + "name": "timelock", + "type": "address" + }, + { + "internalType": "contract IResealManager", + "name": "resealManager", + "type": "address" + }, + { + "internalType": "contract IDualGovernanceConfigProvider", + "name": "configProvider", + "type": "address" + } + ], + "internalType": "struct DualGovernance.DualGovernanceComponents", + "name": "components", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "contract IStETH", + "name": "stETH", + "type": "address" + }, + { + "internalType": "contract IWstETH", + "name": "wstETH", + "type": "address" + }, + { + "internalType": "contract IWithdrawalQueue", + "name": "withdrawalQueue", + "type": "address" + } + ], + "internalType": "struct DualGovernance.SignallingTokens", + "name": "signallingTokens", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "minWithdrawalsBatchSize", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "minTiebreakerActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxTiebreakerActivationTimeout", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "maxSealableWithdrawalBlockersCount", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "maxMinAssetsLockDuration", + "type": "uint32" + } + ], + "internalType": "struct DualGovernance.SanityCheckParams", + "name": "sanityCheckParams", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "AlreadyInitialized", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotAdminExecutor", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotProposalsCanceller", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotResealCommittee", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotTiebreakerCommittee", + "type": "error" + }, + { "inputs": [], "name": "DurationOverflow", "type": "error" }, + { "inputs": [], "name": "DurationUnderflow", "type": "error" }, + { "inputs": [], "name": "ERC1167FailedCreateClone", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "executor", "type": "address" } + ], + "name": "ExecutorNotRegistered", + "type": "error" + }, + { "inputs": [], "name": "IndexOneBasedOverflow", "type": "error" }, + { "inputs": [], "name": "IndexOneBasedUnderflow", "type": "error" }, + { + "inputs": [ + { + "internalType": "contract IDualGovernanceConfigProvider", + "name": "configProvider", + "type": "address" + } + ], + "name": "InvalidConfigProvider", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "executor", "type": "address" } + ], + "name": "InvalidExecutor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "minAssetsLockDuration", + "type": "uint32" + } + ], + "name": "InvalidMinAssetsLockDuration", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "canceller", "type": "address" } + ], + "name": "InvalidProposalsCanceller", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "InvalidProposerAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "rageQuitEthWithdrawalsMinDelay", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "rageQuitEthWithdrawalsMaxDelay", + "type": "uint32" + } + ], + "name": "InvalidRageQuitEthWithdrawalsDelayRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "PercentD16", + "name": "firstSealRageQuitSupport", + "type": "uint128" + }, + { + "internalType": "PercentD16", + "name": "secondSealRageQuitSupport", + "type": "uint128" + } + ], + "name": "InvalidRageQuitSupportRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "resealCommittee", + "type": "address" + } + ], + "name": "InvalidResealCommittee", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IResealManager", + "name": "resealManager", + "type": "address" + } + ], + "name": "InvalidResealManager", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "sealable", "type": "address" } + ], + "name": "InvalidSealable", + "type": "error" + }, + { + "inputs": [ + { "internalType": "Duration", "name": "timeout", "type": "uint32" } + ], + "name": "InvalidTiebreakerActivationTimeout", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "minTiebreakerActivationTimeout", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxTiebreakerActivationTimeout", + "type": "uint32" + } + ], + "name": "InvalidTiebreakerActivationTimeoutBounds", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "InvalidTiebreakerCommittee", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "vetoSignallingMinDuration", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "vetoSignallingMaxDuration", + "type": "uint32" + } + ], + "name": "InvalidVetoSignallingDurationRange", + "type": "error" + }, + { "inputs": [], "name": "PercentD16Underflow", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "ProposalSchedulingBlocked", + "type": "error" + }, + { "inputs": [], "name": "ProposalSubmissionBlocked", "type": "error" }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "ProposerAlreadyRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "ProposerNotRegistered", + "type": "error" + }, + { "inputs": [], "name": "ResealIsNotAllowedInNormalState", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "sealable", "type": "address" } + ], + "name": "SealableWithdrawalBlockerAlreadyAdded", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "sealable", "type": "address" } + ], + "name": "SealableWithdrawalBlockerNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "SealableWithdrawalBlockersLimitReached", + "type": "error" + }, + { "inputs": [], "name": "TiebreakNotAllowed", "type": "error" }, + { "inputs": [], "name": "TimestampOverflow", "type": "error" }, + { + "anonymous": false, + "inputs": [], + "name": "CancelAllPendingProposalsExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "CancelAllPendingProposalsSkipped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IDualGovernanceConfigProvider", + "name": "newConfigProvider", + "type": "address" + } + ], + "name": "ConfigProviderSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "enum State", + "name": "from", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "enum State", + "name": "to", + "type": "uint8" + }, + { + "components": [ + { "internalType": "enum State", "name": "state", "type": "uint8" }, + { + "internalType": "Timestamp", + "name": "enteredAt", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "vetoSignallingActivatedAt", + "type": "uint40" + }, + { + "internalType": "contract ISignallingEscrow", + "name": "signallingEscrow", + "type": "address" + }, + { "internalType": "uint8", "name": "rageQuitRound", "type": "uint8" }, + { + "internalType": "Timestamp", + "name": "vetoSignallingReactivationTime", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "normalOrVetoCooldownExitedAt", + "type": "uint40" + }, + { + "internalType": "contract IRageQuitEscrow", + "name": "rageQuitEscrow", + "type": "address" + }, + { + "internalType": "contract IDualGovernanceConfigProvider", + "name": "configProvider", + "type": "address" + } + ], + "indexed": false, + "internalType": "struct DualGovernanceStateMachine.Context", + "name": "state", + "type": "tuple" + } + ], + "name": "DualGovernanceStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IEscrowBase", + "name": "escrowMasterCopy", + "type": "address" + } + ], + "name": "EscrowMasterCopyDeployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract ISignallingEscrow", + "name": "escrow", + "type": "address" + } + ], + "name": "NewSignallingEscrowDeployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "metadata", + "type": "string" + } + ], + "name": "ProposalSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "proposalsCanceller", + "type": "address" + } + ], + "name": "ProposalsCancellerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "executor", + "type": "address" + } + ], + "name": "ProposerExecutorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "executor", + "type": "address" + } + ], + "name": "ProposerRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "executor", + "type": "address" + } + ], + "name": "ProposerUnregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "resealCommittee", + "type": "address" + } + ], + "name": "ResealCommitteeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "contract IResealManager", + "name": "resealManager", + "type": "address" + } + ], + "name": "ResealManagerSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sealable", + "type": "address" + } + ], + "name": "SealableWithdrawalBlockerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sealable", + "type": "address" + } + ], + "name": "SealableWithdrawalBlockerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "Duration", + "name": "newTiebreakerActivationTimeout", + "type": "uint32" + } + ], + "name": "TiebreakerActivationTimeoutSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTiebreakerCommittee", + "type": "address" + } + ], + "name": "TiebreakerCommitteeSet", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_TIEBREAKER_ACTIVATION_TIMEOUT", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TIEBREAKER_ACTIVATION_TIMEOUT", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TIMELOCK", + "outputs": [ + { "internalType": "contract ITimelock", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "activateNextState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sealableWithdrawalBlocker", + "type": "address" + } + ], + "name": "addTiebreakerSealableWithdrawalBlocker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "canCancelAllPendingProposals", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "canScheduleProposal", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "canSubmitProposal", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cancelAllPendingProposals", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getConfigProvider", + "outputs": [ + { + "internalType": "contract IDualGovernanceConfigProvider", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEffectiveState", + "outputs": [ + { + "internalType": "enum State", + "name": "effectiveState", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPersistedState", + "outputs": [ + { + "internalType": "enum State", + "name": "persistedState", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsCanceller", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "getProposer", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "executor", "type": "address" } + ], + "internalType": "struct Proposers.Proposer", + "name": "proposer", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposers", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "address", "name": "executor", "type": "address" } + ], + "internalType": "struct Proposers.Proposer[]", + "name": "proposers", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRageQuitEscrow", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResealCommittee", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResealManager", + "outputs": [ + { + "internalType": "contract IResealManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStateDetails", + "outputs": [ + { + "components": [ + { + "internalType": "enum State", + "name": "effectiveState", + "type": "uint8" + }, + { + "internalType": "enum State", + "name": "persistedState", + "type": "uint8" + }, + { + "internalType": "Timestamp", + "name": "persistedStateEnteredAt", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "vetoSignallingActivatedAt", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "vetoSignallingReactivationTime", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "normalOrVetoCooldownExitedAt", + "type": "uint40" + }, + { + "internalType": "uint256", + "name": "rageQuitRound", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "vetoSignallingDuration", + "type": "uint32" + } + ], + "internalType": "struct IDualGovernance.StateDetails", + "name": "stateDetails", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTiebreakerDetails", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "isTie", "type": "bool" }, + { + "internalType": "address", + "name": "tiebreakerCommittee", + "type": "address" + }, + { + "internalType": "Duration", + "name": "tiebreakerActivationTimeout", + "type": "uint32" + }, + { + "internalType": "address[]", + "name": "sealableWithdrawalBlockers", + "type": "address[]" + } + ], + "internalType": "struct ITiebreaker.TiebreakerDetails", + "name": "tiebreakerState", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVetoSignallingEscrow", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "executor", "type": "address" } + ], + "name": "isExecutor", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "isProposer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { "internalType": "address", "name": "executor", "type": "address" } + ], + "name": "registerProposer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sealableWithdrawalBlocker", + "type": "address" + } + ], + "name": "removeTiebreakerSealableWithdrawalBlocker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sealable", "type": "address" } + ], + "name": "resealSealable", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "scheduleProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDualGovernanceConfigProvider", + "name": "newConfigProvider", + "type": "address" + } + ], + "name": "setConfigProvider", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newProposalsCanceller", + "type": "address" + } + ], + "name": "setProposalsCanceller", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + }, + { "internalType": "address", "name": "newExecutor", "type": "address" } + ], + "name": "setProposerExecutor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newResealCommittee", + "type": "address" + } + ], + "name": "setResealCommittee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IResealManager", + "name": "newResealManager", + "type": "address" + } + ], + "name": "setResealManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "newTiebreakerActivationTimeout", + "type": "uint32" + } + ], + "name": "setTiebreakerActivationTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newTiebreakerCommittee", + "type": "address" + } + ], + "name": "setTiebreakerCommittee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint96", "name": "value", "type": "uint96" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "internalType": "struct ExternalCall[]", + "name": "calls", + "type": "tuple[]" + }, + { "internalType": "string", "name": "metadata", "type": "string" } + ], + "name": "submitProposal", + "outputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "sealable", "type": "address" } + ], + "name": "tiebreakerResumeSealable", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "tiebreakerScheduleProposal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proposerAccount", + "type": "address" + } + ], + "name": "unregisterProposer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/DualGovernanceAdminExecutor.json b/interfaces/DualGovernanceAdminExecutor.json new file mode 100644 index 000000000..ccf0c519b --- /dev/null +++ b/interfaces/DualGovernanceAdminExecutor.json @@ -0,0 +1,142 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "ETHReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "returndata", + "type": "bytes" + } + ], + "name": "Executed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOwner", "type": "address" } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/utils/config.py b/utils/config.py index c5c80115c..3992b8ef7 100644 --- a/utils/config.py +++ b/utils/config.py @@ -395,6 +395,18 @@ def token_rate_notifier(self) -> interface.TokenRateNotifier: def allowed_tokens_registry(self) -> interface.AllowedTokensRegistry: return interface.AllowedTokensRegistry(EASYTRACK_ALLOWED_TOKENS_REGISTRY) + @property + def dual_governance(self) -> interface.DualGovernance: + return interface.DualGovernance(DUAL_GOVERNANCE) + + @property + def dual_governance_admin_executor(self) -> interface.DualGovernanceAdminExecutor: + return interface.DualGovernanceAdminExecutor(DUAL_GOVERNANCE_ADMIN_EXECUTOR) + + @property + def dual_governance_verifier(self) -> interface.DualGovernanceVerifier: + return interface.DualGovernanceVerifier(DUAL_GOVERNANCE_VERIFIER) + def __getattr__(name: str) -> Any: if name == "contracts": return ContractsLazyLoader() From ede3fa263b0a8f933b6c0d36ff7594fae49027e9 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 17:44:50 +0300 Subject: [PATCH 05/29] feat: add allowed tokens registry for holesky --- configs/config_holesky.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configs/config_holesky.py b/configs/config_holesky.py index 3843d2113..f858f8c81 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -68,6 +68,8 @@ EASYTRACK_SIMPLE_DVT_UPDATE_TARGET_VALIDATOR_LIMITS_FACTORY = "0xC91a676A69Eb49be9ECa1954fE6fc861AE07A9A2" EASYTRACK_SIMPLE_DVT_CHANGE_NODE_OPERATOR_MANAGERS_FACTORY = "0xb8C4728bc0826bA5864D02FA53148de7A44C2f7E" +EASYTRACK_ALLOWED_TOKENS_REGISTRY = "0x091C0eC8B4D54a9fcB36269B5D5E5AF43309e666" + # Multisigs FINANCE_MULTISIG = "" From 7917d33eae6f6c7130e923d4c3fbdc5ee95e5166 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 16 Jan 2025 17:45:15 +0300 Subject: [PATCH 06/29] feat: add upgrade script for holesky dry-run --- interfaces/Foo.json | 16 ++ scripts/dual_governance_upgrade_holesky.py | 170 ++++++++++++++++++ tests/dual_governance_upgrade_holesky_test.py | 63 +++++++ 3 files changed, 249 insertions(+) create mode 100644 interfaces/Foo.json create mode 100644 scripts/dual_governance_upgrade_holesky.py create mode 100644 tests/dual_governance_upgrade_holesky_test.py diff --git a/interfaces/Foo.json b/interfaces/Foo.json new file mode 100644 index 000000000..cf096dafb --- /dev/null +++ b/interfaces/Foo.json @@ -0,0 +1,16 @@ +[ + { + "inputs": [], + "name": "bar", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "callCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py new file mode 100644 index 000000000..b3520fcb8 --- /dev/null +++ b/scripts/dual_governance_upgrade_holesky.py @@ -0,0 +1,170 @@ +import time + +from typing import Dict, Tuple, Optional +from brownie.network.transaction import TransactionReceipt +from brownie.network.account import Account, LocalAccount + +from web3 import Web3 +from utils.agent import agent_forward +from utils.voting import bake_vote_items, confirm_vote_script, create_vote +from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description +from utils.config import ( + contracts, + get_deployer_account, + get_is_live, + get_priority_fee, +) +from utils.permissions import ( + encode_permission_set_manager, + encode_permission_create, + encode_permission_revoke, + encode_permission_grant, +) + +try: + from brownie import interface +except ImportError: + print( + "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" + ) + + +description = "Holesky dual governance upgrade dry-run" + +dual_governance_contracts = { + "adminExecutor": "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04", + "timelock": "0x388AB7b65605e21a75Bb50E24a1eA43DD0091fa5", + "emergencyGovernance": "0xd67d96C6C4DF1eF12c2fb4C908a9484333cEfE60", + "resealManager": "0x632c29848A379a7B30Ee6461ea5e7e1e92d264d0", + "dualGovernance": "0x9F14118Fc548658660a40B351C782a22e9937b42", + "tiebreakerCoreCommittee": "0x6093B9b951C72498EE799639D74dC701Ead3f07B", + "tiebreakerSubCommitteeInfluencers": "0x8F4b730099BFcA35fa4bbFD84f790eD34CAa246f", + "tiebreakerSubCommitteeNodeOperators": "0xBB259276147Af98c0e9186e783D4dbC26e82652F", + "tiebreakerSubCommitteeProtocols": "0x485349eBc3241e0bE8eDf7149C535c0b42Fa9504", + "temporaryEmergencyGovernance": "0xc7467FeFF717C18db08BAEF252f11A84F48e8fF7", + # "EMERGENCY_ACTIVATION_COMMITTEE": "0x526d46eCa1d7969924e981ecDbcAa74e9f0EE566", + # "EMERGENCY_EXECUTION_COMMITTEE": "0x526d46eCa1d7969924e981ecDbcAa74e9f0EE566", +} + +def start_vote(tx_params: Dict[str, str], silent: bool = False): + foo_contract = interface.Foo("0x258C151254bB8C6673dEF05fB965D0dD8cB7eA89") + + vote_desc_items, call_script_items = zip( + ( + "Revoke permission for STAKING_CONTROL_ROLE from Voting contract.", + encode_permission_revoke( + target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", revoke_from=contracts.voting + ), + ), + ( + "Grant permission for STAKING_CONTROL_ROLE to Agent contract.", + encode_permission_grant( + target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", grant_to=contracts.agent + ), + ), + ( + "Change permission manager for Lido STAKING_CONTROL_ROLE.", + encode_permission_set_manager(contracts.lido, "STAKING_CONTROL_ROLE", contracts.agent), + ), + ( + "Grant WithdrawalQueue PAUSE_ROLE to Reseal Manager.", + ( + agent_forward( + [ + ( + contracts.withdrawal_queue.address, + contracts.withdrawal_queue.grantRole.encode_input( + contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"] + ), + ) + ] + ) + ), + ), + ( + "Grant WithdrawalQueue RESUME_ROLE to Reseal Manager.", + ( + agent_forward( + [ + ( + contracts.withdrawal_queue.address, + contracts.withdrawal_queue.grantRole.encode_input( + contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"] + ), + ) + ] + ) + ), + ), + ( + "Grant DEFAULT_ADMIN_ROLE to Voting.", + ( + agent_forward( + [ + ( + contracts.allowed_tokens_registry.address, + contracts.allowed_tokens_registry.grantRole.encode_input( + contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting + ), + ) + ] + ) + ), + ), + ( + "Revoke DEFAULT_ADMIN_ROLE from Agent.", + ( + contracts.allowed_tokens_registry.address, + contracts.allowed_tokens_registry.revokeRole.encode_input( + contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent + ), + ), + ), + ( + "Grant permission for EXECUTE_ROLE to DG Executor contract.", + encode_permission_grant( + target_app=contracts.agent, + permission_name="EXECUTE_ROLE", + grant_to=contracts.dual_governance_admin_executor, + ), + ), + ( + "Verify dual governance deployment", + ( + contracts.dual_governance_verifier.address, + contracts.dual_governance_verifier.verify.encode_input(tuple(dual_governance_contracts.values()), False), + ), + ), + ( + "Submit first dual governance proposal", + ( + contracts.dual_governance.address, + contracts.dual_governance.submitProposal.encode_input( + [(foo_contract.address, 0, foo_contract.bar.encode_input())], "Test proposal" + ) + ) + ) + ) + + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) + + if silent: + desc_ipfs = calculate_vote_ipfs_description(description) + else: + desc_ipfs = upload_vote_ipfs_description(description) + + return confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + +def main(): + tx_params: Dict[str, str] = {"from": get_deployer_account().address} + if get_is_live(): + tx_params["priority_fee"] = get_priority_fee() + + vote_id, _ = start_vote(tx_params=tx_params, silent=False) + + vote_id >= 0 and print(f"Vote created: {vote_id}.") + + time.sleep(5) # hack for waiting thread #2. diff --git a/tests/dual_governance_upgrade_holesky_test.py b/tests/dual_governance_upgrade_holesky_test.py new file mode 100644 index 000000000..4e0f58efd --- /dev/null +++ b/tests/dual_governance_upgrade_holesky_test.py @@ -0,0 +1,63 @@ +""" +Tests for voting 23/07/2024. +""" + +from scripts.dual_governance_upgrade_holesky import start_vote, dual_governance_contracts +from utils.config import contracts +from utils.test.tx_tracing_helpers import * +from brownie.network.transaction import TransactionReceipt +from utils.config import contracts + +try: + from brownie import interface +except ImportError: + print( + "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" + ) + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding): + dao_voting = contracts.voting + + # Lido + assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.voting + assert contracts.acl.hasPermission(contracts.voting, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + + # Allowed tokens registry + assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) + assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) + + # Agent + assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.EXECUTE_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.EXECUTE_ROLE()) + + # Reseal manager + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + + # START VOTE + vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] + + tx: TransactionReceipt = helpers.execute_vote( + vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, skip_time=3 * 60 * 60 * 24 + ) + + # Lido + assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.agent + assert not contracts.acl.hasPermission(contracts.voting, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + assert contracts.acl.hasPermission(contracts.agent, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + + # # Allowed tokens registry + assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) + assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) + + # Agent + assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.EXECUTE_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.EXECUTE_ROLE()) + + # Reseal manager + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + + # # Validate vote events + # if not bypass_events_decoding: + # assert count_vote_items_by_events(tx, dao_voting) == 2, "Incorrect voting items count" From c2aa4fda008cc6bd6860eade5c58110002d05650 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Fri, 17 Jan 2025 13:38:51 +0300 Subject: [PATCH 07/29] feat: add fork helpers --- scripts/dual_governance_upgrade_holesky.py | 20 ++++++++++++++++---- utils/mainnet_fork.py | 18 +++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index b3520fcb8..dbc332722 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -1,10 +1,7 @@ import time -from typing import Dict, Tuple, Optional -from brownie.network.transaction import TransactionReceipt -from brownie.network.account import Account, LocalAccount +from typing import Dict -from web3 import Web3 from utils.agent import agent_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description @@ -20,6 +17,8 @@ encode_permission_revoke, encode_permission_grant, ) +from utils.mainnet_fork import pass_and_exec_dao_vote + try: from brownie import interface @@ -168,3 +167,16 @@ def main(): vote_id >= 0 and print(f"Vote created: {vote_id}.") time.sleep(5) # hack for waiting thread #2. + + +def start_and_execute_vote_on_fork(): + if get_is_live(): + raise Exception("This script is for local testing only.") + + tx_params = {"from": get_deployer_account()} + vote_id, _ = start_vote(tx_params=tx_params, silent=True) + + time.sleep(5) # hack for waiting thread #2. + + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id)) diff --git a/utils/mainnet_fork.py b/utils/mainnet_fork.py index 3c03ee6ac..7db63b5dc 100644 --- a/utils/mainnet_fork.py +++ b/utils/mainnet_fork.py @@ -1,7 +1,7 @@ from contextlib import contextmanager from brownie import chain, accounts, interface -from utils.config import VOTING +from utils.config import VOTING, network_name @contextmanager @@ -27,12 +27,16 @@ def pass_and_exec_dao_vote(vote_id): if not dao_voting.canExecute(vote_id): print(f"Passing vote {vote_id}") - # together these accounts hold 15% of LDO total supply - ldo_holders = [ - "0x3e40d73eb977dc6a537af587d48316fee66e9c8c", - "0xb8d83908aab38a159f3da47a59d84db8e1838712", - "0xa2dfc431297aee387c05beef507e5335e684fbcd", - ] + + if network_name() in ("holesky", "holesky-fork"): + ldo_holders = ["0xc807d4036B400dE8f6cD2aDbd8d9cf9a3a01CC30"] + else: + # together these accounts hold 15% of LDO total supply + ldo_holders = [ + "0x3e40d73eb977dc6a537af587d48316fee66e9c8c", + "0xb8d83908aab38a159f3da47a59d84db8e1838712", + "0xa2dfc431297aee387c05beef507e5335e684fbcd", + ] for holder_addr in ldo_holders: print(f" voting from {holder_addr}") From 3fc8b650fc78097bdddf9bcd2015a67b5ea46acb Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Mon, 20 Jan 2025 11:39:53 +0300 Subject: [PATCH 08/29] fix: change verifier parameter --- scripts/dual_governance_upgrade_holesky.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index dbc332722..04fa47731 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -131,7 +131,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): "Verify dual governance deployment", ( contracts.dual_governance_verifier.address, - contracts.dual_governance_verifier.verify.encode_input(tuple(dual_governance_contracts.values()), False), + contracts.dual_governance_verifier.verify.encode_input(tuple(dual_governance_contracts.values()), True), ), ), ( From 8c0dbc0919c5677510f8e282379f8cbb130e8da1 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 16:14:13 +0300 Subject: [PATCH 09/29] feat: add time constraints interface --- configs/config_holesky.py | 1 + configs/config_mainnet.py | 1 + interfaces/TimeConstraints.json | 69 +++++++++++++++++++++++++++++++++ utils/config.py | 4 ++ 4 files changed, 75 insertions(+) create mode 100644 interfaces/TimeConstraints.json diff --git a/configs/config_holesky.py b/configs/config_holesky.py index f858f8c81..65abffcd9 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -52,6 +52,7 @@ DUAL_GOVERNANCE = "0x9F14118Fc548658660a40B351C782a22e9937b42" DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04" DUAL_GOVERNANCE_VERIFIER = "0xd67DF125fDC3360DeCB880804D1FA3Ae3fC6FFF1" +TIME_CONSTRAINTS = "0x3db5ABA48123bb8789f6f09ec714e7082Bc26747" # EasyTracks EASYTRACK = "0x1763b9ED3586B08AE796c7787811a2E1bc16163a" diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index ae9927095..29fdeeccf 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -58,6 +58,7 @@ DUAL_GOVERNANCE = "0x0000000000000000000000000000000000000000" DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x0000000000000000000000000000000000000000" DUAL_GOVERNANCE_VERIFIER = "0x0000000000000000000000000000000000000000" +TIME_CONSTRAINTS = "0x0000000000000000000000000000000000000000" # EasyTracks EASYTRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" diff --git a/interfaces/TimeConstraints.json b/interfaces/TimeConstraints.json new file mode 100644 index 000000000..f46fb65db --- /dev/null +++ b/interfaces/TimeConstraints.json @@ -0,0 +1,69 @@ +[ + { + "inputs": [ + { + "internalType": "Duration", + "name": "currentDayTime", + "type": "uint32" + }, + { "internalType": "Duration", "name": "startDayTime", "type": "uint32" }, + { "internalType": "Duration", "name": "endDayTime", "type": "uint32" } + ], + "name": "DayTimeOutOfRange", + "type": "error" + }, + { "inputs": [], "name": "DayTimeOverflow", "type": "error" }, + { "inputs": [], "name": "DurationOverflow", "type": "error" }, + { + "inputs": [ + { "internalType": "Duration", "name": "startDayTime", "type": "uint32" }, + { "internalType": "Duration", "name": "endDayTime", "type": "uint32" } + ], + "name": "InvalidDayTimeRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Timestamp", + "name": "requiredTimestamp", + "type": "uint40" + } + ], + "name": "TimestampNotReached", + "type": "error" + }, + { + "inputs": [], + "name": "DAY_DURATION", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "Timestamp", "name": "timestamp", "type": "uint40" } + ], + "name": "checkExecuteAfterTimestamp", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "Duration", "name": "startDayTime", "type": "uint32" }, + { "internalType": "Duration", "name": "endDayTime", "type": "uint32" } + ], + "name": "checkExecuteWithinDayTime", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentDayTime", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/utils/config.py b/utils/config.py index 3992b8ef7..d50190630 100644 --- a/utils/config.py +++ b/utils/config.py @@ -407,6 +407,10 @@ def dual_governance_admin_executor(self) -> interface.DualGovernanceAdminExecuto def dual_governance_verifier(self) -> interface.DualGovernanceVerifier: return interface.DualGovernanceVerifier(DUAL_GOVERNANCE_VERIFIER) + @property + def time_constraints(self) -> interface.TimeConstraints: + return interface.TimeConstraints(TIME_CONSTRAINTS) + def __getattr__(name: str) -> Any: if name == "contracts": return ContractsLazyLoader() From bc2d2995228f6927f555252f37128a18af7f3dc1 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 16:14:42 +0300 Subject: [PATCH 10/29] feat: add emergency protected timelock interface --- interfaces/EmergencyProtectedTimelock.json | 836 +++++++++++++++++++++ 1 file changed, 836 insertions(+) create mode 100644 interfaces/EmergencyProtectedTimelock.json diff --git a/interfaces/EmergencyProtectedTimelock.json b/interfaces/EmergencyProtectedTimelock.json new file mode 100644 index 000000000..9d294c377 --- /dev/null +++ b/interfaces/EmergencyProtectedTimelock.json @@ -0,0 +1,836 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "Duration", + "name": "minExecutionDelay", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxAfterSubmitDelay", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxAfterScheduleDelay", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxEmergencyModeDuration", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "maxEmergencyProtectionDuration", + "type": "uint32" + } + ], + "internalType": "struct EmergencyProtectedTimelock.SanityCheckParams", + "name": "sanityCheckParams", + "type": "tuple" + }, + { "internalType": "address", "name": "adminExecutor", "type": "address" }, + { + "internalType": "Duration", + "name": "afterSubmitDelay", + "type": "uint32" + }, + { + "internalType": "Duration", + "name": "afterScheduleDelay", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "AfterScheduleDelayNotPassed", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "AfterSubmitDelayNotPassed", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotAdminExecutor", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotEmergencyActivationCommittee", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotEmergencyExecutionCommittee", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "caller", "type": "address" } + ], + "name": "CallerIsNotGovernance", + "type": "error" + }, + { "inputs": [], "name": "DurationOverflow", "type": "error" }, + { + "inputs": [ + { "internalType": "Timestamp", "name": "protectedTill", "type": "uint40" } + ], + "name": "EmergencyProtectionExpired", + "type": "error" + }, + { "inputs": [], "name": "EmptyCalls", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "adminExecutor", "type": "address" } + ], + "name": "InvalidAdminExecutor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "afterScheduleDelay", + "type": "uint32" + } + ], + "name": "InvalidAfterScheduleDelay", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "afterSubmitDelay", + "type": "uint32" + } + ], + "name": "InvalidAfterSubmitDelay", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "committee", "type": "address" } + ], + "name": "InvalidEmergencyActivationCommittee", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "committee", "type": "address" } + ], + "name": "InvalidEmergencyExecutionCommittee", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "governance", "type": "address" } + ], + "name": "InvalidEmergencyGovernance", + "type": "error" + }, + { + "inputs": [ + { "internalType": "Duration", "name": "value", "type": "uint32" } + ], + "name": "InvalidEmergencyModeDuration", + "type": "error" + }, + { + "inputs": [ + { "internalType": "Timestamp", "name": "value", "type": "uint40" } + ], + "name": "InvalidEmergencyProtectionEndDate", + "type": "error" + }, + { + "inputs": [ + { "internalType": "Duration", "name": "executionDelay", "type": "uint32" } + ], + "name": "InvalidExecutionDelay", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "governance", "type": "address" } + ], + "name": "InvalidGovernance", + "type": "error" + }, + { "inputs": [], "name": "TimestampOverflow", "type": "error" }, + { + "inputs": [{ "internalType": "bool", "name": "state", "type": "bool" }], + "name": "UnexpectedEmergencyModeState", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "enum Status", "name": "status", "type": "uint8" } + ], + "name": "UnexpectedProposalStatus", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newAdminExecutor", + "type": "address" + } + ], + "name": "AdminExecutorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "Duration", + "name": "newAfterScheduleDelay", + "type": "uint32" + } + ], + "name": "AfterScheduleDelaySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "Duration", + "name": "newAfterSubmitDelay", + "type": "uint32" + } + ], + "name": "AfterSubmitDelaySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newActivationCommittee", + "type": "address" + } + ], + "name": "EmergencyActivationCommitteeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newExecutionCommittee", + "type": "address" + } + ], + "name": "EmergencyExecutionCommitteeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newEmergencyGovernance", + "type": "address" + } + ], + "name": "EmergencyGovernanceSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EmergencyModeActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EmergencyModeDeactivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "Duration", + "name": "newEmergencyModeDuration", + "type": "uint32" + } + ], + "name": "EmergencyModeDurationSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "Timestamp", + "name": "newEmergencyProtectionEndDate", + "type": "uint40" + } + ], + "name": "EmergencyProtectionEndDateSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "GovernanceSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "ProposalScheduled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "executor", + "type": "address" + }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint96", "name": "value", "type": "uint96" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "indexed": false, + "internalType": "struct ExternalCall[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "ProposalSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalsCancelledTill", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_AFTER_SCHEDULE_DELAY", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_AFTER_SUBMIT_DELAY", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_EMERGENCY_MODE_DURATION", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_EMERGENCY_PROTECTION_DURATION", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_EXECUTION_DELAY", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "activateEmergencyMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "canExecute", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "canSchedule", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cancelAllNonExecutedProposals", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deactivateEmergencyMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "emergencyExecute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "emergencyReset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "execute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAdminExecutor", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAfterScheduleDelay", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAfterSubmitDelay", + "outputs": [{ "internalType": "Duration", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEmergencyActivationCommittee", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEmergencyExecutionCommittee", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEmergencyGovernance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEmergencyProtectionDetails", + "outputs": [ + { + "components": [ + { + "internalType": "Duration", + "name": "emergencyModeDuration", + "type": "uint32" + }, + { + "internalType": "Timestamp", + "name": "emergencyModeEndsAfter", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "emergencyProtectionEndsAfter", + "type": "uint40" + } + ], + "internalType": "struct IEmergencyProtectedTimelock.EmergencyProtectionDetails", + "name": "details", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getGovernance", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "getProposal", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "address", "name": "executor", "type": "address" }, + { + "internalType": "Timestamp", + "name": "submittedAt", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "scheduledAt", + "type": "uint40" + }, + { "internalType": "enum Status", "name": "status", "type": "uint8" } + ], + "internalType": "struct ITimelock.ProposalDetails", + "name": "proposalDetails", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint96", "name": "value", "type": "uint96" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "internalType": "struct ExternalCall[]", + "name": "calls", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "getProposalCalls", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint96", "name": "value", "type": "uint96" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "internalType": "struct ExternalCall[]", + "name": "calls", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "getProposalDetails", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "address", "name": "executor", "type": "address" }, + { + "internalType": "Timestamp", + "name": "submittedAt", + "type": "uint40" + }, + { + "internalType": "Timestamp", + "name": "scheduledAt", + "type": "uint40" + }, + { "internalType": "enum Status", "name": "status", "type": "uint8" } + ], + "internalType": "struct ITimelock.ProposalDetails", + "name": "proposalDetails", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalsCount", + "outputs": [ + { "internalType": "uint256", "name": "count", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isEmergencyModeActive", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isEmergencyProtectionEnabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" } + ], + "name": "schedule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdminExecutor", + "type": "address" + } + ], + "name": "setAdminExecutor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "newAfterScheduleDelay", + "type": "uint32" + } + ], + "name": "setAfterScheduleDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "newAfterSubmitDelay", + "type": "uint32" + } + ], + "name": "setAfterSubmitDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newEmergencyGovernance", + "type": "address" + } + ], + "name": "setEmergencyGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Duration", + "name": "newEmergencyModeDuration", + "type": "uint32" + } + ], + "name": "setEmergencyModeDuration", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newEmergencyActivationCommittee", + "type": "address" + } + ], + "name": "setEmergencyProtectionActivationCommittee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Timestamp", + "name": "newEmergencyProtectionEndDate", + "type": "uint40" + } + ], + "name": "setEmergencyProtectionEndDate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newEmergencyExecutionCommittee", + "type": "address" + } + ], + "name": "setEmergencyProtectionExecutionCommittee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newGovernance", "type": "address" } + ], + "name": "setGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "executor", "type": "address" }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint96", "name": "value", "type": "uint96" }, + { "internalType": "bytes", "name": "payload", "type": "bytes" } + ], + "internalType": "struct ExternalCall[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "submit", + "outputs": [ + { "internalType": "uint256", "name": "newProposalId", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "executor", "type": "address" }, + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "transferExecutorOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] From dc6f5d463b77198689b59d33f4f22568ac844216 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 16:14:59 +0300 Subject: [PATCH 11/29] fix: update foo contract interface --- interfaces/Foo.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/interfaces/Foo.json b/interfaces/Foo.json index cf096dafb..3aa189434 100644 --- a/interfaces/Foo.json +++ b/interfaces/Foo.json @@ -1,4 +1,17 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "BarCall", + "type": "event" + }, { "inputs": [], "name": "bar", From 854790dd18be29ac5071ef9dec6df270fb6c0d7b Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 16:16:08 +0300 Subject: [PATCH 12/29] feat: add option to agent_forward through dual governance --- utils/agent.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/utils/agent.py b/utils/agent.py index 4538e4b81..f89ec9262 100644 --- a/utils/agent.py +++ b/utils/agent.py @@ -1,10 +1,11 @@ from utils.config import contracts -from utils.config import AGENT +from utils.config import AGENT, DUAL_GOVERNANCE from utils.evm_script import ( encode_call_script, ) from typing import ( Tuple, + Optional, Sequence, ) @@ -18,3 +19,13 @@ def agent_forward(call_script: Sequence[Tuple[str, str]]) -> Tuple[str, str]: def agent_execute(target: str, value: str, data: str) -> Tuple[str, str]: agent = contracts.agent return (AGENT, agent.execute.encode_input(target, value, data)) + + +def dual_governance_agent_forward( + call_script: Sequence[Tuple[str, str]], + description: Optional[str] = "", +) -> Tuple[str, str]: + dual_governance = contracts.dual_governance + (agent_address, agent_calldata) = agent_forward(call_script) + + return (DUAL_GOVERNANCE, dual_governance.submitProposal.encode_input([(agent_address, 0, agent_calldata)], description)) From f3514f8f87c27794806de66bb703dd39bbe55c8c Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 16:16:21 +0300 Subject: [PATCH 13/29] feat: iterate upgrade scripts --- scripts/dual_governance_downgrade_holesky.py | 147 ++++++++++++++++++ scripts/dual_governance_upgrade_holesky.py | 24 +-- tests/dual_governance_upgrade_holesky_test.py | 28 ++-- 3 files changed, 181 insertions(+), 18 deletions(-) create mode 100644 scripts/dual_governance_downgrade_holesky.py diff --git a/scripts/dual_governance_downgrade_holesky.py b/scripts/dual_governance_downgrade_holesky.py new file mode 100644 index 000000000..4220dbfea --- /dev/null +++ b/scripts/dual_governance_downgrade_holesky.py @@ -0,0 +1,147 @@ +import time + +from typing import Dict + +from utils.agent import agent_forward, dual_governance_agent_forward +from utils.voting import bake_vote_items, confirm_vote_script, create_vote +from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description +from utils.config import ( + contracts, + get_deployer_account, + get_is_live, + get_priority_fee, +) +from utils.permissions import ( + encode_permission_set_manager, + encode_permission_create, + encode_permission_revoke, + encode_permission_grant, +) +from utils.mainnet_fork import pass_and_exec_dao_vote +from dual_governance_upgrade_holesky import dual_governance_contracts + + +try: + from brownie import interface +except ImportError: + print( + "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" + ) + +description = "Holesky dual governance downgrade dry-run" + +def start_vote(tx_params: Dict[str, str], silent: bool = False): + vote_desc_items, call_script_items = zip( + ( + "Change permission manager for Lido STAKING_CONTROL_ROLE.", + agent_forward( + [ + encode_permission_set_manager(contracts.lido, "STAKING_CONTROL_ROLE", contracts.voting) + ] + ) + ), + ( + "Revoke permission for STAKING_CONTROL_ROLE from Agent contract.", + encode_permission_revoke( + target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", revoke_from=contracts.agent + ), + ), + ( + "Grant permission for STAKING_CONTROL_ROLE to Voting contract.", + encode_permission_grant( + target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", grant_to=contracts.voting + ), + ), + ( + "Revoke WithdrawalQueue PAUSE_ROLE from Reseal Manager.", + ( + agent_forward( + [ + ( + contracts.withdrawal_queue.address, + contracts.withdrawal_queue.revokeRole.encode_input( + contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"] + ), + ) + ] + ) + ), + ), + ( + "Revoke WithdrawalQueue RESUME_ROLE from Reseal Manager.", + ( + agent_forward( + [ + ( + contracts.withdrawal_queue.address, + contracts.withdrawal_queue.revokeRole.encode_input( + contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"] + ), + ) + ] + ) + ), + ), + ( + "Grant AllowedTokensRegistry DEFAULT_ADMIN_ROLE to Agent.", + ( + contracts.allowed_tokens_registry.address, + contracts.allowed_tokens_registry.grantRole.encode_input( + contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent + ), + ), + ), + ( + "Revoke AllowedTokensRegistry DEFAULT_ADMIN_ROLE from Voting.", + ( + contracts.allowed_tokens_registry.address, + contracts.allowed_tokens_registry.revokeRole.encode_input( + contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting + ), + ), + ), + ( + "Revoke permission for RUN_SCRIPT_ROLE from DG Executor contract.", + encode_permission_revoke( + target_app=contracts.agent, + permission_name="RUN_SCRIPT_ROLE", + revoke_from=contracts.dual_governance_admin_executor, + ), + ), + ) + + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) + + if silent: + desc_ipfs = calculate_vote_ipfs_description(description) + else: + desc_ipfs = upload_vote_ipfs_description(description) + + return confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + +def main(): + tx_params: Dict[str, str] = {"from": get_deployer_account().address} + if get_is_live(): + tx_params["priority_fee"] = get_priority_fee() + + vote_id, _ = start_vote(tx_params=tx_params, silent=False) + + vote_id >= 0 and print(f"Vote created: {vote_id}.") + + time.sleep(5) # hack for waiting thread #2. + + +def start_and_execute_vote_on_fork(): + if get_is_live(): + raise Exception("This script is for local testing only.") + + tx_params = {"from": get_deployer_account()} + vote_id, _ = start_vote(tx_params=tx_params, silent=True) + + time.sleep(5) # hack for waiting thread #2. + + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id)) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 04fa47731..bc347ee33 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -2,7 +2,7 @@ from typing import Dict -from utils.agent import agent_forward +from utils.agent import agent_forward, dual_governance_agent_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description from utils.config import ( @@ -46,7 +46,7 @@ } def start_vote(tx_params: Dict[str, str], silent: bool = False): - foo_contract = interface.Foo("0x258C151254bB8C6673dEF05fB965D0dD8cB7eA89") + foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") vote_desc_items, call_script_items = zip( ( @@ -96,7 +96,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ), ), ( - "Grant DEFAULT_ADMIN_ROLE to Voting.", + "Grant AllowedTokensRegistry DEFAULT_ADMIN_ROLE to Voting.", ( agent_forward( [ @@ -111,7 +111,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ), ), ( - "Revoke DEFAULT_ADMIN_ROLE from Agent.", + "Revoke AllowedTokensRegistry DEFAULT_ADMIN_ROLE from Agent.", ( contracts.allowed_tokens_registry.address, contracts.allowed_tokens_registry.revokeRole.encode_input( @@ -120,10 +120,10 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ), ), ( - "Grant permission for EXECUTE_ROLE to DG Executor contract.", + "Grant permission for RUN_SCRIPT_ROLE to DG Executor contract.", encode_permission_grant( target_app=contracts.agent, - permission_name="EXECUTE_ROLE", + permission_name="RUN_SCRIPT_ROLE", grant_to=contracts.dual_governance_admin_executor, ), ), @@ -137,9 +137,15 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ( "Submit first dual governance proposal", ( - contracts.dual_governance.address, - contracts.dual_governance.submitProposal.encode_input( - [(foo_contract.address, 0, foo_contract.bar.encode_input())], "Test proposal" + dual_governance_agent_forward( + [( + foo_contract.address, + foo_contract.bar.encode_input() + ), + ( + contracts.time_constraints.address, + contracts.time_constraints.checkExecuteWithinDayTime.encode_input(28800, 72000) + )] ) ) ) diff --git a/tests/dual_governance_upgrade_holesky_test.py b/tests/dual_governance_upgrade_holesky_test.py index 4e0f58efd..db5579023 100644 --- a/tests/dual_governance_upgrade_holesky_test.py +++ b/tests/dual_governance_upgrade_holesky_test.py @@ -7,16 +7,18 @@ from utils.test.tx_tracing_helpers import * from brownie.network.transaction import TransactionReceipt from utils.config import contracts +from brownie.network.account import Account try: - from brownie import interface + from brownie import interface, chain except ImportError: print( "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" ) -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding): +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding, stranger: Account): dao_voting = contracts.voting + timelock = interface.EmergencyProtectedTimelock(contracts.dual_governance.TIMELOCK()) # Lido assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.voting @@ -27,8 +29,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) # Agent - assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.EXECUTE_ROLE()) - assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.EXECUTE_ROLE()) + assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) # Reseal manager assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) @@ -51,13 +53,21 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) # Agent - assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.EXECUTE_ROLE()) - assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.EXECUTE_ROLE()) + assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) # Reseal manager assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) - # # Validate vote events - # if not bypass_events_decoding: - # assert count_vote_items_by_events(tx, dao_voting) == 2, "Incorrect voting items count" + proposal_id = timelock.getProposalsCount() + + # while not contracts.dual_governance.canScheduleProposal(proposal_id): + chain.sleep(60 * 24) + + contracts.dual_governance.scheduleProposal(proposal_id, {"from": stranger}) + + # while not timelock.canExecute(proposal_id): + chain.sleep(60 * 24) + + timelock.execute(proposal_id, {"from": stranger}) From beb58786c39460f71f10470d83f844f12f24dd68 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 21 Jan 2025 19:02:10 +0300 Subject: [PATCH 14/29] feat: add initial role verifier --- interfaces/RolesVerifier.json | 45 ++++++++++++++++++++++ scripts/dual_governance_upgrade_holesky.py | 8 ++++ 2 files changed, 53 insertions(+) create mode 100644 interfaces/RolesVerifier.json diff --git a/interfaces/RolesVerifier.json b/interfaces/RolesVerifier.json new file mode 100644 index 000000000..5a0b85810 --- /dev/null +++ b/interfaces/RolesVerifier.json @@ -0,0 +1,45 @@ +[ + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "who", "type": "address" }, + { "internalType": "bytes32", "name": "what", "type": "bytes32" }, + { "internalType": "address", "name": "where", "type": "address" }, + { "internalType": "bool", "name": "granted", "type": "bool" } + ], + "internalType": "struct AragonRolesVerifier.RoleToVerify[]", + "name": "_rolesToVerify", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ACL_ADDRESS", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "rolesToVerify", + "outputs": [ + { "internalType": "address", "name": "who", "type": "address" }, + { "internalType": "bytes32", "name": "what", "type": "bytes32" }, + { "internalType": "address", "name": "where", "type": "address" }, + { "internalType": "bool", "name": "granted", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "verify", + "outputs": [], + "stateMutability": "view", + "type": "function" + } +] diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index bc347ee33..c28ebe818 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -47,6 +47,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") + roles_verifier = interface.RolesVerifier("0xe0144de0e89390dc469425f471527d9d6bc98b05") vote_desc_items, call_script_items = zip( ( @@ -134,6 +135,13 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): contracts.dual_governance_verifier.verify.encode_input(tuple(dual_governance_contracts.values()), True), ), ), + ( + "Verifiy transferred roles", + ( + roles_verifier.address, + roles_verifier.verify.encode_input() + ) + ), ( "Submit first dual governance proposal", ( From 0d139ebbb8cefe15957ec14a570a323026e8e005 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 28 Jan 2025 16:49:07 +0300 Subject: [PATCH 15/29] Revert "feat: add inteface for dg verifier" This reverts commit 6bc0b07b1d9d02741557e4d55956822faa2cdf26. --- interfaces/DualGovernanceVerifier.json | 611 ------------------------- 1 file changed, 611 deletions(-) delete mode 100644 interfaces/DualGovernanceVerifier.json diff --git a/interfaces/DualGovernanceVerifier.json b/interfaces/DualGovernanceVerifier.json deleted file mode 100644 index 4a6b56628..000000000 --- a/interfaces/DualGovernanceVerifier.json +++ /dev/null @@ -1,611 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "Duration", - "name": "MIN_EXECUTION_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "AFTER_SUBMIT_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_AFTER_SUBMIT_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "AFTER_SCHEDULE_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_AFTER_SCHEDULE_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "EMERGENCY_MODE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_EMERGENCY_MODE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "EMERGENCY_PROTECTION_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_EMERGENCY_PROTECTION_DURATION", - "type": "uint32" - }, - { - "internalType": "address", - "name": "EMERGENCY_ACTIVATION_COMMITTEE", - "type": "address" - }, - { - "internalType": "address", - "name": "EMERGENCY_EXECUTION_COMMITTEE", - "type": "address" - }, - { - "internalType": "address", - "name": "RESEAL_COMMITTEE", - "type": "address" - }, - { - "internalType": "uint256", - "name": "MIN_WITHDRAWALS_BATCH_SIZE", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "Duration", - "name": "activationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "minActivationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "maxActivationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "executionDelay", - "type": "uint32" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "influencers", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "nodeOperators", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "protocols", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - }, - { - "internalType": "address[]", - "name": "sealableWithdrawalBlockers", - "type": "address[]" - } - ], - "internalType": "struct TiebreakerDeployConfig", - "name": "tiebreakerConfig", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT", - "type": "uint256" - }, - { - "internalType": "PercentD16", - "name": "FIRST_SEAL_RAGE_QUIT_SUPPORT", - "type": "uint128" - }, - { - "internalType": "PercentD16", - "name": "SECOND_SEAL_RAGE_QUIT_SUPPORT", - "type": "uint128" - }, - { - "internalType": "Duration", - "name": "MIN_ASSETS_LOCK_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_MIN_ASSETS_LOCK_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MIN_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MAX_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MIN_ACTIVE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_DEACTIVATION_MAX_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_COOLDOWN_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_EXTENSION_PERIOD_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH", - "type": "uint32" - }, - { - "internalType": "address", - "name": "TEMPORARY_EMERGENCY_GOVERNANCE_PROPOSER", - "type": "address" - } - ], - "internalType": "struct DeployConfig", - "name": "config", - "type": "tuple" - }, - { - "components": [ - { "internalType": "uint256", "name": "chainId", "type": "uint256" }, - { - "internalType": "contract IStETH", - "name": "stETH", - "type": "address" - }, - { - "internalType": "contract IWstETH", - "name": "wstETH", - "type": "address" - }, - { - "internalType": "contract IWithdrawalQueue", - "name": "withdrawalQueue", - "type": "address" - }, - { "internalType": "address", "name": "voting", "type": "address" } - ], - "internalType": "struct LidoContracts", - "name": "lidoAddresses", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "DurationOverflow", "type": "error" }, - { "inputs": [], "name": "TimestampOverflow", "type": "error" }, - { "anonymous": false, "inputs": [], "name": "Verified", "type": "event" }, - { - "inputs": [], - "name": "getConfig", - "outputs": [ - { - "components": [ - { - "internalType": "Duration", - "name": "MIN_EXECUTION_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "AFTER_SUBMIT_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_AFTER_SUBMIT_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "AFTER_SCHEDULE_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_AFTER_SCHEDULE_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "EMERGENCY_MODE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_EMERGENCY_MODE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "EMERGENCY_PROTECTION_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_EMERGENCY_PROTECTION_DURATION", - "type": "uint32" - }, - { - "internalType": "address", - "name": "EMERGENCY_ACTIVATION_COMMITTEE", - "type": "address" - }, - { - "internalType": "address", - "name": "EMERGENCY_EXECUTION_COMMITTEE", - "type": "address" - }, - { - "internalType": "address", - "name": "RESEAL_COMMITTEE", - "type": "address" - }, - { - "internalType": "uint256", - "name": "MIN_WITHDRAWALS_BATCH_SIZE", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "Duration", - "name": "activationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "minActivationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "maxActivationTimeout", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "executionDelay", - "type": "uint32" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "influencers", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "nodeOperators", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address[]", - "name": "members", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - } - ], - "internalType": "struct TiebreakerSubCommitteeDeployConfig", - "name": "protocols", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "quorum", - "type": "uint256" - }, - { - "internalType": "address[]", - "name": "sealableWithdrawalBlockers", - "type": "address[]" - } - ], - "internalType": "struct TiebreakerDeployConfig", - "name": "tiebreakerConfig", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT", - "type": "uint256" - }, - { - "internalType": "PercentD16", - "name": "FIRST_SEAL_RAGE_QUIT_SUPPORT", - "type": "uint128" - }, - { - "internalType": "PercentD16", - "name": "SECOND_SEAL_RAGE_QUIT_SUPPORT", - "type": "uint128" - }, - { - "internalType": "Duration", - "name": "MIN_ASSETS_LOCK_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "MAX_MIN_ASSETS_LOCK_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MIN_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MAX_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_MIN_ACTIVE_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_SIGNALLING_DEACTIVATION_MAX_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "VETO_COOLDOWN_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_EXTENSION_PERIOD_DURATION", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_MIN_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_MAX_DELAY", - "type": "uint32" - }, - { - "internalType": "Duration", - "name": "RAGE_QUIT_ETH_WITHDRAWALS_DELAY_GROWTH", - "type": "uint32" - }, - { - "internalType": "address", - "name": "TEMPORARY_EMERGENCY_GOVERNANCE_PROPOSER", - "type": "address" - } - ], - "internalType": "struct DeployConfig", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getLidoAddresses", - "outputs": [ - { - "components": [ - { "internalType": "uint256", "name": "chainId", "type": "uint256" }, - { - "internalType": "contract IStETH", - "name": "stETH", - "type": "address" - }, - { - "internalType": "contract IWstETH", - "name": "wstETH", - "type": "address" - }, - { - "internalType": "contract IWithdrawalQueue", - "name": "withdrawalQueue", - "type": "address" - }, - { "internalType": "address", "name": "voting", "type": "address" } - ], - "internalType": "struct LidoContracts", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "contract Executor", - "name": "adminExecutor", - "type": "address" - }, - { - "internalType": "contract IEmergencyProtectedTimelock", - "name": "timelock", - "type": "address" - }, - { - "internalType": "contract TimelockedGovernance", - "name": "emergencyGovernance", - "type": "address" - }, - { - "internalType": "contract ResealManager", - "name": "resealManager", - "type": "address" - }, - { - "internalType": "contract DualGovernance", - "name": "dualGovernance", - "type": "address" - }, - { - "internalType": "contract TiebreakerCoreCommittee", - "name": "tiebreakerCoreCommittee", - "type": "address" - }, - { - "internalType": "contract TiebreakerSubCommittee", - "name": "tiebreakerSubCommitteeInfluencers", - "type": "address" - }, - { - "internalType": "contract TiebreakerSubCommittee", - "name": "tiebreakerSubCommitteeNodeOperators", - "type": "address" - }, - { - "internalType": "contract TiebreakerSubCommittee", - "name": "tiebreakerSubCommitteeProtocols", - "type": "address" - }, - { - "internalType": "contract TimelockedGovernance", - "name": "temporaryEmergencyGovernance", - "type": "address" - } - ], - "internalType": "struct DeployedContracts", - "name": "dgContracts", - "type": "tuple" - }, - { "internalType": "bool", "name": "onchainVotingCheck", "type": "bool" } - ], - "name": "verify", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] From 03eb26f60932497990f9a510527bbccfcd0b2fdb Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 28 Jan 2025 16:53:15 +0300 Subject: [PATCH 16/29] fix: remove dual governance verifier --- configs/config_holesky.py | 1 - configs/config_mainnet.py | 1 - utils/config.py | 6 +----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/configs/config_holesky.py b/configs/config_holesky.py index 65abffcd9..fb7ee6ce1 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -51,7 +51,6 @@ # Dual Governance DUAL_GOVERNANCE = "0x9F14118Fc548658660a40B351C782a22e9937b42" DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04" -DUAL_GOVERNANCE_VERIFIER = "0xd67DF125fDC3360DeCB880804D1FA3Ae3fC6FFF1" TIME_CONSTRAINTS = "0x3db5ABA48123bb8789f6f09ec714e7082Bc26747" # EasyTracks diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 29fdeeccf..9cead77e4 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -57,7 +57,6 @@ # Dual Governance DUAL_GOVERNANCE = "0x0000000000000000000000000000000000000000" DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x0000000000000000000000000000000000000000" -DUAL_GOVERNANCE_VERIFIER = "0x0000000000000000000000000000000000000000" TIME_CONSTRAINTS = "0x0000000000000000000000000000000000000000" # EasyTracks diff --git a/utils/config.py b/utils/config.py index d50190630..aa7007d0a 100644 --- a/utils/config.py +++ b/utils/config.py @@ -390,7 +390,7 @@ def trp_escrow_factory(self) -> interface.VestingEscrowFactory: @property def token_rate_notifier(self) -> interface.TokenRateNotifier: return interface.TokenRateNotifier(L1_TOKEN_RATE_NOTIFIER) - + @property def allowed_tokens_registry(self) -> interface.AllowedTokensRegistry: return interface.AllowedTokensRegistry(EASYTRACK_ALLOWED_TOKENS_REGISTRY) @@ -403,10 +403,6 @@ def dual_governance(self) -> interface.DualGovernance: def dual_governance_admin_executor(self) -> interface.DualGovernanceAdminExecutor: return interface.DualGovernanceAdminExecutor(DUAL_GOVERNANCE_ADMIN_EXECUTOR) - @property - def dual_governance_verifier(self) -> interface.DualGovernanceVerifier: - return interface.DualGovernanceVerifier(DUAL_GOVERNANCE_VERIFIER) - @property def time_constraints(self) -> interface.TimeConstraints: return interface.TimeConstraints(TIME_CONSTRAINTS) From df047f817d788fd8ca033d087b18b182a12d1e39 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 28 Jan 2025 16:53:28 +0300 Subject: [PATCH 17/29] fix: remove dg verifier from voting script --- scripts/dual_governance_upgrade_holesky.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index c28ebe818..2e6bd8537 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -128,13 +128,6 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): grant_to=contracts.dual_governance_admin_executor, ), ), - ( - "Verify dual governance deployment", - ( - contracts.dual_governance_verifier.address, - contracts.dual_governance_verifier.verify.encode_input(tuple(dual_governance_contracts.values()), True), - ), - ), ( "Verifiy transferred roles", ( From b8c003050dcb62881fe60bbce51de577546fd149 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 28 Jan 2025 20:47:36 +0300 Subject: [PATCH 18/29] fix: script iteration --- interfaces/RolesValidator.json | 132 +++++++++++++++++++ interfaces/RolesVerifier.json | 45 ------- scripts/dual_governance_downgrade_holesky.py | 2 +- scripts/dual_governance_upgrade_holesky.py | 23 ++-- 4 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 interfaces/RolesValidator.json delete mode 100644 interfaces/RolesVerifier.json diff --git a/interfaces/RolesValidator.json b/interfaces/RolesValidator.json new file mode 100644 index 000000000..f963945f1 --- /dev/null +++ b/interfaces/RolesValidator.json @@ -0,0 +1,132 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "entity", "type": "address" }, + { "internalType": "string", "name": "roleName", "type": "string" }, + { "internalType": "address", "name": "app", "type": "address" } + ], + "name": "AragonPermissionGranted", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "entity", "type": "address" }, + { "internalType": "string", "name": "roleName", "type": "string" }, + { + "internalType": "address", + "name": "expectedManager", + "type": "address" + }, + { "internalType": "address", "name": "actualManager", "type": "address" } + ], + "name": "AragonPermissionInvalidManager", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "entity", "type": "address" }, + { "internalType": "string", "name": "roleName", "type": "string" }, + { "internalType": "address", "name": "app", "type": "address" } + ], + "name": "AragonPermissionNotGranted", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "entity", "type": "address" }, + { "internalType": "string", "name": "roleName", "type": "string" }, + { "internalType": "address", "name": "app", "type": "address" } + ], + "name": "OZRoleNotGranted", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "entity", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "roleName", + "type": "string" + } + ], + "name": "RoleValidated", + "type": "event" + }, + { + "inputs": [], + "name": "ACL", + "outputs": [ + { "internalType": "contract IACL", "name": "", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACL_ADDRESS", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "AGENT", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EASYTRACK_ALLOWED_TOKENS_REGISTRY", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VOTING", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWAL_QUEUE", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dgAdminExecutor", + "type": "address" + }, + { + "internalType": "address", + "name": "dgResealManager", + "type": "address" + } + ], + "name": "validate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/RolesVerifier.json b/interfaces/RolesVerifier.json deleted file mode 100644 index 5a0b85810..000000000 --- a/interfaces/RolesVerifier.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { "internalType": "address", "name": "who", "type": "address" }, - { "internalType": "bytes32", "name": "what", "type": "bytes32" }, - { "internalType": "address", "name": "where", "type": "address" }, - { "internalType": "bool", "name": "granted", "type": "bool" } - ], - "internalType": "struct AragonRolesVerifier.RoleToVerify[]", - "name": "_rolesToVerify", - "type": "tuple[]" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ACL_ADDRESS", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "name": "rolesToVerify", - "outputs": [ - { "internalType": "address", "name": "who", "type": "address" }, - { "internalType": "bytes32", "name": "what", "type": "bytes32" }, - { "internalType": "address", "name": "where", "type": "address" }, - { "internalType": "bool", "name": "granted", "type": "bool" } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "verify", - "outputs": [], - "stateMutability": "view", - "type": "function" - } -] diff --git a/scripts/dual_governance_downgrade_holesky.py b/scripts/dual_governance_downgrade_holesky.py index 4220dbfea..114962163 100644 --- a/scripts/dual_governance_downgrade_holesky.py +++ b/scripts/dual_governance_downgrade_holesky.py @@ -18,7 +18,7 @@ encode_permission_grant, ) from utils.mainnet_fork import pass_and_exec_dao_vote -from dual_governance_upgrade_holesky import dual_governance_contracts +from scripts.dual_governance_upgrade_holesky import dual_governance_contracts try: diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 2e6bd8537..d75177795 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -32,22 +32,13 @@ dual_governance_contracts = { "adminExecutor": "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04", - "timelock": "0x388AB7b65605e21a75Bb50E24a1eA43DD0091fa5", - "emergencyGovernance": "0xd67d96C6C4DF1eF12c2fb4C908a9484333cEfE60", - "resealManager": "0x632c29848A379a7B30Ee6461ea5e7e1e92d264d0", "dualGovernance": "0x9F14118Fc548658660a40B351C782a22e9937b42", - "tiebreakerCoreCommittee": "0x6093B9b951C72498EE799639D74dC701Ead3f07B", - "tiebreakerSubCommitteeInfluencers": "0x8F4b730099BFcA35fa4bbFD84f790eD34CAa246f", - "tiebreakerSubCommitteeNodeOperators": "0xBB259276147Af98c0e9186e783D4dbC26e82652F", - "tiebreakerSubCommitteeProtocols": "0x485349eBc3241e0bE8eDf7149C535c0b42Fa9504", - "temporaryEmergencyGovernance": "0xc7467FeFF717C18db08BAEF252f11A84F48e8fF7", - # "EMERGENCY_ACTIVATION_COMMITTEE": "0x526d46eCa1d7969924e981ecDbcAa74e9f0EE566", - # "EMERGENCY_EXECUTION_COMMITTEE": "0x526d46eCa1d7969924e981ecDbcAa74e9f0EE566", + "resealManager": "0x632c29848A379a7B30Ee6461ea5e7e1e92d264d0", } def start_vote(tx_params: Dict[str, str], silent: bool = False): foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") - roles_verifier = interface.RolesVerifier("0xe0144de0e89390dc469425f471527d9d6bc98b05") + roles_validator = interface.RolesValidator("0x0F8826a574BCFDC4997939076f6D82877971feB3") vote_desc_items, call_script_items = zip( ( @@ -125,14 +116,16 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): encode_permission_grant( target_app=contracts.agent, permission_name="RUN_SCRIPT_ROLE", - grant_to=contracts.dual_governance_admin_executor, + grant_to=dual_governance_contracts["adminExecutor"], ), ), ( - "Verifiy transferred roles", + "Validate transferred roles", ( - roles_verifier.address, - roles_verifier.verify.encode_input() + roles_validator.address, + roles_validator.validate.encode_input( + dual_governance_contracts['adminExecutor'], dual_governance_contracts['resealManager'] + ), ) ), ( From cf3ddee672d1fd59ce20492aee0fbe2de61421f6 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Wed, 29 Jan 2025 13:28:08 +0300 Subject: [PATCH 19/29] feat: update addresses --- configs/config_holesky.py | 4 ++-- scripts/dual_governance_upgrade_holesky.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/configs/config_holesky.py b/configs/config_holesky.py index fb7ee6ce1..ed7527b98 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -49,8 +49,8 @@ WITHDRAWAL_VAULT = "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" # Dual Governance -DUAL_GOVERNANCE = "0x9F14118Fc548658660a40B351C782a22e9937b42" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04" +DUAL_GOVERNANCE = "0xb291a7f092D5cCE0A3C93eA21Bda3431129dB202" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0xD5EE9991f44b36E186A658dc2A0357EcCf11b69B" TIME_CONSTRAINTS = "0x3db5ABA48123bb8789f6f09ec714e7082Bc26747" # EasyTracks diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index d75177795..0411298b7 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -31,9 +31,9 @@ description = "Holesky dual governance upgrade dry-run" dual_governance_contracts = { - "adminExecutor": "0x936C1dC7d5fAD05E5aD9aBc48b4ab09B88850f04", - "dualGovernance": "0x9F14118Fc548658660a40B351C782a22e9937b42", - "resealManager": "0x632c29848A379a7B30Ee6461ea5e7e1e92d264d0", + "dualGovernance": "0xb291a7f092D5cCE0A3C93eA21Bda3431129dB202", + "adminExecutor": "0xD5EE9991f44b36E186A658dc2A0357EcCf11b69B", + "resealManager": "0xc2764655e3fe0bd2D3C710D74Fa5a89162099FD8", } def start_vote(tx_params: Dict[str, str], silent: bool = False): From e960309f9847fbc7bcab324495a9c682a0862869 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Mon, 3 Feb 2025 16:58:17 +0300 Subject: [PATCH 20/29] feat: add test for downgrade --- .../dual_governance_downgrade_holesky_test.py | 56 +++++++++++++++++++ tests/dual_governance_upgrade_holesky_test.py | 4 -- 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 tests/dual_governance_downgrade_holesky_test.py diff --git a/tests/dual_governance_downgrade_holesky_test.py b/tests/dual_governance_downgrade_holesky_test.py new file mode 100644 index 000000000..897a61705 --- /dev/null +++ b/tests/dual_governance_downgrade_holesky_test.py @@ -0,0 +1,56 @@ +from scripts.dual_governance_downgrade_holesky import start_vote, dual_governance_contracts +from utils.config import contracts +from utils.test.tx_tracing_helpers import * +from brownie.network.transaction import TransactionReceipt +from utils.config import contracts +from brownie.network.account import Account + +try: + from brownie import interface, chain +except ImportError: + print( + "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" + ) + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding, stranger: Account): + dao_voting = contracts.voting + + # Lido + assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.agent + assert not contracts.acl.hasPermission(contracts.voting, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + assert contracts.acl.hasPermission(contracts.agent, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + + # Reseal manager + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + + # Allowed tokens registry + assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) + assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) + + # Agent + assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + + # START VOTE + vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] + + tx: TransactionReceipt = helpers.execute_vote( + vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, skip_time=3 * 60 * 60 * 24 + ) + + # Lido + assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.voting + assert contracts.acl.hasPermission(contracts.voting, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) + + # Reseal manager + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + + # Allowed tokens registry + assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) + assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) + + # Agent + assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) diff --git a/tests/dual_governance_upgrade_holesky_test.py b/tests/dual_governance_upgrade_holesky_test.py index db5579023..eefa0b120 100644 --- a/tests/dual_governance_upgrade_holesky_test.py +++ b/tests/dual_governance_upgrade_holesky_test.py @@ -1,7 +1,3 @@ -""" -Tests for voting 23/07/2024. -""" - from scripts.dual_governance_upgrade_holesky import start_vote, dual_governance_contracts from utils.config import contracts from utils.test.tx_tracing_helpers import * From a05cf66095508dbb2582b61ff225b8cc7b53c234 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 11 Feb 2025 14:11:40 +0300 Subject: [PATCH 21/29] feat: add script for calldata printing --- scripts/dual_governance_downgrade_holesky.py | 11 +---- scripts/dual_governance_upgrade_holesky.py | 42 +++++++++++++------- utils/voting.py | 1 + 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/scripts/dual_governance_downgrade_holesky.py b/scripts/dual_governance_downgrade_holesky.py index 114962163..5b1acb737 100644 --- a/scripts/dual_governance_downgrade_holesky.py +++ b/scripts/dual_governance_downgrade_holesky.py @@ -2,7 +2,7 @@ from typing import Dict -from utils.agent import agent_forward, dual_governance_agent_forward +from utils.agent import agent_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description from utils.config import ( @@ -13,21 +13,12 @@ ) from utils.permissions import ( encode_permission_set_manager, - encode_permission_create, encode_permission_revoke, encode_permission_grant, ) from utils.mainnet_fork import pass_and_exec_dao_vote from scripts.dual_governance_upgrade_holesky import dual_governance_contracts - -try: - from brownie import interface -except ImportError: - print( - "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" - ) - description = "Holesky dual governance downgrade dry-run" def start_vote(tx_params: Dict[str, str], silent: bool = False): diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 0411298b7..3bcfe0598 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -1,7 +1,7 @@ import time from typing import Dict - +from brownie import interface from utils.agent import agent_forward, dual_governance_agent_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description @@ -13,20 +13,11 @@ ) from utils.permissions import ( encode_permission_set_manager, - encode_permission_create, encode_permission_revoke, encode_permission_grant, ) from utils.mainnet_fork import pass_and_exec_dao_vote - - -try: - from brownie import interface -except ImportError: - print( - "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" - ) - +from utils.evm_script import encode_call_script description = "Holesky dual governance upgrade dry-run" @@ -36,11 +27,11 @@ "resealManager": "0xc2764655e3fe0bd2D3C710D74Fa5a89162099FD8", } -def start_vote(tx_params: Dict[str, str], silent: bool = False): +def get_vote_items(): foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") roles_validator = interface.RolesValidator("0x0F8826a574BCFDC4997939076f6D82877971feB3") - - vote_desc_items, call_script_items = zip( + + return zip( ( "Revoke permission for STAKING_CONTROL_ROLE from Voting contract.", encode_permission_revoke( @@ -145,6 +136,9 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ) ) +def start_vote(tx_params: Dict[str, str], silent: bool = False): + + vote_desc_items, call_script_items = get_vote_items() vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) if silent: @@ -180,3 +174,23 @@ def start_and_execute_vote_on_fork(): print(f"Vote created: {vote_id}.") pass_and_exec_dao_vote(int(vote_id)) + + +def get_voting_calldata(): + vote_desc_items, call_script_items = get_vote_items() + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) + evm_script = encode_call_script(vote_items.values()) + + new_vote_script = encode_call_script( + [ + ( + contracts.voting.address, + contracts.voting.newVote.encode_input( + evm_script, + "description_placeholder" + ), + ) + ] + ) + + print(new_vote_script) \ No newline at end of file diff --git a/utils/voting.py b/utils/voting.py index 0015e85c7..607cc0750 100644 --- a/utils/voting.py +++ b/utils/voting.py @@ -67,6 +67,7 @@ def create_vote( ) ] ) + print(new_vote_script) tx = token_manager.forward(new_vote_script, tx_params) if tx.revert_msg is not None: print(tx.traceback) From a7b4e7d6ffd46bcd05fb553a20b232be9a0bae7c Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 11 Feb 2025 14:42:05 +0300 Subject: [PATCH 22/29] fix: remove print --- utils/voting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/voting.py b/utils/voting.py index 607cc0750..0015e85c7 100644 --- a/utils/voting.py +++ b/utils/voting.py @@ -67,7 +67,6 @@ def create_vote( ) ] ) - print(new_vote_script) tx = token_manager.forward(new_vote_script, tx_params) if tx.revert_msg is not None: print(tx.traceback) From 117a4a842e507752d6488e7fca1b08917f99dd7d Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Tue, 11 Feb 2025 14:43:20 +0300 Subject: [PATCH 23/29] feat: update gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ac7798aa4..b307cb271 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,8 @@ yarn-error.log # local env .envrc + +# Hardhat +cache/ +hardhat.config.js +package-lock.json \ No newline at end of file From c5761b62c089c2566a5d52651cb28d9f4baf3d10 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Wed, 12 Feb 2025 15:23:37 +0300 Subject: [PATCH 24/29] test: update get_voting_calldata signature --- scripts/dual_governance_upgrade_holesky.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 3bcfe0598..12d34ddc5 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -187,7 +187,9 @@ def get_voting_calldata(): contracts.voting.address, contracts.voting.newVote.encode_input( evm_script, - "description_placeholder" + description, + False, + False ), ) ] From c421a68f2a4e2b5f5afd04ce7f15e45c8ce08fd4 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Thu, 13 Feb 2025 17:41:58 +0400 Subject: [PATCH 25/29] Use brownie's Contract API to decode EVM script calls --- README.md | 3 +- utils/evm_script.py | 82 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 133f4a561..7e02c731a 100644 --- a/README.md +++ b/README.md @@ -169,8 +169,7 @@ To start a new vote please provide the `DEPLOYER` brownie account name (wallet): export DEPLOYER= ``` -To run tests with a contract name resolution guided by the Etherscan you should -provide the etherscan API token: +To run scripts that require decoding of EVM scripts and tests with contract name resolution via Etherscan you should provide the etherscan API token: ```bash export ETHERSCAN_TOKEN= diff --git a/utils/evm_script.py b/utils/evm_script.py index 3267d1ca3..3421b0e43 100644 --- a/utils/evm_script.py +++ b/utils/evm_script.py @@ -2,15 +2,16 @@ import os from collections import defaultdict from functools import lru_cache -from typing import List, Union, Optional, Callable +from typing import List, Union, Optional, Callable, Any import eth_abi +from brownie import Contract from brownie.utils import color from eth_typing.evm import HexAddress -from eth_utils import keccak -from hexbytes import HexBytes from web3 import Web3 +# NOTE: The decode_function_call() method is currently unused; it is retained for fallback to the previous decoder version +# (refer to the NOTEs in the decode_evm_script method). from avotes_parser.core import parse_script, EncodedCall, Call, FuncInput, decode_function_call from avotes_parser.core.ABI import get_cached_combined @@ -22,7 +23,7 @@ ) EMPTY_CALLSCRIPT = "0x00000001" -ETHERSCAN_API_KEY = os.getenv("ETHERSCAN_API_KEY", "TGXU5WGVTVYRDDV2MY71R5JYB7147M13FC") +ETHERSCAN_TOKEN = os.getenv("ETHERSCAN_TOKEN", "TGXU5WGVTVYRDDV2MY71R5JYB7147M13FC") def create_executor_id(id) -> str: @@ -78,13 +79,18 @@ def decode_evm_script( logging.basicConfig(level=logging.INFO) return [repr(err)] - abi_storage = get_abi_cache(ETHERSCAN_API_KEY, specific_net) + # NOTE: The line below is not used in the current version; it is retained for fallback to the previous decoder version (see NOTE below). + abi_storage = get_abi_cache(ETHERSCAN_TOKEN, specific_net) calls = [] called_contracts = defaultdict(lambda: defaultdict(dict)) for ind, call in enumerate(parsed.calls): try: - call_info = decode_function_call(call.address, call.method_id, call.encoded_call_data, abi_storage) + call_info = decode_encoded_call(call) + + # NOTE: If the decode_encoded_call(call) method fails, uncomment the line below to fall back to the previous version: + # + # call_info = decode_function_call(call.address, call.method_id, call.encoded_call_data, abi_storage) if call_info is not None: for inp in filter(is_encoded_script, call_info.inputs): @@ -135,10 +141,68 @@ def calls_info_pretty_print(call: Union[str, Call, EncodedCall]) -> str: """Format printing for Call instance.""" return color.highlight(repr(call)) + def encode_error(error: str, values=None) -> str: - encoded_error = error.split('(')[0] + ': ' - args = '' + encoded_error = error.split("(")[0] + ": " + args = "" if values is not None: - args = ', '.join(str(x) for x in values) + args = ", ".join(str(x) for x in values) return f"{encoded_error}{args}" return encoded_error + + +def decode_encoded_call(encoded_call: EncodedCall) -> Optional[Call]: + """ + Decodes an encoded contract call using Brownie's Contract API. + + This function replaces AVotesParser.decode_function_call() and converts the provided + EncodedCall into a Call object or returns None if the decoding wasn't successfull. + Unsuccessfull deconding usually happens when the contract is not verified contract on etherscan + + Parameters: + encoded_call (EncodedCall): An object containing the target contract address, method id, + and encoded call data and encoded call data length. + + Returns: + Call: A Call object with decoded call details if successful, otherwise None if the method + call cannot be decoded. + """ + contract = Contract(encoded_call.address) + + # If the method selector is not found in the locally stored contract, try fetching the full ABI from Etherscan. + if encoded_call.method_id not in contract.selectors: + contract = Contract.from_explorer(encoded_call.address) + + # If the method is still not found, the contract may not be verified. + if encoded_call.method_id not in contract.selectors: + return None + + method_name = contract.selectors[encoded_call.method_id] + contract_method = getattr(contract, method_name) + + method_abi = contract_method.abi + + calldata_with_selector = encoded_call.method_id + encoded_call.encoded_call_data[2:] + decoded_calldata = contract_method.decode_input(calldata_with_selector) + + inputs = [get_func_input(method_abi["inputs"][idx], arg) for idx, arg in enumerate(decoded_calldata)] + + properties = { + "constant": "unknown", # Typically False even for pure methods, but not guaranteed. + "payable": method_abi["stateMutability"] == "payable", + "stateMutability": method_abi["stateMutability"], + "type": "function", + } + + return Call( + contract.address, + encoded_call.method_id, + method_name, + inputs, + properties, + method_abi["outputs"], + ) + + +def get_func_input(input_abi: dict, value: Any) -> FuncInput: + return FuncInput(input_abi["name"], input_abi.get("internalType", input_abi.get("type")), value) From 650979513629298772c20a5e3cdfd66c1b4f2253 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Thu, 13 Feb 2025 18:46:56 +0400 Subject: [PATCH 26/29] Fix decoding for the proxy method calls --- utils/evm_script.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/utils/evm_script.py b/utils/evm_script.py index 3421b0e43..33ab12554 100644 --- a/utils/evm_script.py +++ b/utils/evm_script.py @@ -5,7 +5,7 @@ from typing import List, Union, Optional, Callable, Any import eth_abi -from brownie import Contract +from brownie import Contract, convert from brownie.utils import color from eth_typing.evm import HexAddress from web3 import Web3 @@ -169,11 +169,19 @@ def decode_encoded_call(encoded_call: EncodedCall) -> Optional[Call]: """ contract = Contract(encoded_call.address) - # If the method selector is not found in the locally stored contract, try fetching the full ABI from Etherscan. + # If the method selector is not found in the locally stored contracts, fetch the full ABI from Etherscan. if encoded_call.method_id not in contract.selectors: + # For proxy contracts, Brownie automatically retrieves the implementation ABI. contract = Contract.from_explorer(encoded_call.address) - # If the method is still not found, the contract may not be verified. + # If the method selector is still not found, the call may target the proxy contract directly rather than its implementation. + if encoded_call.method_id not in contract.selectors: + # Explicitly fetch the ABI for the proxy contract itself by setting `as_proxy_for` to the proxy's address. + # NOTE: Normalization via `convert.to_address()` is required; without it, the internal check in `from_explorer()` may fail, + # resulting in the implementation's ABI being downloaded instead. + contract = Contract.from_explorer(encoded_call.address, as_proxy_for=convert.to_address(encoded_call.address)) + + # If the method selector is still not found, the contract is likely not verified. if encoded_call.method_id not in contract.selectors: return None From eba72c37b26e943791d448fc675d0319216b60fe Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Fri, 14 Feb 2025 12:30:04 +0300 Subject: [PATCH 27/29] wip: dry-run #3 --- interfaces/DualGovernanceLaunchVerifier.json | 1 + scripts/dual_governance_upgrade_holesky.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 interfaces/DualGovernanceLaunchVerifier.json diff --git a/interfaces/DualGovernanceLaunchVerifier.json b/interfaces/DualGovernanceLaunchVerifier.json new file mode 100644 index 000000000..742fcaae2 --- /dev/null +++ b/interfaces/DualGovernanceLaunchVerifier.json @@ -0,0 +1 @@ +[{"inputs":[{"components":[{"internalType":"address","name":"timelock","type":"address"},{"internalType":"address","name":"dualGovernance","type":"address"},{"internalType":"address","name":"emergencyGovernance","type":"address"},{"internalType":"address","name":"emergencyActivationCommittee","type":"address"},{"internalType":"address","name":"emergencyExecutionCommittee","type":"address"},{"internalType":"Timestamp","name":"emergencyProtectionEndDate","type":"uint40"},{"internalType":"Duration","name":"emergencyModeDuration","type":"uint32"},{"internalType":"uint256","name":"proposalsCount","type":"uint256"}],"internalType":"struct DGLaunchVerifier.ConstructorParams","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EmergencyModeEnabledAfterLaunch","type":"error"},{"inputs":[{"internalType":"string","name":"paramName","type":"string"},{"internalType":"address","name":"expectedValue","type":"address"},{"internalType":"address","name":"actualValue","type":"address"}],"name":"InvalidDGLaunchConfigAddress","type":"error"},{"inputs":[{"internalType":"string","name":"paramName","type":"string"},{"internalType":"uint256","name":"expectedValue","type":"uint256"},{"internalType":"uint256","name":"actualValue","type":"uint256"}],"name":"InvalidDGLaunchConfigParameter","type":"error"},{"anonymous":false,"inputs":[],"name":"DGLaunchConfigurationValidated","type":"event"},{"inputs":[],"name":"DUAL_GOVERNANCE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_ACTIVATION_COMMITTEE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_EXECUTION_COMMITTEE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_GOVERNANCE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_MODE_DURATION","outputs":[{"internalType":"Duration","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EMERGENCY_PROTECTION_END_DATE","outputs":[{"internalType":"Timestamp","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROPOSALS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TIMELOCK","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"verify","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 12d34ddc5..71a51d827 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -30,20 +30,21 @@ def get_vote_items(): foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") roles_validator = interface.RolesValidator("0x0F8826a574BCFDC4997939076f6D82877971feB3") + launch_verifier = interface.DualGovernanceLaunchVerifier("0xfEcF4634f6571da23C8F21bEEeA8D12788df529e") return zip( - ( - "Revoke permission for STAKING_CONTROL_ROLE from Voting contract.", - encode_permission_revoke( - target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", revoke_from=contracts.voting - ), - ), ( "Grant permission for STAKING_CONTROL_ROLE to Agent contract.", encode_permission_grant( target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", grant_to=contracts.agent ), ), + ( + "Revoke permission for STAKING_CONTROL_ROLE from Voting contract.", + encode_permission_revoke( + target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", revoke_from=contracts.voting + ), + ), ( "Change permission manager for Lido STAKING_CONTROL_ROLE.", encode_permission_set_manager(contracts.lido, "STAKING_CONTROL_ROLE", contracts.agent), @@ -119,6 +120,13 @@ def get_vote_items(): ), ) ), + ( + "Verify dual governance", + ( + launch_verifier.address, + launch_verifier.verify.encode_input(), + ) + ), ( "Submit first dual governance proposal", ( From 30c6254db5236bfba8aee59a7020e0ef391e6e79 Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Mon, 17 Feb 2025 13:54:03 +0300 Subject: [PATCH 28/29] feat: dry-run #3 and dual governance forward improvements --- configs/config_holesky.py | 4 +-- scripts/dual_governance_upgrade_holesky.py | 42 ++++++++++++---------- utils/agent.py | 10 +++--- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/configs/config_holesky.py b/configs/config_holesky.py index ed7527b98..b118e7bf9 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -49,8 +49,8 @@ WITHDRAWAL_VAULT = "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" # Dual Governance -DUAL_GOVERNANCE = "0xb291a7f092D5cCE0A3C93eA21Bda3431129dB202" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0xD5EE9991f44b36E186A658dc2A0357EcCf11b69B" +DUAL_GOVERNANCE = "0xE29D4d0CAD66D87a054b5A93867C708000DaE1E6" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed" TIME_CONSTRAINTS = "0x3db5ABA48123bb8789f6f09ec714e7082Bc26747" # EasyTracks diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 71a51d827..61951ed30 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -2,7 +2,7 @@ from typing import Dict from brownie import interface -from utils.agent import agent_forward, dual_governance_agent_forward +from utils.agent import agent_forward, dual_governance_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description from utils.config import ( @@ -22,9 +22,9 @@ description = "Holesky dual governance upgrade dry-run" dual_governance_contracts = { - "dualGovernance": "0xb291a7f092D5cCE0A3C93eA21Bda3431129dB202", - "adminExecutor": "0xD5EE9991f44b36E186A658dc2A0357EcCf11b69B", - "resealManager": "0xc2764655e3fe0bd2D3C710D74Fa5a89162099FD8", + "dualGovernance": "0xE29D4d0CAD66D87a054b5A93867C708000DaE1E6", + "adminExecutor": "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed", + "resealManager": "0x517C93bb27aD463FE3AD8f15DaFDAD56EC0bEeC3", } def get_vote_items(): @@ -121,27 +121,31 @@ def get_vote_items(): ) ), ( - "Verify dual governance", + "Submit dual governance proposal", ( - launch_verifier.address, - launch_verifier.verify.encode_input(), + dual_governance_forward( + [ + agent_forward( + [( + foo_contract.address, + foo_contract.bar.encode_input() + )] + ), + ( + contracts.time_constraints.address, + contracts.time_constraints.checkExecuteWithinDayTime.encode_input(28800, 72000) + ) + ] + ) ) ), ( - "Submit first dual governance proposal", + "Verify dual governance", ( - dual_governance_agent_forward( - [( - foo_contract.address, - foo_contract.bar.encode_input() - ), - ( - contracts.time_constraints.address, - contracts.time_constraints.checkExecuteWithinDayTime.encode_input(28800, 72000) - )] - ) + launch_verifier.address, + launch_verifier.verify.encode_input(), ) - ) + ), ) def start_vote(tx_params: Dict[str, str], silent: bool = False): diff --git a/utils/agent.py b/utils/agent.py index f89ec9262..941aedc2d 100644 --- a/utils/agent.py +++ b/utils/agent.py @@ -15,17 +15,19 @@ def agent_forward(call_script: Sequence[Tuple[str, str]]) -> Tuple[str, str]: return (AGENT, agent.forward.encode_input(encode_call_script(call_script))) - def agent_execute(target: str, value: str, data: str) -> Tuple[str, str]: agent = contracts.agent return (AGENT, agent.execute.encode_input(target, value, data)) -def dual_governance_agent_forward( +def dual_governance_forward( call_script: Sequence[Tuple[str, str]], description: Optional[str] = "", ) -> Tuple[str, str]: dual_governance = contracts.dual_governance - (agent_address, agent_calldata) = agent_forward(call_script) - return (DUAL_GOVERNANCE, dual_governance.submitProposal.encode_input([(agent_address, 0, agent_calldata)], description)) + external_calls = [] + for call in call_script: + external_calls.append((call[0], 0, call[1])) + + return (DUAL_GOVERNANCE, dual_governance.submitProposal.encode_input(external_calls, description)) From 5f15e30c1338ae2b29029bee70e1c7351feaf3db Mon Sep 17 00:00:00 2001 From: Roman Kolpakov Date: Thu, 20 Feb 2025 18:07:54 +0300 Subject: [PATCH 29/29] feat: wip --- configs/config_holesky.py | 5 - configs/config_mainnet.py | 5 - interfaces/VotingContract.json | 97 ++++++++++ scripts/dual_governance_downgrade_holesky.py | 10 +- scripts/dual_governance_upgrade_holesky.py | 169 ++---------------- .../dual_governance_downgrade_holesky_test.py | 22 +-- tests/dual_governance_upgrade_holesky_test.py | 31 ++-- utils/agent.py | 16 +- utils/config.py | 12 -- 9 files changed, 141 insertions(+), 226 deletions(-) create mode 100644 interfaces/VotingContract.json diff --git a/configs/config_holesky.py b/configs/config_holesky.py index b118e7bf9..0948c29ee 100644 --- a/configs/config_holesky.py +++ b/configs/config_holesky.py @@ -48,11 +48,6 @@ WITHDRAWAL_VAULT = "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" -# Dual Governance -DUAL_GOVERNANCE = "0xE29D4d0CAD66D87a054b5A93867C708000DaE1E6" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed" -TIME_CONSTRAINTS = "0x3db5ABA48123bb8789f6f09ec714e7082Bc26747" - # EasyTracks EASYTRACK = "0x1763b9ED3586B08AE796c7787811a2E1bc16163a" diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 6699804b1..df60bbe60 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -54,11 +54,6 @@ DEPOSIT_SECURITY_MODULE_V1 = "0x710B3303fB508a84F10793c1106e32bE873C24cd" WITHDRAWAL_VAULT = "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f" -# Dual Governance -DUAL_GOVERNANCE = "0x0000000000000000000000000000000000000000" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x0000000000000000000000000000000000000000" -TIME_CONSTRAINTS = "0x0000000000000000000000000000000000000000" - # EasyTracks EASYTRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" diff --git a/interfaces/VotingContract.json b/interfaces/VotingContract.json new file mode 100644 index 000000000..95c9b0e67 --- /dev/null +++ b/interfaces/VotingContract.json @@ -0,0 +1,97 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "dualGovernance", + "type": "address" + }, + { + "internalType": "address", + "name": "adminExecutor", + "type": "address" + }, + { + "internalType": "address", + "name": "resealManager", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "DurationOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "getEVMCallScript", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVoteItems", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct EvmScriptUtils.EvmScriptCall", + "name": "call", + "type": "tuple" + } + ], + "internalType": "struct Voting.VoteItem[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "voteId", + "type": "uint256" + } + ], + "name": "validateVote", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/scripts/dual_governance_downgrade_holesky.py b/scripts/dual_governance_downgrade_holesky.py index 5b1acb737..f5dac92ed 100644 --- a/scripts/dual_governance_downgrade_holesky.py +++ b/scripts/dual_governance_downgrade_holesky.py @@ -17,10 +17,12 @@ encode_permission_grant, ) from utils.mainnet_fork import pass_and_exec_dao_vote -from scripts.dual_governance_upgrade_holesky import dual_governance_contracts description = "Holesky dual governance downgrade dry-run" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed" +RESEAL_MANAGER = "0x517C93bb27aD463FE3AD8f15DaFDAD56EC0bEeC3" + def start_vote(tx_params: Dict[str, str], silent: bool = False): vote_desc_items, call_script_items = zip( ( @@ -51,7 +53,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ( contracts.withdrawal_queue.address, contracts.withdrawal_queue.revokeRole.encode_input( - contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"] + contracts.withdrawal_queue.PAUSE_ROLE(), RESEAL_MANAGER ), ) ] @@ -66,7 +68,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): ( contracts.withdrawal_queue.address, contracts.withdrawal_queue.revokeRole.encode_input( - contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"] + contracts.withdrawal_queue.RESUME_ROLE(), RESEAL_MANAGER ), ) ] @@ -96,7 +98,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): encode_permission_revoke( target_app=contracts.agent, permission_name="RUN_SCRIPT_ROLE", - revoke_from=contracts.dual_governance_admin_executor, + revoke_from=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ), ), ) diff --git a/scripts/dual_governance_upgrade_holesky.py b/scripts/dual_governance_upgrade_holesky.py index 61951ed30..c4438599e 100644 --- a/scripts/dual_governance_upgrade_holesky.py +++ b/scripts/dual_governance_upgrade_holesky.py @@ -2,154 +2,31 @@ from typing import Dict from brownie import interface -from utils.agent import agent_forward, dual_governance_forward from utils.voting import bake_vote_items, confirm_vote_script, create_vote from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description from utils.config import ( - contracts, get_deployer_account, get_is_live, get_priority_fee, ) -from utils.permissions import ( - encode_permission_set_manager, - encode_permission_revoke, - encode_permission_grant, -) from utils.mainnet_fork import pass_and_exec_dao_vote -from utils.evm_script import encode_call_script +voting_contract = "0xD62837Cc18FB25791B9Cc51B1862bb8e06004204" description = "Holesky dual governance upgrade dry-run" -dual_governance_contracts = { - "dualGovernance": "0xE29D4d0CAD66D87a054b5A93867C708000DaE1E6", - "adminExecutor": "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed", - "resealManager": "0x517C93bb27aD463FE3AD8f15DaFDAD56EC0bEeC3", -} - def get_vote_items(): - foo_contract = interface.Foo("0xC3fc22C7e0d20247B797fb6dc743BD3879217c81") - roles_validator = interface.RolesValidator("0x0F8826a574BCFDC4997939076f6D82877971feB3") - launch_verifier = interface.DualGovernanceLaunchVerifier("0xfEcF4634f6571da23C8F21bEEeA8D12788df529e") - - return zip( - ( - "Grant permission for STAKING_CONTROL_ROLE to Agent contract.", - encode_permission_grant( - target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", grant_to=contracts.agent - ), - ), - ( - "Revoke permission for STAKING_CONTROL_ROLE from Voting contract.", - encode_permission_revoke( - target_app=contracts.lido, permission_name="STAKING_CONTROL_ROLE", revoke_from=contracts.voting - ), - ), - ( - "Change permission manager for Lido STAKING_CONTROL_ROLE.", - encode_permission_set_manager(contracts.lido, "STAKING_CONTROL_ROLE", contracts.agent), - ), - ( - "Grant WithdrawalQueue PAUSE_ROLE to Reseal Manager.", - ( - agent_forward( - [ - ( - contracts.withdrawal_queue.address, - contracts.withdrawal_queue.grantRole.encode_input( - contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"] - ), - ) - ] - ) - ), - ), - ( - "Grant WithdrawalQueue RESUME_ROLE to Reseal Manager.", - ( - agent_forward( - [ - ( - contracts.withdrawal_queue.address, - contracts.withdrawal_queue.grantRole.encode_input( - contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"] - ), - ) - ] - ) - ), - ), - ( - "Grant AllowedTokensRegistry DEFAULT_ADMIN_ROLE to Voting.", - ( - agent_forward( - [ - ( - contracts.allowed_tokens_registry.address, - contracts.allowed_tokens_registry.grantRole.encode_input( - contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting - ), - ) - ] - ) - ), - ), - ( - "Revoke AllowedTokensRegistry DEFAULT_ADMIN_ROLE from Agent.", - ( - contracts.allowed_tokens_registry.address, - contracts.allowed_tokens_registry.revokeRole.encode_input( - contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent - ), - ), - ), - ( - "Grant permission for RUN_SCRIPT_ROLE to DG Executor contract.", - encode_permission_grant( - target_app=contracts.agent, - permission_name="RUN_SCRIPT_ROLE", - grant_to=dual_governance_contracts["adminExecutor"], - ), - ), - ( - "Validate transferred roles", - ( - roles_validator.address, - roles_validator.validate.encode_input( - dual_governance_contracts['adminExecutor'], dual_governance_contracts['resealManager'] - ), - ) - ), - ( - "Submit dual governance proposal", - ( - dual_governance_forward( - [ - agent_forward( - [( - foo_contract.address, - foo_contract.bar.encode_input() - )] - ), - ( - contracts.time_constraints.address, - contracts.time_constraints.checkExecuteWithinDayTime.encode_input(28800, 72000) - ) - ] - ) - ) - ), - ( - "Verify dual governance", - ( - launch_verifier.address, - launch_verifier.verify.encode_input(), - ) - ), - ) + voting_items = interface.VotingContract(voting_contract).getVoteItems() -def start_vote(tx_params: Dict[str, str], silent: bool = False): + vote_desc_items = [] + call_script_items = [] + for desc, call_script in voting_items: + vote_desc_items.append(desc) + call_script_items.append((call_script[0], call_script[1].hex())) + + return vote_desc_items, call_script_items + +def start_vote(tx_params: Dict[str, str], silent: bool = False): vote_desc_items, call_script_items = get_vote_items() vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) @@ -170,6 +47,8 @@ def main(): vote_id, _ = start_vote(tx_params=tx_params, silent=False) + assert interface.VotingContract(voting_contract).validateVote(vote_id) + vote_id >= 0 and print(f"Vote created: {vote_id}.") time.sleep(5) # hack for waiting thread #2. @@ -186,25 +65,3 @@ def start_and_execute_vote_on_fork(): print(f"Vote created: {vote_id}.") pass_and_exec_dao_vote(int(vote_id)) - - -def get_voting_calldata(): - vote_desc_items, call_script_items = get_vote_items() - vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) - evm_script = encode_call_script(vote_items.values()) - - new_vote_script = encode_call_script( - [ - ( - contracts.voting.address, - contracts.voting.newVote.encode_input( - evm_script, - description, - False, - False - ), - ) - ] - ) - - print(new_vote_script) \ No newline at end of file diff --git a/tests/dual_governance_downgrade_holesky_test.py b/tests/dual_governance_downgrade_holesky_test.py index 897a61705..5b5998979 100644 --- a/tests/dual_governance_downgrade_holesky_test.py +++ b/tests/dual_governance_downgrade_holesky_test.py @@ -1,16 +1,12 @@ -from scripts.dual_governance_downgrade_holesky import start_vote, dual_governance_contracts +from scripts.dual_governance_downgrade_holesky import start_vote from utils.config import contracts from utils.test.tx_tracing_helpers import * from brownie.network.transaction import TransactionReceipt from utils.config import contracts from brownie.network.account import Account -try: - from brownie import interface, chain -except ImportError: - print( - "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" - ) +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed" +RESEAL_MANAGER = "0x517C93bb27aD463FE3AD8f15DaFDAD56EC0bEeC3" def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding, stranger: Account): dao_voting = contracts.voting @@ -21,15 +17,15 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert contracts.acl.hasPermission(contracts.agent, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) # Reseal manager - assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) - assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), RESEAL_MANAGER) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), RESEAL_MANAGER) # Allowed tokens registry assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) # Agent - assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(DUAL_GOVERNANCE_ADMIN_EXECUTOR, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) # START VOTE @@ -44,13 +40,13 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert contracts.acl.hasPermission(contracts.voting, contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) # Reseal manager - assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) - assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), RESEAL_MANAGER) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), RESEAL_MANAGER) # Allowed tokens registry assert contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) # Agent - assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert not contracts.acl.hasPermission(DUAL_GOVERNANCE_ADMIN_EXECUTOR, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) diff --git a/tests/dual_governance_upgrade_holesky_test.py b/tests/dual_governance_upgrade_holesky_test.py index eefa0b120..f131c702e 100644 --- a/tests/dual_governance_upgrade_holesky_test.py +++ b/tests/dual_governance_upgrade_holesky_test.py @@ -1,20 +1,19 @@ -from scripts.dual_governance_upgrade_holesky import start_vote, dual_governance_contracts +from brownie import interface, chain +from scripts.dual_governance_upgrade_holesky import start_vote from utils.config import contracts from utils.test.tx_tracing_helpers import * from brownie.network.transaction import TransactionReceipt from utils.config import contracts from brownie.network.account import Account -try: - from brownie import interface, chain -except ImportError: - print( - "You're probably running inside Brownie console. " "Please call:\n" "set_console_globals(interface=interface)" - ) +DUAL_GOVERNANCE = "0xE29D4d0CAD66D87a054b5A93867C708000DaE1E6" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x3Cc908B004422fd66FdB40Be062Bf9B0bd5BDbed" +RESEAL_MANAGER = "0x517C93bb27aD463FE3AD8f15DaFDAD56EC0bEeC3" -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_decoding, stranger: Account): +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger: Account): dao_voting = contracts.voting - timelock = interface.EmergencyProtectedTimelock(contracts.dual_governance.TIMELOCK()) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + timelock = interface.EmergencyProtectedTimelock(dual_governance.TIMELOCK()) # Lido assert contracts.acl.getPermissionManager(contracts.lido, contracts.lido.STAKING_CONTROL_ROLE()) == contracts.voting @@ -25,12 +24,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.voting) # Agent - assert not contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert not contracts.acl.hasPermission(DUAL_GOVERNANCE_ADMIN_EXECUTOR, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) # Reseal manager - assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) - assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), RESEAL_MANAGER) + assert not contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), RESEAL_MANAGER) # START VOTE vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] @@ -49,19 +48,19 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, bypass_events_de assert not contracts.allowed_tokens_registry.hasRole(contracts.allowed_tokens_registry.DEFAULT_ADMIN_ROLE(), contracts.agent) # Agent - assert contracts.acl.hasPermission(contracts.dual_governance_admin_executor, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) + assert contracts.acl.hasPermission(DUAL_GOVERNANCE_ADMIN_EXECUTOR, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) assert contracts.acl.hasPermission(contracts.voting, contracts.agent, contracts.agent.RUN_SCRIPT_ROLE()) # Reseal manager - assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), dual_governance_contracts["resealManager"]) - assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), dual_governance_contracts["resealManager"]) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.PAUSE_ROLE(), RESEAL_MANAGER) + assert contracts.withdrawal_queue.hasRole(contracts.withdrawal_queue.RESUME_ROLE(), RESEAL_MANAGER) proposal_id = timelock.getProposalsCount() # while not contracts.dual_governance.canScheduleProposal(proposal_id): chain.sleep(60 * 24) - contracts.dual_governance.scheduleProposal(proposal_id, {"from": stranger}) + dual_governance.scheduleProposal(proposal_id, {"from": stranger}) # while not timelock.canExecute(proposal_id): chain.sleep(60 * 24) diff --git a/utils/agent.py b/utils/agent.py index 941aedc2d..c7f803bcb 100644 --- a/utils/agent.py +++ b/utils/agent.py @@ -1,11 +1,10 @@ from utils.config import contracts -from utils.config import AGENT, DUAL_GOVERNANCE +from utils.config import AGENT from utils.evm_script import ( encode_call_script, ) from typing import ( Tuple, - Optional, Sequence, ) @@ -18,16 +17,3 @@ def agent_forward(call_script: Sequence[Tuple[str, str]]) -> Tuple[str, str]: def agent_execute(target: str, value: str, data: str) -> Tuple[str, str]: agent = contracts.agent return (AGENT, agent.execute.encode_input(target, value, data)) - - -def dual_governance_forward( - call_script: Sequence[Tuple[str, str]], - description: Optional[str] = "", -) -> Tuple[str, str]: - dual_governance = contracts.dual_governance - - external_calls = [] - for call in call_script: - external_calls.append((call[0], 0, call[1])) - - return (DUAL_GOVERNANCE, dual_governance.submitProposal.encode_input(external_calls, description)) diff --git a/utils/config.py b/utils/config.py index aa7007d0a..28f991187 100644 --- a/utils/config.py +++ b/utils/config.py @@ -395,18 +395,6 @@ def token_rate_notifier(self) -> interface.TokenRateNotifier: def allowed_tokens_registry(self) -> interface.AllowedTokensRegistry: return interface.AllowedTokensRegistry(EASYTRACK_ALLOWED_TOKENS_REGISTRY) - @property - def dual_governance(self) -> interface.DualGovernance: - return interface.DualGovernance(DUAL_GOVERNANCE) - - @property - def dual_governance_admin_executor(self) -> interface.DualGovernanceAdminExecutor: - return interface.DualGovernanceAdminExecutor(DUAL_GOVERNANCE_ADMIN_EXECUTOR) - - @property - def time_constraints(self) -> interface.TimeConstraints: - return interface.TimeConstraints(TIME_CONSTRAINTS) - def __getattr__(name: str) -> Any: if name == "contracts": return ContractsLazyLoader()