Skip to content

Commit 115bb38

Browse files
authored
Merge pull request #590 from lidofinance/vote-2026-01-26-hoodi
Vote 2026/01/26 Hoodi
2 parents 3a25164 + d40219c commit 115bb38

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""
2+
Voting 26/01/2026. Hoodi network.
3+
4+
1. Grant MANAGE_SIGNING_KEYS role for operator ID = 1 to 0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF on Hoodi
5+
6+
Vote #56 passed & executed on Jan-26-2026 12:52:36 PM UTC, block 2108630.
7+
"""
8+
9+
from typing import Dict, List, Tuple
10+
11+
from utils.voting import bake_vote_items, confirm_vote_script, create_vote
12+
from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description
13+
from utils.config import get_deployer_account, get_is_live, get_priority_fee
14+
from utils.permissions import encode_permission_grant_p
15+
from utils.permission_parameters import Param, Op, ArgumentValue
16+
from utils.mainnet_fork import pass_and_exec_dao_vote
17+
18+
19+
# ============================== Addresses ===================================
20+
NEW_MANAGER_ADDRESS = "0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF"
21+
TARGET_NO_REGISTRY = "0x682E94d2630846a503BDeE8b6810DF71C9806891"
22+
OPERATOR_ID = 1
23+
24+
25+
# ============================= Description ==================================
26+
IPFS_DESCRIPTION = "Grant MANAGE_SIGNING_KEYS role for operator ID = 1 to 0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF on Hoodi"
27+
28+
29+
def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]:
30+
params = [Param(0, Op.EQ, ArgumentValue(OPERATOR_ID))]
31+
32+
vote_desc_items, call_script_items = zip(
33+
(
34+
"1. Grant MANAGE_SIGNING_KEYS role for operator ID = 1 to 0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF on Hoodi",
35+
encode_permission_grant_p(
36+
target_app=TARGET_NO_REGISTRY,
37+
permission_name="MANAGE_SIGNING_KEYS",
38+
grant_to=NEW_MANAGER_ADDRESS,
39+
params=params,
40+
),
41+
),
42+
)
43+
44+
return vote_desc_items, call_script_items
45+
46+
47+
def start_vote(tx_params: Dict[str, str], silent: bool = False):
48+
vote_desc_items, call_script_items = get_vote_items()
49+
vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items))
50+
51+
desc_ipfs = (
52+
calculate_vote_ipfs_description(IPFS_DESCRIPTION)
53+
if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION)
54+
)
55+
56+
vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list(
57+
create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs)
58+
)
59+
60+
return vote_id, tx
61+
62+
63+
def main():
64+
tx_params: Dict[str, str] = {"from": get_deployer_account().address}
65+
if get_is_live():
66+
tx_params["priority_fee"] = get_priority_fee()
67+
68+
vote_id, _ = start_vote(tx_params=tx_params, silent=False)
69+
vote_id >= 0 and print(f"Vote created: {vote_id}.")
70+
71+
72+
def start_and_execute_vote_on_fork_manual():
73+
if get_is_live():
74+
raise Exception("This script is for local testing only.")
75+
76+
tx_params = {"from": get_deployer_account()}
77+
vote_id, _ = start_vote(tx_params=tx_params, silent=True)
78+
print(f"Vote created: {vote_id}.")
79+
pass_and_exec_dao_vote(int(vote_id), step_by_step=True)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from brownie import interface, web3, reverts
2+
from brownie.network.transaction import TransactionReceipt
3+
from utils.evm_script import encode_call_script
4+
from utils.ipfs import get_lido_vote_cid_from_str
5+
from utils.permission_parameters import Param, Op, ArgumentValue
6+
from utils.test.event_validators.permission import Permission, validate_permission_grantp_event
7+
from utils.test.keys_helpers import random_pubkeys_batch, random_signatures_batch
8+
from utils.test.tx_tracing_helpers import (
9+
count_vote_items_by_events,
10+
display_voting_events,
11+
group_voting_events_from_receipt,
12+
)
13+
from utils.balance import set_balance
14+
from utils.voting import find_metadata_by_vote_id
15+
16+
from archive.scripts.vote_2026_01_26_hoodi import (
17+
start_vote,
18+
get_vote_items,
19+
)
20+
21+
22+
VOTING = "0x49B3512c44891bef83F8967d075121Bd1b07a01B"
23+
TARGET_NO_REGISTRY = "0x682E94d2630846a503BDeE8b6810DF71C9806891"
24+
ACL = "0x78780e70Eae33e2935814a327f7dB6c01136cc62"
25+
NEW_MANAGER_ADDRESS = "0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF"
26+
MANAGE_SIGNING_KEYS = web3.keccak(text="MANAGE_SIGNING_KEYS").hex()
27+
OPERATOR_ID = 1
28+
EXPECTED_REWARD_ADDRESS = "0x031624fAD4E9BFC2524e7a87336C4b190E70BCA8"
29+
30+
EXPECTED_VOTE_ID = 56
31+
EXPECTED_VOTE_EVENTS_COUNT = 1
32+
IPFS_DESCRIPTION_HASH = "bafkreibcmwxupju2hx54awwjh7fpbybkmcxban5v36go4nsnrem2fwipgq"
33+
34+
35+
def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env):
36+
voting = interface.Voting(VOTING)
37+
no = interface.NodeOperatorsRegistry(TARGET_NO_REGISTRY)
38+
perm_param = Param(0, Op.EQ, ArgumentValue(OPERATOR_ID))
39+
perm_param_uint = perm_param.to_uint256()
40+
41+
# =========================================================================
42+
# ======================== Identify or Create vote ========================
43+
# =========================================================================
44+
if vote_ids_from_env:
45+
vote_id = vote_ids_from_env[0]
46+
assert vote_id == EXPECTED_VOTE_ID
47+
elif voting.votesLength() > EXPECTED_VOTE_ID:
48+
vote_id = EXPECTED_VOTE_ID
49+
else:
50+
vote_id, _ = start_vote({"from": ldo_holder}, silent=True)
51+
52+
_, call_script_items = get_vote_items()
53+
onchain_script = voting.getVote(vote_id)["script"]
54+
assert str(onchain_script).lower() == encode_call_script(call_script_items).lower()
55+
56+
# =========================================================================
57+
# ============================= Execute Vote ==============================
58+
# =========================================================================
59+
is_executed = voting.getVote(vote_id)["executed"]
60+
if not is_executed:
61+
# =====================================================================
62+
# ========================= Before voting checks ======================
63+
# =====================================================================
64+
65+
# Item 1
66+
assert no.getNodeOperator(OPERATOR_ID, True)["rewardAddress"] == EXPECTED_REWARD_ADDRESS
67+
assert not no.canPerform(NEW_MANAGER_ADDRESS, MANAGE_SIGNING_KEYS, [perm_param_uint])
68+
# scenario test
69+
add_signing_keys_fails_before_vote(accounts)
70+
71+
assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH
72+
73+
vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting)
74+
display_voting_events(vote_tx)
75+
vote_events = group_voting_events_from_receipt(vote_tx)
76+
77+
# =====================================================================
78+
# ========================= After voting checks =======================
79+
# =====================================================================
80+
81+
# Item 1
82+
assert no.canPerform(NEW_MANAGER_ADDRESS, MANAGE_SIGNING_KEYS, [perm_param_uint])
83+
84+
assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT
85+
assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT
86+
87+
# events check
88+
permission = Permission(entity=NEW_MANAGER_ADDRESS, app=no, role=MANAGE_SIGNING_KEYS)
89+
validate_permission_grantp_event(vote_events[0], permission, [perm_param], emitted_by=ACL)
90+
91+
# scenario tests
92+
manager_adds_signing_keys(accounts)
93+
add_signing_keys_to_notallowed_operator_fails(accounts)
94+
95+
96+
def add_signing_keys_fails_before_vote(accounts):
97+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
98+
99+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
100+
set_balance(manager, 10)
101+
102+
pubkeys = random_pubkeys_batch(1)
103+
signatures = random_signatures_batch(1)
104+
105+
with reverts():
106+
no.addSigningKeys(
107+
OPERATOR_ID,
108+
1,
109+
pubkeys,
110+
signatures,
111+
{"from": manager},
112+
)
113+
114+
115+
def manager_adds_signing_keys(accounts):
116+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
117+
118+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
119+
set_balance(manager, 10)
120+
121+
total_keys_before = no.getTotalSigningKeyCount(OPERATOR_ID)
122+
pubkeys = random_pubkeys_batch(1)
123+
signatures = random_signatures_batch(1)
124+
125+
no.addSigningKeys(
126+
OPERATOR_ID,
127+
1,
128+
pubkeys,
129+
signatures,
130+
{"from": manager},
131+
)
132+
133+
total_keys_after = no.getTotalSigningKeyCount(OPERATOR_ID)
134+
assert total_keys_after == total_keys_before + 1
135+
136+
137+
def add_signing_keys_to_notallowed_operator_fails(accounts):
138+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
139+
140+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
141+
set_balance(manager, 10)
142+
143+
pubkeys = random_pubkeys_batch(1)
144+
signatures = random_signatures_batch(1)
145+
146+
with reverts():
147+
no.addSigningKeys(
148+
2, # NO id 2 - not allowed
149+
1,
150+
pubkeys,
151+
signatures,
152+
{"from": manager},
153+
)

0 commit comments

Comments
 (0)