Skip to content

Commit ddc3f82

Browse files
committed
Vote 2026/01/26 Hoodi
1 parent a760cad commit ddc3f82

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

scripts/vote_2026_01_26_hoodi.py

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 0x031624fAD4E9BFC2524e7a87336C4b190E70BCA8 to 0xc8195bb2851d7129D9100af9d65Bd448A6dE11eF on Hoodi
5+
6+
# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}.
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 0x031624fAD4E9BFC2524e7a87336C4b190E70BCA8 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 0x031624fAD4E9BFC2524e7a87336C4b190E70BCA8 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)

tests/test_2026_01_26_hoodi.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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 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+
29+
EXPECTED_VOTE_ID = 56
30+
EXPECTED_VOTE_EVENTS_COUNT = 1
31+
IPFS_DESCRIPTION_HASH = "bafkreies4yycczkmfgwexirpnogbzlo7j262svtrwcj5k2x7ashyvhnaqm"
32+
33+
34+
def test_vote_acceptance(helpers, accounts, ldo_holder, vote_ids_from_env):
35+
voting = interface.Voting(VOTING)
36+
no = interface.NodeOperatorsRegistry(TARGET_NO_REGISTRY)
37+
38+
# =========================================================================
39+
# ======================== Identify or Create vote ========================
40+
# =========================================================================
41+
if vote_ids_from_env:
42+
vote_id = vote_ids_from_env[0]
43+
assert vote_id == EXPECTED_VOTE_ID
44+
elif voting.votesLength() > EXPECTED_VOTE_ID:
45+
vote_id = EXPECTED_VOTE_ID
46+
else:
47+
vote_id, _ = start_vote({"from": ldo_holder}, silent=True)
48+
49+
_, call_script_items = get_vote_items()
50+
onchain_script = voting.getVote(vote_id)["script"]
51+
assert str(onchain_script).lower() == encode_call_script(call_script_items).lower()
52+
53+
perm_param = Param(0, Op.EQ, ArgumentValue(OPERATOR_ID))
54+
perm_param_uint = perm_param.to_uint256()
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 not no.canPerform(NEW_MANAGER_ADDRESS, MANAGE_SIGNING_KEYS, [perm_param_uint])
67+
# scenario reassurance tests
68+
add_signing_keys_fails_before_vote(accounts)
69+
70+
assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH
71+
72+
vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting)
73+
display_voting_events(vote_tx)
74+
vote_events = group_voting_events_from_receipt(vote_tx)
75+
76+
# =====================================================================
77+
# ========================= After voting checks =======================
78+
# =====================================================================
79+
80+
# Item 1
81+
assert no.canPerform(NEW_MANAGER_ADDRESS, MANAGE_SIGNING_KEYS, [perm_param_uint])
82+
83+
assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT
84+
assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT
85+
86+
# events check
87+
permission = Permission(entity=NEW_MANAGER_ADDRESS, app=no, role=MANAGE_SIGNING_KEYS)
88+
validate_permission_grantp_event(vote_events[0], permission, [perm_param], emitted_by=ACL)
89+
90+
# scenario happy path tests
91+
manager_adds_signing_keys(accounts)
92+
add_signing_keys_to_notallowed_operator_fails(accounts)
93+
94+
95+
def manager_adds_signing_keys(accounts):
96+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
97+
98+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
99+
set_balance(manager, 10)
100+
101+
total_keys_before = no.getTotalSigningKeyCount(OPERATOR_ID)
102+
pubkeys = random_pubkeys_batch(1)
103+
signatures = random_signatures_batch(1)
104+
105+
no.addSigningKeys(
106+
OPERATOR_ID,
107+
1,
108+
pubkeys,
109+
signatures,
110+
{"from": manager},
111+
)
112+
113+
total_keys_after = no.getTotalSigningKeyCount(OPERATOR_ID)
114+
assert total_keys_after == total_keys_before + 1
115+
116+
117+
def add_signing_keys_fails_before_vote(accounts):
118+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
119+
120+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
121+
set_balance(manager, 10)
122+
123+
pubkeys = random_pubkeys_batch(1)
124+
signatures = random_signatures_batch(1)
125+
126+
with reverts():
127+
no.addSigningKeys(
128+
OPERATOR_ID,
129+
1,
130+
pubkeys,
131+
signatures,
132+
{"from": manager},
133+
)
134+
135+
136+
def add_signing_keys_to_notallowed_operator_fails(accounts):
137+
no = interface.SimpleDVT(TARGET_NO_REGISTRY)
138+
139+
manager = accounts.at(NEW_MANAGER_ADDRESS, force=True)
140+
set_balance(manager, 10)
141+
142+
pubkeys = random_pubkeys_batch(1)
143+
signatures = random_signatures_batch(1)
144+
145+
with reverts():
146+
no.addSigningKeys(
147+
2, # NO id 2 - not allowed
148+
1,
149+
pubkeys,
150+
signatures,
151+
{"from": manager},
152+
)

0 commit comments

Comments
 (0)